new console infrastructure (#107)

This commit is contained in:
Michael Quigley 2022-12-21 14:05:17 -05:00
parent f15b0a2952
commit a7349802c6
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
11 changed files with 181 additions and 84 deletions

View File

@ -23,6 +23,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="bootstrap.min.css">
<link href="https://fonts.googleapis.com/css2?family=Russo+One&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap" rel="stylesheet">
<title>zrok</title>
</head>
<body>

View File

@ -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 ? <NewConsole logout={logout} user={user} /> : <Login loginSuccess={setUser} />
const consoleComponent = user ? <Console logout={logout} user={user} /> : <Login loginSuccess={setUser} />
return (
<Router>

View File

@ -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 (
<button onClick={handleClick}><Icon path={mdiContentCopy} size={0.5}/></button>
);
}
export default Copy;

View File

@ -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 (
<Container fluid={"xl"}>
<Navbar bg="primary" variant="dark" id="navbar" expand="md">
@ -33,11 +60,11 @@ const NewConsole = (props) => {
</Navbar.Collapse>
</Container>
</Navbar>
<Visualizer />
<NewEnable show={showEnableModal} onHide={closeEnableModal} token={props.user.token}/>
<NewVersion show={showVersionModal} onHide={closeVersionModal} />
<Visualizer overview={overview} />
<Enable show={showEnableModal} onHide={closeEnableModal} token={props.user.token} />
<Version show={showVersionModal} onHide={closeVersionModal} />
</Container>
);
}
export default NewConsole;
export default Console;

View File

@ -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;
export default Enable;

View File

@ -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;
export default Version;

View File

@ -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 (
<ForceGraph2D
ref={targetRef}
graphData={props.networkGraph}
width={props.size.width}
height={500}
onNodeClick={nodeClicked}
linkOpacity={.75}
linkWidth={1.5}
nodeCanvasObject={paintNode}
backgroundColor={"#3b2693"}
/>
)
}
export default withSize()(Network);

View File

@ -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 (
<div>
<div className={"visualizer-container"}>
<Network
networkGraph={networkGraph}
setRef={setFgRef}
/>
<div className={"visualizer-controls"}>
<Button variant={"secondary"} size={"sm"} onClick={centerFocus}>Zoom to Fit</Button>
</div>
</div>
</div>
)
}
export default Visualizer;

View File

@ -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;
};

View File

@ -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 (
<ForceGraph2D
ref={targetRef}
graphData={props.networkGraph}
width={props.size.width}
height={500}
linkOpacity={.75}
backgroundColor={"#3b2693"}
/>
)
}
export default withSize()(Network);

View File

@ -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 (
<div>
<div className={"visualizer-container"}>
<Network
networkGraph={networkGraph}
setRef={setFgRef}
/>
</div>
<div className={"visualizer-controls"}>
<Button onClick={centerFocus}>Zoom to Fit</Button>
</div>
</div>
)
}
export default Visualizer;