feat(my-joy-beta): add loading and error to instance list actions
This commit is contained in:
parent
966d326f3f
commit
55811372fb
@ -10,7 +10,8 @@ import {
|
||||
CardView,
|
||||
Checkbox,
|
||||
FormGroup,
|
||||
QueryBreakpoints
|
||||
QueryBreakpoints,
|
||||
StatusLoader
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
const { SmallOnly, Small } = QueryBreakpoints;
|
||||
@ -24,7 +25,8 @@ const stateColor = {
|
||||
FAILED: 'red'
|
||||
};
|
||||
|
||||
export default ({ name, state, primary_ip, last, first }) => (
|
||||
// eslint-disable-next-line camelcase
|
||||
export default ({ name, state, primary_ip, loading, last, first }) => (
|
||||
<Card collapsed flat={!last} topMargin={first} bottomless={!last} gapless>
|
||||
<CardView>
|
||||
<CardMeta>
|
||||
@ -34,9 +36,17 @@ export default ({ name, state, primary_ip, last, first }) => (
|
||||
</FormGroup>
|
||||
</CardAction>
|
||||
<CardTitle to={`/instances/${name}`}>{name}</CardTitle>
|
||||
{loading && (
|
||||
<CardLabel>
|
||||
<StatusLoader small />
|
||||
</CardLabel>
|
||||
)}
|
||||
{!loading && (
|
||||
<Small>
|
||||
<CardLabel>{primary_ip}</CardLabel>
|
||||
</Small>
|
||||
)}
|
||||
{!loading && (
|
||||
<Small>
|
||||
<CardLabel
|
||||
color={stateColor[state]}
|
||||
@ -45,12 +55,15 @@ export default ({ name, state, primary_ip, last, first }) => (
|
||||
{titleCase(state)}
|
||||
</CardLabel>
|
||||
</Small>
|
||||
)}
|
||||
{!loading && (
|
||||
<SmallOnly>
|
||||
<CardLabel
|
||||
color={stateColor[state]}
|
||||
title={`The instance is ${state}`}
|
||||
/>
|
||||
</SmallOnly>
|
||||
)}
|
||||
</CardMeta>
|
||||
</CardView>
|
||||
</Card>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from 'react-styled-flexboxgrid';
|
||||
import forceArray from 'force-array';
|
||||
import get from 'lodash.get';
|
||||
import find from 'lodash.find';
|
||||
|
||||
import {
|
||||
FormGroup,
|
||||
@ -9,18 +9,28 @@ import {
|
||||
FormLabel,
|
||||
ViewContainer,
|
||||
StatusLoader,
|
||||
Select
|
||||
Select,
|
||||
Message,
|
||||
MessageTitle,
|
||||
MessageDescription,
|
||||
Button,
|
||||
QueryBreakpoints
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import Item from './item';
|
||||
|
||||
const { SmallOnly, Medium } = QueryBreakpoints;
|
||||
|
||||
export default ({
|
||||
instances = [],
|
||||
selected = [],
|
||||
loading,
|
||||
error,
|
||||
handleChange = () => null,
|
||||
onAction = () => null,
|
||||
handleSubmit,
|
||||
submitting = false,
|
||||
pristine = true,
|
||||
...rest
|
||||
}) => {
|
||||
const allowedActions = {
|
||||
@ -29,30 +39,44 @@ export default ({
|
||||
reboot: true,
|
||||
resize:
|
||||
selected.length === 1 && selected.every(({ brand }) => brand === 'KVM'),
|
||||
// eslint-disable-next-line camelcase
|
||||
enableFw: selected.some(({ firewall_enabled }) => !firewall_enabled),
|
||||
// eslint-disable-next-line camelcase
|
||||
disableFw: selected.some(({ firewall_enabled }) => firewall_enabled),
|
||||
createSnap: true,
|
||||
createSnap: selected.length === 1,
|
||||
startSnap:
|
||||
selected.length === 1 &&
|
||||
selected.every(({ snapshots = [] }) => snapshots.length)
|
||||
};
|
||||
|
||||
const handleActions = ({ target }) =>
|
||||
const handleActions = ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
onAction({
|
||||
name: target.value,
|
||||
name: ev.target.value,
|
||||
items: selected
|
||||
});
|
||||
};
|
||||
|
||||
const _instances = forceArray(instances);
|
||||
|
||||
const items = _instances.map((instance, i, all) => (
|
||||
const items = _instances.map((instance, i, all) => {
|
||||
const { id } = instance;
|
||||
|
||||
const isSelected = Boolean(find(selected, ['id', id]));
|
||||
const isSubmitting = isSelected && submitting;
|
||||
|
||||
return (
|
||||
<Item
|
||||
key={instance.id}
|
||||
key={id}
|
||||
{...instance}
|
||||
last={all.length - 1 === i}
|
||||
first={!i}
|
||||
loading={isSubmitting}
|
||||
/>
|
||||
));
|
||||
);
|
||||
});
|
||||
|
||||
const _loading =
|
||||
!items.length && loading ? (
|
||||
@ -61,19 +85,25 @@ export default ({
|
||||
</ViewContainer>
|
||||
) : null;
|
||||
|
||||
const _error = error &&
|
||||
!submitting && (
|
||||
<Message error>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>{error}</MessageDescription>
|
||||
</Message>
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
onChange={() => handleSubmit(ctx => handleChange(ctx))}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<form>
|
||||
<Row between="xs">
|
||||
<Col xs={10} sm={8} lg={6}>
|
||||
<Col xs={8} 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..."
|
||||
disabled={pristine && !items.length}
|
||||
fluid
|
||||
/>
|
||||
</FormGroup>
|
||||
@ -98,9 +128,9 @@ export default ({
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col xs={2} sm={4} lg={6}>
|
||||
<Col xs={4} sm={4} lg={6}>
|
||||
<Row end="xs">
|
||||
<Col xs={12} sm={3} md={3} lg={2}>
|
||||
<Col xs={6} sm={4} md={3} lg={2}>
|
||||
<FormGroup>
|
||||
<FormLabel>⁣</FormLabel>
|
||||
<Select
|
||||
@ -148,10 +178,26 @@ export default ({
|
||||
</Select>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={6} sm={6} md={5} lg={2}>
|
||||
<FormGroup>
|
||||
<FormLabel>⁣</FormLabel>
|
||||
<Button
|
||||
type="button"
|
||||
small
|
||||
icon
|
||||
fluid
|
||||
onClick={() => onAction({ name: 'create' })}
|
||||
>
|
||||
<SmallOnly>+</SmallOnly>
|
||||
<Medium>Create</Medium>
|
||||
</Button>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
{_loading}
|
||||
{_error}
|
||||
{items}
|
||||
</form>
|
||||
);
|
||||
|
@ -2,12 +2,19 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import { connect } from 'react-redux';
|
||||
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 {
|
||||
reduxForm,
|
||||
SubmissionError,
|
||||
stopSubmit,
|
||||
startSubmit,
|
||||
change
|
||||
} from 'redux-form';
|
||||
|
||||
import {
|
||||
ViewContainer,
|
||||
Title,
|
||||
@ -41,7 +48,7 @@ const List = ({
|
||||
instances = [],
|
||||
loading = false,
|
||||
error,
|
||||
onAction
|
||||
handleAction
|
||||
}) => {
|
||||
const _title = <Title>Instances</Title>;
|
||||
const _instances = forceArray(instances);
|
||||
@ -63,8 +70,8 @@ const List = ({
|
||||
{!_loading && _error}
|
||||
<InstanceListForm
|
||||
instances={_instances}
|
||||
loading={loading}
|
||||
onAction={onAction}
|
||||
loading={_loading}
|
||||
onAction={handleAction}
|
||||
selected={selected}
|
||||
/>
|
||||
</ViewContainer>
|
||||
@ -118,6 +125,7 @@ export default compose(
|
||||
: instances;
|
||||
|
||||
const selected = Object.keys(form)
|
||||
.filter(key => Boolean(form[key]))
|
||||
.map(name => find(values, ['name', name]))
|
||||
.filter(Boolean)
|
||||
.map(({ id }) => find(instances, ['id', id]))
|
||||
@ -129,24 +137,41 @@ export default compose(
|
||||
selected
|
||||
};
|
||||
},
|
||||
(dispatch, { stop, start, reboot, history, location }) => ({
|
||||
onAction: ({ name, items = [] }) => {
|
||||
(
|
||||
dispatch,
|
||||
{ stop, start, reboot, enableFw, disableFw, history, location }
|
||||
) => ({
|
||||
handleAction: ({ name, items = [] }) => {
|
||||
const form = 'instance-list';
|
||||
|
||||
const types = {
|
||||
stop: () =>
|
||||
Promise.all(items.map(({ id }) => stop({ variables: { id } }))),
|
||||
Promise.resolve(dispatch(startSubmit(form))).then(() =>
|
||||
Promise.all(items.map(({ id }) => stop({ variables: { id } })))
|
||||
),
|
||||
start: () =>
|
||||
Promise.all(items.map(({ id }) => start({ variables: { id } }))),
|
||||
Promise.resolve(dispatch(startSubmit(form))).then(() =>
|
||||
Promise.all(items.map(({ id }) => start({ variables: { id } })))
|
||||
),
|
||||
reboot: () =>
|
||||
Promise.all(items.map(({ id }) => reboot({ variables: { id } }))),
|
||||
Promise.resolve(dispatch(startSubmit(form))).then(() =>
|
||||
Promise.all(items.map(({ id }) => reboot({ variables: { id } })))
|
||||
),
|
||||
resize: () =>
|
||||
Promise.resolve(
|
||||
history.push(`/instances/~resize/${items.shift().name}`)
|
||||
),
|
||||
enableFw: () =>
|
||||
Promise.all(items.map(({ id }) => enableFw({ variables: { id } }))),
|
||||
Promise.resolve(dispatch(startSubmit(form))).then(() =>
|
||||
Promise.all(
|
||||
items.map(({ id }) => enableFw({ variables: { id } }))
|
||||
)
|
||||
),
|
||||
disableFw: () =>
|
||||
Promise.resolve(dispatch(startSubmit(form))).then(() =>
|
||||
Promise.all(
|
||||
items.map(({ id }) => disableFw({ variables: { id } }))
|
||||
)
|
||||
),
|
||||
createSnap: () =>
|
||||
Promise.resolve(
|
||||
@ -155,25 +180,32 @@ export default compose(
|
||||
startSnap: () =>
|
||||
Promise.resolve(
|
||||
history.push(`/instances/${items.shift().name}/snapshots`)
|
||||
)
|
||||
),
|
||||
create: () =>
|
||||
Promise.resolve(history.push(`/instances/~create-instance`))
|
||||
};
|
||||
|
||||
const clearSelected = () =>
|
||||
const handleError = error => {
|
||||
throw new SubmissionError({
|
||||
_error: error.graphQLErrors.map(({ message }) => message).join('\n')
|
||||
});
|
||||
};
|
||||
|
||||
const handleSuccess = error =>
|
||||
dispatch(
|
||||
items.map(({ name: field }) => {
|
||||
const form = 'instance-list';
|
||||
const value = false;
|
||||
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
|
||||
return change(form, field, value);
|
||||
})
|
||||
items
|
||||
.map(({ name: field }) => change(form, field, false))
|
||||
.concat([stopSubmit(form)])
|
||||
);
|
||||
|
||||
const fn = types[name];
|
||||
return fn && fn().then(clearSelected);
|
||||
|
||||
return (
|
||||
fn &&
|
||||
fn()
|
||||
.catch(handleError)
|
||||
.then(handleSuccess)
|
||||
);
|
||||
}
|
||||
})
|
||||
)
|
||||
|
@ -16,14 +16,16 @@ import {
|
||||
|
||||
import GetInstance from '@graphql/get-instance.gql';
|
||||
|
||||
const Summary = ({ instance = {}, loading, error }) => {
|
||||
const { name } = instance;
|
||||
const Summary = ({ instance, loading, error }) => {
|
||||
const { name } = instance || {};
|
||||
|
||||
const _title = <Title>Summary</Title>;
|
||||
const _loading = !(loading && !name) ? null : <StatusLoader />;
|
||||
const _summary = !_loading && <ReactJson src={instance} />;
|
||||
const _loading = loading && !name && <StatusLoader />;
|
||||
const _summary = !_loading && instance && <ReactJson src={instance} />;
|
||||
|
||||
const _error = !(error && !_loading) ? null : (
|
||||
const _error = error &&
|
||||
!_loading &&
|
||||
!instance && (
|
||||
<Message error>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>
|
||||
|
Loading…
Reference in New Issue
Block a user