diff --git a/packages/cp-frontend/package.json b/packages/cp-frontend/package.json index 5177bf75..9cfd6cd9 100644 --- a/packages/cp-frontend/package.json +++ b/packages/cp-frontend/package.json @@ -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", diff --git a/packages/cp-frontend/public/index.html b/packages/cp-frontend/public/index.html index de211200..dcb59a49 100644 --- a/packages/cp-frontend/public/index.html +++ b/packages/cp-frontend/public/index.html @@ -12,7 +12,6 @@ .CodeMirror { border: solid 1px #d8d8d8; - margin-bottom: 8px; } html, body, #root { diff --git a/packages/cp-frontend/src/components/manifest/edit-or-create.js b/packages/cp-frontend/src/components/manifest/edit-or-create.js index 068867ea..a66f36d5 100644 --- a/packages/cp-frontend/src/components/manifest/edit-or-create.js +++ b/packages/cp-frontend/src/components/manifest/edit-or-create.js @@ -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 }) => ; -const EEditor = ({ input, defaultValue }) => +const EEditor = ({ input, defaultValue, readOnly }) => ; export const Name = ({ handleSubmit, onCancel, dirty }) => @@ -137,7 +178,7 @@ export const Name = ({ handleSubmit, onCancel, dirty }) => - ; -const Filename = ({ name, onRemoveFile }) => - - - - Remove - - ; +const File = ({ id, name, value, onRemoveFile, readOnly }) => { + const removeButton = !readOnly + ? + Remove + + : null; -export const Files = ({ loading, files, onRemoveFile }) => { - if (loading) { - return ; - } - - const _files = files.map(({ id, name, value }) => -
- - - onRemoveFile(id)} /> - - -
+ : ; + + const input = !readOnly + ? + : ; + + return ( + + + + {input} + {removeButton} + + + {fileEditor} + ); +}; + +const Files = ({ files, onAddFile, onRemoveFile, readOnly }) => { + const footer = !readOnly + ? + : null; return (
-

Files:

- {_files} + {files.map(({ id, ...rest }) => + onRemoveFile(id)} + readOnly={readOnly} + {...rest} + /> + )} + {footer}
); }; @@ -215,66 +277,101 @@ export const Environment = ({ dirty, defaultValue = '', files = [], + readOnly = false, loading -}) => -
- - - - - - - - ; +}) => { + const envEditor = !readOnly + ? + : ; + + const footerDivider = !readOnly ? : null; + + const footer = !readOnly + ? + + + + : null; + + return ( +
+ Global variables + + These variables are going to be availabe for interpolation in the + manifest + + {envEditor} + + Enviroment files + + The variables from this files will be applied to the services that + require them + + + {footerDivider} + {footer} + + ); +}; + +const EnvironmentReview = ({ environment }) => { + const value = environment + .map(({ name, value }) => `${name}=${value}`) + .join('\n'); + + return ; +}; export const Review = ({ handleSubmit, + onEnvironmentToggle = () => null, onCancel, dirty, loading, + environmentToggles, ...state }) => { const serviceList = forceArray(state.services).map(({ name, config }) => + + {name} +
-
- - {name} - -
Image: {config.image}
- {config.environment.length - ?
- Environment: -
- : undefined} - {config.environment.length - ? - : undefined}
+ + onEnvironmentToggle(name)} + > + Environment variables{' '} + + + {environmentToggles[name] + ? + : null}
); @@ -282,11 +379,11 @@ export const Review = ({
{serviceList} - -
diff --git a/packages/cp-frontend/src/components/messaging/error.js b/packages/cp-frontend/src/components/messaging/error.js index 5323ff63..20b91be1 100644 --- a/packages/cp-frontend/src/components/messaging/error.js +++ b/packages/cp-frontend/src/components/messaging/error.js @@ -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' -}) => - +const ErrorMessage = ({ title, message = "Ooops, there's been an error" }) => + ; ErrorMessage.propTypes = { title: PropTypes.string, diff --git a/packages/cp-frontend/src/components/messaging/warning.js b/packages/cp-frontend/src/components/messaging/warning.js index 2af7cefb..b332ac50 100644 --- a/packages/cp-frontend/src/components/messaging/warning.js +++ b/packages/cp-frontend/src/components/messaging/warning.js @@ -2,15 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Message } from 'joyent-ui-toolkit'; -const WarningMessage = ({ - title, - message -}) => - +const WarningMessage = ({ title, message }) => + ; WarningMessage.propTypes = { title: PropTypes.string, diff --git a/packages/cp-frontend/src/containers/deployment-group/delete.js b/packages/cp-frontend/src/containers/deployment-group/delete.js index 8ba301b9..abf72ce9 100644 --- a/packages/cp-frontend/src/containers/deployment-group/delete.js +++ b/packages/cp-frontend/src/containers/deployment-group/delete.js @@ -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 { + onCloseClick={handleCloseClick} + /> ); } @@ -56,7 +56,8 @@ class DeploymentGroupDelete extends Component { + onCloseClick={handleCloseClick} + /> ); } @@ -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 }); }); }; diff --git a/packages/cp-frontend/src/containers/deployment-groups/import.js b/packages/cp-frontend/src/containers/deployment-groups/import.js index 8c6164a2..d21d1da3 100644 --- a/packages/cp-frontend/src/containers/deployment-groups/import.js +++ b/packages/cp-frontend/src/containers/deployment-groups/import.js @@ -46,8 +46,9 @@ class DeploymentGroupImport extends Component { {_title} + title="Ooops!" + message="An error occurred while importing your deployment groups." + /> ); } diff --git a/packages/cp-frontend/src/containers/deployment-groups/list.js b/packages/cp-frontend/src/containers/deployment-groups/list.js index dbab8e55..c46ed621 100644 --- a/packages/cp-frontend/src/containers/deployment-groups/list.js +++ b/packages/cp-frontend/src/containers/deployment-groups/list.js @@ -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 = ({ {_title} + title="Ooops!" + message="An error occured while loading your deployment groups." + /> ); } diff --git a/packages/cp-frontend/src/containers/environment/index.js b/packages/cp-frontend/src/containers/environment/index.js new file mode 100644 index 00000000..1f7f7965 --- /dev/null +++ b/packages/cp-frontend/src/containers/environment/index.js @@ -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 = Environment; + + if (loading && !environment.length && !files.length) { + return ( + + {_title} + + + ); + } + + if (error) { + return ( + + {_title} + + + ); + } + + return ( + + {_title} + + + ); +}; + +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); diff --git a/packages/cp-frontend/src/containers/instances/list.js b/packages/cp-frontend/src/containers/instances/list.js index 0b42ca05..13e9e710 100644 --- a/packages/cp-frontend/src/containers/instances/list.js +++ b/packages/cp-frontend/src/containers/instances/list.js @@ -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 }) => { {_title} + title="Ooops!" + message="An error occured while loading your instances." + /> ); } diff --git a/packages/cp-frontend/src/containers/manifest/edit-or-create.js b/packages/cp-frontend/src/containers/manifest/edit-or-create.js index 3b2fa3ba..7b92f657 100644 --- a/packages/cp-frontend/src/containers/manifest/edit-or-create.js +++ b/packages/cp-frontend/src/containers/manifest/edit-or-create.js @@ -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 ( ); } renderReview() { - const { ReviewForm } = this.state; + const { ReviewForm, environmentToggles } = this.state; return ( ); } render() { - const { error, loading, defaultStage, manifestStage } = this.state; + const { error, defaultStage, manifestStage, manifest, name } = this.state; if (error) { - return ; + return ; } 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 }); } diff --git a/packages/cp-frontend/src/containers/manifest/index.js b/packages/cp-frontend/src/containers/manifest/index.js index b9d7a4da..789f008e 100644 --- a/packages/cp-frontend/src/containers/manifest/index.js +++ b/packages/cp-frontend/src/containers/manifest/index.js @@ -17,13 +17,15 @@ const Manifest = ({ error, manifest = '', environment = '', + files = [], deploymentGroup = null, + hasManifest = false, match }) => { const stage = match.params.stage; const _title = Edit Manifest; - if (loading || !deploymentGroup) { + if (loading || !deploymentGroup || !hasManifest) { return ( {_title} @@ -37,8 +39,9 @@ const Manifest = ({ {_title} + title="Ooops!" + message="An error occured while loading your deployment group." + /> ); } @@ -46,8 +49,9 @@ const Manifest = ({ const _notice = deploymentGroup && deploymentGroup.imported && !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 = ({ @@ -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 }) diff --git a/packages/cp-frontend/src/containers/service/delete.js b/packages/cp-frontend/src/containers/service/delete.js index e2d076e5..8c7b761c 100644 --- a/packages/cp-frontend/src/containers/service/delete.js +++ b/packages/cp-frontend/src/containers/service/delete.js @@ -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 { + onCloseClick={handleCloseClick} + /> ); } const { service, deleteServices } = this.props; - if(this.state.error) { + if (this.state.error) { return ( + onCloseClick={handleCloseClick} + /> ); } 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 ( diff --git a/packages/cp-frontend/src/containers/service/scale.js b/packages/cp-frontend/src/containers/service/scale.js index fc87e0f1..631108ef 100644 --- a/packages/cp-frontend/src/containers/service/scale.js +++ b/packages/cp-frontend/src/containers/service/scale.js @@ -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 { + onCloseClick={handleCloseClick} + /> ); } const { service, scale } = this.props; - if(this.state.error) { + if (this.state.error) { return ( + onCloseClick={handleCloseClick} + /> ); } @@ -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) { diff --git a/packages/cp-frontend/src/containers/services/list.js b/packages/cp-frontend/src/containers/services/list.js index 4043703a..cc286ab0 100644 --- a/packages/cp-frontend/src/containers/services/list.js +++ b/packages/cp-frontend/src/containers/services/list.js @@ -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 ( + title="Ooops!" + message="An error occured while loading your services." + /> ); } @@ -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 = ( - + ); } diff --git a/packages/cp-frontend/src/containers/services/menu.js b/packages/cp-frontend/src/containers/services/menu.js index 260fd4fe..80fb2192 100644 --- a/packages/cp-frontend/src/containers/services/menu.js +++ b/packages/cp-frontend/src/containers/services/menu.js @@ -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; diff --git a/packages/cp-frontend/src/containers/services/topology.js b/packages/cp-frontend/src/containers/services/topology.js index 6b703bed..1b7830f4 100644 --- a/packages/cp-frontend/src/containers/services/topology.js +++ b/packages/cp-frontend/src/containers/services/topology.js @@ -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 ( + title="Ooops!" + message="An error occured while loading your services." + /> ); } @@ -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 = ( - + ); } return (
- { renderedError } + {renderedError} .params.service}/instances`} />; -const App = p => ( +const App = p =>
- ( component={Manifest} /> + + ( component={servicesTopologyRedirect} /> -
-) +
; const Router = ( diff --git a/packages/cp-frontend/src/state/state.js b/packages/cp-frontend/src/state/state.js index 3abf866d..b0192f4c 100644 --- a/packages/cp-frontend/src/state/state.js +++ b/packages/cp-frontend/src/state/state.js @@ -13,6 +13,10 @@ const state = { { pathname: 'manifest/edit', name: 'Manifest' + }, + { + pathname: 'environment', + name: 'Environment' } ], services: [ diff --git a/packages/cp-frontend/src/state/store.js b/packages/cp-frontend/src/state/store.js index 13d67459..e282f209 100644 --- a/packages/cp-frontend/src/state/store.js +++ b/packages/cp-frontend/src/state/store.js @@ -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({ diff --git a/packages/cp-gql-mock-server/package.json b/packages/cp-gql-mock-server/package.json index c4048275..fe4d171f 100644 --- a/packages/cp-gql-mock-server/package.json +++ b/packages/cp-gql-mock-server/package.json @@ -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 } } diff --git a/packages/cp-gql-mock-server/src/resolvers.js b/packages/cp-gql-mock-server/src/resolvers.js index b9c1e893..5a82753c 100644 --- a/packages/cp-gql-mock-server/src/resolvers.js +++ b/packages/cp-gql-mock-server/src/resolvers.js @@ -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 }; diff --git a/packages/docker-compose-client/package.json b/packages/docker-compose-client/package.json index eb852074..2745965c 100644 --- a/packages/docker-compose-client/package.json +++ b/packages/docker-compose-client/package.json @@ -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" } } diff --git a/packages/portal-api/lib/data/transform.js b/packages/portal-api/lib/data/transform.js index 666e6d25..cb5b6031 100644 --- a/packages/portal-api/lib/data/transform.js +++ b/packages/portal-api/lib/data/transform.js @@ -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 }); }; diff --git a/packages/portal-api/package.json b/packages/portal-api/package.json index 41fe06bd..16f9ed04 100644 --- a/packages/portal-api/package.json +++ b/packages/portal-api/package.json @@ -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" } } diff --git a/packages/ui-toolkit/src/button/index.js b/packages/ui-toolkit/src/button/index.js index 4b1fad83..1f413794 100644 --- a/packages/ui-toolkit/src/button/index.js +++ b/packages/ui-toolkit/src/button/index.js @@ -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 + ? + : props.children; + return ( - {props.children} + {children} ); }; diff --git a/packages/ui-toolkit/src/card/card.js b/packages/ui-toolkit/src/card/card.js index b2f058ea..42c2b0de 100644 --- a/packages/ui-toolkit/src/card/card.js +++ b/packages/ui-toolkit/src/card/card.js @@ -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 ( - + {children} diff --git a/packages/ui-toolkit/src/card/header.js b/packages/ui-toolkit/src/card/header.js index ff37f44f..1f859bd0 100644 --- a/packages/ui-toolkit/src/card/header.js +++ b/packages/ui-toolkit/src/card/header.js @@ -35,7 +35,13 @@ const Header = ({ children, ...rest }) => { return ( - + {children} diff --git a/packages/ui-toolkit/src/card/options.js b/packages/ui-toolkit/src/card/options.js index 5b9a3d03..2b869447 100644 --- a/packages/ui-toolkit/src/card/options.js +++ b/packages/ui-toolkit/src/card/options.js @@ -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 + }) => { const render = ({ disabled = false, collapsed = false }) => - + {children} ; diff --git a/packages/ui-toolkit/src/card/subtitle.js b/packages/ui-toolkit/src/card/subtitle.js index fa0ad40a..a99057e0 100644 --- a/packages/ui-toolkit/src/card/subtitle.js +++ b/packages/ui-toolkit/src/card/subtitle.js @@ -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 + }) => { : children; - const render = ({ collapsed = false, disabled = false, fromHeader = false }) => + const render = ({ + collapsed = false, + disabled = false, + fromHeader = false + }) => props.theme.grey}; + height: ${remcalc(1)}; + margin: 0; +`; diff --git a/packages/ui-toolkit/src/index.js b/packages/ui-toolkit/src/index.js index d8039a66..c36b36c6 100644 --- a/packages/ui-toolkit/src/index.js +++ b/packages/ui-toolkit/src/index.js @@ -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'; diff --git a/packages/ui-toolkit/src/message/index.js b/packages/ui-toolkit/src/message/index.js index cb63e7ce..38380cf5 100644 --- a/packages/ui-toolkit/src/message/index.js +++ b/packages/ui-toolkit/src/message/index.js @@ -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 - ? {title} +const Message = ({ title, message, onCloseClick, type = 'MESSAGE' }) => { + const renderTitle = title + ? + {title} + : null; const renderClose = onCloseClick - ? + ? : null; return ( - { renderTitle } - {message} + {renderTitle} + + {message} + - { renderClose } + {renderClose} ); }; @@ -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; diff --git a/packages/ui-toolkit/src/modal/index.js b/packages/ui-toolkit/src/modal/index.js index f63117fa..891db4b3 100644 --- a/packages/ui-toolkit/src/modal/index.js +++ b/packages/ui-toolkit/src/modal/index.js @@ -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'; diff --git a/packages/ui-toolkit/src/progress-bar/index.js b/packages/ui-toolkit/src/progress-bar/index.js index 9417b5e9..a854bd8e 100644 --- a/packages/ui-toolkit/src/progress-bar/index.js +++ b/packages/ui-toolkit/src/progress-bar/index.js @@ -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; `; diff --git a/packages/ui-toolkit/src/progress-bar/item.js b/packages/ui-toolkit/src/progress-bar/item.js index 3262695f..03e8fd11 100644 --- a/packages/ui-toolkit/src/progress-bar/item.js +++ b/packages/ui-toolkit/src/progress-bar/item.js @@ -4,6 +4,7 @@ import Baseline from '../baseline'; const StyledItem = styled.li` float: left; + background-color: ${props => props.theme.white}; `; const ProgressbarItem = ({ children, ...props }) => diff --git a/packages/ui-toolkit/src/status-loader/index.js b/packages/ui-toolkit/src/status-loader/index.js index 13d59544..c736a640 100644 --- a/packages/ui-toolkit/src/status-loader/index.js +++ b/packages/ui-toolkit/src/status-loader/index.js @@ -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 }) => - - - + + + ; diff --git a/packages/ui-toolkit/src/topology/node/index.js b/packages/ui-toolkit/src/topology/node/index.js index 4fc5a5e9..6483bfb9 100644 --- a/packages/ui-toolkit/src/topology/node/index.js +++ b/packages/ui-toolkit/src/topology/node/index.js @@ -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 : ; - const nodeShadow = instancesActive ? =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"