From a7349802c68ec0e1a9acf59648bf2a4c878cf731 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 21 Dec 2022 14:05:17 -0500 Subject: [PATCH] new console infrastructure (#107) --- ui/public/index.html | 1 + ui/src/App.js | 4 +- ui/src/Copy.js | 15 ----- ui/src/{NewConsole.js => console/Console.js} | 45 ++++++++++++--- .../NewEnable.js => console/modals/Enable.js} | 4 +- .../modals/Version.js} | 6 +- ui/src/console/visualizer/Network.js | 57 +++++++++++++++++++ ui/src/console/visualizer/Visualizer.js | 38 +++++++++++++ ui/src/console/visualizer/graph.js | 42 ++++++++++++++ ui/src/visualizer/Network.js | 23 -------- ui/src/visualizer/Visualizer.js | 30 ---------- 11 files changed, 181 insertions(+), 84 deletions(-) delete mode 100644 ui/src/Copy.js rename ui/src/{NewConsole.js => console/Console.js} (58%) rename ui/src/{modals/NewEnable.js => console/modals/Enable.js} (94%) rename ui/src/{modals/NewVersion.js => console/modals/Version.js} (85%) create mode 100644 ui/src/console/visualizer/Network.js create mode 100644 ui/src/console/visualizer/Visualizer.js create mode 100644 ui/src/console/visualizer/graph.js delete mode 100644 ui/src/visualizer/Network.js delete mode 100644 ui/src/visualizer/Visualizer.js diff --git a/ui/public/index.html b/ui/public/index.html index c08e66c4..96df0730 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -23,6 +23,7 @@ + zrok diff --git a/ui/src/App.js b/ui/src/App.js index 7e02958d..a9109740 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -1,6 +1,6 @@ import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import Register from "./Register"; -import NewConsole from "./NewConsole"; +import Console from "./console/Console"; import {useEffect, useState} from "react"; import Login from "./Login"; @@ -20,7 +20,7 @@ const App = () => { localStorage.clear(); } - const consoleComponent = user ? : + const consoleComponent = user ? : return ( diff --git a/ui/src/Copy.js b/ui/src/Copy.js deleted file mode 100644 index fad85260..00000000 --- a/ui/src/Copy.js +++ /dev/null @@ -1,15 +0,0 @@ -import Icon from "@mdi/react"; -import {mdiContentCopy} from "@mdi/js"; - -const Copy = (props) => { - function handleClick(event) { - navigator.clipboard.writeText(props.text); - console.log("copied", props.text); - } - - return ( - - ); -} - -export default Copy; \ No newline at end of file diff --git a/ui/src/NewConsole.js b/ui/src/console/Console.js similarity index 58% rename from ui/src/NewConsole.js rename to ui/src/console/Console.js index e71a92c9..8679de1b 100644 --- a/ui/src/NewConsole.js +++ b/ui/src/console/Console.js @@ -1,10 +1,11 @@ -import {Container, Nav, Navbar, NavDropdown, Row} from "react-bootstrap"; -import {useState} from "react"; +import {Container, Nav, Navbar, NavDropdown} from "react-bootstrap"; +import {useEffect, useState} from "react"; import Visualizer from "./visualizer/Visualizer"; -import NewEnable from "./modals/NewEnable"; -import NewVersion from "./modals/NewVersion"; +import Enable from "./modals/Enable"; +import Version from "./modals/Version"; +import * as metadata from "../api/metadata"; -const NewConsole = (props) => { +const Console = (props) => { const [showEnableModal, setShowEnableModal] = useState(false); const openEnableModal = () => setShowEnableModal(true); const closeEnableModal = () => setShowEnableModal(false); @@ -13,6 +14,32 @@ const NewConsole = (props) => { const openVersionModal = () => setShowVersionModal(true); const closeVersionModal = () => setShowVersionModal(false); + const [overview, setOverview] = useState([]); + + useEffect(() => { + let mounted = true; + metadata.overview().then(resp => { + if(mounted) { + setOverview(resp.data); + } + }); + }, []); + + useEffect(() => { + let mounted = true; + let interval = setInterval(() => { + metadata.overview().then(resp => { + if(mounted) { + setOverview(resp.data); + } + }) + }, 1000) + return () => { + mounted = false; + clearInterval(interval); + } + }, []) + return ( @@ -33,11 +60,11 @@ const NewConsole = (props) => { - - - + + + ); } -export default NewConsole; \ No newline at end of file +export default Console; \ No newline at end of file diff --git a/ui/src/modals/NewEnable.js b/ui/src/console/modals/Enable.js similarity index 94% rename from ui/src/modals/NewEnable.js rename to ui/src/console/modals/Enable.js index 6d1b45f7..3016fd9b 100644 --- a/ui/src/modals/NewEnable.js +++ b/ui/src/console/modals/Enable.js @@ -2,7 +2,7 @@ import Modal from "react-bootstrap/Modal"; import {mdiContentCopy} from "@mdi/js"; import Icon from "@mdi/react"; -const NewEnable = (props) => { +const Enable = (props) => { const handleCopy = async () => { let copiedText = document.getElementById("zrok-enable-command").innerHTML; try { @@ -28,4 +28,4 @@ const NewEnable = (props) => { ); } -export default NewEnable; \ No newline at end of file +export default Enable; \ No newline at end of file diff --git a/ui/src/modals/NewVersion.js b/ui/src/console/modals/Version.js similarity index 85% rename from ui/src/modals/NewVersion.js rename to ui/src/console/modals/Version.js index 14d47852..088ad6ec 100644 --- a/ui/src/modals/NewVersion.js +++ b/ui/src/console/modals/Version.js @@ -1,8 +1,8 @@ import {useEffect, useState} from "react"; -import * as metadata from "../api/metadata"; +import * as metadata from "../../api/metadata"; import Modal from "react-bootstrap/Modal"; -const NewVersion = (props) => { +const Version = (props) => { const [v, setV] = useState(''); useEffect(() => { @@ -27,4 +27,4 @@ const NewVersion = (props) => { ); } -export default NewVersion; \ No newline at end of file +export default Version; \ No newline at end of file diff --git a/ui/src/console/visualizer/Network.js b/ui/src/console/visualizer/Network.js new file mode 100644 index 00000000..349c856e --- /dev/null +++ b/ui/src/console/visualizer/Network.js @@ -0,0 +1,57 @@ +import {withSize} from "react-sizeme"; +import {useEffect, useRef} from "react"; +import {ForceGraph2D} from "react-force-graph"; +import * as d3 from "d3-force-3d"; + +const Network = (props) => { + const targetRef = useRef(); + if(props.setRef != null) { + props.setRef(targetRef); + } + + useEffect(() => { + const fg = targetRef.current; + fg.d3Force('collide', d3.forceCollide(32)); + }, []); + + const paintNode = (node, ctx) => { + let nodeColor = "#777"; + let textColor = "white"; + switch(node.type) { + case "service": + nodeColor = "#7e67e2"; + break; + } + + ctx.textBaseline = "middle"; + ctx.textAlign = "center"; + ctx.font = "6px 'JetBrains Mono'"; + let extents = ctx.measureText(node.label); + let nodeWidth = extents.width + 5; + + ctx.fillStyle = nodeColor; + ctx.fillRect(node.x - (nodeWidth / 2), node.y - 7, nodeWidth, 14); + ctx.fillStyle = textColor; + ctx.fillText(node.label, node.x, node.y); + } + + const nodeClicked = (node) => { + console.log("node clicked", node.label); + } + + return ( + + ) +} + +export default withSize()(Network); \ No newline at end of file diff --git a/ui/src/console/visualizer/Visualizer.js b/ui/src/console/visualizer/Visualizer.js new file mode 100644 index 00000000..c660f760 --- /dev/null +++ b/ui/src/console/visualizer/Visualizer.js @@ -0,0 +1,38 @@ +import React, {useEffect, useState} from "react"; +import {Button} from "react-bootstrap"; +import Network from "./Network"; +import {buildGraph, mergeGraph} from "./graph"; + +const Visualizer = (props) => { + const [networkGraph, setNetworkGraph] = useState({nodes: [], links: []}); + + useEffect(() => { + setNetworkGraph(mergeGraph(networkGraph, props.overview)); + }, [props.overview]); + + // fgRef to access force graph controls from this component + let fgRef = () => { }; + const setFgRef = (ref) => { fgRef = ref }; + + const centerFocus = () => { + if(fgRef) { + fgRef.current.zoomToFit(200); + } + } + + return ( +
+
+ +
+ +
+
+
+ ) +} + +export default Visualizer; \ No newline at end of file diff --git a/ui/src/console/visualizer/graph.js b/ui/src/console/visualizer/graph.js new file mode 100644 index 00000000..2e743973 --- /dev/null +++ b/ui/src/console/visualizer/graph.js @@ -0,0 +1,42 @@ +export const mergeGraph = (oldGraph, newOverview) => { + let graph = { + nodes: [], + links: [] + } + newOverview.forEach(env => { + graph.nodes.push({ + id: env.environment.zId, + label: env.environment.description, + type: "environment" + }); + if(env.services) { + env.services.forEach(svc => { + let svcLabel = svc.token; + if(svc.backendProxyEndpoint !== "") { + svcLabel = svc.backendProxyEndpoint; + } + graph.nodes.push({ + id: svc.token, + label: svcLabel, + type: "service", + val: 10 + }); + graph.links.push({ + target: env.environment.zId, + source: svc.token, + color: "#777" + }); + }); + } + }); + graph.nodes.forEach(newNode => { + let found = oldGraph.nodes.find(oldNode => oldNode.id === newNode.id); + if(found) { + newNode.vx = found.vx; + newNode.vy = found.vy; + newNode.x = found.x; + newNode.y = found.y; + } + }) + return graph; +}; \ No newline at end of file diff --git a/ui/src/visualizer/Network.js b/ui/src/visualizer/Network.js deleted file mode 100644 index 7357f233..00000000 --- a/ui/src/visualizer/Network.js +++ /dev/null @@ -1,23 +0,0 @@ -import {withSize} from "react-sizeme"; -import {useRef} from "react"; -import {ForceGraph2D} from "react-force-graph"; - -const Network = (props) => { - const targetRef = useRef(); - if(props.setRef != null) { - props.setRef(targetRef); - } - - return ( - - ) -} - -export default withSize()(Network); \ No newline at end of file diff --git a/ui/src/visualizer/Visualizer.js b/ui/src/visualizer/Visualizer.js deleted file mode 100644 index f4c1a0e5..00000000 --- a/ui/src/visualizer/Visualizer.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, {useState} from "react"; -import {Button, Container, Row} from "react-bootstrap"; -import Network from "./Network"; - -const Visualizer = (props) => { - const [networkGraph, setNetworkGraph] = useState({nodes: [{id: 1}], links: []}); - const [fgRef, setFgRef] = useState(() => {}); - - const centerFocus = () => { - if(fgRef != null) { - fgRef.current.zoomToFit(200); - } - } - - return ( -
-
- -
-
- -
-
- ) -} - -export default Visualizer; \ No newline at end of file