joyent-portal/packages/cp-frontend/src/components/manifest/edit-or-create.js

461 lines
10 KiB
JavaScript

import React, { Component } from 'react';
import { Field } from 'redux-form';
import styled from 'styled-components';
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';
import {
FormGroup,
FormMeta,
Input,
Button,
Card,
Progressbar,
ProgressbarItem,
ProgressbarButton,
H3,
P,
typography,
Divider,
Chevron
} from 'joyent-ui-toolkit';
const EnvironmentChevron = styled(Chevron)`
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: 0;
`;
const ServiceName = H3.extend`
margin-top: 0;
margin-bottom: ${remcalc(5)};
line-height: 1.6;
font-size: ${remcalc(18)};
`;
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: ${remcalc(29)} 0 ${remcalc(60)} 0;
`;
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;
margin: 0 0 ${remcalc(13)} 0;
`;
const FilenameRemove = Button.extend`
order: 0;
flex: 0 1 auto;
align-self: auto;
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();
this.state = {};
this.handleRender = this.handleRender.bind(this);
}
handleRender(ManifestEditor) {
if (ManifestEditor) {
setTimeout(() => {
this.setState({ ManifestEditor });
}, 80);
}
return <Loader />;
}
render() {
if (!this.state.ManifestEditor) {
return (
<Bundle load={() => import('joyent-manifest-editor')}>
{this.handleRender}
</Bundle>
);
}
const { ManifestEditor } = this.state;
const { children, ...rest } = this.props;
return <ManifestEditor {...rest}>{children}</ManifestEditor>;
}
}
const MEditor = ({ input, defaultValue, readOnly }) => (
<ManifestEditorBundle
mode="yaml"
{...input}
value={input.value || defaultValue}
readOnly={readOnly}
/>
);
const EEditor = ({ input, defaultValue, readOnly }) => (
<ManifestEditorBundle
mode="ini"
{...input}
value={input.value || defaultValue}
readOnly={readOnly}
/>
);
export const Name = ({ handleSubmit, onCancel, dirty }) => (
<form onSubmit={handleSubmit}>
<Row>
<Col xs={12} md={3} lg={3}>
<FormGroup name="name" reduxForm>
<FormMeta left />
<Input type="text" />
</FormGroup>
</Col>
</Row>
<ButtonsRow>
<Button type="button" onClick={onCancel} secondary>
Cancel
</Button>
<Button type="submit" disabled={!dirty}>
Next
</Button>
</ButtonsRow>
</form>
);
export const Manifest = ({
handleSubmit,
onCancel,
dirty,
defaultValue = '',
loading
}) => (
<form onSubmit={handleSubmit}>
<Field name="manifest" defaultValue={defaultValue} component={MEditor} />
<ButtonsRow>
<Button type="button" onClick={onCancel} secondary>
Cancel
</Button>
<Button
disabled={!(dirty || !loading || defaultValue.length)}
loading={loading}
type="submit"
>
Environment
</Button>
</ButtonsRow>
</form>
);
const File = ({ id, name, value, onRemoveFile, readOnly }) => {
const removeButton = !readOnly ? (
<FilenameRemove type="button" onClick={onRemoveFile} secondary>
Remove
</FilenameRemove>
) : null;
const fileEditor = !readOnly ? (
<Field name={`file-value-${id}`} defaultValue={value} component={EEditor} />
) : (
<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>
{files.map(({ id, ...rest }) => (
<File
key={id}
id={id}
onRemoveFile={() => onRemoveFile(id)}
readOnly={readOnly}
{...rest}
/>
))}
{footer}
</div>
);
};
export const Environment = ({
handleSubmit,
onCancel,
onAddFile,
onRemoveFile,
dirty,
defaultValue = '',
files = [],
readOnly = false,
loading
}) => {
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>
<ImageTitle>Image:</ImageTitle> <Image>{config.image}</Image>
</dt>
</Dl>
{config.environment && config.environment.length ? (
<ServiceDivider />
) : null}
{config.environment && config.environment.length ? (
<ServiceEnvironmentTitle
expanded={environmentToggles[name]}
onClick={() => onEnvironmentToggle(name)}
>
Environment variables{' '}
<EnvironmentChevron
down={!environmentToggles[name]}
up={environmentToggles[name]}
/>
</ServiceEnvironmentTitle>
) : null}
{config.environment &&
config.environment.length &&
environmentToggles[name] ? (
<EnvironmentReview environment={config.environment} />
) : null}
</ServiceCard>
));
return (
<form onSubmit={handleSubmit}>
{serviceList}
<ButtonsRow>
<Button type="button" onClick={onCancel} disabled={loading} secondary>
Cancel
</Button>
<Button disabled={loading} loading={loading} type="submit">
Confirm and Deploy
</Button>
</ButtonsRow>
</form>
);
};
export const Progress = ({ stage, create, edit }) => {
const _nameCompleted = stage !== 'name';
const _nameActive = stage === 'name';
const _name = !create ? null : (
<ProgressbarItem>
<ProgressbarButton
zIndex="10"
completed={_nameCompleted}
active={_nameActive}
first
>
Name the group
</ProgressbarButton>
</ProgressbarItem>
);
const _manifestCompleted = ['environment', 'review'].indexOf(stage) >= 0;
const _manifestActive = create ? stage === 'manifest' : stage === 'edit';
const _manifest = (
<ProgressbarItem>
<ProgressbarButton
zIndex="9"
completed={_manifestCompleted}
active={_manifestActive}
first={edit}
>
Define Services
</ProgressbarButton>
</ProgressbarItem>
);
const _environmentCompleted = stage === 'review';
const _environmentActive = stage === 'environment';
const _environment = (
<ProgressbarItem>
<ProgressbarButton
zIndex="8"
completed={_environmentCompleted}
active={_environmentActive}
>
Define Environment
</ProgressbarButton>
</ProgressbarItem>
);
const _reviewActive = stage === 'review';
const _review = (
<ProgressbarItem>
<ProgressbarButton zIndex="7" active={_reviewActive} last>
Review and deploy
</ProgressbarButton>
</ProgressbarItem>
);
return (
<Progressbar>
{_name}
{_manifest}
{_environment}
{_review}
</Progressbar>
);
};