feat(joyent-cp-frontend): create dg initial implementation

This commit is contained in:
Sérgio Ramos 2017-06-09 05:26:25 +01:00 committed by Judit Greskovits
parent b5a8a8e23d
commit 1cc2330b22
46 changed files with 8542 additions and 1096 deletions

View File

@ -34,8 +34,6 @@ Gruntfile.js
.flowconfig
.documentup.json
.yarn-metadata.json
.*.yml
*.yml
# misc
*.gz

View File

@ -2,6 +2,8 @@
"lerna": "2.0.0-rc.4",
"version": "independent",
"npmClient": "yarn",
"hoist": true,
"nohoist": ["graphi"],
"packages": [
"packages/*"
]

View File

@ -46,7 +46,7 @@
"conventional-changelog-lint": "^1.1.9",
"conventional-changelog-lint-config-angular": "^0.4.1",
"conventional-changelog-lint-config-lerna-scopes": "^1.0.0",
"cross-env": "^5.0.0",
"cross-env": "^5.0.1",
"dotenv": "^4.0.0",
"eslint": "^3.19.0",
"eslint-config-prettier": "^2.1.1",
@ -58,11 +58,11 @@
"eslint-plugin-prettier": "^2.1.1",
"eslint-plugin-react": "^7.0.1",
"eslint-tap": "^2.0.1",
"execa": "^0.6.3",
"execa": "^0.7.0",
"figures": "^2.0.0",
"force-array": "^3.1.0",
"husky": "^0.13.4",
"inquirer": "^3.0.6",
"inquirer": "^3.1.0",
"lerna": "^2.0.0-rc.5",
"lerna-wizard": "ramitos/lerna-wizard#7bcdc11",
"license-to-fail": "^2.2.0",
@ -71,9 +71,7 @@
"lodash.isstring": "^4.0.1",
"lodash.uniq": "^4.5.0",
"lodash.uniqby": "^4.7.0",
"npm-check-updates": "^2.11.2",
"per-env": "^1.0.2",
"prettier": "1.3.1",
"prettier": "1.4.4",
"quality-docs": "^3.3.0",
"read-pkg": "^2.0.0",
"redrun": "^5.9.14",

View File

@ -15,11 +15,11 @@
"dependencies": {
"bunyan": "^1.8.10",
"dotenv": "^4.0.0",
"express": "^4.15.2",
"express-graphql": "^0.6.4",
"got": "^6.7.1",
"graphql": "^0.9.3",
"smartdc-auth": "^2.5.2",
"express": "^4.15.3",
"express-graphql": "^0.6.6",
"got": "^7.0.0",
"graphql": "^0.10.1",
"smartdc-auth": "^2.5.5",
"triton": "^5.2.0"
},
"devDependencies": {

View File

@ -20,37 +20,44 @@
},
"dependencies": {
"apollo": "^0.2.2",
"apr-intercept": "^1.0.4",
"constant-case": "^2.0.0",
"graphql-tag": "^2.0.0",
"graphql-tag": "^2.2.2",
"joyent-manifest-editor": "^1.0.0",
"joyent-ui-toolkit": "^1.1.0",
"lodash.isstring": "^4.0.1",
"normalized-styled-components": "^1.0.5",
"normalized-styled-components": "^1.0.8",
"param-case": "^2.1.1",
"prop-types": "^15.5.10",
"react": "^15.5.4",
"react-apollo": "^1.2.0",
"react-apollo": "^1.4.2",
"react-bundle": "^1.0.3",
"react-codemirror": "^1.0.0",
"react-dom": "^15.5.4",
"react-redux": "^5.0.4",
"react-redux": "^5.0.5",
"react-router": "^4.1.1",
"react-router-dom": "^4.1.1",
"react-styled-flexboxgrid": "^1.1.2",
"react-styled-flexboxgrid": "^2.0.0",
"redux": "^3.6.0",
"redux-actions": "^2.0.3",
"redux-batched-actions": "^0.2.0",
"redux-form": "^6.7.0",
"remcalc": "^1.0.5",
"redux-form": "^6.8.0",
"remcalc": "^1.0.8",
"reselect": "^3.0.1",
"simple-statistics": "^4.1.0",
"styled-components": "^2.0.0",
"styled-is": "^1.0.7",
"unitcalc": "^1.0.5"
"styled-components": "^2.0.1",
"styled-is": "^1.0.11",
"styled-text-spinners": "^1.0.1",
"unitcalc": "^1.0.8"
},
"devDependencies": {
"apr-find": "^1.0.5",
"apr-for-each": "^1.0.6",
"apr-main": "^1.0.7",
"babel-plugin-inline-react-svg": "^0.4.0",
"babel-plugin-styled-components": "^1.1.4",
"babel-preset-joyent-portal": "^1.0.0",
"cross-env": "^5.0.0",
"babel-preset-joyent-portal": "^1.0.3",
"cross-env": "^5.0.1",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
"jest": "^20.0.4",
@ -62,12 +69,12 @@
"jest-snapshot": "^20.0.3",
"jest-styled-components": "^3.0.0-2",
"mz": "^2.6.0",
"react-scripts": "^1.0.0",
"react-scripts": "^1.0.7",
"react-test-renderer": "^15.5.4",
"redrun": "^5.9.14",
"stylelint": "^7.10.1",
"stylelint": "^7.11.0",
"stylelint-config-primer": "^1.4.0",
"stylelint-config-standard": "^16.0.0",
"stylelint-processor-styled-components": "^0.1.0"
"stylelint-processor-styled-components": "styled-components/stylelint-processor-styled-components#68b4c4f"
}
}

