Topology constraints, metrics uppercase fix, node title link

This commit is contained in:
Alex Windett 2017-03-23 15:31:25 +00:00 committed by Sérgio Ramos
parent 398071ade2
commit 97a4537330
8 changed files with 95 additions and 47 deletions

View File

@ -2,18 +2,18 @@
"your-dashboard": "Your dashboard", "your-dashboard": "Your dashboard",
"project-feed": "Project Feed", "project-feed": "Project Feed",
"summary": "Summary", "summary": "Summary",
"metric-s": "Metrics", "metrics": "Metrics",
"networks": "Networks", "networks": "Networks",
"tags-metadata": "Tags and Meta Data", "tags-metadata": "Tags and Meta Data",
"activity-feed": "Activity Feed", "activity-feed": "Activity Feed",
"firewall": "Firewall", "firewall": "Firewall",
"service-manifest": "Service manifest",
"settings": "Settings", "settings": "Settings",
"projects": "Projects", "projects": "Projects",
"people": "People", "people": "People",
"services": "Services", "services": "Services",
"instances": "Instances", "instances": "Instances",
"manifest": "Project manifest", "manifest": "Project manifest",
"service-manifest": "Service manifest",
"create-new": "Create new project", "create-new": "Create new project",
"rollback": "Rollback", "rollback": "Rollback",
"import-services-title": "Import your services", "import-services-title": "Import your services",
@ -52,7 +52,7 @@
"description": "Please provide your billing details.", "description": "Please provide your billing details.",
"save-details-label": "Save details" "save-details-label": "Save details"
}, },
"metrics": { "metrics-section": {
"add": { "add": {
"add-label": "Add", "add-label": "Add",
"added-label": "Added", "added-label": "Added",

View File

@ -21,26 +21,29 @@ const AddMetrics = ({
const addButton = (metric) => ( const addButton = (metric) => (
<AddMetricButton metric={metric} onClick={onAddMetric}> <AddMetricButton metric={metric} onClick={onAddMetric}>
<FormattedMessage id={'metrics.add.add-label'} onClick={onAddMetric} /> <FormattedMessage
id={'metrics-section.add.add-label'}
onClick={onAddMetric}
/>
</AddMetricButton> </AddMetricButton>
); );
const addedButton = ( const addedButton = (
<AddMetricButton disabled> <AddMetricButton disabled>
<FormattedMessage id={'metrics.add.added-label'} /> <FormattedMessage id={'metrics-section.add.added-label'} />
</AddMetricButton> </AddMetricButton>
); );
const metricList = metricTypes.map((metric) => ( const metricList = metricTypes.map((metric) => (
<AddMetricTile key={metric.id}> <AddMetricTile key={metric.id}>
<AddMetricTitle> <AddMetricTitle>
<FormattedMessage id={`metrics.${metric.id}.title`} /> <FormattedMessage id={`metrics-section.${metric.id}.title`} />
</AddMetricTitle> </AddMetricTitle>
<AddMetricDescription> <AddMetricDescription>
<FormattedMessage id={`metrics.${metric.id}.description`} /> <FormattedMessage id={`metrics-section.${metric.id}.description`} />
</AddMetricDescription> </AddMetricDescription>
<AddMetricLink href='http://somelink.com'> <AddMetricLink href='http://somelink.com'>
<FormattedMessage id={'metrics.add.link-label'} /> <FormattedMessage id={'metrics-section.add.link-label'} />
</AddMetricLink> </AddMetricLink>
{ added(metric.uuid) ? addedButton : addButton(metric.uuid) } { added(metric.uuid) ? addedButton : addButton(metric.uuid) }
</AddMetricTile> </AddMetricTile>

View File

@ -47,13 +47,13 @@ const MetricCharts = ({
<MetricHeader> <MetricHeader>
<MetricTitle> <MetricTitle>
{type.name} {type.name}
{/*<FormattedMessage id={`metrics.${type.id}.title`} />*/} {/*<FormattedMessage id={`metrics-section.${type.id}.title`} />*/}
</MetricTitle> </MetricTitle>
<MetricSelect onChange={handleSelectChange} value={String(duration)}> <MetricSelect onChange={handleSelectChange} value={String(duration)}>
{optionList} {optionList}
</MetricSelect> </MetricSelect>
<MetricSettingsButton onClick={handleSettingsClick}> <MetricSettingsButton onClick={handleSettingsClick}>
<FormattedMessage id={'metrics.metric.settings-label'} /> <FormattedMessage id={'metrics-section.metric.settings-label'} />
</MetricSettingsButton> </MetricSettingsButton>
<MetricCloseButton onClick={handleRemoveMetric} /> <MetricCloseButton onClick={handleRemoveMetric} />
</MetricHeader> </MetricHeader>

View File

@ -2,7 +2,8 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import styled from 'styled-components'; import styled from 'styled-components';
import PropTypes from '@root/prop-types'; import PropTypes from '@root/prop-types';
import { colors } from '@ui/shared/constants'; import { colors, breakpoints } from '@ui/shared/constants';
import { unitcalc } from '@ui/shared/functions';
import { TopologyGraph } from '@ui/components/topology'; import { TopologyGraph } from '@ui/components/topology';
import { LayoutContainer } from '@components/layout'; import { LayoutContainer } from '@components/layout';
import ServicesTooltip from '@components/services/tooltip'; import ServicesTooltip from '@components/services/tooltip';
@ -22,6 +23,11 @@ const StyledBackground = styled.div`
const StyledContainer = styled(LayoutContainer)` const StyledContainer = styled(LayoutContainer)`
position: relative; position: relative;
padding: ${unitcalc(4)} 2rem;
${breakpoints.large`
padding: ${unitcalc(4)} 0;
`}
`; `;
const Services = (props) => { const Services = (props) => {
@ -30,14 +36,16 @@ const Services = (props) => {
org = {}, org = {},
project = {}, project = {},
toggleTooltip, toggleTooltip,
uiTooltip uiTooltip,
push
} = props; } = props;
const onQuickActions = (evt, tooltipData) => { const getService = (uuid) => services.reduce((acc, service) =>
const service = services.reduce((acc, service) => service.uuid === uuid ? service : acc
service.uuid === tooltipData.service ? service : acc , {});
, {});
const onQuickActions = (evt, tooltipData) => {
const service = getService(tooltipData.service);
const ttData = { const ttData = {
...tooltipData, ...tooltipData,
data: { data: {
@ -54,11 +62,20 @@ const Services = (props) => {
service: uiTooltip.service service: uiTooltip.service
}); });
const onNodeTitleClick = (uuid) => {
const service = getService(uuid);
const path = `/${org.id}/projects/${project.id}/services/${service.id}`;
push(path);
};
return ( return (
<StyledBackground> <StyledBackground>
<StyledContainer> <StyledContainer>
<TopologyGraph <TopologyGraph
onQuickActions={onQuickActions} onQuickActions={onQuickActions}
onNodeTitleClick={onNodeTitleClick}
services={services} services={services}
/> />
<ServicesTooltip {...uiTooltip} onBlur={handleTooltipBlur} /> <ServicesTooltip {...uiTooltip} onBlur={handleTooltipBlur} />
@ -71,6 +88,7 @@ Services.propTypes = {
org: PropTypes.org, org: PropTypes.org,
services: React.PropTypes.arrayOf(PropTypes.service), services: React.PropTypes.arrayOf(PropTypes.service),
project: PropTypes.project, project: PropTypes.project,
push: React.PropTypes.func.isRequired,
toggleTooltip: React.PropTypes.func, toggleTooltip: React.PropTypes.func,
uiTooltip: React.PropTypes.object uiTooltip: React.PropTypes.object
}; };
@ -78,12 +96,14 @@ Services.propTypes = {
const mapStateToProps = (state, { const mapStateToProps = (state, {
match = { match = {
params: {} params: {}
} },
push
}) => ({ }) => ({
org: orgByIdSelector(match.params.org)(state), org: orgByIdSelector(match.params.org)(state),
project: projectByIdSelector(match.params.projectId)(state), project: projectByIdSelector(match.params.projectId)(state),
services: servicesForTopologySelector(match.params.projectId)(state), services: servicesForTopologySelector(match.params.projectId)(state),
uiTooltip: serviceUiTooltipSelector(state) uiTooltip: serviceUiTooltipSelector(state),
push: push
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({

View File

@ -75,7 +75,7 @@
"id": "cpu-wait-time", "id": "cpu-wait-time",
"min": 0, "min": 0,
"max": 100, "max": 100,
"measurement": "%" "measurement": "bytes"
}, { }, {
"uuid": "dca08514-72e5-46ce-ad91-e68b3b0914d7", "uuid": "dca08514-72e5-46ce-ad91-e68b3b0914d7",
"name": "Zfs used", "name": "Zfs used",
@ -96,14 +96,14 @@
"id": "load-average", "id": "load-average",
"min": 0, "min": 0,
"max": 20, "max": 20,
"measurement": "kb" "measurement": " Mb"
}, { }, {
"uuid": "dca08514-72e5-46ce-ad92-e68b3b0914d4", "uuid": "dca08514-72e5-46ce-ad92-e68b3b0914d4",
"name": "Memory", "name": "Memory",
"id": "mem-agg-usage", "id": "mem-agg-usage",
"min": 0, "min": 0,
"max": 100, "max": 100,
"measurement": "%" "measurement": " Mb"
}, { }, {
"uuid": "dca08514-72e5-46ce-ad93-e68b3b0914d4", "uuid": "dca08514-72e5-46ce-ad93-e68b3b0914d4",
"name": "Memory limit", "name": "Memory limit",

View File

@ -14,6 +14,7 @@ const GraphNode = ({
data, data,
index, index,
onDragStart, onDragStart,
onNodeTitleClick,
onQuickActions onQuickActions
}) => { }) => {
@ -61,6 +62,9 @@ const GraphNode = ({
onQuickActions(evt, d); onQuickActions(evt, d);
}; };
const onTitleClick = () =>
onNodeTitleClick(data.uuid);
const onStart = (evt) => { const onStart = (evt) => {
evt.preventDefault(); evt.preventDefault();
onDragStart(evt, data.id); onDragStart(evt, data.id);
@ -107,6 +111,7 @@ const GraphNode = ({
<GraphNodeTitle <GraphNodeTitle
connected={connected} connected={connected}
data={data} data={data}
onNodeTitleClick={onTitleClick}
/> />
<GraphNodeButton <GraphNodeButton
connected={connected} connected={connected}
@ -123,6 +128,7 @@ GraphNode.propTypes = {
data: React.PropTypes.object.isRequired, data: React.PropTypes.object.isRequired,
index: React.PropTypes.number.isRequired, index: React.PropTypes.number.isRequired,
onDragStart: React.PropTypes.func, onDragStart: React.PropTypes.func,
onNodeTitleClick: React.PropTypes.func,
onQuickActions: React.PropTypes.func onQuickActions: React.PropTypes.func
}; };

View File

@ -6,7 +6,8 @@ import HeartIcon from './icon-heart.svg';
const GraphNodeTitle = ({ const GraphNodeTitle = ({
connected, connected,
data data,
onNodeTitleClick
}) => { }) => {
return ( return (
@ -15,6 +16,8 @@ const GraphNodeTitle = ({
x={Constants.paddingLeft} x={Constants.paddingLeft}
y={30} y={30}
connected={connected} connected={connected}
onClick={onNodeTitleClick}
onKeyDown={onNodeTitleClick}
> >
{data.name} {data.name}
</GraphTitle> </GraphTitle>
@ -32,7 +35,8 @@ const GraphNodeTitle = ({
GraphNodeTitle.propTypes = { GraphNodeTitle.propTypes = {
connected: React.PropTypes.bool, connected: React.PropTypes.bool,
data: React.PropTypes.object.isRequired data: React.PropTypes.object.isRequired,
onNodeTitleClick: React.PropTypes.func
}; };
export default Baseline( export default Baseline(

View File

@ -5,7 +5,6 @@ import Constants from './constants';
import GraphNode from './graph-node'; import GraphNode from './graph-node';
import GraphLink from './graph-link'; import GraphLink from './graph-link';
import React from 'react'; import React from 'react';
import { triggerMouseEvent } from '../../shared/functions';
const StyledSvg = styled.svg` const StyledSvg = styled.svg`
width: 100%; width: 100%;
@ -103,26 +102,36 @@ class TopologyGraph extends React.Component {
} }
} }
isWithinSVGBounds(target, x, y) { getSvgSize() {
const svgBounds = document return document.getElementById('topology-svg') ?
.getElementsByClassName('topology-svg')[0] document.getElementById('topology-svg').getBoundingClientRect() :
.getBoundingClientRect(); svgSize;
}
const nodeHeight = target.getBoundingClientRect().height; constrain(x, y, children=false) {
const nodeWidth = target.getBoundingClientRect().width; const svgSize = this.getSvgSize();
const constraints = { const nodeRect = children ?
top: svgBounds.top + (nodeHeight / 2), Constants.nodeRectWithChildren :
left: svgBounds.left + (nodeWidth / 2), Constants.nodeRect;
bottom: svgBounds.bottom - (nodeHeight / 2),
right: svgBounds.right - (nodeWidth / 2) if(x < nodeRect.right + 2) {
x = nodeRect.right + 2;
}
else if(x > svgSize.width + nodeRect.left - 2) {
x = svgSize.width + nodeRect.left - 2;
}
if(y < -nodeRect.top + 2) {
y = -nodeRect.top + 2;
}
else if(y > svgSize.height - nodeRect.bottom - 2) {
y = svgSize.height - nodeRect.bottom - 2;
}
return {
x,
y
}; };
if ( x > constraints.right || x < constraints.left ) return false;
if ( y < constraints.top || y > constraints.bottom ) return false;
return true;
} }
render() { render() {
@ -147,9 +156,14 @@ class TopologyGraph extends React.Component {
y: 0 y: 0
} : simNode(service.uuid); } : simNode(service.uuid);
const constrained = {
...sNode,
...this.constrain(sNode.x, sNode.y, service.children)
};
return ({ return ({
...service, ...service,
...sNode ...constrained
}); });
}); });
@ -188,10 +202,6 @@ class TopologyGraph extends React.Component {
? evt.changedTouches[0].pageY ? evt.changedTouches[0].pageY
: evt.clientY; : evt.clientY;
if ( !this.isWithinSVGBounds(evt.target, x, y) ) {
triggerMouseEvent(evt.target, 'mouseup');
}
const offset = { const offset = {
x: x - dragInfo.position.x, x: x - dragInfo.position.x,
y: y - dragInfo.position.y y: y - dragInfo.position.y
@ -229,12 +239,16 @@ class TopologyGraph extends React.Component {
}; };
}; };
const onTitleClick = (serviceUUID) =>
this.props.onNodeTitleClick(serviceUUID);
const renderedNodes = nodesData.map((n, index) => ( const renderedNodes = nodesData.map((n, index) => (
<GraphNode <GraphNode
key={index} key={index}
data={n} data={n}
index={index} index={index}
onDragStart={onDragStart} onDragStart={onDragStart}
onNodeTitleClick={onTitleClick}
onQuickActions={onQuickActions} onQuickActions={onQuickActions}
connected={n.id !== 'consul'} connected={n.id !== 'consul'}
/> />
@ -255,7 +269,7 @@ class TopologyGraph extends React.Component {
onMouseUp={onDragEnd} onMouseUp={onDragEnd}
onTouchEnd={onDragEnd} onTouchEnd={onDragEnd}
onTouchCancel={onDragEnd} onTouchCancel={onDragEnd}
className='topology-svg' id='topology-svg'
> >
<g> <g>
{renderedNodes} {renderedNodes}
@ -270,6 +284,7 @@ class TopologyGraph extends React.Component {
TopologyGraph.propTypes = { TopologyGraph.propTypes = {
onQuickActions: React.PropTypes.func, onQuickActions: React.PropTypes.func,
onNodeTitleClick: React.PropTypes.func,
services: React.PropTypes.array services: React.PropTypes.array
}; };