2017-11-28 16:50:55 +02:00
|
|
|
import React from 'react';
|
|
|
|
import { compose, graphql } from 'react-apollo';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { set } from 'react-redux-values';
|
2018-03-22 13:22:37 +02:00
|
|
|
import { stopSubmit } from 'redux-form';
|
2018-01-30 18:04:03 +02:00
|
|
|
import { Margin } from 'styled-components-spacing';
|
2018-02-24 05:28:54 +02:00
|
|
|
import intercept from 'apr-intercept';
|
|
|
|
import isArray from 'lodash.isarray';
|
|
|
|
import some from 'lodash.some';
|
|
|
|
import isInteger from 'lodash.isinteger';
|
2018-01-30 18:04:03 +02:00
|
|
|
import get from 'lodash.get';
|
2018-03-22 13:22:37 +02:00
|
|
|
import ReduxForm from 'declarative-redux-form';
|
2017-11-28 16:50:55 +02:00
|
|
|
|
|
|
|
import {
|
|
|
|
ViewContainer,
|
|
|
|
StatusLoader,
|
|
|
|
Message,
|
|
|
|
MessageDescription,
|
|
|
|
MessageTitle
|
|
|
|
} from 'joyent-ui-toolkit';
|
|
|
|
|
|
|
|
import GetInstance from '@graphql/get-instance.gql';
|
|
|
|
import StartInstance from '@graphql/start-instance.gql';
|
|
|
|
import StopInstance from '@graphql/stop-instance.gql';
|
|
|
|
import RebootInstance from '@graphql/reboot-instance.gql';
|
2018-01-03 17:59:29 +02:00
|
|
|
import RemoveInstance from '@graphql/remove-instance.gql';
|
2018-03-22 13:22:37 +02:00
|
|
|
import RenameMachine from '@graphql/rename-machine.gql';
|
2017-12-21 02:12:42 +02:00
|
|
|
import SummaryScreen from '@components/instances/summary';
|
2017-11-28 16:50:55 +02:00
|
|
|
import parseError from '@state/parse-error';
|
2018-03-02 16:18:11 +02:00
|
|
|
import Confirm from '@state/confirm';
|
2018-03-22 13:22:37 +02:00
|
|
|
import { instanceName as validateName } from '@state/validators';
|
|
|
|
|
|
|
|
const FORM = 'change-name';
|
2017-11-28 16:50:55 +02:00
|
|
|
|
2017-12-21 02:12:42 +02:00
|
|
|
export const Summary = ({
|
2017-11-28 16:50:55 +02:00
|
|
|
instance,
|
|
|
|
loading,
|
|
|
|
loadingError,
|
|
|
|
mutationError,
|
|
|
|
handleAction,
|
|
|
|
starting,
|
|
|
|
stopping,
|
|
|
|
rebooting,
|
2018-03-22 13:22:37 +02:00
|
|
|
removing,
|
|
|
|
editName,
|
|
|
|
editingName,
|
|
|
|
handleChangeName,
|
|
|
|
handleAsyncValidate,
|
|
|
|
shouldAsyncValidate
|
2017-11-28 16:50:55 +02:00
|
|
|
}) => {
|
2018-03-23 02:57:18 +02:00
|
|
|
const _loading =
|
|
|
|
loading || (!instance && !loadingError) ? <StatusLoader /> : null;
|
|
|
|
|
2017-11-28 16:50:55 +02:00
|
|
|
const _summary = !_loading &&
|
|
|
|
instance && (
|
2018-03-22 13:22:37 +02:00
|
|
|
<ReduxForm
|
|
|
|
form={FORM}
|
|
|
|
onSubmit={handleChangeName}
|
|
|
|
initialValues={{ name: instance.name }}
|
|
|
|
asyncValidate={handleAsyncValidate}
|
|
|
|
shouldAsyncValidate={shouldAsyncValidate}
|
|
|
|
>
|
|
|
|
{props => (
|
|
|
|
<SummaryScreen
|
|
|
|
{...props}
|
|
|
|
instance={instance}
|
|
|
|
starting={starting}
|
|
|
|
stopping={stopping}
|
|
|
|
rebooting={rebooting}
|
|
|
|
removing={removing}
|
|
|
|
onAction={handleAction}
|
|
|
|
editName={editName}
|
|
|
|
editingName={editingName}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</ReduxForm>
|
2017-11-28 16:50:55 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
const _error = loadingError &&
|
|
|
|
!_loading &&
|
|
|
|
!instance && (
|
2018-04-12 12:53:00 +03:00
|
|
|
<Margin bottom={5}>
|
2018-01-30 18:04:03 +02:00
|
|
|
<Message error>
|
|
|
|
<MessageTitle>Ooops!</MessageTitle>
|
|
|
|
<MessageDescription>
|
|
|
|
An error occurred while loading your instance summary
|
|
|
|
</MessageDescription>
|
|
|
|
</Message>
|
|
|
|
</Margin>
|
2017-11-28 16:50:55 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
const _mutationError = mutationError && (
|
|
|
|
<Message error>
|
|
|
|
<MessageTitle>Ooops!</MessageTitle>
|
|
|
|
<MessageDescription>{mutationError}</MessageDescription>
|
|
|
|
</Message>
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ViewContainer center={Boolean(_loading)} main>
|
|
|
|
{_loading}
|
|
|
|
{_error}
|
|
|
|
{_mutationError}
|
|
|
|
{_summary}
|
|
|
|
</ViewContainer>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2018-02-24 05:28:54 +02:00
|
|
|
// from https://github.com/natesilva/is-in-subnet
|
|
|
|
const ipv4ToLong = ip => {
|
|
|
|
const octets = ip.split('.');
|
|
|
|
|
|
|
|
return (
|
|
|
|
((parseInt(octets[0], 10) << 24) +
|
|
|
|
(parseInt(octets[1], 10) << 16) +
|
|
|
|
(parseInt(octets[2], 10) << 8) +
|
|
|
|
parseInt(octets[3], 10)) >>>
|
|
|
|
0
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
// from https://github.com/natesilva/is-in-subnet
|
|
|
|
const isInSubnet = (address, subnetOrSubnets) => {
|
|
|
|
if (isArray(subnetOrSubnets)) {
|
|
|
|
return some(subnetOrSubnets, subnet => isInSubnet(address, subnet));
|
|
|
|
}
|
|
|
|
|
|
|
|
const subnet = subnetOrSubnets;
|
|
|
|
|
|
|
|
const [subnetAddress, prefixLengthString] = subnet.split('/');
|
|
|
|
const prefixLength = parseInt(prefixLengthString, 10);
|
|
|
|
|
|
|
|
if (!subnetAddress || !isInteger(prefixLength)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prefixLength < 0 || prefixLength > 32) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// the next two lines throw if the addresses are not valid IPv4 addresses
|
|
|
|
const subnetLong = ipv4ToLong(subnetAddress);
|
|
|
|
const addressLong = ipv4ToLong(address);
|
|
|
|
|
|
|
|
if (prefixLength === 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const subnetPrefix = subnetLong >> (32 - prefixLength);
|
|
|
|
const addressPrefix = addressLong >> (32 - prefixLength);
|
|
|
|
|
|
|
|
return subnetPrefix === addressPrefix;
|
|
|
|
};
|
|
|
|
|
|
|
|
// from https://github.com/natesilva/is-in-subnet
|
|
|
|
const isPrivate = address => {
|
|
|
|
return isInSubnet(address, ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']);
|
|
|
|
};
|
|
|
|
|
2017-11-28 16:50:55 +02:00
|
|
|
export default compose(
|
|
|
|
graphql(StopInstance, { name: 'stop' }),
|
2018-03-22 13:22:37 +02:00
|
|
|
graphql(RenameMachine, { name: 'rename' }),
|
2017-11-28 16:50:55 +02:00
|
|
|
graphql(StartInstance, { name: 'start' }),
|
|
|
|
graphql(RebootInstance, { name: 'reboot' }),
|
2018-01-03 17:59:29 +02:00
|
|
|
graphql(RemoveInstance, { name: 'remove' }),
|
2017-11-28 16:50:55 +02:00
|
|
|
graphql(GetInstance, {
|
|
|
|
options: ({ match }) => ({
|
2018-02-20 02:35:31 +02:00
|
|
|
ssr: false,
|
2017-11-28 16:50:55 +02:00
|
|
|
pollInterval: 1000,
|
|
|
|
variables: {
|
2018-03-21 19:35:51 +02:00
|
|
|
id: get(match, 'params.instance')
|
2017-11-28 16:50:55 +02:00
|
|
|
}
|
|
|
|
}),
|
2018-03-21 19:35:51 +02:00
|
|
|
props: ({ data: { loading, error, machine, ...rest } }) => {
|
|
|
|
if (machine) {
|
|
|
|
const { ips } = machine;
|
2018-02-24 05:28:54 +02:00
|
|
|
|
|
|
|
const grupedIps = ips
|
|
|
|
.map(ip => ({ ip, openness: isPrivate(ip) ? 'private' : 'public' }))
|
|
|
|
.reduce(
|
|
|
|
(sum, { ip, openness }) =>
|
|
|
|
Object.assign(sum, {
|
|
|
|
[openness]: (sum[openness] || []).concat([ip])
|
|
|
|
}),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
2018-03-21 19:35:51 +02:00
|
|
|
machine = Object.assign({}, machine, { ips: grupedIps });
|
2018-02-24 05:28:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2018-03-21 19:35:51 +02:00
|
|
|
instance: machine,
|
2018-02-24 05:28:54 +02:00
|
|
|
loading,
|
|
|
|
loadingError: error
|
|
|
|
};
|
|
|
|
}
|
2017-11-28 16:50:55 +02:00
|
|
|
}),
|
|
|
|
connect(
|
2018-03-22 13:22:37 +02:00
|
|
|
({ values }, ownProps) => {
|
2017-11-28 16:50:55 +02:00
|
|
|
const { instance = {} } = ownProps;
|
|
|
|
const { id } = instance;
|
|
|
|
|
|
|
|
if (!id) {
|
|
|
|
return ownProps;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
...ownProps,
|
2018-03-22 13:22:37 +02:00
|
|
|
editingName: get(values, 'editing-name', false),
|
|
|
|
starting: values[`${id}-summary-starting`],
|
|
|
|
stopping: values[`${id}-summary-stoping`],
|
|
|
|
rebooting: values[`${id}-summary-rebooting`],
|
|
|
|
removing: values[`${id}-summary-removeing`],
|
|
|
|
mutationError: values[`${id}-summary-mutation-error`]
|
2017-11-28 16:50:55 +02:00
|
|
|
};
|
|
|
|
},
|
2018-03-22 13:22:37 +02:00
|
|
|
(dispatch, ownProps) => ({
|
|
|
|
shouldAsyncValidate: ({ trigger }) => {
|
|
|
|
return trigger === 'change';
|
|
|
|
},
|
|
|
|
handleAsyncValidate: validateName,
|
|
|
|
editName: () =>
|
|
|
|
dispatch(
|
|
|
|
set({
|
|
|
|
name: `editing-name`,
|
|
|
|
value: true
|
|
|
|
})
|
|
|
|
),
|
|
|
|
handleChangeName: async ({ name, id }) => {
|
|
|
|
const { instance } = ownProps;
|
|
|
|
|
|
|
|
if (name === instance.name) {
|
|
|
|
return dispatch(
|
|
|
|
set({
|
|
|
|
name: `editing-name`,
|
|
|
|
value: false
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
const [err] = await intercept(
|
|
|
|
ownProps.rename({
|
|
|
|
variables: {
|
|
|
|
name,
|
|
|
|
id: get(ownProps, 'match.params.instance')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
return dispatch(
|
|
|
|
stopSubmit(FORM, {
|
|
|
|
_error: parseError(err)
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch(
|
|
|
|
set({
|
|
|
|
name: `editing-name`,
|
|
|
|
value: false
|
|
|
|
})
|
|
|
|
);
|
|
|
|
},
|
2017-11-23 14:18:38 +02:00
|
|
|
handleAction: async action => {
|
2017-11-28 16:50:55 +02:00
|
|
|
const { instance } = ownProps;
|
|
|
|
const { id } = instance;
|
|
|
|
|
2018-03-02 16:18:11 +02:00
|
|
|
if (!await Confirm(`Do you want to ${action} "${instance.name}"?`)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-11-28 16:50:55 +02:00
|
|
|
const gerund = `${action}ing`;
|
2017-12-21 02:12:42 +02:00
|
|
|
const name = `${id}-summary-${gerund}`;
|
2017-11-28 16:50:55 +02:00
|
|
|
|
|
|
|
// sets loading to true
|
2018-03-22 13:22:37 +02:00
|
|
|
dispatch(
|
2017-11-23 14:18:38 +02:00
|
|
|
set({
|
|
|
|
name,
|
|
|
|
value: true
|
|
|
|
})
|
|
|
|
);
|
2017-11-28 16:50:55 +02:00
|
|
|
|
|
|
|
// calls mutation and waits while loading is still true
|
2017-11-23 14:18:38 +02:00
|
|
|
const [err] = await intercept(
|
|
|
|
ownProps[action]({
|
|
|
|
variables: { id }
|
|
|
|
})
|
|
|
|
);
|
2017-11-28 16:50:55 +02:00
|
|
|
|
2018-01-03 17:59:29 +02:00
|
|
|
if (!err && action === 'remove') {
|
2017-11-28 16:50:55 +02:00
|
|
|
const { history } = ownProps;
|
2018-03-06 03:14:33 +02:00
|
|
|
return history.push('/instances');
|
2017-11-28 16:50:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// after mutation, sets loading back to false
|
|
|
|
const setLoadingFalse = set({
|
|
|
|
name,
|
|
|
|
value: false
|
|
|
|
});
|
|
|
|
|
|
|
|
// if error, sets error value
|
2017-11-23 14:18:38 +02:00
|
|
|
const mutationError =
|
|
|
|
err &&
|
|
|
|
set({
|
2017-12-21 02:12:42 +02:00
|
|
|
name: `${id}-summary-mutation-error`,
|
2017-11-23 14:18:38 +02:00
|
|
|
value: parseError(err)
|
|
|
|
});
|
2017-11-28 16:50:55 +02:00
|
|
|
|
2018-03-22 13:22:37 +02:00
|
|
|
return dispatch([mutationError, setLoadingFalse].filter(Boolean));
|
2017-11-28 16:50:55 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
2017-12-21 02:12:42 +02:00
|
|
|
)(Summary);
|