mirror of
https://github.com/openziti/zrok.git
synced 2025-01-31 18:39:22 +01:00
react-flow; lint; layouting (#799)
This commit is contained in:
parent
96eb246241
commit
334637b994
1133
ui100/package-lock.json
generated
1133
ui100/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,10 +14,11 @@
|
|||||||
"@emotion/styled": "^11.13.5",
|
"@emotion/styled": "^11.13.5",
|
||||||
"@mui/icons-material": "^6.1.8",
|
"@mui/icons-material": "^6.1.8",
|
||||||
"@mui/material": "^6.1.8",
|
"@mui/material": "^6.1.8",
|
||||||
|
"@xyflow/react": "^12.3.5",
|
||||||
|
"d3-hierarchy": "^3.1.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router": "^7.0.1",
|
"react-router": "^7.0.1"
|
||||||
"reagraph": "^4.21.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.13.0",
|
"@eslint/js": "^9.13.0",
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {Configuration, MetadataApi} from "./api";
|
import {Configuration, MetadataApi} from "./api";
|
||||||
import buildVisualizerGraph from "./model/visualizer.ts";
|
import buildVisualizerGraph, {VisualOverview} from "./model/visualizer.ts";
|
||||||
import {GraphCanvas} from "reagraph";
|
import {Box} from "@mui/material";
|
||||||
import {Box, Button} from "@mui/material";
|
|
||||||
import NavBar from "./NavBar.tsx";
|
import NavBar from "./NavBar.tsx";
|
||||||
import {reagraphTheme} from "./model/theme.ts";
|
|
||||||
import {User} from "./model/user.ts";
|
import {User} from "./model/user.ts";
|
||||||
|
import Visualizer from "./Visualizer.tsx";
|
||||||
|
|
||||||
interface ApiConsoleProps {
|
interface ApiConsoleProps {
|
||||||
user: User;
|
user: User;
|
||||||
@ -14,8 +13,7 @@ interface ApiConsoleProps {
|
|||||||
|
|
||||||
const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
|
const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
|
||||||
const [version, setVersion] = useState("no version set");
|
const [version, setVersion] = useState("no version set");
|
||||||
const [nodes, setNodes] = useState([]);
|
const [overview, setOverview] = useState(new VisualOverview());
|
||||||
const [edges, setEdges] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let api = new MetadataApi();
|
let api = new MetadataApi();
|
||||||
@ -32,16 +30,13 @@ const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
|
|||||||
let interval = setInterval(() => {
|
let interval = setInterval(() => {
|
||||||
let cfg = new Configuration({
|
let cfg = new Configuration({
|
||||||
headers: {
|
headers: {
|
||||||
// ignorable token, local development environment
|
|
||||||
"X-TOKEN": user.token
|
"X-TOKEN": user.token
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let api = new MetadataApi(cfg);
|
let api = new MetadataApi(cfg);
|
||||||
api.overview()
|
api.overview()
|
||||||
.then(d => {
|
.then(d => {
|
||||||
let graph = buildVisualizerGraph(d);
|
setOverview(buildVisualizerGraph(d));
|
||||||
setNodes(graph.nodes);
|
|
||||||
setEdges(graph.edges);
|
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
@ -54,11 +49,9 @@ const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NavBar logout={logout} version={version} />
|
<NavBar logout={logout} />
|
||||||
<Box>
|
<Box>
|
||||||
<div style={{position: "relative", width: "100%", height: "500px"}}>
|
<Visualizer overview={overview} />
|
||||||
<GraphCanvas nodes={nodes} edges={edges} theme={reagraphTheme} />
|
|
||||||
</div>
|
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,7 @@ import {User} from "./model/user.ts";
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {AccountApi, MetadataApi} from "./api";
|
import {AccountApi, MetadataApi} from "./api";
|
||||||
import {Link} from "react-router";
|
import {Link} from "react-router";
|
||||||
import zroket from "../public/zrok-1.0.0-rocket.svg";
|
import zroket from "./assets/zrok-1.0.0-rocket-purple.svg";
|
||||||
|
|
||||||
interface LoginProps {
|
interface LoginProps {
|
||||||
onLogin: (user: User) => void;
|
onLogin: (user: User) => void;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {AppBar, Box, Button, Grid2, IconButton, Toolbar, Typography} from "@mui/material";
|
import {AppBar, Box, Button, Grid2, IconButton, Toolbar, Typography} from "@mui/material";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import LogoutIcon from "@mui/icons-material/Logout";
|
import LogoutIcon from "@mui/icons-material/Logout";
|
||||||
import zroket from "../public/zrok-1.0.0-rocket-white.svg";
|
import zroket from "./assets/zrok-1.0.0-rocket-white.svg";
|
||||||
|
|
||||||
interface NavBarProps {
|
interface NavBarProps {
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
@ -15,7 +15,7 @@ const NavBar = ({ logout }: NavBarProps) => {
|
|||||||
<IconButton size="large" edge="start" color="inherit" aria-label="menu" sx={{ mr: 2 }}>
|
<IconButton size="large" edge="start" color="inherit" aria-label="menu" sx={{ mr: 2 }}>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h6" sx={{ flexGrow: 1 }} display={{ xs: "none", sm: "none", md: "block" }}>
|
<Typography variant="h6" sx={{ flexGrow: 1 }}>
|
||||||
<Grid2 container sx={{ flexGrow: 1 }}>
|
<Grid2 container sx={{ flexGrow: 1 }}>
|
||||||
<Grid2 display="flex" justifyContent="left">
|
<Grid2 display="flex" justifyContent="left">
|
||||||
<img src={zroket} height="30" />
|
<img src={zroket} height="30" />
|
||||||
|
66
ui100/src/Visualizer.tsx
Normal file
66
ui100/src/Visualizer.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import "@xyflow/react/dist/style.css";
|
||||||
|
import "./react-flow.css";
|
||||||
|
import {Background, Controls, ReactFlow, ReactFlowProvider, useEdgesState, useNodesState} from "@xyflow/react";
|
||||||
|
import {VisualOverview} from "./model/visualizer.ts";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
import {stratify, tree} from "d3-hierarchy";
|
||||||
|
|
||||||
|
interface VisualizerProps {
|
||||||
|
overview: VisualOverview;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Visualizer = ({ overview }: VisualizerProps) => {
|
||||||
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
|
|
||||||
|
const layout = (nodes, edges): VisualOverview => {
|
||||||
|
if(!nodes) {
|
||||||
|
return { nodes: [], edges: [] };
|
||||||
|
}
|
||||||
|
let g = tree();
|
||||||
|
if(nodes.length === 0) return { nodes, edges };
|
||||||
|
// const { width, height } = document.querySelector(`[data-id="$nodes[0].id"]`).getBoundingClientRect();
|
||||||
|
const width = 100;
|
||||||
|
const height = 40;
|
||||||
|
const hierarchy = stratify()
|
||||||
|
.id((node) => node.id)
|
||||||
|
.parentId((node) => edges.find((edge) => edge.target === node.id)?.source);
|
||||||
|
const root = hierarchy(nodes);
|
||||||
|
const layout = g.nodeSize([width * 2, height * 2])(root);
|
||||||
|
return {
|
||||||
|
nodes: layout
|
||||||
|
.descendants()
|
||||||
|
.map((node) => ({ ...node.data, position: { x: node.x, y: node.y }})),
|
||||||
|
edges,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let layouted = layout(overview.nodes, overview.edges);
|
||||||
|
setNodes(layouted.nodes);
|
||||||
|
setEdges(layouted.edges);
|
||||||
|
}, [overview]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
fitView
|
||||||
|
>
|
||||||
|
<Background/>
|
||||||
|
<Controls />
|
||||||
|
</ReactFlow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ({ overview }: VisualizerProps) => {
|
||||||
|
return (
|
||||||
|
<div style={{ height: "400px" }}>
|
||||||
|
<ReactFlowProvider>
|
||||||
|
<Visualizer overview={overview}/>
|
||||||
|
</ReactFlowProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
50
ui100/src/assets/zrok-1.0.0-rocket-purple.svg
Normal file
50
ui100/src/assets/zrok-1.0.0-rocket-purple.svg
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="125.797mm"
|
||||||
|
height="166.26598mm"
|
||||||
|
viewBox="0 0 125.797 166.26598"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.4 (e7c3feb, 2024-10-09)"
|
||||||
|
sodipodi:docname="zrok-1.0.0-rocket.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showguides="false"
|
||||||
|
inkscape:zoom="0.57641403"
|
||||||
|
inkscape:cx="1125.9268"
|
||||||
|
inkscape:cy="717.3663"
|
||||||
|
inkscape:window-width="1952"
|
||||||
|
inkscape:window-height="1304"
|
||||||
|
inkscape:window-x="1311"
|
||||||
|
inkscape:window-y="48"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer1"><inkscape:page
|
||||||
|
x="0"
|
||||||
|
y="-4.4822158e-22"
|
||||||
|
width="125.797"
|
||||||
|
height="166.26598"
|
||||||
|
id="page2"
|
||||||
|
margin="0"
|
||||||
|
bleed="0" /></sodipodi:namedview><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-41.620475,-64.027978)"><path
|
||||||
|
id="path2"
|
||||||
|
style="fill:#241774;fill-opacity:1;stroke-width:0.865487"
|
||||||
|
d="m 104.52059,64.027974 c 0,0 -12.300998,16.684095 -17.163248,24.964306 -4.8623,8.28022 -7.86765,12.84754 -10.59501,26.3339 -2.72736,13.48636 -1.27406,42.21319 -1.27406,42.21319 l -18.58752,20.04004 -15.280273,49.9615 40.422833,-6.79427 a 22.714797,11.567473 0 0 0 22.320548,9.54731 22.714797,11.567473 0 0 0 22.32712,-9.55945 l 40.72649,6.84277 -15.28027,-49.95979 -18.58916,-20.04001 c 0,0 1.45496,-28.72857 -1.2724,-42.21493 -2.72736,-13.48636 -5.73437,-18.05368 -10.59667,-26.3339 -4.8608,-8.277646 -17.14905,-24.947062 -17.15668,-24.957378 z m -0.0151,14.741718 c 0.52421,0.860497 14.76063,18.300498 18.34492,32.586238 3.65093,14.55125 3.25036,30.16797 2.29067,46.75483 -0.68395,11.82081 -5.48912,37.13724 -8.2384,50.97445 a 22.714797,11.567473 0 0 0 -12.54073,-1.9254 22.714797,11.567473 0 0 0 -12.237098,1.84921 c -2.75047,-13.84452 -7.55003,-39.12307 -8.23343,-50.93462 -0.95969,-16.58687 -1.36026,-32.20531 2.29067,-46.75656 3.57561,-14.25109 17.733538,-31.579359 18.323398,-32.548148 z m -27.999198,95.069708 6.36686,35.53319 -30.30472,9.08328 10.94985,-35.7998 z m 56.021568,0.0351 12.988,8.81667 10.9482,35.80155 -30.30311,-9.08504 z" /></g></svg>
|
After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
@ -13,11 +13,11 @@ code {
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #646cff;
|
color: #241775;
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
color: #535bf2;
|
color: #9bf316;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@ -25,25 +25,6 @@ h1 {
|
|||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: inherit;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.25s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
border-color: #646cff;
|
|
||||||
}
|
|
||||||
button:focus,
|
|
||||||
button:focus-visible {
|
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {createTheme} from "@mui/material";
|
import {createTheme} from "@mui/material";
|
||||||
import {Theme} from "reagraph";
|
|
||||||
|
|
||||||
const componentOptions = {
|
const componentOptions = {
|
||||||
MuiCard: {
|
MuiCard: {
|
||||||
@ -45,62 +44,4 @@ export const modalStyle = {
|
|||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
p: 4,
|
p: 4,
|
||||||
};
|
|
||||||
|
|
||||||
export const reagraphTheme: Theme = {
|
|
||||||
canvas: {
|
|
||||||
background: '#fff',
|
|
||||||
fog: '#fff'
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
fill: '#241775',
|
|
||||||
activeFill: '#9bf316',
|
|
||||||
opacity: 1,
|
|
||||||
selectedOpacity: 1,
|
|
||||||
inactiveOpacity: 0.2,
|
|
||||||
label: {
|
|
||||||
color: '#241775',
|
|
||||||
stroke: '#fff',
|
|
||||||
activeColor: '#9bf316'
|
|
||||||
},
|
|
||||||
subLabel: {
|
|
||||||
color: '#241775',
|
|
||||||
stroke: '#eee',
|
|
||||||
activeColor: '#9bf316'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
lasso: {
|
|
||||||
border: '1px solid #55aaff',
|
|
||||||
background: 'rgba(75, 160, 255, 0.1)'
|
|
||||||
},
|
|
||||||
ring: {
|
|
||||||
fill: '#D8E6EA',
|
|
||||||
activeFill: '#1DE9AC'
|
|
||||||
},
|
|
||||||
edge: {
|
|
||||||
fill: '#D8E6EA',
|
|
||||||
activeFill: '#1DE9AC',
|
|
||||||
opacity: 1,
|
|
||||||
selectedOpacity: 1,
|
|
||||||
inactiveOpacity: 0.1,
|
|
||||||
label: {
|
|
||||||
stroke: '#fff',
|
|
||||||
color: '#2A6475',
|
|
||||||
activeColor: '#1DE9AC'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
arrow: {
|
|
||||||
fill: '#D8E6EA',
|
|
||||||
activeFill: '#1DE9AC'
|
|
||||||
},
|
|
||||||
cluster: {
|
|
||||||
stroke: '#D8E6EA',
|
|
||||||
opacity: 1,
|
|
||||||
selectedOpacity: 1,
|
|
||||||
inactiveOpacity: 0.1,
|
|
||||||
label: {
|
|
||||||
stroke: '#fff',
|
|
||||||
color: '#2A6475'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
@ -1,18 +1,34 @@
|
|||||||
import {Overview} from "../api";
|
import {Overview} from "../api";
|
||||||
|
import {Edge, Node} from "@xyflow/react";
|
||||||
|
|
||||||
|
export class VisualOverview {
|
||||||
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildVisualizerGraph = (overview: Overview): VisualOverview => {
|
||||||
|
let out = new VisualOverview();
|
||||||
|
out.nodes = [
|
||||||
|
{ id: "1", position: { x: 0, y: 0 }, data: { label: "michael@quigley.com" } }
|
||||||
|
];
|
||||||
|
out.edges = [];
|
||||||
|
|
||||||
const buildVisualizerGraph = (overview: Overview) => {
|
|
||||||
let nodes = [
|
|
||||||
{ id: "1", label: "michael@quigley.com" }
|
|
||||||
]
|
|
||||||
let edges = [];
|
|
||||||
overview.environments?.forEach((env, i) => {
|
overview.environments?.forEach((env, i) => {
|
||||||
nodes.push({ id: (i+2).toString(), label: env.environment?.description! });
|
out.nodes.push({
|
||||||
edges.push({ source: (i+2).toString(), target: "1", id: (i+2)+"-1" });
|
id: (i+2).toString(),
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
data: { label: env.environment?.description! },
|
||||||
|
});
|
||||||
|
out.edges.push({
|
||||||
|
id: "e" + (i+2) + "-1",
|
||||||
|
source: "1",
|
||||||
|
target: (i+2).toString()
|
||||||
|
});
|
||||||
})
|
})
|
||||||
return {
|
|
||||||
nodes: nodes,
|
console.log(out);
|
||||||
edges: edges,
|
|
||||||
}
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default buildVisualizerGraph;
|
export default buildVisualizerGraph;
|
8
ui100/src/react-flow.css
Normal file
8
ui100/src/react-flow.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.react-flow__node {
|
||||||
|
font-family: "Poppins", sans-serif;
|
||||||
|
color: #241775;
|
||||||
|
border: 1px solid #241775;
|
||||||
|
}
|
||||||
|
.react-flow__attribution {
|
||||||
|
display: none;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user