fix(my-joy-beta): create instance deploy ux improvements

fixes #1015
This commit is contained in:
Sérgio Ramos 2018-01-18 14:46:49 +00:00 committed by Sérgio Ramos
parent 57eecf0552
commit 67f36a464b
21 changed files with 6264 additions and 2320 deletions

View File

@ -41,7 +41,7 @@
"react-apollo": "^2.0.4",
"react-dom": "^16.2.0",
"react-redux": "^5.0.6",
"react-redux-values": "^1.0.2",
"react-redux-values": "^1.1.2",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"redux": "^3.7.2",

View File

@ -1,14 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders <Title /> without throwing 1`] = `
Array [
.c3 {
.c3 {
font-size: 80%;
color: rgba(73,73,73,1);
line-height: 1.125rem;
font-size: 0.8125rem;
}
.c7 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c6 {
background-color: rgb(216,216,216);
margin: 0;
height: 0.0625rem;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
@ -62,75 +86,77 @@ Array [
margin-right: 0.25rem;
}
<div
className="c0"
>
<div
className="c1"
>
<div
className="c2"
/>
</div>
<small
className="c3"
/>
</div>,
.c3 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c2 {
background-color: rgb(216,216,216);
margin: 0;
height: 0.0625rem;
}
.c1 {
.c5 {
margin-top: 0.25rem;
margin-bottom: 1rem;
}
.c0 + div:empty,
.c0 + form:empty {
.c4 + div:empty,
.c4 + form:empty {
margin-bottom: 2.5rem;
}
<div
className="c0 c1"
className=""
>
<div
className="c0"
>
<div
className="c2 c3"
height="0.0625rem"
className="c1"
>
<div
className="c2"
/>
</div>
<small
className="c3"
/>
</div>,
]
</div>
<div
className="c4 c5"
>
<div
className="c6 c7"
height="0.0625rem"
/>
</div>
</div>
`;
exports[`renders <Title icon="NameIcon"/> without throwing 1`] = `
Array [
.c3 {
.c3 {
font-size: 80%;
color: rgba(73,73,73,1);
line-height: 1.125rem;
font-size: 0.8125rem;
}
.c7 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c6 {
background-color: rgb(216,216,216);
margin: 0;
height: 0.0625rem;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
@ -184,96 +210,98 @@ Array [
margin-right: 0.25rem;
}
<div
className="c0"
>
<div
className="c1"
>
<div
className="c2"
>
<svg
className=""
height="17"
innerRef={undefined}
style={
Object {
"transform": "rotate(0deg)",
}
}
version="1.1"
viewBox="0 0 16.24 13.55"
width="17"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<path
d="M.24,13.55c-1-2,1.39-6.73,1.52-7C4.47,1.5,6.77-.55,9,.12a2.34,2.34,0,0,1,1.4,1.16c.54,1.06-.26,2.41-1.18,4-.55.91-1.69,3.42-1.78,3.67-.37,1-.88,2.35-.52,2.86.19.28.73.34,1.14.34s1-.77,1.31-1.49c.56-1.48,1.87-4.94,4.1-4.27,1.78.53,1.27,2.56.93,3.91s-.11,1.51-.1,1.52a2.21,2.21,0,0,0,1.95-.24v1c-1.41.73-2.11.72-2.74.21-.88-.71-.48-2.19-.33-2.75.38-1.52.47-2.3-.07-2.46-.73-.22-1.6,1-2.57,3.52-.4,1.1-1.26,2.3-2.48,2.3a2.48,2.48,0,0,1-2.17-.88c-.72-1.05-.14-2.62.38-4,.09-.23,1.3-2.91,1.87-3.87s2.11-3.06.49-3.29S5.18,2.83,2.87,7.16c-1.71,3.21-1.78,5.95-1.62,6.39Z"
fill="rgba(73, 73, 73, 1)"
/>
</svg>
</div>
</div>
<small
className="c3"
/>
</div>,
.c3 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c2 {
background-color: rgb(216,216,216);
margin: 0;
height: 0.0625rem;
}
.c1 {
.c5 {
margin-top: 0.25rem;
margin-bottom: 1rem;
}
.c0 + div:empty,
.c0 + form:empty {
.c4 + div:empty,
.c4 + form:empty {
margin-bottom: 2.5rem;
}
<div
className="c0 c1"
className=""
>
<div
className="c0"
>
<div
className="c2 c3"
height="0.0625rem"
className="c1"
>
<div
className="c2"
>
<svg
className=""
height="17"
innerRef={undefined}
style={
Object {
"transform": "rotate(0deg)",
}
}
version="1.1"
viewBox="0 0 16.24 13.55"
width="17"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<path
d="M.24,13.55c-1-2,1.39-6.73,1.52-7C4.47,1.5,6.77-.55,9,.12a2.34,2.34,0,0,1,1.4,1.16c.54,1.06-.26,2.41-1.18,4-.55.91-1.69,3.42-1.78,3.67-.37,1-.88,2.35-.52,2.86.19.28.73.34,1.14.34s1-.77,1.31-1.49c.56-1.48,1.87-4.94,4.1-4.27,1.78.53,1.27,2.56.93,3.91s-.11,1.51-.1,1.52a2.21,2.21,0,0,0,1.95-.24v1c-1.41.73-2.11.72-2.74.21-.88-.71-.48-2.19-.33-2.75.38-1.52.47-2.3-.07-2.46-.73-.22-1.6,1-2.57,3.52-.4,1.1-1.26,2.3-2.48,2.3a2.48,2.48,0,0,1-2.17-.88c-.72-1.05-.14-2.62.38-4,.09-.23,1.3-2.91,1.87-3.87s2.11-3.06.49-3.29S5.18,2.83,2.87,7.16c-1.71,3.21-1.78,5.95-1.62,6.39Z"
fill="rgba(73, 73, 73, 1)"
/>
</svg>
</div>
</div>
<small
className="c3"
/>
</div>,
]
</div>
<div
className="c4 c5"
>
<div
className="c6 c7"
height="0.0625rem"
/>
</div>
</div>
`;
exports[`renders <Title icon="Test" label="Instance name"/> without throwing 1`] = `
Array [
.c3 {
.c3 {
font-size: 80%;
color: rgba(73,73,73,1);
line-height: 1.125rem;
font-size: 0.8125rem;
}
.c7 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c6 {
background-color: rgb(216,216,216);
margin: 0;
height: 0.0625rem;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
@ -327,96 +355,99 @@ Array [
margin-right: 0.25rem;
}
<div
className="c0"
>
<div
className="c1"
>
<div
className="c2"
>
<svg
className=""
height="17"
innerRef={undefined}
style={
Object {
"transform": "rotate(0deg)",
}
}
version="1.1"
viewBox="0 0 16.24 13.55"
width="17"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<path
d="M.24,13.55c-1-2,1.39-6.73,1.52-7C4.47,1.5,6.77-.55,9,.12a2.34,2.34,0,0,1,1.4,1.16c.54,1.06-.26,2.41-1.18,4-.55.91-1.69,3.42-1.78,3.67-.37,1-.88,2.35-.52,2.86.19.28.73.34,1.14.34s1-.77,1.31-1.49c.56-1.48,1.87-4.94,4.1-4.27,1.78.53,1.27,2.56.93,3.91s-.11,1.51-.1,1.52a2.21,2.21,0,0,0,1.95-.24v1c-1.41.73-2.11.72-2.74.21-.88-.71-.48-2.19-.33-2.75.38-1.52.47-2.3-.07-2.46-.73-.22-1.6,1-2.57,3.52-.4,1.1-1.26,2.3-2.48,2.3a2.48,2.48,0,0,1-2.17-.88c-.72-1.05-.14-2.62.38-4,.09-.23,1.3-2.91,1.87-3.87s2.11-3.06.49-3.29S5.18,2.83,2.87,7.16c-1.71,3.21-1.78,5.95-1.62,6.39Z"
fill="rgba(73, 73, 73, 1)"
/>
</svg>
</div>
</div>
<small
className="c3"
/>
</div>,
.c3 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c2 {
background-color: rgb(216,216,216);
margin: 0;
height: 0.0625rem;
}
.c1 {
.c5 {
margin-top: 0.25rem;
margin-bottom: 1rem;
}
.c0 + div:empty,
.c0 + form:empty {
.c4 + div:empty,
.c4 + form:empty {
margin-bottom: 2.5rem;
}
<div
className="c0 c1"
className=""
label="Instance name"
>
<div
className="c0"
>
<div
className="c2 c3"
height="0.0625rem"
className="c1"
>
<div
className="c2"
>
<svg
className=""
height="17"
innerRef={undefined}
style={
Object {
"transform": "rotate(0deg)",
}
}
version="1.1"
viewBox="0 0 16.24 13.55"
width="17"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<path
d="M.24,13.55c-1-2,1.39-6.73,1.52-7C4.47,1.5,6.77-.55,9,.12a2.34,2.34,0,0,1,1.4,1.16c.54,1.06-.26,2.41-1.18,4-.55.91-1.69,3.42-1.78,3.67-.37,1-.88,2.35-.52,2.86.19.28.73.34,1.14.34s1-.77,1.31-1.49c.56-1.48,1.87-4.94,4.1-4.27,1.78.53,1.27,2.56.93,3.91s-.11,1.51-.1,1.52a2.21,2.21,0,0,0,1.95-.24v1c-1.41.73-2.11.72-2.74.21-.88-.71-.48-2.19-.33-2.75.38-1.52.47-2.3-.07-2.46-.73-.22-1.6,1-2.57,3.52-.4,1.1-1.26,2.3-2.48,2.3a2.48,2.48,0,0,1-2.17-.88c-.72-1.05-.14-2.62.38-4,.09-.23,1.3-2.91,1.87-3.87s2.11-3.06.49-3.29S5.18,2.83,2.87,7.16c-1.71,3.21-1.78,5.95-1.62,6.39Z"
fill="rgba(73, 73, 73, 1)"
/>
</svg>
</div>
</div>
<small
className="c3"
/>
</div>,
]
</div>
<div
className="c4 c5"
>
<div
className="c6 c7"
height="0.0625rem"
/>
</div>
</div>
`;
exports[`renders <Title label="Test"/> without throwing 1`] = `
Array [
.c3 {
.c3 {
font-size: 80%;
color: rgba(73,73,73,1);
line-height: 1.125rem;
font-size: 0.8125rem;
}
.c7 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c6 {
background-color: rgb(216,216,216);
margin: 0;
height: 0.0625rem;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
@ -470,62 +501,41 @@ Array [
margin-right: 0.25rem;
}
<div
className="c0"
>
<div
className="c1"
>
<div
className="c2"
/>
</div>
<small
className="c3"
/>
</div>,
.c3 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c2 {
background-color: rgb(216,216,216);
margin: 0;
height: 0.0625rem;
}
.c1 {
.c5 {
margin-top: 0.25rem;
margin-bottom: 1rem;
}
.c0 + div:empty,
.c0 + form:empty {
.c4 + div:empty,
.c4 + form:empty {
margin-bottom: 2.5rem;
}
<div
className="c0 c1"
className=""
label="Test"
>
<div
className="c0"
>
<div
className="c2 c3"
height="0.0625rem"
className="c1"
>
<div
className="c2"
/>
</div>
<small
className="c3"
/>
</div>,
]
</div>
<div
className="c4 c5"
>
<div
className="c6 c7"
height="0.0625rem"
/>
</div>
</div>
`;

View File

@ -16,10 +16,8 @@ import {
Button,
Toggle,
H4,
Select,
StatusLoader
Select
} from 'joyent-ui-toolkit';
import Description from '@components/create-instance/description';
const fadeIn = keyframes`
from {
@ -88,7 +86,7 @@ const getImageByID = (id, images) => {
const image = images
.map(image => ({
...image,
versions: image.versions.filter(version => version.id === id)
versions: (image.versions || []).filter(version => version.id === id)
}))
.filter(e => e.versions.length)[0];
return image
@ -100,105 +98,77 @@ const getImageByID = (id, images) => {
: {};
};
export const Preview = ({ imageID, images, isVmSelected, onEdit }) => (
<Fragment>
<Margin bottom={2} top={3}>
<H3 bold>
{titleCase(getImageByID(imageID, images).name)} -{' '}
{getImageByID(imageID, images).version}
</H3>
<P>
{isVmSelected ? 'Hardware Virtual Machine' : 'Infrastructure Container'}{' '}
</P>
</Margin>
<Button type="button" secondary onClick={onEdit}>
Edit
</Button>
</Fragment>
);
export default ({
handleSubmit,
pristine,
expanded,
imageID,
onCancel,
loading,
images,
images = [],
isVmSelected
}) => (
<form onSubmit={handleSubmit}>
{expanded && (
<Fragment>
<Description>
Hardware virtual machines are generally used for non-containerized
applications. Infrastructure containers are generally for running any
Linux image on secure, bare metal containers.{' '}
<a
href="https://docs.joyent.com/private-cloud/images"
rel="noopener noreferrer"
target="_blank"
>
Read the docs
</a>
</Description>
{loading ? (
<StatusLoader />
) : (
<Fragment>
<Margin bottom={4}>
<FormGroup name="vms" field={Field}>
<Flex alignCenter>
<FormLabel>Infrastructure Container </FormLabel>
<Toggle onBlur={null}>Hardware Virtual Machine</Toggle>
</Flex>
<Margin bottom={4}>
<FormGroup name="vms" field={Field}>
<Flex alignCenter>
<FormLabel>Infrastructure Container </FormLabel>
<Toggle onBlur={null}>Hardware Virtual Machine</Toggle>
</Flex>
</FormGroup>
</Margin>
<Row>
{images &&
images.filter(i => i.isVm === isVmSelected).map(image => (
<Col md={2} sm={3}>
<Card
selected={
image.imageName === getImageByID(imageID, images).imageName
}
>
<img
src={getImage(image.imageName).url}
width={getImage(image.imageName).size}
height={getImage(image.imageName).size}
style={{
marginBottom: getImage(image.imageName).bottom
}}
alt={image.imageName}
/>
<H4>{titleCase(image.imageName)}</H4>
<FormGroup name="image" field={Field}>
<Version onBlur={null}>
<option selected>Version</option>
{image.versions && image.versions.map(version => (
<option
key={`${version.name} - ${version.version}`}
value={version.id}
>{`${version.name} - ${version.version}`}</option>
))}
</Version>
</FormGroup>
</Margin>
<Row>
{images &&
images.filter(i => i.isVm === isVmSelected).map(image => (
<Col md={2} sm={3}>
<Card
selected={
image.imageName ===
getImageByID(imageID, images).imageName
}
>
<img
src={getImage(image.imageName).url}
width={getImage(image.imageName).size}
height={getImage(image.imageName).size}
style={{
marginBottom: getImage(image.imageName).bottom
}}
alt={image.imageName}
/>
<H4>{titleCase(image.imageName)}</H4>
<FormGroup name="image" field={Field}>
<Version onBlur={null}>
<option selected>Version</option>
{image.versions.map(version => (
<option
key={`${version.name} - ${version.version}`}
value={version.id}
>{`${version.name} - ${version.version}`}</option>
))}
</Version>
</FormGroup>
</Card>
</Col>
))}
</Row>
<Margin top={4}>
<Button type="submit" disabled={pristine || !imageID}>
Next
</Button>
</Margin>
</Fragment>
)}
</Fragment>
)}
{!expanded &&
imageID && (
<Fragment>
<Margin bottom={2} top={3}>
<H3 bold>
{titleCase(getImageByID(imageID, images).name)} -{' '}
{getImageByID(imageID, images).version}
</H3>
<P>
{isVmSelected
? 'Hardware Virtual Machine'
: 'Infrastructure Container'}{' '}
</P>
</Margin>
<Button type="button" secondary onClick={onCancel}>
Edit
</Button>
</Fragment>
)}
</Card>
</Col>
))}
</Row>
<Margin top={4}>
<Button type="submit" disabled={pristine || !imageID}>
Next
</Button>
</Margin>
</form>
);

View File

@ -1,11 +1,10 @@
import React, { Fragment } from 'react';
import React from 'react';
import { Field } from 'redux-form';
import { Margin } from 'styled-components-spacing';
import Flex, { FlexItem } from 'styled-flex-component';
import remcalc from 'remcalc';
import {
H3,
FormGroup,
FormLabel,
Input,
@ -20,64 +19,44 @@ export default ({
handleSubmit,
pristine,
asyncValidating,
expanded,
name,
placeholderName,
randomizing,
onCancel,
onRandomize
}) => (
<form onSubmit={handleSubmit}>
{expanded ? (
<Fragment>
<Description>
Your instance name will be used to identify this specific instance.
</Description>
<Flex>
<FlexItem>
<FormGroup name="name" fluid field={Field}>
<FormLabel>Instance Name</FormLabel>
<Input placeholder={placeholderName} onBlur={null} />
<FormMeta marginless />
</FormGroup>
</FlexItem>
<FlexItem>
<FormLabel>&#8291;</FormLabel>
<Margin left={1}>
<Button
type="button"
marginTop={remcalc(8)}
onClick={onRandomize}
loading={randomizing}
marginless
secondary
icon
>
<RandomizeIcon />
<span>Randomize</span>
</Button>
</Margin>
</FlexItem>
</Flex>
<Margin top={2} bottom={4}>
<Button type="submit" disabled={pristine} loading={asyncValidating}>
Next
<Description>
Your instance name will be used to identify this specific instance.
</Description>
<Flex>
<FlexItem>
<FormGroup name="name" fluid field={Field}>
<FormLabel>Instance Name</FormLabel>
<Input placeholder={placeholderName} onBlur={null} />
<FormMeta marginless />
</FormGroup>
</FlexItem>
<FlexItem>
<FormLabel>&#8291;</FormLabel>
<Margin left={1}>
<Button
type="button"
marginTop={remcalc(8)}
onClick={onRandomize}
loading={randomizing}
marginless
secondary
icon
>
<RandomizeIcon />
<span>Randomize</span>
</Button>
</Margin>
</Fragment>
) : (
<Fragment>
{name ? (
<Fragment>
<Margin bottom={2} top={3}>
<H3 bold>{name}</H3>
</Margin>
<Button type="button" secondary onClick={onCancel}>
Edit
</Button>
</Fragment>
) : null}
</Fragment>
)}
</FlexItem>
</Flex>
<Margin top={2} bottom={4}>
<Button type="submit" disabled={pristine} loading={asyncValidating}>
Next
</Button>
</Margin>
</form>
);

View File

@ -1,8 +1,9 @@
import React, { Fragment } from 'react';
import React from 'react';
import Flex from 'styled-flex-component';
import { Margin } from 'styled-components-spacing';
import remcalc from 'remcalc';
import styled from 'styled-components';
import is from 'styled-is';
import { Divider, Small } from 'joyent-ui-toolkit';
@ -13,8 +14,14 @@ const IsEmpty = styled(Margin)`
}
`;
export default ({ icon, children }) => (
<Fragment>
const Container = styled.div`
${is('onClick')`
cursor: pointer;
`};
`;
export default ({ icon, children, ...rest }) => (
<Container {...rest}>
<Flex>
<Margin right={1}>
<Flex alignCenter full>
@ -26,5 +33,5 @@ export default ({ icon, children }) => (
<IsEmpty top={1} bottom={3}>
<Divider height={remcalc(1)} />
</IsEmpty>
</Fragment>
</Container>
);

View File

@ -46,7 +46,12 @@ export const Affinity = ({
rule
}) => (
<Fragment>
<Title icon={<AffinityIcon />}>Affinity</Title>
<Title
onClick={!expanded && !proceeded && handleEdit}
icon={<AffinityIcon />}
>
Affinity
</Title>
{expanded ? (
<Description>
Control placement of instances on the physical servers. Design

View File

@ -54,7 +54,9 @@ const CNSContainer = ({
loading
}) => (
<Fragment>
<Title icon={<CnsIcon />}>Container Name Service</Title>
<Title onClick={!expanded && !proceeded && handleEdit} icon={<CnsIcon />}>
Container Name Service
</Title>
{expanded ? (
<Description>
Triton CNS is used to automatically update hostnames for your

View File

@ -29,7 +29,12 @@ const Firewall = ({
handleEdit
}) => (
<Fragment>
<Title icon={<FirewallIcon />}>Firewall</Title>
<Title
onClick={!expanded && !proceeded && handleEdit}
icon={<FirewallIcon />}
>
Firewall
</Title>
{expanded ? (
<Description>
Cloud Firewall rules control traffic across instances. Enabling the
@ -59,7 +64,8 @@ const Firewall = ({
tagRules={tagRules}
enabled={enabled}
/>
) : null}
) : null
}
</ReduxForm>
) : null}
{proceeded && !expanded ? (

View File

@ -2,11 +2,13 @@ import React, { Fragment } from 'react';
import { compose, graphql } from 'react-apollo';
import ReduxForm from 'declarative-redux-form';
import { connect } from 'react-redux';
import { set } from 'react-redux-values';
import get from 'lodash.get';
import { InstanceTypeIcon } from 'joyent-ui-toolkit';
import Image from '@components/create-instance/image';
import { InstanceTypeIcon, StatusLoader } from 'joyent-ui-toolkit';
import Image, { Preview } from '@components/create-instance/image';
import Title from '@components/create-instance/title';
import Description from '@components/create-instance/description';
import imageData from '../../data/images-map.json';
import getImages from '../../graphql/get-images.gql';
@ -14,31 +16,58 @@ import getImages from '../../graphql/get-images.gql';
const ImageContainer = ({
expanded,
image,
handleSubmit,
handleCancel,
handleNext,
handleEdit,
loading,
images,
vms
}) => (
<Fragment>
<Title icon={<InstanceTypeIcon />}>Instance type and image</Title>
<Title
onClick={!expanded && !image && handleEdit}
icon={<InstanceTypeIcon />}
>
Instance type and image
</Title>
{expanded ? (
<Description>
Hardware virtual machines are generally used for non-containerized
applications. Infrastructure containers are generally for running any
Linux image on secure, bare metal containers.{' '}
<a
href="https://docs.joyent.com/private-cloud/images"
rel="noopener noreferrer"
target="_blank"
>
Read the docs
</a>
</Description>
) : null}
<ReduxForm
form="create-instance-image"
destroyOnUnmount={false}
forceUnregisterOnUnmount={true}
onSubmit={handleSubmit}
onSubmit={handleNext}
>
{props => (
<Image
{...props}
isVmSelected={vms}
loading={loading}
imageID={image}
images={images}
expanded={expanded}
onCancel={handleCancel}
/>
)}
{props =>
(loading && expanded) ? (
<StatusLoader />
) : expanded ? (
<Image
{...props}
isVmSelected={vms}
imageID={image}
images={images}
/>
) : image ? (
<Preview
isVmSelected={vms}
imageID={image}
images={images}
onEdit={handleEdit}
/>
) : null
}
</ReduxForm>
</Fragment>
);
@ -53,8 +82,12 @@ export default compose(
};
},
(dispatch, { history }) => ({
handleSubmit: () => history.push(`/instances/~create/package`),
handleCancel: () => history.push(`/instances/~create/image`)
handleNext: () => {
dispatch(set({ name: 'create-instance-image-proceeded', value: true }));
return history.push(`/instances/~create/package`);
},
handleEdit: () => history.push(`/instances/~create/image`)
})
),
graphql(getImages, {

View File

@ -5,6 +5,7 @@ import { Margin } from 'styled-components-spacing';
import ReduxForm from 'declarative-redux-form';
import { stopSubmit, destroy } from 'redux-form';
import { connect } from 'react-redux';
import { destroyAll } from 'react-redux-values';
import { graphql, compose } from 'react-apollo';
import intercept from 'apr-intercept';
import constantCase from 'constant-case';
@ -28,43 +29,59 @@ import parseError from '@state/parse-error';
const CREATE_FORM = 'CREATE-INSTANCE';
const CreateInstance = ({ step, handleSubmit, ...props }) => (
const CreateInstance = ({ step, disabled, handleSubmit, history, match }) => (
<ViewContainer>
<Margin top={4} bottom={4}>
<H2>Create Instances</H2>
</Margin>
<Margin bottom={4}>
<Name {...props} expanded={step === 'name'} />
<Name history={history} match={match} expanded={step === 'name'} />
</Margin>
<Margin bottom={4}>
<Image {...props} expanded={step === 'image'} />
<Image history={history} match={match} expanded={step === 'image'} />
</Margin>
<Margin bottom={4}>
<Package {...props} expanded={step === 'package'} />
<Package history={history} match={match} expanded={step === 'package'} />
</Margin>
<Margin bottom={4}>
<Tags {...props} expanded={step === 'tags'} />
<Tags history={history} match={match} expanded={step === 'tags'} />
</Margin>
<Margin bottom={4}>
<Metadata {...props} expanded={step === 'metadata'} />
<Metadata
history={history}
match={match}
expanded={step === 'metadata'}
/>
</Margin>
<Margin bottom={4}>
<Networks {...props} expanded={step === 'networks'} />
<Networks
history={history}
match={match}
expanded={step === 'networks'}
/>
</Margin>
<Margin bottom={5}>
<Firewall {...props} expanded={step === 'firewall'} />
<Firewall
history={history}
match={match}
expanded={step === 'firewall'}
/>
</Margin>
<Margin bottom={4}>
<CNS {...props} expanded={step === 'cns'} />
<CNS history={history} match={match} expanded={step === 'cns'} />
</Margin>
<Margin bottom={4}>
<Affinity {...props} expanded={step === 'affinity'} />
<Affinity
history={history}
match={match}
expanded={step === 'affinity'}
/>
</Margin>
<Margin top={7} bottom={10}>
<ReduxForm form={CREATE_FORM} onSubmit={handleSubmit}>
{({ handleSubmit, submitting }) => (
<form onSubmit={handleSubmit}>
<Button disabled={step !== 'summary'} loading={submitting}>
<Button disabled={disabled} loading={submitting}>
Deploy
</Button>
</form>
@ -76,14 +93,22 @@ const CreateInstance = ({ step, handleSubmit, ...props }) => (
export default compose(
graphql(CreateInstanceMutation, { name: 'createInstance' }),
connect(({ form, values }, ownProps) => {
connect(({ form, values }, { step }) => {
const disabled = ['name', 'image', 'package', 'networks'].some(
step => !get(values, `create-instance-${step}-proceeded`, false)
);
if (disabled) {
return { disabled };
}
const name = get(
form,
'create-instance-name.values.name',
'<instance-name>'
);
const firewall = get(
const firewall_enabled = get(
form,
'CREATE-INSTANCE-FIREWALL.values.enabled',
false
@ -98,14 +123,10 @@ export default compose(
const pkg = get(
form,
'create-instance-package.values.package',
'<instance-image>'
'<instance-pkg>'
);
const networks = get(
form,
'CREATE-INSTANCE-NETWORKS.values',
'<instance-image>'
);
const networks = get(form, 'CREATE-INSTANCE-NETWORKS.values', {});
const metadata = get(values, 'create-instance-metadata', []);
const receivedTags = get(values, 'create-instance-tags', []);
@ -121,52 +142,17 @@ export default compose(
tags.push({ name: 'triton.cns.services', value: cnsServices.join(',') });
}
const affRules = affinity
.map(aff => ({
conditional: aff['rule-instance-conditional'],
placement: aff['rule-instance-placement'],
identity: aff['rule-type'],
key: aff['rule-instance-tag-key'],
pattern: aff['rule-instance-tag-value-pattern'],
value:
aff['rule-type'] === 'name'
? aff['rule-instance-name']
: aff['rule-instance-tag-value']
}))
.map(({ conditional, placement, identity, key, pattern, value }) => {
const type = constantCase(
`${conditional}_${placement === 'same' ? 'equal' : 'not_equal'}`
);
console.log(pattern);
const patterns = {
equalling: value => value,
'not-equalling': value => `/^!${value}$/`,
containing: value => `/${value}/`,
starting: value => `/^${value}/`,
ending: value => `/${value}$/`
};
const _key = identity === 'name' ? 'instance' : key;
const _value = patterns[pattern](value);
return {
type,
key: _key,
value: _value
};
});
return {
forms: Object.keys(form), // improve this
name: name.toLowerCase(),
name,
pkg,
image,
affinity: affRules,
metadata: metadata.map(a => omit(a, 'expanded')),
tags: uniqBy(tags, 'name'),
firewall_enabled: firewall,
networks: Object.keys(networks).filter(network => networks[network])
affinity,
metadata,
tags,
firewall_enabled,
networks,
disabled
};
}),
connect(null, (dispatch, ownProps) => {
@ -186,17 +172,59 @@ export default compose(
return {
handleSubmit: async () => {
const _affinity = affinity
.map(aff => ({
conditional: aff['rule-instance-conditional'],
placement: aff['rule-instance-placement'],
identity: aff['rule-type'],
key: aff['rule-instance-tag-key'],
pattern: aff['rule-instance-tag-value-pattern'],
value:
aff['rule-type'] === 'name'
? aff['rule-instance-name']
: aff['rule-instance-tag-value']
}))
.map(({ conditional, placement, identity, key, pattern, value }) => {
const type = constantCase(
`${conditional}_${placement === 'same' ? 'equal' : 'not_equal'}`
);
const patterns = {
equalling: value => value,
'not-equalling': value => `/^!${value}$/`,
containing: value => `/${value}/`,
starting: value => `/^${value}/`,
ending: value => `/${value}$/`
};
const _key = identity === 'name' ? 'instance' : key;
const _value = patterns[pattern](value);
return {
type,
key: _key,
value: _value
};
});
const _metadata = metadata.map(a => omit(a, 'expanded'));
const _tags = uniqBy(tags, 'name');
const _networks = Object.keys(networks).filter(
network => networks[network]
);
const _name = name.toLowerCase();
const [err, res] = await intercept(
createInstance({
variables: {
name,
name: _name,
package: pkg,
image,
affinity,
metadata,
tags,
affinity: _affinity,
metadata: _metadata,
tags: _tags,
firewall_enabled,
networks
networks: _networks
}
})
);
@ -209,7 +237,7 @@ export default compose(
);
}
dispatch(forms.map(name => destroy(name)));
dispatch([destroyAll(), forms.map(name => destroy(name))]);
history.push(`/instances/${res.data.createMachine.name}`);
}

View File

@ -31,7 +31,12 @@ export const Metadata = ({
handleEdit
}) => (
<Fragment>
<Title icon={<MetadataIcon />}>Metadata</Title>
<Title
onClick={!expanded && !proceeded && handleEdit}
icon={<MetadataIcon />}
>
Metadata
</Title>
{expanded ? (
<Description>
Metadata can be used to pass data to the instance. It can also be used

View File

@ -2,13 +2,14 @@ import React, { Fragment } from 'react';
import { compose, graphql } from 'react-apollo';
import { set } from 'react-redux-values';
import ReduxForm from 'declarative-redux-form';
import { Margin } from 'styled-components-spacing';
import { change } from 'redux-form';
import { connect } from 'react-redux';
import intercept from 'apr-intercept';
import get from 'lodash.get';
import punycode from 'punycode';
import { NameIcon } from 'joyent-ui-toolkit';
import { NameIcon, H3, Button } from 'joyent-ui-toolkit';
import Name from '@components/create-instance/name';
import Title from '@components/create-instance/title';
@ -17,7 +18,7 @@ import GetRandomName from '@graphql/get-random-name.gql';
import { client } from '@state/store';
import parseError from '@state/parse-error';
const FORM_NAME = 'CREATE_INSTANCE_NAME';
const FORM_NAME = 'create-instance-name';
const NameContainer = ({
expanded,
@ -27,11 +28,13 @@ const NameContainer = ({
handleAsyncValidation,
shouldAsyncValidate,
handleNext,
handleCancel,
handleRandomize
handleRandomize,
handleEdit
}) => (
<Fragment>
<Title icon={<NameIcon />}>Instance name</Title>
<Title onClick={!expanded && !name && handleEdit} icon={<NameIcon />}>
Instance name
</Title>
<ReduxForm
form={FORM_NAME}
destroyOnUnmount={false}
@ -40,17 +43,25 @@ const NameContainer = ({
asyncValidate={handleAsyncValidation}
shouldAsyncValidate={shouldAsyncValidate}
>
{props => (
<Name
{...props}
name={name}
placeholderName={placeholderName}
expanded={expanded}
randomizing={randomizing}
onCancel={handleCancel}
onRandomize={handleRandomize}
/>
)}
{props =>
expanded ? (
<Name
{...props}
placeholderName={placeholderName}
randomizing={randomizing}
onRandomize={handleRandomize}
/>
) : name ? (
<Fragment>
<Margin bottom={2} top={3}>
<H3 bold>{name}</H3>
</Margin>
<Button type="button" secondary onClick={handleEdit}>
Edit
</Button>
</Fragment>
) : null
}
</ReduxForm>
</Fragment>
);
@ -80,14 +91,21 @@ export default compose(
(dispatch, { history }) => ({
shouldAsyncValidate: ({ trigger }) => trigger === 'submit',
handleAsyncValidation: async ({ name }) => {
const sanitized = punycode.encode(name).replace(/\-$/, '');
const sanitized = punycode.encode(name).replace(/-$/, '');
if (sanitized !== name) {
// eslint-disable-next-line no-throw-literal
throw {
name: 'Special characters are not accepted'
};
}
if (!(/^[a-zA-Z0-9][a-zA-Z0-9\\_\\.\\-]*$/).test(name)) {
throw {
name: 'Invalid name'
};
}
const [err, res] = await intercept(
client.query({
fetchPolicy: 'network-only',
@ -97,6 +115,7 @@ export default compose(
);
if (err) {
// eslint-disable-next-line no-throw-literal
throw {
name: parseError(err)
};
@ -106,13 +125,18 @@ export default compose(
const { machines = [] } = data;
if (machines.length) {
// eslint-disable-next-line no-throw-literal
throw {
name: `${name} already exists`
};
}
},
handleNext: () => history.push(`/instances/~create/image`),
handleCancel: () => history.push(`/instances/~create/name`),
handleNext: () => {
dispatch(set({ name: 'create-instance-name-proceeded', value: true }));
return history.push(`/instances/~create/image`);
},
handleEdit: () => history.push(`/instances/~create/name`),
handleRandomize: async () => {
dispatch(
set({ name: 'create-instance-name-randomizing', value: true })

View File

@ -29,7 +29,12 @@ export const Networks = ({
return (
<Fragment>
<Title icon={<NetworkIcon />}>Networks</Title>
<Title
onClick={!expanded && !proceeded && handleEdit}
icon={<NetworkIcon />}
>
Networks
</Title>
{expanded ? (
<Description>
Instances are automatically connected to a private fabric network,

View File

@ -29,8 +29,8 @@ const FILTERS = 'create-instance-package-filters';
const PackageContainer = ({
expanded,
hasVms,
handleSubmit,
handleCancel,
handleNext,
handleEdit,
loading,
packages,
selected = {},
@ -40,7 +40,12 @@ const PackageContainer = ({
resetFilters
}) => (
<Fragment>
<Title icon={<PackageIcon />}>Package</Title>
<Title
onClick={!expanded && !selected.id && handleEdit}
icon={<PackageIcon />}
>
Package
</Title>
<div>
{expanded ? (
<Description>
@ -71,7 +76,7 @@ const PackageContainer = ({
form={FORM_NAME}
destroyOnUnmount={false}
forceUnregisterOnUnmount={true}
onSubmit={handleSubmit}
onSubmit={handleNext}
>
{props => (
<Fragment>
@ -98,11 +103,7 @@ const PackageContainer = ({
</Fragment>
) : null}
{!expanded && selected.id ? (
<Overview
{...selected}
hasVms={hasVms}
onCancel={handleCancel}
/>
<Overview {...selected} hasVms={hasVms} onCancel={handleEdit} />
) : null}
</Fragment>
)}
@ -195,8 +196,14 @@ export default compose(
};
},
(dispatch, { history }) => ({
handleSubmit: () => history.push('/instances/~create/tags'),
handleCancel: () => history.push('/instances/~create/package'),
handleNext: () => {
dispatch(
set({ name: 'create-instance-package-proceeded', value: true })
);
return history.push('/instances/~create/tags');
},
handleEdit: () => history.push('/instances/~create/package'),
resetFilters: () => {
dispatch(reset(`${FILTERS}-filters`));
},

View File

@ -32,7 +32,9 @@ export const Tags = ({
handleEdit
}) => (
<Fragment>
<Title icon={<TagsIcon />}>Tags</Title>
<Title onClick={!expanded && !proceeded && handleEdit} icon={<TagsIcon />}>
Tags
</Title>
{expanded ? (
<Description>
Tags can be used to identify your instances, group multiple instances

View File

@ -6762,8 +6762,8 @@ multicast-dns-service-types@^1.1.0:
resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
multicast-dns@^6.0.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.1.tgz#c5035defa9219d30640558a49298067352098060"
version "6.2.2"
resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.2.tgz#300b6133361f8aaaf2b8d1248e85c363fe5b95a0"
dependencies:
dns-packet "^1.0.1"
thunky "^0.1.0"
@ -8234,9 +8234,9 @@ react-popper@^0.7.4:
popper.js "^1.12.5"
prop-types "^15.5.10"
react-redux-values@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-redux-values/-/react-redux-values-1.0.2.tgz#f626997a1107626883147e45612cb8d1c0bce8fb"
react-redux-values@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/react-redux-values/-/react-redux-values-1.1.2.tgz#e55e72fe74db1370e26aa96b27f15c262b1afa0c"
dependencies:
lodash.isundefined "^3.0.1"
prop-types "^15.6.0"