View File

@ -5,6 +5,16 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
<style>
.CodeMirror, .ReactCodeMirror {
height: 100% !important;
}
.CodeMirror {
border: solid 1px #d8d8d8;
margin-bottom: 8px;
}
</style>
</head>
<body>
<div id="root"></div>

View File

@ -1,10 +1,9 @@
const { readFile, writeFile, exists } = require('mz/fs');
const main = require('apr-main');
const forEach = require('apr-for-each');
const find = require('apr-find');
const path = require('path');
const ROOT = path.join(__dirname, '../node_modules/react-scripts/config');
const configs = ['webpack.config.dev', 'webpack.config.prod'];
const toCopy = [
@ -13,6 +12,8 @@ const toCopy = [
'webpack.config.prod'
];
let ROOT;
const backup = async file => {
const backupPath = path.join(ROOT, `${file}.original.js`);
const backupExists = await exists(backupPath);
@ -36,6 +37,15 @@ const copy = async file => {
main(
(async () => {
ROOT = await find([
path.join(__dirname, '../node_modules/react-scripts/config'),
path.join(__dirname, '../../../node_modules/react-scripts/config')
], exists);
if (!ROOT) {
throw new Error('react-scripts not found');
}
await forEach(configs, backup);
await forEach(toCopy, copy);
})()

View File

@ -0,0 +1,53 @@
import React from 'react';
import { FormGroup, FormMeta, Input, Button } from 'joyent-ui-toolkit';
import { Field } from 'redux-form';
import { Row, Col } from 'react-styled-flexboxgrid';
import { Dots2 } from 'styled-text-spinners';
import Bundle from 'react-bundle';
const Editor = ManifestEditor => ({ input }) =>
<ManifestEditor mode="yaml" {...input} />;
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>
<Row>
<Button onClick={onCancel} secondary>Cancel</Button>
<Button type="submit" disabled={!dirty}>Next</Button>
</Row>
</form>;
export const Manifest = ({ handleSubmit, onCancel, dirty, mode }) =>
<form onSubmit={handleSubmit}>
<Bundle load={() => import('joyent-manifest-editor')}>
{ManifestEditor =>
ManifestEditor
? <Field name="manifest" component={Editor(ManifestEditor)} />
: <Dots2 />}
</Bundle>
<Row>
<Button onClick={onCancel} secondary>Cancel</Button>
<Button type="submit" disabled={!dirty}>Review</Button>
</Row>
</form>;
export const Review = ({ handleSubmit, onCancel, dirty, ...state }) =>
<form onSubmit={handleSubmit}>
<pre>{state.deploymentGroupName}</pre>
<pre>{state.manifest}</pre>
<Row>
<Button onClick={onCancel} disabled={state.loading} secondary>
Cancel
</Button>
<Button disabled={state.loading} type="submit">
{state.loading ? <Dots2 /> : 'Provision'}
</Button>
</Row>
</form>;

View File

@ -2,10 +2,9 @@ import React from 'react';
import { Col, Row } from 'react-styled-flexboxgrid';
export default () => (
export default () =>
<Row>
<Col xs={12}>
<p>you don't have any deployment groups</p>
</Col>
</Row>
);
</Row>;

View File

@ -3,10 +3,9 @@ import React from 'react';
import { Col, Row } from 'react-styled-flexboxgrid';
import { P } from 'joyent-ui-toolkit';
export default () => (
export default () =>
<Row>
<Col xs={12}>
<P>You don't have any instances</P>
</Col>
</Row>
);
</Row>;

View File

@ -13,7 +13,7 @@ const InstanceCard = ({
instance = {},
onOptionsClick = () => null,
toggleCollapsed = () => null
}) => (
}) =>
<Card collapsed={true} key={instance.uuid}>
<CardView>
<CardMeta onClick={toggleCollapsed}>
@ -21,8 +21,7 @@ const InstanceCard = ({
</CardMeta>
</CardView>
<CardOptions onClick={onOptionsClick} />
</Card>
);
</Card>;
InstanceCard.propTypes = {
instance: PropTypes.object,

View File

@ -47,7 +47,7 @@ function getBreadcrumbLinks(links) {
return null;
}
const Breadcrumb = ({ links = [] }) => (
const Breadcrumb = ({ links = [] }) =>
<Grid>
<Row>
<Col xs={12}>
@ -58,8 +58,7 @@ const Breadcrumb = ({ links = [] }) => (
</StyledDiv>
</Col>
</Row>
</Grid>
);
</Grid>;
Breadcrumb.propTypes = {
links: PropTypes.arrayOf(

View File

@ -15,7 +15,7 @@ const StyledLogo = Img.extend`
height: ${remcalc(25)};
`;
const NavHeader = ({ datacenter, username }) => (
const NavHeader = ({ datacenter, username }) =>
<Header>
<HeaderBrand>
<Link to="/">
@ -24,8 +24,7 @@ const NavHeader = ({ datacenter, username }) => (
</HeaderBrand>
<HeaderItem>{datacenter}</HeaderItem>
<HeaderItem>{username}</HeaderItem>
</Header>
);
</Header>;
NavHeader.propTypes = {
datacenter: PropTypes.string,

View File

@ -16,7 +16,7 @@ const StyledBox = styled.div`
}
`;
export default () => (
export default () =>
<LayoutContainer>
<Row>
<Col>
@ -28,7 +28,8 @@ export default () => (
<Col md={10}>
<H3>Import your services</H3>
<P>
You can import your services from a Git repository hosting service. Learn more.
You can import your services from a Git repository hosting
service. Learn more.
</P>
<Button secondary>from GitHub</Button>
<Button secondary>from GitLab</Button>
@ -42,7 +43,9 @@ export default () => (
<Col md={9}>
<H3>Alternatively, you can upload or edit manifest file.</H3>
<P>
Manifest is a file describing your services. It is similar to Docker Compose file. You can upload a file from you local machine or edit it manually. Learn more.
Manifest is a file describing your services. It is similar
to Docker Compose file. You can upload a file from you local
machine or edit it manually. Learn more.
</P>
<Button secondary>Upload manifest</Button>
<Button secondary>Edit manifest</Button>
@ -53,5 +56,4 @@ export default () => (
</Row>
</Col>
</Row>
</LayoutContainer>
);
</LayoutContainer>;

View File

@ -44,13 +44,13 @@ const ServiceListItem = ({
const isChild = Boolean(service.parent);
const children = service.children
? service.children.map(service => (
? service.children.map(service =>
<ServiceListItem
key={service.id}
deploymentGroup={deploymentGroup}
service={service}
/>
))
)
: null;
const to = `/deployment-groups/${deploymentGroup}/services/${service.slug}`;

View File

@ -0,0 +1,231 @@
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
import { compose, graphql } from 'react-apollo';
import { Redirect } from 'react-router-dom';
import intercept from 'apr-intercept';
import PropTypes from 'prop-types';
import paramCase from 'param-case';
import DeploymentGroupBySlug from '@graphql/DeploymentGroupBySlug.gql';
import DeploymentGroupCreateMutation from '@graphql/DeploymentGroupCreate.gql';
import DeploymentGroupProvisionMutation from '@graphql/DeploymentGroupProvision.gql';
import { client } from '@state/store';
import { LayoutContainer } from '@components/layout';
import { Name, Manifest, Review } from '@components/deployment-groups/create';
import { H2 } from 'joyent-ui-toolkit';
const Title = H2.extend`
margin-top: 0;
`;
const validateName = async ({ name = '' }) => {
const { data } = await client.query({
fetchPolicy: 'network-only',
query: DeploymentGroupBySlug,
variables: {
slug: paramCase(name.trim())
}
});
if (data.deploymentGroups.length) {
throw { name: `"${name}" already exists!` };
}
};
const NameForm = reduxForm({
form: 'create-deployment-group',
destroyOnUnmount: true,
forceUnregisterOnUnmount: true,
asyncValidate: validateName
})(Name);
const ManifestForm = reduxForm({
form: 'create-deployment-group',
destroyOnUnmount: true,
forceUnregisterOnUnmount: true
})(Manifest);
const ReviewForm = reduxForm({
form: 'create-deployment-group',
destroyOnUnmount: true,
forceUnregisterOnUnmount: true
})(Review);
// TODO: move state to redux. why: because in redux we can cache transactional
// state between refreshes
class DeploymentGroupCreate extends Component {
state = {
deploymentGroupName: '',
manifest: '',
loading: false,
error: null
};
constructor() {
super();
this.stages = {
name: this.renderNameForm.bind(this),
manifest: this.renderManifestEditor.bind(this),
review: this.renderReview.bind(this)
};
this.handleNameSubmit = this.handleNameSubmit.bind(this);
this.handleManifestSubmit = this.handleManifestSubmit.bind(this);
this.handleReviewSubmit = this.handleReviewSubmit.bind(this);
this.handleCancel = this.handleCancel.bind(this);
}
handleNameSubmit({ name = '' }) {
this.setState({ deploymentGroupName: name }, () =>
this.redirect({ stage: 'manifest', prog: true })
);
}
handleManifestSubmit({ manifest = '' }) {
this.setState({ manifest }, () =>
this.redirect({ stage: 'review', prog: true })
);
}
handleReviewSubmit() {
const { history } = this.props;
const submit = async () => {
const { id, slug } = await this.createDeploymentGroup();
if (!id) {
return;
}
const manifest = await this.provision(id);
if (!manifest) {
return;
}
history.push(`/deployment-groups/${slug}`);
};
this.setState({ loading: true }, submit);
}
createDeploymentGroup = async () => {
const { deploymentGroupName } = this.state;
const { createDeploymentGroup } = this.props;
const [err, res] = await intercept(
createDeploymentGroup({ name: deploymentGroupName })
);
if (err) {
this.setState({
error: err.message
});
}
return err ? null : res.data.createDeploymentGroup;
};
provision = async deploymentGroupId => {
const { manifest } = this.state;
const { provisionManifest } = this.props;
const [err, { data }] = await intercept(
provisionManifest({
deploymentGroupId,
type: 'COMPOSE',
format: 'YAML',
raw: manifest
})
);
if (err) {
this.setState({
error: err.message
});
}
return err ? null : true;
};
renderNameForm() {
return (
<NameForm onSubmit={this.handleNameSubmit} onCancel={this.handleCancel} />
);
}
renderManifestEditor() {
return (
<ManifestForm
onSubmit={this.handleManifestSubmit}
onCancel={this.handleCancel}
/>
);
}
renderReview() {
return (
<ReviewForm
onSubmit={this.handleReviewSubmit}
onCancel={this.handleCancel}
{...this.state}
/>
);
}
handleCancel() {
const { history } = this.props;
history.push('/');
}
redirect({ stage = 'name', prog = false }) {
const { match, history } = this.props;
const pathname = match.url.replace(/~create(.*)/, '~create');
const to = `${pathname}/${stage}`;
if (!prog) {
return <Redirect to={to} />;
}
history.push(to);
}
render() {
const { match } = this.props;
const stage = match.params.stage;
if (!stage) {
return this.redirect({ stage: 'name' });
}
if (!this.stages[stage]) {
return this.redirect({ stage: 'name' });
}
const view = this.stages[stage]();
return (
<LayoutContainer>
<Title>Creating deployment group</Title>
{view}
</LayoutContainer>
);
}
}
export default compose(
graphql(DeploymentGroupCreateMutation, {
props: ({ mutate }) => ({
createDeploymentGroup: variables => mutate({ variables })
})
}),
graphql(DeploymentGroupProvisionMutation, {
props: ({ mutate }) => ({
provisionManifest: variables => mutate({ variables })
})
})
)(DeploymentGroupCreate);

View File

@ -1 +1,2 @@
export { default as DeploymentGroupList } from './list';
export { default as DeploymentGroupCreate } from './create';

View File

@ -28,13 +28,13 @@ class InstanceList extends Component {
}
const instanceList = instances
? instances.map((instance, index) => (
? instances.map((instance, index) =>
<InstanceListItem
instance={instance}
key={instance.id}
toggleCollapsed={() => null}
/>
))
)
: <EmptyInstances />;
return (

View File

@ -14,12 +14,11 @@ const Header = ({
},
loading,
error
}) => (
}) =>
<HeaderComponent
datacenter={portal.datacenter.region}
username={portal.user.firstName}
/>
);
/>;
const HeaderWithData = graphql(PortalQuery, {
props: ({ data: { portal, loading, error } }) => ({

View File

@ -2,24 +2,25 @@ import React from 'react';
import { connect } from 'react-redux';
import { Menu as MenuComponent } from '@components/navigation';
const Menu = ({ sections, matchUrl }) => {
return <MenuComponent links={sections} />;
};
const Menu = ({ sections }) =>
sections && sections.length ? <MenuComponent links={sections} /> : null;
const ConnectedMenu = connect(
(state, ownProps) => {
const params = ownProps.match.params;
const matchUrl = ownProps.match.url;
const deploymentGroupSlug = params.deploymentGroup;
const serviceSlug = params.service;
if ((deploymentGroupSlug || '').match(/^\~/)) {
return {};
}
const sections = serviceSlug
? state.ui.sections.services
: deploymentGroupSlug ? state.ui.sections.deploymentGroups : null;
return {
sections,
matchUrl
sections
};
},
dispatch => ({})

View File

@ -48,19 +48,19 @@ class ServiceList extends Component {
toggleServicesQuickActions(o);
};
const serviceList = services.map(service => (
const serviceList = services.map(service =>
<ServiceListItem
key={service.id}
deploymentGroup={deploymentGroup.slug}
service={service}
showQuickActions={
servicesQuickActions.service &&
servicesQuickActions.service.id === service.id
servicesQuickActions.service.id === service.id
}
onQuickActionsClick={handleQuickActionsClick}
onQuickActionsBlur={handleQuickActionsBlur}
/>
));
);
return (
<LayoutContainer>

View File

@ -0,0 +1,5 @@
query DeploymentGroupBySlug($slug: String!) {
deploymentGroups(slug: $slug) {
id
}
}

View File

@ -0,0 +1,7 @@
#import "./DeploymentGroupInfo.gql"
mutation createDeploymentGroup($name: String!) {
createDeploymentGroup(name: $name) {
...DeploymentGroupInfo
}
}

View File

@ -0,0 +1,9 @@
mutation provisionManifest($deploymentGroupId: ID!, $type: ManifestType!, $format: ManifestFormat!, $raw: String!) {
provisionManifest(deploymentGroupId: $deploymentGroupId, type: $type, format: $format, raw: $raw) {
id
created
type
format
obj
}
}

View File

@ -2,28 +2,31 @@ import React from 'react';
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
import { Header, Breadcrumb, Menu } from '@containers/navigation';
import { InstanceList } from '@containers/instances';
import {
DeploymentGroupList,
DeploymentGroupCreate
} from '@containers/deployment-groups';
import { DeploymentGroupList } from '@containers/deployment-groups';
import {
ServiceList,
ServicesTopology,
ServicesMenu
} from '@containers/services';
import { InstanceList } from '@containers/instances';
const rootRedirect = p => <Redirect to="/deployment-groups" />;
const deploymentGroupRedirect = p => (
const deploymentGroupRedirect = p =>
<Redirect
to={`/deployment-groups/${p.match.params.deploymentGroup}/services-list`}
/>
);
/>;
const serviceRedirect = p => (
const serviceRedirect = p =>
<Redirect
to={`/deployment-groups/${p.match.params.deploymentGroup}/services/${p.match.params.service}/instances`}
/>
);
to={`/deployment-groups/${p.match.params.deploymentGroup}/services/${p.match
.params.service}/instances`}
/>;
const Router = (
<BrowserRouter>
@ -53,16 +56,24 @@ const Router = (
<Route path="/" exact component={rootRedirect} />
<Route path="/deployment-groups" exact component={DeploymentGroupList} />
<Route
path="/deployment-groups/:deploymentGroup"
exact
component={deploymentGroupRedirect}
/>
<Route
path="/deployment-groups/:deploymentGroup/services"
exact
component={deploymentGroupRedirect}
/>
<Switch>
<Route
path="/deployment-groups/~create/:stage?"
exact
component={DeploymentGroupCreate}
/>
<Route
path="/deployment-groups/:deploymentGroup"
exact
component={deploymentGroupRedirect}
/>
<Route
path="/deployment-groups/:deploymentGroup/services"
exact
component={deploymentGroupRedirect}
/>
</Switch>
<Route
path="/deployment-groups/:deploymentGroup/instances"
exact

View File

@ -1,4 +1,5 @@
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { reducer as formReducer } from 'redux-form';
// import { enableBatching } from 'redux-batched-actions';
import { ApolloClient, createNetworkInterface } from 'react-apollo';
import state from './state';
@ -20,12 +21,12 @@ export const client = new ApolloClient({
const id = o.slug
? o.slug
: o.id
? o.id
: o.uuid
? o.uuid
: o.timestamp
? o.timestamp
: o.name ? o.name : 'apollo-cache-key-not-defined';
? o.id
: o.uuid
? o.uuid
: o.timestamp
? o.timestamp
: o.name ? o.name : 'apollo-cache-key-not-defined';
return `${o.__typename}:${id}`;
},
networkInterface: createNetworkInterface({
@ -36,7 +37,8 @@ export const client = new ApolloClient({
export const store = createStore(
combineReducers({
ui,
apollo: client.reducer()
apollo: client.reducer(),
form: formReducer
}),
state, // Initial state
compose(

View File

@ -2390,10 +2390,6 @@ eslint-plugin-import@2.2.0:
minimatch "^3.0.3"
pkg-up "^1.0.0"
eslint-plugin-jest@^20.0.3:
version "20.0.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-20.0.3.tgz#ec15eba6ac0ab44a67ebf6e02672ca9d7e7cba29"
eslint-plugin-jsx-a11y@5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-5.0.3.tgz#4a939f76ec125010528823331bf948cc573380b6"
@ -3886,9 +3882,9 @@ jest-snapshot@^20.0.3:
natural-compare "^1.4.0"
pretty-format "^20.0.3"
jest-styled-components@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-2.2.0.tgz#7c0748c07979b090ede10220ffa67ab9057b4e8e"
jest-styled-components@^3.0.0-2:
version "3.0.0-2"
resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-3.0.0-2.tgz#147fd618f3642d0fd7724285bdc08a8b3a91361b"
dependencies:
css "^2.2.1"
@ -4607,6 +4603,10 @@ npmlog@^4.0.2:
gauge "~2.7.3"
set-blocking "~2.0.0"
nprogress@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1"
nth-check@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4"

View File

@ -14,13 +14,17 @@
"dev": "nodemon src/index.js"
},
"dependencies": {
"camel-case": "^3.0.0",
"good": "^7.2.0",
"good-console": "^6.4.0",
"good-squeeze": "^5.0.2",
"graphi": "^2.0.0",
"hapi": "^16.1.1",
"joi": "^10.5.0",
"joyent-cp-gql-schema": "^1.0.4"
"graphi": "^2.2.1",
"hapi": "^16.4.3",
"joi": "^10.5.2",
"joyent-cp-gql-schema": "^1.0.4",
"js-yaml": "^3.8.4",
"param-case": "^2.1.1",
"uuid": "^3.0.1"
},
"devDependencies": {
"eslint": "^3.19.0",

View File

@ -15,7 +15,7 @@
"deploymentGroups": [
{
"id": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
"slug": "forest-foundation-dev",
"slug": "warp-records-blog",
"name": "WarpRecords Blog"
},
{

View File

@ -1,3 +1,8 @@
const { v4: uuid } = require('uuid');
const paramCase = require('param-case');
const camelCase = require('camel-case');
const yaml = require('js-yaml');
const {
datacenter,
portal,
@ -58,6 +63,43 @@ const getPortal = () =>
})
);
const createDeploymentGroup = ({ name }) => {
const dg = {
id: uuid(),
slug: paramCase(name),
name
};
deploymentGroups.push(dg);
return Promise.resolve(dg);
};
const createServicesFromManifest = ({ deploymentGroupId, raw }) => {
const manifest = yaml.safeLoad(raw);
Object.keys(manifest).forEach(name => {
const service = {
id: uuid(),
deploymentGroup: deploymentGroupId,
slug: paramCase(name),
name
};
const instance = {
id: uuid(),
name: camelCase(`${service.slug}_01`),
service: service.id,
deploymentGroup: deploymentGroupId
};
services.push(service);
instances.push(instance);
});
return Promise.resolve(undefined);
};
module.exports = {
portal: getPortal,
deploymentGroups: getDeploymentGroups,
@ -65,5 +107,12 @@ module.exports = {
services: getServices,
service: query => getServices(query).then(services => services.shift()),
instances: getInstances,
instance: query => getInstances(query).then(instances => instances.shift())
instance: query => getInstances(query).then(instances => instances.shift()),
createDeploymentGroup,
provisionManifest: options =>
createServicesFromManifest(options).then(() => ({
id: uuid(),
type: options.type,
format: options.format
}))
};

View File

@ -14,7 +14,7 @@
"zerorpc": "^0.9.7"
},
"devDependencies": {
"code": "^4.0.0",
"code": "^4.1.0",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
"js-yaml": "^3.8.4",

View File

@ -13,13 +13,13 @@
"peerDependencies": {
"babel-eslint": "^7.2.3",
"eslint": "^3.19.0",
"eslint-config-prettier": "^2.1.0",
"eslint-config-prettier": "^2.1.1",
"eslint-config-react-app": "^0.6.2",
"eslint-config-xo-space": "^0.16.0",
"eslint-plugin-flowtype": "^2.33.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^5.0.3",
"eslint-plugin-prettier": "^2.0.1",
"eslint-plugin-prettier": "^2.1.1",
"eslint-plugin-react": "^7.0.1"
}
}

View File

@ -23,21 +23,21 @@
"prepublish": "redrun build"
},
"dependencies": {
"prop-types": "^15.5.10"
"prop-types": "^15.5.10",
"react-codemirror": "^1.0.0"
},
"devDependencies": {
"babel-preset-react-app": "^3.0.0",
"bup": "^1.0.9",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.3",
"eslint-config-joyent-portal": "1.0.0",
"jest": "^20.0.4",
"react": "^15.5.4",
"react-test-renderer": "^15.5.4",
"redrun": "^5.9.14"
},
"peerDependencies": {
"react": "*",
"react-codemirror": "*"
"react": "*"
},
"jest": {
"testEnvironment": "jsdom",

View File

@ -1,3 +1,20 @@
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/eclipse.css';
import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/addon/lint/lint.css';
import 'codemirror/mode/yaml/yaml';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/addon/edit/closebrackets';
import 'codemirror/addon/edit/matchbrackets';
import 'codemirror/addon/fold/foldcode';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/indent-fold';
import 'codemirror/addon/fold/comment-fold';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/selection/active-line';
import 'codemirror/addon/edit/closetag';
import React, { Component } from 'react';
import ReactCodeMirror from 'react-codemirror';
import PropTypes from 'prop-types';
@ -11,7 +28,6 @@ const options = {
electricChars: true,
lineNumbers: true,
inputStyle: 'contenteditable',
readOnly: false,
lint: true,
autoCloseBrackets: true,
styleActiveLine: true,
@ -38,7 +54,7 @@ class ManifestEditor extends Component {
this._refs[name] = ref;
};
}
options({ mode }) {
options({ mode, readOnly }) {
const modes = {
json: {
name: 'javascript',
@ -48,7 +64,8 @@ class ManifestEditor extends Component {
};
return Object.assign({}, options, {
mode: modes[mode.toLowerCase()]
mode: modes[mode.toLowerCase()],
readOnly
});
}
render() {
@ -72,7 +89,8 @@ ManifestEditor.defaultProps = {
onChange: () => null,
onFocusChange: () => null,
autoSave: true,
preserveScrollPosition: true
preserveScrollPosition: true,
readOnly: false
};
ManifestEditor.propTypes = {
@ -81,7 +99,8 @@ ManifestEditor.propTypes = {
onChange: PropTypes.func,
onFocusChange: PropTypes.func,
autoSave: PropTypes.bool,
preserveScrollPosition: PropTypes.bool
preserveScrollPosition: PropTypes.bool,
readOnly: PropTypes.bool
};
export default ManifestEditor;

View File

@ -30,12 +30,12 @@
"prepublish": "redrun build"
},
"dependencies": {
"remcalc": "^1.0.5"
"remcalc": "^1.0.8"
},
"devDependencies": {
"babel-plugin-styled-components": "^1.1.4",
"babel-preset-react-app": "^3.0.0",
"bup": "^1.0.7",
"bup": "^1.0.9",
"chalk": "^1.1.3",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
@ -44,13 +44,13 @@
"jest-junit": "^1.5.1",
"jest-matcher-utils": "^20.0.3",
"jest-snapshot": "^20.0.3",
"jest-styled-components": "^3.0.0-1",
"jest-styled-components": "^3.0.0-2",
"react": "^15.5.4",
"react-test-renderer": "^15.5.4",
"redrun": "^5.9.14",
"strip-ansi": "^3.0.1",
"styled-components": "^2.0.0",
"stylelint": "^7.10.1",
"styled-components": "^2.0.1",
"stylelint": "^7.11.0",
"stylelint-config-primer": "^1.4.0",
"stylelint-config-standard": "^16.0.0",
"stylelint-processor-styled-components": "styled-components/stylelint-processor-styled-components#68b4c4f"

View File

@ -18,19 +18,19 @@
"license": "MPL-2.0",
"devDependencies": {
"belly-button": "^3.1.0",
"code": "^4.0.0",
"hapi": "^16.1.1",
"code": "^4.1.0",
"hapi": "^16.4.3",
"hapi-swagger": "^7.7.0",
"inert": "^4.2.0",
"lab": "^13.0.2",
"lab": "^13.1.0",
"vision": "^4.1.1"
},
"dependencies": {
"boom": "^4.3.1",
"graphi": "^2.0.0",
"boom": "^5.1.0",
"graphi": "^2.2.1",
"hoek": "^4.1.1",
"joi": "^10.4.1",
"joi": "^10.5.2",
"joyent-cp-gql-schema": "^1.0.4",
"portal-data": "^1.0.0"
"portal-data": "^1.1.0"
}
}

View File

@ -19,13 +19,13 @@
"docker-compose-client": "^1.0.7",
"dockerode": "^2.4.3",
"hoek": "^4.1.1",
"penseur": "^7.8.1",
"penseur": "^7.12.3",
"vasync": "^1.6.4",
"yamljs": "^0.2.10"
},
"devDependencies": {
"belly-button": "^3.1.0",
"code": "^4.0.0",
"lab": "^13.0.4"
"code": "^4.1.0",
"lab": "^13.1.0"
}
}

View File

@ -39,15 +39,14 @@
},
"devDependencies": {
"ava": "0.19.1",
"babel-plugin-istanbul": "^4.1.3",
"babel-preset-env": "^1.5.1",
"babel-plugin-istanbul": "^4.1.4",
"babel-preset-env": "^1.5.2",
"babel-register": "^6.24.1",
"bup": "^1.0.7",
"cross-env": "^5.0.0",
"bup": "^1.0.9",
"cross-env": "^5.0.1",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
"nyc": "^10.3.2",
"prettier": "^1.3.1",
"nyc": "^11.0.2",
"redrun": "^5.9.14",
"tap-xunit": "^1.7.0"
},

View File

@ -37,19 +37,18 @@
"has-own-prop": "^1.0.0",
"lodash.isnull": "^3.0.0",
"lodash.isundefined": "^3.0.1",
"yaml-ast-parser": "0.0.32"
"yaml-ast-parser": "0.0.33"
},
"devDependencies": {
"ava": "0.19.1",
"babel-plugin-istanbul": "^4.1.3",
"babel-preset-env": "^1.5.1",
"babel-plugin-istanbul": "^4.1.4",
"babel-preset-env": "^1.5.2",
"babel-register": "^6.24.1",
"bup": "^1.0.7",
"cross-env": "^5.0.0",
"bup": "^1.0.9",
"cross-env": "^5.0.1",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
"nyc": "^10.3.2",
"prettier": "^1.3.1",
"nyc": "^11.0.2",
"redrun": "^5.9.14",
"tap-xunit": "^1.7.0"
},

View File

@ -29,17 +29,17 @@
},
"devDependencies": {
"ava": "0.19.1",
"babel-plugin-istanbul": "^4.1.3",
"babel-plugin-istanbul": "^4.1.4",
"babel-plugin-transform-es2015-arrow-functions": "^6.22.0",
"babel-plugin-transform-es2015-parameters": "^6.24.1",
"babel-plugin-transform-es2015-spread": "^6.22.0",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
"babel-register": "^6.24.1",
"bup": "^1.0.8",
"cross-env": "^5.0.0",
"bup": "^1.0.9",
"cross-env": "^5.0.1",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
"nyc": "^10.3.2",
"nyc": "^11.0.2",
"tap-xunit": "^1.7.0"
},
"nyc": {

View File

@ -25,16 +25,16 @@
},
"devDependencies": {
"ava": "0.19.1",
"babel-plugin-istanbul": "^4.1.3",
"babel-plugin-istanbul": "^4.1.4",
"babel-plugin-transform-es2015-arrow-functions": "^6.22.0",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
"babel-register": "^6.24.1",
"bup": "^1.0.8",
"cross-env": "^5.0.0",
"bup": "^1.0.9",
"cross-env": "^5.0.1",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
"lodash.uniq": "^4.5.0",
"nyc": "^10.3.2",
"nyc": "^11.0.2",
"tap-xunit": "^1.7.0"
},
"nyc": {

View File

@ -28,20 +28,20 @@
},
"devDependencies": {
"ava": "0.19.1",
"babel-plugin-istanbul": "^4.1.3",
"babel-plugin-istanbul": "^4.1.4",
"babel-plugin-transform-es2015-arrow-functions": "^6.22.0",
"babel-plugin-transform-es2015-parameters": "^6.24.1",
"babel-plugin-transform-es2015-spread": "^6.22.0",
"babel-plugin-transform-es2015-template-literals": "^6.22.0",
"babel-register": "^6.24.1",
"bup": "^1.0.7",
"cross-env": "^5.0.0",
"bup": "^1.0.9",
"cross-env": "^5.0.1",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
"nyc": "^10.3.2",
"nyc": "^11.0.2",
"react": "^15.5.4",
"redrun": "^5.9.14",
"styled-components": "^2.0.0",
"styled-components": "^2.0.1",
"tap-xunit": "^1.7.0"
},
"peerDependencies": {

View File

@ -32,48 +32,54 @@
"d3": "^4.9.1",
"lodash.isstring": "^4.0.1",
"normalized-styled-components": "^1.0.5",
"polished": "^1.1.2",
"polished": "^1.1.3",
"prop-types": "^15.5.10",
"react": "^15.5.4",
"react-broadcast": "^0.1.2",
"react-dom": "^15.5.4",
"react-router-dom": "^4.1.1",
"react-styled-flexboxgrid": "^1.1.2",
"redux-form": "^6.7.0",
"remcalc": "^1.0.5",
"rnd-id": "^1.0.5",
"styled-components": "^2.0.0",
"styled-is": "^1.0.7",
"unitcalc": "^1.0.5"
"react-styled-flexboxgrid": "^2.0.0",
"remcalc": "^1.0.8",
"rnd-id": "^1.0.8",
"styled-components": "^2.0.1",
"styled-is": "^1.0.11",
"unitcalc": "^1.0.8"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-plugin-inline-react-svg": "^0.4.0",
"babel-plugin-styled-components": "^1.1.4",
"babel-preset-joyent-portal": "^1.0.0",
"cross-env": "^5.0.0",
"cross-env": "^5.0.1",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
"jest": "^20.0.3",
"jest": "^20.0.4",
"jest-diff": "^20.0.3",
"jest-matcher-utils": "^20.0.3",
"jest-snapshot": "^20.0.3",
"jest-styled-components": "^2.0.0",
"jest-styled-components": "^3.0.0-2",
"react": "^15.5.4",
"react-docgen": "^2.15.0",
"react-docgen-displayname-handler": "^1.0.0",
"react-dom": "^15.5.4",
"react-redux": "^5.0.5",
"react-scripts": "^1.0.0",
"react-styleguidist": "^5.2.1",
"react-router-dom": "^4.1.1",
"react-scripts": "^1.0.7",
"react-styleguidist": "^5.4.2",
"react-test-renderer": "^15.5.4",
"redrun": "^5.9.14",
"redux": "^3.6.0",
"redux-form": "^6.8.0",
"snapguidist": "^1.1.2",
"stylelint": "^7.10.1",
"stylelint": "^7.11.0",
"stylelint-config-primer": "^1.4.0",
"stylelint-config-standard": "^16.0.0",
"stylelint-processor-styled-components": "ramitos/stylelint-processor-styled-components#e81e1d0",
"stylelint-processor-styled-components": "styled-components/stylelint-processor-styled-components#68b4c4f",
"tinycolor2": "^1.4.1",
"title-case": "^2.1.1",
"webpack": "^2.5.1"
"webpack": "^2.6.1"
},
"peerDependencies": {
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-router-dom": "^4.1.1",
"redux-form": "^6.8.0"
}
}

View File

@ -36,9 +36,8 @@ class FormGroup extends Component {
render() {
const {
name = rndId(),
defaultValue,
normalize,
reduxForm = false
reduxForm = false,
...rest
} = this.props;
if (!reduxForm) {
@ -48,9 +47,8 @@ class FormGroup extends Component {
return (
<Field
name={name}
defaultValue={defaultValue}
component={this.renderGroup}
normalize={normalize}
{...rest}
/>
);
}

View File

@ -28,20 +28,20 @@
},
"dependencies": {
"lodash.flatten": "^4.4.0",
"remcalc": "^1.0.5"
"remcalc": "^1.0.8"
},
"devDependencies": {
"ava": "0.19.1",
"babel-plugin-istanbul": "^4.1.3",
"babel-plugin-istanbul": "^4.1.4",
"babel-plugin-transform-es2015-arrow-functions": "^6.22.0",
"babel-plugin-transform-es2015-parameters": "^6.24.1",
"babel-plugin-transform-es2015-spread": "^6.22.0",
"babel-register": "^6.24.1",
"bup": "^1.0.8",
"cross-env": "^5.0.0",
"bup": "^1.0.9",
"cross-env": "^5.0.1",
"eslint": "^3.19.0",
"eslint-config-joyent-portal": "1.0.0",
"nyc": "^10.3.2",
"nyc": "^11.0.2",
"tap-xunit": "^1.7.0"
},
"nyc": {

8849
yarn.lock

File diff suppressed because it is too large Load Diff