334 lines
9.7 KiB
JavaScript
334 lines
9.7 KiB
JavaScript
import React, { Fragment, Component } from 'react';
|
||
import { If, Then, Else } from 'react-if';
|
||
import ReduxForm from 'declarative-redux-form';
|
||
import { connect } from 'react-redux';
|
||
import { set } from 'react-redux-values';
|
||
import { Field } from 'redux-form';
|
||
import { compose, graphql } from 'react-apollo';
|
||
import { Link } from 'react-router-dom';
|
||
import { Margin, Padding } from 'styled-components-spacing';
|
||
import Flex, { FlexItem } from 'styled-flex-component';
|
||
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
||
import get from 'lodash.get';
|
||
import sort from 'lodash.sortby';
|
||
import reverse from 'lodash.reverse';
|
||
import find from 'lodash.find';
|
||
import Fuse from 'fuse.js';
|
||
|
||
import Step, {
|
||
Header as StepHeader,
|
||
Description as StepDescription,
|
||
Preview as StepPreview,
|
||
Outlet as StepOutlet
|
||
} from 'joyent-ui-resource-step';
|
||
|
||
import {
|
||
H2,
|
||
Label,
|
||
Divider,
|
||
Button,
|
||
FormGroup,
|
||
FormLabel,
|
||
Input,
|
||
TemplateIcon,
|
||
StatusLoader,
|
||
Message,
|
||
MessageTitle,
|
||
MessageDescription
|
||
} from 'joyent-ui-toolkit';
|
||
|
||
import TemplatesList, {
|
||
Item as TemplatesItem,
|
||
EmptyCard as TemplatesEmptyCard,
|
||
EmptyRow as TemplatesEmptyRow,
|
||
LoadingRow
|
||
} from '@components/templates';
|
||
|
||
import { Global } from '@state/global.js';
|
||
import GetTemplate from '@graphql/get-template.gql';
|
||
import ListTemplates from '@graphql/list-templates.gql';
|
||
import { Forms, Values } from '@root/constants';
|
||
|
||
const { SGC_T_F, SGC_F_F } = Forms;
|
||
const { SGC_T_SB_V, SGC_T_SO_V } = Values;
|
||
|
||
class Template extends Component {
|
||
getSnapshotBeforeUpdate(prevProps, prevState) {
|
||
const { preview: prev } = prevProps;
|
||
const { preview: next, onDefocus, readOnly } = this.props;
|
||
|
||
if (!(readOnly && prev !== next && onDefocus)) {
|
||
return;
|
||
}
|
||
|
||
onDefocus(next);
|
||
}
|
||
|
||
render() {
|
||
const {
|
||
loading = false,
|
||
empty = false,
|
||
handleGetValue,
|
||
preview = {},
|
||
initialValues,
|
||
templates = [],
|
||
filter = '',
|
||
sortBy = 'name',
|
||
sortOrder = 'asc',
|
||
expanded = false,
|
||
readOnly = false,
|
||
error = null,
|
||
handleSortBy,
|
||
...props
|
||
} = this.props;
|
||
|
||
return (
|
||
<Step
|
||
name="template"
|
||
getValue={handleGetValue}
|
||
readOnly={readOnly}
|
||
expanded={readOnly ? false : expanded}
|
||
{...props}
|
||
>
|
||
<StepHeader icon={<TemplateIcon />}>Select template</StepHeader>
|
||
<StepDescription>
|
||
Select the template you’d like to deloy your instances from. Once a
|
||
Service Group is deployed with a templates, any changes to that
|
||
template will not effect the acting service group.
|
||
</StepDescription>
|
||
<StepPreview>
|
||
<Margin top="5">
|
||
<If condition={loading && !preview.name}>
|
||
<Then>
|
||
<StatusLoader />
|
||
</Then>
|
||
<Else>
|
||
<Fragment>
|
||
<Margin bottom="2">
|
||
<H2>{preview.name}</H2>
|
||
</Margin>
|
||
<Flex alignCenter>
|
||
<FlexItem>
|
||
<Padding right="3">
|
||
<Label inline>{preview.image}</Label>
|
||
</Padding>
|
||
</FlexItem>
|
||
<Divider vertical />
|
||
<FlexItem>
|
||
<Padding right="3" left="3">
|
||
<Label inline>{preview.package}</Label>
|
||
</Padding>
|
||
</FlexItem>
|
||
<Divider vertical />
|
||
<FlexItem>
|
||
<Padding right="3" left="3">
|
||
<Label inline>
|
||
{distanceInWordsToNow(preview.created)}
|
||
</Label>
|
||
</Padding>
|
||
</FlexItem>
|
||
</Flex>
|
||
</Fragment>
|
||
</Else>
|
||
</If>
|
||
</Margin>
|
||
</StepPreview>
|
||
<StepOutlet>
|
||
{({ next }) => (
|
||
<Margin top="5">
|
||
<If condition={error}>
|
||
<Then>
|
||
<Margin bottom="3">
|
||
<Message error>
|
||
<MessageTitle>Ooops!</MessageTitle>
|
||
<MessageDescription>
|
||
An error occurred while loading your templates
|
||
</MessageDescription>
|
||
</Message>
|
||
</Margin>
|
||
</Then>
|
||
</If>
|
||
<ReduxForm form={SGC_F_F}>
|
||
{props => (
|
||
<If condition={empty}>
|
||
<Else>
|
||
<Margin bottom="3">
|
||
<FormGroup name="filter" field={Field}>
|
||
<FormLabel>Filter</FormLabel>
|
||
<Margin top="0.5">
|
||
<Input disabled={!(filter || templates.length)} />
|
||
</Margin>
|
||
</FormGroup>
|
||
</Margin>
|
||
</Else>
|
||
</If>
|
||
)}
|
||
</ReduxForm>
|
||
<ReduxForm form={SGC_T_F} initialValues={initialValues}>
|
||
{({ pristine }) => (
|
||
<Fragment>
|
||
<If condition={empty}>
|
||
<Then>
|
||
<TemplatesEmptyCard />
|
||
</Then>
|
||
<Else>
|
||
<TemplatesList
|
||
{...props}
|
||
sortBy={sortBy}
|
||
sortOrder={sortOrder}
|
||
onSortBy={newSortBy =>
|
||
handleSortBy(newSortBy, { sortOrder, sortBy })
|
||
}
|
||
>
|
||
<If condition={templates.length}>
|
||
<Then>
|
||
<Fragment>
|
||
{templates.map(({ id, ...template }) => (
|
||
<TemplatesItem
|
||
key={id}
|
||
id={id}
|
||
{...template}
|
||
/>
|
||
))}
|
||
</Fragment>
|
||
</Then>
|
||
<Else>
|
||
<If condition={loading}>
|
||
<Then>
|
||
<LoadingRow />
|
||
</Then>
|
||
<Else>
|
||
<TemplatesEmptyRow />
|
||
</Else>
|
||
</If>
|
||
</Else>
|
||
</If>
|
||
</TemplatesList>
|
||
</Else>
|
||
</If>
|
||
<Margin top="5">
|
||
<Button
|
||
type="button"
|
||
disabled={pristine}
|
||
component={Link}
|
||
to={next}
|
||
>
|
||
Save
|
||
</Button>
|
||
</Margin>
|
||
</Fragment>
|
||
)}
|
||
</ReduxForm>
|
||
</Margin>
|
||
)}
|
||
</StepOutlet>
|
||
</Step>
|
||
);
|
||
}
|
||
}
|
||
|
||
export default compose(
|
||
graphql(ListTemplates, {
|
||
options: ({ location }) => {
|
||
const tmpl = Global().query.template;
|
||
|
||
return {
|
||
ssr: true,
|
||
fetchPolicy: tmpl ? 'cache-only' : 'cache-and-network'
|
||
};
|
||
},
|
||
props: ({ data: { error, templates = [], networkStatus } }) => ({
|
||
error,
|
||
loading: networkStatus === 1,
|
||
templates,
|
||
index: new Fuse(templates, {
|
||
keys: ['id', 'name', 'created']
|
||
})
|
||
})
|
||
}),
|
||
graphql(GetTemplate, {
|
||
options: ({ match }) => {
|
||
const tmpl = Global().query.template;
|
||
|
||
return {
|
||
ssr: true,
|
||
fetchPolicy: tmpl ? 'cache-and-network' : 'cache-only',
|
||
variables: {
|
||
id: tmpl
|
||
}
|
||
};
|
||
},
|
||
props: ({ data }) => {
|
||
const { variables, networkStatus, error, template } = data;
|
||
|
||
if (!variables.id) {
|
||
return {};
|
||
}
|
||
|
||
return {
|
||
readOnly: Boolean(variables.id),
|
||
loading: networkStatus === 1,
|
||
error,
|
||
preview: template
|
||
};
|
||
}
|
||
}),
|
||
connect(
|
||
(state, ownProps) => {
|
||
const { form, values: flags } = state;
|
||
const { templates: items, preview = {}, loading, index } = ownProps;
|
||
|
||
const filter = get(form, `${SGC_F_F}.values.filter`, '');
|
||
const sortBy = get(flags, SGC_T_SB_V, 'name');
|
||
const sortOrder = get(flags, SGC_T_SO_V, 'asc');
|
||
|
||
const templates = sort(filter.length ? index.search(filter) : items, [
|
||
sortBy
|
||
]);
|
||
|
||
return {
|
||
filter,
|
||
empty: !filter && !loading && !templates.length,
|
||
handleGetValue: () => {
|
||
return find(templates, [
|
||
'id',
|
||
get(form, `${SGC_T_F}.values.template`)
|
||
]);
|
||
},
|
||
initialValues: {
|
||
template: preview.id
|
||
},
|
||
templates: sortOrder === 'asc' ? templates : reverse(templates),
|
||
sortBy,
|
||
sortOrder
|
||
};
|
||
},
|
||
(dispatch, { templates = [], refetch, removeGroup }) => {
|
||
return {
|
||
handleSortBy: (newSortBy, { sortBy: currentSortBy, sortOrder }) => {
|
||
// sort prop is the same, toggle
|
||
if (currentSortBy === newSortBy) {
|
||
return dispatch(
|
||
set({
|
||
name: SGC_T_SO_V,
|
||
value: sortOrder === 'desc' ? 'asc' : 'desc'
|
||
})
|
||
);
|
||
}
|
||
|
||
dispatch([
|
||
set({
|
||
name: SGC_T_SO_V,
|
||
value: 'desc'
|
||
}),
|
||
set({
|
||
name: SGC_T_SB_V,
|
||
value: newSortBy
|
||
})
|
||
]);
|
||
}
|
||
};
|
||
}
|
||
)
|
||
)(Template);
|