feat(my-joy-beta): revise snapshots implementation
according to new designs fixes #872
This commit is contained in:
parent
8eecebfe47
commit
3caaebb0e9
@ -4826,6 +4826,7 @@ exports[`renders <KeyValue method="edit" /> without throwing 1`] = `
|
|||||||
<b>
|
<b>
|
||||||
undefined:
|
undefined:
|
||||||
</b>
|
</b>
|
||||||
|
,
|
||||||
<span />
|
<span />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1871,6 +1871,7 @@ exports[`renders <EditForm /> without throwing 1`] = `
|
|||||||
<b>
|
<b>
|
||||||
undefined:
|
undefined:
|
||||||
</b>
|
</b>
|
||||||
|
,
|
||||||
<span />
|
<span />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1809,6 +1809,7 @@ exports[`renders <EditForm /> without throwing 1`] = `
|
|||||||
<b>
|
<b>
|
||||||
undefined:
|
undefined:
|
||||||
</b>
|
</b>
|
||||||
|
,
|
||||||
<span />
|
<span />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
});
|
@ -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: {
|
||||||
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
168
packages/my-joy-beta/src/components/instances/footer.js
Normal file
168
packages/my-joy-beta/src/components/instances/footer.js
Normal 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>
|
||||||
|
)
|
||||||
|
);
|
@ -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';
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
|
@ -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>⁣</FormLabel>
|
|
||||||
<Select
|
|
||||||
value="actions"
|
|
||||||
disabled={!items.length || !selected.length}
|
|
||||||
onChange={handleActions}
|
|
||||||
fluid
|
|
||||||
>
|
|
||||||
<option value="actions" selected disabled>
|
|
||||||
≡
|
|
||||||
</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>⁣</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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
@ -80,4 +84,4 @@ it('renders <Metadata metadata /> without throwing', () => {
|
|||||||
)
|
)
|
||||||
.toJSON()
|
.toJSON()
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -116,4 +120,4 @@ it('renders <Tags tags /> without throwing', () => {
|
|||||||
)
|
)
|
||||||
.toJSON()
|
.toJSON()
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -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);
|
|
@ -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>;
|
||||||
|
@ -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';
|
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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';
|
||||||
|
@ -3,6 +3,7 @@ query instance($name: String!) {
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
snapshots {
|
snapshots {
|
||||||
|
id
|
||||||
name
|
name
|
||||||
state
|
state
|
||||||
created
|
created
|
||||||
|
@ -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" />} />
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user