mirror of
https://github.com/openziti/zrok.git
synced 2025-06-21 02:07:44 +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="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="stylesheet" href="bootstrap.min.css">
|
<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=Russo+One&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap" rel="stylesheet">
|
||||||
<title>zrok</title>
|
<title>zrok</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
||||||
import Register from "./Register";
|
import Register from "./Register";
|
||||||
import NewConsole from "./NewConsole";
|
import Console from "./console/Console";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ const App = () => {
|
|||||||
localStorage.clear();
|
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 (
|
return (
|
||||||
<Router>
|
<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 {Container, Nav, Navbar, NavDropdown} from "react-bootstrap";
|
||||||
import {useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import Visualizer from "./visualizer/Visualizer";
|
import Visualizer from "./visualizer/Visualizer";
|
||||||
import NewEnable from "./modals/NewEnable";
|
import Enable from "./modals/Enable";
|
||||||
import NewVersion from "./modals/NewVersion";
|
import Version from "./modals/Version";
|
||||||
|
import * as metadata from "../api/metadata";
|
||||||
|
|
||||||
const NewConsole = (props) => {
|
const Console = (props) => {
|
||||||
const [showEnableModal, setShowEnableModal] = useState(false);
|
const [showEnableModal, setShowEnableModal] = useState(false);
|
||||||
const openEnableModal = () => setShowEnableModal(true);
|
const openEnableModal = () => setShowEnableModal(true);
|
||||||
const closeEnableModal = () => setShowEnableModal(false);
|
const closeEnableModal = () => setShowEnableModal(false);
|
||||||
@ -13,6 +14,32 @@ const NewConsole = (props) => {
|
|||||||
const openVersionModal = () => setShowVersionModal(true);
|
const openVersionModal = () => setShowVersionModal(true);
|
||||||
const closeVersionModal = () => setShowVersionModal(false);
|
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 (
|
return (
|
||||||
<Container fluid={"xl"}>
|
<Container fluid={"xl"}>
|
||||||
<Navbar bg="primary" variant="dark" id="navbar" expand="md">
|
<Navbar bg="primary" variant="dark" id="navbar" expand="md">
|
||||||
@ -33,11 +60,11 @@ const NewConsole = (props) => {
|
|||||||
</Navbar.Collapse>
|
</Navbar.Collapse>
|
||||||
</Container>
|
</Container>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
<Visualizer />
|
<Visualizer overview={overview} />
|
||||||
<NewEnable show={showEnableModal} onHide={closeEnableModal} token={props.user.token}/>
|
<Enable show={showEnableModal} onHide={closeEnableModal} token={props.user.token} />
|
||||||
<NewVersion show={showVersionModal} onHide={closeVersionModal} />
|
<Version show={showVersionModal} onHide={closeVersionModal} />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NewConsole;
|
export default Console;
|
@ -2,7 +2,7 @@ import Modal from "react-bootstrap/Modal";
|
|||||||
import {mdiContentCopy} from "@mdi/js";
|
import {mdiContentCopy} from "@mdi/js";
|
||||||
import Icon from "@mdi/react";
|
import Icon from "@mdi/react";
|
||||||
|
|
||||||
const NewEnable = (props) => {
|
const Enable = (props) => {
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
let copiedText = document.getElementById("zrok-enable-command").innerHTML;
|
let copiedText = document.getElementById("zrok-enable-command").innerHTML;
|
||||||
try {
|
try {
|
||||||
@ -28,4 +28,4 @@ const NewEnable = (props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NewEnable;
|
export default Enable;
|
@ -1,8 +1,8 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import * as metadata from "../api/metadata";
|
import * as metadata from "../../api/metadata";
|
||||||
import Modal from "react-bootstrap/Modal";
|
import Modal from "react-bootstrap/Modal";
|
||||||
|
|
||||||
const NewVersion = (props) => {
|
const Version = (props) => {
|
||||||
const [v, setV] = useState('');
|
const [v, setV] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
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