2018-04-12 12:53:00 +03:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import { Margin, Padding } from 'styled-components-spacing';
|
|
|
|
import { graphql, compose } from 'react-apollo';
|
|
|
|
import ReduxForm from 'declarative-redux-form';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { SubmissionError, destroy } from 'redux-form';
|
|
|
|
import { set, destroyAll } from 'react-redux-values';
|
|
|
|
import intercept from 'apr-intercept';
|
|
|
|
import get from 'lodash.get';
|
|
|
|
import omit from 'lodash.omit';
|
|
|
|
import uniqBy from 'lodash.uniqby';
|
|
|
|
import constantCase from 'constant-case';
|
|
|
|
|
|
|
|
import { H3, ViewContainer, Button } from 'joyent-ui-toolkit';
|
|
|
|
import { Forms } from '../constants';
|
|
|
|
import { Provider as ResourceSteps } from 'joyent-ui-resource-step';
|
|
|
|
import parseError from '../state/parse-error';
|
|
|
|
import CreateInstanceMutation from '../graphql/create-instance.gql';
|
|
|
|
|
|
|
|
import {
|
|
|
|
Name,
|
|
|
|
Image,
|
|
|
|
Package,
|
2018-05-09 13:36:16 +03:00
|
|
|
Networks,
|
2018-04-12 12:53:00 +03:00
|
|
|
Tags,
|
|
|
|
Metadata,
|
|
|
|
UserScript,
|
|
|
|
Firewall,
|
|
|
|
CNS,
|
|
|
|
Affinity
|
|
|
|
} from 'joyent-ui-instance-steps';
|
|
|
|
|
|
|
|
const { IC_F } = Forms;
|
|
|
|
const names = {
|
|
|
|
name: 'IC_NAME',
|
|
|
|
image: 'IC_IMAGE',
|
|
|
|
package: 'IC_PACKAGE',
|
2018-05-08 19:55:06 +03:00
|
|
|
networks: 'IC_NETWORKS',
|
2018-04-12 12:53:00 +03:00
|
|
|
tags: 'IC_TAGS',
|
|
|
|
metadata: 'IC_METADATA',
|
2018-05-08 20:02:01 +03:00
|
|
|
'user-script': 'IC_USERSCRIPT',
|
2018-04-12 12:53:00 +03:00
|
|
|
firewall: 'IC_FIREWALL',
|
|
|
|
cns: 'IC_CNS',
|
|
|
|
affinity: 'IC_AFFINITY'
|
|
|
|
};
|
|
|
|
|
|
|
|
class CreateInstance extends Component {
|
|
|
|
constructor(...args) {
|
|
|
|
super(...args);
|
|
|
|
this.isValids = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
setIsValid = name => ref => {
|
|
|
|
if (!ref) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { isValid } = ref;
|
|
|
|
|
|
|
|
if (!isValid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isValids = Object.assign({}, this.isValids, {
|
|
|
|
[name]: isValid
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
isFormValid = () => {
|
|
|
|
const { steps } = this.props;
|
|
|
|
|
2018-05-08 19:55:06 +03:00
|
|
|
return Object.keys(this.isValids).every(name =>
|
|
|
|
this.isValids[name](steps[name] || {})
|
|
|
|
);
|
2018-04-12 12:53:00 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
isStepValid = step => {
|
|
|
|
const { steps } = this.props;
|
|
|
|
const fn = this.isValids[step];
|
|
|
|
const values = steps[step];
|
|
|
|
|
|
|
|
if (!fn || !values) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fn(values);
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { match, steps, handleDefocus, handleSubmit, disabled } = this.props;
|
|
|
|
const { params } = match;
|
|
|
|
const { step } = params;
|
|
|
|
|
|
|
|
const {
|
|
|
|
name,
|
|
|
|
image,
|
|
|
|
package: packageResult,
|
2018-05-08 19:55:06 +03:00
|
|
|
networks,
|
2018-04-12 12:53:00 +03:00
|
|
|
tags,
|
|
|
|
metadata,
|
|
|
|
firewall,
|
|
|
|
cns,
|
|
|
|
affinity
|
|
|
|
} = steps;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ViewContainer main>
|
|
|
|
<Margin top={5}>
|
|
|
|
<H3>Create Instance</H3>
|
|
|
|
</Margin>
|
|
|
|
<Padding top="5">
|
|
|
|
<ResourceSteps namespace="instances/~create">
|
|
|
|
<Margin bottom="5">
|
|
|
|
<Name
|
|
|
|
ref={this.setIsValid('name')}
|
|
|
|
expanded={step === 'name'}
|
|
|
|
next="image"
|
|
|
|
saved={get(steps, 'name.name', false)}
|
|
|
|
onDefocus={handleDefocus('name')}
|
|
|
|
preview={name}
|
|
|
|
isValid={this.isStepValid('name')}
|
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
<Margin bottom="5">
|
|
|
|
<Image
|
|
|
|
ref={this.setIsValid('image')}
|
|
|
|
expanded={step === 'image'}
|
|
|
|
next="package"
|
|
|
|
saved={steps.image && steps.image.id}
|
|
|
|
onDefocus={handleDefocus('image')}
|
|
|
|
preview={image}
|
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
<Margin bottom="5">
|
|
|
|
<Package
|
|
|
|
ref={this.setIsValid('package')}
|
|
|
|
expanded={step === 'package'}
|
2018-05-08 19:55:06 +03:00
|
|
|
next="networks"
|
2018-04-12 12:53:00 +03:00
|
|
|
saved={steps.package}
|
|
|
|
onDefocus={handleDefocus('package')}
|
|
|
|
preview={packageResult}
|
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
<Margin bottom="5">
|
2018-05-09 13:36:16 +03:00
|
|
|
<Networks
|
2018-05-08 19:55:06 +03:00
|
|
|
ref={this.setIsValid('networks')}
|
|
|
|
expanded={step === 'networks'}
|
2018-04-12 12:53:00 +03:00
|
|
|
next="tags"
|
2018-05-09 13:36:16 +03:00
|
|
|
saved={steps.networks}
|
2018-05-08 19:55:06 +03:00
|
|
|
onDefocus={handleDefocus('networks')}
|
|
|
|
preview={networks}
|
2018-04-12 12:53:00 +03:00
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
<Margin bottom="5">
|
|
|
|
<Tags
|
|
|
|
ref={this.setIsValid('tags')}
|
|
|
|
expanded={step === 'tags'}
|
|
|
|
next="metadata"
|
|
|
|
saved={steps.tags && steps.tags.length}
|
|
|
|
onDefocus={handleDefocus('tags')}
|
|
|
|
preview={tags}
|
|
|
|
optional
|
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
<Margin bottom="5">
|
|
|
|
<Metadata
|
|
|
|
ref={this.setIsValid('metadata')}
|
|
|
|
expanded={step === 'metadata'}
|
|
|
|
next="user-script"
|
|
|
|
saved={steps.metadata && steps.metadata.length}
|
|
|
|
onDefocus={handleDefocus('metadata')}
|
|
|
|
preview={metadata}
|
|
|
|
optional
|
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
<Margin bottom="5">
|
|
|
|
<UserScript
|
2018-05-08 20:02:01 +03:00
|
|
|
ref={this.setIsValid('user-script')}
|
2018-04-12 12:53:00 +03:00
|
|
|
expanded={step === 'user-script'}
|
|
|
|
next="firewall"
|
2018-05-08 20:02:01 +03:00
|
|
|
saved={get(steps, 'user-script.lines', false)}
|
2018-04-12 12:53:00 +03:00
|
|
|
onDefocus={handleDefocus('user-script')}
|
2018-05-08 20:02:01 +03:00
|
|
|
preview={steps['user-script']}
|
2018-04-12 12:53:00 +03:00
|
|
|
optional
|
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
<Margin bottom="5">
|
|
|
|
<Firewall
|
|
|
|
ref={this.setIsValid('firewall')}
|
|
|
|
expanded={step === 'firewall'}
|
|
|
|
next="cns"
|
|
|
|
saved={steps.firewall}
|
|
|
|
onDefocus={handleDefocus('firewall')}
|
|
|
|
preview={firewall}
|
|
|
|
optional
|
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
<Margin bottom="5">
|
|
|
|
<CNS
|
|
|
|
ref={this.setIsValid('cns')}
|
|
|
|
expanded={step === 'cns'}
|
|
|
|
next="affinity"
|
|
|
|
saved={steps.cns}
|
|
|
|
onDefocus={handleDefocus('cns')}
|
|
|
|
preview={cns}
|
|
|
|
optional
|
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
<Margin bottom="5">
|
|
|
|
<Affinity
|
|
|
|
ref={this.setIsValid('affinity')}
|
|
|
|
expanded={step === 'affinity'}
|
|
|
|
next=""
|
|
|
|
saved={steps.affinity}
|
|
|
|
onDefocus={handleDefocus('affinity')}
|
|
|
|
preview={affinity}
|
|
|
|
optional
|
|
|
|
/>
|
|
|
|
</Margin>
|
|
|
|
</ResourceSteps>
|
|
|
|
<Margin bottom={5}>
|
|
|
|
<ReduxForm form={IC_F} onSubmit={handleSubmit}>
|
|
|
|
{({ handleSubmit, submitting }) => (
|
|
|
|
<form onSubmit={handleSubmit}>
|
2018-05-08 19:55:06 +03:00
|
|
|
<Button
|
|
|
|
disabled={disabled || !this.isFormValid()}
|
|
|
|
loading={submitting}
|
|
|
|
>
|
2018-04-12 12:53:00 +03:00
|
|
|
Deploy
|
|
|
|
</Button>
|
|
|
|
</form>
|
|
|
|
)}
|
|
|
|
</ReduxForm>
|
|
|
|
</Margin>
|
|
|
|
</Padding>
|
|
|
|
</ViewContainer>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default compose(
|
|
|
|
graphql(CreateInstanceMutation, { name: 'createInstance' }),
|
|
|
|
connect(({ form, values = {} }, { match, location }) => {
|
|
|
|
const steps = {
|
|
|
|
name: values[names.name],
|
|
|
|
image: values[names.image],
|
|
|
|
package: values[names.package],
|
2018-05-08 19:55:06 +03:00
|
|
|
networks: values[names.networks],
|
2018-04-12 12:53:00 +03:00
|
|
|
tags: values[names.tags],
|
|
|
|
metadata: values[names.metadata],
|
2018-05-08 20:02:01 +03:00
|
|
|
'user-script': values[names['user-script']],
|
2018-04-12 12:53:00 +03:00
|
|
|
firewall: values[names.firewall],
|
|
|
|
cns: values[names.cns],
|
|
|
|
affinity: values[names.affinity]
|
|
|
|
};
|
|
|
|
|
|
|
|
const error = get(form, `${IC_F}.error`, null);
|
|
|
|
|
|
|
|
// Maybe re-use saved to only write the rule once
|
2018-05-08 19:55:06 +03:00
|
|
|
const disabled = !(
|
|
|
|
!error &&
|
|
|
|
steps.name &&
|
|
|
|
steps.name.name &&
|
|
|
|
steps.image &&
|
|
|
|
steps.image.id &&
|
|
|
|
steps.package &&
|
|
|
|
steps.package.id &&
|
|
|
|
steps.networks &&
|
|
|
|
Array.isArray(steps.networks)
|
2018-04-12 12:53:00 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
return {
|
2018-05-08 19:55:06 +03:00
|
|
|
disabled,
|
2018-04-12 12:53:00 +03:00
|
|
|
forms: Object.keys(form), // improve this
|
|
|
|
error,
|
|
|
|
steps
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
connect(null, (dispatch, { steps = {}, forms, history, createInstance }) => {
|
|
|
|
const parseAffRule = ({
|
|
|
|
conditional,
|
|
|
|
placement,
|
|
|
|
identity,
|
|
|
|
name,
|
|
|
|
pattern,
|
|
|
|
value
|
|
|
|
}) => {
|
|
|
|
const type = constantCase(
|
|
|
|
`${conditional}_${placement === 'same' ? 'equal' : 'not_equal'}`
|
|
|
|
);
|
|
|
|
|
|
|
|
const patterns = {
|
|
|
|
equalling: value => value,
|
|
|
|
starting: value => `/^${value}/`
|
|
|
|
};
|
|
|
|
|
|
|
|
const _name = identity === 'name' ? 'instance' : name;
|
|
|
|
const _value = patterns[pattern](type === 'name' ? name : value);
|
|
|
|
|
|
|
|
return {
|
|
|
|
type,
|
|
|
|
key: _name,
|
|
|
|
value: _value
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
2018-05-09 13:11:03 +03:00
|
|
|
handleDefocus: name => value => {
|
2018-05-09 13:21:40 +03:00
|
|
|
return dispatch(set({ name: names[name], value }));
|
2018-05-09 13:11:03 +03:00
|
|
|
},
|
2018-04-12 12:53:00 +03:00
|
|
|
|
|
|
|
handleSubmit: async () => {
|
|
|
|
const _affinity = steps.affinity ? parseAffRule(steps.affinity) : null;
|
|
|
|
const _name = steps.name && steps.name.name.toLowerCase();
|
|
|
|
|
2018-05-09 13:21:40 +03:00
|
|
|
const _metadata =
|
|
|
|
(steps.metadata && steps.metadata.map(a => omit(a, 'open'))) || [];
|
2018-04-12 12:53:00 +03:00
|
|
|
|
|
|
|
const _tags =
|
2018-05-09 13:21:40 +03:00
|
|
|
(steps.tags &&
|
|
|
|
uniqBy(steps.tags.map(a => omit(a, 'expanded')), 'name').map(a =>
|
|
|
|
omit(a, 'expanded')
|
|
|
|
)) ||
|
|
|
|
[];
|
2018-04-12 12:53:00 +03:00
|
|
|
|
2018-05-09 13:21:40 +03:00
|
|
|
const _networks = steps.networks && steps.networks.map(({ id }) => id);
|
2018-04-12 12:53:00 +03:00
|
|
|
|
2018-05-09 13:11:03 +03:00
|
|
|
if (steps['user-script'] && steps['user-script'].lines) {
|
2018-05-09 13:21:40 +03:00
|
|
|
_metadata.push({
|
|
|
|
name: 'user-script',
|
|
|
|
value: steps['user-script'].script
|
|
|
|
});
|
2018-04-12 12:53:00 +03:00
|
|
|
}
|
|
|
|
|
2018-05-09 13:11:03 +03:00
|
|
|
if (steps.cns) {
|
|
|
|
_tags.push({
|
2018-04-12 12:53:00 +03:00
|
|
|
name: 'triton.cns.disable',
|
|
|
|
value: !steps.cns.cnsEnabled
|
|
|
|
});
|
2018-05-09 13:11:03 +03:00
|
|
|
}
|
2018-04-12 12:53:00 +03:00
|
|
|
|
2018-05-09 13:21:40 +03:00
|
|
|
if (steps.cns && (steps.cns.serviceNames && steps.cns.cnsEnabled)) {
|
2018-05-09 13:11:03 +03:00
|
|
|
_tags.push({
|
2018-04-12 12:53:00 +03:00
|
|
|
name: 'triton.cns.services',
|
2018-05-09 13:21:40 +03:00
|
|
|
value: steps.cns.serviceNames.join(',')
|
2018-04-12 12:53:00 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const [err, res] = await intercept(
|
|
|
|
createInstance({
|
|
|
|
variables: {
|
|
|
|
name: _name,
|
|
|
|
package: steps.package.id,
|
|
|
|
image: steps.image.id,
|
|
|
|
affinity: _affinity ? [_affinity] : [],
|
|
|
|
metadata: _metadata,
|
|
|
|
tags: _tags,
|
|
|
|
firewall_enabled: steps.firewall
|
|
|
|
? steps.firewall.enabled
|
|
|
|
: undefined,
|
|
|
|
networks: _networks && _networks.length ? _networks : undefined
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
throw new SubmissionError({
|
|
|
|
_error: parseError(err)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch([destroyAll(), forms.map(name => destroy(name))]);
|
|
|
|
history.push(`/instances/${res.data.createMachine.id}`);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})
|
|
|
|
)(CreateInstance);
|