back to react flow (for now)

This commit is contained in:
Michael Quigley 2022-08-05 10:42:39 -04:00
parent 0eb2163a9c
commit 8e3dc00698
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
4 changed files with 182 additions and 59 deletions

120
ui/package-lock.json generated
View File

@ -13,9 +13,11 @@
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"dagre": "^0.8.5",
"react": "^18.2.0",
"react-data-table-component": "^7.5.2",
"react-dom": "^18.2.0",
"react-flow-renderer": "^10.3.12",
"react-force-graph-2d": "^1.23.10",
"react-scripts": "5.0.1",
"styled-components": "^5.3.5",
@ -5589,6 +5591,11 @@
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA=="
},
"node_modules/classcat": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.3.tgz",
"integrity": "sha512-6dK2ke4VEJZOFx2ZfdDAl5OhEL8lvkl6EHF92IfRePfHxQTqir5NlcNVUv+2idjDqCX2NDc8m8YSAI5NI975ZQ=="
},
"node_modules/clean-css": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz",
@ -6503,6 +6510,15 @@
"node": ">=12"
}
},
"node_modules/dagre": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
"integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
"dependencies": {
"graphlib": "^2.1.8",
"lodash": "^4.17.15"
}
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -8770,6 +8786,14 @@
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ=="
},
"node_modules/graphlib": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
"integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
"dependencies": {
"lodash": "^4.17.15"
}
},
"node_modules/gzip-size": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
@ -14629,6 +14653,26 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
},
"node_modules/react-flow-renderer": {
"version": "10.3.12",
"resolved": "https://registry.npmjs.org/react-flow-renderer/-/react-flow-renderer-10.3.12.tgz",
"integrity": "sha512-DTaz4HV0rA/qtvY80fjdb/QwIvtZEhqCQ2iAqfzFH08RjWCrLmESX4Nc400EB3CGcCK8/pDn/ta4cOS3udunTw==",
"dependencies": {
"@babel/runtime": "^7.18.9",
"classcat": "^5.0.3",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"zustand": "^3.7.2"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": "16 || 17 || 18",
"react-dom": "16 || 17 || 18"
}
},
"node_modules/react-force-graph-2d": {
"version": "1.23.10",
"resolved": "https://registry.npmjs.org/react-force-graph-2d/-/react-force-graph-2d-1.23.10.tgz",
@ -16424,6 +16468,19 @@
"is-typedarray": "^1.0.0"
}
},
"node_modules/typescript": {
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@ -17445,6 +17502,22 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zustand": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz",
"integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==",
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"react": ">=16.8"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
}
}
},
"dependencies": {
@ -21385,6 +21458,11 @@
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA=="
},
"classcat": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.3.tgz",
"integrity": "sha512-6dK2ke4VEJZOFx2ZfdDAl5OhEL8lvkl6EHF92IfRePfHxQTqir5NlcNVUv+2idjDqCX2NDc8m8YSAI5NI975ZQ=="
},
"clean-css": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz",
@ -22044,6 +22122,15 @@
"d3-transition": "2 - 3"
}
},
"dagre": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
"integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
"requires": {
"graphlib": "^2.1.8",
"lodash": "^4.17.15"
}
},
"damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -23708,6 +23795,14 @@
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ=="
},
"graphlib": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
"integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
"requires": {
"lodash": "^4.17.15"
}
},
"gzip-size": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
@ -27775,6 +27870,19 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
},
"react-flow-renderer": {
"version": "10.3.12",
"resolved": "https://registry.npmjs.org/react-flow-renderer/-/react-flow-renderer-10.3.12.tgz",
"integrity": "sha512-DTaz4HV0rA/qtvY80fjdb/QwIvtZEhqCQ2iAqfzFH08RjWCrLmESX4Nc400EB3CGcCK8/pDn/ta4cOS3udunTw==",
"requires": {
"@babel/runtime": "^7.18.9",
"classcat": "^5.0.3",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"zustand": "^3.7.2"
}
},
"react-force-graph-2d": {
"version": "1.23.10",
"resolved": "https://registry.npmjs.org/react-force-graph-2d/-/react-force-graph-2d-1.23.10.tgz",
@ -29104,6 +29212,12 @@
"is-typedarray": "^1.0.0"
}
},
"typescript": {
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"peer": true
},
"unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@ -29884,6 +29998,12 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
},
"zustand": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz",
"integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==",
"requires": {}
}
}
}

View File

