diff --git a/ui/src/components/topology/constants.js b/ui/src/components/topology/constants.js new file mode 100644 index 00000000..40d05329 --- /dev/null +++ b/ui/src/components/topology/constants.js @@ -0,0 +1,67 @@ +const Lengths = { + paddingLeft: 18, + nodeWidth: 180 +}; + +const Sizes = { + buttonSize: { + width: 40, + height: 48 + }, + contentSize: { + width: Lengths.nodeWidth, + height: 101 // This is the height w/o info comp + }, + nodeSize: { + width: Lengths.nodeWidth, + height: 156 + }, + nodeSizeWithChildren: { + width: Lengths.nodeWidth, + height: 276 + } +}; + +const Points = { + buttonPosition: { + x: Lengths.nodeWidth - Sizes.buttonSize.width, + y: 0 + }, + contentPosition: { + x: 0, + y: Sizes.buttonSize.height + }, + infoPosition: { + x: Lengths.paddingLeft, + y: 11 + }, + metricsPosition: { + x: Lengths.paddingLeft, + y: 41 + }, + subtitlePosition: { + x: Lengths.paddingLeft, + y: 23 + } +}; + +const Rects = { + buttonRect: { + ...Sizes.buttonSize, + ...Points.buttonPosition + }, + contentRect: { + ...Sizes.contentSize, + ...Points.contentPosition + } +}; + + +const Constants = { + ...Lengths, + ...Sizes, + ...Points, + ...Rects +}; + +export default Constants; diff --git a/ui/src/components/topology/graph-link.js b/ui/src/components/topology/graph-link/index.js similarity index 78% rename from ui/src/components/topology/graph-link.js rename to ui/src/components/topology/graph-link/index.js index 69c79f89..46b560c3 100644 --- a/ui/src/components/topology/graph-link.js +++ b/ui/src/components/topology/graph-link/index.js @@ -1,25 +1,11 @@ -import styled from 'styled-components'; -import { Baseline } from '../../shared/composers'; -import { colors } from '../../shared/constants'; -import PropTypes from './prop-types'; +import { Baseline } from '../../../shared/composers'; +import Constants from '../constants'; import React from 'react'; - -const StyledLine = styled.line` - stroke: ${colors.base.secondaryActive}; - stroke-width: 1.5; -`; - -const StyledCircle = styled.circle` - stroke: ${colors.base.secondaryActive}; - fill: ${colors.base.secondary}; - stroke-width: 1.5; -`; - -const StyledArrow = styled.line` - stroke: ${colors.base.white}; - stroke-width: 2; - stroke-linecap: round; -`; +import { + GraphLinkLine, + GraphLinkCircle, + GraphLinkArrow +} from './shapes'; const getAngleFromPoints = (source, target) => { @@ -81,8 +67,7 @@ const getPositions = (halfWidth, halfHeight, halfCorner=0) => ([{ const GraphLink = ({ data, - index, - nodeSize + index }) => { const { @@ -91,10 +76,11 @@ const GraphLink = ({ } = data; // actually, this will need to be got dynamically, in case them things are different sizes + // yeah right, now you'll get to do exactly that const { width, height - } = nodeSize; + } = Constants.nodeSize; const halfWidth = width/2; const halfHeight = height/2; @@ -109,7 +95,7 @@ const GraphLink = ({ return ( - - - - props.connected ? colors.base.white : colors.base.secondary}; -`; - -const GraphNodeButton = ({ - connected, - buttonRect, - onButtonClick, - index -}) => { - - const buttonCircleRadius = 2; - const buttonCircleSpacing = 2; - const buttonCircleY = - (buttonRect.height - buttonCircleRadius*4 - buttonCircleSpacing*2)/2; - const buttonCircles = [1,2,3].map((item, index) => ( - - )); - - return ( - - - {buttonCircles} - - ); -}; - -GraphNodeButton.propTypes = { - buttonRect: React.PropTypes.shape({ - x: React.PropTypes.number, - y: React.PropTypes.number, - width: React.PropTypes.number, - height: React.PropTypes.number - }).isRequired, - connected: React.PropTypes.bool, - index: React.PropTypes.number.isRequired, - onButtonClick: React.PropTypes.func.isRequired -}; - -export default Baseline( - GraphNodeButton -); diff --git a/ui/src/components/topology/graph-node-info.js b/ui/src/components/topology/graph-node-info.js deleted file mode 100644 index 85b46ec2..00000000 --- a/ui/src/components/topology/graph-node-info.js +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { Baseline } from '../../shared/composers'; -import { colors } from '../../shared/constants'; -import DataCentresIcon from './icon-data-centers.svg'; -import InstancesIcon from './icon-instances.svg'; -import styled from 'styled-components'; - -const StyledText = styled.text` - fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; - font-size: 12px; - opacity: 0.8; -`; - -const StyledInstancesIcon = styled(InstancesIcon)` - fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; -`; - -const StyledDataCentresIcon = styled(DataCentresIcon)` - fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; -`; - -const GraphNodeInfo = ({ - connected, - datacentres, - instances, - healthy, - infoPosition -}) => { - - return ( - - - - - - {`${datacentres} inst.`} - - - - - - {`${instances} DCs`} - - - ); -}; - -GraphNodeInfo.propTypes = { - connected: React.PropTypes.bool, - datacentres: React.PropTypes.number, - healthy: React.PropTypes.bool, - infoPosition: React.PropTypes.shape({ - x: React.PropTypes.number, - y: React.PropTypes.number - }), - instances: React.PropTypes.number -}; - -export default Baseline( - GraphNodeInfo -); diff --git a/ui/src/components/topology/graph-node-metrics.js b/ui/src/components/topology/graph-node-metrics.js deleted file mode 100644 index 2ba02f02..00000000 --- a/ui/src/components/topology/graph-node-metrics.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import { Baseline } from '../../shared/composers'; -import { colors } from '../../shared/constants'; -import styled from 'styled-components'; - -const StyledText = styled.text` - fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; - font-size: 12px; - opacity: 0.8; -`; - -const GraphNodeMetrics = ({ - connected, - metrics, - metricsPosition -}) => { - - const metricSpacing = 18; - const metricsText = metrics.map((metric, index) => ( - - {`${metric.name}: ${metric.value}`} - - )); - - return ( - - {metricsText} - - ); -}; - -GraphNodeMetrics.propTypes = { - connected: React.PropTypes.bool, - metrics: React.PropTypes.arrayOf(React.PropTypes.shape({ - name: React.PropTypes.string, - stat: React.PropTypes.string - })), - metricsPosition: React.PropTypes.shape({ - x: React.PropTypes.number, - y: React.PropTypes.number - }) -}; - -export default Baseline( - GraphNodeMetrics -); diff --git a/ui/src/components/topology/graph-node.js b/ui/src/components/topology/graph-node.js deleted file mode 100644 index d87f8d39..00000000 --- a/ui/src/components/topology/graph-node.js +++ /dev/null @@ -1,188 +0,0 @@ -import { Baseline } from '../../shared/composers'; -import { colors } from '../../shared/constants'; -import PropTypes from './prop-types'; -// import HeartIcon from './icon-heart.svg'; -import GraphNodeButton from './graph-node-button'; -// import GraphNodeInfo from './graph-node-info'; -import GraphNodeMetrics from './graph-node-metrics'; -import styled from 'styled-components'; -import React from 'react'; - - - -const StyledRect = styled.rect` - stroke: ${props => props.connected ? - colors.base.secondaryActive : colors.base.grey}; - fill: ${props => props.connected ? colors.base.secondary : colors.base.white}; - stroke-width: 1.5; - rx: 4; - ry: 4; -`; - -const StyledShadowRect = styled.rect` - fill: ${props => props.connected ? colors.base.secondary : colors.base.grey}; - opacity: 0.33; - rx: 4; - ry: 4; -`; - -const StyledLine = styled.line` - stroke: ${props => props.connected ? - colors.base.secondaryActive : colors.base.grey}; - stroke-width: 1.5; -`; - -const StyledText = styled.text` - fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; - font-size: 16px; - font-weight: 600; -`; - -const HeartCircle = styled.circle` - fill: ${colors.base.green}; -`; - -const GraphNode = ({ - connected, - data, - index, - size, - onDragStart -}) => { - - const { - width, - height - } = size; - - const halfWidth = width/2; - const halfHeight = height/2; - const lineY = 48; - const lineX = 140; - const buttonRect = { - x: lineX, - y: 0, - width: 40, - height: 48 - }; - - const onButtonClick = (evt) => { - // console.log('Rect clicked!!!'); - }; - - const paddingLeft = 18; - /*const infoPosition = { - x: paddingLeft, - y: 59 - };*/ - const metricsPosition = { - x: paddingLeft, - y: 89 - }; - - // const titleBbBox = {x:100, y: 30 - halfHeight}; - const onStart = (evt) => { - evt.preventDefault(); - onDragStart(evt, data.id); - }; - - const position = connected ? { - x: data.x-halfWidth, - y: data.y-halfHeight - } : { - x: data.x, - y: data.y - }; - - const nodeRect = connected ? ( - - ) : ( - - ); - - return ( - - - {nodeRect} - - - - {data.name} - - - - {/**/} - - - {/**/} - - - ); -}; - -GraphNode.propTypes = { - connected: React.PropTypes.bool, - data: React.PropTypes.object.isRequired, - index: React.PropTypes.number.isRequired, - onDragStart: React.PropTypes.func, - size: PropTypes.Size -}; - -export default Baseline( - GraphNode -); diff --git a/ui/src/components/topology/graph-node/button.js b/ui/src/components/topology/graph-node/button.js new file mode 100644 index 00000000..52e2d799 --- /dev/null +++ b/ui/src/components/topology/graph-node/button.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { Baseline } from '../../../shared/composers'; +import Constants from '../constants'; +import { GraphLine, GraphButtonRect, GraphButtonCircle } from './shapes'; + +const GraphNodeButton = ({ + connected, + onButtonClick, + index +}) => { + + const { + x, + y, + width, + height + } = Constants.buttonRect; + + const buttonCircleRadius = 2; + const buttonCircleSpacing = 2; + const buttonCircleY = + (height - buttonCircleRadius*4 - buttonCircleSpacing*2)/2; + const buttonCircles = [1,2,3].map((item, index) => ( + + )); + + return ( + + + + {buttonCircles} + + ); +}; + +GraphNodeButton.propTypes = { + connected: React.PropTypes.bool, + index: React.PropTypes.number.isRequired, + onButtonClick: React.PropTypes.func.isRequired +}; + +export default Baseline( + GraphNodeButton +); diff --git a/ui/src/components/topology/graph-node/content.js b/ui/src/components/topology/graph-node/content.js new file mode 100644 index 00000000..18d75792 --- /dev/null +++ b/ui/src/components/topology/graph-node/content.js @@ -0,0 +1,84 @@ +import React from 'react'; +import { Baseline } from '../../../shared/composers'; +import Constants from '../constants'; +import { GraphLine, GraphSubtitle } from './shapes'; +import GraphNodeInfo from './info'; +import GraphNodeMetrics from './metrics'; + +const GraphNodeContent = ({ + connected, + child=false, + data, + index=0 +}) => { + + const { + x, + y, + width, + height + } = Constants.contentRect; + + const contentY = y + height*index; + + const offset = index ? 18 : - 6; + + const nodeInfoPos = child ? { + x: Constants.infoPosition.x, + y: Constants.infoPosition.y + offset + } : Constants.infoPosition; + + const nodeMetricsPos = child ? { + x: Constants.metricsPosition.x, + y: Constants.metricsPosition.y + offset + } : Constants.metricsPosition; + + const nodeSubtitle = child ? ( + + {data.name} + + ) : null; + + const nodeInfo = !child || index ? ( + + ) : null; + + return ( + + + {nodeSubtitle} + {nodeInfo} + + + ); +}; + +GraphNodeContent.propTypes = { + child: React.PropTypes.bool, + connected: React.PropTypes.bool, + data: React.PropTypes.object.isRequired, + index: React.PropTypes.number +}; + +export default Baseline( + GraphNodeContent +); diff --git a/ui/src/components/topology/icon-data-centers.svg b/ui/src/components/topology/graph-node/icon-data-centers.svg similarity index 100% rename from ui/src/components/topology/icon-data-centers.svg rename to ui/src/components/topology/graph-node/icon-data-centers.svg diff --git a/ui/src/components/topology/icon-heart.svg b/ui/src/components/topology/graph-node/icon-heart.svg similarity index 100% rename from ui/src/components/topology/icon-heart.svg rename to ui/src/components/topology/graph-node/icon-heart.svg diff --git a/ui/src/components/topology/icon-instances.svg b/ui/src/components/topology/graph-node/icon-instances.svg similarity index 100% rename from ui/src/components/topology/icon-instances.svg rename to ui/src/components/topology/graph-node/icon-instances.svg diff --git a/ui/src/components/topology/graph-node/index.js b/ui/src/components/topology/graph-node/index.js new file mode 100644 index 00000000..95c2700d --- /dev/null +++ b/ui/src/components/topology/graph-node/index.js @@ -0,0 +1,106 @@ +import React from 'react'; +import { Baseline } from '../../../shared/composers'; +import Constants from '../constants'; +import GraphNodeTitle from './title'; +import GraphNodeButton from './button'; +import GraphNodeContent from './content'; +import { + GraphNodeRect, + GraphShadowRect +} from './shapes'; + +const GraphNode = ({ + connected, + data, + index, + onDragStart +}) => { + + const { + width, + height + } = data.children ? + Constants.nodeSizeWithChildren : + Constants.nodeSize; + + const halfWidth = width/2; + const halfHeight = height/2; + + let x = 0; + let y = 0; + if(connected) { + x = data.x-halfWidth; + y = data.y-halfHeight; + } + + const onButtonClick = (evt) => { + // console.log('Rect clicked!!!'); + }; + + const onStart = (evt) => { + evt.preventDefault(); + onDragStart(evt, data.id); + }; + + const nodeRectEvents = connected ? { + onMouseDown: onStart, + onTouchStart: onStart + } : {}; + + const nodeContent = data.children ? + data.children.map((d, i) => ( + + )) : ( + + ); + + return ( + + + + + + {nodeContent} + + ); +}; + +GraphNode.propTypes = { + connected: React.PropTypes.bool, + data: React.PropTypes.object.isRequired, + index: React.PropTypes.number.isRequired, + onDragStart: React.PropTypes.func +}; + +export default Baseline( + GraphNode +); diff --git a/ui/src/components/topology/graph-node/info.js b/ui/src/components/topology/graph-node/info.js new file mode 100644 index 00000000..242d686d --- /dev/null +++ b/ui/src/components/topology/graph-node/info.js @@ -0,0 +1,67 @@ +import React from 'react'; +//import styled from 'styled-components'; +import { Baseline } from '../../../shared/composers'; +//import { colors } from '../../../shared/constants'; +//import DataCentresIcon from './icon-data-centers.svg'; +//import InstancesIcon from './icon-instances.svg'; +import PropTypes from '../prop-types'; +import { GraphText } from './shapes'; + +/*const StyledInstancesIcon = styled(InstancesIcon)` + fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; +`; + +const StyledDataCentresIcon = styled(DataCentresIcon)` + fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; +`;*/ + +const GraphNodeInfo = ({ + connected, + datacentres, + instances, + healthy, + pos +}) => { + + const { + x, + y + } = pos; + + return ( + + + {/*}*} + + + {`${datacentres} inst.`} + + + {/**/} + + + {`${instances} DCs`} + + + ); +}; + +GraphNodeInfo.propTypes = { + connected: React.PropTypes.bool, + datacentres: React.PropTypes.number, + healthy: React.PropTypes.bool, + instances: React.PropTypes.number, + pos: PropTypes.Point.isRequired +}; + +export default Baseline( + GraphNodeInfo +); diff --git a/ui/src/components/topology/graph-node/metrics.js b/ui/src/components/topology/graph-node/metrics.js new file mode 100644 index 00000000..dc33697b --- /dev/null +++ b/ui/src/components/topology/graph-node/metrics.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { Baseline } from '../../../shared/composers'; +import PropTypes from '../prop-types'; +import { GraphText } from './shapes'; + +const GraphNodeMetrics = ({ + connected, + metrics, + pos +}) => { + + const { + x, + y + } = pos; + + const metricSpacing = 18; + const metricsText = metrics.map((metric, index) => ( + + {`${metric.name}: ${metric.value}`} + + )); + + return ( + + {metricsText} + + ); +}; + +GraphNodeMetrics.propTypes = { + connected: React.PropTypes.bool, + metrics: React.PropTypes.arrayOf(React.PropTypes.shape({ + name: React.PropTypes.string.isRequired, + value: React.PropTypes.string.isRequired + })), + pos: PropTypes.Point.isRequired +}; + +export default Baseline( + GraphNodeMetrics +); diff --git a/ui/src/components/topology/graph-node/shapes.js b/ui/src/components/topology/graph-node/shapes.js new file mode 100644 index 00000000..8ad486c7 --- /dev/null +++ b/ui/src/components/topology/graph-node/shapes.js @@ -0,0 +1,55 @@ +import styled from 'styled-components'; +import { colors } from '../../../shared/constants'; + +export const GraphLine = styled.line` + stroke: ${props => props.connected ? + colors.base.secondaryActive : colors.base.grey}; + stroke-width: 1.5; +`; + +export const GraphNodeRect = styled.rect` + stroke: ${props => props.connected ? + colors.base.secondaryActive : colors.base.grey}; + fill: ${props => props.connected ? colors.base.secondary : colors.base.white}; + stroke-width: 1.5; + rx: 4; + ry: 4; +`; + +export const GraphShadowRect = styled.rect` + fill: ${props => props.connected ? colors.base.secondary : colors.base.grey}; + opacity: 0.33; + rx: 4; + ry: 4; +`; + +export const GraphTitle = styled.text` + fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; + font-size: 16px; + font-weight: 600; +`; + +export const GraphSubtitle = styled.text` + fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; + font-size: 12px; + font-weight: 600; +`; + +export const GraphText = styled.text` + fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; + font-size: 12px; + opacity: 0.8; +`; + +export const GraphButtonRect = styled.rect` + opacity: 0; + cursor: pointer; +`; + +export const GraphButtonCircle = styled.circle` + fill: ${props => props.connected ? colors.base.white : colors.base.secondary}; +`; + +export const GraphHealthyCircle = styled.circle` + fill: ${colors.base.green}; +`; diff --git a/ui/src/components/topology/graph-node/title.js b/ui/src/components/topology/graph-node/title.js new file mode 100644 index 00000000..4d09fac2 --- /dev/null +++ b/ui/src/components/topology/graph-node/title.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { Baseline } from '../../../shared/composers'; +import Constants from '../constants'; +import { GraphTitle, GraphHealthyCircle } from './shapes'; +// import HeartIcon from './icon-heart.svg'; + +const GraphNodeTitle = ({ + connected, + data +}) => { + + return ( + + + {data.name} + + + + {/**/} + + + ); +}; + +GraphNodeTitle.propTypes = { + connected: React.PropTypes.bool, + data: React.PropTypes.object.isRequired +}; + +export default Baseline( + GraphNodeTitle +); diff --git a/ui/src/components/topology/index.js b/ui/src/components/topology/index.js index cbd63aa3..3207d036 100644 --- a/ui/src/components/topology/index.js +++ b/ui/src/components/topology/index.js @@ -1,13 +1,9 @@ import TopologyGraph from './topology-graph'; import TopologyGraphNode from './graph-node'; import TopologyGraphLink from './graph-link'; -import TopologyGraphNodeButton from './graph-node-button'; -import TopologyGraphNodeMetrics from './graph-node-metrics'; export { TopologyGraph, TopologyGraphNode, - TopologyGraphLink, - TopologyGraphNodeButton, - TopologyGraphNodeMetrics + TopologyGraphLink }; diff --git a/ui/src/components/topology/story.js b/ui/src/components/topology/story.js index f790472c..bac12622 100644 --- a/ui/src/components/topology/story.js +++ b/ui/src/components/topology/story.js @@ -26,13 +26,7 @@ storiesOf('Topology', module) }, { name: 'Network', stat: '5.9KB/sec' - }], - x: 0, - y: 0 - }} - size={{ - width: 180, - height: 156 + }] }} connected={false} /> diff --git a/ui/src/components/topology/topology-graph.js b/ui/src/components/topology/topology-graph.js index 8b0947ef..b5a7d743 100644 --- a/ui/src/components/topology/topology-graph.js +++ b/ui/src/components/topology/topology-graph.js @@ -27,7 +27,7 @@ let dragInfo = { }; class TopologyGraph extends React.Component { - + componentWillMount() { const services = this.props.services; @@ -111,19 +111,23 @@ class TopologyGraph extends React.Component { links } = this.state; - const node = (nodeId) => - nodes.reduce((acc, simNode, index) => { - return simNode.id === nodeId ? simNode : acc; - }, {}); + const simNode = (nodeId) => + nodes.reduce((acc, simNode, index) => + simNode.id === nodeId ? simNode : acc, {}); const nodesData = services.map((service, index) => ({ ...service, - ...node(service.uuid) + ...simNode(service.uuid) })); + const nodeData = (nodeId) => + nodesData.reduce((acc, nodeData, index) => + nodeData.id === nodeId ? nodeData : acc, {}); + // TODO links will need to know whether a service has children + // if it does, the height of it will be different const linksData = links.map((link, index) => ({ - source: node(link.source.id), - target: node(link.target.id) + source: nodeData(link.source.id), + target: nodeData(link.target.id) })); const onDragStart = (evt, nodeId) => { @@ -207,7 +211,6 @@ class TopologyGraph extends React.Component { key={index} data={n} index={index} - size={nodeSize} onDragStart={onDragStart} connected /> @@ -218,7 +221,6 @@ class TopologyGraph extends React.Component { key={index} data={l} index={index} - nodeSize={nodeSize} /> ));