forked from cerc-io/laconic-console
Signal graph (#49)
* Initial signal graph * Minor update * Change graph using object mutator * Added SignalServer graph * Remove d3 dependencies * fixed signal kube system information * updated gem * Minor fix * update configuration * /api for signal * apollo1 * Added visx network graph * Remove info table. * Fixed tooltip zIndex Co-authored-by: Martin Acosta <Martín Acosta> Co-authored-by: Thomas E Lackey <telackey@bozemanpass.com> Co-authored-by: Thomas E Lackey <thomas@wireline.io>
This commit is contained in:
parent
afa99a564c
commit
e099fca2f8
@ -30,7 +30,7 @@ services:
|
|||||||
|
|
||||||
signal:
|
signal:
|
||||||
server: 'wss://kube.local/dxos/signal'
|
server: 'wss://kube.local/dxos/signal'
|
||||||
api: 'https://kube.local/dxos/signal'
|
api: 'https://kube.local/dxos/signal/api'
|
||||||
|
|
||||||
ipfs:
|
ipfs:
|
||||||
server: 'https://kube.local/dxos/ipfs/api'
|
server: 'https://kube.local/dxos/ipfs/api'
|
||||||
|
@ -30,7 +30,7 @@ services:
|
|||||||
|
|
||||||
signal:
|
signal:
|
||||||
server: 'ws://127.0.0.1:4000'
|
server: 'ws://127.0.0.1:4000'
|
||||||
api: 'http://127.0.0.1:4000'
|
api: 'http://127.0.0.1:4000/api'
|
||||||
|
|
||||||
ipfs:
|
ipfs:
|
||||||
server: 'http://127.0.0.1:5001'
|
server: 'http://127.0.0.1:5001'
|
||||||
|
@ -27,7 +27,7 @@ routes:
|
|||||||
webui: '/dxos/wns/console'
|
webui: '/dxos/wns/console'
|
||||||
|
|
||||||
signal:
|
signal:
|
||||||
api: '/dxos/signal'
|
api: '/dxos/signal/api'
|
||||||
|
|
||||||
ipfs:
|
ipfs:
|
||||||
server: '/dxos/ipfs/api'
|
server: '/dxos/ipfs/api'
|
||||||
|
@ -30,7 +30,7 @@ services:
|
|||||||
|
|
||||||
signal:
|
signal:
|
||||||
server: 'wss://apollo1.kube.moon.dxos.network/dxos/signal'
|
server: 'wss://apollo1.kube.moon.dxos.network/dxos/signal'
|
||||||
api: 'https://apollo1.kube.moon.dxos.network/dxos/signal'
|
api: 'https://apollo1.kube.moon.dxos.network/dxos/signal/api'
|
||||||
|
|
||||||
ipfs:
|
ipfs:
|
||||||
server: 'https://apollo1.kube.moon.dxos.network/dxos/ipfs/api'
|
server: 'https://apollo1.kube.moon.dxos.network/dxos/ipfs/api'
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"author": "DXOS.org",
|
"author": "DXOS.org",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 5%"
|
"> 2%"
|
||||||
],
|
],
|
||||||
"jest": {
|
"jest": {
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
@ -28,19 +28,25 @@
|
|||||||
"@apollo/react-components": "^3.1.5",
|
"@apollo/react-components": "^3.1.5",
|
||||||
"@apollo/react-hooks": "^3.1.5",
|
"@apollo/react-hooks": "^3.1.5",
|
||||||
"@babel/runtime": "^7.8.7",
|
"@babel/runtime": "^7.8.7",
|
||||||
"@dxos/debug": "^1.0.0-beta.20",
|
"@dxos/debug": "^1.0.0-beta.2",
|
||||||
"@dxos/gem-core": "^1.0.0-beta.11",
|
"@dxos/gem-core": "^1.0.0-beta.25",
|
||||||
"@dxos/react-ux": "^1.1.0-beta.0",
|
"@dxos/react-ux": "^1.1.0-beta.0",
|
||||||
"@material-ui/core": "^4.10.0",
|
"@material-ui/core": "^4.10.0",
|
||||||
"@material-ui/icons": "^4.9.1",
|
"@material-ui/icons": "^4.9.1",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.54",
|
"@material-ui/lab": "^4.0.0-alpha.54",
|
||||||
|
"@rehooks/component-size": "^1.0.3",
|
||||||
|
"@visx/network": "^1.0.0",
|
||||||
|
"@visx/tooltip": "^1.0.0",
|
||||||
|
"@visx/zoom": "^1.0.0",
|
||||||
"@wirelineio/registry-client": "^1.1.0-beta.2",
|
"@wirelineio/registry-client": "^1.1.0-beta.2",
|
||||||
"apollo-cache-inmemory": "^1.6.6",
|
"apollo-cache-inmemory": "^1.6.6",
|
||||||
"apollo-client": "^2.6.10",
|
"apollo-client": "^2.6.10",
|
||||||
|
"apollo-link": "^1.2.14",
|
||||||
"apollo-link-http": "^1.5.17",
|
"apollo-link-http": "^1.5.17",
|
||||||
"build-url": "^2.0.0",
|
"build-url": "^2.0.0",
|
||||||
"clsx": "^1.1.0",
|
"clsx": "^1.1.0",
|
||||||
"compare-versions": "^3.6.0",
|
"compare-versions": "^3.6.0",
|
||||||
|
"d3-force": "^2.1.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"graphql-tag": "^2.10.3",
|
"graphql-tag": "^2.10.3",
|
||||||
"lodash.defaultsdeep": "^4.6.1",
|
"lodash.defaultsdeep": "^4.6.1",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { ApolloClient } from 'apollo-client';
|
import { ApolloClient } from 'apollo-client';
|
||||||
|
import { ApolloLink } from 'apollo-link';
|
||||||
import { createHttpLink } from 'apollo-link-http';
|
import { createHttpLink } from 'apollo-link-http';
|
||||||
import { InMemoryCache } from 'apollo-cache-inmemory';
|
import { InMemoryCache } from 'apollo-cache-inmemory';
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ export const graphqlApi = config => {
|
|||||||
*/
|
*/
|
||||||
export const clientFactory = config => {
|
export const clientFactory = config => {
|
||||||
// https://www.apollographql.com/docs/link/
|
// https://www.apollographql.com/docs/link/
|
||||||
const link = createHttpLink({
|
const defaultLink = createHttpLink({
|
||||||
uri: graphqlApi(config),
|
uri: graphqlApi(config),
|
||||||
|
|
||||||
// TODO(burdon): Authentication: send signed message to server (from client wallet).
|
// TODO(burdon): Authentication: send signed message to server (from client wallet).
|
||||||
@ -36,11 +37,21 @@ export const clientFactory = config => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const serviceLinks = {
|
||||||
|
signal: createHttpLink({
|
||||||
|
uri: config.services.signal.api
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
// https://www.apollographql.com/docs/react/api/apollo-client/
|
// https://www.apollographql.com/docs/react/api/apollo-client/
|
||||||
return new ApolloClient({
|
return new ApolloClient({
|
||||||
connectToDevTools: true,
|
connectToDevTools: true,
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache(),
|
||||||
resolvers: createResolvers(config),
|
resolvers: createResolvers(config),
|
||||||
link
|
link: ApolloLink.split(
|
||||||
|
operation => operation.getContext().api && serviceLinks[operation.getContext().api],
|
||||||
|
operation => serviceLinks[operation.getContext().api].request(operation),
|
||||||
|
defaultLink
|
||||||
|
)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
173
packages/console-app/src/components/NetworkGraph.js
Normal file
173
packages/console-app/src/components/NetworkGraph.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import React, { useEffect, useRef, useReducer, useMemo, useCallback } from 'react';
|
||||||
|
import { forceLink, forceSimulation, forceCenter, forceCollide, forceManyBody, forceRadial } from 'd3-force';
|
||||||
|
|
||||||
|
import { Graph } from '@visx/network';
|
||||||
|
import { Tooltip, useTooltip, defaultStyles } from '@visx/tooltip';
|
||||||
|
import { Zoom } from '@visx/zoom';
|
||||||
|
|
||||||
|
import * as colors from '@material-ui/core/colors';
|
||||||
|
|
||||||
|
const kNodes = Symbol('nodes');
|
||||||
|
const kLinks = Symbol('links');
|
||||||
|
const kUpdate = Symbol('update');
|
||||||
|
|
||||||
|
const background = '#101020';
|
||||||
|
|
||||||
|
const nodeStyle = {
|
||||||
|
default: {
|
||||||
|
fill: colors.pink[400],
|
||||||
|
r: 20
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
fill: colors.blue[400],
|
||||||
|
r: 25
|
||||||
|
},
|
||||||
|
adjacent: {
|
||||||
|
fill: colors.red[400],
|
||||||
|
r: 20
|
||||||
|
},
|
||||||
|
detach: {
|
||||||
|
fill: colors.grey[400],
|
||||||
|
r: 15
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tooltipStyles = {
|
||||||
|
...defaultStyles,
|
||||||
|
backgroundColor: 'rgba(53,71,125,0.9)',
|
||||||
|
color: 'white',
|
||||||
|
padding: 12,
|
||||||
|
zIndex: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
const useForceUpdate = () => useReducer(x => !x, false)[1];
|
||||||
|
|
||||||
|
const useForce = ({ width, height, graph }) => {
|
||||||
|
const forceUpdate = useForceUpdate();
|
||||||
|
const d3force = useRef(null);
|
||||||
|
|
||||||
|
if (d3force.current) {
|
||||||
|
const oldNodes = d3force.current[kNodes];
|
||||||
|
const newNodes = graph.nodes.map(n => n.id);
|
||||||
|
let update = newNodes.filter(id => !oldNodes.includes(id)).length !== oldNodes.filter(id => !newNodes.includes(id)).length;
|
||||||
|
if (!update) {
|
||||||
|
const oldLinks = d3force.current[kLinks];
|
||||||
|
const newLinks = graph.links.map(l => l.id);
|
||||||
|
update = newLinks.filter(id => !oldLinks.includes(id)).length !== oldLinks.filter(id => !newLinks.includes(id)).length;
|
||||||
|
}
|
||||||
|
if (update) {
|
||||||
|
d3force.current[kUpdate] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let restart = true;
|
||||||
|
|
||||||
|
if (!d3force.current) {
|
||||||
|
restart = false;
|
||||||
|
|
||||||
|
d3force.current = forceSimulation(graph.nodes)
|
||||||
|
.force('link', forceLink().id(d => d.id).links(graph.links))
|
||||||
|
.force('charge', forceManyBody().strength(-2000))
|
||||||
|
.force('collision', forceCollide().strength(1));
|
||||||
|
|
||||||
|
d3force.current.on('tick', () => {
|
||||||
|
forceUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
d3force.current
|
||||||
|
.force('center', forceCenter(width / 2, height / 2).strength(0))
|
||||||
|
.force('r', forceRadial(200).strength(1));
|
||||||
|
|
||||||
|
d3force.current[kNodes] = graph.nodes.map(n => n.id);
|
||||||
|
d3force.current[kLinks] = graph.links.map(l => l.id);
|
||||||
|
|
||||||
|
if (!restart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
d3force.current.nodes(graph.nodes);
|
||||||
|
d3force.current.force('link').links(graph.links);
|
||||||
|
if (d3force.current[kUpdate]) {
|
||||||
|
d3force.current[kUpdate] = false;
|
||||||
|
d3force.current.alpha(1).restart();
|
||||||
|
} else {
|
||||||
|
d3force.current.restart();
|
||||||
|
}
|
||||||
|
}, [width, height, graph]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useNode = (handlers) => useMemo(() => function Node ({ node }) {
|
||||||
|
const { label, type = 'default', ...positions } = node;
|
||||||
|
const style = nodeStyle[type] || {};
|
||||||
|
|
||||||
|
const handleMouseEnter = useCallback((e) => handlers.onMouseEnter(node, e), [node]);
|
||||||
|
const handleMouseLeave = useCallback((e) => handlers.onMouseLeave(node, e), [node]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
<circle r={style.r} fill={style.fill} {...positions} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} />
|
||||||
|
{label &&
|
||||||
|
<text dy={style.r} dx={style.r + 2} fill='white' fontFamily='arial'>
|
||||||
|
{label}
|
||||||
|
</text>}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
export default function NetworkGraph ({ width, height, graph, onTooltip = () => {} }) {
|
||||||
|
useForce({ width, height, graph });
|
||||||
|
|
||||||
|
const { showTooltip, hideTooltip, tooltipOpen, tooltipData, tooltipLeft = 0, tooltipTop = 0 } = useTooltip();
|
||||||
|
|
||||||
|
const Node = useNode({
|
||||||
|
onMouseEnter: (node, e) => {
|
||||||
|
showTooltip({
|
||||||
|
tooltipTop: e.clientY,
|
||||||
|
tooltipLeft: e.clientX,
|
||||||
|
tooltipData: node
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onMouseLeave: () => {
|
||||||
|
hideTooltip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (width <= 0 || height <= 0 || !graph) ? null : (
|
||||||
|
<div style={{ width, height }}>
|
||||||
|
{tooltipData && tooltipData.data && tooltipOpen &&
|
||||||
|
<Tooltip key={Math.random()} left={tooltipLeft} top={tooltipTop} style={tooltipStyles}>
|
||||||
|
{onTooltip(tooltipData.data)}
|
||||||
|
</Tooltip>}
|
||||||
|
<Zoom
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
scaleXMin={1 / 2}
|
||||||
|
scaleXMax={4}
|
||||||
|
scaleYMin={1 / 2}
|
||||||
|
scaleYMax={4}
|
||||||
|
>
|
||||||
|
{zoom => (
|
||||||
|
<svg width={width} height={height}>
|
||||||
|
<rect
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
fill={background}
|
||||||
|
style={{ cursor: zoom.isDragging ? 'grabbing' : 'grab' }}
|
||||||
|
onTouchStart={zoom.dragStart} // eslint-disable-line
|
||||||
|
onTouchMove={zoom.dragMove} // eslint-disable-line
|
||||||
|
onTouchEnd={zoom.dragEnd} // eslint-disable-line
|
||||||
|
onMouseDown={zoom.dragStart} // eslint-disable-line
|
||||||
|
onMouseMove={zoom.dragMove} // eslint-disable-line
|
||||||
|
onMouseUp={zoom.dragEnd} // eslint-disable-line
|
||||||
|
/>
|
||||||
|
<g transform={zoom.toString()}>
|
||||||
|
<Graph graph={graph} top={height / 2} left={width / 2} nodeComponent={Node} />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</Zoom>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -6,7 +6,6 @@ import React, { useContext } from 'react';
|
|||||||
import { useQuery } from '@apollo/react-hooks';
|
import { useQuery } from '@apollo/react-hooks';
|
||||||
|
|
||||||
import TableBody from '@material-ui/core/TableBody';
|
import TableBody from '@material-ui/core/TableBody';
|
||||||
import TableContainer from '@material-ui/core/TableContainer';
|
|
||||||
import TableHead from '@material-ui/core/TableHead';
|
import TableHead from '@material-ui/core/TableHead';
|
||||||
import TableRow from '@material-ui/core/TableRow';
|
import TableRow from '@material-ui/core/TableRow';
|
||||||
|
|
||||||
@ -19,15 +18,31 @@ import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
|
|||||||
|
|
||||||
const SignalChannels = () => {
|
const SignalChannels = () => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const data = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { pollInterval: config.api.intervalQuery }));
|
const data = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { fetchPolicy: 'no-cache', pollInterval: config.api.pollInterval, context: { api: 'signal' } }));
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { json: { channels = [] } } = data.signal_status;
|
const { nodes = [] } = data.signal_status;
|
||||||
|
|
||||||
|
const channels = new Map();
|
||||||
|
nodes.forEach(node => {
|
||||||
|
const { signal: { topics = [] } } = node;
|
||||||
|
topics.forEach(topic => {
|
||||||
|
if (!channels.has(topic.id)) {
|
||||||
|
channels.set(topic.id, {
|
||||||
|
id: topic.id,
|
||||||
|
peers: topic.peers
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ch = channels.get(topic.id);
|
||||||
|
ch.peers = [...ch.peers, ...topic.peers];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer>
|
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@ -36,11 +51,11 @@ const SignalChannels = () => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{channels.map(({ channel, peers = [] }) => {
|
{Array.from(channels.values()).map(({ id, peers = [] }) => {
|
||||||
return (
|
return (
|
||||||
<TableRow key={channel} size='small'>
|
<TableRow key={id} size='small'>
|
||||||
<TableCell monospace>
|
<TableCell monospace>
|
||||||
{channel}
|
{id}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell monospace>
|
<TableCell monospace>
|
||||||
{peers.length}
|
{peers.length}
|
||||||
@ -50,7 +65,6 @@ const SignalChannels = () => {
|
|||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,52 +2,184 @@
|
|||||||
// Copyright 2020 DXOS.org
|
// Copyright 2020 DXOS.org
|
||||||
//
|
//
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext, useRef, useEffect, useState, useCallback } from 'react';
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
import { useQuery } from '@apollo/react-hooks';
|
||||||
|
import useComponentSize from '@rehooks/component-size';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import Grid from '@material-ui/core/Grid';
|
||||||
import TableBody from '@material-ui/core/TableBody';
|
import TableBody from '@material-ui/core/TableBody';
|
||||||
import TableContainer from '@material-ui/core/TableContainer';
|
import TableContainer from '@material-ui/core/TableContainer';
|
||||||
import TableHead from '@material-ui/core/TableHead';
|
import TableHead from '@material-ui/core/TableHead';
|
||||||
import TableRow from '@material-ui/core/TableRow';
|
import TableRow from '@material-ui/core/TableRow';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
import Box from '@material-ui/core/Box';
|
||||||
|
import Collapse from '@material-ui/core/Collapse';
|
||||||
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||||
|
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
|
||||||
|
|
||||||
import Table from '../../../components/Table';
|
import Table from '../../../components/Table';
|
||||||
import TableCell from '../../../components/TableCell';
|
import TableCell from '../../../components/TableCell';
|
||||||
|
import NetworkGraph from '../../../components/NetworkGraph';
|
||||||
|
|
||||||
import SIGNAL_STATUS from '../../../gql/signal_status.graphql';
|
import SIGNAL_STATUS from '../../../gql/signal_status.graphql';
|
||||||
|
|
||||||
import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
|
import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
|
||||||
|
|
||||||
const SignalServers = () => {
|
const buildDataGraph = (rootId, prevGraph, nodes) => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const newGraph = { nodes: [], links: [] };
|
||||||
const data = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { pollInterval: config.api.intervalQuery }));
|
|
||||||
if (!data) {
|
const rootNode = nodes.find(n => n.id === rootId);
|
||||||
return null;
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
let type = 'detach';
|
||||||
|
if (rootId === node.id) {
|
||||||
|
type = 'root';
|
||||||
|
} else {
|
||||||
|
const isAdjacent = rootNode.connections.find(conn => conn.target === node.id) || node.connections.find(conn => conn.target === rootId);
|
||||||
|
if (isAdjacent) {
|
||||||
|
type = 'adjacent';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { json: { signals = [] } } = data.signal_status;
|
const oldNode = prevGraph.nodes.find(n => n.id === node.id) || {};
|
||||||
|
const newNode = { ...oldNode, id: node.id, label: node.id.slice(0, 6), type, data: node };
|
||||||
|
if (type === 'root') {
|
||||||
|
newNode.fx = 0;
|
||||||
|
newNode.fy = 0;
|
||||||
|
}
|
||||||
|
newGraph.nodes.push(newNode);
|
||||||
|
});
|
||||||
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
node.connections.forEach(conn => {
|
||||||
|
newGraph.links.push({ id: conn.id, source: node.id, target: conn.target });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return newGraph;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useDataGraph = (response) => {
|
||||||
|
const [dataGraph, setDataGraph] = useState({ updatedAt: 0, nodes: [], links: [] });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!response) return;
|
||||||
|
const { id: rootId, nodes = [] } = response.signal_status;
|
||||||
|
const updatedAt = moment(response.signal_status.updatedAt).valueOf();
|
||||||
|
|
||||||
|
if (dataGraph.updatedAt >= updatedAt) return;
|
||||||
|
|
||||||
|
const graph = buildDataGraph(rootId, dataGraph, nodes);
|
||||||
|
setDataGraph({
|
||||||
|
updatedAt,
|
||||||
|
...graph
|
||||||
|
});
|
||||||
|
}, [response && response.signal_status.updatedAt]);
|
||||||
|
|
||||||
|
return dataGraph;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useRowStyles = makeStyles({
|
||||||
|
root: {
|
||||||
|
'& > *': {
|
||||||
|
borderBottom: 'unset'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function Row (props) {
|
||||||
|
const { row } = props;
|
||||||
|
|
||||||
|
const classes = useRowStyles();
|
||||||
|
|
||||||
|
const system = row.kubeStatus.system;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<TableRow className={classes.root}>
|
||||||
|
<TableCell component='th' scope='row'>
|
||||||
|
{row.id}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align='right'>{row.signal.topics.reduce((prev, curr) => prev + curr.peers.length, 0)}</TableCell>
|
||||||
|
<TableCell align='right'>{system?.version || '-'}</TableCell>
|
||||||
|
<TableCell align='right'>{system?.nodejs?.version || '-'}</TableCell>
|
||||||
|
<TableCell align='right'>{system?.memory?.used || '-'}</TableCell>
|
||||||
|
<TableCell align='right'>{system?.memory?.total || '-'}</TableCell>
|
||||||
|
<TableCell align='right'>{system?.time?.up ? moment(system?.time?.up).format('lll') : '-'}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignalServers () {
|
||||||
|
const { config } = useContext(ConsoleContext);
|
||||||
|
const response = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { fetchPolicy: 'no-cache', pollInterval: config.api.pollInterval, context: { api: 'signal' } }));
|
||||||
|
|
||||||
|
const data = useDataGraph(response);
|
||||||
|
|
||||||
|
const sizeRef = useRef(null);
|
||||||
|
const { width, height } = useComponentSize(sizeRef);
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(null);
|
||||||
|
|
||||||
|
const handleOpen = useCallback(
|
||||||
|
(id) => {
|
||||||
|
if (open && open === id) {
|
||||||
|
setOpen(null);
|
||||||
|
} else {
|
||||||
|
setOpen(id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[open]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container spacing={0} direction='column' alignItems='stretch' ref={sizeRef}>
|
||||||
|
<Grid item xs>
|
||||||
|
<NetworkGraph
|
||||||
|
width={width}
|
||||||
|
height={height / 2}
|
||||||
|
graph={data}
|
||||||
|
onTooltip={(node) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<strong>WebRTC Peers:</strong> {node.signal.topics.reduce((prev, curr) => prev + curr.peers.length, 0)}
|
||||||
|
<br />
|
||||||
|
{node.kubeStatus.services.map((service) => {
|
||||||
|
return <span key={service.name}><strong>{service.name}:</strong> {service.status}<br /></span>;
|
||||||
|
})}
|
||||||
|
</>);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Signal Server</TableCell>
|
<TableCell>Signal</TableCell>
|
||||||
|
<TableCell align='right'>Peers (WebRTC)</TableCell>
|
||||||
|
<TableCell align='right'>Kube version</TableCell>
|
||||||
|
<TableCell align='right'>Node.JS version</TableCell>
|
||||||
|
<TableCell align='right'>Memory usage</TableCell>
|
||||||
|
<TableCell align='right'>Memory total</TableCell>
|
||||||
|
<TableCell align='right'>Uptime</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{signals.map((signal) => {
|
{data && data.nodes.map(({ data }) => {
|
||||||
return (
|
return <Row key={data.id} row={data} open={open && open === data.id} setOpen={handleOpen} />;
|
||||||
<TableRow key={signal} size='small'>
|
|
||||||
<TableCell monospace>
|
|
||||||
{signal}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default SignalServers;
|
export default SignalServers;
|
||||||
|
@ -3,8 +3,41 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
query {
|
query {
|
||||||
signal_status @client {
|
signal_status: status {
|
||||||
timestamp
|
id
|
||||||
json
|
updatedAt,
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
kubeStatus {
|
||||||
|
system {
|
||||||
|
memory {
|
||||||
|
total
|
||||||
|
used
|
||||||
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
up
|
||||||
|
}
|
||||||
|
|
||||||
|
nodejs {
|
||||||
|
version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
services {
|
||||||
|
name
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connections {
|
||||||
|
id
|
||||||
|
target
|
||||||
|
}
|
||||||
|
signal {
|
||||||
|
topics {
|
||||||
|
id
|
||||||
|
peers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"name": "@dxos/console-app",
|
"name": "@dxos/console-app",
|
||||||
"buildDate": "2020-08-27T19:33:06.925Z",
|
"buildDate": "2020-10-07T16:33:05.270Z",
|
||||||
"version": "1.1.0-beta.1"
|
"version": "1.1.0-beta.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ services:
|
|||||||
webui: 'https://kube.local/dxos/wns/console'
|
webui: 'https://kube.local/dxos/wns/console'
|
||||||
|
|
||||||
signal:
|
signal:
|
||||||
server: 'wss://kube.local/dxos/signal'
|
server: 'wss://kube.local/dxos/signal/api'
|
||||||
api: 'https://kube.local/dxos/signal'
|
api: 'https://kube.local/dxos/signal'
|
||||||
|
|
||||||
ipfs:
|
ipfs:
|
||||||
|
@ -30,7 +30,7 @@ services:
|
|||||||
|
|
||||||
signal:
|
signal:
|
||||||
server: 'wss://apollo1.kube.moon.dxos.network/dxos/signal'
|
server: 'wss://apollo1.kube.moon.dxos.network/dxos/signal'
|
||||||
api: 'https://apollo1.kube.moon.dxos.network/dxos/signal'
|
api: 'https://apollo1.kube.moon.dxos.network/dxos/signal/api'
|
||||||
|
|
||||||
ipfs:
|
ipfs:
|
||||||
server: 'https://apollo1.kube.moon.dxos.network/dxos/ipfs/api'
|
server: 'https://apollo1.kube.moon.dxos.network/dxos/ipfs/api'
|
||||||
|
@ -30,7 +30,7 @@ services:
|
|||||||
|
|
||||||
signal:
|
signal:
|
||||||
server: 'ws://127.0.0.1:4000'
|
server: 'ws://127.0.0.1:4000'
|
||||||
api: 'http://127.0.0.1:4000'
|
api: 'http://127.0.0.1:4000/api'
|
||||||
|
|
||||||
ipfs:
|
ipfs:
|
||||||
server: 'http://127.0.0.1:5001'
|
server: 'http://127.0.0.1:5001'
|
||||||
|
Loading…
Reference in New Issue
Block a user