@ -8,9 +8,11 @@
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"dagre": "^0.8.5",
"react": "^18.2.0",
"react-data-table-component": "^7.5.2",
"react-dom": "^18.2.0",
"react-flow-renderer": "^10.3.12",
"react-force-graph-2d": "^1.23.10",
"react-scripts": "5.0.1",
"styled-components": "^5.3.5",

View File

@ -1,23 +1,19 @@
import * as metadata from './api/metadata';
import {useEffect, useLayoutEffect, useRef, useState} from "react";
import ForceGraph2D from 'react-force-graph-2d';
let g1 = {}
import ReactFlow, {isNode, useNodesState} from "react-flow-renderer";
import dagre from 'dagre';
const Network = (props) => {
const ref = useRef();
const [graph, setGraph] = useState({nodes: [], links: []})
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges] = useState([]);
useEffect(() => {
let mounted = true
let g1 = graph
let interval = setInterval(() => {
metadata.overview().then(resp => {
let g = buildGraph(resp.data)
if(!compareGraphs(g, g1)) {
setGraph(g)
g1 = g
}
setNodes(getLayout(g))
setEdges(g.edges)
})
}, 1000)
return () => {
@ -29,34 +25,10 @@ const Network = (props) => {
return (
<div className={"network"}>
<h1>Network</h1>
<ForceGraph2D
ref={ref}
width={1024}
height={300}
graphData={graph}
nodeDefaultSize={[100, 50]}
nodeCanvasObject={(node, ctx, globalScale) => {
const label = node.name;
const fontSize = 12/globalScale;
ctx.font = `${fontSize}px JetBrains Mono`;
const textWidth = ctx.measureText(label).width;
const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 2.2); // some padding
ctx.fillStyle = '#3b2693';
ctx.strokeStyle = '#3b2693'
ctx.lineWidth = 0.5
ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2, ...bckgDimensions);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'white';
ctx.fillText(label, node.x, node.y);
node.__bckgDimensions = bckgDimensions; // to re-use in nodePointerAreaPaint
}}
onEngineStop={() => {
ref.current.zoomToFit(200);
}}
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
/>
</div>
)
@ -65,24 +37,34 @@ const Network = (props) => {
function buildGraph(overview) {
let out = {
nodes: [],
links: []
edges: []
}
let id = 1
overview.forEach((item) => {
let envId = id
out.nodes.push({
id: item.environment.zitiIdentityId,
name: 'Environment: ' + item.environment.zitiIdentityId
id: '' + envId,
data: {label: 'Environment: ' + item.environment.zitiIdentityId},
position: {x: (id * 25), y: 0},
draggable: true
});
id++
if(item.services != null) {
item.services.forEach((svc) => {
if(svc.active) {
item.services.forEach((item) => {
if(item.active) {
out.nodes.push({
id: svc.zitiServiceId,
name: 'Service: ' + svc.zitiServiceId
id: '' + id,
data: {label: 'Service: ' + item.zitiServiceId},
position: {x: (id * 25), y: 0},
draggable: true
})
out.links.push({
source: item.environment.zitiIdentityId,
target: svc.zitiServiceId
out.edges.push({
id: 'e' + envId + '-' + id,
source: '' + envId,
target: '' + id,
animated: true
})
id++
}
});
}
@ -90,18 +72,33 @@ function buildGraph(overview) {
return out
}
function compareGraphs(g, g1) {
if(g.nodes.length !== g1.nodes.length) return false;
for(let i = 0; i < g.nodes.length; i++) {
if(!compareNodes(g.nodes[i], g1.nodes[i])) return false;
}
return true
const nodeWidth = 215;
const nodeHeight = 75;
function getLayout(overview) {
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setGraph({ rankdir: 'TB' });
dagreGraph.setDefaultEdgeLabel(() => ({}));
overview.nodes.forEach((n) => {
dagreGraph.setNode(n.id, { width: nodeWidth, height: nodeHeight });
})
overview.edges.forEach((e) => {
dagreGraph.setEdge(e.source, e.target);
})
dagre.layout(dagreGraph);
return overview.nodes.map((n) => {
const nodeWithPosition = dagreGraph.node(n.id);
n.targetPosition = 'top';
n.sourcePosition = 'bottom';
n.position = {
x: nodeWithPosition.x - (nodeWidth / 2) + (Math.random() / 1000) + 50,
y: nodeWithPosition.y - (nodeHeight / 2) + 50,
}
return n;
});
}
function compareNodes(n, n1) {
if(n.id !== n1.id) return false;
if(n.name !== n1.name) return false;
return true;
}
export default Network;

View File

@ -117,4 +117,8 @@ h1, h2, h3, h4, h5, h6 {
.network {
height: 400px;
}
.react-flow__attribution {
display: none;
}