mirror of
https://github.com/yldio/copilot.git
synced 2024-11-28 06:00:06 +02:00
feat: number input and mutations for restart, stop and start
This commit is contained in:
parent
0623c06fc7
commit
7e359e5836
@ -38,3 +38,5 @@ Gruntfile.js
|
||||
# misc
|
||||
*.gz
|
||||
*.md
|
||||
|
||||
!nyc/node_modules/istanbul-reports/lib/html/assets
|
||||
|
@ -4,32 +4,44 @@ import styled from 'styled-components';
|
||||
import unitcalc from 'unitcalc';
|
||||
|
||||
import { H2, P, Button } from 'joyent-ui-toolkit';
|
||||
import { FormGroup, Input, NumberInput } from 'joyent-ui-toolkit';
|
||||
import {
|
||||
FormGroup,
|
||||
NumberInput,
|
||||
NumberInputNormalize,
|
||||
FormMeta
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
const StyledH2 = styled(H2)`
|
||||
margin: 0 0 ${unitcalc(2)} 0;
|
||||
`;
|
||||
|
||||
const ServiceScale = ({ service, onConfirmClick, onCancelClick }) => {
|
||||
const handleScaleClick = () => {
|
||||
onConfirmClick(2);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<StyledH2>Scaling a service: <br />{service.name}</StyledH2>
|
||||
<P>Choose how many instances of a service you want to have running.</P>
|
||||
<form onSubmit={() => {}}>
|
||||
<NumberInput />
|
||||
<Button secondary onClick={onCancelClick}>Cancel</Button>
|
||||
<Button secondary onClick={handleScaleClick}>Scale</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const ServiceScale = ({
|
||||
service,
|
||||
handleSubmit,
|
||||
onCancelClick,
|
||||
invalid,
|
||||
pristine
|
||||
}) =>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<StyledH2>Scaling a service: <br />{service.name}</StyledH2>
|
||||
<P>Choose how many instances of a service you want to have running.</P>
|
||||
<FormGroup
|
||||
name="replicas"
|
||||
normalize={NumberInputNormalize({ minValue: 1 })}
|
||||
reduxForm
|
||||
>
|
||||
<FormMeta />
|
||||
<NumberInput minValue={1} />
|
||||
</FormGroup>
|
||||
<Button secondary onClick={onCancelClick}>Cancel</Button>
|
||||
<Button type="submit" disabled={pristine || invalid} secondary>
|
||||
Scale
|
||||
</Button>
|
||||
</form>;
|
||||
|
||||
ServiceScale.propTypes = {
|
||||
service: PropTypes.object,
|
||||
onScaleClick: PropTypes.func,
|
||||
onSubmitClick: PropTypes.func,
|
||||
onCancelClick: PropTypes.func
|
||||
};
|
||||
|
||||
|
@ -66,7 +66,7 @@ const ServiceListItem = ({
|
||||
const subtitle = <CardSubTitle>{service.instances} instances</CardSubTitle>;
|
||||
|
||||
const handleCardOptionsClick = evt => {
|
||||
onQuickActionsClick(evt, { service });
|
||||
onQuickActionsClick(evt, service);
|
||||
};
|
||||
|
||||
const header = isChild
|
||||
|
@ -2,7 +2,16 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Tooltip, TooltipButton, TooltipDivider } from 'joyent-ui-toolkit';
|
||||
|
||||
const ServicesQuickActions = ({ show, position, service, url, onBlur }) => {
|
||||
const ServicesQuickActions = ({
|
||||
show,
|
||||
position,
|
||||
service,
|
||||
url,
|
||||
onBlur,
|
||||
onRestartClick,
|
||||
onStopClick,
|
||||
onStartClick
|
||||
}) => {
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
@ -19,11 +28,25 @@ const ServicesQuickActions = ({ show, position, service, url, onBlur }) => {
|
||||
const scaleUrl = `${url}/${service.slug}/scale`;
|
||||
const deleteUrl = `${url}/${service.slug}/delete`;
|
||||
|
||||
const handleRestartClick = evt => {
|
||||
onRestartClick(evt, service);
|
||||
};
|
||||
|
||||
const handleStopClick = evt => {
|
||||
onStopClick(evt, service);
|
||||
};
|
||||
|
||||
const handleStartClick = evt => {
|
||||
onStartClick(evt, service);
|
||||
};
|
||||
|
||||
// TODO we'll need to check for service status and diplay start or restart & stop accordingly
|
||||
|
||||
return (
|
||||
<Tooltip {...p} onBlur={onBlur}>
|
||||
<TooltipButton to={scaleUrl}>Scale</TooltipButton>
|
||||
<TooltipButton>Restart</TooltipButton>
|
||||
<TooltipButton>Stop</TooltipButton>
|
||||
<TooltipButton onClick={handleRestartClick}>Restart</TooltipButton>
|
||||
<TooltipButton onClick={handleStopClick}>Stop</TooltipButton>
|
||||
<TooltipDivider />
|
||||
<TooltipButton to={deleteUrl}>Delete</TooltipButton>
|
||||
</Tooltip>
|
||||
@ -35,7 +58,10 @@ ServicesQuickActions.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
position: PropTypes.object,
|
||||
show: PropTypes.bool,
|
||||
onBlur: PropTypes.func
|
||||
onBlur: PropTypes.func,
|
||||
onRestartClick: PropTypes.func,
|
||||
onStopClick: PropTypes.func,
|
||||
onStartClick: PropTypes.func
|
||||
};
|
||||
|
||||
export default ServicesQuickActions;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose, graphql, gql } from 'react-apollo';
|
||||
import ServiceScaleMutation from '@graphql/ServiceScale.gql';
|
||||
import ServicesDeleteMutation from '@graphql/ServicesDeleteMutation.gql';
|
||||
import { Loader, ErrorMessage } from '@components/messaging';
|
||||
import { ServiceDelete as ServiceDeleteComponent } from '@components/service';
|
||||
import { Modal } from 'joyent-ui-toolkit';
|
||||
@ -47,16 +47,7 @@ ServiceDelete.propTypes = {
|
||||
deleteServices: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const DeleteGql = gql`
|
||||
mutation deleteServices($ids: [ID]!) {
|
||||
deleteServices(ids: $ids) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DeleteServicesGql = graphql(DeleteGql, {
|
||||
const DeleteServicesGql = graphql(ServicesDeleteMutation, {
|
||||
props: ({ mutate }) => ({
|
||||
deleteServices: serviceId => mutate({ variables: { ids: [serviceId] } })
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import ServiceScaleMutation from '@graphql/ServiceScale.gql';
|
||||
import { Loader, ErrorMessage } from '@components/messaging';
|
||||
import { ServiceScale as ServiceScaleComponent } from '@components/service';
|
||||
@ -20,21 +21,40 @@ class ServiceScale extends Component {
|
||||
|
||||
const { service, scale, history, match } = this.props;
|
||||
|
||||
const validateReplicas = ({ replicas }) => {
|
||||
if (replicas === '') {
|
||||
return {
|
||||
replicas:
|
||||
'Please enter the number of instances you would like to scale to.'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const ServiceScaleForm = reduxForm({
|
||||
form: 'scale-service',
|
||||
destroyOnUnmount: true,
|
||||
forceUnregisterOnUnmount: true,
|
||||
validate: validateReplicas,
|
||||
initialValues: {
|
||||
replicas: service.instances.length
|
||||
}
|
||||
})(ServiceScaleComponent);
|
||||
|
||||
const handleCloseClick = evt => {
|
||||
const closeUrl = match.url.split('/').slice(0, -2).join('/');
|
||||
history.replace(closeUrl);
|
||||
};
|
||||
|
||||
const handleConfirmClick = evt => {
|
||||
scale(service.id, 2);
|
||||
const handleSubmitClick = values => {
|
||||
scale(service.id, values.replicas);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal width={460} onCloseClick={handleCloseClick}>
|
||||
<ServiceScaleComponent
|
||||
<ServiceScaleForm
|
||||
service={service}
|
||||
onConfirmClick={handleConfirmClick}
|
||||
onCancelClick={handleCloseClick}
|
||||
onSubmit={handleSubmitClick.bind(this)}
|
||||
onCancel={handleCloseClick}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
@ -3,6 +3,9 @@ import { compose, graphql } from 'react-apollo';
|
||||
import { connect } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import ServicesQuery from '@graphql/Services.gql';
|
||||
import ServicesRestartMutation from '@graphql/ServicesRestartMutation.gql';
|
||||
import ServicesStopMutation from '@graphql/ServicesStopMutation.gql';
|
||||
import ServicesStartMutation from '@graphql/ServicesStartMutation.gql';
|
||||
|
||||
import { processServices } from '@root/state/selectors';
|
||||
import { toggleServicesQuickActions } from '@root/state/actions';
|
||||
@ -34,7 +37,10 @@ class ServiceList extends Component {
|
||||
error,
|
||||
servicesQuickActions,
|
||||
toggleServicesQuickActions,
|
||||
url
|
||||
url,
|
||||
restartServices,
|
||||
stopServices,
|
||||
startServices
|
||||
} = this.props;
|
||||
|
||||
if (loading) {
|
||||
@ -71,6 +77,18 @@ class ServiceList extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
const handleRestartClick = (evt, service) => {
|
||||
restartServices(service.id);
|
||||
};
|
||||
|
||||
const handleStopClick = (evt, service) => {
|
||||
stopServices(service.id);
|
||||
};
|
||||
|
||||
const handleStartClick = (evt, service) => {
|
||||
startServices(service.id);
|
||||
};
|
||||
|
||||
const handleQuickActionsBlur = o => {
|
||||
toggleServicesQuickActions({ show: false });
|
||||
};
|
||||
@ -95,6 +113,9 @@ class ServiceList extends Component {
|
||||
show={servicesQuickActions.show}
|
||||
url={url}
|
||||
onBlur={handleQuickActionsBlur}
|
||||
onRestartClick={handleRestartClick}
|
||||
onStopClick={handleStopClick}
|
||||
onStartClick={handleStartClick}
|
||||
/>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
@ -132,6 +153,30 @@ const ServicesGql = graphql(ServicesQuery, {
|
||||
})
|
||||
});
|
||||
|
||||
const ServiceListWithData = compose(ServicesGql, UiConnect)(ServiceList);
|
||||
const ServicesRestartGql = graphql(ServicesRestartMutation, {
|
||||
props: ({ mutate }) => ({
|
||||
restartServices: serviceId => mutate({ variables: { ids: [serviceId] } })
|
||||
})
|
||||
});
|
||||
|
||||
const ServicesStopGql = graphql(ServicesStopMutation, {
|
||||
props: ({ mutate }) => ({
|
||||
stopServices: serviceId => mutate({ variables: { ids: [serviceId] } })
|
||||
})
|
||||
});
|
||||
|
||||
const ServicesStartGql = graphql(ServicesStartMutation, {
|
||||
props: ({ mutate }) => ({
|
||||
startServices: serviceId => mutate({ variables: { ids: [serviceId] } })
|
||||
})
|
||||
});
|
||||
|
||||
const ServiceListWithData = compose(
|
||||
ServicesGql,
|
||||
ServicesStopGql,
|
||||
ServicesStartGql,
|
||||
ServicesGql,
|
||||
UiConnect
|
||||
)(ServiceList);
|
||||
|
||||
export default ServiceListWithData;
|
||||
|
@ -3,6 +3,9 @@ import { compose, graphql } from 'react-apollo';
|
||||
import { connect } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import ServicesQuery from '@graphql/ServicesTopology.gql';
|
||||
import ServicesRestartMutation from '@graphql/ServicesRestartMutation.gql';
|
||||
import ServicesStopMutation from '@graphql/ServicesStopMutation.gql';
|
||||
import ServicesStartMutation from '@graphql/ServicesStartMutation.gql';
|
||||
import unitcalc from 'unitcalc';
|
||||
|
||||
import { processServices } from '@root/state/selectors';
|
||||
@ -31,7 +34,10 @@ const ServicesTopology = ({
|
||||
loading,
|
||||
error,
|
||||
servicesQuickActions,
|
||||
toggleServicesQuickActions
|
||||
toggleServicesQuickActions,
|
||||
restartServices,
|
||||
stopServices,
|
||||
startServices
|
||||
}) => {
|
||||
if (loading) {
|
||||
return (
|
||||
@ -55,6 +61,18 @@ const ServicesTopology = ({
|
||||
toggleServicesQuickActions({ show: false });
|
||||
};
|
||||
|
||||
const handleRestartClick = (evt, service) => {
|
||||
restartServices(service.id);
|
||||
};
|
||||
|
||||
const handleStopClick = (evt, service) => {
|
||||
stopServices(service.id);
|
||||
};
|
||||
|
||||
const handleStartClick = (evt, service) => {
|
||||
startServices(service.id);
|
||||
};
|
||||
|
||||
const handleNodeTitleClick = (evt, { service }) => {
|
||||
push(`${url.split('/').slice(0, 3).join('/')}/services/${service.slug}`);
|
||||
};
|
||||
@ -73,6 +91,9 @@ const ServicesTopology = ({
|
||||
position={servicesQuickActions.position}
|
||||
url={url}
|
||||
onBlur={handleTooltipBlur}
|
||||
onRestartClick={handleRestartClick}
|
||||
onStopClick={handleStopClick}
|
||||
onStartClick={handleStartClick}
|
||||
/>
|
||||
</StyledContainer>
|
||||
</StyledBackground>
|
||||
@ -108,8 +129,30 @@ const ServicesGql = graphql(ServicesQuery, {
|
||||
})
|
||||
});
|
||||
|
||||
const ServicesTopologyWithData = compose(ServicesGql, UiConnect)(
|
||||
ServicesTopology
|
||||
);
|
||||
const ServicesRestartGql = graphql(ServicesRestartMutation, {
|
||||
props: ({ mutate }) => ({
|
||||
restartServices: serviceId => mutate({ variables: { ids: [serviceId] } })
|
||||
})
|
||||
});
|
||||
|
||||
const ServicesStopGql = graphql(ServicesStopMutation, {
|
||||
props: ({ mutate }) => ({
|
||||
stopServices: serviceId => mutate({ variables: { ids: [serviceId] } })
|
||||
})
|
||||
});
|
||||
|
||||
const ServicesStartGql = graphql(ServicesStartMutation, {
|
||||
props: ({ mutate }) => ({
|
||||
startServices: serviceId => mutate({ variables: { ids: [serviceId] } })
|
||||
})
|
||||
});
|
||||
|
||||
const ServicesTopologyWithData = compose(
|
||||
ServicesRestartGql,
|
||||
ServicesStopGql,
|
||||
ServicesStartGql,
|
||||
ServicesGql,
|
||||
UiConnect
|
||||
)(ServicesTopology);
|
||||
|
||||
export default ServicesTopologyWithData;
|
||||
|
@ -0,0 +1,6 @@
|
||||
mutation DeleteServices($ids: [ID]!) {
|
||||
deleteServices(ids: $ids) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
mutation RestartServices($ids: [ID]!) {
|
||||
restartServices(ids: $ids) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
mutation StartServices($ids: [ID]!) {
|
||||
startServices(ids: $ids) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
mutation StopServices($ids: [ID]!) {
|
||||
stopServices(ids: $ids) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
@ -100,10 +100,14 @@ const createServicesFromManifest = ({ deploymentGroupId, raw }) => {
|
||||
return Promise.resolve(undefined);
|
||||
};
|
||||
|
||||
const deleteServices = options => getServices({ id: options.ids[0] });
|
||||
const deleteServices = options => {
|
||||
const service = getServices({ id: options.ids[0] });
|
||||
return service;
|
||||
};
|
||||
|
||||
const scale = options => {
|
||||
const service = getServices({ id: options.serviceId })[0];
|
||||
|
||||
return {
|
||||
scale: [
|
||||
{
|
||||
@ -115,6 +119,21 @@ const scale = options => {
|
||||
};
|
||||
};
|
||||
|
||||
const restartServices = options => {
|
||||
const service = getServices({ id: options.ids[0] });
|
||||
return service;
|
||||
};
|
||||
|
||||
const stopServices = options => {
|
||||
const service = getServices({ id: options.ids[0] });
|
||||
return service;
|
||||
};
|
||||
|
||||
const startServices = options => {
|
||||
const service = getServices({ id: options.ids[0] });
|
||||
return service;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
portal: getPortal,
|
||||
deploymentGroups: getDeploymentGroups,
|
||||
@ -131,5 +150,8 @@ module.exports = {
|
||||
format: options.format
|
||||
})),
|
||||
deleteServices: (options, request, fn) => fn(null, deleteServices(options)),
|
||||
scale: (options, reguest, fn) => fn(null, scale(options))
|
||||
scale: (options, reguest, fn) => fn(null, scale(options)),
|
||||
restartServices: (options, request, fn) => fn(null, restartServices(options)),
|
||||
stopServices: (options, request, fn) => fn(null, stopServices(options)),
|
||||
startServices: (options, request, fn) => fn(null, startServices(options))
|
||||
};
|
||||
|
@ -191,7 +191,7 @@ type Mutation {
|
||||
updateDeploymentGroup(id: ID!, name: String!) : DeploymentGroup
|
||||
|
||||
provisionManifest(deploymentGroupId: ID!, type: ManifestType!, format: ManifestFormat!, raw: String!) : Manifest
|
||||
scale(service: ID!, replicas: Int!) : Version
|
||||
scale(serviceId: ID!, replicas: Int!) : Version
|
||||
|
||||
stopServices(ids: [ID]!) : [Service]
|
||||
startServices(ids: [ID]!) : [Service]
|
||||
|
@ -9,3 +9,4 @@ export { default as Radio, RadioList } from './radio';
|
||||
export { default as Select } from './select';
|
||||
export { default as Toggle, ToggleList } from './toggle';
|
||||
export { default as NumberInput } from './number-input';
|
||||
export { NumberInputNormalize } from './number-input';
|
||||
|
@ -12,7 +12,7 @@ const StyledContainer = styled.div`
|
||||
margin-bottom: ${unitcalc(4)};
|
||||
`;
|
||||
|
||||
const StyledNumberInput = styled(Baseline(BaseInput(Stylable('input'))))`
|
||||
const StyledNumberInput = styled(BaseInput(Stylable('input')))`
|
||||
width: ${unitcalc(20)};
|
||||
margin: 0 ${unitcalc(1)} 0 0;
|
||||
vertical-align: middle;
|
||||
@ -21,24 +21,41 @@ const StyledNumberInput = styled(Baseline(BaseInput(Stylable('input'))))`
|
||||
/**
|
||||
* @example ./usage-number-input.md
|
||||
*/
|
||||
const NumberInput = ({ value, ...rest }) => {
|
||||
const render = value =>
|
||||
<StyledContainer>
|
||||
<StyledNumberInput value={value} />
|
||||
<IconButton onClick={() => {}}>
|
||||
<MinusIcon verticalAlign="middle" />
|
||||
</IconButton>
|
||||
<IconButton onClick={() => {}}>
|
||||
<PlusIcon verticalAlign="middle" />
|
||||
</IconButton>
|
||||
</StyledContainer>;
|
||||
const NumberInput = BaseInput(props => {
|
||||
const { children, minValue, maxValue, ...rest } = props;
|
||||
|
||||
const render = value => {
|
||||
const handleMinusClick = evt => {
|
||||
evt.preventDefault();
|
||||
const nextValue = value.input.value - 1;
|
||||
value.input.onChange(nextValue);
|
||||
};
|
||||
|
||||
const handlePlusClick = evt => {
|
||||
evt.preventDefault();
|
||||
const nextValue = value.input.value + 1;
|
||||
value.input.onChange(nextValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledNumberInput {...props} />
|
||||
<IconButton onClick={handleMinusClick}>
|
||||
<MinusIcon verticalAlign="middle" />
|
||||
</IconButton>
|
||||
<IconButton onClick={handlePlusClick}>
|
||||
<PlusIcon verticalAlign="middle" />
|
||||
</IconButton>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Subscriber channel="input-group">
|
||||
{render}
|
||||
</Subscriber>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
NumberInput.propTypes = {
|
||||
value: PropTypes.number,
|
||||
@ -48,3 +65,18 @@ NumberInput.propTypes = {
|
||||
};
|
||||
|
||||
export default Baseline(NumberInput);
|
||||
|
||||
export const NumberInputNormalize = ({ minValue, maxValue }) => {
|
||||
return value => {
|
||||
if (value === '') {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!isNaN(value) &&
|
||||
(isNaN(minValue) || value >= minValue) &&
|
||||
(isNaN(maxValue) || value <= maxValue)
|
||||
) {
|
||||
return Number(value);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -67,7 +67,8 @@ export {
|
||||
Select,
|
||||
Toggle,
|
||||
ToggleList,
|
||||
NumberInput
|
||||
NumberInput,
|
||||
NumberInputNormalize
|
||||
} from './form';
|
||||
|
||||
export {
|
||||
|
Loading…
Reference in New Issue
Block a user