mirror of
https://github.com/openziti/zrok.git
synced 2025-06-21 10:17:51 +02:00
new console infrastructure (#107)
This commit is contained in:
parent
f15b0a2952
commit
a7349802c6
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
57
ui/src/console/visualizer/Network.js
Normal file
57
ui/src/console/visualizer/Network.js
Normal 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);
|
38
ui/src/console/visualizer/Visualizer.js
Normal file
38
ui/src/console/visualizer/Visualizer.js
Normal 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;
|
42
ui/src/console/visualizer/graph.js
Normal file
42
ui/src/console/visualizer/graph.js
Normal 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;
|
||||
};
|
@ -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);
|
@ -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;
|
Loading…
x
Reference in New Issue
Block a user