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",
|
||||
"@mui/icons-material": "^6.1.8",
|
||||
"@mui/material": "^6.1.8",
|
||||
"@xyflow/react": "^12.3.5",
|
||||
"d3-hierarchy": "^3.1.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router": "^7.0.1",
|
||||
"reagraph": "^4.21.0"
|
||||
"react-router": "^7.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.13.0",
|
||||
|
@ -1,11 +1,10 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import {Configuration, MetadataApi} from "./api";
|
||||
import buildVisualizerGraph from "./model/visualizer.ts";
|
||||
import {GraphCanvas} from "reagraph";
|
||||
import {Box, Button} from "@mui/material";
|
||||
import buildVisualizerGraph, {VisualOverview} from "./model/visualizer.ts";
|
||||
import {Box} from "@mui/material";
|
||||
import NavBar from "./NavBar.tsx";
|
||||
import {reagraphTheme} from "./model/theme.ts";
|
||||
import {User} from "./model/user.ts";
|
||||
import Visualizer from "./Visualizer.tsx";
|
||||
|
||||
interface ApiConsoleProps {
|
||||
user: User;
|
||||
@ -14,8 +13,7 @@ interface ApiConsoleProps {
|
||||
|
||||
const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
|
||||
const [version, setVersion] = useState("no version set");
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [edges, setEdges] = useState([]);
|
||||
const [overview, setOverview] = useState(new VisualOverview());
|
||||
|
||||
useEffect(() => {
|
||||
let api = new MetadataApi();
|
||||
@ -32,16 +30,13 @@ const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
|
||||
let interval = setInterval(() => {
|
||||
let cfg = new Configuration({
|
||||
headers: {
|
||||
// ignorable token, local development environment
|
||||
"X-TOKEN": user.token
|
||||
}
|
||||
});
|
||||
let api = new MetadataApi(cfg);
|
||||
api.overview()
|
||||
.then(d => {
|
||||
let graph = buildVisualizerGraph(d);
|
||||
setNodes(graph.nodes);
|
||||
setEdges(graph.edges);
|
||||
setOverview(buildVisualizerGraph(d));
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e);
|
||||
@ -54,11 +49,9 @@ const ApiConsole = ({ user, logout }: ApiConsoleProps) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NavBar logout={logout} version={version} />
|
||||
<NavBar logout={logout} />
|
||||
<Box>
|
||||
<div style={{position: "relative", width: "100%", height: "500px"}}>
|
||||
<GraphCanvas nodes={nodes} edges={edges} theme={reagraphTheme} />
|
||||
</div>
|
||||
<Visualizer overview={overview} />
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import {User} from "./model/user.ts";
|
||||
import {useEffect, useState} from "react";
|
||||
import {AccountApi, MetadataApi} from "./api";
|
||||
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 {
|
||||
onLogin: (user: User) => void;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {AppBar, Box, Button, Grid2, IconButton, Toolbar, Typography} from "@mui/material";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
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 {
|
||||
logout: () => void;
|
||||
@ -15,7 +15,7 @@ const NavBar = ({ logout }: NavBarProps) => {
|
||||
<IconButton size="large" edge="start" color="inherit" aria-label="menu" sx={{ mr: 2 }}>
|
||||
<MenuIcon />
|
||||
</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 display="flex" justifyContent="left">
|
||||
<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 {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
color: #241775;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
color: #9bf316;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -25,25 +25,6 @@ h1 {
|
||||
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 {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {createTheme} from "@mui/material";
|
||||
import {Theme} from "reagraph";
|
||||
|
||||
const componentOptions = {
|
||||
MuiCard: {
|
||||
@ -45,62 +44,4 @@ export const modalStyle = {
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
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 {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) => {
|
||||
nodes.push({ id: (i+2).toString(), label: env.environment?.description! });
|
||||
edges.push({ source: (i+2).toString(), target: "1", id: (i+2)+"-1" });
|
||||
out.nodes.push({
|
||||
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,
|
||||
edges: edges,
|
||||
}
|
||||
|
||||
console.log(out);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
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