diff --git a/packages/cp-frontend/src/components/deployment-group/delete.js b/packages/cp-frontend/src/components/deployment-group/delete.js new file mode 100644 index 00000000..843a7466 --- /dev/null +++ b/packages/cp-frontend/src/components/deployment-group/delete.js @@ -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 }) => +
+ + Deleting a deployment group:
{deploymentGroup.name} +
+ + 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? + + + +
; + +DeploymentGroupDelete.propTypes = { + deploymentGroup: PropTypes.object, + onCancelClick: PropTypes.func.isRequired, + onConfirmClick: PropTypes.func.isRequired +}; + +export default DeploymentGroupDelete; diff --git a/packages/cp-frontend/src/components/deployment-group/index.js b/packages/cp-frontend/src/components/deployment-group/index.js new file mode 100644 index 00000000..b8cadaaa --- /dev/null +++ b/packages/cp-frontend/src/components/deployment-group/index.js @@ -0,0 +1 @@ +export { default as DeploymentGroupDelete } from './delete'; diff --git a/packages/cp-frontend/src/components/service/delete.js b/packages/cp-frontend/src/components/service/delete.js index ca40b6b3..c4a2b046 100644 --- a/packages/cp-frontend/src/components/service/delete.js +++ b/packages/cp-frontend/src/components/service/delete.js @@ -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, diff --git a/packages/cp-frontend/src/components/service/scale.js b/packages/cp-frontend/src/components/service/scale.js index cd67e8a2..8cb885bb 100644 --- a/packages/cp-frontend/src/components/service/scale.js +++ b/packages/cp-frontend/src/components/service/scale.js @@ -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 }) =>
- Scaling a service:
{service.name}
-

Choose how many instances of a service you want to have running.

+ Scaling a service:
{service.name}
+ Choose how many instances of a service you want to have running. ; + } + if (this.props.error) { + return ( + + ); + } + + 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 ( + + + + ); + } +} + +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; diff --git a/packages/cp-frontend/src/containers/deployment-group/index.js b/packages/cp-frontend/src/containers/deployment-group/index.js new file mode 100644 index 00000000..b8cadaaa --- /dev/null +++ b/packages/cp-frontend/src/containers/deployment-group/index.js @@ -0,0 +1 @@ +export { default as DeploymentGroupDelete } from './delete'; diff --git a/packages/cp-frontend/src/containers/deployment-groups/list.js b/packages/cp-frontend/src/containers/deployment-groups/list.js index ffba064b..7ca135c4 100644 --- a/packages/cp-frontend/src/containers/deployment-groups/list.js +++ b/packages/cp-frontend/src/containers/deployment-groups/list.js @@ -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 }) => - - {name} + + + {name} + + + + ); diff --git a/packages/cp-frontend/src/graphql/DeploymentGroup.gql b/packages/cp-frontend/src/graphql/DeploymentGroup.gql new file mode 100644 index 00000000..ac896c7e --- /dev/null +++ b/packages/cp-frontend/src/graphql/DeploymentGroup.gql @@ -0,0 +1,7 @@ +#import "./DeploymentGroupInfo.gql" + +query DeploymentGroup($deploymentGroupSlug: String!) { + deploymentGroup(slug: $deploymentGroupSlug) { + ...DeploymentGroupInfo + } +} diff --git a/packages/cp-frontend/src/graphql/DeploymentGroupsDeleteMutation.gql b/packages/cp-frontend/src/graphql/DeploymentGroupsDeleteMutation.gql new file mode 100644 index 00000000..68f366d6 --- /dev/null +++ b/packages/cp-frontend/src/graphql/DeploymentGroupsDeleteMutation.gql @@ -0,0 +1,7 @@ +#import "./DeploymentGroupInfo.gql" + +mutation DeleteDeploymentGroups($ids: [ID]!) { + deleteDeploymentGroups(ids: $ids) { + ...DeploymentGroupInfo + } +} diff --git a/packages/cp-frontend/src/router.js b/packages/cp-frontend/src/router.js index 1c70d0b4..ce2e9020 100644 --- a/packages/cp-frontend/src/router.js +++ b/packages/cp-frontend/src/router.js @@ -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,15 +61,21 @@ const Router = ( + - + + .join(';\n'); export default Component => - Component.extend + /* Component.extend ? Component.extend`${alignsFromProps}` - : styled(Component)`${alignsFromProps}`; + : */ styled(Component)`${alignsFromProps}`; diff --git a/packages/ui-toolkit/src/form/number-input.js b/packages/ui-toolkit/src/form/number-input.js index 040597f3..c02bc2f9 100644 --- a/packages/ui-toolkit/src/form/number-input.js +++ b/packages/ui-toolkit/src/form/number-input.js @@ -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 ( - + - + diff --git a/packages/ui-toolkit/src/icon-button/index.js b/packages/ui-toolkit/src/icon-button/index.js index b6a0c661..c660bcef 100644 --- a/packages/ui-toolkit/src/icon-button/index.js +++ b/packages/ui-toolkit/src/icon-button/index.js @@ -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 }) => - +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 ( + + + {children} + + + ); +}; + +/* ({ children, ...rest }) => + {children} - ; + ; */ IconButton.propTypes = { children: PropTypes.node, diff --git a/packages/ui-toolkit/src/icons/bin.js b/packages/ui-toolkit/src/icons/bin.js new file mode 100644 index 00000000..5b8b8357 --- /dev/null +++ b/packages/ui-toolkit/src/icons/bin.js @@ -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); diff --git a/packages/ui-toolkit/src/icons/index.js b/packages/ui-toolkit/src/icons/index.js index 63a0dd2a..542f00b2 100644 --- a/packages/ui-toolkit/src/icons/index.js +++ b/packages/ui-toolkit/src/icons/index.js @@ -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'; diff --git a/packages/ui-toolkit/src/icons/svg/icon_bin.svg b/packages/ui-toolkit/src/icons/svg/icon_bin.svg new file mode 100644 index 00000000..dc6ed75e --- /dev/null +++ b/packages/ui-toolkit/src/icons/svg/icon_bin.svg @@ -0,0 +1,12 @@ + + + + icon: delete + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/packages/ui-toolkit/src/index.js b/packages/ui-toolkit/src/index.js index c341b118..94db9eac 100644 --- a/packages/ui-toolkit/src/index.js +++ b/packages/ui-toolkit/src/index.js @@ -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'; diff --git a/packages/ui-toolkit/src/modal/index.js b/packages/ui-toolkit/src/modal/index.js index f8d0cd5b..580953e4 100644 --- a/packages/ui-toolkit/src/modal/index.js +++ b/packages/ui-toolkit/src/modal/index.js @@ -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; +`;