feat(ui-toolkit, cp-frontend): Add ui to delete a deployment group

This commit is contained in:
JUDIT GRESKOVITS 2017-07-17 12:41:26 +01:00 committed by Sérgio Ramos
parent db378a6f3a
commit ee5a071bd9
18 changed files with 261 additions and 35 deletions

View File

@ -0,0 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { ModalHeading, ModalText, Button } from 'joyent-ui-toolkit';
const DeploymentGroupDelete = ({ deploymentGroup, onCancelClick, onConfirmClick }) =>
<div>
<ModalHeading>
Deleting a deployment group: <br /> {deploymentGroup.name}
</ModalHeading>
<ModalText marginBottom="3">
Deleting a deployment group will also remove all of the services and instances associated with that deployment group. Are you sure you want to continue?
</ModalText>
<Button onClick={onCancelClick} secondary>Cancel</Button>
<Button onClick={onConfirmClick}>Delete deployment group</Button>
</div>;
DeploymentGroupDelete.propTypes = {
deploymentGroup: PropTypes.object,
onCancelClick: PropTypes.func.isRequired,
onConfirmClick: PropTypes.func.isRequired
};
export default DeploymentGroupDelete;

View File

@ -0,0 +1 @@
export { default as DeploymentGroupDelete } from './delete';

View File

