feat(my-joy-beta): revise snapshots implementation

according to new designs

fixes #872
This commit is contained in:
Sara Vieira 2018-01-05 15:42:09 +00:00 committed by Sérgio Ramos
parent 8eecebfe47
commit 3caaebb0e9
29 changed files with 4903 additions and 6209 deletions

View File

@ -4826,6 +4826,7 @@ exports[`renders <KeyValue method="edit" /> without throwing 1`] = `
<b> <b>
undefined: undefined:
</b> </b>
,
<span /> <span />
</span> </span>
</div> </div>

View File

@ -1871,6 +1871,7 @@ exports[`renders <EditForm /> without throwing 1`] = `
<b> <b>
undefined: undefined:
</b> </b>
,
<span /> <span />
</span> </span>
</div> </div>

View File

@ -1809,6 +1809,7 @@ exports[`renders <EditForm /> without throwing 1`] = `
<b> <b>
undefined: undefined:
</b> </b>
,
<span /> <span />
</span> </span>
</div> </div>

View File

@ -3,70 +3,9 @@ import renderer from 'react-test-renderer';
import 'jest-styled-components'; import 'jest-styled-components';
import { Table, TableTbody } from 'joyent-ui-toolkit'; import { Table, TableTbody } from 'joyent-ui-toolkit';
import InstanceList, { Actions, Item } from '../list'; import InstanceList, { Item } from '../list';
import Theme from '@mocks/theme'; import Theme from '@mocks/theme';
it('renders <Actions /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Actions />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions submitting /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Actions submitting />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions submitting statuses /> without throwing', () => {
const statuses = {
starting: true,
stopping: true,
restarting: true,
deleting: true
};
expect(
renderer
.create(
<Theme>
<Actions submitting statuses={statuses} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions allowedActions /> without throwing', () => {
const allowedActions = {
start: true,
stop: true,
reboot: true
};
expect(
renderer
.create(
<Theme>
<Actions allowedActions={allowedActions} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Item /> without throwing', () => { it('renders <Item /> without throwing', () => {
expect( expect(
renderer renderer

View File

@ -0,0 +1,128 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import { Table, TableTbody } from 'joyent-ui-toolkit';
import SnapshotList, { Item } from '../snapshots';
import Theme from '@mocks/theme';
it('renders <Item /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Item />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Item mutating /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Table>
<TableTbody>
<Item mutating />
</TableTbody>
</Table>
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Item {...item} /> without throwing', () => {
const item = {
updated: '12/09/2017',
created: '12/09/2017',
machineID: '657-sh',
name: 'name',
state: 'STARTED'
};
expect(
renderer
.create(
<Theme>
<Item {...item} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <SnapshotList /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<SnapshotList />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<SnapshotList selected={[1, 3]} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <SnapshotList sortBy /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<SnapshotList sortBy="state" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <SnapshotList sortBy sortOrder /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<SnapshotList sortBy="state" sortOrder="asc" />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <SnapshotList submitting /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<SnapshotList submitting />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <SnapshotList allSelected /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<SnapshotList allSelected />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -64,11 +64,13 @@ it('renders <Summary state /> without throwing', () => {
it('renders <Summary instance /> without throwing', () => { it('renders <Summary instance /> without throwing', () => {
const instance1 = { const instance1 = {
id: '2252839a-e698-ceec-afac-9549ad0c6624', id: '2252839a-e698-ceec-afac-9549ad0c6624',
// eslint-disable-next-line camelcase
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0', compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: { image: {
id: '19aa3328-0025-11e7-a19a-c39077bfd4cf', id: '19aa3328-0025-11e7-a19a-c39077bfd4cf',
name: 'Alpine 3' name: 'Alpine 3'
}, },
// eslint-disable-next-line camelcase
primary_ip: '72.2.119.146', primary_ip: '72.2.119.146',
ips: ['72.2.119.146', '10.112.5.63'], ips: ['72.2.119.146', '10.112.5.63'],
package: { package: {
@ -90,10 +92,12 @@ it('renders <Summary instance /> without throwing', () => {
const instance2 = { const instance2 = {
id: '2252839a-e698-ceec-afac-9549ad0c6624', id: '2252839a-e698-ceec-afac-9549ad0c6624',
// eslint-disable-next-line camelcase
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0', compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: { image: {
id: '19aa3328-0025-11e7-a19a-c39077bfd4cf' id: '19aa3328-0025-11e7-a19a-c39077bfd4cf'
}, },
// eslint-disable-next-line camelcase
primary_ip: '72.2.119.146', primary_ip: '72.2.119.146',
ips: ['72.2.119.146', '10.112.5.63'], ips: ['72.2.119.146', '10.112.5.63'],
package: { package: {

View File

@ -1,43 +0,0 @@
import React from 'react';
import { Field } from 'redux-form';
import {
FormGroup,
FormLabel,
Input,
Button,
Message,
MessageTitle,
MessageDescription
} from 'joyent-ui-toolkit';
export default ({
submitting = false,
error,
handleSubmit = () => {},
onCancel = () => {}
}) => {
const _error = error &&
!submitting && (
<Message error>
<MessageTitle>Ooops!</MessageTitle>
<MessageDescription>{error}</MessageDescription>
</Message>
);
return (
<form onSubmit={handleSubmit}>
{_error}
<FormGroup name="name" field={Field}>
<FormLabel>Name (optional)</FormLabel>
<Input placeholder="Snapshot name" />
</FormGroup>
<Button type="button" disabled={submitting} onClick={onCancel} secondary>
Back
</Button>
<Button type="submit" disabled={submitting} loading={submitting}>
Create
</Button>
</form>
);
};

View File

@ -0,0 +1,168 @@
import React from 'react';
import { withTheme } from 'styled-components';
import {
Row,
Col,
Button,
Footer,
QueryBreakpoints,
StartIcon,
StopIcon,
ResetIcon,
DeleteIcon
} from 'joyent-ui-toolkit';
const { SmallOnly, Medium } = QueryBreakpoints;
export default withTheme(
({
submitting = false,
statuses = {},
allowedActions = {},
onStart,
onStop,
onReboot,
onRemove,
theme = {}
}) => (
<Footer fixed bottom>
<Row between="xs" middle="xs">
<Col xs={7}>
{onStart && [
<SmallOnly key="small-only">
<Button
type="button"
onClick={onStart}
disabled={submitting || !allowedActions.start}
loading={submitting && statuses.starting}
secondary
small
icon
>
<StartIcon disabled={submitting || !allowedActions.start} />
</Button>
</SmallOnly>,
<Medium key="medium">
<Button
type="button"
onClick={onStart}
disabled={submitting || !allowedActions.start}
loading={submitting && statuses.starting}
secondary
icon
>
<StartIcon disabled={submitting || !allowedActions.start} />
<span>Start</span>
</Button>
</Medium>
]}
{onStop && [
<SmallOnly key="small-only">
<Button
type="button"
onClick={onStop}
disabled={submitting || !allowedActions.stop}
loading={submitting && statuses.stopping}
secondary
small
icon
>
<StopIcon disabled={submitting || !allowedActions.stop} />
</Button>
</SmallOnly>,
<Medium key="medium">
<Button
type="button"
onClick={onStop}
disabled={submitting || !allowedActions.stop}
loading={submitting && statuses.stopping}
secondary
icon
>
<StopIcon disabled={submitting || !allowedActions.stop} />
<span>Stop</span>
</Button>
</Medium>
]}
{onReboot && [
<SmallOnly key="small-only">
<Button
type="button"
onClick={onReboot}
disabled={submitting || !allowedActions.reboot}
loading={submitting && statuses.rebooting}
secondary
small
icon
>
<ResetIcon disabled={submitting || !allowedActions.reboot} />
</Button>
</SmallOnly>,
<Medium key="medium">
<Button
type="button"
onClick={onReboot}
disabled={submitting || !allowedActions.reboot}
loading={submitting && statuses.rebooting}
secondary
icon
>
<ResetIcon disabled={submitting || !allowedActions.reboot} />
<span>Reboot</span>
</Button>
</Medium>
]}
</Col>
{onRemove && (
<Col xs={5}>
<SmallOnly key="small-only">
<Button
type="button"
onClick={onRemove}
disabled={submitting || !allowedActions.remove}
loading={submitting && statuses.removing}
secondary
error
right
small
icon
>
<DeleteIcon
disabled={submitting}
fill={
!(submitting || !allowedActions.remove)
? theme.red
: undefined
}
/>
</Button>
</SmallOnly>
<Medium key="medium">
<Button
type="button"
onClick={onRemove}
disabled={submitting || !allowedActions.remove}
loading={submitting && statuses.removing}
error
secondary
right
icon
>
<DeleteIcon
disabled={submitting || !allowedActions.remove}
fill={
!(submitting || !allowedActions.remove)
? theme.red
: undefined
}
/>
<span>Remove</span>
</Button>
</Medium>
</Col>
)}
</Row>
</Footer>
)
);

View File

@ -3,5 +3,4 @@ export { default as KeyValue } from './key-value';
export { default as Network } from './network'; export { default as Network } from './network';
export { default as FirewallRule } from './firewall-rule'; export { default as FirewallRule } from './firewall-rule';
export { default as Resize } from './resize'; export { default as Resize } from './resize';
export { default as CreateSnapshot } from './create-snapshot';
export { default as Snapshots } from './snapshots'; export { default as Snapshots } from './snapshots';

View File

@ -98,6 +98,18 @@ const InputKeyValue = ({ type, submitting }) => (
</Flex> </Flex>
); );
const InputName = ({ type, submitting }) => (
<Flex justifyStart contentStretch>
<FlexItem basis="auto">
<FormGroup name="name" field={Field} fluid>
<FormLabel>{titleCase(type)} Name</FormLabel>
<Input type="text" disabled={submitting} />
<FormMeta />
</FormGroup>
</FlexItem>
</Flex>
);
export const KeyValue = ({ export const KeyValue = ({
input = 'input', input = 'input',
type = 'metadata', type = 'metadata',
@ -111,7 +123,9 @@ export const KeyValue = ({
onToggleExpanded = () => null, onToggleExpanded = () => null,
onCancel = () => null, onCancel = () => null,
onRemove = () => null, onRemove = () => null,
theme = {} theme = {},
onlyName = false,
noRemove = false
}) => { }) => {
const handleHeaderClick = method === 'edit' && onToggleExpanded; const handleHeaderClick = method === 'edit' && onToggleExpanded;
@ -124,7 +138,7 @@ export const KeyValue = ({
onClick={handleHeaderClick} onClick={handleHeaderClick}
> >
<CardHeaderMeta> <CardHeaderMeta>
{method === 'add' ? ( {method === 'add' || method === 'create' ? (
<H4>{`${titleCase(method)} ${type}`}</H4> <H4>{`${titleCase(method)} ${type}`}</H4>
) : ( ) : (
<CollapsedKeyValue> <CollapsedKeyValue>
@ -132,8 +146,9 @@ export const KeyValue = ({
name="name" name="name"
type="text" type="text"
component={({ input = {} }) => component={({ input = {} }) =>
!expanded ? `${input.value}: ` : <b>{`${input.value}: `}</b>} !expanded ? `${input.value}: ` : <b>{`${input.value}: `}</b>
/> }
/>,
<Field <Field
name="value" name="value"
type="text" type="text"
@ -157,7 +172,11 @@ export const KeyValue = ({
</Row> </Row>
) : null} ) : null}
{input === 'input' ? ( {input === 'input' ? (
<InputKeyValue type={type} submitting={submitting} /> onlyName ? (
<InputName type={type} submitting={submitting} />
) : (
<InputKeyValue type={type} submitting={submitting} />
)
) : ( ) : (
<TextareaKeyValue type={type} submitting={submitting} /> <TextareaKeyValue type={type} submitting={submitting} />
)} )}
@ -181,25 +200,27 @@ export const KeyValue = ({
<span>{method === 'add' ? 'Create' : 'Save'}</span> <span>{method === 'add' ? 'Create' : 'Save'}</span>
</Button> </Button>
</Col> </Col>
<Col xs={method === 'add' ? false : 5}> {!noRemove && (
<Button <Col xs={method === 'add' ? false : 5}>
type="button" <Button
onClick={onRemove} type="button"
disabled={submitting} onClick={onRemove}
loading={removing}
secondary
right
icon
error
marginless
>
<DeleteIcon
disabled={submitting} disabled={submitting}
fill={submitting ? undefined : theme.red} loading={removing}
/> secondary
<span>Delete</span> right
</Button> icon
</Col> error
marginless
>
<DeleteIcon
disabled={submitting}
fill={submitting ? undefined : theme.red}
/>
<span>Delete</span>
</Button>
</Col>
)}
</Row> </Row>
</Padding> </Padding>
</CardOutlet> </CardOutlet>

View File

@ -6,36 +6,25 @@ import { Link } from 'react-router-dom';
import { Field } from 'redux-form'; import { Field } from 'redux-form';
import { import {
Row,
Col,
Anchor, Anchor,
FormGroup, FormGroup,
Checkbox, Checkbox,
Button,
Table, Table,
TableThead, TableThead,
TableTr, TableTr,
TableTh, TableTh,
TableTd, TableTd,
TableTbody, TableTbody,
Footer,
StatusLoader, StatusLoader,
Popover, Popover,
PopoverContainer, PopoverContainer,
PopoverTarget, PopoverTarget,
PopoverItem, PopoverItem,
PopoverDivider, PopoverDivider,
QueryBreakpoints,
DotIcon, DotIcon,
StartIcon,
StopIcon,
ResetIcon,
DeleteIcon,
ActionsIcon ActionsIcon
} from 'joyent-ui-toolkit'; } from 'joyent-ui-toolkit';
const { SmallOnly, Medium } = QueryBreakpoints;
const stateColor = { const stateColor = {
PROVISIONING: 'primary', PROVISIONING: 'primary',
RUNNING: 'green', RUNNING: 'green',
@ -45,131 +34,6 @@ const stateColor = {
FAILED: 'red' FAILED: 'red'
}; };
export const Actions = ({
submitting = false,
statuses = {},
allowedActions = {},
onStart = () => null,
onStop = () => null,
onReboot = () => null,
onDelete = () => null
}) => (
<Footer fixed bottom>
<Row between="xs" middle="xs">
<Col xs={7}>
<SmallOnly key="small-only">
<Button
type="button"
onClick={onStart}
disabled={submitting || !allowedActions.start}
loading={submitting && statuses.starting}
secondary
small
icon
>
<StartIcon disabled={submitting || !allowedActions.start} />
</Button>
</SmallOnly>
<Medium key="medium">
<Button
type="button"
onClick={onStart}
disabled={submitting || !allowedActions.start}
loading={submitting && statuses.starting}
secondary
icon
>
<StartIcon disabled={submitting || !allowedActions.start} />
<span>Start</span>
</Button>
</Medium>
<SmallOnly key="small-only">
<Button
type="button"
onClick={onStop}
disabled={submitting || !allowedActions.stop}
loading={submitting && statuses.stopping}
secondary
small
icon
>
<StopIcon disabled={submitting || !allowedActions.stop} />
</Button>
</SmallOnly>
<Medium key="medium">
<Button
type="button"
onClick={onStop}
disabled={submitting || !allowedActions.stop}
loading={submitting && statuses.stopping}
secondary
icon
>
<StopIcon disabled={submitting || !allowedActions.stop} />
<span>Stop</span>
</Button>
</Medium>
<SmallOnly key="small-only">
<Button
type="button"
onClick={onReboot}
disabled={submitting || !allowedActions.reboot}
loading={submitting && statuses.rebooting}
secondary
small
icon
>
<ResetIcon disabled={submitting || !allowedActions.reboot} />
</Button>
</SmallOnly>
<Medium key="medium">
<Button
type="button"
onClick={onReboot}
disabled={submitting || !allowedActions.reboot}
loading={submitting && statuses.rebooting}
secondary
icon
>
<ResetIcon disabled={submitting || !allowedActions.reboot} />
<span>Reboot</span>
</Button>
</Medium>
</Col>
<Col xs={5}>
<SmallOnly key="small-only">
<Button
type="button"
onClick={onDelete}
disabled={submitting}
loading={submitting && statuses.deleting}
secondary
right
small
icon
>
<DeleteIcon disabled={submitting} />
</Button>
</SmallOnly>
<Medium key="medium">
<Button
type="button"
onClick={onDelete}
disabled={submitting}
loading={submitting && statuses.deleting}
secondary
right
icon
>
<DeleteIcon disabled={submitting} />
<span>Delete</span>
</Button>
</Medium>
</Col>
</Row>
</Footer>
);
export const Item = ({ export const Item = ({
id = '', id = '',
name, name,

View File

@ -1,22 +1,22 @@
import React from 'react'; import React from 'react';
import { Row, Col } from 'react-styled-flexboxgrid';
import forceArray from 'force-array'; import forceArray from 'force-array';
import find from 'lodash.find'; import find from 'lodash.find';
import { Field } from 'redux-form'; import { Field } from 'redux-form';
import titleCase from 'title-case';
import moment from 'moment'; import moment from 'moment';
import remcalc from 'remcalc';
import InstanceListActions from '@components/instances/footer';
import { KeyValue } from '@components/instances';
import ReduxForm from 'declarative-redux-form';
import { Margin } from 'styled-components-spacing';
import { import {
FormGroup, FormGroup,
Input,
FormLabel,
ViewContainer, ViewContainer,
StatusLoader, StatusLoader,
Select,
Message, Message,
MessageTitle, MessageTitle,
MessageDescription, MessageDescription,
Button,
QueryBreakpoints,
Table, Table,
TableThead, TableThead,
TableTr, TableTr,
@ -24,173 +24,174 @@ import {
TableTbody, TableTbody,
TableTd, TableTd,
Checkbox, Checkbox,
P Popover,
PopoverContainer,
PopoverTarget,
PopoverItem,
ActionsIcon,
DotIcon
} from 'joyent-ui-toolkit'; } from 'joyent-ui-toolkit';
const { SmallOnly, Medium } = QueryBreakpoints; const stateColor = {
QUEUED: 'primary',
CREATED: 'green'
};
const Item = ({ name, state, created }) => ( export const Item = ({
name,
state,
created,
onStart,
onRemove,
updated,
mutating
}) => (
<TableTr> <TableTr>
<TableTd center middle> {!mutating ? (
<FormGroup name={name} field={Field}> [
<Checkbox /> <TableTd padding="0" paddingLeft={remcalc(12)} middle left>
</FormGroup> <FormGroup paddingTop={remcalc(4)} name={name} field={Field}>
</TableTd> <Checkbox noMargin />
<TableTd>{name}</TableTd> </FormGroup>
<TableTd>{moment.unix(created).fromNow()}</TableTd> </TableTd>,
<TableTd middle left>
{name}
</TableTd>,
<TableTd middle left>
<DotIcon
width={remcalc(11)}
height={remcalc(11)}
borderRadius={remcalc(11)}
color={stateColor[state]}
/>{' '}
{titleCase(state)}
</TableTd>,
<TableTd xs="0" sm="160" middle left>
{moment.unix(created).fromNow()}
</TableTd>,
<TableTd xs="0" sm="160" middle left>
{moment.unix(updated).fromNow()}
</TableTd>,
<PopoverContainer clickable>
<TableTd padding="0" hasBorder="left">
<PopoverTarget box>
<ActionsIcon />
</PopoverTarget>
<Popover placement="top">
<PopoverItem onClick={onStart}>Start</PopoverItem>
<PopoverItem onClick={onRemove}>Remove</PopoverItem>
</Popover>
</TableTd>
</PopoverContainer>
]
) : (
<TableTd colSpan="6">
<StatusLoader />
</TableTd>
)}
</TableTr> </TableTr>
); );
export const AddForm = props => (
<KeyValue
{...props}
method="create"
input="input"
type="snapshot"
expanded
onlyName
noRemove
/>
);
export default ({ export default ({
snapshots = [], snapshots = [],
selected = [], selected = [],
loading,
error, error,
handleChange = () => null, handleChange = () => null,
onAction = () => null, onAction = () => null,
handleSubmit,
submitting = false, submitting = false,
pristine = true, sortBy = 'name',
sortOrder = 'desc',
onSortBy = () => null,
allSelected = false,
toggleSelectAll = () => null,
onStart,
onRemove,
...rest ...rest
}) => { }) => {
const allowedActions = { return (
delete: selected.length > 0,
start: selected.length === 1
};
const handleActions = ev => {
ev.stopPropagation();
ev.preventDefault();
onAction({
name: ev.target.value,
items: selected
});
};
const _snapshots = forceArray(snapshots);
const _loading = !_snapshots.length &&
loading && (
<ViewContainer center>
<StatusLoader />
</ViewContainer>
);
const items = _snapshots.map(snapshot => {
const { name } = snapshot;
const isSelected = Boolean(find(selected, ['name', name]));
const isSubmitting = isSelected && submitting;
return {
...snapshot,
isSubmitting,
isSelected
};
});
const _error = error &&
!submitting && (
<Message error>
<MessageTitle>Ooops!</MessageTitle>
<MessageDescription>{error}</MessageDescription>
</Message>
);
const _table = !items.length ? null : (
<Table> <Table>
<TableThead> <TableThead>
<TableTr> <TableTr>
<TableTh xs="48" /> <TableTh xs="32" padding="0" paddingLeft={remcalc(12)} middle left>
<TableTh left bottom> <FormGroup paddingTop={remcalc(4)}>
<P>Name</P> <Checkbox
checked={allSelected}
disabled={submitting}
onChange={toggleSelectAll}
noMargin
/>
</FormGroup>
</TableTh> </TableTh>
<TableTh xs="120" left bottom> <TableTh
<P>Created</P> onClick={() => onSortBy('name', sortOrder)}
sortOrder={sortOrder}
showSort={sortBy === 'name'}
left
middle
actionable
>
<span>Name </span>
</TableTh> </TableTh>
<TableTh
xs="150"
onClick={() => onSortBy('state', sortOrder)}
sortOrder={sortOrder}
showSort={sortBy === 'state'}
left
middle
actionable
>
<span>Status </span>
</TableTh>
<TableTh
xs="0"
sm="160"
onClick={() => onSortBy('created', sortOrder)}
sortOrder={sortOrder}
showSort={sortBy === 'created'}
left
middle
actionable
>
<span>Created </span>
</TableTh>
<TableTh
xs="0"
sm="160"
onClick={() => onSortBy('updated', sortOrder)}
sortOrder={sortOrder}
showSort={sortBy === 'updated'}
left
middle
actionable
>
<span>Updated </span>
</TableTh>
<TableTh xs="60" padding="0" />
</TableTr> </TableTr>
</TableThead> </TableThead>
<TableTbody> <TableTbody>
{items.map(snapshot => <Item key={snapshot.name} {...snapshot} />)} {snapshots.map(snapshot => (
<Item
onStart={() => onStart(snapshot)}
onRemove={() => onRemove(snapshot)}
key={snapshot.id}
{...snapshot}
/>
))}
</TableTbody> </TableTbody>
</Table> </Table>
); );
return (
<form
onChange={() => handleSubmit(ctx => handleChange(ctx))}
onSubmit={handleSubmit}
>
<Row between="xs">
<Col xs={8} sm={8} lg={6}>
<Row>
<Col xs={7} sm={7} md={6} lg={6}>
<FormGroup name="filter" field={Field}>
<FormLabel>Filter snapshots</FormLabel>
<Input
placeholder="Search for name or state"
disabled={pristine && !items.length}
fluid
/>
</FormGroup>
</Col>
<Col xs={5} sm={3} lg={3}>
<FormGroup name="sort" field={Field}>
<FormLabel>Sort</FormLabel>
<Select disabled={!items.length} fluid>
<option value="name">Name</option>
<option value="state">State</option>
<option value="created">Created</option>
<option value="updated">Updated</option>
</Select>
</FormGroup>
</Col>
</Row>
</Col>
<Col xs={4} sm={4} lg={6}>
<Row end="xs">
<Col xs={6} sm={4} md={3} lg={2}>
<FormGroup>
<FormLabel>&#8291;</FormLabel>
<Select
value="actions"
disabled={!items.length || !selected.length}
onChange={handleActions}
fluid
>
<option value="actions" selected disabled>
&#8801;
</option>
<option value="delete" disabled={!allowedActions.delete}>
Delete
</option>
<option value="start" disabled={!allowedActions.start}>
Start
</option>
</Select>
</FormGroup>
</Col>
<Col xs={6} sm={6} md={5} lg={2}>
<FormGroup>
<FormLabel>&#8291;</FormLabel>
<Button
type="button"
small
icon
fluid
onClick={() => onAction({ name: 'create' })}
>
<SmallOnly>+</SmallOnly>
<Medium>Create</Medium>
</Button>
</FormGroup>
</Col>
</Row>
</Col>
</Row>
{_loading}
{_error}
{_table}
</form>
);
}; };

View File

@ -4574,6 +4574,7 @@ exports[`renders <Metadata metadata /> without throwing 1`] = `
<b> <b>
undefined: undefined:
</b> </b>
,
<span /> <span />
</span> </span>
</div> </div>
@ -4764,6 +4765,7 @@ exports[`renders <Metadata metadata /> without throwing 1`] = `
<b> <b>
undefined: undefined:
</b> </b>
,
<span /> <span />
</span> </span>
</div> </div>
@ -4954,6 +4956,7 @@ exports[`renders <Metadata metadata /> without throwing 1`] = `
<b> <b>
undefined: undefined:
</b> </b>
,
<span /> <span />
</span> </span>
</div> </div>

