mirror of
https://github.com/yldio/copilot.git
synced 2024-11-28 06:00:06 +02:00
feat(ui-toolkit, cp-frontend): Add clear status and health messaging and refactor tooltips use
This commit is contained in:
parent
24bee629e8
commit
bc026b2341
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import remcalc from 'remcalc';
|
||||
import styled from 'styled-components';
|
||||
@ -7,10 +7,16 @@ import titleCase from 'title-case';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardInfo,
|
||||
CardView,
|
||||
CardMeta,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
HealthyIcon,
|
||||
Tooltip,
|
||||
TooltipLabel,
|
||||
P,
|
||||
Label,
|
||||
typography
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
@ -29,31 +35,18 @@ const STATUSES = [
|
||||
'UNKNOWN'
|
||||
];
|
||||
|
||||
const Span = styled.span`
|
||||
${typography.fontFamily};
|
||||
${typography.normal};
|
||||
`;
|
||||
|
||||
const Dot = styled.div`
|
||||
const Dot = styled.span`
|
||||
margin-right: ${remcalc(6)};
|
||||
width: ${remcalc(6)};
|
||||
height: ${remcalc(6)};
|
||||
border-radius: ${remcalc(3)};
|
||||
display: inline-block;
|
||||
|
||||
${isOr('provisioning', 'ready', 'active')`
|
||||
background-color: ${props => props.theme.primary};
|
||||
`};
|
||||
|
||||
${is('running')`
|
||||
${isOr('provisioning', 'ready', 'active', 'running')`
|
||||
background-color: ${props => props.theme.green};
|
||||
`};
|
||||
|
||||
${is('stopping')`
|
||||
background-color: orange;
|
||||
`};
|
||||
|
||||
${is('stopped')`
|
||||
${isOr('stopping', 'stopped')`
|
||||
background-color: ${props => props.theme.grey};
|
||||
`};
|
||||
|
||||
@ -66,23 +59,6 @@ const Dot = styled.div`
|
||||
`};
|
||||
`;
|
||||
|
||||
const StatusBadge = ({ status }) => {
|
||||
const props = STATUSES.reduce(
|
||||
(acc, name) =>
|
||||
Object.assign(acc, {
|
||||
[name.toLowerCase()]: name === status
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
return (
|
||||
<Span>
|
||||
<Dot {...props} />
|
||||
{titleCase(status)}
|
||||
</Span>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledCard = Card.extend`
|
||||
flex-direction: row;
|
||||
|
||||
@ -92,37 +68,91 @@ const StyledCard = Card.extend`
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: ${props => props.theme.white};
|
||||
|
||||
${isOr('stopping', 'stopped', 'offline', 'destroyed', 'failed', 'deleted', 'incomplete', 'unknown')`
|
||||
background-color: ${props => props.theme.background};
|
||||
|
||||
& [name="card-options"] > button {
|
||||
background-color: ${props => props.theme.background};
|
||||
}
|
||||
}`
|
||||
}
|
||||
`;
|
||||
|
||||
const InstanceCard = ({
|
||||
instance = {},
|
||||
onOptionsClick = () => null,
|
||||
toggleCollapsed = () => null
|
||||
}) =>
|
||||
<StyledCard collapsed={true} key={instance.uuid}>
|
||||
toggleCollapsed = () => null,
|
||||
onHealthMouseOver,
|
||||
onStatusMouseOver,
|
||||
onMouseOut
|
||||
}) => {
|
||||
|
||||
const statusProps = STATUSES.reduce(
|
||||
(acc, name) =>
|
||||
Object.assign(acc, {
|
||||
[name.toLowerCase()]: name === instance.status
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
const label = instance.healthy.toLowerCase();
|
||||
const icon = <HealthyIcon healthy={instance.healthy} />;
|
||||
|
||||
const handleHealthMouseOver = (evt) => {
|
||||
onHealthMouseOver(evt, instance);
|
||||
}
|
||||
|
||||
const handleStatusMouseOver = (evt) => {
|
||||
onStatusMouseOver(evt, instance);
|
||||
}
|
||||
|
||||
const handleMouseOut = (evt) => {
|
||||
onMouseOut(evt);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledCard collapsed={true} key={instance.uuid} {...statusProps}>
|
||||
<CardView>
|
||||
<CardMeta onClick={toggleCollapsed}>
|
||||
<CardTitle>
|
||||
{instance.name}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<StatusBadge status={instance.status} />
|
||||
<div
|
||||
onMouseOver={handleHealthMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
>
|
||||
<CardInfo
|
||||
icon={icon}
|
||||
iconPosition='left'
|
||||
label={label}
|
||||
color='dark'
|
||||
/>
|
||||
</div>
|
||||
</CardDescription>
|
||||
<CardDescription>
|
||||
<div
|
||||
onMouseOver={handleStatusMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
>
|
||||
<Label>
|
||||
<Dot {...statusProps} />
|
||||
{titleCase(instance.status)}
|
||||
</Label>
|
||||
</div>
|
||||
</CardDescription>
|
||||
</CardMeta>
|
||||
</CardView>
|
||||
</StyledCard>;
|
||||
</StyledCard>
|
||||
)
|
||||
};
|
||||
|
||||
InstanceCard.propTypes = {
|
||||
instance: PropTypes.object,
|
||||
onOptionsClick: PropTypes.func,
|
||||
toggleCollapsed: PropTypes.func
|
||||
toggleCollapsed: PropTypes.func,
|
||||
onHealthMouseOver: PropTypes.func,
|
||||
onStatusMouseOver: PropTypes.func,
|
||||
onMouseOut: PropTypes.func
|
||||
};
|
||||
|
||||
export default InstanceCard;
|
||||
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import forceArray from 'force-array';
|
||||
import sortBy from 'lodash.sortby';
|
||||
import { isNot } from 'styled-is';
|
||||
|
||||
import { InstancesIcon, HealthyIcon, UnhealthyIcon } from 'joyent-ui-toolkit';
|
||||
import Status from './status';
|
||||
@ -27,10 +28,16 @@ const StyledCardHeader = styled(CardHeader)`
|
||||
const TitleInnerContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const StyledAnchor = styled(Anchor)`
|
||||
${isNot('active')`
|
||||
color: ${props => props.theme.text}
|
||||
`};
|
||||
`;
|
||||
|
||||
const ServiceListItem = ({
|
||||
onQuickActionsClick = () => {},
|
||||
deploymentGroup = '',
|
||||
@ -42,7 +49,7 @@ const ServiceListItem = ({
|
||||
};
|
||||
|
||||
const children = sortBy(forceArray(service.children), ['slug']);
|
||||
const isServiceInactive = service.status && service.status !== 'ACTIVE';
|
||||
// const isServiceInactive = service.status && service.status !== 'ACTIVE';
|
||||
const to = `/deployment-groups/${deploymentGroup}/services/${service.slug}`;
|
||||
|
||||
const instancesCount = children.length
|
||||
@ -64,9 +71,9 @@ const ServiceListItem = ({
|
||||
</CardTitle>
|
||||
: <CardTitle>
|
||||
<TitleInnerContainer>
|
||||
<Anchor to={to} disabled={isServiceInactive} secondary>
|
||||
<StyledAnchor to={to} secondary active={service.instancesActive}>
|
||||
{service.name}
|
||||
</Anchor>
|
||||
</StyledAnchor>
|
||||
</TitleInnerContainer>
|
||||
</CardTitle>;
|
||||
|
||||
@ -87,28 +94,29 @@ const ServiceListItem = ({
|
||||
label={`${instancesCount} ${instancesCount > 1
|
||||
? 'instances'
|
||||
: 'instance'}`}
|
||||
color={isServiceInactive ? 'disabled' : 'light'}
|
||||
color={!service.instancesActive ? 'disabled' : 'light'}
|
||||
/>
|
||||
</CardDescription>
|
||||
<CardOptions onClick={handleCardOptionsClick} />
|
||||
</StyledCardHeader>
|
||||
: null;
|
||||
|
||||
const healthyInfo = isServiceInactive
|
||||
? null
|
||||
: service.instancesHealthy
|
||||
? <CardInfo
|
||||
icon={<HealthyIcon />}
|
||||
iconPosition="left"
|
||||
label="Healthy"
|
||||
color="dark"
|
||||
let healthyInfo = null;
|
||||
if(service.instancesActive) {
|
||||
const { total, healthy } = service.instancesHealthy;
|
||||
const iconHealthy = total === healthy ? 'HEALTHY' : 'NOT HEALTHY';
|
||||
const icon = <HealthyIcon healthy={iconHealthy} />;
|
||||
const label = `${healthy} of ${total} healthy`;
|
||||
|
||||
healthyInfo = (
|
||||
<CardInfo
|
||||
icon={icon}
|
||||
iconPosition='left'
|
||||
label={label}
|
||||
color='dark'
|
||||
/>
|
||||
: <CardInfo
|
||||
icon={<UnhealthyIcon />}
|
||||
iconPosition="left"
|
||||
label="Unhealthy"
|
||||
color="dark"
|
||||
/>;
|
||||
)
|
||||
}
|
||||
|
||||
const view = childrenItems.length
|
||||
? <CardGroupView>
|
||||
@ -126,7 +134,7 @@ const ServiceListItem = ({
|
||||
return (
|
||||
<Card
|
||||
collapsed={service.collapsed}
|
||||
disabled={isServiceInactive}
|
||||
active={service.instancesActive}
|
||||
flat={isChild}
|
||||
headed={!isChild}
|
||||
key={service.id}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Tooltip, TooltipButton, TooltipDivider } from 'joyent-ui-toolkit';
|
||||
import { Tooltip, TooltipButton, TooltipDivider, TooltipList } from 'joyent-ui-toolkit';
|
||||
|
||||
const ServicesQuickActions = ({
|
||||
show,
|
||||
@ -18,14 +18,14 @@ const ServicesQuickActions = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const p = Object.keys(position).reduce((p, key) => {
|
||||
/* const p = Object.keys(position).reduce((p, key) => {
|
||||
if (typeof position[key] === 'number') {
|
||||
p[key] = `${position[key]}px`;
|
||||
} else {
|
||||
p[key] = position[key];
|
||||
}
|
||||
return p;
|
||||
}, {});
|
||||
}, {}); */
|
||||
|
||||
const handleRestartClick = evt => {
|
||||
onRestartClick(evt, service);
|
||||
@ -58,31 +58,43 @@ const ServicesQuickActions = ({
|
||||
const startService =
|
||||
status === 'RUNNING'
|
||||
? null
|
||||
: <TooltipButton onClick={handleStartClick} disabled={disabled}>
|
||||
: <li>
|
||||
<TooltipButton onClick={handleStartClick} disabled={disabled}>
|
||||
Start
|
||||
</TooltipButton>;
|
||||
</TooltipButton>
|
||||
</li>;
|
||||
|
||||
const stopService =
|
||||
status === 'STOPPED'
|
||||
? null
|
||||
: <TooltipButton onClick={handleStopClick} disabled={disabled}>
|
||||
: <li>
|
||||
<TooltipButton onClick={handleStopClick} disabled={disabled}>
|
||||
Stop
|
||||
</TooltipButton>;
|
||||
</TooltipButton>
|
||||
</li>;
|
||||
|
||||
return (
|
||||
<Tooltip {...p} onBlur={onBlur}>
|
||||
<Tooltip {...position} onBlur={onBlur}>
|
||||
<TooltipList>
|
||||
<li>
|
||||
<TooltipButton onClick={handleScaleClick} disabled={disabled}>
|
||||
Scale
|
||||
</TooltipButton>
|
||||
</li>
|
||||
<li>
|
||||
<TooltipButton onClick={handleRestartClick} disabled={disabled}>
|
||||
Restart
|
||||
</TooltipButton>
|
||||
</li>
|
||||
{startService}
|
||||
{stopService}
|
||||
<TooltipDivider />
|
||||
<li>
|
||||
<TooltipButton onClick={handleDeleteClick} disabled={disabled}>
|
||||
Delete
|
||||
</TooltipButton>
|
||||
</li>
|
||||
</TooltipList>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
@ -6,13 +6,13 @@ import { StatusLoader, P } from 'joyent-ui-toolkit';
|
||||
|
||||
const StyledStatusContainer = styled.div`
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
margin: 0 0 ${remcalc(15)} 0;
|
||||
height: ${remcalc(54)};
|
||||
width: ${remcalc(200)};
|
||||
`;
|
||||
|
||||
const StyledStatus = P.extend`
|
||||
margin: 0;
|
||||
margin: 0 0 ${remcalc(6)} 0;
|
||||
font-size: ${remcalc(13)};
|
||||
line-height: ${remcalc(13)};
|
||||
`;
|
||||
|
@ -1 +1,2 @@
|
||||
export { default as InstanceList } from './list';
|
||||
export { default as InstancesTooltip } from './tooltip';
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import { connect } from 'react-redux';
|
||||
import InstancesQuery from '@graphql/Instances.gql';
|
||||
import forceArray from 'force-array';
|
||||
import sortBy from 'lodash.sortby';
|
||||
@ -8,10 +10,18 @@ import { LayoutContainer } from '@components/layout';
|
||||
import { Title } from '@components/navigation';
|
||||
import { Loader, ErrorMessage } from '@components/messaging';
|
||||
import { InstanceListItem, EmptyInstances } from '@components/instances';
|
||||
|
||||
import { toggleInstancesTooltip } from '@root/state/actions';
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
|
||||
const InstanceList = ({ deploymentGroup, instances = [], loading, error }) => {
|
||||
const InstanceList = ({
|
||||
deploymentGroup,
|
||||
instances = [],
|
||||
loading,
|
||||
error,
|
||||
instancesTooltip,
|
||||
toggleInstancesTooltip
|
||||
}) => {
|
||||
|
||||
const _title = <Title>Instances</Title>;
|
||||
|
||||
if (loading && !forceArray(instances).length) {
|
||||
@ -44,11 +54,48 @@ const InstanceList = ({ deploymentGroup, instances = [], loading, error }) => {
|
||||
);
|
||||
}
|
||||
|
||||
const handleHealthMouseOver = (evt, instance) => {
|
||||
handleMouseOver(evt, instance, 'healthy');
|
||||
};
|
||||
|
||||
const handleStatusMouseOver = (evt, instance) => {
|
||||
handleMouseOver(evt, instance, 'status');
|
||||
};
|
||||
|
||||
const handleMouseOver = (evt, instance, type) => {
|
||||
|
||||
const label = evt.currentTarget;
|
||||
const labelRect = label.getBoundingClientRect();
|
||||
const offset = type === 'healthy'
|
||||
? 48 : type === 'status' ? 36 : 0;
|
||||
|
||||
const position = {
|
||||
left:
|
||||
`${window.scrollX + labelRect.left + offset}px`,
|
||||
top: `${window.scrollY + labelRect.bottom}px`
|
||||
};
|
||||
|
||||
const tooltipData = {
|
||||
instance,
|
||||
position,
|
||||
type
|
||||
}
|
||||
|
||||
toggleInstancesTooltip(tooltipData);
|
||||
};
|
||||
|
||||
const handleMouseOut = (evt) => {
|
||||
toggleInstancesTooltip({ show: false });
|
||||
};
|
||||
|
||||
const instanceList = instances.map((instance, index) =>
|
||||
<InstanceListItem
|
||||
instance={instance}
|
||||
key={instance.id}
|
||||
toggleCollapsed={() => null}
|
||||
onHealthMouseOver={handleHealthMouseOver}
|
||||
onStatusMouseOver={handleStatusMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -60,7 +107,17 @@ const InstanceList = ({ deploymentGroup, instances = [], loading, error }) => {
|
||||
{_instances}
|
||||
</LayoutContainer>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
instancesTooltip: state.ui.instances.tooltip
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleInstancesTooltip: data => dispatch(toggleInstancesTooltip(data))
|
||||
});
|
||||
|
||||
const UiConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
const InstanceListGql = graphql(InstancesQuery, {
|
||||
options(props) {
|
||||
@ -94,6 +151,7 @@ const InstanceListGql = graphql(InstancesQuery, {
|
||||
});
|
||||
|
||||
export default compose(
|
||||
UiConnect,
|
||||
InstanceListGql,
|
||||
withNotFound([
|
||||
GqlPaths.DEPLOYMENT_GROUP,
|
||||
|
68
packages/cp-frontend/src/containers/instances/tooltip.js
Normal file
68
packages/cp-frontend/src/containers/instances/tooltip.js
Normal file
@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import { Tooltip, TooltipLabel } from 'joyent-ui-toolkit';
|
||||
import { ServicesQuickActions } from '@components/services';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
`;
|
||||
|
||||
const healthMessages = {
|
||||
healthy: 'Your instance is operating as expected',
|
||||
unhealthy: 'Your instance is not operating as expected',
|
||||
maintenance: 'You\'ve set your instance to this manually, use the Container Pilot CLI to change',
|
||||
unknown: 'We\'ve connected to your instance but we have no health information',
|
||||
unavailable: 'We cannot connect to your instance',
|
||||
};
|
||||
|
||||
const statusMessages = {
|
||||
running: 'Your instance is operating',
|
||||
provisioning: 'Your instance is downloading dependencies and compiling',
|
||||
ready: 'Your instance finished provisioning and is ready to be run, it\'ll be running soon',
|
||||
stopping: 'Your instance is going to be stopped soon',
|
||||
stopped: 'Your instance isn\'t doing anything, you can start it',
|
||||
offline: 'We have no idea what this means, do you??????',
|
||||
failed: 'Your instance has crashed',
|
||||
unknown: 'We cannot work out what status your instance is in',
|
||||
};
|
||||
|
||||
const InstancesTooltip = ({
|
||||
instancesTooltip
|
||||
}) => {
|
||||
|
||||
if(instancesTooltip.show) {
|
||||
const {
|
||||
type,
|
||||
instance
|
||||
} = instancesTooltip;
|
||||
|
||||
const message = type === 'healthy'
|
||||
? healthMessages[instance.healthy.toLowerCase()]
|
||||
: type === 'status'
|
||||
? statusMessages[instance.status.toLowerCase()]
|
||||
: '';
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<Tooltip {...instancesTooltip.position} secondary>
|
||||
<TooltipLabel>{message}</TooltipLabel>
|
||||
</Tooltip>
|
||||
</StyledContainer>
|
||||
)
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
instancesTooltip: state.ui.instances.tooltip
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({});
|
||||
|
||||
const UiConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export default UiConnect(InstancesTooltip);
|
@ -1,3 +1,4 @@
|
||||
export { default as ServiceList } from './list';
|
||||
export { default as ServicesTopology } from './topology';
|
||||
export { default as ServicesMenu } from './menu';
|
||||
export { default as ServicesQuickActions } from './quick-actions';
|
||||
|
@ -6,21 +6,14 @@ import forceArray from 'force-array';
|
||||
import sortBy from 'lodash.sortby';
|
||||
|
||||
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';
|
||||
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
import { LayoutContainer } from '@components/layout';
|
||||
import { Loader, ErrorMessage } from '@components/messaging';
|
||||
import { ServiceListItem } from '@components/services';
|
||||
|
||||
import { ServicesQuickActions } from '@components/services';
|
||||
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
@ -34,28 +27,13 @@ class ServiceList extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
ref(name) {
|
||||
this._refs = this._refs || {};
|
||||
|
||||
return el => {
|
||||
this._refs[name] = el;
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
deploymentGroup,
|
||||
services,
|
||||
loading,
|
||||
error,
|
||||
servicesQuickActions,
|
||||
toggleServicesQuickActions,
|
||||
url,
|
||||
push,
|
||||
restartServices,
|
||||
stopServices,
|
||||
startServices,
|
||||
location
|
||||
} = this.props;
|
||||
|
||||
if (loading && !forceArray(services).length) {
|
||||
@ -90,17 +68,13 @@ class ServiceList extends Component {
|
||||
}
|
||||
|
||||
const handleQuickActionsClick = (evt, service) => {
|
||||
const list = this._refs.container;
|
||||
const listRect = list.getBoundingClientRect();
|
||||
const button = evt.currentTarget;
|
||||
const buttonRect = button.getBoundingClientRect();
|
||||
|
||||
const position = {
|
||||
left:
|
||||
buttonRect.left -
|
||||
listRect.left +
|
||||
(buttonRect.right - buttonRect.left) / 2,
|
||||
top: buttonRect.bottom - listRect.top
|
||||
`${buttonRect.left + window.scrollX + (buttonRect.right - buttonRect.left) / 2}px`,
|
||||
top: `${buttonRect.bottom + window.scrollY}px`
|
||||
};
|
||||
|
||||
toggleServicesQuickActions({
|
||||
@ -109,41 +83,6 @@ class ServiceList extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
const handleRestartClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
restartServices(service.id).catch(err => {
|
||||
this.setState({ errors: { restart: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStopClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
stopServices(service.id).catch(err => {
|
||||
this.setState({ errors: { stop: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStartClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
startServices(service.id).catch(err => {
|
||||
this.setState({ errors: { start: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleScaleClick = (evt, service) => {
|
||||
toggleServicesQuickActions({ show: false });
|
||||
push(`${url}/${service.slug}/scale`);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (evt, service) => {
|
||||
toggleServicesQuickActions({ show: false });
|
||||
push(`${url}/${service.slug}/delete`);
|
||||
};
|
||||
|
||||
const handleQuickActionsBlur = o => {
|
||||
toggleServicesQuickActions({ show: false });
|
||||
};
|
||||
|
||||
let renderedError = null;
|
||||
|
||||
if (
|
||||
@ -181,31 +120,14 @@ class ServiceList extends Component {
|
||||
<LayoutContainer>
|
||||
{renderedError}
|
||||
<StyledContainer>
|
||||
<div ref={this.ref('container')}>
|
||||
{serviceList}
|
||||
<ServicesQuickActions
|
||||
position={servicesQuickActions.position}
|
||||
service={servicesQuickActions.service}
|
||||
show={servicesQuickActions.show}
|
||||
onBlur={handleQuickActionsBlur}
|
||||
onRestartClick={handleRestartClick}
|
||||
onStopClick={handleStopClick}
|
||||
onStartClick={handleStartClick}
|
||||
onScaleClick={handleScaleClick}
|
||||
onDeleteClick={handleDeleteClick}
|
||||
/>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
servicesQuickActions: state.ui.services.quickActions,
|
||||
url: ownProps.match.url.replace(/\/$/, ''),
|
||||
push: ownProps.history.push
|
||||
});
|
||||
const mapStateToProps = (state, ownProps) => ({});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleServicesQuickActions: data => dispatch(toggleServicesQuickActions(data))
|
||||
@ -232,29 +154,7 @@ const ServicesGql = graphql(ServicesQuery, {
|
||||
})
|
||||
});
|
||||
|
||||
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,
|
||||
ServicesRestartGql,
|
||||
ServicesStopGql,
|
||||
ServicesStartGql,
|
||||
ServicesGql,
|
||||
UiConnect,
|
||||
withNotFound([ GqlPaths.DEPLOYMENT_GROUP ])
|
||||
|
172
packages/cp-frontend/src/containers/services/quick-actions.js
Normal file
172
packages/cp-frontend/src/containers/services/quick-actions.js
Normal file
@ -0,0 +1,172 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import styled from 'styled-components';
|
||||
import ServicesRestartMutation from '@graphql/ServicesRestartMutation.gql';
|
||||
import ServicesStopMutation from '@graphql/ServicesStopMutation.gql';
|
||||
import ServicesStartMutation from '@graphql/ServicesStartMutation.gql';
|
||||
import { Tooltip, TooltipLabel } from 'joyent-ui-toolkit';
|
||||
import { toggleServicesQuickActions } from '@root/state/actions';
|
||||
import { ServicesQuickActions as QuickActions } from '@components/services';
|
||||
import { ErrorMessage } from '@components/messaging';
|
||||
import { LayoutContainer } from '@components/layout';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
`;
|
||||
|
||||
class ServicesQuickActions extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
errors: {}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
servicesQuickActions,
|
||||
toggleServicesQuickActions,
|
||||
restartServices,
|
||||
stopServices,
|
||||
startServices,
|
||||
url,
|
||||
push
|
||||
} = this.props;
|
||||
|
||||
let errorMessage = null;
|
||||
let quickActions = null;
|
||||
|
||||
if (
|
||||
this.state.errors.stop ||
|
||||
this.state.errors.start ||
|
||||
this.state.errors.restart
|
||||
) {
|
||||
const message = this.state.errors.stop
|
||||
? 'An error occurred while attempting to stop your service.'
|
||||
: this.state.errors.start
|
||||
? 'An error occurred while attempting to start your service.'
|
||||
: this.state.errors.restart
|
||||
? 'An error occurred while attempting to restart your service.'
|
||||
: '';
|
||||
|
||||
errorMessage = (
|
||||
<LayoutContainer>
|
||||
<ErrorMessage title="Ooops!" message={message} />
|
||||
</LayoutContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if(servicesQuickActions.show) {
|
||||
const handleTooltipBlur = evt => {
|
||||
toggleServicesQuickActions({ show: false });
|
||||
};
|
||||
|
||||
const handleRestartClick = (evt, service) => {
|
||||
this.setState({errors: {}});
|
||||
toggleServicesQuickActions({ show: false });
|
||||
restartServices(service.id).catch(err => {
|
||||
this.setState({ errors: { restart: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStopClick = (evt, service) => {
|
||||
this.setState({errors: {}});
|
||||
toggleServicesQuickActions({ show: false });
|
||||
stopServices(service.id).catch(err => {
|
||||
this.setState({ errors: { stop: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStartClick = (evt, service) => {
|
||||
this.setState({errors: {}});
|
||||
toggleServicesQuickActions({ show: false });
|
||||
startServices(service.id).catch(err => {
|
||||
this.setState({ errors: { start: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleScaleClick = (evt, service) => {
|
||||
this.setState({errors: {}});
|
||||
toggleServicesQuickActions({ show: false });
|
||||
push(`${url}/${service.slug}/scale`);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (evt, service) => {
|
||||
this.setState({errors: {}});
|
||||
toggleServicesQuickActions({ show: false });
|
||||
push(`${url}/${service.slug}/delete`);
|
||||
};
|
||||
|
||||
quickActions = (
|
||||
<StyledContainer>
|
||||
<QuickActions
|
||||
service={servicesQuickActions.service}
|
||||
show={servicesQuickActions.show}
|
||||
position={servicesQuickActions.position}
|
||||
onBlur={handleTooltipBlur}
|
||||
onRestartClick={handleRestartClick}
|
||||
onStopClick={handleStopClick}
|
||||
onStartClick={handleStartClick}
|
||||
onScaleClick={handleScaleClick}
|
||||
onDeleteClick={handleDeleteClick}
|
||||
/>
|
||||
</StyledContainer>
|
||||
)
|
||||
}
|
||||
|
||||
if(quickActions || errorMessage) {
|
||||
return (
|
||||
<div>
|
||||
{errorMessage}
|
||||
{quickActions}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
servicesQuickActions: state.ui.services.quickActions,
|
||||
url: ownProps.match.url.replace(/\/$/, ''),
|
||||
push: ownProps.history.push
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleServicesQuickActions: data => dispatch(toggleServicesQuickActions(data))
|
||||
});
|
||||
|
||||
const UiConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
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 ConnectedServicesQuickActions = compose(
|
||||
ServicesRestartGql,
|
||||
ServicesStopGql,
|
||||
ServicesStartGql,
|
||||
UiConnect
|
||||
)(ServicesQuickActions);
|
||||
|
||||
export default ConnectedServicesQuickActions;
|
@ -3,23 +3,16 @@ import { compose, graphql } from 'react-apollo';
|
||||
import { connect } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import forceArray from 'force-array';
|
||||
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 unitcalc from 'unitcalc';
|
||||
|
||||
import ServicesQuery from '@graphql/Services.gql';
|
||||
import { processServicesForTopology } from '@root/state/selectors';
|
||||
import { toggleServicesQuickActions } from '@root/state/actions';
|
||||
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
import { LayoutContainer } from '@components/layout';
|
||||
import { Loader, ErrorMessage } from '@components/messaging';
|
||||
import { ServicesQuickActions } from '@components/services';
|
||||
|
||||
import { Topology } from 'joyent-ui-toolkit';
|
||||
|
||||
import { withNotFound, GqlPaths } from '@containers/navigation';
|
||||
|
||||
const StyledBackground = styled.div`
|
||||
padding: ${unitcalc(4)};
|
||||
background-color: ${props => props.theme.whiteActive};
|
||||
@ -38,6 +31,14 @@ class ServicesTopology extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
ref(name) {
|
||||
this._refs = this._refs || {};
|
||||
|
||||
return el => {
|
||||
this._refs[name] = el;
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
url,
|
||||
@ -46,12 +47,7 @@ class ServicesTopology extends Component {
|
||||
services,
|
||||
loading,
|
||||
error,
|
||||
servicesQuickActions,
|
||||
toggleServicesQuickActions,
|
||||
restartServices,
|
||||
stopServices,
|
||||
startServices,
|
||||
location
|
||||
} = this.props;
|
||||
|
||||
if (loading && !forceArray(services).length) {
|
||||
@ -86,42 +82,17 @@ class ServicesTopology extends Component {
|
||||
}
|
||||
|
||||
const handleQuickActionsClick = (evt, tooltipData) => {
|
||||
toggleServicesQuickActions(tooltipData);
|
||||
};
|
||||
|
||||
const handleTooltipBlur = evt => {
|
||||
toggleServicesQuickActions({ show: false });
|
||||
};
|
||||
|
||||
const handleRestartClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
restartServices(service.id).catch(err => {
|
||||
this.setState({ errors: { restart: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStopClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
stopServices(service.id).catch(err => {
|
||||
this.setState({ errors: { stop: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleStartClick = (evt, service) => {
|
||||
this.setState({ errors: {} });
|
||||
startServices(service.id).catch(err => {
|
||||
this.setState({ errors: { start: err } });
|
||||
});
|
||||
};
|
||||
|
||||
const handleScaleClick = (evt, service) => {
|
||||
toggleServicesQuickActions({ show: false });
|
||||
push(`${url}/${service.slug}/scale`);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (evt, service) => {
|
||||
toggleServicesQuickActions({ show: false });
|
||||
push(`${url}/${service.slug}/delete`);
|
||||
const container = this._refs.container;
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const position = {
|
||||
top: `${containerRect.top + window.scrollY + tooltipData.position.top}px`,
|
||||
left: `${containerRect.left + window.scrollX + tooltipData.position.left}px`
|
||||
}
|
||||
const data = {
|
||||
...tooltipData,
|
||||
position
|
||||
}
|
||||
toggleServicesQuickActions(data);
|
||||
};
|
||||
|
||||
const handleNodeTitleClick = (evt, { service }) => {
|
||||
@ -155,22 +126,13 @@ class ServicesTopology extends Component {
|
||||
{renderedError}
|
||||
<StyledBackground>
|
||||
<StyledContainer>
|
||||
<div ref={this.ref('container')}>
|
||||
<Topology
|
||||
services={services}
|
||||
onQuickActionsClick={handleQuickActionsClick}
|
||||
onNodeTitleClick={handleNodeTitleClick}
|
||||
/>
|
||||
<ServicesQuickActions
|
||||
service={servicesQuickActions.service}
|
||||
show={servicesQuickActions.show}
|
||||
position={servicesQuickActions.position}
|
||||
onBlur={handleTooltipBlur}
|
||||
onRestartClick={handleRestartClick}
|
||||
onStopClick={handleStopClick}
|
||||
onStartClick={handleStartClick}
|
||||
onScaleClick={handleScaleClick}
|
||||
onDeleteClick={handleDeleteClick}
|
||||
/>
|
||||
</div>
|
||||
</StyledContainer>
|
||||
</StyledBackground>
|
||||
</div>
|
||||
@ -179,7 +141,6 @@ class ServicesTopology extends Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
servicesQuickActions: state.ui.services.quickActions,
|
||||
url: ownProps.match.url.replace(/\/$/, ''),
|
||||
push: ownProps.history.push
|
||||
});
|
||||
@ -209,28 +170,7 @@ const ServicesGql = graphql(ServicesQuery, {
|
||||
})
|
||||
});
|
||||
|
||||
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,
|
||||
withNotFound([ GqlPaths.DEPLOYMENT_GROUP ])
|
||||
|
@ -4,7 +4,6 @@ import styled from 'styled-components';
|
||||
|
||||
import { Header, Breadcrumb, Menu } from '@containers/navigation';
|
||||
import { ServiceScale, ServiceDelete } from '@containers/service';
|
||||
import { InstanceList } from '@containers/instances';
|
||||
import Manifest from '@containers/manifest';
|
||||
import Environment from '@containers/environment';
|
||||
|
||||
@ -17,9 +16,19 @@ import {
|
||||
import {
|
||||
ServiceList,
|
||||
ServicesTopology,
|
||||
ServicesMenu
|
||||
ServicesMenu,
|
||||
ServicesQuickActions
|
||||
} from '@containers/services';
|
||||
|
||||
import {
|
||||
InstanceList,
|
||||
InstancesTooltip
|
||||
} from '@containers/instances';
|
||||
|
||||
import {
|
||||
Tooltip
|
||||
} from '@containers/tooltip';
|
||||
|
||||
import { DeploymentGroupDelete } from '@containers/deployment-group';
|
||||
|
||||
import { NotFound } from '@components/navigation';
|
||||
@ -88,6 +97,28 @@ const App = p =>
|
||||
component={ServicesMenu}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/deployment-groups/:deploymentGroup/services-list"
|
||||
component={ServicesQuickActions}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/deployment-groups/:deploymentGroup/services-topology"
|
||||
component={ServicesQuickActions}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/deployment-groups/:deploymentGroup/instances"
|
||||
exact
|
||||
component={InstancesTooltip}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/deployment-groups/:deploymentGroup/services/:service/instances"
|
||||
exact
|
||||
component={InstancesTooltip}
|
||||
/>
|
||||
|
||||
<Switch>
|
||||
<Route
|
||||
path="/deployment-groups/:deploymentGroup/delete"
|
||||
|
@ -8,3 +8,7 @@ const APP = constantCase(process.env.APP_NAME);
|
||||
export const toggleServicesQuickActions = createAction(
|
||||
`${APP}/TOGGLE_SERVICES_QUICK_ACTIONS`
|
||||
);
|
||||
|
||||
export const toggleInstancesTooltip = createAction(
|
||||
`${APP}/TOGGLE_INSTANCES_TOOLTIP`
|
||||
);
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { handleActions } from 'redux-actions';
|
||||
import { toggleServicesQuickActions } from '@state/actions';
|
||||
import { toggleServicesQuickActions, toggleInstancesTooltip } from '@state/actions';
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[toggleServicesQuickActions.toString()]: (state, action) => {
|
||||
const _toggleServicesQuickActions = (state, action) => {
|
||||
const { position, service, show } = action.payload;
|
||||
|
||||
const s =
|
||||
@ -29,7 +27,41 @@ export default handleActions(
|
||||
quickActions
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const _toggleInstancesTooltip = (state, action) => {
|
||||
const { position, instance, show, type } = action.payload;
|
||||
|
||||
const s =
|
||||
show === undefined
|
||||
? !state.instances.tooltip.instance ||
|
||||
instance.id !== state.instances.tooltip.instance.id
|
||||
: show;
|
||||
|
||||
const tooltip = s
|
||||
? {
|
||||
show: true,
|
||||
position,
|
||||
instance,
|
||||
type
|
||||
}
|
||||
: {
|
||||
show: false
|
||||
};
|
||||
|
||||
return {
|
||||
...state,
|
||||
instances: {
|
||||
...state.instances,
|
||||
tooltip
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[toggleServicesQuickActions.toString()]: _toggleServicesQuickActions,
|
||||
[toggleInstancesTooltip.toString()]: _toggleInstancesTooltip
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
@ -61,7 +61,6 @@ const activeInstanceStatuses = [
|
||||
'READY',
|
||||
'ACTIVE',
|
||||
'RUNNING',
|
||||
'STOPPING',
|
||||
'INCOMPLETE'
|
||||
];
|
||||
|
||||
@ -103,8 +102,12 @@ const getInstancesActive = instanceStatuses => {
|
||||
|
||||
const getInstancesHealthy = instances => {
|
||||
return instances.reduce(
|
||||
(healthy, instance) => (instance.healthy === 'HEALTHY' ? healthy : false),
|
||||
true
|
||||
(healthy, instance) => ({
|
||||
total: healthy.total + 1,
|
||||
healthy: instance.healthy === 'HEALTHY' ?
|
||||
healthy.healthy + 1 : healthy.healthy
|
||||
}),
|
||||
{total: 0, healthy: 0}
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -30,6 +30,11 @@ const state = {
|
||||
quickActions: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
instances: {
|
||||
tooltip: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -76,6 +76,7 @@ const getUnfilteredServices = query => {
|
||||
|
||||
const getServices = query => {
|
||||
// get all services
|
||||
|
||||
const services = getUnfilteredServices(query)
|
||||
// get all instances
|
||||
.then(services =>
|
||||
@ -95,9 +96,10 @@ const getServices = query => {
|
||||
);
|
||||
// get all the serviceIds of the available instances
|
||||
// and then get the servcies with those ids
|
||||
return uniq(
|
||||
const ret = uniq(
|
||||
availableInstances.map(({ serviceId }) => serviceId)
|
||||
).map(serviceId => lfind(services, ['id', serviceId]));
|
||||
return ret;
|
||||
});
|
||||
|
||||
return Promise.resolve(services)
|
||||
@ -331,12 +333,12 @@ const updateServiceAndInstancesStatus = (
|
||||
instancesStatus
|
||||
) => {
|
||||
return Promise.all([
|
||||
getServices({ id: serviceId }),
|
||||
getServices({ parentId: serviceId })
|
||||
getServices({ id: serviceId })/*,
|
||||
getServices({ parentId: serviceId })*/
|
||||
])
|
||||
.then(services =>
|
||||
services.reduce((services, service) => services.concat(service), [])
|
||||
)
|
||||
.then(services => {
|
||||
return services.reduce((services, service) => services.concat(service), [])
|
||||
})
|
||||
.then(services => {
|
||||
updateServiceStatus(services, serviceStatus);
|
||||
return Promise.all(
|
||||
@ -356,8 +358,8 @@ const updateServiceAndInstancesStatus = (
|
||||
})
|
||||
.then(() =>
|
||||
Promise.all([
|
||||
getUnfilteredServices({ id: serviceId }),
|
||||
getUnfilteredServices({ parentId: serviceId })
|
||||
getUnfilteredServices({ id: serviceId })/*,
|
||||
getUnfilteredServices({ parentId: serviceId })*/
|
||||
])
|
||||
)
|
||||
.then(services =>
|
||||
|
@ -114,8 +114,8 @@
|
||||
"serviceId": "6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"deploymentGroupId": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"machineId": "8d8a2238-d981-4849-b523-a37456fbe20b",
|
||||
"status": "RUNNING",
|
||||
"healthy": "HEALTHY"
|
||||
"status": "STOPPING",
|
||||
"healthy": "MAINTENANCE"
|
||||
},
|
||||
{
|
||||
"id": "68d3046e-8e34-4f5d-a0e5-db3795a250fd",
|
||||
@ -132,8 +132,8 @@
|
||||
"serviceId": "6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"deploymentGroupId": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"machineId": "d6871ac4-6433-40c3-89e8-8853ce7f8571",
|
||||
"status": "RUNNING",
|
||||
"healthy": "HEALTHY"
|
||||
"status": "OFFLINE",
|
||||
"healthy": "UNAVAILABLE"
|
||||
},
|
||||
{
|
||||
"id": "25f6bc62-63b8-4959-908e-1f6d7ff6341d",
|
||||
@ -141,8 +141,8 @@
|
||||
"serviceId": "6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"deploymentGroupId": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"machineId": "d89612c8-0578-474a-b45d-98a1dcf6dd18",
|
||||
"status": "RUNNING",
|
||||
"healthy": "HEALTHY"
|
||||
"status": "FAILED",
|
||||
"healthy": "UNHEALTHY"
|
||||
},
|
||||
{
|
||||
"id": "8be01042-0281-4a77-a357-25979e87bf3d",
|
||||
@ -151,7 +151,7 @@
|
||||
"deploymentGroupId": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"machineId": "3a9fbaf8-722b-463a-86bd-8d3afe0dd759",
|
||||
"status": "RUNNING",
|
||||
"healthy": "HEALTHY"
|
||||
"healthy": "UNKNOWN"
|
||||
},
|
||||
{
|
||||
"id": "3d652e9d-73e8-4a6f-8171-84fa83740662",
|
||||
|
@ -14,5 +14,6 @@ export const border = {
|
||||
checked: css`${remcalc(1)} solid ${props => props.theme.primary}`,
|
||||
unchecked: css`${remcalc(1)} solid ${props => props.theme.grey}`,
|
||||
confirmed: css`${remcalc(1)} solid ${props => props.theme.grey}`,
|
||||
error: css`${remcalc(1)} solid ${props => props.theme.red}`
|
||||
error: css`${remcalc(1)} solid ${props => props.theme.red}`,
|
||||
secondary: css`${remcalc(1)} solid ${props => props.theme.secondaryActive}`,
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import Baseline from '../baseline';
|
||||
import paperEffect from '../paper-effect';
|
||||
import { bottomShaddow, bottomShaddowDarker } from '../boxes';
|
||||
import remcalc from 'remcalc';
|
||||
import is from 'styled-is';
|
||||
import is, { isNot } from 'styled-is';
|
||||
import { Row } from 'react-styled-flexboxgrid';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
@ -38,6 +38,10 @@ const StyledCard = Row.extend`
|
||||
${is('stacked')`
|
||||
${paperEffect}
|
||||
`};
|
||||
|
||||
${isNot('active')`
|
||||
background-color: ${props => props.theme.disabled};
|
||||
`};
|
||||
`;
|
||||
|
||||
/**
|
||||
@ -47,7 +51,7 @@ const Card = ({
|
||||
children,
|
||||
collapsed = false,
|
||||
headed = false,
|
||||
disabled = false,
|
||||
active = true,
|
||||
...rest
|
||||
}) => {
|
||||
const render = value => {
|
||||
@ -55,14 +59,14 @@ const Card = ({
|
||||
fromHeader: (value || {}).fromHeader,
|
||||
headed,
|
||||
collapsed,
|
||||
disabled
|
||||
active
|
||||
};
|
||||
|
||||
return (
|
||||
<Broadcast channel="card" value={newValue}>
|
||||
<StyledCard
|
||||
name="card"
|
||||
disabled={disabled}
|
||||
active={active}
|
||||
collapsed={collapsed}
|
||||
headed={headed}
|
||||
{...rest}
|
||||
|
@ -12,7 +12,8 @@ const StyledTitle = Title.extend`
|
||||
${typography.fontFamily};
|
||||
${typography.normal};
|
||||
|
||||
flex-grow: 2;
|
||||
flex-grow: 1;
|
||||
flex-basis: ${remcalc(90)};
|
||||
|
||||
${isNot('collapsed')`
|
||||
padding-bottom: ${remcalc(12)};
|
||||
@ -21,11 +22,6 @@ const StyledTitle = Title.extend`
|
||||
|
||||
const InnerDescription = styled.div`
|
||||
justify-content: flex-start;
|
||||
|
||||
${is('collapsed')`
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
`};
|
||||
`;
|
||||
|
||||
const Description = ({ children, ...rest }) => {
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Broadcast, Subscriber } from 'react-broadcast';
|
||||
import remcalc from 'remcalc';
|
||||
import PropTypes from 'prop-types';
|
||||
import is from 'styled-is';
|
||||
import is, { isNot } from 'styled-is';
|
||||
import Baseline from '../baseline';
|
||||
import Card from './card';
|
||||
|
||||
@ -17,16 +17,15 @@ const StyledCard = Card.extend`
|
||||
width: calc(100% + ${remcalc(2)});
|
||||
margin: ${remcalc(-1)} ${remcalc(-1)} 0 ${remcalc(-1)};
|
||||
|
||||
${is('disabled')`
|
||||
${isNot('active')`
|
||||
background-color: ${props => props.theme.disabled};
|
||||
border-color: ${props => props.theme.grey};
|
||||
color: ${props => props.theme.grey};
|
||||
`};
|
||||
`;
|
||||
|
||||
const Header = ({ children, ...rest }) => {
|
||||
const render = value => {
|
||||
const { disabled } = value;
|
||||
const { active } = value;
|
||||
|
||||
const newValue = {
|
||||
...value,
|
||||
@ -37,7 +36,7 @@ const Header = ({ children, ...rest }) => {
|
||||
<Broadcast channel="card" value={newValue}>
|
||||
<StyledCard
|
||||
name="card-header"
|
||||
disabled={disabled}
|
||||
active={active}
|
||||
collapsed
|
||||
headed
|
||||
{...rest}
|
||||
|
@ -5,9 +5,13 @@ import remcalc from 'remcalc';
|
||||
import Label from '../label';
|
||||
|
||||
const StyledLabel = Label.extend`
|
||||
display: inline-block;
|
||||
${props => (props.color === 'light' ? `color: ${props.theme.white};` : '')};
|
||||
${props => (props.color === 'disabled' ? `color: ${props.theme.grey};` : '')};
|
||||
${props => (props.color === 'disabled' ? `color: ${props.theme.text};` : '')};
|
||||
margin-left: ${props => (props.iconPosition === 'left' ? remcalc(24) : 0)};
|
||||
&::first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledIconContainer = styled.div`
|
||||
@ -15,7 +19,7 @@ const StyledIconContainer = styled.div`
|
||||
|
||||
> svg {
|
||||
${props => (props.color === 'light' ? `fill: ${props.theme.white};` : '')};
|
||||
${props => (props.color === 'disabled' ? `fill: ${props.theme.grey};` : '')};
|
||||
${props => (props.color === 'disabled' ? `fill: ${props.theme.text};` : '')};
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -3,7 +3,7 @@ import styled from 'styled-components';
|
||||
import { Nav } from 'normalized-styled-components';
|
||||
import Baseline from '../baseline';
|
||||
import remcalc from 'remcalc';
|
||||
import is from 'styled-is';
|
||||
import is, { isNot } from 'styled-is';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from '../button';
|
||||
import React from 'react';
|
||||
@ -17,7 +17,7 @@ const StyledNav = Nav.extend`
|
||||
border-left-color: ${props => props.theme.primaryDesaturatedActive};
|
||||
`};
|
||||
|
||||
${is('disabled')`
|
||||
${isNot('active')`
|
||||
border-left-color: ${props => props.theme.grey};
|
||||
`};
|
||||
`;
|
||||
@ -54,6 +54,19 @@ const StyledButton = Button.extend`
|
||||
&:active:focus {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
${isNot('active')`
|
||||
background-color: ${props => props.theme.disabled};
|
||||
border-color: ${props => props.theme.grey};
|
||||
|
||||
&:focus,
|
||||
&:hover,
|
||||
&:active,
|
||||
&:active:hover,
|
||||
&:active:focus {
|
||||
background-color: ${props => props.theme.grey};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@ -73,8 +86,8 @@ const StyledCircle = styled.div`
|
||||
background-color: ${props => props.theme.secondary};
|
||||
`};
|
||||
|
||||
${is('disabled')`
|
||||
background-color: ${props => props.theme.grey};
|
||||
${isNot('active')`
|
||||
background-color: ${props => props.theme.text};
|
||||
`};
|
||||
`;
|
||||
|
||||
@ -82,20 +95,20 @@ const Options = ({ children, ...rest }) => {
|
||||
const render = ({
|
||||
fromHeader = false,
|
||||
collapsed = false,
|
||||
disabled = false
|
||||
active = true
|
||||
}) =>
|
||||
<StyledNav disabled={disabled} fromHeader={fromHeader} name="card-options">
|
||||
<StyledNav active={active} fromHeader={fromHeader} name="card-options">
|
||||
<StyledButton
|
||||
secondary={!fromHeader}
|
||||
collapsed={collapsed}
|
||||
disabled={disabled}
|
||||
active={active}
|
||||
rect
|
||||
{...rest}
|
||||
>
|
||||
<StyledContainer>
|
||||
<StyledCircle disabled={disabled} secondary={!fromHeader} />
|
||||
<StyledCircle disabled={disabled} secondary={!fromHeader} />
|
||||
<StyledCircle disabled={disabled} secondary={!fromHeader} />
|
||||
<StyledCircle active={active} secondary={!fromHeader} />
|
||||
<StyledCircle active={active} secondary={!fromHeader} />
|
||||
<StyledCircle active={active} secondary={!fromHeader} />
|
||||
</StyledContainer>
|
||||
</StyledButton>
|
||||
</StyledNav>;
|
||||
|
@ -2,7 +2,7 @@ import { Subscriber } from 'react-broadcast';
|
||||
import typography from '../typography';
|
||||
import Baseline from '../baseline';
|
||||
import { Col } from 'react-styled-flexboxgrid';
|
||||
import is from 'styled-is';
|
||||
import is, { isNot } from 'styled-is';
|
||||
import remcalc from 'remcalc';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
@ -20,16 +20,16 @@ const StyledCol = Col.extend`
|
||||
display: none;
|
||||
`};
|
||||
|
||||
${is('disabled')`
|
||||
${isNot('active')`
|
||||
color: ${props => props.theme.grey};
|
||||
`};
|
||||
`;
|
||||
|
||||
const Outlet = ({ children, ...rest }) => {
|
||||
const render = ({ disabled = false, collapsed = false }) =>
|
||||
const render = ({ active = true, collapsed = false }) =>
|
||||
<StyledCol
|
||||
name="card-outlet"
|
||||
disabled={disabled}
|
||||
active={active}
|
||||
collapsed={collapsed}
|
||||
xs={6}
|
||||
{...rest}
|
||||
|
@ -3,7 +3,7 @@ import styled from 'styled-components';
|
||||
import Baseline from '../baseline';
|
||||
import typography from '../typography';
|
||||
import remcalc from 'remcalc';
|
||||
import is from 'styled-is';
|
||||
import is, { isNot } from 'styled-is';
|
||||
import PropTypes from 'prop-types';
|
||||
import Title from './title';
|
||||
import React from 'react';
|
||||
@ -27,10 +27,6 @@ const Span = styled.span`
|
||||
${is('fromHeader')`
|
||||
color: ${props => props.theme.white};
|
||||
`};
|
||||
|
||||
${is('disabled')`
|
||||
color: ${props => props.theme.grey};
|
||||
`};
|
||||
`;
|
||||
|
||||
const StyledTitle = Title.extend`
|
||||
@ -48,7 +44,7 @@ const StyledTitle = Title.extend`
|
||||
|
||||
const Subtitle = ({ children, ...props }) => {
|
||||
const render = ({
|
||||
disabled = false,
|
||||
active = true,
|
||||
fromHeader = false,
|
||||
collapsed = false
|
||||
}) =>
|
||||
@ -56,7 +52,7 @@ const Subtitle = ({ children, ...props }) => {
|
||||
name="card-subtitle"
|
||||
fromHeader={fromHeader}
|
||||
collapsed={collapsed}
|
||||
disabled={disabled}
|
||||
active={active}
|
||||
{...props}
|
||||
>
|
||||
<Span fromHeader={fromHeader} collapsed={collapsed}>
|
||||
|
@ -3,7 +3,7 @@ import isString from 'lodash.isstring';
|
||||
import typography from '../typography';
|
||||
import Baseline from '../baseline';
|
||||
import remcalc from 'remcalc';
|
||||
import is from 'styled-is';
|
||||
import is, { isNot } from 'styled-is';
|
||||
import styled from 'styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
@ -21,6 +21,7 @@ const Container = styled.div`
|
||||
justify-content: flex-start;
|
||||
|
||||
flex-grow: 2;
|
||||
flex-basis: ${remcalc(90)};
|
||||
width: 100%;
|
||||
|
||||
padding: ${remcalc(12)} ${remcalc(18)} 0 ${remcalc(18)};
|
||||
@ -29,12 +30,8 @@ const Container = styled.div`
|
||||
color: ${props => props.theme.white};
|
||||
`};
|
||||
|
||||
${is('disabled')`
|
||||
color: ${props => props.theme.grey};
|
||||
`};
|
||||
|
||||
${is('collapsed')`
|
||||
flex-grow: 0;
|
||||
flex-grow: 6;
|
||||
flex-direction: column;
|
||||
width: auto;
|
||||
justify-content: center;
|
||||
@ -61,16 +58,17 @@ const Title = ({ children, ...rest }) => {
|
||||
|
||||
const render = ({
|
||||
collapsed = false,
|
||||
disabled = false,
|
||||
active = true,
|
||||
fromHeader = false
|
||||
}) =>
|
||||
<Container
|
||||
collapsed={collapsed}
|
||||
fromHeader={fromHeader}
|
||||
disabled={disabled}
|
||||
active={active}
|
||||
name="card-title"
|
||||
xs={collapsed ? 6 : 12}
|
||||
{...rest}
|
||||
name='container'
|
||||
>
|
||||
{_children}
|
||||
</Container>;
|
||||
|
@ -18,6 +18,9 @@ const StyledSelectList = styled(Tooltip)`
|
||||
position: relative;
|
||||
display: block;
|
||||
left: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
ul:after, ul:before {
|
||||
left: 97%;
|
||||
@ -107,7 +110,9 @@ class Dropdown extends Component {
|
||||
<StyledArrowIcon onClick={this.toggleDropdown} />
|
||||
{this.state.isDroppedDown &&
|
||||
<StyledSelectList>
|
||||
<ul>
|
||||
{data.map((val, index) =>
|
||||
<li>
|
||||
<DropdownItem
|
||||
key={index}
|
||||
value={val}
|
||||
@ -115,7 +120,9 @@ class Dropdown extends Component {
|
||||
>
|
||||
{val}
|
||||
</DropdownItem>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</StyledSelectList>}
|
||||
</Container>
|
||||
);
|
||||
|
@ -1,7 +1,13 @@
|
||||
import Baseline from '../baseline';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import HealthyIcon from './svg/icon_healthy.svg';
|
||||
|
||||
export default Baseline(HealthyIcon);
|
||||
const StyledHealthyIcon = styled(HealthyIcon)`
|
||||
fill: ${props => !props.healthy || props.healthy === 'HEALTHY'
|
||||
? props.theme.green : props.theme.orange};
|
||||
`;
|
||||
|
||||
export default Baseline(StyledHealthyIcon);
|
||||
|
@ -4,12 +4,12 @@
|
||||
<title>icon: state</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill-rule="evenodd">
|
||||
<g id="topology" transform="translate(-489.000000, -441.000000)">
|
||||
<g id="services" transform="translate(0.000000, 348.000000)">
|
||||
<g id="service:-nginx" transform="translate(476.000000, 36.000000)">
|
||||
<g id="icon:-state" transform="translate(13.000000, 57.000000)">
|
||||
<circle id="Oval" fill="#00AF66" cx="9" cy="9" r="9"></circle>
|
||||
<circle id="Oval" cx="9" cy="9" r="9"></circle>
|
||||
<path d="M9.47745233,6.60270759 L8.95496861,7.04565311 L8.51133742,6.60270759 C7.70841297,5.79909747 6.40563205,5.79909747 5.60270759,6.60270759 C4.79909747,7.40631772 4.79909747,8.70841297 5.60270759,9.5120231 L8.95496861,12.8642841 L12.3668833,9.5120231 C13.1698077,8.70841297 13.2301471,7.40631772 12.4265369,6.60270759 C11.6229268,5.79909747 10.2810625,5.79909747 9.47745233,6.60270759 Z" id="icon:-health" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
@ -17,7 +17,7 @@ export { default as Chevron } from './chevron';
|
||||
export { default as CloseButton } from './close-button';
|
||||
export { default as Divider } from './divider';
|
||||
export { default as IconButton } from './icon-button';
|
||||
export { Tooltip, TooltipButton, TooltipDivider } from './tooltip';
|
||||
export { Tooltip, TooltipButton, TooltipDivider, TooltipList, TooltipLabel } from './tooltip';
|
||||
export { Dropdown } from './dropdown';
|
||||
export { default as StatusLoader } from './status-loader';
|
||||
export { default as Message } from './message';
|
||||
|
@ -45,8 +45,6 @@ const StyledButton = styled(Button)`
|
||||
`;
|
||||
|
||||
const TooltipButton = props =>
|
||||
<li>
|
||||
<StyledButton {...props} />
|
||||
</li>;
|
||||
<StyledButton {...props} />;
|
||||
|
||||
export default TooltipButton;
|
||||
|
@ -1,3 +1,5 @@
|
||||
export { default as Tooltip } from './tooltip';
|
||||
export { default as TooltipButton } from './button';
|
||||
export { default as TooltipDivider } from './divider';
|
||||
export { default as TooltipList } from './list';
|
||||
export { default as TooltipLabel } from './label';
|
||||
|
9
packages/ui-toolkit/src/tooltip/label.js
Normal file
9
packages/ui-toolkit/src/tooltip/label.js
Normal file
@ -0,0 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
import remcalc from 'remcalc';
|
||||
import P from '../text/p';
|
||||
|
||||
export default styled(P)`
|
||||
margin: 0 ${remcalc(18)};
|
||||
color: ${props => props.theme.white};
|
||||
white-space: nowrap;
|
||||
`;
|
7
packages/ui-toolkit/src/tooltip/list.js
Normal file
7
packages/ui-toolkit/src/tooltip/list.js
Normal file
@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export default styled.ul`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
`;
|
@ -18,19 +18,18 @@ const StyledContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledList = styled.ul`
|
||||
const StyledInnerContainer = styled.div`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
top: ${remcalc(5)};
|
||||
left: -50%;
|
||||
margin: 0;
|
||||
padding: ${unitcalc(2)} 0;
|
||||
list-style-type: none;
|
||||
background-color: ${theme.white};
|
||||
border: ${border.unchecked};
|
||||
background-color: ${props => props.secondary ? props.theme.secondary : props.theme.white};
|
||||
border: ${props => props.secondary ? border.secondary : border.unchecked};
|
||||
box-shadow: ${tooltipShadow};
|
||||
border-radius: ${borderRadius};
|
||||
z-index: 1;
|
||||
z-index: 1000;
|
||||
|
||||
&:after,
|
||||
&:before {
|
||||
@ -44,13 +43,13 @@ const StyledList = styled.ul`
|
||||
}
|
||||
|
||||
&:after {
|
||||
border-bottom-color: ${theme.white};
|
||||
border-bottom-color: ${props => props.secondary ? props.theme.secondary : theme.white};
|
||||
border-width: ${remcalc(3)};
|
||||
margin-left: ${remcalc(-3)};
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-bottom-color: ${theme.grey};
|
||||
border-bottom-color: ${props => props.secondary ? props.theme.secondaryActive : theme.grey};
|
||||
border-width: ${remcalc(5)};
|
||||
margin-left: ${remcalc(-5)};
|
||||
}
|
||||
@ -84,27 +83,39 @@ class Tooltip extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
let {
|
||||
children,
|
||||
top = 'auto',
|
||||
left = 'auto',
|
||||
bottom = 'auto',
|
||||
right = 'auto',
|
||||
className,
|
||||
secondary,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
if(typeof top === 'number') {
|
||||
top = `${top}px`
|
||||
}
|
||||
if(typeof left === 'number') {
|
||||
left = `${left}px`
|
||||
}
|
||||
if(typeof bottom === 'number') {
|
||||
bottom = `${bottom}px`
|
||||
}
|
||||
if(typeof right === 'number') {
|
||||
right = `${right}px`
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
className={className}
|
||||
top={top}
|
||||
left={left}
|
||||
bottom={bottom}
|
||||
right={right}
|
||||
{...rest}
|
||||
>
|
||||
<StyledList>
|
||||
<StyledInnerContainer secondary={secondary}>
|
||||
{children}
|
||||
</StyledList>
|
||||
</StyledInnerContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
||||
@ -116,7 +127,8 @@ Tooltip.propTypes = {
|
||||
left: PropTypes.string,
|
||||
bottom: PropTypes.string,
|
||||
right: PropTypes.string,
|
||||
onBlur: PropTypes.func
|
||||
onBlur: PropTypes.func,
|
||||
secondary: PropTypes.boolean
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
|
Loading…
Reference in New Issue
Block a user