@ -1,16 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { H2, Button, P } from 'joyent-ui-toolkit';
const ModalHeading = styled(H2)`
line-height: 1.25;
color: ${props => props.theme.secondary};
`;
const ModalText = styled(P)`
color: ${props => props.theme.secondary};
`;
import { ModalHeading, ModalText, Button } from 'joyent-ui-toolkit';
const propTypes = {
service: PropTypes.object,

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import styled from 'styled-components';
import unitcalc from 'unitcalc';
import { H2, P, Button } from 'joyent-ui-toolkit';
import { ModalHeading, ModalText, Button } from 'joyent-ui-toolkit';
import {
FormGroup,
NumberInput,
@ -11,10 +11,6 @@ import {
FormMeta
} from 'joyent-ui-toolkit';
const StyledH2 = styled(H2)`
margin: 0 0 ${unitcalc(2)} 0;
`;
const ServiceScale = ({
service,
handleSubmit,
@ -23,8 +19,8 @@ const ServiceScale = ({
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>
<ModalHeading>Scaling a service: <br />{service.name}</ModalHeading>
<ModalText>Choose how many instances of a service you want to have running.</ModalText>
<FormGroup
name="replicas"
normalize={NumberInputNormalize({ minValue: 1 })}

View File

@ -0,0 +1,80 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { compose, graphql } from 'react-apollo';
import DeploymentGroupsDeleteMutation from '@graphql/DeploymentGroupsDeleteMutation.gql';
import DeploymentGroupQuery from '@graphql/DeploymentGroup.gql';
import { Loader, ErrorMessage } from '@components/messaging';
import { DeploymentGroupDelete as DeploymentGroupDeleteComponent } from '@components/deployment-group';
import { Modal } from 'joyent-ui-toolkit';
class DeploymentGroupDelete extends Component {
render() {
if (this.props.loading) {
return <Loader />;
}
if (this.props.error) {
return (
<ErrorMessage message="Oops, an error occured while loading your service." />
);
}
const { deploymentGroup, deleteDeploymentGroups, history, match } = this.props;
const handleCloseClick = evt => {
const closeUrl = match.url.split('/').slice(0, -2).join('/');
history.replace(closeUrl);
};
const handleConfirmClick = evt => {
deleteDeploymentGroups(deploymentGroup.id).then(() => handleCloseClick());
};
return (
<Modal width={460} onCloseClick={handleCloseClick}>
<DeploymentGroupDeleteComponent
deploymentGroup={deploymentGroup}
onConfirmClick={handleConfirmClick}
onCancelClick={handleCloseClick}
/>
</Modal>
);
}
}
DeploymentGroupDelete.propTypes = {
deploymentGroup: PropTypes.object,
history: PropTypes.object,
deleteDeploymentGroups: PropTypes.func.isRequired
};
const DeleteDeploymentGroupsGql = graphql(DeploymentGroupsDeleteMutation, {
props: ({ mutate }) => ({
deleteDeploymentGroups: deploymentGroupId =>
mutate({
variables: { ids: [deploymentGroupId] }
})
})
});
const DeploymentGroupGql = graphql(DeploymentGroupQuery, {
options(props) {
const params = props.match.params;
const deploymentGroupSlug = params.deploymentGroup;
return {
variables: {
deploymentGroupSlug
}
};
},
props: ({ data: { deploymentGroup, loading, error } }) => ({
deploymentGroup,
loading,
error
})
});
const DeploymentGroupDeleteWithData = compose(DeleteDeploymentGroupsGql, DeploymentGroupGql)(
DeploymentGroupDelete
);
export default DeploymentGroupDeleteWithData;

View File

@ -0,0 +1 @@
export { default as DeploymentGroupDelete } from './delete';

View File

@ -12,7 +12,7 @@ import { ErrorMessage } from '@components/messaging';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import DeploymentGroupsQuery from '@graphql/DeploymentGroups.gql';
import DeploymentGroupsImportableQuery from '@graphql/DeploymentGroupsImportable.gql';
import { H2, H3, Small } from 'joyent-ui-toolkit';
import { H2, H3, Small, IconButton, BinIcon, Button } from 'joyent-ui-toolkit';
const Title = H2.extend`
margin-top: ${remcalc(2)};
@ -22,7 +22,9 @@ const DGsRows = Row.extend`
margin-top: ${remcalc(-7)};
`;
const Box = Col.withComponent(Link).extend`
// const Box = Col.withComponent(Link).extend`
const Box = styled.div`
position: relative;
text-decoration: none;
color: ${props => props.theme.secondary};
background-color: ${props => props.theme.white};
@ -76,6 +78,36 @@ const ServiceTitle = H3.extend`
font-weight: 600;
`;
const StyledLink = styled(Link)`
display: flex;
flex-grow: 1;
text-decoration: none;
color: ${props => props.theme.secondary};
`;
const DeleteButtonContainer = styled.div`
position: absolute;
right: 0;
bottom: 0;
`;
const StyledIconButton = styled(IconButton)`
position: absolute;
right: 0;
bottom: 0;
border: none;
&:hover {
background-color: ${props => props.theme.white};
}
&:active,
&:active:hover,
&:active:focus {
background-color: ${props => props.theme.white};
}
`;
const DeploymentGroupList = ({
location,
deploymentGroups,
@ -95,8 +127,13 @@ const DeploymentGroupList = ({
const groups = forceArray(deploymentGroups).map(({ slug, name }) =>
<Col xs={12} sm={4} md={3} lg={3} key={slug}>
<Box to={`${match.path}/${slug}`}>
<Box>
<StyledLink to={`${match.path}/${slug}`}>
<ServiceTitle>{name}</ServiceTitle>
</StyledLink>
<StyledIconButton to={`${match.url}/${slug}/delete`}>
<BinIcon />
</StyledIconButton>
</Box>
</Col>
);

View File

@ -0,0 +1,7 @@
#import "./DeploymentGroupInfo.gql"
query DeploymentGroup($deploymentGroupSlug: String!) {
deploymentGroup(slug: $deploymentGroupSlug) {
...DeploymentGroupInfo
}
}

View File

@ -0,0 +1,7 @@
#import "./DeploymentGroupInfo.gql"
mutation DeleteDeploymentGroups($ids: [ID]!) {
deleteDeploymentGroups(ids: $ids) {
...DeploymentGroupInfo
}
}

View File

@ -19,6 +19,10 @@ import {
ServicesMenu
} from '@containers/services';
import {
DeploymentGroupDelete
} from '@containers/deployment-group';
const Container = styled.div`
display: flex;
flex: 1 1 auto;
@ -57,6 +61,11 @@ const Router = (
<Route path="/deployment-groups" component={Breadcrumb} />
</Switch>
<Switch>
<Route
path="/deployment-groups/:deploymentGroup/delete"
exact
component={DeploymentGroupDelete}
/>
<Route
path="/deployment-groups/:deploymentGroup/services/:service"
component={Menu}
@ -66,6 +75,7 @@ const Router = (
<Route path="/" exact component={rootRedirect} />
<Route path="/deployment-groups" exact component={DeploymentGroupList} />
<Route path="/deployment-groups/:deploymentGroup/delete" exact component={DeploymentGroupList} />
<Route
path="/deployment-groups/:deploymentGroup"

View File

@ -15,6 +15,6 @@ const alignsFromProps = props =>
.join(';\n');
export default Component =>
Component.extend
/* Component.extend
? Component.extend`${alignsFromProps}`
: styled(Component)`${alignsFromProps}`;
: */ styled(Component)`${alignsFromProps}`;

View File

@ -14,7 +14,7 @@ const StyledContainer = styled.div`
const StyledNumberInput = styled(BaseInput(Stylable('input')))`
width: ${unitcalc(20)};
margin: 0 ${unitcalc(1)} 0 0;
margin: 0 ${unitcalc(2)} 0 0;
vertical-align: middle;
`;
@ -39,11 +39,11 @@ const NumberInput = BaseInput(props => {
return (
<StyledContainer>
<StyledNumberInput {...props} />
<StyledNumberInput {...props} marginRight={2}/>
<IconButton onClick={handleMinusClick}>
<MinusIcon verticalAlign="middle" />
</IconButton>
<IconButton onClick={handlePlusClick}>
<IconButton onClick={handlePlusClick} marginLeft={1}>
<PlusIcon verticalAlign="middle" />
</IconButton>
</StyledContainer>

View File

@ -1,11 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import remcalc from 'remcalc';
import { borderRadius } from '../boxes';
import Baseline from '../baseline';
import { A, Button as NButton } from 'normalized-styled-components';
import { Link } from 'react-router-dom';
const StyledIconButton = styled.button`
const style = css`
border-radius: ${borderRadius};
border: solid ${remcalc(1)} ${props => props.theme.grey};
background-color: ${props => props.theme.white};
@ -13,13 +15,13 @@ const StyledIconButton = styled.button`
display: inline-block;
justify-content: center;
align-items: center;
margin-left: ${remcalc(6)};
padding: ${remcalc(15)} ${remcalc(18)};
position: relative;
text-align: center;
line-height: normal;
vertical-align: middle;
touch-action: manipulation;
min-width: 0;
cursor: pointer;
&:focus {
@ -67,6 +69,20 @@ const StyledIconButton = styled.button`
}
`;
const StyledButton = NButton.extend`
${style}
`;
const StyledAnchor = A.extend`
display: inline-block;
${style}
`;
const StyledLink = styled(Link)`
display: inline-block;
${style}
`;
const StyledDiv = styled.div`
height: ${remcalc(16)};
`;
@ -74,12 +90,32 @@ const StyledDiv = styled.div`
/**
* @example ./usage.md
*/
const IconButton = ({ children, onClick }) =>
<StyledIconButton onClick={onClick}>
const IconButton = props => {
const { href = '', to = '', children } = props;
const Views = [
() => (to ? StyledLink : null),
() => (href ? StyledAnchor : null),
() => StyledButton
];
const View = Views.reduce((sel, view) => (sel ? sel : view()), null);
return (
<View {...props}>
<StyledDiv>
{children}
</StyledDiv>
</StyledIconButton>;
</View>
);
};
/* ({ children, ...rest }) =>
<StyledIconButton {...rest}>
<StyledDiv>
{children}
</StyledDiv>
</StyledIconButton>; */
IconButton.propTypes = {
children: PropTypes.node,

View File

@ -0,0 +1,7 @@
import Baseline from '../baseline';
// eslint-disable-next-line no-unused-vars
import React from 'react';
import BinIcon from './svg/icon_bin.svg';
export default Baseline(BinIcon);

View File

@ -6,3 +6,4 @@ export { default as TickIcon } from './tick';
export { default as InstancesIcon } from './instances';
export { default as HealthyIcon } from './healthy';
export { default as UnhealthyIcon } from './unhealthy';
export { default as BinIcon } from './bin';

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="12px" height="17px" viewBox="0 0 12 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 45.2 (43514) - http://www.bohemiancoding.com/sketch -->
<title>icon: delete</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="home" transform="translate(-467.000000, -421.000000)" fill="#646464">
<path d="M471,421 L471,422 L467,422 L467,424 L479,424 L479,422 L475.001,422 L475.001,421 L471,421 Z M468,438 L478,438 L478,425 L468,425 L468,438 Z" id="icon:-delete"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 737 B

View File

@ -12,7 +12,7 @@ export { default as Small } from './text/small';
export { default as theme } from './theme';
export { default as typography, fonts } from './typography';
export { default as Topology } from './topology';
export { default as Modal } from './modal';
export { default as Modal, ModalHeading, ModalText } from './modal';
export { default as CloseButton } from './close-button';
export { default as IconButton } from './icon-button';
export { Tooltip, TooltipButton, TooltipDivider } from './tooltip';
@ -101,5 +101,6 @@ export {
ArrowIcon,
InstancesIcon,
HealthyIcon,
UnhealthyIcon
UnhealthyIcon,
BinIcon
} from './icons';

View File

@ -7,6 +7,8 @@ import theme from '../theme';
import { border, borderRadius, modalShadow } from '../boxes';
import Button from '../button';
import CloseButton from '../close-button';
import P from '../text/p';
import { H2 } from '../text/headings';
const StyledBackground = styled.div`
position: fixed;
@ -15,6 +17,7 @@ const StyledBackground = styled.div`
top: 0;
left: 0;
background-color: rgba(250, 250, 250, 0.5);
z-index: 100;
`;
const StyledModal = styled.div`
@ -27,6 +30,7 @@ const StyledModal = styled.div`
width: ${props => remcalc(props.width)};
margin: 0 auto 0 -${props => remcalc(props.width / 2)};
z-index: 101;
`;
// tmp
@ -66,3 +70,14 @@ Modal.propTypes = {
};
export default Modal;
export const ModalHeading = styled(H2)`
line-height: 1.25;
color: ${props => props.theme.secondary};
margin: 0 0 ${remcalc(12)} 0;
`;
export const ModalText = styled(P)`
color: ${props => props.theme.secondary};
margin: ${remcalc(12)} 0 ${remcalc(30)} 0;
`;