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-apollo": "^2.0.4",
"react-dom": "^16.2.0", "react-dom": "^16.2.0",
"react-redux": "^5.0.6", "react-redux": "^5.0.6",
"react-redux-values": "^1.0.2", "react-redux-values": "^1.1.2",
"react-router": "^4.2.0", "react-router": "^4.2.0",
"react-router-dom": "^4.2.2", "react-router-dom": "^4.2.2",
"redux": "^3.7.2", "redux": "^3.7.2",

View File

@ -1,14 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders <Title /> without throwing 1`] = ` exports[`renders <Title /> without throwing 1`] = `
Array [ .c3 {
.c3 {
font-size: 80%; font-size: 80%;
color: rgba(73,73,73,1); color: rgba(73,73,73,1);
line-height: 1.125rem; line-height: 1.125rem;
font-size: 0.8125rem; 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 { .c0 {
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
@ -62,75 +86,77 @@ Array [
margin-right: 0.25rem; margin-right: 0.25rem;
} }
<div .c5 {
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 {
margin-top: 0.25rem; margin-top: 0.25rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.c0 + div:empty, .c4 + div:empty,
.c0 + form:empty { .c4 + form:empty {
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
<div <div
className="c0 c1" className=""
> >
<div
className="c0"
>
<div <div
className="c2 c3" className="c1"
height="0.0625rem" >
<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`] = ` exports[`renders <Title icon="NameIcon"/> without throwing 1`] = `
Array [ .c3 {
.c3 {
font-size: 80%; font-size: 80%;
color: rgba(73,73,73,1); color: rgba(73,73,73,1);
line-height: 1.125rem; line-height: 1.125rem;
font-size: 0.8125rem; 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 { .c0 {
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
@ -184,96 +210,98 @@ Array [
margin-right: 0.25rem; margin-right: 0.25rem;
} }
<div .c5 {
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 {
margin-top: 0.25rem; margin-top: 0.25rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.c0 + div:empty, .c4 + div:empty,
.c0 + form:empty { .c4 + form:empty {
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
<div <div
className="c0 c1" className=""
> >
<div
className="c0"
>
<div <div
className="c2 c3" className="c1"
height="0.0625rem" >
<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`] = ` exports[`renders <Title icon="Test" label="Instance name"/> without throwing 1`] = `
Array [ .c3 {
.c3 {
font-size: 80%; font-size: 80%;
color: rgba(73,73,73,1); color: rgba(73,73,73,1);
line-height: 1.125rem; line-height: 1.125rem;
font-size: 0.8125rem; 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 { .c0 {
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
@ -327,96 +355,99 @@ Array [
margin-right: 0.25rem; margin-right: 0.25rem;
} }
<div .c5 {
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 {
margin-top: 0.25rem; margin-top: 0.25rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.c0 + div:empty, .c4 + div:empty,
.c0 + form:empty { .c4 + form:empty {
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
<div <div
className="c0 c1" className=""
label="Instance name"
> >
<div
className="c0"
>
<div <div
className="c2 c3" className="c1"
height="0.0625rem" >
<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`] = ` exports[`renders <Title label="Test"/> without throwing 1`] = `
Array [ .c3 {
.c3 {
font-size: 80%; font-size: 80%;
color: rgba(73,73,73,1); color: rgba(73,73,73,1);
line-height: 1.125rem; line-height: 1.125rem;
font-size: 0.8125rem; 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 { .c0 {
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
@ -470,62 +501,41 @@ Array [
margin-right: 0.25rem; margin-right: 0.25rem;
} }
<div .c5 {
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 {
margin-top: 0.25rem; margin-top: 0.25rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.c0 + div:empty, .c4 + div:empty,
.c0 + form:empty { .c4 + form:empty {
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
<div <div
className="c0 c1" className=""
label="Test"
> >
<div
className="c0"
>
<div <div
className="c2 c3" className="c1"
height="0.0625rem" >
<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, Button,
Toggle, Toggle,
H4, H4,
Select, Select
StatusLoader
} from 'joyent-ui-toolkit'; } from 'joyent-ui-toolkit';
import Description from '@components/create-instance/description';
const fadeIn = keyframes` const fadeIn = keyframes`
from { from {
@ -88,7 +86,7 @@ const getImageByID = (id, images) => {
const image = images const image = images
.map(image => ({ .map(image => ({
...image, ...image,
versions: image.versions.filter(version => version.id === id) versions: (image.versions || []).filter(version => version.id === id)
})) }))
.filter(e => e.versions.length)[0]; .filter(e => e.versions.length)[0];
return image 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 ({ export default ({
handleSubmit, handleSubmit,
pristine, pristine,
expanded,
imageID, imageID,
onCancel, images = [],
loading,
images,
isVmSelected isVmSelected
}) => ( }) => (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
{expanded && ( <Margin bottom={4}>
<Fragment> <FormGroup name="vms" field={Field}>
<Description> <Flex alignCenter>
Hardware virtual machines are generally used for non-containerized <FormLabel>Infrastructure Container </FormLabel>
applications. Infrastructure containers are generally for running any <Toggle onBlur={null}>Hardware Virtual Machine</Toggle>
Linux image on secure, bare metal containers.{' '} </Flex>
<a </FormGroup>
href="https://docs.joyent.com/private-cloud/images" </Margin>
rel="noopener noreferrer" <Row>
target="_blank" {images &&
> images.filter(i => i.isVm === isVmSelected).map(image => (
Read the docs <Col md={2} sm={3}>
</a> <Card
</Description> selected={
{loading ? ( image.imageName === getImageByID(imageID, images).imageName
<StatusLoader /> }
) : ( >
<Fragment> <img
<Margin bottom={4}> src={getImage(image.imageName).url}
<FormGroup name="vms" field={Field}> width={getImage(image.imageName).size}
<Flex alignCenter> height={getImage(image.imageName).size}
<FormLabel>Infrastructure Container </FormLabel> style={{
<Toggle onBlur={null}>Hardware Virtual Machine</Toggle> marginBottom: getImage(image.imageName).bottom
</Flex> }}
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> </FormGroup>
</Margin> </Card>
<Row> </Col>
{images && ))}
images.filter(i => i.isVm === isVmSelected).map(image => ( </Row>
<Col md={2} sm={3}> <Margin top={4}>
<Card <Button type="submit" disabled={pristine || !imageID}>
selected={ Next
image.imageName === </Button>
getImageByID(imageID, images).imageName </Margin>
}
>
<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>
)}
</form> </form>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,11 +2,13 @@ import React, { Fragment } from 'react';
import { compose, graphql } from 'react-apollo'; import { compose, graphql } from 'react-apollo';
import ReduxForm from 'declarative-redux-form'; import ReduxForm from 'declarative-redux-form';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { set } from 'react-redux-values';
import get from 'lodash.get'; import get from 'lodash.get';
import { InstanceTypeIcon } from 'joyent-ui-toolkit'; import { InstanceTypeIcon, StatusLoader } from 'joyent-ui-toolkit';
import Image from '@components/create-instance/image'; import Image, { Preview } from '@components/create-instance/image';
import Title from '@components/create-instance/title'; import Title from '@components/create-instance/title';
import Description from '@components/create-instance/description';
import imageData from '../../data/images-map.json'; import imageData from '../../data/images-map.json';
import getImages from '../../graphql/get-images.gql'; import getImages from '../../graphql/get-images.gql';
@ -14,31 +16,58 @@ import getImages from '../../graphql/get-images.gql';
const ImageContainer = ({ const ImageContainer = ({
expanded, expanded,
image, image,
handleSubmit, handleNext,
handleCancel, handleEdit,
loading, loading,
images, images,
vms vms
}) => ( }) => (
<Fragment> <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 <ReduxForm
form="create-instance-image" form="create-instance-image"
destroyOnUnmount={false} destroyOnUnmount={false}
forceUnregisterOnUnmount={true} forceUnregisterOnUnmount={true}
onSubmit={handleSubmit} onSubmit={handleNext}
> >
{props => ( {props =>
<Image (loading && expanded) ? (
{...props} <StatusLoader />
isVmSelected={vms} ) : expanded ? (
loading={loading} <Image
imageID={image} {...props}
images={images} isVmSelected={vms}
expanded={expanded} imageID={image}
onCancel={handleCancel} images={images}
/> />
)} ) : image ? (
<Preview
isVmSelected={vms}
imageID={image}
images={images}
onEdit={handleEdit}
/>
) : null
}
</ReduxForm> </ReduxForm>
</Fragment> </Fragment>
); );
@ -53,8 +82,12 @@ export default compose(
}; };
}, },
(dispatch, { history }) => ({ (dispatch, { history }) => ({
handleSubmit: () => history.push(`/instances/~create/package`), handleNext: () => {
handleCancel: () => history.push(`/instances/~create/image`) dispatch(set({ name: 'create-instance-image-proceeded', value: true }));
return history.push(`/instances/~create/package`);
},
handleEdit: () => history.push(`/instances/~create/image`)
}) })
), ),
graphql(getImages, { graphql(getImages, {

View File

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

View File

@ -31,7 +31,12 @@ export const Metadata = ({
handleEdit handleEdit
}) => ( }) => (
<Fragment> <Fragment>
<Title icon={<MetadataIcon />}>Metadata</Title> <Title
onClick={!expanded && !proceeded && handleEdit}
icon={<MetadataIcon />}
>
Metadata
</Title>
{expanded ? ( {expanded ? (
<Description> <Description>
Metadata can be used to pass data to the instance. It can also be used 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 { compose, graphql } from 'react-apollo';
import { set } from 'react-redux-values'; import { set } from 'react-redux-values';
import ReduxForm from 'declarative-redux-form'; import ReduxForm from 'declarative-redux-form';
import { Margin } from 'styled-components-spacing';
import { change } from 'redux-form'; import { change } from 'redux-form';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import intercept from 'apr-intercept'; import intercept from 'apr-intercept';
import get from 'lodash.get'; import get from 'lodash.get';
import punycode from 'punycode'; 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 Name from '@components/create-instance/name';
import Title from '@components/create-instance/title'; 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 { client } from '@state/store';
import parseError from '@state/parse-error'; import parseError from '@state/parse-error';
const FORM_NAME = 'CREATE_INSTANCE_NAME'; const FORM_NAME = 'create-instance-name';
const NameContainer = ({ const NameContainer = ({
expanded, expanded,
@ -27,11 +28,13 @@ const NameContainer = ({
handleAsyncValidation, handleAsyncValidation,
shouldAsyncValidate, shouldAsyncValidate,
handleNext, handleNext,
handleCancel, handleRandomize,
handleRandomize handleEdit
}) => ( }) => (
<Fragment> <Fragment>
<Title icon={<NameIcon />}>Instance name</Title> <Title onClick={!expanded && !name && handleEdit} icon={<NameIcon />}>
Instance name
</Title>
<ReduxForm <ReduxForm
form={FORM_NAME} form={FORM_NAME}
destroyOnUnmount={false} destroyOnUnmount={false}
@ -40,17 +43,25 @@ const NameContainer = ({
asyncValidate={handleAsyncValidation} asyncValidate={handleAsyncValidation}
shouldAsyncValidate={shouldAsyncValidate} shouldAsyncValidate={shouldAsyncValidate}
> >
{props => ( {props =>
<Name expanded ? (
{...props} <Name
name={name} {...props}
placeholderName={placeholderName} placeholderName={placeholderName}
expanded={expanded} randomizing={randomizing}
randomizing={randomizing} onRandomize={handleRandomize}
onCancel={handleCancel} />
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> </ReduxForm>
</Fragment> </Fragment>
); );
@ -80,14 +91,21 @@ export default compose(
(dispatch, { history }) => ({ (dispatch, { history }) => ({
shouldAsyncValidate: ({ trigger }) => trigger === 'submit', shouldAsyncValidate: ({ trigger }) => trigger === 'submit',
handleAsyncValidation: async ({ name }) => { handleAsyncValidation: async ({ name }) => {
const sanitized = punycode.encode(name).replace(/\-$/, ''); const sanitized = punycode.encode(name).replace(/-$/, '');
if (sanitized !== name) { if (sanitized !== name) {
// eslint-disable-next-line no-throw-literal
throw { throw {
name: 'Special characters are not accepted' 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( const [err, res] = await intercept(
client.query({ client.query({
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
@ -97,6 +115,7 @@ export default compose(
); );
if (err) { if (err) {
// eslint-disable-next-line no-throw-literal
throw { throw {
name: parseError(err) name: parseError(err)
}; };
@ -106,13 +125,18 @@ export default compose(
const { machines = [] } = data; const { machines = [] } = data;
if (machines.length) { if (machines.length) {
// eslint-disable-next-line no-throw-literal
throw { throw {
name: `${name} already exists` name: `${name} already exists`
}; };
} }
}, },
handleNext: () => history.push(`/instances/~create/image`), handleNext: () => {
handleCancel: () => history.push(`/instances/~create/name`), dispatch(set({ name: 'create-instance-name-proceeded', value: true }));
return history.push(`/instances/~create/image`);
},
handleEdit: () => history.push(`/instances/~create/name`),
handleRandomize: async () => { handleRandomize: async () => {
dispatch( dispatch(
set({ name: 'create-instance-name-randomizing', value: true }) set({ name: 'create-instance-name-randomizing', value: true })

View File

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

View File

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

View File

@ -32,7 +32,9 @@ export const Tags = ({
handleEdit handleEdit
}) => ( }) => (
<Fragment> <Fragment>
<Title icon={<TagsIcon />}>Tags</Title> <Title onClick={!expanded && !proceeded && handleEdit} icon={<TagsIcon />}>
Tags
</Title>
{expanded ? ( {expanded ? (
<Description> <Description>
Tags can be used to identify your instances, group multiple instances 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" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
multicast-dns@^6.0.1: multicast-dns@^6.0.1:
version "6.2.1" version "6.2.2"
resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.1.tgz#c5035defa9219d30640558a49298067352098060" resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.2.tgz#300b6133361f8aaaf2b8d1248e85c363fe5b95a0"
dependencies: dependencies:
dns-packet "^1.0.1" dns-packet "^1.0.1"
thunky "^0.1.0" thunky "^0.1.0"
@ -8234,9 +8234,9 @@ react-popper@^0.7.4:
popper.js "^1.12.5" popper.js "^1.12.5"
prop-types "^15.5.10" prop-types "^15.5.10"
react-redux-values@^1.0.2: react-redux-values@^1.1.2:
version "1.0.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/react-redux-values/-/react-redux-values-1.0.2.tgz#f626997a1107626883147e45612cb8d1c0bce8fb" resolved "https://registry.yarnpkg.com/react-redux-values/-/react-redux-values-1.1.2.tgz#e55e72fe74db1370e26aa96b27f15c262b1afa0c"
dependencies: dependencies:
lodash.isundefined "^3.0.1" lodash.isundefined "^3.0.1"
prop-types "^15.6.0" prop-types "^15.6.0"