let's get rid of the olde (#107)

This commit is contained in:
Michael Quigley 2022-12-21 14:10:10 -05:00
parent a7349802c6
commit c724699dd8
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
13 changed files with 6 additions and 475 deletions

View File

@ -1,137 +0,0 @@
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.token }</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 = 100;
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

View File

@ -1,8 +1,8 @@
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Register from "./Register";
import Register from "./register/Register";
import Console from "./console/Console";
import {useEffect, useState} from "react";
import Login from "./Login";
import Login from "./login/Login";
const App = () => {
const [user, setUser] = useState();

View File

@ -1,58 +0,0 @@
import {useEffect, useState} from "react";
import Login from "./Login";
import Version from "./Version";
import Token from "./Token";
import Logout from "./Logout";
import Account from "./Account";
import {ReactFlowProvider} from "react-flow-renderer";
const Console = () => {
const [user, setUser] = useState();
useEffect(() => {
const localUser = localStorage.getItem("user")
if (localUser) {
setUser(JSON.parse(localUser))
console.log('reloaded user', localUser)
}
}, []);
if (!user) {
return (
<Login loginSuccess={setUser}/>
);
}
const logout = () => {
setUser(null);
localStorage.clear();
}
return (
<div className="zrok">
<div className="container">
<div className="header">
<img alt="ziggy goes to space" src="ziggy.svg" width="65px"/>
<p className="header-title">zrok</p>
<div className={"header-status"}>
<div>
<p>{user.email}</p>
<Version/>
</div>
<div className={"header-controls"}>
<Token user={user}/>
<Logout user={user} logout={logout}/>
</div>
</div>
</div>
<div className="main">
<ReactFlowProvider>
<Account user={user}/>
</ReactFlowProvider>
</div>
</div>
</div>
);
}
export default Console;

View File

@ -1,26 +0,0 @@
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

View File

@ -1,75 +0,0 @@
import DataTable from 'react-data-table-component';
import Services from './Services';
import Icon from "@mdi/react";
import {mdiCloseOutline} from "@mdi/js";
import * as environment from './api/environment';
const Environments = (props) => {
const humanizeDuration = require("humanize-duration")
const disableEnvironment = (envId) => {
if(window.confirm('really disable environment "' + envId +'"?')) {
environment.disable({body: {identity: envId}}).then(resp => {
console.log(resp);
})
}
}
const columns = [
{
name: 'Description',
selector: row => row.environment.description,
sortable: true,
},
{
name: 'Host',
selector: row => row.environment.host,
sortable: true,
},
{
name: 'Address',
selector: row => row.environment.address,
sortable: true,
},
{
name: 'Identity',
selector: row => row.environment.zId,
sortable: true,
},
{
name: 'Actions',
selector: row => <>
<button data-value={row.environment.zId} onClick={e => disableEnvironment(row.environment.zId)} title={"Disable Environment '"+row.environment.zId+"'"}>
<Icon path={mdiCloseOutline} size={0.7}/>
</button>
</>
},
{
name: 'Uptime',
selector: row => humanizeDuration(Date.now() - row.environment.updatedAt),
sortable: true,
},
]
const servicesComponent = ({ data }) => <Services envId={data.environment.zId} services={data.services} />
const servicesExpanded = row => row.services != null && row.services.length > 0
return (
<div>
<h2>Environments</h2>
{ props.overview && props.overview.length > 0 && (
<div>
<DataTable
columns={columns}
data={props.overview}
defaultSortFieldId={1}
expandableRows
expandableRowsComponent={servicesComponent}
expandableRowExpanded={servicesExpanded}
/>
</div>
)}
</div>
)
};
export default Environments;

View File

@ -1,16 +0,0 @@
import Icon from '@mdi/react';
import { mdiLogout } from '@mdi/js';
const logoutIcon = mdiLogout;
const Logout = (props) => {
const onClick = () => {
props.logout()
}
return (
<button onClick={onClick} aria-label={"log out"} title={"Log out"}><Icon path={logoutIcon} size={.7}/></button>
);
}
export default Logout;

View File

@ -1,21 +0,0 @@
import Environments from './Environments';
import ReactFlow from "react-flow-renderer";
const Network = (props) => {
return (
<div>
<div className={"network"}>
<h1>Network</h1>
<ReactFlow
nodes={props.nodes}
edges={props.edges}
/>
</div>
<Environments
overview={props.overview}
/>
</div>
)
}
export default Network;

View File

@ -1,59 +0,0 @@
import DataTable from 'react-data-table-component';
import {Sparklines, SparklinesLine, SparklinesSpots} from 'react-sparklines';
import {mdiCloseOutline} from "@mdi/js";
import Icon from "@mdi/react";
import * as service from './api/service';
const Services = (props) => {
const humanizeDuration = require("humanize-duration")
const unshareService = (envId, svcToken) => {
if(window.confirm('really disable service "' + svcToken +'"?')) {
service.unshare({body: {envZId: envId, svcToken: svcToken}}).then(resp => {
console.log(resp)
})
}
}
const columns = [
{
name: 'Frontend',
selector: row => row.frontendEndpoint,
sortable: true,
},
{
name: 'Backend',
selector: row => row.backendProxyEndpoint,
sortable: true,
},
{
name: 'Actions',
selector: row => <>
<button data-value={row.name} onClick={e => unshareService(props.envId, row.token)} title={"Un-share Service '"+row.token+"'"}>
<Icon path={mdiCloseOutline} size={0.7}/>
</button>
</>
},
{
name: 'Uptime',
selector: row => humanizeDuration(Date.now() - row.updatedAt),
},
{
name: 'Activity',
cell: row => <Sparklines data={row.metrics} height={20} limit={60}><SparklinesLine color={"#3b2693"}/><SparklinesSpots/></Sparklines>
}
]
return (
<div className={"nested-services"}>
{ props.services && props.services.length > 0 && (
<DataTable
columns={columns}
data={props.services}
defaultSortFieldId={1}
/>
)}
</div>
)
}
export default Services;

View File

@ -1,53 +0,0 @@
import Icon from "@mdi/react";
import {mdiKey, mdiContentCopy} from "@mdi/js";
import Popover from "@mui/material/Popover";
import {useState} from "react";
const Token = (props) => {
const [anchorEl, setAnchorEl] = useState(null);
const handlePopoverClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handlePopoverClose = () => {
setAnchorEl(null);
}
const popoverOpen = Boolean(anchorEl);
const popoverId = popoverOpen ? 'token-popover' : undefined;
const text = "zrok enable " + props.user.token
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>
<button aria-describedby={popoverId} onClick={handlePopoverClick} title={"See Enable Secret"}><Icon path={mdiKey} size={0.7}/></button>
<Popover
id={popoverId}
open={popoverOpen}
anchorEl={anchorEl}
onClose={handlePopoverClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<div className={"popover"}>
<h3>Enable zrok access in your shell:</h3>
<pre>
$ <span id={"zrok-enable-command"}>{text}</span> <Icon path={mdiContentCopy} size={0.7} onClick={handleCopy}/>
</pre>
</div>
</Popover>
</div>
);
}
export default Token;

View File

@ -1,24 +0,0 @@
import {useEffect, useState} from "react";
import * as metadata from "./api/metadata";
const Version = () => {
const [v, setV] = useState('');
useEffect(() => {
let mounted = true;
metadata.version().then(resp => {
if(mounted) {
setV(resp.data);
}
});
return () => {
mounted = false;
};
}, []);
return (
<p>{v}</p>
);
}
export default Version;

View File

@ -1,7 +1,7 @@
import React, {useEffect, useState} from "react";
import {Button} from "react-bootstrap";
import Network from "./Network";
import {buildGraph, mergeGraph} from "./graph";
import {mergeGraph} from "./graph";
const Visualizer = (props) => {
const [networkGraph, setNetworkGraph] = useState({nodes: [], links: []});

View File

@ -1,5 +1,5 @@
import {useState} from "react";
import * as account from './api/account';
import * as account from '../api/account';
const Login = (props) => {
const [email, setEmail] = useState('');
@ -34,7 +34,7 @@ const Login = (props) => {
return (
<div className={"fullscreen"}>
<img src={"ziggy.svg"} width={200}/>
<img src={"/ziggy.svg"} width={200}/>
<h1>zrok</h1>
{message}
<form onSubmit={handleSubmit}>

View File

@ -2,7 +2,7 @@ import Icon from "@mdi/react";
import { useParams } from 'react-router-dom';
import {useEffect, useState} from "react";
import {mdiContentCopy} from "@mdi/js";
import * as account from "./api/account";
import * as account from "../api/account";
const RegistrationForm = (props) => {
const [password, setPassword] = useState('');