fix(my-joy-beta): simplify create-instance images implementation
fixes #1027 fixes #1028
@ -12,7 +12,7 @@
|
||||
"lint-ci": "eslint . --ext .js --ext .md",
|
||||
"lint": "eslint . --fix --ext .js --ext .md",
|
||||
"test-ci": "NODE_ENV=test joyent-react-scripts test --env=jsdom --testPathIgnorePatterns='.ui.js'",
|
||||
"test": "DEFAULT_TIMEOUT_INTERVAL=50000 NODE_ENV=test joyent-react-scripts test --env=jsdom",
|
||||
"test": "DEFAULT_TIMEOUT_INTERVAL=80000 NODE_ENV=test joyent-react-scripts test --env=jsdom",
|
||||
"prepublish": "echo 0"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -27,6 +27,7 @@
|
||||
"joyent-manifest-editor": "3.0.1",
|
||||
"joyent-ui-toolkit": "^4.5.0",
|
||||
"lodash.find": "^4.6.0",
|
||||
"lodash.findindex": "^4.6.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isarray": "^4.0.0",
|
||||
@ -34,6 +35,7 @@
|
||||
"lodash.isfunction": "^3.0.8",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"lodash.reverse": "^4.0.1",
|
||||
"lodash.sortby": "^4.7.0",
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
"lunr": "^2.1.5",
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 7.3 KiB |
@ -6,6 +6,7 @@ import Flex from 'styled-flex-component';
|
||||
import remcalc from 'remcalc';
|
||||
import { Row, Col } from 'react-styled-flexboxgrid';
|
||||
import titleCase from 'title-case';
|
||||
import includes from 'lodash.includes';
|
||||
|
||||
import {
|
||||
H3,
|
||||
@ -47,47 +48,54 @@ const getImage = name => {
|
||||
}
|
||||
};
|
||||
|
||||
const getImageByID = (id, images) => {
|
||||
const image = images
|
||||
.map(image => ({
|
||||
...image,
|
||||
versions: (image.versions || []).filter(version => version.id === id)
|
||||
}))
|
||||
.filter(e => e.versions.length)[0];
|
||||
return image
|
||||
? {
|
||||
imageName: image.imageName,
|
||||
name: image.versions[0].name,
|
||||
version: image.versions[0].version
|
||||
}
|
||||
: {};
|
||||
};
|
||||
|
||||
export const Preview = ({ imageID, images, isVmSelected, onEdit }) => (
|
||||
export const Preview = ({ name, version, isVm }) => (
|
||||
<Fragment>
|
||||
<Margin bottom={2} top={3}>
|
||||
<H3 bold>
|
||||
{titleCase(getImageByID(imageID, images).name)} -{' '}
|
||||
{getImageByID(imageID, images).version}
|
||||
{name} - {version}
|
||||
</H3>
|
||||
<P>
|
||||
{isVmSelected ? 'Hardware Virtual Machine' : 'Infrastructure Container'}{' '}
|
||||
</P>
|
||||
<P>{isVm ? 'Hardware Virtual Machine' : 'Infrastructure Container'} </P>
|
||||
</Margin>
|
||||
<Button type="button" secondary onClick={onEdit}>
|
||||
Edit
|
||||
</Button>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default ({
|
||||
handleSubmit,
|
||||
pristine,
|
||||
imageID,
|
||||
images = [],
|
||||
isVmSelected
|
||||
}) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
const Image = ({ onClick, active, ...image }) => {
|
||||
const { imageName = '', versions = [] } = image;
|
||||
|
||||
const ids = [`image-card-${imageName}`, `image-img-${imageName}`];
|
||||
const handleClick = ev =>
|
||||
includes(ids, ev.target.id) ? onClick(image) : null;
|
||||
|
||||
return (
|
||||
<Col md={2} sm={3}>
|
||||
<Card id={ids[0]} onClick={handleClick} active={active} preview>
|
||||
<img
|
||||
id={ids[1]}
|
||||
src={getImage(imageName).url}
|
||||
width={getImage(imageName).size}
|
||||
height={getImage(imageName).size}
|
||||
style={{ marginBottom: getImage(imageName).bottom }}
|
||||
alt={imageName}
|
||||
/>
|
||||
<H4>{titleCase(imageName)}</H4>
|
||||
<FormGroup name="image" field={Field}>
|
||||
<Version onBlur={null}>
|
||||
<option selected>Version</option>
|
||||
{versions.map(({ name, version, id }) => (
|
||||
<option
|
||||
key={`${name} - ${version}`}
|
||||
value={id}
|
||||
>{`${name} - ${version}`}</option>
|
||||
))}
|
||||
</Version>
|
||||
</FormGroup>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
export const ImageType = () => (
|
||||
<form>
|
||||
<Margin bottom={4}>
|
||||
<FormGroup name="vms" field={Field}>
|
||||
<Flex alignCenter>
|
||||
@ -96,46 +104,20 @@ export default ({
|
||||
</Flex>
|
||||
</FormGroup>
|
||||
</Margin>
|
||||
<Row>
|
||||
{images &&
|
||||
images.filter(i => i.isVm === isVmSelected).map(image => (
|
||||
<Col md={2} sm={3}>
|
||||
<Card
|
||||
active={
|
||||
image.imageName === getImageByID(imageID, images).imageName
|
||||
}
|
||||
preview
|
||||
>
|
||||
<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>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Margin top={4}>
|
||||
<Button type="submit" disabled={pristine || !imageID}>
|
||||
Next
|
||||
</Button>
|
||||
</Margin>
|
||||
</form>
|
||||
);
|
||||
|
||||
export default ({ images = [], onSelectLatest }) => (
|
||||
<form>
|
||||
<Row>
|
||||
{images.map(({ imageName, ...image }) => (
|
||||
<Image
|
||||
{...image}
|
||||
key={imageName}
|
||||
imageName={imageName}
|
||||
onClick={onSelectLatest}
|
||||
/>
|
||||
))}
|
||||
</Row>
|
||||
</form>
|
||||
);
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
@ -1,31 +1,40 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import ReduxForm from 'declarative-redux-form';
|
||||
import { change } from 'redux-form';
|
||||
import { connect } from 'react-redux';
|
||||
import { set } from 'react-redux-values';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import includes from 'lodash.includes';
|
||||
import sortBy from 'lodash.sortby';
|
||||
import findIndex from 'lodash.findindex';
|
||||
import find from 'lodash.find';
|
||||
import reverse from 'lodash.reverse';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import { InstanceTypeIcon, StatusLoader } from 'joyent-ui-toolkit';
|
||||
import { InstanceTypeIcon, StatusLoader, Button } from 'joyent-ui-toolkit';
|
||||
|
||||
import Description from '@components/description';
|
||||
import Image, { Preview } from '@components/create-instance/image';
|
||||
import Image, { Preview, ImageType } from '@components/create-instance/image';
|
||||
import Title from '@components/create-instance/title';
|
||||
import imageData from '@data/images-map.json';
|
||||
import getImages from '@graphql/get-images.gql';
|
||||
import GetImages from '@graphql/get-images.gql';
|
||||
|
||||
const ImageContainer = ({
|
||||
expanded,
|
||||
image,
|
||||
proceeded,
|
||||
image = {},
|
||||
handleNext,
|
||||
handleEdit,
|
||||
handleSelectLatest,
|
||||
loading,
|
||||
images,
|
||||
vms
|
||||
}) => (
|
||||
<Fragment>
|
||||
{console.log({ image, vms })}
|
||||
<Title
|
||||
onClick={!expanded && !image && handleEdit}
|
||||
onClick={!expanded && !image.id && handleEdit}
|
||||
icon={<InstanceTypeIcon />}
|
||||
>
|
||||
Instance type and image
|
||||
@ -44,12 +53,19 @@ const ImageContainer = ({
|
||||
</a>
|
||||
</Description>
|
||||
) : null}
|
||||
<ReduxForm
|
||||
form="create-instance-vms"
|
||||
destroyOnUnmount={false}
|
||||
forceUnregisterOnUnmount={true}
|
||||
initialValues={{ vms: true }}
|
||||
>
|
||||
{props => (loading || !expanded ? null : <ImageType {...props} />)}
|
||||
</ReduxForm>
|
||||
<ReduxForm
|
||||
form="create-instance-image"
|
||||
destroyOnUnmount={false}
|
||||
forceUnregisterOnUnmount={true}
|
||||
initialValues={{ vms: true }}
|
||||
onSubmit={handleNext}
|
||||
>
|
||||
{props =>
|
||||
loading && expanded ? (
|
||||
@ -57,30 +73,44 @@ const ImageContainer = ({
|
||||
) : expanded ? (
|
||||
<Image
|
||||
{...props}
|
||||
isVmSelected={vms}
|
||||
imageID={image}
|
||||
images={images}
|
||||
/>
|
||||
) : image ? (
|
||||
<Preview
|
||||
isVmSelected={vms}
|
||||
imageID={image}
|
||||
images={images}
|
||||
onEdit={handleEdit}
|
||||
images={images.filter(i => i.isVm === vms)}
|
||||
onSelectLatest={handleSelectLatest}
|
||||
/>
|
||||
) : image.id ? (
|
||||
<Preview {...image} />
|
||||
) : null
|
||||
}
|
||||
</ReduxForm>
|
||||
<Fragment>
|
||||
{expanded ? (
|
||||
<Margin bottom={4}>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleNext}
|
||||
disabled={!image.id || vms !== image.isVm}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</Margin>
|
||||
) : proceeded ? (
|
||||
<Margin bottom={4}>
|
||||
<Button type="button" onClick={handleEdit} secondary>
|
||||
Edit
|
||||
</Button>
|
||||
</Margin>
|
||||
) : null}
|
||||
</Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default compose(
|
||||
connect(
|
||||
(state, ownProps) => {
|
||||
({ form, values }, ownProps) => {
|
||||
return {
|
||||
...ownProps,
|
||||
vms: get(state, 'form.create-instance-image.values.vms', false),
|
||||
image: get(state, 'form.create-instance-image.values.image', null)
|
||||
proceeded: get(values, 'create-instance-image-proceeded', false),
|
||||
vms: get(form, 'create-instance-vms.values.vms', false),
|
||||
image: get(form, 'create-instance-image.values.image', null)
|
||||
};
|
||||
},
|
||||
(dispatch, { history }) => ({
|
||||
@ -89,58 +119,76 @@ export default compose(
|
||||
|
||||
return history.push(`/instances/~create/package`);
|
||||
},
|
||||
handleEdit: () => history.push(`/instances/~create/image`)
|
||||
handleEdit: () => history.push(`/instances/~create/image`),
|
||||
handleSelectLatest: ({ versions }) => {
|
||||
const id = versions[versions.length - 1].id;
|
||||
return dispatch(change('create-instance-image', 'image', id));
|
||||
}
|
||||
})
|
||||
),
|
||||
graphql(getImages, {
|
||||
props: ({ ownProps: { vms = false }, data: { loading, images = [] } }) => ({
|
||||
loading,
|
||||
images: images.reduce((accumulator, image) => {
|
||||
const isVm = !includes(image.type, 'DATASET');
|
||||
graphql(GetImages, {
|
||||
props: ({ ownProps, data }) => {
|
||||
const { image = '' } = ownProps;
|
||||
const { loading = false, images = [] } = data;
|
||||
|
||||
if (isVm && !vms) {
|
||||
return accumulator;
|
||||
}
|
||||
const values = images
|
||||
.reduce((acc, img) => {
|
||||
const isVm = !includes(img.type, 'DATASET');
|
||||
|
||||
const name =
|
||||
imageData[
|
||||
image.name
|
||||
.split('-')[0]
|
||||
.split(' ')[0]
|
||||
.toLowerCase()
|
||||
];
|
||||
const imageName =
|
||||
imageData[
|
||||
img.name
|
||||
.split('-')[0]
|
||||
.split(' ')[0]
|
||||
.toLowerCase()
|
||||
];
|
||||
|
||||
const exists = Boolean(
|
||||
accumulator.filter(e => e.imageName === name && isVm === e.isVm)
|
||||
.length
|
||||
);
|
||||
const exists = Boolean(find(acc, { imageName, isVm }));
|
||||
|
||||
if (!exists) {
|
||||
return accumulator.concat([
|
||||
{
|
||||
imageName: name,
|
||||
versions: [
|
||||
{
|
||||
name: image.name,
|
||||
version: image.version,
|
||||
id: image.id
|
||||
}
|
||||
],
|
||||
isVm
|
||||
}
|
||||
]);
|
||||
}
|
||||
const version = {
|
||||
name: img.name,
|
||||
version: img.version,
|
||||
id: img.id
|
||||
};
|
||||
|
||||
return accumulator.map(({ versions, ...rest }) => ({
|
||||
...rest,
|
||||
versions:
|
||||
rest.imageName === name && rest.isVm === isVm
|
||||
? versions.concat([
|
||||
{ name: image.name, version: image.version, id: image.id }
|
||||
])
|
||||
: versions
|
||||
if (!exists) {
|
||||
return acc.concat([
|
||||
{
|
||||
isVm,
|
||||
imageName,
|
||||
versions: [version]
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
const index = findIndex(acc, {
|
||||
imageName,
|
||||
isVm
|
||||
});
|
||||
|
||||
acc[index] = {
|
||||
...acc[index],
|
||||
versions: acc[index].versions.concat([version])
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, [])
|
||||
.map(({ versions, ...img }) => ({
|
||||
...img,
|
||||
active: Boolean(find(versions, ['id', image])),
|
||||
versions: reverse(sortBy(versions, ['name']))
|
||||
}));
|
||||
}, [])
|
||||
})
|
||||
|
||||
const selected = find(images, ['id', image]) || {};
|
||||
|
||||
return {
|
||||
loading,
|
||||
images: values,
|
||||
image: {
|
||||
...selected,
|
||||
isVm: !includes(selected.type || '', 'DATASET')
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
)(ImageContainer);
|
||||
|
@ -8,6 +8,7 @@ import { set } from 'react-redux-values';
|
||||
import sortBy from 'lodash.sortby';
|
||||
import find from 'lodash.find';
|
||||
import includes from 'lodash.includes';
|
||||
import reverse from 'lodash.reverse';
|
||||
import constantCase from 'constant-case';
|
||||
import { reset } from 'redux-form';
|
||||
|
||||
@ -192,7 +193,7 @@ export default compose(
|
||||
...ownProps,
|
||||
sortBy: _sortBy,
|
||||
sortOrder: _sortOrder,
|
||||
packages: _sortOrder === 'asc' ? filtered : filtered.reverse(),
|
||||
packages: _sortOrder === 'asc' ? filtered : reverse(filtered),
|
||||
hasVms: vmSelected,
|
||||
selected: find(packages, ['id', pkgSelected])
|
||||
};
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -8,6 +8,7 @@ import forceArray from 'force-array';
|
||||
import get from 'lodash.get';
|
||||
import intercept from 'apr-intercept';
|
||||
import find from 'lodash.find';
|
||||
import reverse from 'lodash.reverse';
|
||||
import sort from 'lodash.sortby';
|
||||
import remcalc from 'remcalc';
|
||||
|
||||
@ -231,7 +232,7 @@ export default compose(
|
||||
|
||||
return {
|
||||
// is sortOrder !== asc, reverse order
|
||||
instances: sortOrder === 'asc' ? ascSorted : ascSorted.reverse(),
|
||||
instances: sortOrder === 'asc' ? ascSorted : reverse(ascSorted),
|
||||
allowedActions,
|
||||
selected,
|
||||
statuses,
|
||||
|
@ -5,6 +5,7 @@ import { set } from 'react-redux-values';
|
||||
import forceArray from 'force-array';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import find from 'lodash.find';
|
||||
import reverse from 'lodash.reverse';
|
||||
import sortBy from 'lodash.sortby';
|
||||
import get from 'lodash.get';
|
||||
|
||||
@ -82,7 +83,7 @@ export default compose(
|
||||
|
||||
const instance = find(forceArray(machines), ['name', name]);
|
||||
const values = get(instance, 'networks', []);
|
||||
const networks = sortBy(values, 'public').reverse();
|
||||
const networks = reverse(sortBy(values, 'public'));
|
||||
|
||||
return {
|
||||
networks,
|
||||
|
@ -4,6 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { stopSubmit, startSubmit, change, reset } from 'redux-form';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import find from 'lodash.find';
|
||||
import reverse from 'lodash.reverse';
|
||||
import get from 'lodash.get';
|
||||
import sort from 'lodash.sortby';
|
||||
import { set } from 'react-redux-values';
|
||||
@ -223,7 +224,7 @@ export default compose(
|
||||
|
||||
return {
|
||||
...rest,
|
||||
snapshots: sortOrder === 'asc' ? ascSorted : ascSorted.reverse(),
|
||||
snapshots: sortOrder === 'asc' ? ascSorted : reverse(ascSorted),
|
||||
selected,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
|
@ -107,6 +107,10 @@ const Preview = styled.div`
|
||||
margin-bottom: ${remcalc(20)};
|
||||
animation: ${fadeIn} 0.2s ease-in-out;
|
||||
|
||||
${is('onClick')`
|
||||
cursor: pointer;
|
||||
`};
|
||||
|
||||
${is('active')`
|
||||
border: ${remcalc(1)} solid ${props => props.theme.primaryActive};
|
||||
|
||||
|
@ -6662,6 +6662,10 @@ lodash.find@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1"
|
||||
|
||||
lodash.findindex@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106"
|
||||
|
||||
lodash.flatten@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
@ -6746,6 +6750,10 @@ lodash.pick@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
|
||||
|
||||
lodash.reverse@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.reverse/-/lodash.reverse-4.0.1.tgz#1f2afedace2e16e660f3aa7c59d3300a6f25d13c"
|
||||
|
||||
lodash.some@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
||||
|