mirror of
https://github.com/yldio/copilot.git
synced 2024-11-28 06:00:06 +02:00
feat(ui-toolkit, cp-fronted, portal-api): Env variables input redesign
This commit is contained in:
parent
2eb7f4197f
commit
2fb4a77c96
@ -27,9 +27,13 @@
|
||||
"jest-cli": "^20.0.4",
|
||||
"joyent-manifest-editor": "^1.0.0",
|
||||
"joyent-ui-toolkit": "^1.1.0",
|
||||
"js-yaml": "^3.9.1",
|
||||
"lodash.find": "^4.6.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.remove": "^4.7.0",
|
||||
"lodash.uniq": "^4.5.0",
|
||||
"normalized-styled-components": "^1.0.8",
|
||||
"param-case": "^2.1.1",
|
||||
"prop-types": "^15.5.10",
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
.CodeMirror {
|
||||
border: solid 1px #d8d8d8;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
html, body, #root {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Field } from 'redux-form';
|
||||
import styled from 'styled-components';
|
||||
import SimpleTable from 'react-simple-table';
|
||||
import { Row, Col } from 'react-styled-flexboxgrid';
|
||||
import Bundle from 'react-bundle';
|
||||
import remcalc from 'remcalc';
|
||||
import forceArray from 'force-array';
|
||||
import is from 'styled-is';
|
||||
|
||||
import { Loader } from '@components/messaging';
|
||||
|
||||
@ -19,36 +19,56 @@ import {
|
||||
ProgressbarItem,
|
||||
ProgressbarButton,
|
||||
H3,
|
||||
P,
|
||||
typography,
|
||||
StatusLoader
|
||||
Divider,
|
||||
Chevron
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
|
||||
const EnvironmentChevron = Chevron.extend`
|
||||
float: right;
|
||||
`;
|
||||
|
||||
const EnvironmentDivider = Divider.extend`
|
||||
margin-top: ${remcalc(34)};
|
||||
`;
|
||||
|
||||
const ServiceDivider = Divider.extend`
|
||||
margin: ${remcalc(13)} ${remcalc(-20)} 0 ${remcalc(-20)};
|
||||
`;
|
||||
|
||||
const Dl = styled.dl`
|
||||
margin: ${remcalc(13)} ${remcalc(19)};
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const ServiceName = H3.extend`
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-bottom: ${remcalc(5)};
|
||||
line-height: 1.6;
|
||||
font-weight: 600;
|
||||
font-size: ${remcalc(18)};
|
||||
`;
|
||||
|
||||
const ServiceCard = Card.extend`
|
||||
min-height: ${remcalc(72)};
|
||||
`;
|
||||
|
||||
const ImageTitle = ServiceName.extend`
|
||||
const ImageTitle = H3.extend`
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const Image = styled.span`
|
||||
${typography.fontFamily};
|
||||
font-size: ${remcalc(15)};
|
||||
`;
|
||||
|
||||
const ServiceEnvironmentTitle = P.extend`
|
||||
margin: ${remcalc(13)} 0 0 0;
|
||||
|
||||
${is('expanded')`
|
||||
margin-bottom: ${remcalc(13)};
|
||||
`};
|
||||
`;
|
||||
|
||||
const ButtonsRow = Row.extend`
|
||||
margin-top: ${remcalc(29)};
|
||||
margin-bottom: ${remcalc(60)};
|
||||
margin: ${remcalc(29)} 0 ${remcalc(60)} 0;
|
||||
`;
|
||||
|
||||
const FilenameContainer = styled.span`
|
||||
@ -64,17 +84,36 @@ const FilenameInput = styled(Input)`
|
||||
order: 0;
|
||||
flex: 1 1 auto;
|
||||
align-self: stretch;
|
||||
margin: 0 0 ${remcalc(13)} 0;
|
||||
`;
|
||||
|
||||
const FilenameRemove = Button.extend`
|
||||
order: 0;
|
||||
flex: 0 1 auto;
|
||||
align-self: auto;
|
||||
margin: ${remcalc(8)};
|
||||
margin-right: 0;
|
||||
margin: 0 0 0 ${remcalc(8)};
|
||||
height: ${remcalc(48)};
|
||||
`;
|
||||
|
||||
const FileCard = Card.extend`
|
||||
padding: ${remcalc(24)} ${remcalc(19)};
|
||||
`;
|
||||
|
||||
const ServiceCard = Card.extend`
|
||||
padding: ${remcalc(13)} ${remcalc(19)};
|
||||
min-height: initial;
|
||||
`;
|
||||
|
||||
const Subtitle = H3.extend`
|
||||
margin-top: ${remcalc(34)};
|
||||
margin-bottom: ${remcalc(3)};
|
||||
`;
|
||||
|
||||
const Description = P.extend`
|
||||
margin-top: ${remcalc(3)};
|
||||
margin-bottom: ${remcalc(20)};
|
||||
`;
|
||||
|
||||
class ManifestEditorBundle extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
@ -112,18 +151,20 @@ class ManifestEditorBundle extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const MEditor = ({ input, defaultValue }) =>
|
||||
const MEditor = ({ input, defaultValue, readOnly }) =>
|
||||
<ManifestEditorBundle
|
||||
mode="yaml"
|
||||
{...input}
|
||||
value={input.value || defaultValue}
|
||||
readOnly={readOnly}
|
||||
/>;
|
||||
|
||||
const EEditor = ({ input, defaultValue }) =>
|
||||
const EEditor = ({ input, defaultValue, readOnly }) =>
|
||||
<ManifestEditorBundle
|
||||
mode="ini"
|
||||
{...input}
|
||||
value={input.value || defaultValue}
|
||||
readOnly={readOnly}
|
||||
/>;
|
||||
|
||||
export const Name = ({ handleSubmit, onCancel, dirty }) =>
|
||||
@ -137,7 +178,7 @@ export const Name = ({ handleSubmit, onCancel, dirty }) =>
|
||||
</Col>
|
||||
</Row>
|
||||
<ButtonsRow>
|
||||
<Button onClick={onCancel} secondary>
|
||||
<Button type="button" onClick={onCancel} secondary>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={!dirty}>
|
||||
@ -156,53 +197,74 @@ export const Manifest = ({
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Field name="manifest" defaultValue={defaultValue} component={MEditor} />
|
||||
<ButtonsRow>
|
||||
<Button onClick={onCancel} secondary>
|
||||
<Button type="button" onClick={onCancel} secondary>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!(dirty || !loading || defaultValue.length)}
|
||||
loading={loading}
|
||||
type="submit"
|
||||
>
|
||||
{loading ? <StatusLoader /> : 'Environment'}
|
||||
Environment
|
||||
</Button>
|
||||
</ButtonsRow>
|
||||
</form>;
|
||||
|
||||
const Filename = ({ name, onRemoveFile }) =>
|
||||
<FilenameContainer>
|
||||
<FilenameInput
|
||||
type="text"
|
||||
placeholder="Filename including extension…"
|
||||
defaultValue={name}
|
||||
/>
|
||||
<FilenameRemove type="button" onClick={onRemoveFile} secondary>
|
||||
Remove
|
||||
</FilenameRemove>
|
||||
</FilenameContainer>;
|
||||
const File = ({ id, name, value, onRemoveFile, readOnly }) => {
|
||||
const removeButton = !readOnly
|
||||
? <FilenameRemove type="button" onClick={onRemoveFile} secondary>
|
||||
Remove
|
||||
</FilenameRemove>
|
||||
: null;
|
||||
|
||||
export const Files = ({ loading, files, onRemoveFile }) => {
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
const _files = files.map(({ id, name, value }) =>
|
||||
<div key={id}>
|
||||
<FormGroup name={`file-name-${id}`} reduxForm>
|
||||
<FormMeta left />
|
||||
<Filename name={name} onRemoveFile={() => onRemoveFile(id)} />
|
||||
</FormGroup>
|
||||
<Field
|
||||
const fileEditor = !readOnly
|
||||
? <Field
|
||||
name={`file-value-${id}`}
|
||||
defaultValue={value}
|
||||
component={EEditor}
|
||||
/>
|
||||
</div>
|
||||
: <EEditor input={{ value }} readOnly />;
|
||||
|
||||
const input = !readOnly
|
||||
? <FilenameInput type="text" placeholder="Filename including extension…" />
|
||||
: <FilenameInput
|
||||
type="text"
|
||||
placeholder="Filename including extension…"
|
||||
value={name}
|
||||
/>;
|
||||
|
||||
return (
|
||||
<FileCard>
|
||||
<FormGroup name={`file-name-${id}`} reduxForm={!readOnly}>
|
||||
<FilenameContainer>
|
||||
{input}
|
||||
{removeButton}
|
||||
</FilenameContainer>
|
||||
</FormGroup>
|
||||
{fileEditor}
|
||||
</FileCard>
|
||||
);
|
||||
};
|
||||
|
||||
const Files = ({ files, onAddFile, onRemoveFile, readOnly }) => {
|
||||
const footer = !readOnly
|
||||
? <Button type="button" onClick={onAddFile} secondary>
|
||||
Create new .env file
|
||||
</Button>
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<H3>Files:</H3>
|
||||
{_files}
|
||||
{files.map(({ id, ...rest }) =>
|
||||
<File
|
||||
key={id}
|
||||
id={id}
|
||||
onRemoveFile={() => onRemoveFile(id)}
|
||||
readOnly={readOnly}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
{footer}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -215,66 +277,101 @@ export const Environment = ({
|
||||
dirty,
|
||||
defaultValue = '',
|
||||
files = [],
|
||||
readOnly = false,
|
||||
loading
|
||||
}) =>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Field name="environment" defaultValue={defaultValue} component={EEditor} />
|
||||
<Files files={files} onRemoveFile={onRemoveFile} loading={loading} />
|
||||
<ButtonsRow>
|
||||
<Button onClick={onCancel} secondary>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="button" onClick={onAddFile} secondary>
|
||||
Add File
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!(dirty || !loading || defaultValue.length)}
|
||||
type="submit"
|
||||
>
|
||||
{loading ? <StatusLoader /> : 'Review'}
|
||||
</Button>
|
||||
</ButtonsRow>
|
||||
</form>;
|
||||
}) => {
|
||||
const envEditor = !readOnly
|
||||
? <Field
|
||||
name="environment"
|
||||
defaultValue={defaultValue}
|
||||
component={EEditor}
|
||||
/>
|
||||
: <EEditor input={{ value: defaultValue }} readOnly />;
|
||||
|
||||
const footerDivider = !readOnly ? <EnvironmentDivider /> : null;
|
||||
|
||||
const footer = !readOnly
|
||||
? <ButtonsRow>
|
||||
<Button type="button" onClick={onCancel} secondary>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!(dirty || !loading || defaultValue.length)}
|
||||
loading={loading}
|
||||
type="submit"
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</ButtonsRow>
|
||||
: null;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Subtitle>Global variables</Subtitle>
|
||||
<Description>
|
||||
These variables are going to be availabe for interpolation in the
|
||||
manifest
|
||||
</Description>
|
||||
{envEditor}
|
||||
<EnvironmentDivider />
|
||||
<Subtitle>Enviroment files</Subtitle>
|
||||
<Description>
|
||||
The variables from this files will be applied to the services that
|
||||
require them
|
||||
</Description>
|
||||
<Files
|
||||
files={files}
|
||||
onAddFile={onAddFile}
|
||||
onRemoveFile={onRemoveFile}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
{footerDivider}
|
||||
{footer}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const EnvironmentReview = ({ environment }) => {
|
||||
const value = environment
|
||||
.map(({ name, value }) => `${name}=${value}`)
|
||||
.join('\n');
|
||||
|
||||
return <EEditor input={{ value }} />;
|
||||
};
|
||||
|
||||
export const Review = ({
|
||||
handleSubmit,
|
||||
onEnvironmentToggle = () => null,
|
||||
onCancel,
|
||||
dirty,
|
||||
loading,
|
||||
environmentToggles,
|
||||
...state
|
||||
}) => {
|
||||
const serviceList = forceArray(state.services).map(({ name, config }) =>
|
||||
<ServiceCard key={name}>
|
||||
<ServiceName>
|
||||
{name}
|
||||
</ServiceName>
|
||||
<Dl>
|
||||
<dt>
|
||||
<ServiceName>
|
||||
{name}
|
||||
</ServiceName>
|
||||
</dt>
|
||||
<dt>
|
||||
<ImageTitle>Image:</ImageTitle> <Image>{config.image}</Image>
|
||||
</dt>
|
||||
{config.environment.length
|
||||
? <dt>
|
||||
<ImageTitle>Environment:</ImageTitle>
|
||||
</dt>
|
||||
: undefined}
|
||||
{config.environment.length
|
||||
? <SimpleTable
|
||||
columns={[
|
||||
{
|
||||
columnHeader: 'Name',
|
||||
path: 'name'
|
||||
},
|
||||
{
|
||||
columnHeader: 'Value',
|
||||
path: 'value'
|
||||
}
|
||||
]}
|
||||
data={config.environment}
|
||||
/>
|
||||
: undefined}
|
||||
</Dl>
|
||||
<ServiceDivider />
|
||||
<ServiceEnvironmentTitle
|
||||
expanded={environmentToggles[name]}
|
||||
onClick={() => onEnvironmentToggle(name)}
|
||||
>
|
||||
Environment variables{' '}
|
||||
<EnvironmentChevron
|
||||
down={!environmentToggles[name]}
|
||||
up={environmentToggles[name]}
|
||||
/>
|
||||
</ServiceEnvironmentTitle>
|
||||
{environmentToggles[name]
|
||||
? <EnvironmentReview environment={config.environment} />
|
||||
: null}
|
||||
</ServiceCard>
|
||||
);
|
||||
|
||||
@ -282,11 +379,11 @@ export const Review = ({
|
||||
<form onSubmit={handleSubmit}>
|
||||
{serviceList}
|
||||
<ButtonsRow>
|
||||
<Button onClick={onCancel} disabled={loading} secondary>
|
||||
<Button type="button" onClick={onCancel} disabled={loading} secondary>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={loading} type="submit">
|
||||
{loading ? <StatusLoader /> : 'Confirm and Deploy'}
|
||||
<Button disabled={loading} loading={loading} type="submit">
|
||||
Confirm and Deploy
|
||||
</Button>
|
||||
</ButtonsRow>
|
||||
</form>
|
||||
|
@ -2,15 +2,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Message } from 'joyent-ui-toolkit';
|
||||
|
||||
const ErrorMessage = ({
|
||||
title,
|
||||
message = 'Ooops, there\'s been an error'
|
||||
}) =>
|
||||
<Message
|
||||
title={title}
|
||||
message={message}
|
||||
type='ERROR'
|
||||
/>
|
||||
const ErrorMessage = ({ title, message = "Ooops, there's been an error" }) =>
|
||||
<Message title={title} message={message} type="ERROR" />;
|
||||
|
||||
ErrorMessage.propTypes = {
|
||||
title: PropTypes.string,
|
||||
|
@ -2,15 +2,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Message } from 'joyent-ui-toolkit';
|
||||
|
||||
const WarningMessage = ({
|
||||
title,
|
||||
message
|
||||
}) =>
|
||||
<Message
|
||||
title={title}
|
||||
message={message}
|
||||
type='WARNING'
|
||||
/>
|
||||
const WarningMessage = ({ title, message }) =>
|
||||
<Message title={title} message={message} type="WARNING" />;
|
||||
|
||||
WarningMessage.propTypes = {
|
||||
title: PropTypes.string,
|
||||
|
@ -9,13 +9,12 @@ import { Modal, ModalHeading, Button } from 'joyent-ui-toolkit'
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
|
||||
class DeploymentGroupDelete extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -40,7 +39,8 @@ class DeploymentGroupDelete extends Component {
|
||||
<ModalErrorMessage
|
||||
title='Ooops!'
|
||||
message='An error occurred while loading your deployment group.'
|
||||
onCloseClick={handleCloseClick} />
|
||||
onCloseClick={handleCloseClick}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@ -56,7 +56,8 @@ class DeploymentGroupDelete extends Component {
|
||||
<ModalErrorMessage
|
||||
title='Ooops!'
|
||||
message={`An error occured while attempting to delete the ${deploymentGroup.name} deployment group.`}
|
||||
onCloseClick={handleCloseClick} />
|
||||
onCloseClick={handleCloseClick}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@ -64,8 +65,7 @@ class DeploymentGroupDelete extends Component {
|
||||
const handleConfirmClick = evt => {
|
||||
deleteDeploymentGroup(deploymentGroup.id)
|
||||
.then(() => handleCloseClick())
|
||||
.catch((err) => {
|
||||
console.log('err = ', err);
|
||||
.catch(err => {
|
||||
this.setState({ error: err });
|
||||
});
|
||||
};
|
||||
|
@ -46,8 +46,9 @@ class DeploymentGroupImport extends Component {
|
||||
<LayoutContainer>
|
||||
{_title}
|
||||
<ErrorMessage
|
||||
title='Ooops!'
|
||||
message='An error occurred while importing your deployment groups.' />
|
||||
title="Ooops!"
|
||||
message="An error occurred while importing your deployment groups."
|
||||
/>
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import { Title } from '@components/navigation';
|
||||
import { ErrorMessage, Loader } from '@components/messaging';
|
||||
import DeploymentGroupsQuery from '@graphql/DeploymentGroups.gql';
|
||||
import DeploymentGroupsImportableQuery from '@graphql/DeploymentGroupsImportable.gql';
|
||||
import { H2, H3, Small, IconButton, BinIcon } from 'joyent-ui-toolkit';
|
||||
import { H3, Small, IconButton, BinIcon } from 'joyent-ui-toolkit';
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
|
||||
const DGsRows = Row.extend`
|
||||
@ -134,8 +134,9 @@ const DeploymentGroupList = ({
|
||||
<LayoutContainer>
|
||||
{_title}
|
||||
<ErrorMessage
|
||||
title='Ooops!'
|
||||
message='An error occured while loading your deployment groups.' />
|
||||
title="Ooops!"
|
||||
message="An error occured while loading your deployment groups."
|
||||
/>
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
64
packages/cp-frontend/src/containers/environment/index.js
Normal file
64
packages/cp-frontend/src/containers/environment/index.js
Normal file
@ -0,0 +1,64 @@
|
||||
import React, { Component } from 'react';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import ManifestQuery from '@graphql/Manifest.gql';
|
||||
|
||||
import { LayoutContainer } from '@components/layout';
|
||||
import { Title } from '@components/navigation';
|
||||
import { Loader, ErrorMessage, WarningMessage } from '@components/messaging';
|
||||
import { Environment } from '@components/manifest/edit-or-create';
|
||||
|
||||
const EnvironmentReadOnly = ({
|
||||
files = [],
|
||||
environment = '',
|
||||
loading,
|
||||
error
|
||||
}) => {
|
||||
const _title = <Title>Environment</Title>;
|
||||
|
||||
if (loading && !environment.length && !files.length) {
|
||||
return (
|
||||
<LayoutContainer center>
|
||||
{_title}
|
||||
<Loader />
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<LayoutContainer>
|
||||
{_title}
|
||||
<ErrorMessage
|
||||
title="Ooops!"
|
||||
message="An error occured while loading environment data."
|
||||
/>
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LayoutContainer>
|
||||
{_title}
|
||||
<Environment defaultValue={environment} files={files} readOnly />
|
||||
</LayoutContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default compose(
|
||||
graphql(ManifestQuery, {
|
||||
options: props => ({
|
||||
fetchPolicy: 'cache-and-network',
|
||||
variables: {
|
||||
deploymentGroupSlug: props.match.params.deploymentGroup
|
||||
}
|
||||
}),
|
||||
props: ({ data: { deploymentGroup, loading, error } }) => ({
|
||||
files: get(deploymentGroup, 'version.manifest.files', []),
|
||||
environment: get(deploymentGroup, 'version.manifest.environment', ''),
|
||||
loading,
|
||||
error
|
||||
})
|
||||
})
|
||||
)(EnvironmentReadOnly);
|
@ -1,8 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import InstancesQuery from '@graphql/Instances.gql';
|
||||
import { Row } from 'react-styled-flexboxgrid';
|
||||
import remcalc from 'remcalc';
|
||||
import forceArray from 'force-array';
|
||||
import sortBy from 'lodash.sortby';
|
||||
|
||||
@ -30,8 +28,9 @@ const InstanceList = ({ deploymentGroup, instances = [], loading, error }) => {
|
||||
<LayoutContainer>
|
||||
{_title}
|
||||
<ErrorMessage
|
||||
title='Ooops!'
|
||||
message='An error occured while loading your instances.' />
|
||||
title="Ooops!"
|
||||
message="An error occured while loading your instances."
|
||||
/>
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
@ -6,6 +6,10 @@ import { Redirect } from 'react-router-dom';
|
||||
import intercept from 'apr-intercept';
|
||||
import paramCase from 'param-case';
|
||||
import remove from 'lodash.remove';
|
||||
import flatten from 'lodash.flatten';
|
||||
import uniq from 'lodash.uniq';
|
||||
import find from 'lodash.find';
|
||||
import { safeLoad } from 'js-yaml';
|
||||
import uuid from 'uuid/v4';
|
||||
|
||||
import DeploymentGroupBySlugQuery from '@graphql/DeploymentGroupBySlug.gql';
|
||||
@ -22,13 +26,15 @@ import {
|
||||
Review
|
||||
} from '@components/manifest/edit-or-create';
|
||||
|
||||
const INTERPOLATE_REGEX = /\$([_a-z][_a-z0-9]*)/gi;
|
||||
|
||||
// TODO: move state to redux. why: because in redux we can cache transactional
|
||||
// state between refreshes
|
||||
class DeploymentGroupEditOrCreate extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { create, edit, files = [] } = props;
|
||||
const { create, files = [], manifest } = props;
|
||||
const type = create ? 'create' : 'edit';
|
||||
|
||||
const NameForm =
|
||||
@ -38,63 +44,59 @@ class DeploymentGroupEditOrCreate extends Component {
|
||||
destroyOnUnmount: true,
|
||||
forceUnregisterOnUnmount: true,
|
||||
asyncValidate: async ({ name = '' }) => {
|
||||
const [err] = await intercept(client.query({
|
||||
fetchPolicy: 'network-only',
|
||||
query: DeploymentGroupBySlugQuery,
|
||||
variables: {
|
||||
slug: paramCase(name.trim())
|
||||
}
|
||||
}));
|
||||
const [err, res] = await intercept(
|
||||
client.query({
|
||||
fetchPolicy: 'network-only',
|
||||
query: DeploymentGroupBySlugQuery,
|
||||
variables: {
|
||||
slug: paramCase(name.trim())
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (!err) {
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw { name: `"${name}" already exists!` };
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!res.data.deploymentGroups.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw { name: `"${name}" already exists!` };
|
||||
}
|
||||
})(Name);
|
||||
|
||||
const ManifestForm = reduxForm({
|
||||
form: `${type}-deployment-group`,
|
||||
destroyOnUnmount: true,
|
||||
forceUnregisterOnUnmount: true
|
||||
form: `${type}-deployment-group`
|
||||
})(Manifest);
|
||||
|
||||
const EnvironmentForm = reduxForm({
|
||||
form: `${type}-deployment-group`,
|
||||
destroyOnUnmount: true,
|
||||
forceUnregisterOnUnmount: true
|
||||
})(Environment);
|
||||
|
||||
const ReviewForm = reduxForm({
|
||||
form: `${type}-deployment-group`,
|
||||
destroyOnUnmount: true,
|
||||
forceUnregisterOnUnmount: true
|
||||
form: `${type}-deployment-group`
|
||||
})(Review);
|
||||
|
||||
if (!files.length) {
|
||||
files.push({
|
||||
id: uuid(),
|
||||
name: '',
|
||||
value: '#'
|
||||
});
|
||||
}
|
||||
|
||||
this.state = {
|
||||
type,
|
||||
defaultStage: create ? 'name' : 'edit',
|
||||
manifestStage: create ? 'manifest' : 'edit',
|
||||
name: '',
|
||||
manifest: '',
|
||||
environment: '',
|
||||
files,
|
||||
files: this.resolveManifestFiles(files, manifest),
|
||||
services: [],
|
||||
environmentToggles: {},
|
||||
loading: false,
|
||||
error: null,
|
||||
NameForm,
|
||||
ManifestForm,
|
||||
EnvironmentForm,
|
||||
ReviewForm
|
||||
ReviewForm,
|
||||
ManifestForm
|
||||
};
|
||||
|
||||
this.state.EnvironmentForm = this.getEnvironmentForm(
|
||||
this.state.files,
|
||||
manifest
|
||||
);
|
||||
|
||||
this.stages = {
|
||||
name: create && this.renderNameForm.bind(this),
|
||||
[create ? 'manifest' : 'edit']: this.renderManifestEditor.bind(this),
|
||||
@ -111,10 +113,77 @@ class DeploymentGroupEditOrCreate extends Component {
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
this.handleFileAdd = this.handleFileAdd.bind(this);
|
||||
this.handleRemoveFile = this.handleRemoveFile.bind(this);
|
||||
this.handleEnvironmentToggle = this.handleEnvironmentToggle.bind(this);
|
||||
}
|
||||
|
||||
if (edit) {
|
||||
setTimeout(this.getDeploymentGroup, 16);
|
||||
resolveManifestFiles(currentFiles = [], manifestStr = '') {
|
||||
if (!manifestStr.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let manifest = {};
|
||||
|
||||
try {
|
||||
manifest = safeLoad(manifestStr);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
|
||||
const services = manifest.services ? manifest.services : manifest;
|
||||
|
||||
const filenames = uniq(
|
||||
// eslint-disable-next-line camelcase
|
||||
flatten(Object.values(services).map(({ env_file }) => env_file))
|
||||
);
|
||||
|
||||
return filenames
|
||||
.filter(filename => !find(currentFiles, ['name', filename]))
|
||||
.map(this.getDefaultFile)
|
||||
.concat(currentFiles);
|
||||
}
|
||||
|
||||
getEnvironmentForm(files = [], manifest = '') {
|
||||
const { type } = this.state;
|
||||
|
||||
const initialValues = files.reduce(
|
||||
(acc, { id, name, value }) =>
|
||||
Object.assign(acc, {
|
||||
[`file-name-${id}`]: name,
|
||||
[`file-value-${id}`]: value
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
return reduxForm({
|
||||
form: `${type}-deployment-group`,
|
||||
initialValues
|
||||
})(Environment);
|
||||
}
|
||||
|
||||
getEnvironmentDefaultValue() {
|
||||
const { environment = '' } = this.props;
|
||||
const { manifest = '' } = this.state;
|
||||
|
||||
if (environment.length) {
|
||||
return environment;
|
||||
}
|
||||
|
||||
const names = manifest
|
||||
.match(INTERPOLATE_REGEX)
|
||||
.map(name => name.replace(/^\$/, ''));
|
||||
|
||||
const vars = uniq(names).map(name => `\n${name}=`).join('');
|
||||
|
||||
return `# define your interpolatable variables here\n${vars}`;
|
||||
}
|
||||
|
||||
getDefaultFile(name = '') {
|
||||
return {
|
||||
id: uuid(),
|
||||
name,
|
||||
value: '# define your environment variables here\n'
|
||||
};
|
||||
}
|
||||
|
||||
createDeploymentGroup = async () => {
|
||||
@ -176,9 +245,19 @@ class DeploymentGroupEditOrCreate extends Component {
|
||||
}
|
||||
|
||||
handleManifestSubmit({ manifest = '' }) {
|
||||
this.setState({ manifest: manifest || this.props.manifest }, () => {
|
||||
this.redirect({ stage: 'environment', prog: true });
|
||||
});
|
||||
const { files } = this.state;
|
||||
|
||||
const _manifest = manifest || this.props.manifest;
|
||||
const _files = this.resolveManifestFiles(files, _manifest);
|
||||
|
||||
const EnvironmentForm = this.getEnvironmentForm(_files, _manifest);
|
||||
|
||||
this.setState(
|
||||
{ manifest: _manifest, EnvironmentForm, files: _files },
|
||||
() => {
|
||||
this.redirect({ stage: 'environment', prog: true });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
handleEnvironmentSubmit(change) {
|
||||
@ -270,23 +349,33 @@ class DeploymentGroupEditOrCreate extends Component {
|
||||
const { history, create, deploymentGroup } = this.props;
|
||||
|
||||
history.push(create ? '/' : `/deployment-groups/${deploymentGroup.slug}`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
handleFileAdd() {
|
||||
const { files = [] } = this.state;
|
||||
|
||||
this.setState({
|
||||
files: this.state.files.concat([
|
||||
{
|
||||
id: uuid(),
|
||||
name: '',
|
||||
value: '#'
|
||||
}
|
||||
])
|
||||
files: files.concat([this.getDefaultFile()])
|
||||
});
|
||||
}
|
||||
|
||||
handleRemoveFile(fileId) {
|
||||
const { files = [] } = this.state;
|
||||
|
||||
this.setState({
|
||||
files: remove(this.state.files, ({ id }) => id !== fileId)
|
||||
files: remove(files, ({ id }) => id !== fileId)
|
||||
});
|
||||
}
|
||||
|
||||
handleEnvironmentToggle(serviceName) {
|
||||
const { environmentToggles } = this.state;
|
||||
|
||||
this.setState({
|
||||
environmentToggles: Object.assign({}, environmentToggles, {
|
||||
[serviceName]: !environmentToggles[serviceName]
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@ -327,40 +416,40 @@ class DeploymentGroupEditOrCreate extends Component {
|
||||
}
|
||||
|
||||
renderEnvironmentEditor() {
|
||||
const { EnvironmentForm } = this.state;
|
||||
const { EnvironmentForm, files, loading } = this.state;
|
||||
|
||||
return (
|
||||
<EnvironmentForm
|
||||
defaultValue={this.props.environment}
|
||||
files={this.state.files}
|
||||
defaultValue={this.getEnvironmentDefaultValue()}
|
||||
files={files}
|
||||
onSubmit={this.handleEnvironmentSubmit}
|
||||
onCancel={this.handleCancel}
|
||||
onAddFile={this.handleFileAdd}
|
||||
onRemoveFile={this.handleRemoveFile}
|
||||
loading={this.state.loading}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderReview() {
|
||||
const { ReviewForm } = this.state;
|
||||
const { ReviewForm, environmentToggles } = this.state;
|
||||
|
||||
return (
|
||||
<ReviewForm
|
||||
onSubmit={this.handleReviewSubmit}
|
||||
onCancel={this.handleCancel}
|
||||
onEnvironmentToggle={this.handleEnvironmentToggle}
|
||||
environmentToggles={environmentToggles}
|
||||
{...this.state}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { error, loading, defaultStage, manifestStage } = this.state;
|
||||
const { error, defaultStage, manifestStage, manifest, name } = this.state;
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage
|
||||
title='Ooops!'
|
||||
message={error} />;
|
||||
return <ErrorMessage title="Ooops!" message={error} />;
|
||||
}
|
||||
|
||||
const { match, create } = this.props;
|
||||
@ -374,11 +463,11 @@ class DeploymentGroupEditOrCreate extends Component {
|
||||
return this.redirect({ stage: defaultStage });
|
||||
}
|
||||
|
||||
if (create && stage !== 'name' && !this.state.name) {
|
||||
if (create && stage !== 'name' && !name) {
|
||||
return this.redirect({ stage: defaultStage });
|
||||
}
|
||||
|
||||
if (stage === 'environment' && !this.state.manifest) {
|
||||
if (stage === 'environment' && !manifest) {
|
||||
return this.redirect({ stage: manifestStage });
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,15 @@ const Manifest = ({
|
||||
error,
|
||||
manifest = '',
|
||||
environment = '',
|
||||
files = [],
|
||||
deploymentGroup = null,
|
||||
hasManifest = false,
|
||||
match
|
||||
}) => {
|
||||
const stage = match.params.stage;
|
||||
const _title = <Title>Edit Manifest</Title>;
|
||||
|
||||
if (loading || !deploymentGroup) {
|
||||
if (loading || !deploymentGroup || !hasManifest) {
|
||||
return (
|
||||
<LayoutContainer center>
|
||||
{_title}
|
||||
@ -37,8 +39,9 @@ const Manifest = ({
|
||||
<LayoutContainer>
|
||||
{_title}
|
||||
<ErrorMessage
|
||||
title='Ooops!'
|
||||
message='An error occured while loading your deployment group.' />
|
||||
title="Ooops!"
|
||||
message="An error occured while loading your deployment group."
|
||||
/>
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
@ -46,8 +49,9 @@ const Manifest = ({
|
||||
const _notice =
|
||||
deploymentGroup && deploymentGroup.imported && !manifest
|
||||
? <WarningMessage
|
||||
title='Be aware'
|
||||
message='Since this DeploymentGroup was imported, it doesn't have the initial manifest.' />
|
||||
title="Be aware"
|
||||
message="Since this DeploymentGroup was imported, it doesn't have the initial manifest."
|
||||
/>
|
||||
: null;
|
||||
|
||||
return (
|
||||
@ -58,6 +62,7 @@ const Manifest = ({
|
||||
<ManifestEditOrCreate
|
||||
manifest={manifest}
|
||||
environment={environment}
|
||||
files={files}
|
||||
deploymentGroup={deploymentGroup}
|
||||
edit
|
||||
/>
|
||||
@ -74,8 +79,10 @@ export default compose(
|
||||
}
|
||||
}),
|
||||
props: ({ data: { deploymentGroup, loading, error } }) => ({
|
||||
files: get(deploymentGroup, 'version.manifest.files', []),
|
||||
manifest: get(deploymentGroup, 'version.manifest.raw', ''),
|
||||
environment: get(deploymentGroup, 'version.manifest.environment', ''),
|
||||
hasManifest: Boolean(get(deploymentGroup, 'version.manifest')),
|
||||
loading,
|
||||
error
|
||||
})
|
||||
|
@ -9,13 +9,12 @@ import ServiceGql from './service-gql';
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
|
||||
class ServiceDelete extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -40,30 +39,30 @@ class ServiceDelete extends Component {
|
||||
<ModalErrorMessage
|
||||
title='Ooops!'
|
||||
message='An error occured while loading your service.'
|
||||
onCloseClick={handleCloseClick} />
|
||||
onCloseClick={handleCloseClick}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const { service, deleteServices } = this.props;
|
||||
|
||||
if(this.state.error) {
|
||||
if (this.state.error) {
|
||||
return (
|
||||
<Modal width={460} onCloseClick={handleCloseClick}>
|
||||
<ModalErrorMessage
|
||||
title='Ooops!'
|
||||
message={`An error occured while attempting to delete the ${service.name} service.`}
|
||||
onCloseClick={handleCloseClick} />
|
||||
onCloseClick={handleCloseClick}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const handleConfirmClick = evt => {
|
||||
deleteServices(service.id)
|
||||
.then(() => handleCloseClick())
|
||||
.catch((err) => {
|
||||
this.setState({ error: err });
|
||||
});
|
||||
deleteServices(service.id).then(() => handleCloseClick()).catch(err => {
|
||||
this.setState({ error: err });
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -10,13 +10,12 @@ import ServiceGql from './service-gql';
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
|
||||
class ServiceScale extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -41,20 +40,22 @@ class ServiceScale extends Component {
|
||||
<ModalErrorMessage
|
||||
title='Ooops!'
|
||||
message='An error occured while loading your service.'
|
||||
onCloseClick={handleCloseClick} />
|
||||
onCloseClick={handleCloseClick}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const { service, scale } = this.props;
|
||||
|
||||
if(this.state.error) {
|
||||
if (this.state.error) {
|
||||
return (
|
||||
<Modal width={460} onCloseClick={handleCloseClick}>
|
||||
<ModalErrorMessage
|
||||
title='Ooops!'
|
||||
message={`An error occured while attempting to scale the ${service.name} service.`}
|
||||
onCloseClick={handleCloseClick} />
|
||||
onCloseClick={handleCloseClick}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@ -69,11 +70,9 @@ class ServiceScale extends Component {
|
||||
};
|
||||
|
||||
const handleSubmitClick = values => {
|
||||
scale(service.id, values.replicas)
|
||||
.then(handleCloseClick)
|
||||
.catch((err) => {
|
||||
this.setState({ error: err });
|
||||
});
|
||||
scale(service.id, values.replicas).then(handleCloseClick).catch(err => {
|
||||
this.setState({ error: err });
|
||||
});
|
||||
};
|
||||
|
||||
if (!service) {
|
||||
|
@ -19,8 +19,6 @@ import { ServiceListItem } from '@components/services';
|
||||
|
||||
import { ServicesQuickActions } from '@components/services';
|
||||
|
||||
import { Message } from 'joyent-ui-toolkit';
|
||||
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@ -28,13 +26,12 @@ const StyledContainer = styled.div`
|
||||
`;
|
||||
|
||||
class ServiceList extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
errors: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ref(name) {
|
||||
@ -73,8 +70,9 @@ class ServiceList extends Component {
|
||||
return (
|
||||
<LayoutContainer>
|
||||
<ErrorMessage
|
||||
title='Ooops!'
|
||||
message='An error occured while loading your services.' />
|
||||
title="Ooops!"
|
||||
message="An error occured while loading your services."
|
||||
/>
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
@ -113,26 +111,23 @@ class ServiceList extends Component {
|
||||
|
||||
const handleRestartClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
restartServices(service.id)
|
||||
.catch((err) => {
|
||||
this.setState({ errors: { restart: err }});
|
||||
});
|
||||
restartServices(service.id).catch(err => {
|
||||
this.setState({ errors: { restart: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStopClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
stopServices(service.id)
|
||||
.catch((err) => {
|
||||
this.setState({ errors: { stop: err }});
|
||||
});
|
||||
stopServices(service.id).catch(err => {
|
||||
this.setState({ errors: { stop: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStartClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
startServices(service.id)
|
||||
.catch((err) => {
|
||||
this.setState({ errors: { start: err }});
|
||||
});
|
||||
startServices(service.id).catch(err => {
|
||||
this.setState({ errors: { start: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleScaleClick = (evt, service) => {
|
||||
@ -151,21 +146,22 @@ class ServiceList extends Component {
|
||||
|
||||
let renderedError = null;
|
||||
|
||||
if (this.state.errors.stop || this.state.errors.start || this.state.errors.restart) {
|
||||
|
||||
if (
|
||||
this.state.errors.stop ||
|
||||
this.state.errors.start ||
|
||||
this.state.errors.restart
|
||||
) {
|
||||
const message = this.state.errors.stop
|
||||
? 'An error occured while attempting to stop your service.'
|
||||
: this.state.errors.start
|
||||
? 'An error occured while attempting to start your service.'
|
||||
: this.state.errors.restart
|
||||
? 'An error occured while attempting to restart your service.'
|
||||
: '';
|
||||
? 'An error occured while attempting to start your service.'
|
||||
: this.state.errors.restart
|
||||
? 'An error occured while attempting to restart your service.'
|
||||
: '';
|
||||
|
||||
renderedError = (
|
||||
<LayoutContainer>
|
||||
<ErrorMessage
|
||||
title='Ooops!'
|
||||
message={message} />
|
||||
<ErrorMessage title="Ooops!" message={message} />
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { LayoutContainer } from '@components/layout';
|
||||
import { Title } from '@components/navigation';
|
||||
import { withNotFound } from '@containers/navigation';
|
||||
|
||||
import { H2, FormGroup, Toggle, ToggleList, Legend } from 'joyent-ui-toolkit';
|
||||
import { FormGroup, Toggle, ToggleList, Legend } from 'joyent-ui-toolkit';
|
||||
|
||||
const StyledLegend = Legend.extend`
|
||||
float: left;
|
||||
|
@ -30,23 +30,20 @@ const StyledContainer = styled.div`
|
||||
`;
|
||||
|
||||
class ServicesTopology extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
errors: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const {
|
||||
url,
|
||||
push,
|
||||
deploymentGroup,
|
||||
services,
|
||||
datacenter,
|
||||
loading,
|
||||
error,
|
||||
servicesQuickActions,
|
||||
@ -69,8 +66,9 @@ class ServicesTopology extends Component {
|
||||
return (
|
||||
<LayoutContainer>
|
||||
<ErrorMessage
|
||||
title='Ooops!'
|
||||
message='An error occured while loading your services.' />
|
||||
title="Ooops!"
|
||||
message="An error occured while loading your services."
|
||||
/>
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
@ -97,26 +95,23 @@ class ServicesTopology extends Component {
|
||||
|
||||
const handleRestartClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
restartServices(service.id)
|
||||
.catch((err) => {
|
||||
this.setState({ errors: { restart: err }});
|
||||
});
|
||||
restartServices(service.id).catch(err => {
|
||||
this.setState({ errors: { restart: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStopClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
stopServices(service.id)
|
||||
.catch((err) => {
|
||||
this.setState({ errors: { stop: err }});
|
||||
});
|
||||
stopServices(service.id).catch(err => {
|
||||
this.setState({ errors: { stop: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStartClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
startServices(service.id)
|
||||
.catch((err) => {
|
||||
this.setState({ errors: { start: err }});
|
||||
});
|
||||
startServices(service.id).catch(err => {
|
||||
this.setState({ errors: { start: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleScaleClick = (evt, service) => {
|
||||
@ -135,28 +130,29 @@ class ServicesTopology extends Component {
|
||||
|
||||
let renderedError = null;
|
||||
|
||||
if (this.state.errors.stop || this.state.errors.start || this.state.errors.restart) {
|
||||
|
||||
if (
|
||||
this.state.errors.stop ||
|
||||
this.state.errors.start ||
|
||||
this.state.errors.restart
|
||||
) {
|
||||
const message = this.state.errors.stop
|
||||
? 'An error occured while attempting to stop your service.'
|
||||
: this.state.errors.start
|
||||
? 'An error occured while attempting to start your service.'
|
||||
: this.state.errors.restart
|
||||
? 'An error occured while attempting to restart your service.'
|
||||
: '';
|
||||
? 'An error occured while attempting to start your service.'
|
||||
: this.state.errors.restart
|
||||
? 'An error occured while attempting to restart your service.'
|
||||
: '';
|
||||
|
||||
renderedError = (
|
||||
<LayoutContainer>
|
||||
<ErrorMessage
|
||||
title='Ooops!'
|
||||
message={message} />
|
||||
<ErrorMessage title="Ooops!" message={message} />
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ renderedError }
|
||||
{renderedError}
|
||||
<StyledBackground>
|
||||
<StyledContainer>
|
||||
<Topology
|
||||
|
@ -5,6 +5,11 @@ query ManifestById($deploymentGroupSlug: String!) {
|
||||
id
|
||||
type
|
||||
environment
|
||||
files {
|
||||
id
|
||||
name
|
||||
value
|
||||
}
|
||||
format
|
||||
raw
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { Header, Breadcrumb, Menu } from '@containers/navigation';
|
||||
import { ServiceScale, ServiceDelete } from '@containers/service';
|
||||
import { InstanceList } from '@containers/instances';
|
||||
import Manifest from '@containers/manifest';
|
||||
import Environment from '@containers/environment';
|
||||
|
||||
import {
|
||||
DeploymentGroupList,
|
||||
@ -48,9 +49,8 @@ const serviceRedirect = p =>
|
||||
.params.service}/instances`}
|
||||
/>;
|
||||
|
||||
const App = p => (
|
||||
const App = p =>
|
||||
<div>
|
||||
|
||||
<Switch>
|
||||
<Route
|
||||
path="/deployment-groups/:deploymentGroup/services/:service"
|
||||
@ -118,6 +118,12 @@ const App = p => (
|
||||
component={Manifest}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/deployment-groups/:deploymentGroup/environment"
|
||||
exact
|
||||
component={Environment}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/deployment-groups/:deploymentGroup/services-list"
|
||||
component={ServiceList}
|
||||
@ -178,8 +184,7 @@ const App = p => (
|
||||
component={servicesTopologyRedirect}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
)
|
||||
</div>;
|
||||
|
||||
const Router = (
|
||||
<BrowserRouter>
|
||||
|
@ -13,6 +13,10 @@ const state = {
|
||||
{
|
||||
pathname: 'manifest/edit',
|
||||
name: 'Manifest'
|
||||
},
|
||||
{
|
||||
pathname: 'environment',
|
||||
name: 'Environment'
|
||||
}
|
||||
],
|
||||
services: [
|
||||
|
@ -14,7 +14,8 @@ const GLOBAL =
|
||||
};
|
||||
|
||||
const GQL_PORT = process.env.REACT_APP_GQL_PORT || 443;
|
||||
const GQL_HOSTNAME = process.env.REACT_APP_GQL_HOSTNAME || GLOBAL.location.hostname;
|
||||
const GQL_HOSTNAME =
|
||||
process.env.REACT_APP_GQL_HOSTNAME || GLOBAL.location.hostname;
|
||||
const GQL_PROTOCOL = process.env.REACT_APP_GQL_PROTOCOL || 'https';
|
||||
|
||||
export const client = new ApolloClient({
|
||||
|
@ -17,6 +17,7 @@
|
||||
"dependencies": {
|
||||
"build-array": "^1.0.0",
|
||||
"camel-case": "^3.0.0",
|
||||
"force-array": "^3.1.0",
|
||||
"good": "^7.2.0",
|
||||
"good-console": "^6.4.0",
|
||||
"good-squeeze": "^5.0.2",
|
||||
@ -25,7 +26,7 @@
|
||||
"hasha": "^3.0.0",
|
||||
"joi": "^10.6.0",
|
||||
"joyent-cp-gql-schema": "^1.0.4",
|
||||
"js-yaml": "^3.8.4",
|
||||
"js-yaml": "^3.9.1",
|
||||
"lodash.find": "^4.6.0",
|
||||
"lodash.findindex": "^4.6.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
@ -47,8 +48,12 @@
|
||||
"tap-xunit": "^1.7.0"
|
||||
},
|
||||
"ava": {
|
||||
"files": ["test/*.js"],
|
||||
"source": ["src/*.js"],
|
||||
"files": [
|
||||
"test/*.js"
|
||||
],
|
||||
"source": [
|
||||
"src/*.js"
|
||||
],
|
||||
"failFast": true
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ const { v4: uuid } = require('uuid');
|
||||
const paramCase = require('param-case');
|
||||
const camelCase = require('camel-case');
|
||||
const buildArray = require('build-array');
|
||||
const forceArray = require('force-array');
|
||||
const lfind = require('lodash.find');
|
||||
const findIndex = require('lodash.findindex');
|
||||
const flatten = require('lodash.flatten');
|
||||
@ -31,6 +32,8 @@ const instances = wpData.instances
|
||||
.concat(cpData.instances)
|
||||
.concat(complexData.instances);
|
||||
|
||||
const INTERPOLATE_REGEX = /\$([_a-z][_a-z0-9]*)/gi;
|
||||
|
||||
const find = (query = {}) => item =>
|
||||
Object.keys(query).every(key => item[key] === query[key]);
|
||||
|
||||
@ -185,14 +188,41 @@ const deleteDeploymentGroup = options => {
|
||||
return Promise.resolve(deleteDeploymentGroup);
|
||||
};
|
||||
|
||||
const createServicesFromManifest = ({ deploymentGroupId, raw }) => {
|
||||
const createServicesFromManifest = ({
|
||||
deploymentGroupId = '',
|
||||
environment = '',
|
||||
files = [],
|
||||
type,
|
||||
format,
|
||||
raw = ''
|
||||
}) => {
|
||||
const _config = config({
|
||||
environment,
|
||||
files,
|
||||
raw,
|
||||
_plain: true
|
||||
});
|
||||
|
||||
const manifest = yaml.safeLoad(raw);
|
||||
|
||||
const version = {
|
||||
id: uuid(),
|
||||
manifest: {
|
||||
id: uuid(),
|
||||
type,
|
||||
format,
|
||||
environment,
|
||||
files,
|
||||
raw
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(manifest).forEach(name => {
|
||||
const _service = {
|
||||
deploymentGroupId,
|
||||
slug: paramCase(name),
|
||||
name
|
||||
name,
|
||||
config: lfind(_config, ['name', name]).config
|
||||
};
|
||||
|
||||
const service = Object.assign({}, _service, {
|
||||
@ -213,6 +243,12 @@ const createServicesFromManifest = ({ deploymentGroupId, raw }) => {
|
||||
instances.push(instance);
|
||||
});
|
||||
|
||||
const dgIndex = findIndex(deploymentGroups, ['id', deploymentGroupId]);
|
||||
deploymentGroups[dgIndex] = Object.assign(deploymentGroups[dgIndex], {
|
||||
version,
|
||||
history: forceArray(deploymentGroups[dgIndex].history).concat([version])
|
||||
});
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
};
|
||||
|
||||
@ -409,6 +445,86 @@ const restartServices = options => {
|
||||
return Promise.resolve(restartService);
|
||||
};
|
||||
|
||||
const parseEnvVars = (str = '') =>
|
||||
str
|
||||
.split(/\n/g)
|
||||
.filter(line => line.match(/\=/g))
|
||||
.map(line => line.split(/\=/))
|
||||
.reduce(
|
||||
(acc, [name, value]) =>
|
||||
Object.assign(acc, {
|
||||
[name.trim()]: value.trim()
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
const findEnvInterpolation = (str = '') =>
|
||||
uniq(str.match(INTERPOLATE_REGEX).map(name => name.replace(/^\$/, '')));
|
||||
|
||||
const config = ({
|
||||
environment = '',
|
||||
files = [],
|
||||
raw = '',
|
||||
_plain = false
|
||||
}) => {
|
||||
const interpolatableNames = findEnvInterpolation(raw);
|
||||
const interpolatableEnv = parseEnvVars(environment);
|
||||
|
||||
const interpolatedRaw = interpolatableNames.reduce(
|
||||
(str = '', name) =>
|
||||
str.replace(new RegExp(`\\$${name}`), interpolatableEnv[name]),
|
||||
raw
|
||||
);
|
||||
|
||||
const manifest = yaml.safeLoad(interpolatedRaw);
|
||||
const services = manifest.services || manifest;
|
||||
|
||||
const config = Object.keys(services)
|
||||
.map(name =>
|
||||
Object.assign(services[name], {
|
||||
name
|
||||
})
|
||||
)
|
||||
// eslint-disable-next-line camelcase
|
||||
.map(({ name, image, env_file, environment }) => ({
|
||||
name,
|
||||
slug: paramCase(name),
|
||||
instances: [],
|
||||
config: {
|
||||
image,
|
||||
environment: forceArray(env_file).reduce(
|
||||
(env, file) =>
|
||||
Object.assign(
|
||||
env,
|
||||
parseEnvVars(lfind(files, ['name', file]).value)
|
||||
),
|
||||
forceArray(environment)
|
||||
.map(parseEnvVars)
|
||||
.reduce(
|
||||
(genv, variable) => Object.assign(genv, variable),
|
||||
interpolatableEnv
|
||||
)
|
||||
)
|
||||
}
|
||||
}))
|
||||
.map(service =>
|
||||
Object.assign(service, {
|
||||
id: hasha(JSON.stringify(service)),
|
||||
config: Object.assign(service.config, {
|
||||
id: hasha(JSON.stringify(service.config)),
|
||||
environment: Object.keys(service.config.environment).map(name => ({
|
||||
name,
|
||||
id: hasha(JSON.stringify(service.config.environment[name])),
|
||||
value: service.config.environment[name]
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
return _plain ? config : Promise.resolve(config);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
portal: getPortal,
|
||||
deploymentGroups: getDeploymentGroups,
|
||||
@ -430,5 +546,6 @@ module.exports = {
|
||||
scale: (options, reguest, fn) => fn(null, scale(options)),
|
||||
restartServices: (options, request, fn) => fn(null, restartServices(options)),
|
||||
stopServices: (options, request, fn) => fn(null, stopServices(options)),
|
||||
startServices: (options, request, fn) => fn(null, startServices(options))
|
||||
startServices: (options, request, fn) => fn(null, startServices(options)),
|
||||
config
|
||||
};
|
||||
|
@ -18,7 +18,7 @@
|
||||
"code": "^4.1.0",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-config-joyent-portal": "2.0.0",
|
||||
"js-yaml": "^3.8.4",
|
||||
"js-yaml": "^3.9.1",
|
||||
"lab": "^14.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const Yamljs = require('yamljs');
|
||||
const ParamCase = require('param-case');
|
||||
const Uuid = require('uuid/v4');
|
||||
|
||||
@ -138,8 +137,7 @@ exports.toManifest = function (clientManifest) {
|
||||
id: m.id || Uuid()
|
||||
});
|
||||
}) : undefined,
|
||||
raw: clientManifest.raw,
|
||||
json: clientManifest.json || Yamljs.parse(clientManifest.raw)
|
||||
raw: clientManifest.raw
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -55,7 +55,6 @@
|
||||
"triton": "^5.2.0",
|
||||
"triton-watch": "^1.1.0",
|
||||
"uuid": "^3.1.0",
|
||||
"vasync": "^1.6.4",
|
||||
"yamljs": "^0.2.10"
|
||||
"vasync": "^1.6.4"
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import { bottomShaddow, borderRadius } from '../boxes';
|
||||
import paperEffect from '../paper-effect';
|
||||
import typography from '../typography';
|
||||
import Baseline from '../baseline';
|
||||
import StatusLoader from '../status-loader';
|
||||
|
||||
// Based on bootstrap 4
|
||||
const style = css`
|
||||
@ -169,7 +170,7 @@ const StyledLink = styled(Link)`
|
||||
* @example ./usage.md
|
||||
*/
|
||||
const Button = props => {
|
||||
const { href = '', to = '' } = props;
|
||||
const { href = '', to = '', loading = false, secondary, tertiary } = props;
|
||||
|
||||
const Views = [
|
||||
() => (to ? StyledLink : null),
|
||||
@ -179,9 +180,13 @@ const Button = props => {
|
||||
|
||||
const View = Views.reduce((sel, view) => (sel ? sel : view()), null);
|
||||
|
||||
const children = loading
|
||||
? <StatusLoader secondary={!secondary} tertiary={tertiary} />
|
||||
: props.children;
|
||||
|
||||
return (
|
||||
<View {...props}>
|
||||
{props.children}
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
@ -43,7 +43,13 @@ const StyledCard = Row.extend`
|
||||
/**
|
||||
* @example ./usage.md
|
||||
*/
|
||||
const Card = ({ children, collapsed = false, headed = false, disabled = false, ...rest }) => {
|
||||
const Card = ({
|
||||
children,
|
||||
collapsed = false,
|
||||
headed = false,
|
||||
disabled = false,
|
||||
...rest
|
||||
}) => {
|
||||
const render = value => {
|
||||
const newValue = {
|
||||
fromHeader: (value || {}).fromHeader,
|
||||
@ -54,7 +60,13 @@ const Card = ({ children, collapsed = false, headed = false, disabled = false, .
|
||||
|
||||
return (
|
||||
<Broadcast channel="card" value={newValue}>
|
||||
<StyledCard name="card" disabled={disabled} collapsed={collapsed} headed={headed} {...rest}>
|
||||
<StyledCard
|
||||
name="card"
|
||||
disabled={disabled}
|
||||
collapsed={collapsed}
|
||||
headed={headed}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</StyledCard>
|
||||
</Broadcast>
|
||||
|
@ -35,7 +35,13 @@ const Header = ({ children, ...rest }) => {
|
||||
|
||||
return (
|
||||
<Broadcast channel="card" value={newValue}>
|
||||
<StyledCard name="card-header" disabled={disabled} collapsed headed {...rest}>
|
||||
<StyledCard
|
||||
name="card-header"
|
||||
disabled={disabled}
|
||||
collapsed
|
||||
headed
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</StyledCard>
|
||||
</Broadcast>
|
||||
|
@ -79,7 +79,11 @@ const StyledCircle = styled.div`
|
||||
`;
|
||||
|
||||
const Options = ({ children, ...rest }) => {
|
||||
const render = ({ fromHeader = false, collapsed = false, disabled = false }) =>
|
||||
const render = ({
|
||||
fromHeader = false,
|
||||
collapsed = false,
|
||||
disabled = false
|
||||
}) =>
|
||||
<StyledNav disabled={disabled} fromHeader={fromHeader} name="card-options">
|
||||
<StyledButton
|
||||
secondary={!fromHeader}
|
||||
|
@ -27,7 +27,13 @@ const StyledCol = Col.extend`
|
||||
|
||||
const Outlet = ({ children, ...rest }) => {
|
||||
const render = ({ disabled = false, collapsed = false }) =>
|
||||
<StyledCol name="card-outlet" disabled={disabled} collapsed={collapsed} xs={6} {...rest}>
|
||||
<StyledCol
|
||||
name="card-outlet"
|
||||
disabled={disabled}
|
||||
collapsed={collapsed}
|
||||
xs={6}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</StyledCol>;
|
||||
|
||||
|
@ -47,7 +47,11 @@ const StyledTitle = Title.extend`
|
||||
`;
|
||||
|
||||
const Subtitle = ({ children, ...props }) => {
|
||||
const render = ({ disabled = false, fromHeader = false, collapsed = false }) =>
|
||||
const render = ({
|
||||
disabled = false,
|
||||
fromHeader = false,
|
||||
collapsed = false
|
||||
}) =>
|
||||
<StyledTitle
|
||||
name="card-subtitle"
|
||||
fromHeader={fromHeader}
|
||||
|
@ -59,7 +59,11 @@ const Title = ({ children, ...rest }) => {
|
||||
</Span>
|
||||
: children;
|
||||
|
||||
const render = ({ collapsed = false, disabled = false, fromHeader = false }) =>
|
||||
const render = ({
|
||||
collapsed = false,
|
||||
disabled = false,
|
||||
fromHeader = false
|
||||
}) =>
|
||||
<Container
|
||||
collapsed={collapsed}
|
||||
fromHeader={fromHeader}
|
||||
|
27
packages/ui-toolkit/src/chevron/index.js
Normal file
27
packages/ui-toolkit/src/chevron/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
import is from 'styled-is';
|
||||
import typography from '../typography';
|
||||
import P from '../text/p';
|
||||
|
||||
export default P.extend`
|
||||
${typography.fontFamily};
|
||||
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
|
||||
${is('up')`
|
||||
transform: rotate(-90deg);
|
||||
`};
|
||||
|
||||
${is('down')`
|
||||
transform: rotate(90deg);
|
||||
`};
|
||||
|
||||
${is('left')`
|
||||
transform: rotate(180deg);
|
||||
`};
|
||||
|
||||
|
||||
&:before {
|
||||
content: "\\003e";
|
||||
}
|
||||
`;
|
9
packages/ui-toolkit/src/divider/index.js
Normal file
9
packages/ui-toolkit/src/divider/index.js
Normal file
@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
import { Row } from 'react-styled-flexboxgrid';
|
||||
import remcalc from 'remcalc';
|
||||
|
||||
export default styled(Row)`
|
||||
background-color: ${props => props.theme.grey};
|
||||
height: ${remcalc(1)};
|
||||
margin: 0;
|
||||
`;
|
@ -13,7 +13,9 @@ export { default as theme } from './theme';
|
||||
export { default as typography, fonts } from './typography';
|
||||
export { default as Topology } from './topology';
|
||||
export { default as Modal, ModalHeading, ModalText } from './modal';
|
||||
export { default as Chevron } from './chevron';
|
||||
export { default as CloseButton } from './close-button';
|
||||
export { default as Divider } from './divider';
|
||||
export { default as IconButton } from './icon-button';
|
||||
export { Tooltip, TooltipButton, TooltipDivider } from './tooltip';
|
||||
export { Dropdown } from './dropdown';
|
||||
|
@ -23,10 +23,11 @@ const StyledColor = styled.div`
|
||||
width: ${unitcalc(6)};
|
||||
height: 100%;
|
||||
background-color: ${props =>
|
||||
props.type === 'ERROR' ? props.theme.red
|
||||
: props.type === 'WARNING' ? props.theme.orange
|
||||
: props.type === 'EDUCATION' ? props.theme.green
|
||||
: props.theme.green};
|
||||
props.type === 'ERROR'
|
||||
? props.theme.red
|
||||
: props.type === 'WARNING'
|
||||
? props.theme.orange
|
||||
: props.type === 'EDUCATION' ? props.theme.green : props.theme.green};
|
||||
`;
|
||||
|
||||
const StyledMessageContainer = styled.div`
|
||||
@ -48,29 +49,27 @@ const StyledClose = styled(CloseButton)`
|
||||
top: ${unitcalc(0.5)};
|
||||
`;
|
||||
|
||||
const Message = ({
|
||||
title,
|
||||
message,
|
||||
onCloseClick,
|
||||
type='MESSAGE'
|
||||
}) => {
|
||||
|
||||
const renderTitle = title
|
||||
? <StyledTitle>{title}</StyledTitle>
|
||||
const Message = ({ title, message, onCloseClick, type = 'MESSAGE' }) => {
|
||||
const renderTitle = title
|
||||
? <StyledTitle>
|
||||
{title}
|
||||
</StyledTitle>
|
||||
: null;
|
||||
|
||||
const renderClose = onCloseClick
|
||||
? <StyledClose onClick={ onCloseClick } />
|
||||
? <StyledClose onClick={onCloseClick} />
|
||||
: null;
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledColor type={type} />
|
||||
<StyledMessageContainer>
|
||||
{ renderTitle }
|
||||
<StyledMessage>{message}</StyledMessage>
|
||||
{renderTitle}
|
||||
<StyledMessage>
|
||||
{message}
|
||||
</StyledMessage>
|
||||
</StyledMessageContainer>
|
||||
{ renderClose }
|
||||
{renderClose}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -79,12 +78,7 @@ Message.propTypes = {
|
||||
title: PropTypes.string,
|
||||
message: PropTypes.string.isRequired,
|
||||
onCloseClick: PropTypes.func,
|
||||
type: PropTypes.oneOf([
|
||||
'ERROR',
|
||||
'WARNING',
|
||||
'EDUCATION',
|
||||
'MESSAGE'
|
||||
])
|
||||
type: PropTypes.oneOf(['ERROR', 'WARNING', 'EDUCATION', 'MESSAGE'])
|
||||
};
|
||||
|
||||
export default Message;
|
||||
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import disableScroll from 'disable-scroll';
|
||||
import remcalc from 'remcalc';
|
||||
import theme from '../theme';
|
||||
import { modalShadow } from '../boxes';
|
||||
import CloseButton from '../close-button';
|
||||
import P from '../text/p';
|
||||
|
@ -1,11 +1,9 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import theme from '../theme';
|
||||
|
||||
const StyledList = styled.ul`
|
||||
display: table;
|
||||
list-style-type: none;
|
||||
background-color: ${theme.white};
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
|
@ -4,6 +4,7 @@ import Baseline from '../baseline';
|
||||
|
||||
const StyledItem = styled.li`
|
||||
float: left;
|
||||
background-color: ${props => props.theme.white};
|
||||
`;
|
||||
|
||||
const ProgressbarItem = ({ children, ...props }) =>
|
||||
|
@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import is from 'styled-is';
|
||||
|
||||
const animationName = keyframes`
|
||||
0% {
|
||||
opacity: 1;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.25;
|
||||
stroke-width: 0;
|
||||
@ -15,6 +17,17 @@ const animationName = keyframes`
|
||||
const StyledFirstRect = styled.rect`
|
||||
fill: ${props => props.theme.primary};
|
||||
stroke: ${props => props.theme.primary};
|
||||
|
||||
${is('secondary')`
|
||||
fill: ${props => props.theme.white};
|
||||
stroke: ${props => props.theme.white};
|
||||
`};
|
||||
|
||||
${is('tertiary')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
stroke: ${props => props.theme.secondary};
|
||||
`};
|
||||
|
||||
animation: ${animationName} 1.5s ease-out 0s infinite;
|
||||
`;
|
||||
|
||||
@ -26,9 +39,30 @@ const StyledThirdRect = StyledFirstRect.extend`
|
||||
animation-delay: 1s;
|
||||
`;
|
||||
|
||||
export default () =>
|
||||
export default ({ secondary, tertiary }) =>
|
||||
<svg width="28" height="10">
|
||||
<StyledFirstRect x="2" y="2" width="6" height="6" />
|
||||
<StyledSecondRect x="11" y="2" width="6" height="6" />
|
||||
<StyledThirdRect x="20" y="2" width="6" height="6" />
|
||||
<StyledFirstRect
|
||||
tertiary={tertiary}
|
||||
secondary={secondary}
|
||||
x="2"
|
||||
y="2"
|
||||
width="6"
|
||||
height="6"
|
||||
/>
|
||||
<StyledSecondRect
|
||||
tertiary={tertiary}
|
||||
secondary={secondary}
|
||||
x="11"
|
||||
y="2"
|
||||
width="6"
|
||||
height="6"
|
||||
/>
|
||||
<StyledThirdRect
|
||||
tertiary={tertiary}
|
||||
secondary={secondary}
|
||||
x="20"
|
||||
y="2"
|
||||
width="6"
|
||||
height="6"
|
||||
/>
|
||||
</svg>;
|
||||
|
@ -16,7 +16,7 @@ const GraphNode = ({
|
||||
onQuickActions
|
||||
}) => {
|
||||
const { left, top, width, height } = data.nodeRect;
|
||||
const { connected, id, children, instancesActive, isConsul, status } = data;
|
||||
const { connected, id, children, instancesActive, isConsul } = data;
|
||||
|
||||
let x = data.x;
|
||||
let y = data.y;
|
||||
@ -75,7 +75,6 @@ const GraphNode = ({
|
||||
).children
|
||||
: <GraphNodeContent data={data} />;
|
||||
|
||||
|
||||
const nodeShadow = instancesActive
|
||||
? <GraphShadowRect
|
||||
x={0}
|
||||
|
118
yarn.lock
118
yarn.lock
@ -85,7 +85,7 @@ abbrev@1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f"
|
||||
|
||||
accept@2.x.x:
|
||||
accept@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/accept/-/accept-2.1.4.tgz#887af54ceee5c7f4430461971ec400c61d09acbb"
|
||||
dependencies:
|
||||
@ -201,7 +201,7 @@ amdefine@>=0.0.4:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
|
||||
|
||||
ammo@2.x.x:
|
||||
ammo@2.x.x, ammo@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/ammo/-/ammo-2.0.4.tgz#bf80aab211698ea78f63ef5e7f113dd5d9e8917f"
|
||||
dependencies:
|
||||
@ -1981,7 +1981,7 @@ call-signature@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-signature/-/call-signature-0.0.2.tgz#a84abc825a55ef4cb2b028bd74e205a65b9a4996"
|
||||
|
||||
call@4.x.x:
|
||||
call@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call/-/call-4.0.2.tgz#df76f5f51ee8dd48b856ac8400f7e69e6d7399c4"
|
||||
dependencies:
|
||||
@ -2042,12 +2042,12 @@ caniuse-api@^1.5.2:
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-db@^1.0.30000187, caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
|
||||
version "1.0.30000709"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000709.tgz#0b600072b7cdbbf6336a8758b71b9ad03268ede2"
|
||||
version "1.0.30000710"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000710.tgz#f03614ef04b76ba41232755b7d4e45d7cc1c13b8"
|
||||
|
||||
caniuse-lite@^1.0.30000669, caniuse-lite@^1.0.30000670, caniuse-lite@^1.0.30000697, caniuse-lite@^1.0.30000704:
|
||||
version "1.0.30000709"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000709.tgz#e027c7a0dfd5ada58f931a1080fc71965375559b"
|
||||
version "1.0.30000710"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000710.tgz#1c249bf7c6a61161c9b10906e3ad9fa5b6761af1"
|
||||
|
||||
capture-stack-trace@^1.0.0:
|
||||
version "1.0.0"
|
||||
@ -2061,13 +2061,13 @@ caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
|
||||
catbox-memory@2.x.x:
|
||||
catbox-memory@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/catbox-memory/-/catbox-memory-2.0.4.tgz#433e255902caf54233d1286429c8f4df14e822d5"
|
||||
dependencies:
|
||||
hoek "4.x.x"
|
||||
|
||||
catbox@7.x.x:
|
||||
catbox@^7.1.5:
|
||||
version "7.1.5"
|
||||
resolved "https://registry.yarnpkg.com/catbox/-/catbox-7.1.5.tgz#c56f7e8e9555d27c0dc038a96ef73e57d186bb1f"
|
||||
dependencies:
|
||||
@ -2929,7 +2929,7 @@ cryptiles@2.x.x:
|
||||
dependencies:
|
||||
boom "2.x.x"
|
||||
|
||||
cryptiles@3.x.x:
|
||||
cryptiles@3.x.x, cryptiles@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
|
||||
dependencies:
|
||||
@ -3744,8 +3744,8 @@ dtrace-provider@~0.6:
|
||||
nan "^2.0.8"
|
||||
|
||||
dtrace-provider@~0.8:
|
||||
version "0.8.4"
|
||||
resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.4.tgz#f27c12dc0ec3105606f9833c118b8d711c8d532a"
|
||||
version "0.8.5"
|
||||
resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.5.tgz#98ebba221afac46e1c39fd36858d8f9367524b92"
|
||||
dependencies:
|
||||
nan "^2.3.3"
|
||||
|
||||
@ -3789,8 +3789,8 @@ ee-first@1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
|
||||
electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.16:
|
||||
version "1.3.16"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.16.tgz#d0e026735754770901ae301a21664cba45d92f7d"
|
||||
version "1.3.17"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.17.tgz#41c13457cc7166c5c15e767ae61d86a8cacdee5d"
|
||||
|
||||
elliptic@^6.0.0:
|
||||
version "6.4.0"
|
||||
@ -3869,13 +3869,14 @@ error-ex@^1.2.0:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
es-abstract@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c"
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.8.0.tgz#3b00385e85729932beffa9163bbea1234e932914"
|
||||
dependencies:
|
||||
es-to-primitive "^1.1.1"
|
||||
function-bind "^1.1.0"
|
||||
has "^1.0.1"
|
||||
is-callable "^1.1.3"
|
||||
is-regex "^1.0.3"
|
||||
is-regex "^1.0.4"
|
||||
|
||||
es-to-primitive@^1.1.1:
|
||||
version "1.1.1"
|
||||
@ -4595,10 +4596,10 @@ fillers@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fillers/-/fillers-1.1.1.tgz#9d1a8f0150d47f78a898de4cd43cf079d417148e"
|
||||
|
||||
finalhandler@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89"
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.4.tgz#18574f2e7c4b98b8ae3b230c21f201f31bdb3fb7"
|
||||
dependencies:
|
||||
debug "2.6.7"
|
||||
debug "2.6.8"
|
||||
encodeurl "~1.0.1"
|
||||
escape-html "~1.0.3"
|
||||
on-finished "~2.3.0"
|
||||
@ -5225,27 +5226,27 @@ hapi-swagger@^7.7.0:
|
||||
swagger-parser "^3.4.1"
|
||||
|
||||
hapi@^16.4.3:
|
||||
version "16.5.0"
|
||||
resolved "https://registry.yarnpkg.com/hapi/-/hapi-16.5.0.tgz#89e4770f0034e3b69ee99ed929cc5b573805f303"
|
||||
version "16.5.2"
|
||||
resolved "https://registry.yarnpkg.com/hapi/-/hapi-16.5.2.tgz#d1dadf33721c6ac3aaa905ce086d9c7ffb883092"
|
||||
dependencies:
|
||||
accept "2.x.x"
|
||||
ammo "2.x.x"
|
||||
boom "5.x.x"
|
||||
call "4.x.x"
|
||||
catbox "7.x.x"
|
||||
catbox-memory "2.x.x"
|
||||
cryptiles "3.x.x"
|
||||
heavy "4.x.x"
|
||||
hoek "4.x.x"
|
||||
iron "4.x.x"
|
||||
items "2.x.x"
|
||||
joi "10.x.x"
|
||||
mimos "3.x.x"
|
||||
podium "1.x.x"
|
||||
shot "3.x.x"
|
||||
statehood "5.x.x"
|
||||
subtext "5.x.x"
|
||||
topo "2.x.x"
|
||||
accept "^2.1.4"
|
||||
ammo "^2.0.4"
|
||||
boom "^5.2.0"
|
||||
call "^4.0.2"
|
||||
catbox "^7.1.5"
|
||||
catbox-memory "^2.0.4"
|
||||
cryptiles "^3.1.2"
|
||||
heavy "^4.0.4"
|
||||
hoek "^4.2.0"
|
||||
iron "^4.0.5"
|
||||
items "^2.1.1"
|
||||
joi "^10.6.0"
|
||||
mimos "^3.0.3"
|
||||
podium "^1.3.0"
|
||||
shot "^3.4.2"
|
||||
statehood "^5.0.3"
|
||||
subtext "^5.0.0"
|
||||
topo "^2.0.2"
|
||||
|
||||
har-schema@^1.0.5:
|
||||
version "1.0.5"
|
||||
@ -5342,7 +5343,7 @@ he@1.1.x:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
||||
|
||||
heavy@4.x.x:
|
||||
heavy@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/heavy/-/heavy-4.0.4.tgz#36c91336c00ccfe852caa4d153086335cd2f00e9"
|
||||
dependencies:
|
||||
@ -5380,7 +5381,7 @@ hoek@2.x.x:
|
||||
version "2.16.3"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
|
||||
|
||||
hoek@4.x.x, hoek@^4.0.1, hoek@^4.1.1:
|
||||
hoek@4.x.x, hoek@^4.0.1, hoek@^4.1.1, hoek@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
|
||||
|
||||
@ -5748,7 +5749,7 @@ ipaddr.js@1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0"
|
||||
|
||||
iron@4.x.x:
|
||||
iron@4.x.x, iron@^4.0.5:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/iron/-/iron-4.0.5.tgz#4f042cceb8b9738f346b59aa734c83a89bc31428"
|
||||
dependencies:
|
||||
@ -5990,7 +5991,7 @@ is-redirect@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
|
||||
|
||||
is-regex@^1.0.3:
|
||||
is-regex@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
|
||||
dependencies:
|
||||
@ -6189,7 +6190,7 @@ isurl@^1.0.0-alpha5:
|
||||
has-to-string-tag-x "^1.2.0"
|
||||
is-object "^1.0.1"
|
||||
|
||||
items@2.x.x:
|
||||
items@2.x.x, items@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198"
|
||||
|
||||
@ -7484,7 +7485,7 @@ mimic-response@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e"
|
||||
|
||||
mimos@3.x.x:
|
||||
mimos@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/mimos/-/mimos-3.0.3.tgz#b9109072ad378c2b72f6a0101c43ddfb2b36641f"
|
||||
dependencies:
|
||||
@ -7556,8 +7557,8 @@ moment@2.x.x, moment@^2.10.6, moment@^2.6.0:
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
|
||||
|
||||
mooremachine@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mooremachine/-/mooremachine-2.1.0.tgz#e935cf356ca6b6a28b92fbd446d1b31a5c19848d"
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/mooremachine/-/mooremachine-2.2.0.tgz#ec70bf284f5ae478afa7b359b294af67e2c97906"
|
||||
dependencies:
|
||||
assert-plus ">=0.2.0 <0.3.0"
|
||||
optionalDependencies:
|
||||
@ -8448,7 +8449,7 @@ pluralize@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-6.0.0.tgz#d9b51afad97d3d51075cc1ddba9b132cacccb7ba"
|
||||
|
||||
podium@1.x.x:
|
||||
podium@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/podium/-/podium-1.3.0.tgz#3c490f54d16f10f5260cbe98641f1cb733a8851c"
|
||||
dependencies:
|
||||
@ -10401,7 +10402,7 @@ sherlock@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sherlock/-/sherlock-1.0.0.tgz#e246eacfd72c0e3b3e8243a6c9e55340d80c854e"
|
||||
|
||||
shot@3.x.x:
|
||||
shot@^3.4.2:
|
||||
version "3.4.2"
|
||||
resolved "https://registry.yarnpkg.com/shot/-/shot-3.4.2.tgz#1e5c3f6f2b26649adc42f7eb350214a5a0291d67"
|
||||
dependencies:
|
||||
@ -10755,7 +10756,7 @@ state-toggle@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.0.tgz#d20f9a616bb4f0c3b98b91922d25b640aa2bc425"
|
||||
|
||||
statehood@5.x.x:
|
||||
statehood@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/statehood/-/statehood-5.0.3.tgz#c07a75620db5379b60d2edd47f538002a8ac7dd6"
|
||||
dependencies:
|
||||
@ -10940,8 +10941,8 @@ style-search@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
|
||||
|
||||
styled-components@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.1.1.tgz#7e9b5bc319ee3963b47aebb74f4658119ea9d484"
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.1.2.tgz#bb419978e1287c5d0d88fa9106b2dd75f66a324c"
|
||||
dependencies:
|
||||
buffer "^5.0.3"
|
||||
css-to-react-native "^2.0.3"
|
||||
@ -11127,7 +11128,7 @@ stylis@^3.2.1:
|
||||
version "3.2.8"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.2.8.tgz#9b23a3e06597f7944a3d9ae880d5796248b8784f"
|
||||
|
||||
subtext@5.x.x:
|
||||
subtext@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/subtext/-/subtext-5.0.0.tgz#9c3f083018bb1586b167ad8cfd87083f5ccdfe0f"
|
||||
dependencies:
|
||||
@ -11551,7 +11552,7 @@ to-vfile@^2.1.1:
|
||||
is-buffer "^1.1.4"
|
||||
vfile "^2.0.0"
|
||||
|
||||
topo@2.x.x:
|
||||
topo@2.x.x, topo@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182"
|
||||
dependencies:
|
||||
@ -12576,13 +12577,6 @@ yamlish@0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/yamlish/-/yamlish-0.0.7.tgz#b4af9a1dcc63618873c3d6e451ec3213c39a57fb"
|
||||
|
||||
yamljs@^0.2.10:
|
||||
version "0.2.10"
|
||||
resolved "https://registry.yarnpkg.com/yamljs/-/yamljs-0.2.10.tgz#481cc7c25ca73af59f591f0c96e3ce56c757a40f"
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
glob "^7.0.5"
|
||||
|
||||
yargs-parser@^4.2.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"
|
||||
|
Loading…
Reference in New Issue
Block a user