feat(cp-frontend): add and edit env_file's

This commit is contained in:
Sérgio Ramos 2017-07-15 04:34:34 +01:00 committed by Judit Greskovits
parent 0f74cb5453
commit f68c2ae78a
12 changed files with 191 additions and 25 deletions

View File

@ -30,6 +30,7 @@
"joyent-ui-toolkit": "^1.1.0",
"lodash.get": "^4.4.2",
"lodash.isstring": "^4.0.1",
"lodash.remove": "^4.7.0",
"normalized-styled-components": "^1.0.8",
"param-case": "^2.1.1",
"prop-types": "^15.5.10",
@ -46,7 +47,7 @@
"redux": "^3.6.0",
"redux-actions": "^2.0.3",
"redux-batched-actions": "^0.2.0",
"redux-form": "^6.8.0",
"redux-form": "^7.0.0",
"remcalc": "^1.0.8",
"reselect": "^3.0.1",
"simple-statistics": "^4.1.0",
@ -54,7 +55,8 @@
"styled-is": "^1.0.11",
"styled-text-spinners": "^1.0.1",
"title-case": "^2.1.1",
"unitcalc": "^1.0.8"
"unitcalc": "^1.0.8",
"uuid": "^3.1.0"
},
"devDependencies": {
"apr-for-each": "^1.0.6",

View File

@ -49,6 +49,30 @@ const ButtonsRow = Row.extend`
margin-bottom: ${remcalc(60)};
`;
const FilenameContainer = styled.span`
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-content: stretch;
align-items: stretch;
`;
const FilenameInput = styled(Input)`
order: 0;
flex: 1 1 auto;
align-self: stretch;
`;
const FilenameRemove = Button.extend`
order: 0;
flex: 0 1 auto;
align-self: auto;
margin: ${remcalc(8)};
margin-right: 0;
height: ${remcalc(48)};
`;
const MEditor = ManifestEditor => ({ input, defaultValue }) =>
<ManifestEditor mode="yaml" {...input} value={input.value || defaultValue} />;
@ -71,7 +95,13 @@ export const Name = ({ handleSubmit, onCancel, dirty }) =>
</ButtonsRow>
</form>;
export const Manifest = ({ handleSubmit, onCancel, dirty, defaultValue }) =>
export const Manifest = ({
handleSubmit,
onCancel,
dirty,
defaultValue = '',
loading
}) =>
<form onSubmit={handleSubmit}>
<Bundle load={() => import('joyent-manifest-editor')}>
{ManifestEditor =>
@ -85,17 +115,67 @@ export const Manifest = ({ handleSubmit, onCancel, dirty, defaultValue }) =>
</Bundle>
<ButtonsRow>
<Button onClick={onCancel} secondary>Cancel</Button>
<Button disabled={!dirty} type="submit">
<Button
disabled={!(dirty || !loading || defaultValue.length)}
type="submit"
>
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>;
export const Files = ({ loading, files, onRemoveFile }) => {
if (loading) {
return null;
}
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>
<Bundle load={() => import('joyent-manifest-editor')}>
{ManifestEditor =>
ManifestEditor
? <Field
name={`file-value-${id}`}
defaultValue={value}
component={EEditor(ManifestEditor)}
/>
: <Dots2 />}
</Bundle>
</div>
);
return (
<div>
<H3>Files:</H3>
{_files}
</div>
);
};
export const Environment = ({
handleSubmit,
onCancel,
onAddFile,
onRemoveFile,
dirty,
defaultValue,
defaultValue = '',
files = [],
loading
}) =>
<form onSubmit={handleSubmit}>
@ -109,9 +189,14 @@ export const Environment = ({
/>
: <Dots2 />}
</Bundle>
<Files files={files} onRemoveFile={onRemoveFile} loading={loading} />
<ButtonsRow>
<Button onClick={onCancel} secondary>Cancel</Button>
<Button disabled={loading || !dirty} type="submit">
<Button type="button" onClick={onAddFile} secondary>Add File</Button>
<Button
disabled={!(dirty || !loading || defaultValue.length)}
type="submit"
>
{loading ? <Dots2 /> : 'Review'}
</Button>
</ButtonsRow>

View File

@ -1,6 +1,6 @@
import React from 'react';
import DeploymentGroupEditOrCreate from './edit-or-create';
import ManifestEditOrCreate from '@containers/manifest/edit-or-create';
import { Progress } from '@components/deployment-groups/create';
import { LayoutContainer } from '@components/layout';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
@ -21,7 +21,7 @@ export default ({
{loading && <DeploymentGroupsLoading />}
{error && <span>{error.toString()}</span>}
<Progress stage={stage} create />
<DeploymentGroupEditOrCreate
<ManifestEditOrCreate
create
manifest={manifest}
deploymentGroup={deploymentGroup}

View File

@ -1,4 +1,3 @@
export { default as DeploymentGroupList } from './list';
export { default as DeploymentGroupCreate } from './create';
export { default as DeploymentGroupImport } from './import';
export { default as DeploymentGroupManifest } from './manifest';

View File

@ -5,6 +5,8 @@ import { withRouter } from 'react-router';
import { Redirect } from 'react-router-dom';
import intercept from 'apr-intercept';
import paramCase from 'param-case';
import remove from 'lodash.remove';
import uuid from 'uuid/v4';
import DeploymentGroupBySlugQuery from '@graphql/DeploymentGroupBySlug.gql';
import DeploymentGroupCreateMutation from '@graphql/DeploymentGroupCreate.gql';
@ -25,7 +27,7 @@ class DeploymentGroupEditOrCreate extends Component {
constructor(props) {
super(props);
const { create, edit } = props;
const { create, edit, files = [] } = props;
const type = create ? 'create' : 'edit';
const NameForm =
@ -68,12 +70,21 @@ class DeploymentGroupEditOrCreate extends Component {
forceUnregisterOnUnmount: true
})(Review);
if (!files.length) {
files.push({
id: uuid(),
name: '',
value: '#'
});
}
this.state = {
defaultStage: create ? 'name' : 'edit',
manifestStage: create ? 'manifest' : 'edit',
name: '',
manifest: '',
environment: '',
files,
services: [],
loading: false,
error: null,
@ -97,6 +108,8 @@ class DeploymentGroupEditOrCreate extends Component {
this.handleEnvironmentSubmit = this.handleEnvironmentSubmit.bind(this);
this.handleReviewSubmit = this.handleReviewSubmit.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.handleFileAdd = this.handleFileAdd.bind(this);
this.handleRemoveFile = this.handleRemoveFile.bind(this);
if (edit) {
setTimeout(this.getDeploymentGroup, 16);
@ -132,7 +145,7 @@ class DeploymentGroupEditOrCreate extends Component {
};
provision = async deploymentGroupId => {
const { manifest, environment } = this.state;
const { manifest, environment, files } = this.state;
const { provisionManifest } = this.props;
const [err] = await intercept(
@ -141,6 +154,7 @@ class DeploymentGroupEditOrCreate extends Component {
type: 'COMPOSE',
format: 'YAML',
environment,
files,
raw: manifest
})
);
@ -161,15 +175,39 @@ class DeploymentGroupEditOrCreate extends Component {
}
handleManifestSubmit({ manifest = '' }) {
this.setState({ manifest }, () => {
this.setState({ manifest: manifest || this.props.manifest }, () => {
this.redirect({ stage: 'environment', prog: true });
});
}
handleEnvironmentSubmit({ environment = '' }) {
handleEnvironmentSubmit(change) {
const { environment = '' } = change;
const { name, manifest } = this.state;
const files = Object.values(
Object.keys(change).reduce((acc, key) => {
const match = key.match(/file-(name|value)-(.*)/);
if (!match) {
return acc;
}
const [_, type, id] = match;
if (!acc[id]) {
acc[id] = {
id
};
}
acc[id][type] = change[key];
return acc;
}, {})
);
const getConfig = async () => {
const { environment } = this.state;
const [err, conf] = await intercept(
client.query({
query: DeploymentGroupConfigQuery,
@ -179,6 +217,7 @@ class DeploymentGroupEditOrCreate extends Component {
type: 'COMPOSE',
format: 'YAML',
environment,
files,
raw: manifest
}
})
@ -193,12 +232,15 @@ class DeploymentGroupEditOrCreate extends Component {
const { data } = conf;
const { config: services } = data;
this.setState({ loading: false, services }, () => {
this.setState({ loading: false, services, files }, () => {
this.redirect({ stage: 'review', prog: true });
});
};
this.setState({ environment, loading: true }, getConfig);
this.setState(
{ environment: environment || this.props.environment, loading: true },
getConfig
);
}
handleReviewSubmit() {
@ -229,6 +271,24 @@ class DeploymentGroupEditOrCreate extends Component {
history.push(create ? '/' : `/deployment-groups/${deploymentGroup.slug}`);
}
handleFileAdd() {
this.setState({
files: this.state.files.concat([
{
id: uuid(),
name: '',
value: '#'
}
])
});
}
handleRemoveFile(fileId) {
this.setState({
files: remove(this.state.files, ({ id }) => id !== fileId)
});
}
redirect({ stage = 'name', prog = false }) {
const { match, history, create } = this.props;
@ -271,8 +331,11 @@ class DeploymentGroupEditOrCreate extends Component {
return (
<EnvironmentForm
defaultValue={this.props.environment}
files={this.state.files}
onSubmit={this.handleEnvironmentSubmit}
onCancel={this.handleCancel}
onAddFile={this.handleFileAdd}
onRemoveFile={this.handleRemoveFile}
loading={this.state.loading}
/>
);

View File

@ -5,7 +5,7 @@ import get from 'lodash.get';
import ManifestQuery from '@graphql/Manifest.gql';
import DeploymentGroupBySlugQuery from '@graphql/DeploymentGroupBySlug.gql';
import DeploymentGroupEditOrCreate from './edit-or-create';
import ManifestEditOrCreate from '@containers/manifest/edit-or-create';
import { Progress } from '@components/deployment-groups/create';
import { LayoutContainer } from '@components/layout';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
@ -15,6 +15,7 @@ const Manifest = ({
loading,
error,
manifest = '',
environment = '',
deploymentGroup = null,
match
}) => {
@ -24,9 +25,10 @@ const Manifest = ({
const _view = loading || !deploymentGroup
? null
: <DeploymentGroupEditOrCreate
: <ManifestEditOrCreate
edit
manifest={manifest}
environment={environment}
deploymentGroup={deploymentGroup}
/>;
@ -36,7 +38,8 @@ const Manifest = ({
deploymentGroup.imported &&
!manifest
? <span>
Since this DeploymentGroup was imported, it doesn't have the initial
Since this DeploymentGroup was imported, it doesn&#x27;t have the
initial
manifest
</span>
: null;
@ -62,6 +65,7 @@ export default compose(
}),
props: ({ data: { deploymentGroup, loading, error } }) => ({
manifest: get(deploymentGroup, 'version.manifest.raw', ''),
environment: get(deploymentGroup, 'version.manifest.environment', ''),
loading,
error
})

View File

@ -0,0 +1 @@
export default null;

View File

@ -1,7 +1,7 @@
#import "./ServiceInfo.gql"
query config($deploymentGroupName: String!, $type: ManifestType!, $format: ManifestFormat!, $environment: String!, $raw: String!) {
config(deploymentGroupName: $deploymentGroupName, type: $type, format: $format, environment: $environment, raw: $raw) {
query config($deploymentGroupName: String!, $type: ManifestType!, $format: ManifestFormat!, $environment: String!, $files: [KeyValueInput]!, $raw: String!) {
config(deploymentGroupName: $deploymentGroupName, type: $type, format: $format, environment: $environment, files: $files, raw: $raw) {
...ServiceInfo
config {
id

View File

@ -1,5 +1,5 @@
mutation provisionManifest($deploymentGroupId: ID!, $type: ManifestType!, $format: ManifestFormat!, $environment: String!, $raw: String!) {
provisionManifest(deploymentGroupId: $deploymentGroupId, type: $type, format: $format, environment: $environment, raw: $raw) {
mutation provisionManifest($deploymentGroupId: ID!, $type: ManifestType!, $format: ManifestFormat!, $environment: String!, $files: [KeyValueInput]!, $raw: String!) {
provisionManifest(deploymentGroupId: $deploymentGroupId, type: $type, format: $format, environment: $environment, files: $files, raw: $raw) {
scale {
serviceName
replicas

View File

@ -4,6 +4,7 @@ query ManifestById($deploymentGroupSlug: String!) {
manifest {
id
type
environment
format
raw
}

View File

@ -5,12 +5,13 @@ import styled from 'styled-components';
import { Header, Breadcrumb, Menu } from '@containers/navigation';
import { ServiceScale, ServiceDelete } from '@containers/service';
import { InstanceList } from '@containers/instances';
import Manifest from '@containers/manifest';
import Rollback from '@containers/rollback';
import {
DeploymentGroupList,
DeploymentGroupCreate,
DeploymentGroupImport,
DeploymentGroupManifest
DeploymentGroupImport
} from '@containers/deployment-groups';
import {
@ -116,7 +117,13 @@ const Router = (
<Route
path="/deployment-groups/:deploymentGroup/manifest/:stage?"
exact
component={DeploymentGroupManifest}
component={Manifest}
/>
<Route
path="/deployment-groups/:deploymentGroup/rollback"
exact
component={Rollback}
/>
<Route

View File

@ -13,6 +13,10 @@ const state = {
{
pathname: 'manifest',
name: 'Manifest'
},
{
pathname: 'rollback',
name: 'Rollback'
}
],
services: [