feat(ui-toolkit): remove topology
This commit is contained in:
parent
af817cf059
commit
ee30f5e7bf
@ -42,21 +42,17 @@
|
||||
"babel-template": "^6.26.0",
|
||||
"camel-case": "^3.0.0",
|
||||
"cross-env": "^5.0.5",
|
||||
"d3": "^4.11.0",
|
||||
"disable-scroll": "^0.3.0",
|
||||
"file-loader": "^1.1.5",
|
||||
"fontfaceobserver": "^2.0.13",
|
||||
"joy-react-broadcast": "^0.6.9",
|
||||
"joyent-manifest-editor": "^1.4.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"lodash.differenceby": "^4.8.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.isequalwith": "^4.4.0",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"moment": "^2.19.1",
|
||||
"normalized-styled-components": "^1.0.17",
|
||||
"pascal-case": "^2.0.1",
|
||||
"polished": "^1.8.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-bundle": "^1.0.4",
|
||||
"react-input-range": "^1.2.1",
|
||||
|
@ -12,7 +12,6 @@ export { default as Small } from './text/small';
|
||||
export { default as Title } from './text/title';
|
||||
export { default as theme } from './theme';
|
||||
export { default as typography, fonts } from './typography';
|
||||
export { default as Topology } from './topology';
|
||||
export { default as Modal, ModalHeading, ModalText } from './modal';
|
||||
export { default as Chevron } from './chevron';
|
||||
export { default as CloseButton } from './close-button';
|
||||
|
@ -1,94 +0,0 @@
|
||||
const Lengths = {
|
||||
paddingLeft: 12,
|
||||
nodeWidth: 180,
|
||||
statusHeight: 18
|
||||
};
|
||||
|
||||
const Sizes = {
|
||||
buttonSize: {
|
||||
width: 40,
|
||||
height: 48
|
||||
},
|
||||
contentSize: {
|
||||
width: Lengths.nodeWidth,
|
||||
// height: 101 // This is the height w/o info comp
|
||||
height: 42
|
||||
},
|
||||
childContentSize: {
|
||||
width: Lengths.nodeWidth,
|
||||
height: 60
|
||||
},
|
||||
nodeSize: {
|
||||
width: Lengths.nodeWidth,
|
||||
// height: 156
|
||||
height: 90
|
||||
},
|
||||
nodeSizeWithChildren: {
|
||||
width: Lengths.nodeWidth,
|
||||
// height: 276
|
||||
height: 176
|
||||
}
|
||||
};
|
||||
|
||||
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 = {
|
||||
// X, y, width, height
|
||||
buttonRect: {
|
||||
...Sizes.buttonSize,
|
||||
...Points.buttonPosition
|
||||
},
|
||||
contentRect: {
|
||||
...Sizes.contentSize,
|
||||
...Points.contentPosition
|
||||
},
|
||||
childContentRect: {
|
||||
...Sizes.childContentSize,
|
||||
...Points.contentPosition
|
||||
},
|
||||
// Top, bottom, left, right - from 'centre'
|
||||
nodeRect: {
|
||||
...Sizes.nodeSize,
|
||||
left: -Sizes.nodeSize.width / 2,
|
||||
right: Sizes.nodeSize.width / 2,
|
||||
top: -Sizes.nodeSize.height / 2,
|
||||
bottom: Sizes.nodeSize.height / 2
|
||||
},
|
||||
nodeRectWithChildren: {
|
||||
...Sizes.nodeSizeWithChildren,
|
||||
left: -Sizes.nodeSizeWithChildren.width / 2,
|
||||
right: Sizes.nodeSizeWithChildren.width / 2,
|
||||
top: -Sizes.nodeSizeWithChildren.height / 2 + Sizes.contentSize.height / 3,
|
||||
bottom: Sizes.nodeSizeWithChildren.height / 2 + Sizes.contentSize.height / 3
|
||||
}
|
||||
};
|
||||
|
||||
const Constants = {
|
||||
...Lengths,
|
||||
...Sizes,
|
||||
...Points,
|
||||
...Rects
|
||||
};
|
||||
|
||||
export default Constants;
|
File diff suppressed because it is too large
Load Diff
@ -1,147 +0,0 @@
|
||||
[
|
||||
{
|
||||
"uuid": "081a792c-47e0-4439-924b-2efa9788ae9e",
|
||||
"id": "nginx",
|
||||
"name": "Nginx",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"instances": [""],
|
||||
"metrics": [
|
||||
{
|
||||
"name": "CPU",
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"value": "20%"
|
||||
},
|
||||
{
|
||||
"name": "Network",
|
||||
"value": "2.9Kb/sec"
|
||||
}
|
||||
],
|
||||
"connections": ["be227788-74f1-4e5b-a85f-b5c71cbae8d8"],
|
||||
"healthy": true,
|
||||
"datacentres": 1
|
||||
},
|
||||
{
|
||||
"uuid": "be227788-74f1-4e5b-a85f-b5c71cbae8d8",
|
||||
"id": "wordpress",
|
||||
"name": "Wordpress",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"instances": ["", ""],
|
||||
"metrics": [
|
||||
{
|
||||
"name": "CPU",
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"value": "20%"
|
||||
},
|
||||
{
|
||||
"name": "Network",
|
||||
"value": "2.9Kb/sec"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
"6a0eee76-c019-413b-9d5f-44712b55b993",
|
||||
"6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"4ee4103e-1a52-4099-a48e-01588f597c70"
|
||||
],
|
||||
"healthy": true,
|
||||
"datacentres": 2
|
||||
},
|
||||
{
|
||||
"uuid": "6a0eee76-c019-413b-9d5f-44712b55b993",
|
||||
"id": "nfs",
|
||||
"name": "NFS",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"instances": ["", ""],
|
||||
"metrics": [
|
||||
{
|
||||
"name": "CPU",
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"value": "20%"
|
||||
},
|
||||
{
|
||||
"name": "Network",
|
||||
"value": "2.9Kb/sec"
|
||||
}
|
||||
],
|
||||
"healthy": true,
|
||||
"datacentres": 2
|
||||
},
|
||||
{
|
||||
"uuid": "6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"id": "memcached",
|
||||
"name": "Memcached",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"instances": ["", ""],
|
||||
"metrics": [
|
||||
{
|
||||
"name": "CPU",
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"value": "20%"
|
||||
},
|
||||
{
|
||||
"name": "Network",
|
||||
"value": "2.9Kb/sec"
|
||||
}
|
||||
],
|
||||
"healthy": true,
|
||||
"datacentres": 2
|
||||
},
|
||||
{
|
||||
"uuid": "4ee4103e-1a52-4099-a48e-01588f597c70",
|
||||
"id": "percona",
|
||||
"name": "Percona",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"instances": ["", ""],
|
||||
"metrics": [
|
||||
{
|
||||
"name": "CPU",
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"value": "20%"
|
||||
},
|
||||
{
|
||||
"name": "Network",
|
||||
"value": "2.9Kb/sec"
|
||||
}
|
||||
],
|
||||
"healthy": true,
|
||||
"datacentres": 1
|
||||
},
|
||||
{
|
||||
"uuid": "97c68055-db88-45c9-ad49-f26da4264777",
|
||||
"id": "consul",
|
||||
"name": "Consul",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"instances": ["", ""],
|
||||
"isConsul": true,
|
||||
"metrics": [
|
||||
{
|
||||
"name": "CPU",
|
||||
"value": "50%"
|
||||
},
|
||||
{
|
||||
"name": "Memory",
|
||||
"value": "20%"
|
||||
},
|
||||
{
|
||||
"name": "Network",
|
||||
"value": "2.9Kb/sec"
|
||||
}
|
||||
],
|
||||
"healthy": true,
|
||||
"datacentres": 2
|
||||
}
|
||||
]
|
@ -1,148 +0,0 @@
|
||||
import Constants from './constants';
|
||||
|
||||
const getAngleFromPoints = (source, target) => {
|
||||
const lineAngle = Math.atan2(target.y - source.y, target.x - source.x);
|
||||
const lineAngleDeg = lineAngle * 180 / Math.PI;
|
||||
const zeroToThreeSixty = lineAngleDeg < 0 ? 360 + lineAngleDeg : lineAngleDeg;
|
||||
|
||||
return zeroToThreeSixty;
|
||||
};
|
||||
|
||||
const getPosition = (angle, positions, position, noCorners = false) => {
|
||||
const positionIndex = noCorners
|
||||
? Math.round(angle / 90) * 2
|
||||
: Math.round(angle / 45);
|
||||
|
||||
const offsetPosition = positions[positionIndex];
|
||||
|
||||
return {
|
||||
id: offsetPosition.id,
|
||||
x: position.x + offsetPosition.x,
|
||||
y: position.y + offsetPosition.y
|
||||
};
|
||||
};
|
||||
|
||||
const getPositions = (rect, halfCorner = 0) => [
|
||||
{
|
||||
id: 'r',
|
||||
x: rect.right,
|
||||
y: 0
|
||||
},
|
||||
{
|
||||
id: 'br',
|
||||
x: rect.right - halfCorner,
|
||||
y: rect.bottom - halfCorner
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
x: 0,
|
||||
y: rect.bottom
|
||||
},
|
||||
{
|
||||
id: 'bl',
|
||||
x: rect.left + halfCorner,
|
||||
y: rect.bottom - halfCorner
|
||||
},
|
||||
{
|
||||
id: 'l',
|
||||
x: rect.left,
|
||||
y: 0
|
||||
},
|
||||
{
|
||||
id: 'tl',
|
||||
x: rect.left + halfCorner,
|
||||
y: rect.top + halfCorner
|
||||
},
|
||||
{
|
||||
id: 't',
|
||||
x: 0,
|
||||
y: rect.top
|
||||
},
|
||||
{
|
||||
id: 'tr',
|
||||
x: rect.right - halfCorner,
|
||||
y: rect.top + halfCorner
|
||||
},
|
||||
{
|
||||
id: 'r',
|
||||
x: rect.right,
|
||||
y: 0
|
||||
}
|
||||
];
|
||||
|
||||
/* const getRect = data =>
|
||||
data.children ? Constants.nodeRectWithChildren : Constants.nodeRect; */
|
||||
|
||||
const calculateLineLayout = ({ source, target }) => {
|
||||
// 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 halfCorner = 2;
|
||||
|
||||
const sourcePositions = getPositions(source.nodeRect, halfCorner);
|
||||
const sourceAngle = getAngleFromPoints(source, target);
|
||||
const sourcePosition = getPosition(sourceAngle, sourcePositions, source);
|
||||
|
||||
const targetPositions = getPositions(target.nodeRect, halfCorner);
|
||||
const targetAngle = getAngleFromPoints(target, sourcePosition);
|
||||
const targetPosition = getPosition(targetAngle, targetPositions, target); // , true);
|
||||
|
||||
const arrowAngle = getAngleFromPoints(sourcePosition, targetPosition);
|
||||
|
||||
return {
|
||||
source,
|
||||
target,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
arrowAngle
|
||||
};
|
||||
};
|
||||
|
||||
const getStatusesLength = data =>
|
||||
data.transitionalStatus ? 1 : data.instanceStatuses.length;
|
||||
|
||||
const getStatusesHeight = data => {
|
||||
const statuses = data.children
|
||||
? data.children.reduce(
|
||||
(statuses, child) => statuses + getStatusesLength(child),
|
||||
0
|
||||
)
|
||||
: getStatusesLength(data);
|
||||
|
||||
return statuses ? Constants.statusHeight * statuses + 6 : 0;
|
||||
};
|
||||
|
||||
const getContentRect = (data, isChild = false) => {
|
||||
const contentSize = isChild
|
||||
? Constants.childContentSize
|
||||
: Constants.contentSize;
|
||||
|
||||
const { height } = contentSize;
|
||||
const contentHeight = height + getStatusesHeight(data);
|
||||
|
||||
return {
|
||||
...Constants.contentPosition,
|
||||
width: contentSize.width,
|
||||
height: contentHeight
|
||||
};
|
||||
};
|
||||
|
||||
const getNodeRect = data => {
|
||||
const nodeSize = data.children
|
||||
? Constants.nodeSizeWithChildren
|
||||
: Constants.nodeSize;
|
||||
|
||||
const { width, height } = nodeSize;
|
||||
const nodeHeight = height + getStatusesHeight(data);
|
||||
|
||||
return {
|
||||
left: -width / 2,
|
||||
right: width / 2,
|
||||
top: -height / 2,
|
||||
bottom: nodeHeight - height / 2,
|
||||
width,
|
||||
height: nodeHeight
|
||||
};
|
||||
};
|
||||
|
||||
export { getContentRect, getNodeRect, calculateLineLayout };
|
@ -1,462 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Svg } from 'normalized-styled-components';
|
||||
import PropTypes from 'prop-types';
|
||||
import difference from 'lodash.difference';
|
||||
import differenceBy from 'lodash.differenceby';
|
||||
|
||||
import Baseline from '../baseline';
|
||||
import Constants from './constants';
|
||||
import { createSimulation } from './simulation';
|
||||
import TopologyNode from './node';
|
||||
import TopologyLink from './link';
|
||||
import TopologyLinkArrow from './link/arrow';
|
||||
import { getNodeRect, calculateLineLayout } from './functions';
|
||||
|
||||
const StyledSvg = Svg.extend`
|
||||
width: 100%;
|
||||
height: 1000px;
|
||||
`;
|
||||
|
||||
/**
|
||||
* @example ./usage.md
|
||||
*/
|
||||
class Topology extends React.Component {
|
||||
componentWillMount() {
|
||||
this.create(this.props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.boundResize = this.handleResize.bind(this);
|
||||
window.addEventListener('resize', this.boundResize);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.boundResize);
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
getChangedConnections(services, nextServices) {
|
||||
return nextServices.reduce((changed, nextService) => {
|
||||
if (changed.added || changed.removed) {
|
||||
return changed;
|
||||
}
|
||||
const service = services
|
||||
.filter(service => service.id === nextService.id)
|
||||
.shift();
|
||||
const connectionsAdded = difference(
|
||||
nextService.connections,
|
||||
service.connections
|
||||
).length;
|
||||
// there's a new connection, we need to redraw
|
||||
if (connectionsAdded) {
|
||||
return { added: true };
|
||||
}
|
||||
const connectionsRemoved = difference(
|
||||
service.connections,
|
||||
nextService.connections
|
||||
).length;
|
||||
// we'll need to remove the offending connections from links
|
||||
if (connectionsRemoved) {
|
||||
return { removed: true };
|
||||
}
|
||||
return changed;
|
||||
}, {});
|
||||
}
|
||||
|
||||
getNextLinks(nextServices) {
|
||||
const links = this.state.links;
|
||||
return links.reduce((nextLinks, link) => {
|
||||
const sourceExists = nextServices.filter(
|
||||
nextService => nextService.id === link.source.id
|
||||
);
|
||||
if (sourceExists.length) {
|
||||
const source = sourceExists.shift();
|
||||
const targetExists = nextServices.filter(
|
||||
nextService => nextService.id === link.target.id
|
||||
).length;
|
||||
const connectionExists = source.connections.filter(
|
||||
connection => connection === link.target.id
|
||||
).length;
|
||||
if (targetExists && connectionExists) {
|
||||
nextLinks.push(link);
|
||||
}
|
||||
}
|
||||
return nextLinks;
|
||||
}, []);
|
||||
}
|
||||
|
||||
getNextNodes(nextServices) {
|
||||
const nodes = this.state.nodes;
|
||||
// let notConnectedX = 0;
|
||||
return nodes.reduce((nextNodes, node) => {
|
||||
const keep = nextServices.filter(
|
||||
nextService => nextService.id === node.id
|
||||
).length;
|
||||
if (keep) {
|
||||
nextNodes.push(node);
|
||||
}
|
||||
return nextNodes;
|
||||
}, []);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// if we remove a node, it should just be removed from the simulation nodes and links
|
||||
// if we add a node, then we should recreate the damn thing
|
||||
// on other updates, we should update the services on the state and that's it
|
||||
// we should forceUpdate once the state has been updated
|
||||
const nextServices = nextProps.services.sort();
|
||||
const connectedNextServices = nextServices.filter(
|
||||
service => service.connected
|
||||
);
|
||||
const notConnectedNextServices = nextServices.filter(
|
||||
service => !service.connected
|
||||
);
|
||||
|
||||
const { services } = this.state;
|
||||
if (nextServices.length > services.length) {
|
||||
// new service added, we need to redraw
|
||||
this.create(nextProps);
|
||||
} else if (nextServices.length <= services.length) {
|
||||
const servicesRemoved = differenceBy(services, nextServices, 'id');
|
||||
const servicesChanged = differenceBy(nextServices, services, 'id');
|
||||
if (
|
||||
servicesChanged.length ||
|
||||
servicesRemoved.length !== services.length - nextServices.length
|
||||
) {
|
||||
this.create(nextProps);
|
||||
} else {
|
||||
// check whether there are new connections. if so, we need to redraw
|
||||
// if we just dropped one, we need to remove it from links
|
||||
// comparison to yield 3 possible outcomes; no change, added, dropped
|
||||
const changedConnections = this.getChangedConnections(
|
||||
services,
|
||||
nextServices
|
||||
);
|
||||
// if connections are added, we'll need to redraw
|
||||
if (changedConnections.added) {
|
||||
this.create(nextProps);
|
||||
} else if (servicesRemoved.length || changedConnections.removed) {
|
||||
const nextNodes = this.getNextNodes(connectedNextServices);
|
||||
const notConnectedNodes = this.getNotConnectedNodes(
|
||||
notConnectedNextServices
|
||||
);
|
||||
const nextLinks = this.getNextLinks(nextServices);
|
||||
|
||||
this.setState(
|
||||
{
|
||||
services: nextServices,
|
||||
links: nextLinks,
|
||||
nodes: nextNodes,
|
||||
notConnectedNodes
|
||||
},
|
||||
() => this.forceUpdate()
|
||||
);
|
||||
} else {
|
||||
// we've got the same services, no links changed, so we just need to set them to the state
|
||||
this.setState({ services: nextServices }, () => this.forceUpdate());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getNotConnectedNodes(notConnectedServices) {
|
||||
return notConnectedServices.map((notConnectedService, index) => {
|
||||
const svgSize = this.getSvgSize();
|
||||
const x = notConnectedService.isConsul
|
||||
? svgSize.width - Constants.nodeSize.width
|
||||
: (Constants.nodeSize.width + 10) * index;
|
||||
|
||||
return {
|
||||
id: notConnectedService.id,
|
||||
x,
|
||||
y: 0
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handleResize(evt) {
|
||||
this.create(this.props);
|
||||
// resize should just rejig the positions
|
||||
}
|
||||
|
||||
create(props) {
|
||||
// other updates should also just update the services rather than recreate the simulation
|
||||
const services = props.services.sort();
|
||||
const connectedServices = services.filter(service => service.connected);
|
||||
const notConnectedServices = services.filter(service => !service.connected);
|
||||
const svgSize = this.getSvgSize();
|
||||
|
||||
const { nodes, links, simulation } = createSimulation(
|
||||
connectedServices,
|
||||
svgSize
|
||||
);
|
||||
const notConnectedNodes = this.getNotConnectedNodes(notConnectedServices);
|
||||
|
||||
this.setState(
|
||||
{
|
||||
notConnectedNodes,
|
||||
nodes,
|
||||
links,
|
||||
simulation,
|
||||
services
|
||||
},
|
||||
() => {
|
||||
this.forceUpdate();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getSvgSize() {
|
||||
if (document.getElementById('topology-svg')) {
|
||||
return document.getElementById('topology-svg').getBoundingClientRect();
|
||||
}
|
||||
|
||||
const windowWidth =
|
||||
window.innerWidth ||
|
||||
document.documentElement.clientWidth ||
|
||||
document.body.clientWidth;
|
||||
|
||||
return {
|
||||
width: windowWidth - 2 * 24,
|
||||
height: 1000
|
||||
};
|
||||
}
|
||||
|
||||
constrainNodePosition(x, y, nodeRect, children = false) {
|
||||
const svgSize = this.getSvgSize();
|
||||
|
||||
/* const nodeRect = children
|
||||
? Constants.nodeRectWithChildren
|
||||
: Constants.nodeRect; */
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
findNode(nodeId) {
|
||||
return this.state.nodes.reduce(
|
||||
(acc, simNode, index) => (simNode.id === nodeId ? simNode : acc),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
getConstrainedNodePosition(nodeId, nodeRect, children = false) {
|
||||
const node = this.findNode(nodeId);
|
||||
return this.constrainNodePosition(node.x, node.y, nodeRect, children);
|
||||
}
|
||||
|
||||
getNotConnectedNodePosition(nodeId) {
|
||||
return this.state.notConnectedNodes
|
||||
.filter(ncn => ncn.id === nodeId)
|
||||
.shift();
|
||||
}
|
||||
|
||||
findNodeData(nodesData, nodeId) {
|
||||
return nodesData.filter(nodeData => nodeData.id === nodeId).shift();
|
||||
}
|
||||
|
||||
setDragInfo(dragging, nodeId = null, position = {}) {
|
||||
this.dragInfo = {
|
||||
dragging,
|
||||
nodeId,
|
||||
position
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onQuickActionsClick, onNodeTitleClick } = this.props;
|
||||
|
||||
const { nodes, links, services } = this.state;
|
||||
|
||||
const nodesData = services.map((service, index) => {
|
||||
const nodeRect = getNodeRect(service);
|
||||
const nodePosition = service.connected
|
||||
? this.getConstrainedNodePosition(
|
||||
service.id,
|
||||
nodeRect,
|
||||
service.children
|
||||
)
|
||||
: this.getNotConnectedNodePosition(service.id);
|
||||
|
||||
return {
|
||||
...service,
|
||||
...nodePosition,
|
||||
nodeRect
|
||||
};
|
||||
});
|
||||
|
||||
// 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: this.findNodeData(nodesData, link.source.id),
|
||||
target: this.findNodeData(nodesData, link.target.id)
|
||||
}))
|
||||
.map((linkData, index) => {
|
||||
return calculateLineLayout(linkData, index);
|
||||
});
|
||||
|
||||
const onDragStart = (evt, nodeId) => {
|
||||
// It's this node's position that we'll need to update
|
||||
|
||||
const x = evt.changedTouches ? evt.changedTouches[0].pageX : evt.clientX;
|
||||
const y = evt.changedTouches ? evt.changedTouches[0].pageY : evt.clientY;
|
||||
|
||||
this.setDragInfo(true, nodeId, {
|
||||
x,
|
||||
y
|
||||
});
|
||||
};
|
||||
|
||||
const onDragMove = evt => {
|
||||
if (this.dragInfo && this.dragInfo.dragging) {
|
||||
const x = evt.changedTouches
|
||||
? evt.changedTouches[0].pageX
|
||||
: evt.clientX;
|
||||
const y = evt.changedTouches
|
||||
? evt.changedTouches[0].pageY
|
||||
: evt.clientY;
|
||||
|
||||
const offset = {
|
||||
x: x - this.dragInfo.position.x,
|
||||
y: y - this.dragInfo.position.y
|
||||
};
|
||||
|
||||
const dragNodes = nodes.map((simNode, index) => {
|
||||
if (simNode.id === this.dragInfo.nodeId) {
|
||||
return {
|
||||
...simNode,
|
||||
x: simNode.x + offset.x,
|
||||
y: simNode.y + offset.y
|
||||
};
|
||||
}
|
||||
return {
|
||||
...simNode
|
||||
};
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{
|
||||
nodes: dragNodes
|
||||
},
|
||||
() => this.forceUpdate()
|
||||
);
|
||||
|
||||
this.setDragInfo(true, this.dragInfo.nodeId, {
|
||||
x,
|
||||
y
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onDragEnd = evt => {
|
||||
this.setDragInfo(false);
|
||||
};
|
||||
|
||||
const renderedNode = (n, index) => (
|
||||
<TopologyNode
|
||||
key={index}
|
||||
data={n}
|
||||
index={index}
|
||||
onDragStart={onDragStart}
|
||||
onNodeTitleClick={onNodeTitleClick}
|
||||
onQuickActions={onQuickActionsClick}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderedLink = (l, index) => (
|
||||
<TopologyLink key={index} data={l} index={index} />
|
||||
);
|
||||
|
||||
const renderedLinkArrow = (l, index) => (
|
||||
<TopologyLinkArrow key={index} data={l} index={index} />
|
||||
);
|
||||
|
||||
const renderedNodes =
|
||||
this.dragInfo && this.dragInfo.dragging
|
||||
? nodesData
|
||||
.filter((n, index) => n.id !== this.dragInfo.nodeId)
|
||||
.map((n, index) => renderedNode(n, index))
|
||||
: nodesData.map((n, index) => renderedNode(n, index));
|
||||
|
||||
const renderedLinks = linksData.map((l, index) => renderedLink(l, index));
|
||||
|
||||
const renderedLinkArrows =
|
||||
this.dragInfo && this.dragInfo.dragging
|
||||
? linksData
|
||||
.filter((l, index) => l.target.id !== this.dragInfo.nodeId)
|
||||
.map((l, index) => renderedLinkArrow(l, index))
|
||||
: linksData.map((l, index) => renderedLinkArrow(l, index));
|
||||
|
||||
const dragNode =
|
||||
!this.dragInfo || !this.dragInfo.dragging
|
||||
? null
|
||||
: renderedNode(
|
||||
nodesData.reduce((dragNode, n, index) => {
|
||||
if (n.id === this.dragInfo.nodeId) {
|
||||
return n;
|
||||
}
|
||||
return dragNode;
|
||||
}, {})
|
||||
);
|
||||
|
||||
const dragLinkArrow =
|
||||
!this.dragInfo ||
|
||||
!this.dragInfo.dragging ||
|
||||
renderedLinkArrows.length === renderedLinks.length
|
||||
? null
|
||||
: renderedLinkArrow(
|
||||
linksData.reduce((dragLinkArrow, l, index) => {
|
||||
if (l.target.id === this.dragInfo.nodeId) {
|
||||
return l;
|
||||
}
|
||||
return dragLinkArrow;
|
||||
}, {})
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledSvg
|
||||
onMouseMove={onDragMove}
|
||||
onTouchMove={onDragMove}
|
||||
onMouseUp={onDragEnd}
|
||||
onTouchEnd={onDragEnd}
|
||||
onTouchCancel={onDragEnd}
|
||||
id="topology-svg"
|
||||
>
|
||||
<g>{renderedNodes}</g>
|
||||
<g>{renderedLinks}</g>
|
||||
<g>{renderedLinkArrows}</g>
|
||||
<g>{dragNode}</g>
|
||||
<g>{dragLinkArrow}</g>
|
||||
</StyledSvg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Topology.propTypes = {
|
||||
onQuickActionsClick: PropTypes.func,
|
||||
onNodeTitleClick: PropTypes.func,
|
||||
services: PropTypes.array
|
||||
};
|
||||
|
||||
export default Baseline(Topology);
|
||||
|
||||
export { default as TopologyNode } from './node';
|
||||
export { default as TopologyLink } from './link';
|
@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { GraphLinkCircle, GraphLinkArrowLine } from './shapes';
|
||||
import Baseline from '../../baseline';
|
||||
|
||||
const GraphLinkArrow = ({ data, index }) => {
|
||||
const { targetPosition, arrowAngle } = data;
|
||||
|
||||
return (
|
||||
<g
|
||||
transform={// eslint-disable-next-line max-len
|
||||
`translate(${targetPosition.x}, ${targetPosition.y}) rotate(${arrowAngle})`}
|
||||
>
|
||||
<GraphLinkCircle cx={0} cy={0} r={9} />
|
||||
<GraphLinkArrowLine x1={-1} x2={2} y1={-3} y2={0} />
|
||||
<GraphLinkArrowLine x1={-1} x2={2} y1={3} y2={0} />
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
GraphLinkArrow.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
index: PropTypes.number
|
||||
};
|
||||
|
||||
export default Baseline(GraphLinkArrow);
|
@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { GraphLinkLine } from './shapes';
|
||||
import Baseline from '../../baseline';
|
||||
|
||||
const GraphLink = ({ data, index }) => {
|
||||
const { sourcePosition, targetPosition } = data;
|
||||
|
||||
return (
|
||||
<GraphLinkLine
|
||||
x1={sourcePosition.x}
|
||||
x2={targetPosition.x}
|
||||
y1={sourcePosition.y}
|
||||
y2={targetPosition.y}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
GraphLink.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
index: PropTypes.number
|
||||
};
|
||||
|
||||
export default Baseline(GraphLink);
|
@ -1,18 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const GraphLinkLine = styled.line`
|
||||
stroke: ${props => props.theme.secondaryActive};
|
||||
stroke-width: 1.5;
|
||||
`;
|
||||
|
||||
export const GraphLinkCircle = styled.circle`
|
||||
stroke: ${props => props.theme.secondaryActive};
|
||||
fill: ${props => props.theme.secondary};
|
||||
stroke-width: 1.5;
|
||||
`;
|
||||
|
||||
export const GraphLinkArrowLine = styled.line`
|
||||
stroke: ${props => props.theme.white};
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
`;
|
@ -1,58 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Baseline from '../../baseline';
|
||||
import Constants from '../constants';
|
||||
import { GraphLine, GraphButtonRect, GraphButtonCircle } from './shapes';
|
||||
|
||||
const NodeButton = ({ onButtonClick, index, isConsul, instancesActive }) => {
|
||||
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) => (
|
||||
<GraphButtonCircle
|
||||
cx={width / 2}
|
||||
cy={
|
||||
buttonCircleY + (buttonCircleRadius * 2 + buttonCircleSpacing) * index
|
||||
}
|
||||
key={index}
|
||||
r={2}
|
||||
consul={isConsul}
|
||||
active={instancesActive}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<g transform={`translate(${x}, ${y})`}>
|
||||
<GraphLine
|
||||
x1={0}
|
||||
y1={0}
|
||||
x2={0}
|
||||
y2={height}
|
||||
consul={isConsul}
|
||||
active={instancesActive}
|
||||
/>
|
||||
{buttonCircles}
|
||||
<GraphButtonRect
|
||||
height={height}
|
||||
onClick={onButtonClick}
|
||||
onKeyDown={onButtonClick}
|
||||
width={width}
|
||||
role="button"
|
||||
tabIndex={100 + index}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
NodeButton.propTypes = {
|
||||
index: PropTypes.number.isRequired,
|
||||
onButtonClick: PropTypes.func.isRequired,
|
||||
isConsul: PropTypes.bool,
|
||||
instancesActive: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Baseline(NodeButton);
|
@ -1,57 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Baseline from '../../baseline';
|
||||
import Constants from '../constants';
|
||||
import { GraphLine, GraphSubtitle } from './shapes';
|
||||
import GraphNodeInfo from './info';
|
||||
|
||||
const GraphNodeContent = ({
|
||||
child = false,
|
||||
data,
|
||||
y = Constants.contentRect.y,
|
||||
index = 0
|
||||
}) => {
|
||||
const { x, width } = Constants.contentRect;
|
||||
|
||||
const nodeInfoPos = child
|
||||
? {
|
||||
x: Constants.infoPosition.x,
|
||||
y: Constants.infoPosition.y + 21
|
||||
}
|
||||
: Constants.infoPosition;
|
||||
|
||||
const nodeSubtitle = child ? (
|
||||
<GraphSubtitle
|
||||
{...Constants.subtitlePosition}
|
||||
consul={data.isConsul}
|
||||
active={data.instancesActive}
|
||||
>
|
||||
{data.name}
|
||||
</GraphSubtitle>
|
||||
) : null;
|
||||
|
||||
const nodeInfo = <GraphNodeInfo data={data} pos={nodeInfoPos} />;
|
||||
|
||||
return (
|
||||
<g transform={`translate(${x}, ${y})`}>
|
||||
<GraphLine
|
||||
x1={0}
|
||||
y1={0}
|
||||
x2={width}
|
||||
y2={0}
|
||||
consul={data.isConsul}
|
||||
active={data.instancesActive}
|
||||
/>
|
||||
{nodeSubtitle}
|
||||
{nodeInfo}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
GraphNodeContent.propTypes = {
|
||||
child: PropTypes.bool,
|
||||
data: PropTypes.object.isRequired,
|
||||
index: PropTypes.number
|
||||
};
|
||||
|
||||
export default Baseline(GraphNodeContent);
|
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="9px" height="13px" viewBox="0 0 9 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>icon: data center</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill-rule="evenodd">
|
||||
<g id="Project:-topology-of-services" transform="translate(-575.000000, -487.000000)">
|
||||
<g id="services-copy" transform="translate(213.000000, 426.000000)">
|
||||
<g id="service:-nginx" transform="translate(263.000000, 0.000000)">
|
||||
<g id="metric">
|
||||
<g id="data-centers-&-instanecs" transform="translate(18.000000, 59.000000)">
|
||||
<path d="M81,15 L90,15 L90,2 L81,2 L81,15 Z M83,13 L88,13 L88,4 L83,4 L83,13 Z M84,6 L87.001,6 L87.001,5 L84,5 L84,6 Z M84,8 L87.001,8 L87.001,7 L84,7 L84,8 Z M84,10 L87.001,10 L87.001,9 L84,9 L84,10 Z" id="icon:--data-center"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="80px" height="70px" viewBox="0 0 80 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>icon: health</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Project:-topology-of-services" transform="translate(0, 0)" fill="#FFFFFF">
|
||||
<g id="services-copy" transform="translate(0, 0)">
|
||||
<g id="service:-nginx" transform="translate(0, 0)">
|
||||
<g id="icon:-state" transform="translate(0, 0)">
|
||||
<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"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="18px" height="9px" viewBox="0 0 18 9" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>icon: instances</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill-rule="evenodd">
|
||||
<g id="Project:-topology-of-services" transform="translate(-494.000000, -489.000000)">
|
||||
<g id="services-copy" transform="translate(213.000000, 426.000000)">
|
||||
<g id="service:-nginx" transform="translate(263.000000, 0.000000)">
|
||||
<g id="metric">
|
||||
<g id="data-centers-&-instanecs" transform="translate(18.000000, 59.000000)">
|
||||
<path d="M4.5,4 C2.015,4 0,6.015 0,8.5 C0,10.985 2.015,13 4.5,13 C6.985,13 9,10.985 9,8.5 C9,6.015 6.985,4 4.5,4 M13.0909091,4 C12.7145455,4 12.3512727,4.047 12,4.12 C14.184,4.576 15.8181818,6.359 15.8181818,8.5 C15.8181818,10.641 14.184,12.424 12,12.88 C12.3512727,12.953 12.7145455,13 13.0909091,13 C15.8018182,13 18,10.985 18,8.5 C18,6.015 15.8018182,4 13.0909091,4 M14,8.5 C14,10.985 11.8018182,13 9.09090909,13 C8.71454545,13 8.35127273,12.953 8,12.88 C10.184,12.424 11.8181818,10.641 11.8181818,8.5 C11.8181818,6.359 10.184,4.576 8,4.12 C8.35127273,4.047 8.71454545,4 9.09090909,4 C11.8018182,4 14,6.015 14,8.5" id="icon:-instances"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1,124 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Constants from '../constants';
|
||||
import { getContentRect } from '../functions';
|
||||
import GraphNodeTitle from './title';
|
||||
import GraphNodeButton from './button';
|
||||
import GraphNodeContent from './content';
|
||||
import { GraphNodeRect, GraphShadowRect } from './shapes';
|
||||
import Baseline from '../../baseline';
|
||||
|
||||
const GraphNode = ({
|
||||
data,
|
||||
index,
|
||||
onDragStart,
|
||||
onNodeTitleClick,
|
||||
onQuickActions
|
||||
}) => {
|
||||
const { left, top, width, height } = data.nodeRect;
|
||||
const { connected, id, children, instancesActive, isConsul } = data;
|
||||
|
||||
let x = data.x;
|
||||
let y = data.y;
|
||||
|
||||
if (connected) {
|
||||
x = data.x + left;
|
||||
y = data.y + top;
|
||||
}
|
||||
|
||||
const onButtonClick = evt => {
|
||||
const tooltipPosition = {
|
||||
x: data.x + Constants.buttonRect.x + Constants.buttonRect.width / 2,
|
||||
y: data.y + Constants.buttonRect.y + Constants.buttonRect.height
|
||||
};
|
||||
|
||||
if (connected) {
|
||||
tooltipPosition.x += left;
|
||||
tooltipPosition.y += top;
|
||||
}
|
||||
|
||||
const d = {
|
||||
service: data,
|
||||
position: {
|
||||
left: tooltipPosition.x,
|
||||
top: tooltipPosition.y
|
||||
}
|
||||
};
|
||||
|
||||
onQuickActions(evt, d);
|
||||
};
|
||||
|
||||
const onTitleClick = evt => onNodeTitleClick(evt, { service: data });
|
||||
|
||||
const onStart = evt => {
|
||||
evt.preventDefault();
|
||||
onDragStart(evt, id);
|
||||
};
|
||||
|
||||
const nodeRectEvents = connected
|
||||
? {
|
||||
onMouseDown: onStart,
|
||||
onTouchStart: onStart
|
||||
}
|
||||
: {};
|
||||
|
||||
const nodeContent = children ? (
|
||||
children.reduce(
|
||||
(acc, d, i) => {
|
||||
acc.children.push(
|
||||
<GraphNodeContent key={i} child data={d} index={i} y={acc.y} />
|
||||
);
|
||||
acc.y += getContentRect(d, true).height;
|
||||
return acc;
|
||||
},
|
||||
{ y: Constants.contentRect.y, children: [] }
|
||||
).children
|
||||
) : (
|
||||
<GraphNodeContent data={data} />
|
||||
);
|
||||
|
||||
const nodeShadow = instancesActive ? (
|
||||
<GraphShadowRect
|
||||
x={0}
|
||||
y={3}
|
||||
width={width}
|
||||
height={height}
|
||||
consul={isConsul}
|
||||
active={instancesActive}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<g transform={`translate(${x}, ${y})`}>
|
||||
{nodeShadow}
|
||||
<GraphNodeRect
|
||||
x={0}
|
||||
y={0}
|
||||
width={width}
|
||||
height={height}
|
||||
consul={isConsul}
|
||||
active={instancesActive}
|
||||
connected={connected}
|
||||
{...nodeRectEvents}
|
||||
/>
|
||||
<GraphNodeTitle data={data} onNodeTitleClick={onTitleClick} />
|
||||
<GraphNodeButton
|
||||
index={index}
|
||||
onButtonClick={onButtonClick}
|
||||
isConsul={isConsul}
|
||||
instancesActive={instancesActive}
|
||||
/>
|
||||
{nodeContent}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
GraphNode.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
onDragStart: PropTypes.func,
|
||||
onNodeTitleClick: PropTypes.func,
|
||||
onQuickActions: PropTypes.func
|
||||
};
|
||||
|
||||
export default Baseline(GraphNode);
|
@ -1,96 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import is, { isNot } from 'styled-is';
|
||||
import PropTypes from 'prop-types';
|
||||
import Baseline from '../../baseline';
|
||||
import InstancesIcon from './icon-instances.svg';
|
||||
import { Point } from '../prop-types';
|
||||
import { GraphText } from './shapes';
|
||||
import { HealthyIcon } from '../../icons';
|
||||
|
||||
const StyledInstancesIcon = styled(InstancesIcon)`
|
||||
fill: ${props => props.theme.white};
|
||||
|
||||
${is('consul')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
|
||||
${isNot('active')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
`;
|
||||
|
||||
// const StyledDataCentresIcon = styled(DataCentresIcon)`
|
||||
// fill: ${props => props.theme.white};
|
||||
//
|
||||
// ${is('consul')`
|
||||
// fill: ${props => props.theme.secondary};
|
||||
// `};
|
||||
//
|
||||
// ${isNot('active')`
|
||||
// fill: ${props => props.theme.secondary};
|
||||
// `};
|
||||
// `;
|
||||
|
||||
const GraphNodeInfo = ({ data, pos }) => {
|
||||
const {
|
||||
instances,
|
||||
instanceStatuses,
|
||||
instancesHealthy,
|
||||
isConsul,
|
||||
instancesActive,
|
||||
transitionalStatus,
|
||||
status
|
||||
} = data;
|
||||
|
||||
const { x, y } = pos;
|
||||
|
||||
const statuses = transitionalStatus ? (
|
||||
<GraphText consul={isConsul} active={instancesActive}>
|
||||
{status.toLowerCase()}
|
||||
</GraphText>
|
||||
) : (
|
||||
instanceStatuses.map((instanceStatus, index) => (
|
||||
<GraphText key={index} consul={isConsul} active={instancesActive}>
|
||||
{`${instanceStatus.count}
|
||||
${instanceStatus.status.toLowerCase()}`}
|
||||
</GraphText>
|
||||
))
|
||||
);
|
||||
|
||||
const healthy = (
|
||||
<HealthyIcon
|
||||
healthy={
|
||||
instancesHealthy && instancesHealthy.total === instancesHealthy.healthy
|
||||
? 'HEALTHY'
|
||||
: 'UNHEALTHY'
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<g transform={`translate(${x}, ${y})`}>
|
||||
<g transform={`translate(0, 0)`}>{healthy}</g>
|
||||
<g transform={'translate(30, 4.5)'}>
|
||||
<StyledInstancesIcon consul={isConsul} active={instancesActive} />
|
||||
</g>
|
||||
<GraphText x={54} y={14} consul={isConsul} active={instancesActive}>
|
||||
{`${instances.length} inst.`}
|
||||
</GraphText>
|
||||
<g transform={'translate(54, 36)'}>{statuses}</g>
|
||||
{/* <g transform={'translate(82, 0)'}>
|
||||
<StyledDataCentresIcon connected={connected} />
|
||||
</g>
|
||||
<GraphText x={96} y={12} connected={connected}>
|
||||
{datacenter}
|
||||
</GraphText> */}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
GraphNodeInfo.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
pos: Point.isRequired
|
||||
};
|
||||
|
||||
export default Baseline(GraphNodeInfo);
|
@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import Baseline from '../../baseline';
|
||||
import { Point } from '../prop-types';
|
||||
import { GraphText } from './shapes';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const GraphNodeMetrics = ({ connected, metrics, pos }) => {
|
||||
const { x, y } = pos;
|
||||
|
||||
const metricSpacing = 18;
|
||||
const metricsText = metrics.map((metric, index) => (
|
||||
<GraphText
|
||||
key={index}
|
||||
x={0}
|
||||
y={12 + metricSpacing * index}
|
||||
connected={connected}
|
||||
>
|
||||
{`${metric.name}: ${metric.value}`}
|
||||
</GraphText>
|
||||
));
|
||||
|
||||
return <g transform={`translate(${x}, ${y})`}>{metricsText}</g>;
|
||||
};
|
||||
|
||||
GraphNodeMetrics.propTypes = {
|
||||
connected: PropTypes.bool,
|
||||
metrics: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired
|
||||
})
|
||||
),
|
||||
pos: Point.isRequired
|
||||
};
|
||||
|
||||
export default Baseline(GraphNodeMetrics);
|
@ -1,124 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
import is, { isNot } from 'styled-is';
|
||||
import typography from '../../typography';
|
||||
|
||||
export const GraphLine = styled.line`
|
||||
stroke: ${props => props.theme.secondaryActive};
|
||||
stroke-width: 1.5;
|
||||
|
||||
${is('consul')`
|
||||
stroke: ${props => props.theme.grey};
|
||||
`};
|
||||
|
||||
${isNot('active')`
|
||||
stroke: ${props => props.theme.grey};
|
||||
`};
|
||||
`;
|
||||
|
||||
export const GraphNodeRect = styled.rect`
|
||||
fill: ${props => props.theme.secondary};
|
||||
stroke: ${props => props.theme.secondaryActive};
|
||||
stroke-width: 1.5;
|
||||
rx: 4;
|
||||
ry: 4;
|
||||
|
||||
${is('consul')`
|
||||
stroke: ${props => props.theme.grey};
|
||||
fill: ${props => props.theme.white};
|
||||
`};
|
||||
|
||||
${isNot('active')`
|
||||
stroke: ${props => props.theme.grey};
|
||||
fill: ${props => props.theme.whiteActive};
|
||||
`};
|
||||
|
||||
${is('connected')`
|
||||
cursor: move;
|
||||
`};
|
||||
`;
|
||||
|
||||
export const GraphShadowRect = styled.rect`
|
||||
fill: ${props => props.theme.secondary};
|
||||
opacity: 0.33;
|
||||
rx: 4;
|
||||
ry: 4;
|
||||
|
||||
${is('consul')`
|
||||
fill: ${props => props.theme.grey};
|
||||
`};
|
||||
`;
|
||||
|
||||
export const GraphTitle = styled.text`
|
||||
${typography.normal};
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
fill: ${props => props.theme.white};
|
||||
|
||||
${is('consul')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
|
||||
${isNot('active')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const GraphSubtitle = styled.text`
|
||||
${typography.normal};
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
fill: ${props => props.theme.white};
|
||||
|
||||
${is('consul')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
|
||||
${isNot('active')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
`;
|
||||
|
||||
export const GraphText = styled.text`
|
||||
${typography.normal};
|
||||
|
||||
font-size: 12px;
|
||||
fill: ${props => props.theme.white};
|
||||
opacity: 0.8;
|
||||
|
||||
${is('consul')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
|
||||
${isNot('active')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
`;
|
||||
|
||||
export const GraphButtonRect = styled.rect`
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const GraphButtonCircle = styled.circle`
|
||||
fill: ${props => props.theme.white};
|
||||
|
||||
${is('consul')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
|
||||
${isNot('active')`
|
||||
fill: ${props => props.theme.secondary};
|
||||
`};
|
||||
`;
|
||||
|
||||
export const GraphHealthyCircle = styled.circle`
|
||||
fill: ${props => props.theme.green};
|
||||
`;
|
@ -1,31 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Baseline from '../../baseline';
|
||||
import Constants from '../constants';
|
||||
import { GraphTitle } from './shapes';
|
||||
|
||||
const GraphNodeTitle = ({ data, onNodeTitleClick }) => (
|
||||
<g>
|
||||
<GraphTitle
|
||||
x={Constants.paddingLeft}
|
||||
y={30}
|
||||
onClick={onNodeTitleClick}
|
||||
onKeyDown={onNodeTitleClick}
|
||||
consul={data.isConsul}
|
||||
active={data.instancesActive}
|
||||
>
|
||||
{data.name}
|
||||
</GraphTitle>
|
||||
{/* <g transform={`translate(${115}, ${15})`}>
|
||||
<GraphHealthyCircle cx={9} cy={9} r={9} />
|
||||
<HeartIcon />
|
||||
</g> */}
|
||||
</g>
|
||||
);
|
||||
|
||||
GraphNodeTitle.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
onNodeTitleClick: PropTypes.func
|
||||
};
|
||||
|
||||
export default Baseline(GraphNodeTitle);
|
@ -1,26 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const p = {
|
||||
x: PropTypes.number.isRequired,
|
||||
y: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
const s = {
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number
|
||||
};
|
||||
|
||||
const Point = PropTypes.shape({
|
||||
...p
|
||||
});
|
||||
|
||||
const Size = PropTypes.shape({
|
||||
...s
|
||||
});
|
||||
|
||||
const Rect = PropTypes.shape({
|
||||
...p,
|
||||
...s
|
||||
});
|
||||
|
||||
export { Point, Rect, Size };
|
@ -1,140 +0,0 @@
|
||||
import { forceSimulation, forceLink, forceCollide, forceCenter } from 'd3';
|
||||
import Constants from './constants';
|
||||
|
||||
const hypotenuse = (a, b) => Math.sqrt(a * a + b * b);
|
||||
|
||||
const rectRadius = ({ width, height }) =>
|
||||
Math.round(hypotenuse(width, height) / 2);
|
||||
|
||||
const forcePlayAnimation = (simulation, animationTicks) => {
|
||||
const n =
|
||||
Math.ceil(
|
||||
Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())
|
||||
) + 100; // - animationTicks;
|
||||
|
||||
for (let i = 0; i < n; ++i) {
|
||||
simulation.tick();
|
||||
}
|
||||
};
|
||||
|
||||
const createLinks = services =>
|
||||
services.reduce(
|
||||
(acc, service, index) =>
|
||||
service.connections
|
||||
? acc.concat(
|
||||
service.connections.reduce((connections, connection, index) => {
|
||||
const targetExists = services.filter(
|
||||
service => service.id === connection
|
||||
).length;
|
||||
if (targetExists) {
|
||||
connections.push({
|
||||
source: service.id,
|
||||
target: connection
|
||||
});
|
||||
}
|
||||
return connections;
|
||||
}, [])
|
||||
)
|
||||
: acc,
|
||||
[]
|
||||
);
|
||||
|
||||
const createSimulation = (services, svgSize, animationTicks = 0) => {
|
||||
// This is not going to work given that as well as the d3 layout stuff, other things might be at play too
|
||||
// We should pass two objects to the components - one for positioning and one for data
|
||||
const nodes = services.map((service, index) => {
|
||||
return {
|
||||
id: service.id,
|
||||
index
|
||||
};
|
||||
});
|
||||
|
||||
const links = createLinks(services);
|
||||
|
||||
const { width, height } = svgSize;
|
||||
|
||||
const nodeRadius = rectRadius(Constants.nodeSizeWithChildren);
|
||||
|
||||
const simulation = forceSimulation(nodes)
|
||||
.force('link', forceLink(links).id(d => d.id))
|
||||
.force('collide', forceCollide(nodeRadius))
|
||||
.force('center', forceCenter(width / 2, height / 2));
|
||||
|
||||
forcePlayAnimation(simulation, animationTicks);
|
||||
|
||||
return {
|
||||
nodes,
|
||||
links,
|
||||
simulation
|
||||
};
|
||||
};
|
||||
|
||||
// TODO we need to kill the previous simulation
|
||||
const updateSimulation = (
|
||||
simulation,
|
||||
services,
|
||||
simNodes,
|
||||
simLinks,
|
||||
svgSize,
|
||||
onTick,
|
||||
onEnd
|
||||
) => {
|
||||
const nodes = services.map((service, index) => {
|
||||
const simNode = simNodes.reduce((acc, n, i) => {
|
||||
return service.id === n.id ? n : acc;
|
||||
}, null);
|
||||
|
||||
return simNode
|
||||
? {
|
||||
id: simNode.id,
|
||||
// Fx: simNode.x,
|
||||
// fy: simNode.y,
|
||||
index
|
||||
}
|
||||
: {
|
||||
id: service.id,
|
||||
index
|
||||
};
|
||||
});
|
||||
|
||||
const links = createLinks(services);
|
||||
|
||||
const { width, height } = svgSize;
|
||||
|
||||
const nodeRadius = rectRadius(Constants.nodeSizeWithChildren);
|
||||
|
||||
return {
|
||||
simulation: forceSimulation(nodes)
|
||||
.force('link', forceLink(links).id(d => d.id))
|
||||
.force('collide', forceCollide(nodeRadius))
|
||||
.force('center', forceCenter(width / 2, height / 2))
|
||||
.on('tick', onTick)
|
||||
.on('end', onEnd),
|
||||
nodes,
|
||||
links
|
||||
};
|
||||
};
|
||||
|
||||
export { createSimulation, updateSimulation };
|
||||
|
||||
/*
|
||||
Const simulation = forceSimulation(dataNodes)
|
||||
// .alpha(1).alphaDecay(0.1)
|
||||
// .force('charge', forceManyBody())
|
||||
.force('link', forceLink(dataLinks)
|
||||
//.distance(() => linkDistance)
|
||||
.id(d => d.id))
|
||||
.force('collide', forceCollide(nodeRadius))
|
||||
.force('center', forceCenter(1024/2, 860/2))
|
||||
.on('tick', () => {
|
||||
console.log('SIMULATION TICK');
|
||||
console.log('tickCounter = ', tickCounter);
|
||||
tickCounter++;
|
||||
this.forceUpdate();
|
||||
})
|
||||
.on('end', () => {
|
||||
console.log('SIMULATION END');
|
||||
console.log('tickCounter = ', tickCounter);
|
||||
// this.forceUpdate();
|
||||
})
|
||||
*/
|
@ -1,94 +0,0 @@
|
||||
```
|
||||
<Topology services=
|
||||
{[
|
||||
{
|
||||
"index": 0,
|
||||
"id": "af6a5cd2-291f-490b-bf3b-141b010635db",
|
||||
"name": "frontend",
|
||||
"slug": "frontend",
|
||||
"status": "ACTIVE",
|
||||
"__typename": "Service",
|
||||
"branches": [],
|
||||
"connections": [
|
||||
"aea06a05-830a-46d3-bdc1-9dcba97303de"
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"id": "f1fb3c1d-9e0e-4538-b2ad-1124bce2459e",
|
||||
"status": "RUNNING",
|
||||
"healthy": "UNKNOWN",
|
||||
"__typename": "Instance"
|
||||
},
|
||||
{
|
||||
"id": "c5c7ae33-cfe1-43cc-9e9b-6f453de3888d",
|
||||
"status": "FAILED",
|
||||
"healthy": "UNAVAILABLE",
|
||||
"__typename": "Instance"
|
||||
}
|
||||
],
|
||||
"instanceStatuses": [
|
||||
{
|
||||
"status": "RUNNING",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"status": "FAILED",
|
||||
"count": 1
|
||||
}
|
||||
],
|
||||
"instancesActive": true,
|
||||
"instancesHealthy": {
|
||||
"total": 2,
|
||||
"healthy": 0
|
||||
},
|
||||
"transitionalStatus": false,
|
||||
"isConsul": false,
|
||||
"connected": true
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"id": "af6a5cd2-291f-490b-bf3b-asdasads",
|
||||
"name": "consul",
|
||||
"slug": "consul",
|
||||
"status": "ACTIVE",
|
||||
"__typename": "Service",
|
||||
"branches": [],
|
||||
"connections": [
|
||||
"aea06a05-830a-46d3-bdc1-9dcba97303de"
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"id": "f1fb3c1d-9e0e-4538-b2ad-1124bce2459e",
|
||||
"status": "RUNNING",
|
||||
"healthy": "UNKNOWN",
|
||||
"__typename": "Instance"
|
||||
},
|
||||
{
|
||||
"id": "c5c7ae33-cfe1-43cc-9e9b-6f453de3888d",
|
||||
"status": "FAILED",
|
||||
"healthy": "UNAVAILABLE",
|
||||
"__typename": "Instance"
|
||||
}
|
||||
],
|
||||
"instanceStatuses": [
|
||||
{
|
||||
"status": "RUNNING",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"status": "RUNNING",
|
||||
"count": 1
|
||||
}
|
||||
],
|
||||
"instancesActive": true,
|
||||
"instancesHealthy": {
|
||||
"total": 2,
|
||||
"healthy": 2
|
||||
},
|
||||
"transitionalStatus": false,
|
||||
"isConsul": true,
|
||||
"connected": true
|
||||
}
|
||||
]
|
||||
} />
|
||||
```
|
@ -105,8 +105,7 @@ module.exports = {
|
||||
'src/form/radio.js',
|
||||
'src/section-list/index.js',
|
||||
'src/form/select.js',
|
||||
'src/form/toggle.js',
|
||||
'src/topology/index.js'
|
||||
'src/form/toggle.js'
|
||||
]
|
||||
}
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user