mirror of
https://github.com/openziti/zrok.git
synced 2025-06-20 17:58:50 +02:00
better component hierarchy; display enable help when no environments (#84)
This commit is contained in:
parent
5a2d5fcbb1
commit
7d1c69ad0e
137
ui/src/Account.js
Normal file
137
ui/src/Account.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {useReactFlow} from "react-flow-renderer";
|
||||||
|
import Icon from "@mdi/react";
|
||||||
|
import {mdiAccessPointNetwork, mdiDesktopClassic} from "@mdi/js";
|
||||||
|
import dagre from "dagre";
|
||||||
|
import * as metadata from "./api/metadata";
|
||||||
|
import Network from "./Network";
|
||||||
|
import Enable from "./Enable";
|
||||||
|
|
||||||
|
const Account = (props) => {
|
||||||
|
const [mode, setMode] = useState(<></>);
|
||||||
|
const reactFlow = useReactFlow();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true
|
||||||
|
metadata.overview().then(resp => {
|
||||||
|
if(mounted) {
|
||||||
|
let overview = resp.data
|
||||||
|
let g = buildGraph(resp.data)
|
||||||
|
let nodes = getLayout(g)
|
||||||
|
let edges = g.edges
|
||||||
|
|
||||||
|
if(resp.data.length > 0) {
|
||||||
|
setMode(<Network
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
overview={overview}
|
||||||
|
/>)
|
||||||
|
reactFlow.fitView({maxZoom: 1})
|
||||||
|
} else {
|
||||||
|
setMode(<Enable token={props.user.token}/>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let mounted = true
|
||||||
|
let interval = setInterval(() => {
|
||||||
|
metadata.overview().then(resp => {
|
||||||
|
if(mounted) {
|
||||||
|
let overview = resp.data
|
||||||
|
let g = buildGraph(resp.data)
|
||||||
|
let nodes = getLayout(g)
|
||||||
|
let edges = g.edges
|
||||||
|
|
||||||
|
if(resp.data.length > 0) {
|
||||||
|
setMode(<Network
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
overview={overview}
|
||||||
|
/>)
|
||||||
|
reactFlow.fitView({maxZoom: 1})
|
||||||
|
} else {
|
||||||
|
setMode(<Enable token={props.user.token}/>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
return () => {
|
||||||
|
mounted = false
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <>{mode}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGraph(overview) {
|
||||||
|
let out = {
|
||||||
|
nodes: [],
|
||||||
|
edges: []
|
||||||
|
}
|
||||||
|
let id = 1
|
||||||
|
overview.forEach((item) => {
|
||||||
|
let envId = id
|
||||||
|
out.nodes.push({
|
||||||
|
id: '' + envId,
|
||||||
|
data: { label: <div><Icon path={mdiDesktopClassic} size={0.75} className={"flowNode"}/> { item.environment.description } </div> },
|
||||||
|
position: { x: (id * 25), y: 0 },
|
||||||
|
style: { width: 'fit-content', backgroundColor: '#aaa', color: 'white' },
|
||||||
|
type: 'input',
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
id++
|
||||||
|
if(item.services != null) {
|
||||||
|
item.services.forEach((item) => {
|
||||||
|
out.nodes.push({
|
||||||
|
id: '' + id,
|
||||||
|
data: {label: <div><Icon path={mdiAccessPointNetwork} size={0.75} className={"flowNode"}/> { item.frontend }</div>},
|
||||||
|
position: {x: (id * 25), y: 0},
|
||||||
|
style: { width: 'fit-content', backgroundColor: '#9367ef', color: 'white' },
|
||||||
|
type: 'output',
|
||||||
|
draggable: true
|
||||||
|
})
|
||||||
|
out.edges.push({
|
||||||
|
id: 'e' + envId + '-' + id,
|
||||||
|
source: '' + envId,
|
||||||
|
target: '' + id,
|
||||||
|
animated: true
|
||||||
|
})
|
||||||
|
id++
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeWidth = 215;
|
||||||
|
const nodeHeight = 75;
|
||||||
|
|
||||||
|
function getLayout(overview) {
|
||||||
|
const dagreGraph = new dagre.graphlib.Graph();
|
||||||
|
dagreGraph.setGraph({ rankdir: 'TB' });
|
||||||
|
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||||
|
|
||||||
|
overview.nodes.forEach((n) => {
|
||||||
|
dagreGraph.setNode(n.id, { width: nodeWidth, height: nodeHeight });
|
||||||
|
})
|
||||||
|
overview.edges.forEach((e) => {
|
||||||
|
dagreGraph.setEdge(e.source, e.target);
|
||||||
|
})
|
||||||
|
dagre.layout(dagreGraph);
|
||||||
|
|
||||||
|
return overview.nodes.map((n) => {
|
||||||
|
const nodeWithPosition = dagreGraph.node(n.id);
|
||||||
|
n.targetPosition = 'top';
|
||||||
|
n.sourcePosition = 'bottom';
|
||||||
|
n.position = {
|
||||||
|
x: nodeWithPosition.x - (nodeWidth / 2) + (Math.random() / 1000) + 50,
|
||||||
|
y: nodeWithPosition.y - (nodeHeight / 2) + 50,
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Account
|
@ -1,17 +1,16 @@
|
|||||||
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 Overview from "./Overview";
|
import Console from "./Console";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={"/"} element={<Overview />}/>
|
<Route path={"/"} element={<Console/>}/>
|
||||||
<Route path={"register/:token"} element={<Register />} />
|
<Route path={"register/:token"} element={<Register/>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App
|
||||||
|
|
@ -3,10 +3,10 @@ import Login from "./Login";
|
|||||||
import Version from "./Version";
|
import Version from "./Version";
|
||||||
import Token from "./Token";
|
import Token from "./Token";
|
||||||
import Logout from "./Logout";
|
import Logout from "./Logout";
|
||||||
import Network from "./Network";
|
import Account from "./Account";
|
||||||
import {ReactFlowProvider} from "react-flow-renderer";
|
import {ReactFlowProvider} from "react-flow-renderer";
|
||||||
|
|
||||||
const Overview = () => {
|
const Console = () => {
|
||||||
const [user, setUser] = useState();
|
const [user, setUser] = useState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -47,7 +47,7 @@ const Overview = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="main">
|
<div className="main">
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<Network />
|
<Account user={user}/>
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -55,4 +55,4 @@ const Overview = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Overview;
|
export default Console;
|
26
ui/src/Enable.js
Normal file
26
ui/src/Enable.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import Icon from "@mdi/react";
|
||||||
|
import {mdiContentCopy} from "@mdi/js";
|
||||||
|
|
||||||
|
const Enable = (props) => {
|
||||||
|
const handleCopy = async () => {
|
||||||
|
let copiedText = document.getElementById("zrok-enable-command").innerHTML;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(copiedText);
|
||||||
|
console.log("copied enable command");
|
||||||
|
} catch(err) {
|
||||||
|
console.error("failed to copy", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div id={"zrok-enable"}>
|
||||||
|
<h1>Enable an Environment</h1>
|
||||||
|
<p>To enable your shell for zrok, use this command:</p>
|
||||||
|
<pre>
|
||||||
|
$ <span id={"zrok-enable-command"}>zrok enable {props.token}</span> <Icon path={mdiContentCopy} size={0.7} onClick={handleCopy}/>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Enable
|
@ -1,134 +1,21 @@
|
|||||||
import Environments from './Environments';
|
import Environments from './Environments';
|
||||||
import * as metadata from './api/metadata';
|
import ReactFlow from "react-flow-renderer";
|
||||||
import {useEffect, useLayoutEffect, useRef, useState} from "react";
|
|
||||||
import ReactFlow, {isNode, useNodesState, useReactFlow} from "react-flow-renderer";
|
|
||||||
import dagre from 'dagre';
|
|
||||||
import { mdiDesktopClassic, mdiAccessPointNetwork } from '@mdi/js';
|
|
||||||
import Icon from "@mdi/react";
|
|
||||||
|
|
||||||
const Network = () => {
|
|
||||||
const [overview, setOverview] = useState([]);
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
||||||
const [edges, setEdges] = useState([]);
|
|
||||||
const reactFlow = useReactFlow();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let mounted = true
|
|
||||||
metadata.overview().then(resp => {
|
|
||||||
if(mounted) {
|
|
||||||
setOverview(resp.data)
|
|
||||||
|
|
||||||
let g = buildGraph(resp.data)
|
|
||||||
setNodes(getLayout(g))
|
|
||||||
setEdges(g.edges)
|
|
||||||
reactFlow.fitView({maxZoom: 1})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let mounted = true
|
|
||||||
let interval = setInterval(() => {
|
|
||||||
metadata.overview().then(resp => {
|
|
||||||
if(mounted) {
|
|
||||||
setOverview(resp.data)
|
|
||||||
|
|
||||||
let g = buildGraph(resp.data)
|
|
||||||
setNodes(getLayout(g))
|
|
||||||
setEdges(g.edges)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, 1000)
|
|
||||||
return () => {
|
|
||||||
mounted = false
|
|
||||||
clearInterval(interval)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
const Network = (props) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={"network"}>
|
<div className={"network"}>
|
||||||
<h1>Network</h1>
|
<h1>Network</h1>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
nodes={nodes}
|
nodes={props.nodes}
|
||||||
edges={edges}
|
edges={props.edges}
|
||||||
onNodesChange={onNodesChange}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Environments
|
<Environments
|
||||||
overview={overview}
|
overview={props.overview}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGraph(overview) {
|
|
||||||
let out = {
|
|
||||||
nodes: [],
|
|
||||||
edges: []
|
|
||||||
}
|
|
||||||
let id = 1
|
|
||||||
overview.forEach((item) => {
|
|
||||||
let envId = id
|
|
||||||
out.nodes.push({
|
|
||||||
id: '' + envId,
|
|
||||||
data: { label: <div><Icon path={mdiDesktopClassic} size={0.75} className={"flowNode"}/> { item.environment.description } </div> },
|
|
||||||
position: { x: (id * 25), y: 0 },
|
|
||||||
style: { width: 'fit-content', backgroundColor: '#aaa', color: 'white' },
|
|
||||||
type: 'input',
|
|
||||||
draggable: true
|
|
||||||
});
|
|
||||||
id++
|
|
||||||
if(item.services != null) {
|
|
||||||
item.services.forEach((item) => {
|
|
||||||
out.nodes.push({
|
|
||||||
id: '' + id,
|
|
||||||
data: {label: <div><Icon path={mdiAccessPointNetwork} size={0.75} className={"flowNode"}/> { item.frontend }</div>},
|
|
||||||
position: {x: (id * 25), y: 0},
|
|
||||||
style: { width: 'fit-content', backgroundColor: '#9367ef', color: 'white' },
|
|
||||||
type: 'output',
|
|
||||||
draggable: true
|
|
||||||
})
|
|
||||||
out.edges.push({
|
|
||||||
id: 'e' + envId + '-' + id,
|
|
||||||
source: '' + envId,
|
|
||||||
target: '' + id,
|
|
||||||
animated: true
|
|
||||||
})
|
|
||||||
id++
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeWidth = 215;
|
|
||||||
const nodeHeight = 75;
|
|
||||||
|
|
||||||
function getLayout(overview) {
|
|
||||||
const dagreGraph = new dagre.graphlib.Graph();
|
|
||||||
dagreGraph.setGraph({ rankdir: 'TB' });
|
|
||||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
|
||||||
|
|
||||||
overview.nodes.forEach((n) => {
|
|
||||||
dagreGraph.setNode(n.id, { width: nodeWidth, height: nodeHeight });
|
|
||||||
})
|
|
||||||
overview.edges.forEach((e) => {
|
|
||||||
dagreGraph.setEdge(e.source, e.target);
|
|
||||||
})
|
|
||||||
dagre.layout(dagreGraph);
|
|
||||||
|
|
||||||
return overview.nodes.map((n) => {
|
|
||||||
const nodeWithPosition = dagreGraph.node(n.id);
|
|
||||||
n.targetPosition = 'top';
|
|
||||||
n.sourcePosition = 'bottom';
|
|
||||||
n.position = {
|
|
||||||
x: nodeWithPosition.x - (nodeWidth / 2) + (Math.random() / 1000) + 50,
|
|
||||||
y: nodeWithPosition.y - (nodeHeight / 2) + 50,
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default Network;
|
export default Network;
|
@ -14,6 +14,14 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
font-family: 'Russo One', sans-serif;
|
font-family: 'Russo One', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#zrok-enable {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 100px;
|
||||||
|
}
|
||||||
|
#zrok-enable h1 {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: grid;
|
||||||
margin-left: 10%;
|
margin-left: 10%;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user