feat(my-joy-beta): sort and actions
This commit is contained in:
parent
3135f3b5a7
commit
dd32058c9d
@ -3,6 +3,7 @@
|
||||
"rules": {
|
||||
"no-console": 0,
|
||||
"new-cap": 0,
|
||||
"camelcase": 1,
|
||||
// temp
|
||||
"no-undef": 1,
|
||||
"no-debugger": 1,
|
||||
|
@ -16,11 +16,13 @@
|
||||
"prepublish": "echo 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manaflair/redux-batch": "^0.1.0",
|
||||
"apollo": "^0.2.2",
|
||||
"joyent-ui-toolkit": "^2.0.0",
|
||||
"lodash.find": "^4.6.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.sortby": "^4.7.0",
|
||||
"lunr": "^2.1.3",
|
||||
"normalized-styled-components": "^1.0.14",
|
||||
"param-case": "^2.1.1",
|
||||
|
@ -24,7 +24,7 @@ const stateColor = {
|
||||
FAILED: 'red'
|
||||
};
|
||||
|
||||
export default ({ name, state, primaryIp, last, first }) => (
|
||||
export default ({ name, state, primary_ip, last, first }) => (
|
||||
<Card collapsed flat={!last} topMargin={first} bottomless={!last} gapless>
|
||||
<CardView>
|
||||
<CardMeta>
|
||||
@ -35,7 +35,7 @@ export default ({ name, state, primaryIp, last, first }) => (
|
||||
</CardAction>
|
||||
<CardTitle to={`/instances/${name}`}>{name}</CardTitle>
|
||||
<Small>
|
||||
<CardLabel>{primaryIp}</CardLabel>
|
||||
<CardLabel>{primary_ip}</CardLabel>
|
||||
</Small>
|
||||
<Small>
|
||||
<CardLabel
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from 'react-styled-flexboxgrid';
|
||||
import forceArray from 'force-array';
|
||||
|
||||
import {
|
||||
@ -6,7 +7,8 @@ import {
|
||||
Input,
|
||||
FormLabel,
|
||||
ViewContainer,
|
||||
StatusLoader
|
||||
StatusLoader,
|
||||
Select
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import Item from './item';
|
||||
@ -15,7 +17,9 @@ export default ({
|
||||
instances,
|
||||
loading,
|
||||
handleChange = () => null,
|
||||
handleSubmit
|
||||
onAction = () => null,
|
||||
handleSubmit,
|
||||
...rest
|
||||
}) => {
|
||||
const _instances = forceArray(instances);
|
||||
|
||||
@ -36,11 +40,66 @@ export default ({
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<form onSubmit={() => handleSubmit(ctx => handleChange(ctx))}>
|
||||
<FormGroup name="filter" reduxForm>
|
||||
<FormLabel>Filter instances</FormLabel>
|
||||
<Input />
|
||||
</FormGroup>
|
||||
<form
|
||||
onChange={() => handleSubmit(ctx => handleChange(ctx))}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Row between="xs">
|
||||
<Col xs={10} sm={8} lg={6}>
|
||||
<Row>
|
||||
<Col xs={7} sm={7} md={6} lg={6}>
|
||||
<FormGroup name="filter" reduxForm>
|
||||
<FormLabel>Filter instances</FormLabel>
|
||||
<Input placeholder="Search for name, state, tags, etc..." fluid />
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={5} sm={3} lg={3}>
|
||||
<FormGroup name="sort" reduxForm>
|
||||
<FormLabel>Sort</FormLabel>
|
||||
<Select disabled={!items.length}>
|
||||
<option value="name">Name</option>
|
||||
<option value="state">State</option>
|
||||
<option value="primary_ip">IP</option>
|
||||
<option value="image.name">Image</option>
|
||||
<option value="firewall_enabled">Firewall</option>
|
||||
<option value="created">Created</option>
|
||||
<option value="updated">Updated</option>
|
||||
<option value="brand">Brand</option>
|
||||
<option value="memory">Memory</option>
|
||||
<option value="disk">Disk</option>
|
||||
<option value="package.name">Package</option>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col xs={2} sm={4} lg={6}>
|
||||
<Row end="xs">
|
||||
<Col xs={12} sm={3} md={3} lg={2}>
|
||||
<FormGroup>
|
||||
<FormLabel>⁣</FormLabel>
|
||||
<Select
|
||||
value="actions"
|
||||
disabled={!items.length}
|
||||
onChange={({ target }) => onAction(target.value)}
|
||||
>
|
||||
<option value="actions" selected disabled>
|
||||
≡
|
||||
</option>
|
||||
<option value="stop">Stop</option>
|
||||
<option value="start">Start</option>
|
||||
<option value="reboot">Reboot</option>
|
||||
<option value="resize">Resize</option>
|
||||
<option value="enable-fw">Enable Firewall</option>
|
||||
<option value="disable-fw">Disable Firewall</option>
|
||||
<option value="create-snap">Create Snapshot</option>
|
||||
<option value="start-snap">Start from Snapshot</option>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
{_loading}
|
||||
{items}
|
||||
</form>
|
||||
|
@ -32,13 +32,14 @@ const Firewall = ({
|
||||
/>
|
||||
));
|
||||
|
||||
const _error = (error && !values.length && !_loading) ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instance firewall rules"
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
const _error =
|
||||
error && !values.length && !_loading ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instance firewall rules"
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<ViewContainer center={Boolean(_loading)} main>
|
||||
|
@ -2,39 +2,65 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import { connect } from 'react-redux';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import { reduxForm, change } from 'redux-form';
|
||||
import forceArray from 'force-array';
|
||||
import get from 'lodash.get';
|
||||
import sortBy from 'lodash.sortby';
|
||||
import find from 'lodash.find';
|
||||
|
||||
import { ViewContainer, Title, Message } from 'joyent-ui-toolkit';
|
||||
|
||||
import GetInstances from '@graphql/list-instances.gql';
|
||||
import ListInstances from '@graphql/list-instances.gql';
|
||||
import StopInstance from '@graphql/stop-instance.gql';
|
||||
import StartInstance from '@graphql/start-instance.gql';
|
||||
import RebootInstance from '@graphql/reboot-instance.gql';
|
||||
import ResizeInstance from '@graphql/resize-instance.gql';
|
||||
import EnableInstanceFw from '@graphql/enable-instance-fw.gql';
|
||||
import DisableInstanceFw from '@graphql/disable-instance-fw.gql';
|
||||
import CreateSnapshot from '@graphql/create-snapshot.gql';
|
||||
import StartSnapshot from '@graphql/start-from-snapshot.gql';
|
||||
|
||||
import { List as InstanceList } from '@components/instances';
|
||||
import GenIndex from '@state/gen-index';
|
||||
|
||||
const InstanceListForm = reduxForm({
|
||||
form: `instance-list`
|
||||
form: `instance-list`,
|
||||
initialValues: {
|
||||
sort: 'name'
|
||||
}
|
||||
})(InstanceList);
|
||||
|
||||
const List = ({ instances = [], loading = false, error }) => {
|
||||
const List = ({
|
||||
selected = [],
|
||||
instances = [],
|
||||
loading = false,
|
||||
error,
|
||||
onAction
|
||||
}) => {
|
||||
const _title = <Title>Instances</Title>;
|
||||
const _instances = forceArray(instances);
|
||||
const _loading = !instances.length && loading;
|
||||
|
||||
const _error = (error && !_instances.length && !_loading) ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instances."
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
const _error =
|
||||
error && !_instances.length && !_loading ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instances."
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
|
||||
const handleAction = name => onAction({ name, ids: selected });
|
||||
|
||||
return (
|
||||
<ViewContainer main>
|
||||
{_title}
|
||||
{!_loading && _error}
|
||||
<InstanceListForm instances={_instances} loading={loading} />
|
||||
<InstanceListForm
|
||||
instances={_instances}
|
||||
loading={loading}
|
||||
onAction={handleAction}
|
||||
/>
|
||||
</ViewContainer>
|
||||
);
|
||||
};
|
||||
@ -50,7 +76,15 @@ List.propTypes = {
|
||||
};
|
||||
|
||||
export default compose(
|
||||
graphql(GetInstances, {
|
||||
graphql(StopInstance, { name: 'stop' }),
|
||||
graphql(StartInstance, { name: 'start' }),
|
||||
graphql(RebootInstance, { name: 'reboot' }),
|
||||
graphql(ResizeInstance, { name: 'resize' }),
|
||||
graphql(EnableInstanceFw, { name: 'enableFw' }),
|
||||
graphql(DisableInstanceFw, { name: 'disableFw' }),
|
||||
graphql(CreateSnapshot, { name: 'createSnapshot' }),
|
||||
graphql(StartSnapshot, { name: 'startSnapshot' }),
|
||||
graphql(ListInstances, {
|
||||
options: () => ({
|
||||
pollInterval: 1000
|
||||
}),
|
||||
@ -65,15 +99,60 @@ export default compose(
|
||||
};
|
||||
}
|
||||
}),
|
||||
connect((state, ownProps) => {
|
||||
const filter = get(state, 'form.instance-list.values.filter');
|
||||
const { index, instances = [], ...rest } = ownProps;
|
||||
connect(
|
||||
(state, ownProps) => {
|
||||
const { index, instances = [], ...rest } = ownProps;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
instances: !filter
|
||||
? instances
|
||||
: index.search(filter).map(({ ref }) => find(instances, ['id', ref]))
|
||||
};
|
||||
})
|
||||
const form = get(state, 'form.instance-list.values', {});
|
||||
const filter = get(form, 'filter');
|
||||
const sort = get(form, 'sort');
|
||||
|
||||
const values = filter
|
||||
? index.search(filter).map(({ ref }) => find(instances, ['id', ref]))
|
||||
: instances;
|
||||
|
||||
const selected = Object.keys(form)
|
||||
.map(name => find(values, ['name', name]))
|
||||
.filter(Boolean)
|
||||
.map(({ id }) => id);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
instances: sortBy(values, value => get(value, sort)),
|
||||
selected
|
||||
};
|
||||
},
|
||||
(dispatch, { instances, ...ownProps }) => ({
|
||||
onAction: ({ name, ids = [] }) => {
|
||||
const types = {
|
||||
stop: () =>
|
||||
Promise.all(ids.map(id => ownProps.stop({ variables: { id } }))),
|
||||
start: () =>
|
||||
Promise.all(ids.map(id => ownProps.start({ variables: { id } }))),
|
||||
reboot: () =>
|
||||
Promise.all(ids.map(id => ownProps.reboot({ variables: { id } }))),
|
||||
resize: () => null,
|
||||
'enable-fw': () => null,
|
||||
'disable-fw': () => null,
|
||||
'create-snap': () => null,
|
||||
'start-snap': () => null
|
||||
};
|
||||
|
||||
const clearSelected = () => dispatch(ids.map(id => {
|
||||
const form = 'instance-list';
|
||||
const field = get(find(instances, ['id', id]), 'name');
|
||||
const value = false;
|
||||
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
return change(form, field, value);
|
||||
}));
|
||||
|
||||
const fn = types[name];
|
||||
return fn && fn().then(clearSelected);
|
||||
}
|
||||
})
|
||||
)
|
||||
)(List);
|
||||
|
@ -38,19 +38,18 @@ const MetadataForms = (metadata = []) =>
|
||||
const Metadata = ({ metadata = [], loading, error }) => {
|
||||
const values = forceArray(metadata);
|
||||
const _title = <Title>Metadata</Title>;
|
||||
const _loading = !(loading && !values.length) ? null : (
|
||||
<StatusLoader />
|
||||
);
|
||||
const _loading = !(loading && !values.length) ? null : <StatusLoader />;
|
||||
|
||||
const _metadata = !_loading && MetadataForms(values);
|
||||
|
||||
const _error = (error && !values.length && !_loading) ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instance metadata"
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
const _error =
|
||||
error && !values.length && !_loading ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instance metadata"
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<ViewContainer center={Boolean(_loading)} main>
|
||||
|
@ -28,13 +28,14 @@ const Networks = ({ networks = [], loading, error }) => {
|
||||
/>
|
||||
));
|
||||
|
||||
const _error = (error && !values.length && !_loading) ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instance networks"
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
const _error =
|
||||
error && !values.length && !_loading ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instance networks"
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<ViewContainer center={Boolean(_loading)} main>
|
||||
|
@ -38,19 +38,18 @@ const TagForms = (tags = []) =>
|
||||
const Tags = ({ tags = [], loading, error }) => {
|
||||
const values = forceArray(tags);
|
||||
const _title = <Title>Tags</Title>;
|
||||
const _loading = (loading && !values.length) ? (
|
||||
<StatusLoader />
|
||||
) : null;
|
||||
const _loading = loading && !values.length ? <StatusLoader /> : null;
|
||||
|
||||
const _tags = !_loading && TagForms(tags);
|
||||
|
||||
const _error = (error && !values.length && !_loading) ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instance tags"
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
const _error =
|
||||
error && !values.length && !_loading ? (
|
||||
<Message
|
||||
title="Ooops!"
|
||||
message="An error occurred while loading your instance tags"
|
||||
error
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<ViewContainer center={Boolean(_loading)} main>
|
||||
|
3
packages/my-joy-beta/src/graphql/create-snapshot.gql
Normal file
3
packages/my-joy-beta/src/graphql/create-snapshot.gql
Normal file
@ -0,0 +1,3 @@
|
||||
mutation createInstanceSnapshot($id: ID!, $name: String) {
|
||||
createMachineSnapshot(id: $id, name: $name)
|
||||
}
|
3
packages/my-joy-beta/src/graphql/disable-instance-fw.gql
Normal file
3
packages/my-joy-beta/src/graphql/disable-instance-fw.gql
Normal file
@ -0,0 +1,3 @@
|
||||
mutation disableMachineFirewall($id: ID!) {
|
||||
disableMachineFirewall(id: $id)
|
||||
}
|
3
packages/my-joy-beta/src/graphql/enable-instance-fw.gql
Normal file
3
packages/my-joy-beta/src/graphql/enable-instance-fw.gql
Normal file
@ -0,0 +1,3 @@
|
||||
mutation enableMachineFirewall($id: ID!) {
|
||||
enableMachineFirewall(id: $id)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
query Instance($name: String!) {
|
||||
query instance($name: String!) {
|
||||
machines(name: $name) {
|
||||
id
|
||||
name
|
||||
|
@ -1,4 +1,4 @@
|
||||
query Instance($name: String!) {
|
||||
query instance($name: String!) {
|
||||
machines(name: $name) {
|
||||
id
|
||||
name
|
||||
|
@ -1,10 +1,17 @@
|
||||
query Instances {
|
||||
query instances {
|
||||
machines {
|
||||
id
|
||||
name
|
||||
state
|
||||
firewall_enabled
|
||||
primary_ip
|
||||
docker
|
||||
firewall_enabled
|
||||
created
|
||||
updated
|
||||
brand
|
||||
memory
|
||||
disk
|
||||
package {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
query Instance($name: String!) {
|
||||
query instance($name: String!) {
|
||||
machines(name: $name) {
|
||||
id
|
||||
name
|
||||
|
@ -1,4 +1,4 @@
|
||||
query Instance($name: String!) {
|
||||
query instance($name: String!) {
|
||||
machines(name: $name) {
|
||||
id
|
||||
name
|
||||
|
@ -1,4 +1,4 @@
|
||||
query Instance($name: String!) {
|
||||
query instance($name: String!) {
|
||||
machines(name: $name) {
|
||||
id
|
||||
name
|
||||
|
@ -1,4 +1,4 @@
|
||||
query Instance($name: String!) {
|
||||
query instance($name: String!) {
|
||||
machines(name: $name) {
|
||||
id
|
||||
name
|
||||
|
5
packages/my-joy-beta/src/graphql/reboot-instance.gql
Normal file
5
packages/my-joy-beta/src/graphql/reboot-instance.gql
Normal file
@ -0,0 +1,5 @@
|
||||
mutation rebootInstance($id: ID!) {
|
||||
rebootMachine(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
3
packages/my-joy-beta/src/graphql/resize-instance.gql
Normal file
3
packages/my-joy-beta/src/graphql/resize-instance.gql
Normal file
@ -0,0 +1,3 @@
|
||||
mutation resizeInstance($id: ID!, $package: ID!) {
|
||||
resizeMachine(id: $id, package: $package)
|
||||
}
|
3
packages/my-joy-beta/src/graphql/start-from-snapshot.gql
Normal file
3
packages/my-joy-beta/src/graphql/start-from-snapshot.gql
Normal file
@ -0,0 +1,3 @@
|
||||
mutation startInstanceFromSnapshot($id: ID!, $snapshot: ID!) {
|
||||
startMachineFromSnapshot(id: $id, snapshot: $snapshot)
|
||||
}
|
3
packages/my-joy-beta/src/graphql/start-instance.gql
Normal file
3
packages/my-joy-beta/src/graphql/start-instance.gql
Normal file
@ -0,0 +1,3 @@
|
||||
mutation startInstance($id: ID!) {
|
||||
startMachine(id: $id)
|
||||
}
|
3
packages/my-joy-beta/src/graphql/stop-instance.gql
Normal file
3
packages/my-joy-beta/src/graphql/stop-instance.gql
Normal file
@ -0,0 +1,3 @@
|
||||
mutation stopInstance($id: ID!) {
|
||||
stopMachine(id: $id)
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { reduxBatch } from '@manaflair/redux-batch';
|
||||
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
|
||||
import { reducer as formReducer } from 'redux-form';
|
||||
import { ApolloClient, createNetworkInterface } from 'react-apollo';
|
||||
@ -52,6 +53,7 @@ export const store = createStore(
|
||||
}),
|
||||
state, // Initial state
|
||||
compose(
|
||||
reduxBatch,
|
||||
applyMiddleware(client.middleware()),
|
||||
// If you are using the devToolsExtension, you can add it here also
|
||||
// eslint-disable-next-line no-negated-condition
|
||||
|
@ -126,9 +126,11 @@ const ToggleBase = ({ container = null, type = 'radio' }) =>
|
||||
id: rndId()
|
||||
};
|
||||
|
||||
const checked = type === 'checkbox' && rest.value === true;
|
||||
|
||||
const toggle = (
|
||||
<InnerContainer {...types} type={type}>
|
||||
<StyledInput {...rest} id={newValue.id} type={type} />
|
||||
<StyledInput {...rest} id={newValue.id} type={type} checked={checked} />
|
||||
<Label
|
||||
{...types}
|
||||
htmlFor={newValue.id}
|
||||
|
@ -11,6 +11,7 @@ const Label = styled.label`
|
||||
font-stretch: normal;
|
||||
display: block;
|
||||
color: ${props => props.theme.secondary};
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
export default Baseline(Label);
|
||||
|
Loading…
Reference in New Issue
Block a user