View File

@ -3171,6 +3171,7 @@ exports[`renders <Tags editing /> without throwing 1`] = `
<b> <b>
undefined: undefined:
</b> </b>
,
<span /> <span />
</span> </span>
</div> </div>
@ -4672,6 +4673,7 @@ exports[`renders <Tags editing.removing /> without throwing 1`] = `
<b> <b>
undefined: undefined:
</b> </b>
,
<span /> <span />
</span> </span>
</div> </div>

View File

@ -54,22 +54,26 @@ it('renders <Metadata addOpen /> without throwing', () => {
}); });
it('renders <Metadata metadata /> without throwing', () => { it('renders <Metadata metadata /> without throwing', () => {
const metadata = [{ const metadata = [
name: 'name1', {
value: 'value1', name: 'name1',
id: 'name1-value1' value: 'value1',
}, { id: 'name1-value1'
name: 'name2', },
value: 'value2', {
id: 'name2-value2', name: 'name2',
expanded: true value: 'value2',
}, { id: 'name2-value2',
name: 'name3', expanded: true
value: 'value3', },
id: 'name3-value3', {
expanded: true, name: 'name3',
removing: true value: 'value3',
}]; id: 'name3-value3',
expanded: true,
removing: true
}
];
expect( expect(
renderer renderer

View File

@ -68,11 +68,13 @@ it('renders <Summary starting stopping rebooting deleting /> without throwing',
it('renders <Summary starting stopping rebooting deleting /> without throwing', () => { it('renders <Summary starting stopping rebooting deleting /> without throwing', () => {
const instance1 = { const instance1 = {
id: '2252839a-e698-ceec-afac-9549ad0c6624', id: '2252839a-e698-ceec-afac-9549ad0c6624',
// eslint-disable-next-line camelcase
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0', compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: { image: {
id: '19aa3328-0025-11e7-a19a-c39077bfd4cf', id: '19aa3328-0025-11e7-a19a-c39077bfd4cf',
name: 'Alpine 3' name: 'Alpine 3'
}, },
// eslint-disable-next-line camelcase
primary_ip: '72.2.119.146', primary_ip: '72.2.119.146',
ips: ['72.2.119.146', '10.112.5.63'], ips: ['72.2.119.146', '10.112.5.63'],
package: { package: {
@ -94,10 +96,12 @@ it('renders <Summary starting stopping rebooting deleting /> without throwing',
const instance2 = { const instance2 = {
id: '2252839a-e698-ceec-afac-9549ad0c6624', id: '2252839a-e698-ceec-afac-9549ad0c6624',
// eslint-disable-next-line camelcase
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0', compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: { image: {
id: '19aa3328-0025-11e7-a19a-c39077bfd4cf' id: '19aa3328-0025-11e7-a19a-c39077bfd4cf'
}, },
// eslint-disable-next-line camelcase
primary_ip: '72.2.119.146', primary_ip: '72.2.119.146',
ips: ['72.2.119.146', '10.112.5.63'], ips: ['72.2.119.146', '10.112.5.63'],
package: { package: {
@ -117,4 +121,3 @@ it('renders <Summary starting stopping rebooting deleting /> without throwing',
.toJSON() .toJSON()
).toMatchSnapshot(); ).toMatchSnapshot();
}); });

View File

@ -93,19 +93,23 @@ it('renders <Tags editing.removing /> without throwing', () => {
}); });
it('renders <Tags tags /> without throwing', () => { it('renders <Tags tags /> without throwing', () => {
const tags = [{ const tags = [
name: 'name1', {
value: 'value1', name: 'name1',
id: 'name1-value1' value: 'value1',
}, { id: 'name1-value1'
name: 'name2', },
value: 'value2', {
id: 'name2-value2' name: 'name2',
}, { value: 'value2',
name: 'name3', id: 'name2-value2'
value: 'value3', },
id: 'name3-value3' {
}]; name: 'name3',
value: 'value3',
id: 'name3-value3'
}
];
expect( expect(
renderer renderer

View File

@ -1,91 +0,0 @@
import React from 'react';
import { reduxForm, SubmissionError } from 'redux-form';
import { connect } from 'react-redux';
import { compose, graphql } from 'react-apollo';
import find from 'lodash.find';
import get from 'lodash.get';
import {
ViewContainer,
StatusLoader,
Message,
MessageTitle,
MessageDescription
} from 'joyent-ui-toolkit';
import { CreateSnapshot as InstanceCreateSnapshot } from '@components/instances';
import CreateSnapshotMutation from '@graphql/create-snapshot.gql';
import GetInstance from '@graphql/get-instance.gql';
const CreateSnapshot = ({
match,
instance,
loading,
error,
handleSubmit,
handleCancel
}) => {
const _loading = !(loading && !instance) ? null : <StatusLoader />;
const CreateSnapshotForm = reduxForm({
form: `create-snapshot-${match.params.instance}`
})(InstanceCreateSnapshot);
const _error = error &&
!instance &&
!_loading && (
<Message error>
<MessageTitle>Ooops!</MessageTitle>
<MessageDescription>
An error occurred while loading your instance
</MessageDescription>
</Message>
);
const _form = !loading &&
!_error && (
<CreateSnapshotForm onSubmit={handleSubmit} onCancel={handleCancel} />
);
return (
<ViewContainer center={Boolean(_loading)} main>
{_loading}
{_error}
{_form}
</ViewContainer>
);
};
export default compose(
graphql(CreateSnapshotMutation, { name: 'createSnapshot' }),
graphql(GetInstance, {
options: ({ match }) => ({
variables: {
name: get(match, 'params.instance')
}
}),
props: ({ data: { loading, error, variables, ...rest } }) => ({
instance: find(get(rest, 'machines', []), ['name', variables.name]),
loading,
error
})
}),
connect(
null,
(dispatch, { history, location, instance, createSnapshot }) => ({
handleCancel: () => history.push(location.pathname.split(/\/~/).shift()),
handleSubmit: ({ name }) =>
createSnapshot({
variables: { name, id: instance.id }
})
.catch(error => {
throw new SubmissionError({
_error: error.graphQLErrors
.map(({ message }) => message)
.join('\n')
});
})
.then(() => history.push(`/instances/${instance.name}/snapshots`))
})
)
)(CreateSnapshot);

View File

@ -15,7 +15,9 @@ import {
import ListDNS from '@graphql/list-dns.gql'; import ListDNS from '@graphql/list-dns.gql';
const DNS = ({ instance, loading, error }) => { const DNS = ({ instance, loading, error }) => {
// eslint-disable-next-line camelcase
const { name, dns_names } = instance || {}; const { name, dns_names } = instance || {};
// eslint-disable-next-line camelcase
const _loading = loading && !name && !dns_names && <StatusLoader />; const _loading = loading && !name && !dns_names && <StatusLoader />;
const _summary = !_loading && const _summary = !_loading &&
instance && <pre>{JSON.stringify(dns_names, null, 2)}</pre>; instance && <pre>{JSON.stringify(dns_names, null, 2)}</pre>;

View File

@ -7,4 +7,3 @@ export { default as Firewall } from './firewall';
export { default as Dns } from './dns'; export { default as Dns } from './dns';
export { default as Snapshots } from './snapshots'; export { default as Snapshots } from './snapshots';
export { default as Resize } from './resize'; export { default as Resize } from './resize';
export { default as CreateSnapshot } from './create-snapshot';

View File

@ -35,10 +35,11 @@ import parseError from '@state/parse-error';
import { import {
default as InstanceList, default as InstanceList,
Item as InstanceListItem, Item as InstanceListItem
Actions as InstanceListActions
} from '@components/instances/list'; } from '@components/instances/list';
import InstanceListActions from '@components/instances/footer';
const TABLE_FORM_NAME = 'instance-list-table'; const TABLE_FORM_NAME = 'instance-list-table';
const MENU_FORM_NAME = 'instance-list-menu'; const MENU_FORM_NAME = 'instance-list-menu';

View File

@ -2,42 +2,67 @@ import React from 'react';
import moment from 'moment'; import moment from 'moment';
import forceArray from 'force-array'; import forceArray from 'force-array';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { reduxForm, stopSubmit, startSubmit, change, reset } from 'redux-form';
import { compose, graphql } from 'react-apollo'; import { compose, graphql } from 'react-apollo';
import find from 'lodash.find'; import find from 'lodash.find';
import sortBy from 'lodash.sortby';
import get from 'lodash.get'; import get from 'lodash.get';
import sort from 'lodash.sortby';
import { reduxForm, stopSubmit, startSubmit, change } from 'redux-form'; import { set } from 'react-redux-values';
import ReduxForm from 'declarative-redux-form';
import { withRouter } from 'react-router-dom';
import { Margin } from 'styled-components-spacing';
import intercept from 'apr-intercept';
import { import {
ViewContainer, ViewContainer,
Message, Message,
MessageTitle, MessageTitle,
MessageDescription MessageDescription,
StatusLoader
} from 'joyent-ui-toolkit'; } from 'joyent-ui-toolkit';
import GetSnapshots from '@graphql/list-snapshots.gql'; import GetSnapshots from '@graphql/list-snapshots.gql';
import StartSnapshot from '@graphql/start-from-snapshot.gql'; import StartSnapshot from '@graphql/start-from-snapshot.gql';
import RemoveSnapshot from '@graphql/remove-snapshot.gql'; import RemoveSnapshot from '@graphql/remove-snapshot.gql';
import { Snapshots as SnapshotsList } from '@components/instances'; import CreateSnapshotMutation from '@graphql/create-snapshot.gql';
import GenIndex from '@state/gen-index'; import GenIndex from '@state/gen-index';
import ToolbarForm from '@components/instances/toolbar';
import SnapshotsListActions from '@components/instances/footer';
import parseError from '@state/parse-error';
const SnapshotsListForm = reduxForm({ import {
form: `snapshots-list`, default as SnapshotsList,
initialValues: { AddForm as SnapshotAddForm
sort: 'name' } from '@components/instances/snapshots';
}
})(SnapshotsList); const MENU_FORM_NAME = 'snapshot-list-menu';
const TABLE_FORM_NAME = 'snapshot-list-table';
const CREATE_FORM_NAME = 'create-snapshot-form';
const Snapshots = ({ const Snapshots = ({
snapshots = [], snapshots = [],
instance = {},
selected = [], selected = [],
loading, loading,
submitting,
error, error,
handleAction mutationError,
allowedActions,
statuses,
handleAction,
handleCreateSnapshot,
sortOrder,
handleSortBy,
sortBy,
toggleSelectAll,
toggleCreateSnapshotOpen,
createSnapshotOpen
}) => { }) => {
const _values = forceArray(snapshots); const _values = forceArray(snapshots);
const _loading = !_values.length && loading; const _loading = !_values.length && loading ? <StatusLoader /> : null;
const handleStart = selected => handleAction({ name: 'start', selected });
const handleRemove = selected => handleAction({ name: 'remove', selected });
const _error = error && const _error = error &&
!_loading && !_loading &&
@ -50,22 +75,86 @@ const Snapshots = ({
</Message> </Message>
); );
const _createSnapshot =
!loading && createSnapshotOpen ? (
<ReduxForm form={CREATE_FORM_NAME} onSubmit={handleCreateSnapshot}>
{props => (
<Margin top={5}>
<SnapshotAddForm
{...props}
onCancel={() => toggleCreateSnapshotOpen(false)}
/>
</Margin>
)}
</ReduxForm>
) : null;
const _footer =
!loading && selected.length > 0 ? (
<SnapshotsListActions
submitting={submitting}
allowedActions={allowedActions}
statuses={statuses}
onStart={() => handleStart(selected)}
onRemove={() => handleRemove(selected)}
/>
) : null;
const _mutationError = mutationError ? (
<Message error>
<MessageTitle>Ooops!</MessageTitle>
<MessageDescription>{mutationError}</MessageDescription>
</Message>
) : null;
const _items = !_loading ? (
<ReduxForm form={TABLE_FORM_NAME}>
{props => (
<SnapshotsList
snapshots={_values}
onStart={snapshot => handleStart([snapshot])}
onRemove={snapshot => handleRemove([snapshot])}
selected={selected}
sortBy={sortBy}
sortOrder={sortOrder}
onSortBy={handleSortBy}
toggleSelectAll={toggleSelectAll}
allSelected={_values.length && selected.length === _values.length}
/>
)}
</ReduxForm>
) : null;
return ( return (
<ViewContainer main> <ViewContainer main>
<ReduxForm form={MENU_FORM_NAME}>
{props => (
<ToolbarForm
{...props}
searchLabel="Filter Snapshots"
searchPlaceholder="Search for name, created...."
searchable={!_loading}
actionLabel="Create Snapshot"
actionable={!createSnapshotOpen}
onActionClick={() => toggleCreateSnapshotOpen(true)}
/>
)}
</ReduxForm>
{_loading}
{_error} {_error}
<SnapshotsListForm {_mutationError}
snapshots={_values} {_createSnapshot}
loading={_loading} {_items}
onAction={handleAction} {_footer}
selected={selected}
/>
</ViewContainer> </ViewContainer>
); );
}; };
export default compose( export default compose(
withRouter,
graphql(StartSnapshot, { name: 'start' }), graphql(StartSnapshot, { name: 'start' }),
graphql(RemoveSnapshot, { name: 'remove' }), graphql(RemoveSnapshot, { name: 'remove' }),
graphql(CreateSnapshotMutation, { name: 'createSnapshot' }),
graphql(GetSnapshots, { graphql(GetSnapshots, {
options: ({ match }) => ({ options: ({ match }) => ({
pollInterval: 1000, pollInterval: 1000,
@ -73,19 +162,17 @@ export default compose(
name: get(match, 'params.instance') name: get(match, 'params.instance')
} }
}), }),
props: ({ data: { loading, error, variables, ...rest } }) => { props: ({ data: { loading, error, variables, refetch, ...rest } }) => {
const { name } = variables; const { name } = variables;
const instance = find(get(rest, 'machines', []), ['name', name]); const instance = find(get(rest, 'machines', []), ['name', name]);
const snapshots = get( const snapshots = get(instance, 'snapshots', []).map(
instance, ({ created, updated, ...rest }) => ({
'snapshots', ...rest,
[] created: moment.utc(created).unix(),
).map(({ created, updated, ...rest }) => ({ updated: moment.utc(updated).unix()
...rest, })
created: moment.utc(created).unix(), );
updated: moment.utc(updated).unix()
}));
const index = GenIndex( const index = GenIndex(
snapshots.map(({ name, ...rest }) => ({ ...rest, id: name })) snapshots.map(({ name, ...rest }) => ({ ...rest, id: name }))
@ -96,85 +183,230 @@ export default compose(
snapshots, snapshots,
instance, instance,
loading, loading,
error error,
refetch
}; };
} }
}), }),
connect( connect(
(state, { index, snapshots = [], ...rest }) => { ({ form, values }, { index, snapshots = [], ...rest }) => {
const form = get(state, 'form.snapshots-list.values', {}); const tableValues = get(form, `${TABLE_FORM_NAME}.values`) || {};
const filter = get(form, 'filter'); const filter = get(form, `${MENU_FORM_NAME}.values.filter`, false);
const sort = get(form, 'sort');
const values = filter // check whether the table form has an error
const tableMutationError = get(form, `${TABLE_FORM_NAME}.error`, null);
// check whether the create form has an error
const createMutationError = get(form, `${CREATE_FORM_NAME}.error`, null);
// check whether the main form is submitting
const submitting = get(form, `${TABLE_FORM_NAME}.submitting`, false);
const selected = Object.keys(tableValues)
.filter(key => Boolean(tableValues[key]))
.map(name => find(snapshots, ['name', name]))
.filter(Boolean);
const sortBy = get(values, 'snapshots-list-sort-by', 'name');
const sortOrder = get(values, 'snapshots-list-sort-order', 'asc');
const createSnapshotOpen = get(values, 'snapshots-create-open', false);
// if user is searching something, get items that match that query
const filtered = filter
? index.search(filter).map(({ ref }) => find(snapshots, ['name', ref])) ? index.search(filter).map(({ ref }) => find(snapshots, ['name', ref]))
: snapshots; : snapshots;
const selected = Object.keys(form) // from filtered instances, sort asc
.filter(key => Boolean(form[key])) // set's mutating flag
.map(name => find(values, ['name', name])) const ascSorted = sort(filtered, [sortBy]).map(({ id, ...item }) => ({
.filter(Boolean) ...item,
.map(({ name }) => find(snapshots, ['name', name])) id,
.filter(Boolean); mutating: get(values, `${id}-mutating`, false)
}));
const allowedActions = {
start: selected.length === 1,
remove: true
};
// get mutating statuses
const statuses = {
starting: get(values, 'snapshot-list-starting', false),
removing: get(values, 'snapshot-list-removeing', false)
};
return { return {
...rest, ...rest,
snapshots: sortBy(values, value => get(value, sort)), snapshots: sortOrder === 'asc' ? ascSorted : ascSorted.reverse(),
selected selected,
sortBy,
sortOrder,
submitting,
mutationError: tableMutationError || createMutationError,
allowedActions,
statuses,
createSnapshotOpen
}; };
}, },
(dispatch, { create, start, remove, instance, history, match }) => ({ (dispatch, ownProps) => {
handleAction: ({ name, items = [] }) => { const {
const form = 'snapshots-list'; create,
start,
remove,
instance,
history,
match,
createSnapshot,
refetch
} = ownProps;
const types = { return {
start: () => handleSortBy: (newSortBy, sortOrder) => {
Promise.resolve(dispatch(startSubmit(form))).then(() => dispatch([
Promise.all( set({
items.map(({ name }) => name: `snapshots-list-sort-order`,
start({ variables: { id: instance.id, snapshot: name } }) value: sortOrder === 'desc' ? 'asc' : 'desc'
) }),
) set({
), name: `snapshots-list-sort-by`,
delete: () => value: newSortBy
Promise.resolve(dispatch(startSubmit(form))).then(() => })
Promise.all( ]);
items.map(({ name }) => },
remove({ variables: { id: instance.id, snapshot: name } }) toggleCreateSnapshotOpen: value =>
)
)
),
create: () =>
Promise.resolve(
history.push(`/instances/${instance.name}/snapshots/~create`)
)
};
const handleError = error => {
dispatch( dispatch(
stopSubmit(form, { set({
_error: error.graphQLErrors name: `snapshots-create-open`,
.map(({ message }) => message) value
.join('\n') })
),
toggleSelectAll: ({ selected = [], snapshots = [] }) => () => {
const same = selected.length === snapshots.length;
const hasSelected = selected.length > 0;
// none are selected, toggle to all
if (!hasSelected) {
return dispatch(
snapshots.map(({ name }) => change(TABLE_FORM_NAME, name, true))
);
}
// all are selected, toggle to none
if (hasSelected && same) {
return dispatch(
snapshots.map(({ name }) => change(TABLE_FORM_NAME, name, false))
);
}
// some are selected, toggle to all
if (hasSelected && !same) {
return dispatch(
snapshots.map(({ name }) => change(TABLE_FORM_NAME, name, true))
);
}
},
handleCreateSnapshot: async ({ name }) => {
const [err] = await intercept(
createSnapshot({
variables: { name, id: instance.id }
}) })
); );
};
const handleSuccess = () => { if (err) {
return dispatch(
stopSubmit(form, {
_error: parseError(error)
})
);
}
dispatch( dispatch(
items set({
.map(({ name: field }) => change(form, field, false)) name: `snapshots-create-open`,
.concat([stopSubmit(form)]) value: false
})
); );
}; },
return ( handleAction: async ({ name, selected = [] }) => {
types[name] && const action = ownProps[name];
types[name]() const gerund = `${name}ing`;
.then(handleSuccess)
.catch(handleError) // flips submitting flag to true so that we can disable everything
); const flipSubmitTrue = startSubmit(TABLE_FORM_NAME);
}
}) // sets (starting/rebooting/etc) to true so that we can, for instance,
// have a spinner on the correct button
const setIngTrue = set({
name: `snapshot-list-${gerund}`,
value: true
});
// sets the individual item mutation flags so that we can show a
// spinner in the row
const setMutatingTrue = selected.map(({ id }) =>
set({ name: `${id}-mutating`, value: true })
);
// wait for everything to finish and catch the error
const [err] = await intercept(
Promise.resolve(
dispatch([flipSubmitTrue, setIngTrue, ...setMutatingTrue])
).then(() => {
// starts all the mutations for all the selected items
return Promise.all(
selected.map(({ name }) =>
action({ variables: { id: instance.id, snapshot: name } })
)
);
})
);
// reverts submitting flag to false and propagates the error if it exists
const flipSubmitFalse = stopSubmit(TABLE_FORM_NAME, {
_error: err && parseError(err)
});
// if no error, clears selected
const clearSelected = !err && reset(TABLE_FORM_NAME);
// reverts (starting/rebooting/etc) to false
const setIngFalse = set({
name: `snapshot-list-${gerund}`,
value: false
});
// reverts the individual item mutation flags
// when action === remove, let it stay spinning
const setMutatingFalse =
name !== 'remove' &&
selected.map(({ id }) =>
set({ name: `${id}-mutating`, value: false })
);
const actions = [
flipSubmitFalse,
clearSelected,
setIngFalse,
...setMutatingFalse
].filter(Boolean);
// refetch list - even though we poll anyway - after clearing everything
return Promise.resolve(dispatch(actions)).then(() => refetch());
}
};
},
(stateProps, dispatchProps, ownProps) => {
const { selected, snapshots, sortBy, sortOrder } = stateProps;
const { toggleSelectAll } = dispatchProps;
return {
...ownProps,
...stateProps,
selected,
snapshots,
...dispatchProps,
toggleSelectAll: toggleSelectAll({ selected, snapshots })
};
}
) )
)(Snapshots); )(Snapshots);

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { compose, graphql } from 'react-apollo'; import { compose, graphql } from 'react-apollo';
import find from 'lodash.find'; import find from 'lodash.find';
import get from 'lodash.get'; import get from 'lodash.get';

View File

@ -3,6 +3,7 @@ query instance($name: String!) {
id id
name name
snapshots { snapshots {
id
name name
state state
created created

View File

@ -16,8 +16,7 @@ import {
Firewall as InstanceFirewall, Firewall as InstanceFirewall,
Dns as InstanceDns, Dns as InstanceDns,
Snapshots as InstanceSnapshots, Snapshots as InstanceSnapshots,
Resize as InstanceResize, Resize as InstanceResize
CreateSnapshot as InstanceCreateSnapshot
} from '@containers/instances'; } from '@containers/instances';
export default () => ( export default () => (
@ -50,10 +49,6 @@ export default () => (
{/* Instance Sections */} {/* Instance Sections */}
<Switch> <Switch>
<Route path="/instances/~:action" component={() => null} /> <Route path="/instances/~:action" component={() => null} />
<Route
path="/instances/:instance/:section?/~create-snapshot"
component={() => null}
/>
<Route <Route
path="/instances/:instance/summary" path="/instances/:instance/summary"
exact exact
@ -108,16 +103,6 @@ export default () => (
exact exact
component={InstanceResize} component={InstanceResize}
/> />
<Route
path="/instances/~create-snapshot/:instance"
exact
component={InstanceCreateSnapshot}
/>
<Route
path="/instances/:instance/snapshots/~create"
exact
component={InstanceCreateSnapshot}
/>
</Switch> </Switch>
<Route path="/" exact component={() => <Redirect to="/instances" />} /> <Route path="/" exact component={() => <Redirect to="/instances" />} />

View File

@ -1,6 +1,6 @@
{ {
"name": "joyent-ui-toolkit", "name": "joyent-ui-toolkit",
"version": "4.0.0", "version": "4.0.1",
"license": "MPL-2.0", "license": "MPL-2.0",
"repository": "github:yldio/joyent-portal", "repository": "github:yldio/joyent-portal",
"main": "dist/umd/index.js", "main": "dist/umd/index.js",
@ -10,7 +10,6 @@
"lint-ci": "eslint . --ext .js --ext .md", "lint-ci": "eslint . --ext .js --ext .md",
"lint": "eslint . --fix --ext .js --ext .md", "lint": "eslint . --fix --ext .js --ext .md",
"test-ci": "redrun test", "test-ci": "redrun test",
"test": "redrun -p lint jest",
"test": "NODE_ENV=test joyent-react-scripts test --env=jsdom", "test": "NODE_ENV=test joyent-react-scripts test --env=jsdom",
"compile:es": "babel src --out-dir dist/es --ignore spec.js", "compile:es": "babel src --out-dir dist/es --ignore spec.js",
"compile:umd": "UMD=1 babel src --out-dir dist/umd --ignore spec.js", "compile:umd": "UMD=1 babel src --out-dir dist/umd --ignore spec.js",