commit
3887b2a834
@ -15,6 +15,7 @@ const useStyles = makeStyles(theme => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
log: {
|
log: {
|
||||||
|
width: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
// Pin to bottom (render in time order).
|
// Pin to bottom (render in time order).
|
||||||
flexDirection: 'column-reverse',
|
flexDirection: 'column-reverse',
|
||||||
@ -106,6 +107,9 @@ const Log = ({ log = [] }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO(telackey): Why do these display in reverse?
|
||||||
|
log.reverse();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<div className={classes.log}>
|
<div className={classes.log}>
|
||||||
|
54
packages/console-app/src/components/LogPoller.js
Normal file
54
packages/console-app/src/components/LogPoller.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useQuery } from '@apollo/react-hooks';
|
||||||
|
|
||||||
|
import { ConsoleContext, useQueryStatusReducer } from '../hooks';
|
||||||
|
|
||||||
|
import LOGS from '../gql/logs.graphql';
|
||||||
|
|
||||||
|
import Log from './Log';
|
||||||
|
|
||||||
|
const MAX_LINES = 1000;
|
||||||
|
const _logBuffers = new Map();
|
||||||
|
|
||||||
|
const getLogBuffer = (name) => {
|
||||||
|
let buffer = _logBuffers.get(name);
|
||||||
|
if (!buffer) {
|
||||||
|
buffer = [];
|
||||||
|
_logBuffers.set(name, buffer);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LogPoller = ({ service }) => {
|
||||||
|
const { config } = useContext(ConsoleContext);
|
||||||
|
const logBuffer = getLogBuffer(service);
|
||||||
|
const data = useQueryStatusReducer(useQuery(LOGS, {
|
||||||
|
pollInterval: config.api.intervalLog,
|
||||||
|
variables: { service, incremental: logBuffer.length !== 0 }
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { incremental, lines } = JSON.parse(data.logs.json);
|
||||||
|
|
||||||
|
if (!incremental && lines.length) {
|
||||||
|
logBuffer.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
logBuffer.push(...lines);
|
||||||
|
if (logBuffer.length > MAX_LINES) {
|
||||||
|
logBuffer.splice(0, logBuffer.length - MAX_LINES);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Log log={logBuffer.slice(0)} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogPoller;
|
@ -2,11 +2,16 @@
|
|||||||
// Copyright 2020 DxOS.org
|
// Copyright 2020 DxOS.org
|
||||||
//
|
//
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
import { useQuery } from '@apollo/react-hooks';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
import Tab from '@material-ui/core/Tab';
|
||||||
|
import Tabs from '@material-ui/core/Tabs';
|
||||||
|
import TabContext from '@material-ui/lab/TabContext';
|
||||||
|
|
||||||
import Json from '../../components/Json';
|
import Json from '../../components/Json';
|
||||||
|
|
||||||
|
import SERVICE_STATUS from '../../gql/service_status.graphql';
|
||||||
import SYSTEM_STATUS from '../../gql/system_status.graphql';
|
import SYSTEM_STATUS from '../../gql/system_status.graphql';
|
||||||
|
|
||||||
import { ConsoleContext, useQueryStatusReducer } from '../../hooks';
|
import { ConsoleContext, useQueryStatusReducer } from '../../hooks';
|
||||||
@ -14,22 +19,64 @@ import { ConsoleContext, useQueryStatusReducer } from '../../hooks';
|
|||||||
import Panel from '../../components/Panel';
|
import Panel from '../../components/Panel';
|
||||||
import Toolbar from '../../components/Toolbar';
|
import Toolbar from '../../components/Toolbar';
|
||||||
|
|
||||||
|
const TAB_SYSTEM = 'system';
|
||||||
|
const TAB_SERVICES = 'services';
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
expand: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
|
||||||
|
panel: {
|
||||||
|
display: 'flex',
|
||||||
|
overflow: 'hidden',
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
|
||||||
|
paper: {
|
||||||
|
display: 'flex',
|
||||||
|
overflow: 'hidden',
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
const Status = () => {
|
const Status = () => {
|
||||||
|
const classes = useStyles();
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
|
const [tab, setTab] = useState(TAB_SYSTEM);
|
||||||
const systemResponse = useQueryStatusReducer(useQuery(SYSTEM_STATUS, { pollInterval: config.api.intervalQuery }));
|
const systemResponse = useQueryStatusReducer(useQuery(SYSTEM_STATUS, { pollInterval: config.api.intervalQuery }));
|
||||||
if (!systemResponse) {
|
const serviceResponse = useQueryStatusReducer(useQuery(SERVICE_STATUS, { pollInterval: config.api.intervalQuery }));
|
||||||
|
if (!systemResponse || !serviceResponse) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = JSON.parse(systemResponse.system_status.json);
|
const systemData = JSON.parse(systemResponse.system_status.json);
|
||||||
|
const serviceData = JSON.parse(serviceResponse.service_status.json);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
toolbar={
|
toolbar={
|
||||||
<Toolbar />
|
<Toolbar>
|
||||||
|
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
|
||||||
|
<Tab value={TAB_SYSTEM} label='System' />
|
||||||
|
<Tab value={TAB_SERVICES} label='Services' />
|
||||||
|
</Tabs>
|
||||||
|
</Toolbar>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Json data={data} />
|
<TabContext value={tab}>
|
||||||
|
{tab === TAB_SYSTEM && (
|
||||||
|
<div className={classes.panel}>
|
||||||
|
<Json data={systemData} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tab === TAB_SERVICES && (
|
||||||
|
<div className={classes.panel}>
|
||||||
|
<Json data={serviceData} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</TabContext>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -11,8 +11,10 @@ import Panel from '../../../components/Panel';
|
|||||||
import Toolbar from '../../../components/Toolbar';
|
import Toolbar from '../../../components/Toolbar';
|
||||||
|
|
||||||
import AppRecords from './AppRecords';
|
import AppRecords from './AppRecords';
|
||||||
|
import LogPoller from '../../../components/LogPoller';
|
||||||
|
|
||||||
const TAB_RECORDS = 'records';
|
const TAB_RECORDS = 'records';
|
||||||
|
const TAB_LOG = 'log';
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
root: {}
|
root: {}
|
||||||
@ -29,6 +31,7 @@ const Apps = () => {
|
|||||||
<Toolbar>
|
<Toolbar>
|
||||||
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
|
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
|
||||||
<Tab value={TAB_RECORDS} label='Records' />
|
<Tab value={TAB_RECORDS} label='Records' />
|
||||||
|
<Tab value={TAB_LOG} label='Log' />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
}
|
}
|
||||||
@ -36,6 +39,10 @@ const Apps = () => {
|
|||||||
{tab === TAB_RECORDS && (
|
{tab === TAB_RECORDS && (
|
||||||
<AppRecords />
|
<AppRecords />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{tab === TAB_LOG && (
|
||||||
|
<LogPoller service='app-server' />
|
||||||
|
)}
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -29,7 +29,7 @@ const AppRecords = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const records = data.wns_records.json;
|
const records = JSON.parse(data.wns_records.json);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
|
@ -3,122 +3,44 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import get from 'lodash.get';
|
|
||||||
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import { makeStyles } from '@material-ui/core';
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
import Paper from '@material-ui/core/Paper';
|
||||||
import Tab from '@material-ui/core/Tab';
|
import Tab from '@material-ui/core/Tab';
|
||||||
import Tabs from '@material-ui/core/Tabs';
|
import Tabs from '@material-ui/core/Tabs';
|
||||||
import TableBody from '@material-ui/core/TableBody';
|
import TabContext from '@material-ui/lab/TabContext';
|
||||||
import TableHead from '@material-ui/core/TableHead';
|
|
||||||
import TableRow from '@material-ui/core/TableRow';
|
|
||||||
|
|
||||||
import IPFS_STATUS from '../../../gql/ipfs_status.graphql';
|
|
||||||
import WNS_RECORDS from '../../../gql/wns_records.graphql';
|
|
||||||
|
|
||||||
import { useQueryStatusReducer } from '../../../hooks';
|
|
||||||
|
|
||||||
import Json from '../../../components/Json';
|
|
||||||
import Panel from '../../../components/Panel';
|
import Panel from '../../../components/Panel';
|
||||||
import Table from '../../../components/Table';
|
|
||||||
import TableCell from '../../../components/TableCell';
|
|
||||||
import Toolbar from '../../../components/Toolbar';
|
import Toolbar from '../../../components/Toolbar';
|
||||||
import { BooleanIcon } from '../../../components/BooleanIcon';
|
|
||||||
|
|
||||||
const RECORD_TYPE = 'wrn:service';
|
import LogPoller from '../../../components/LogPoller';
|
||||||
const SERVICE_TYPE = 'ipfs';
|
import IPFSStatus from './IPFSStatus';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
tableContainer: {
|
|
||||||
flex: 1,
|
|
||||||
overflowY: 'scroll'
|
|
||||||
},
|
|
||||||
|
|
||||||
table: {
|
|
||||||
tableLayout: 'fixed',
|
|
||||||
|
|
||||||
'& th': {
|
|
||||||
fontVariant: 'all-small-caps',
|
|
||||||
fontSize: 18,
|
|
||||||
cursor: 'ns-resize'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
connected: {
|
|
||||||
fontWeight: 'bold'
|
|
||||||
},
|
|
||||||
|
|
||||||
disconnected: {
|
|
||||||
fontStyle: 'italic'
|
|
||||||
},
|
|
||||||
|
|
||||||
colShort: {
|
|
||||||
width: '30%'
|
|
||||||
},
|
|
||||||
|
|
||||||
colWide: {},
|
|
||||||
|
|
||||||
colBoolean: {
|
|
||||||
width: '10%'
|
|
||||||
},
|
|
||||||
|
|
||||||
caption: {
|
|
||||||
backgroundColor: theme.palette.primary[500],
|
|
||||||
color: theme.palette.primary.contrastText,
|
|
||||||
paddingLeft: '1em',
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const TAB_STATUS = 'status';
|
const TAB_STATUS = 'status';
|
||||||
const TAB_LOG = 'log';
|
const TAB_LOG = 'log';
|
||||||
|
const TAB_SWARM_LOG = 'swarm';
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
expand: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
|
||||||
|
panel: {
|
||||||
|
display: 'flex',
|
||||||
|
overflow: 'hidden',
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
|
||||||
|
paper: {
|
||||||
|
display: 'flex',
|
||||||
|
overflow: 'hidden',
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
const IPFS = () => {
|
const IPFS = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [tab, setTab] = useState(TAB_STATUS);
|
const [tab, setTab] = useState(TAB_STATUS);
|
||||||
|
|
||||||
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
|
|
||||||
const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
|
||||||
variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } }
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!wnsResponse || !ipfsResponse) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ipfsData = JSON.parse(ipfsResponse.ipfs_status.json);
|
|
||||||
const registeredServers = JSON.parse(wnsResponse.wns_records.json);
|
|
||||||
|
|
||||||
const displayServers = registeredServers.map((service) => {
|
|
||||||
console.error(service);
|
|
||||||
const addresses = get(service, 'attributes.ipfs.addresses');
|
|
||||||
let connected = false;
|
|
||||||
for (const address of addresses) {
|
|
||||||
const parts = address.split('/');
|
|
||||||
const nodeId = parts[parts.length - 1];
|
|
||||||
connected = !!ipfsData.swarm.peers.find(({ peer }) => peer === nodeId);
|
|
||||||
if (connected) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: get(service, 'name'),
|
|
||||||
version: get(service, 'version'),
|
|
||||||
description: get(service, 'attributes.description'),
|
|
||||||
ipfs: get(service, 'attributes.ipfs'),
|
|
||||||
connected
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
displayServers.sort((a, b) => {
|
|
||||||
return a.connected && !b.connected ? -1 : b.connected && !a.connected ? 1 : b.name < a.name ? 1 : -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (displayServers.length === 0) {
|
|
||||||
displayServers.push({ name: 'None' });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
toolbar={
|
toolbar={
|
||||||
@ -126,46 +48,32 @@ const IPFS = () => {
|
|||||||
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
|
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
|
||||||
<Tab value={TAB_STATUS} label='Status' />
|
<Tab value={TAB_STATUS} label='Status' />
|
||||||
<Tab value={TAB_LOG} label='Log' />
|
<Tab value={TAB_LOG} label='Log' />
|
||||||
|
<Tab value={TAB_SWARM_LOG} label='Connection Log' />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<h4 className={classes.caption}>WNS-registered IPFS Servers</h4>
|
<TabContext value={tab}>
|
||||||
<Table stickyHeader size='small' className={classes.table}>
|
{tab === TAB_STATUS && (
|
||||||
<TableHead>
|
<div className={classes.panel}>
|
||||||
<TableRow>
|
<Paper className={classes.paper}>
|
||||||
<TableCell>Identifier</TableCell>
|
<IPFSStatus />
|
||||||
<TableCell size='medium'>Description</TableCell>
|
</Paper>
|
||||||
<TableCell size='icon'>Connected</TableCell>
|
</div>
|
||||||
<TableCell>Address</TableCell>
|
)}
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{displayServers.map(({ name, description, ipfs, connected }) => (
|
|
||||||
<TableRow key={name}>
|
|
||||||
<TableCell>{name}</TableCell>
|
|
||||||
<TableCell>{description}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<BooleanIcon yes={connected} />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{ipfs.addresses}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
|
|
||||||
<h4 className={classes.caption}>Local IPFS Server</h4>
|
{tab === TAB_LOG && (
|
||||||
<Json data={{
|
<div className={classes.panel}>
|
||||||
id: ipfsData.id.id,
|
<LogPoller service='ipfs' />
|
||||||
version: ipfsData.id.agentVersion,
|
</div>
|
||||||
addresses: ipfsData.id.addresses,
|
)}
|
||||||
peers: ipfsData.swarm.peers.length,
|
|
||||||
numObjects: ipfsData.repo.stats.numObjects,
|
{tab === TAB_SWARM_LOG && (
|
||||||
repoSize: ipfsData.repo.stats.repoSize
|
<div className={classes.panel}>
|
||||||
}}
|
<LogPoller service='ipfs-swarm-connect' />
|
||||||
/>
|
</div>
|
||||||
|
)}
|
||||||
|
</TabContext>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
157
packages/console-app/src/containers/panels/ipfs/IPFSStatus.js
Normal file
157
packages/console-app/src/containers/panels/ipfs/IPFSStatus.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
|
||||||
|
import { useQuery } from '@apollo/react-hooks';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
import TableBody from '@material-ui/core/TableBody';
|
||||||
|
import TableHead from '@material-ui/core/TableHead';
|
||||||
|
import TableRow from '@material-ui/core/TableRow';
|
||||||
|
|
||||||
|
import IPFS_STATUS from '../../../gql/ipfs_status.graphql';
|
||||||
|
import WNS_RECORDS from '../../../gql/wns_records.graphql';
|
||||||
|
|
||||||
|
import { useQueryStatusReducer } from '../../../hooks';
|
||||||
|
|
||||||
|
import Json from '../../../components/Json';
|
||||||
|
import Panel from '../../../components/Panel';
|
||||||
|
import Table from '../../../components/Table';
|
||||||
|
import TableCell from '../../../components/TableCell';
|
||||||
|
import { BooleanIcon } from '../../../components/BooleanIcon';
|
||||||
|
|
||||||
|
const RECORD_TYPE = 'wrn:service';
|
||||||
|
const SERVICE_TYPE = 'ipfs';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
tableContainer: {
|
||||||
|
flex: 1,
|
||||||
|
overflowY: 'scroll'
|
||||||
|
},
|
||||||
|
|
||||||
|
table: {
|
||||||
|
tableLayout: 'fixed',
|
||||||
|
|
||||||
|
'& th': {
|
||||||
|
fontVariant: 'all-small-caps',
|
||||||
|
fontSize: 18,
|
||||||
|
cursor: 'ns-resize'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
connected: {
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnected: {
|
||||||
|
fontStyle: 'italic'
|
||||||
|
},
|
||||||
|
|
||||||
|
colShort: {
|
||||||
|
width: '30%'
|
||||||
|
},
|
||||||
|
|
||||||
|
colWide: {},
|
||||||
|
|
||||||
|
colBoolean: {
|
||||||
|
width: '10%'
|
||||||
|
},
|
||||||
|
|
||||||
|
caption: {
|
||||||
|
backgroundColor: theme.palette.primary[500],
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
paddingLeft: '1em',
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const IPFSStatus = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
|
||||||
|
const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
|
variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } }
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!wnsResponse || !ipfsResponse) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipfsData = JSON.parse(ipfsResponse.ipfs_status.json);
|
||||||
|
const registeredServers = JSON.parse(wnsResponse.wns_records.json);
|
||||||
|
|
||||||
|
const displayServers = registeredServers.map((service) => {
|
||||||
|
console.error(service);
|
||||||
|
const addresses = get(service, 'attributes.ipfs.addresses');
|
||||||
|
let connected = false;
|
||||||
|
for (const address of addresses) {
|
||||||
|
const parts = address.split('/');
|
||||||
|
const nodeId = parts[parts.length - 1];
|
||||||
|
connected = !!ipfsData.swarm.peers.find(({ peer }) => peer === nodeId);
|
||||||
|
if (connected) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: get(service, 'name'),
|
||||||
|
version: get(service, 'version'),
|
||||||
|
description: get(service, 'attributes.description'),
|
||||||
|
ipfs: get(service, 'attributes.ipfs'),
|
||||||
|
connected
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
displayServers.sort((a, b) => {
|
||||||
|
return a.connected && !b.connected ? -1 : b.connected && !a.connected ? 1 : b.name < a.name ? 1 : -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (displayServers.length === 0) {
|
||||||
|
displayServers.push({ name: 'None' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel>
|
||||||
|
<h4 className={classes.caption}>WNS-registered IPFS Servers</h4>
|
||||||
|
<Table stickyHeader size='small' className={classes.table}>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Identifier</TableCell>
|
||||||
|
<TableCell size='medium'>Description</TableCell>
|
||||||
|
<TableCell size='icon'>Connected</TableCell>
|
||||||
|
<TableCell>Address</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{displayServers.map(({ name, description, ipfs, connected }) => (
|
||||||
|
<TableRow key={name}>
|
||||||
|
<TableCell>{name}</TableCell>
|
||||||
|
<TableCell>{description}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<BooleanIcon yes={connected} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{ipfs.addresses}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<h4 className={classes.caption}>Local IPFS Server</h4>
|
||||||
|
<Json data={{
|
||||||
|
id: ipfsData.id.id,
|
||||||
|
version: ipfsData.id.agentVersion,
|
||||||
|
addresses: ipfsData.id.addresses,
|
||||||
|
peers: ipfsData.swarm.peers.length,
|
||||||
|
numObjects: ipfsData.repo.stats.numObjects,
|
||||||
|
repoSize: ipfsData.repo.stats.repoSize
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IPFSStatus;
|
@ -1,26 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2020 DxOS.org
|
|
||||||
//
|
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
|
|
||||||
import SIGNAL_LOG from '../../../gql/signal_log.graphql';
|
|
||||||
|
|
||||||
import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
|
|
||||||
|
|
||||||
import Log from '../../../components/Log';
|
|
||||||
|
|
||||||
const SignalLog = () => {
|
|
||||||
const { config } = useContext(ConsoleContext);
|
|
||||||
const data = useQueryStatusReducer(useQuery(SIGNAL_LOG, { pollInterval: config.api.intervalLog }));
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Log log={data.signal_log.log} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SignalLog;
|
|
@ -12,8 +12,8 @@ import TabContext from '@material-ui/lab/TabContext';
|
|||||||
import Panel from '../../../components/Panel';
|
import Panel from '../../../components/Panel';
|
||||||
import Toolbar from '../../../components/Toolbar';
|
import Toolbar from '../../../components/Toolbar';
|
||||||
|
|
||||||
import SignalLog from './SignalLog';
|
|
||||||
import SignalStatus from './SignalStatus';
|
import SignalStatus from './SignalStatus';
|
||||||
|
import LogPoller from '../../../components/LogPoller';
|
||||||
|
|
||||||
const TAB_STATUS = 'status';
|
const TAB_STATUS = 'status';
|
||||||
const TAB_LOG = 'log';
|
const TAB_LOG = 'log';
|
||||||
@ -39,7 +39,6 @@ const useStyles = makeStyles(() => ({
|
|||||||
const Signal = () => {
|
const Signal = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [tab, setTab] = useState(TAB_STATUS);
|
const [tab, setTab] = useState(TAB_STATUS);
|
||||||
const [type, setType] = useState();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
@ -63,7 +62,7 @@ const Signal = () => {
|
|||||||
|
|
||||||
{tab === TAB_LOG && (
|
{tab === TAB_LOG && (
|
||||||
<div className={classes.panel}>
|
<div className={classes.panel}>
|
||||||
<SignalLog />
|
<LogPoller service='signal' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</TabContext>
|
</TabContext>
|
||||||
|
@ -9,10 +9,10 @@ import Tab from '@material-ui/core/Tab';
|
|||||||
import Tabs from '@material-ui/core/Tabs';
|
import Tabs from '@material-ui/core/Tabs';
|
||||||
import TabContext from '@material-ui/lab/TabContext';
|
import TabContext from '@material-ui/lab/TabContext';
|
||||||
|
|
||||||
|
import LogPoller from '../../../components/LogPoller';
|
||||||
import Panel from '../../../components/Panel';
|
import Panel from '../../../components/Panel';
|
||||||
import Toolbar from '../../../components/Toolbar';
|
import Toolbar from '../../../components/Toolbar';
|
||||||
|
|
||||||
import WNSLog from './WNSLog';
|
|
||||||
import WNSRecords, { WNSRecordType } from './WNSRecords';
|
import WNSRecords, { WNSRecordType } from './WNSRecords';
|
||||||
import WNSStatus from './WNSStatus';
|
import WNSStatus from './WNSStatus';
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ const WNS = () => {
|
|||||||
|
|
||||||
{tab === TAB_LOG && (
|
{tab === TAB_LOG && (
|
||||||
<div className={classes.panel}>
|
<div className={classes.panel}>
|
||||||
<WNSLog />
|
<LogPoller service='wns-lite' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</TabContext>
|
</TabContext>
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2020 DxOS.org
|
|
||||||
//
|
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
|
|
||||||
import WNS_LOG from '../../../gql/wns_log.graphql';
|
|
||||||
|
|
||||||
import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
|
|
||||||
|
|
||||||
import Log from '../../../components/Log';
|
|
||||||
|
|
||||||
const WNSLog = () => {
|
|
||||||
const { config } = useContext(ConsoleContext);
|
|
||||||
const data = useQueryStatusReducer(useQuery(WNS_LOG, { pollInterval: config.api.intervalLog }));
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Log log={data.wns_log.log} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WNSLog;
|
|
10
packages/console-app/src/gql/logs.graphql
Normal file
10
packages/console-app/src/gql/logs.graphql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2020 DxOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
query ($service: String!, $incremental: Boolean) {
|
||||||
|
logs(service: $service, incremental: $incremental) {
|
||||||
|
timestamp
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,8 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
query {
|
query {
|
||||||
wns_log @client {
|
service_status {
|
||||||
timestamp
|
timestamp,
|
||||||
log
|
json
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2020 DxOS.org
|
|
||||||
#
|
|
||||||
|
|
||||||
query {
|
|
||||||
signal_log @client {
|
|
||||||
timestamp
|
|
||||||
log
|
|
||||||
}
|
|
||||||
}
|
|
@ -105,7 +105,7 @@ export const createResolvers = config => {
|
|||||||
timestamp: timestamp(),
|
timestamp: timestamp(),
|
||||||
log: []
|
log: []
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
7
packages/console-app/src/version.json
Normal file
7
packages/console-app/src/version.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"name": "@dxos/console-app",
|
||||||
|
"buildDate": "2020-06-11T06:21:53.153Z",
|
||||||
|
"version": "1.0.0-beta.0"
|
||||||
|
}
|
||||||
|
}
|
@ -8,33 +8,21 @@ type JSONResult {
|
|||||||
json: String!
|
json: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result {
|
|
||||||
timestamp: String!
|
|
||||||
code: Int!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Log {
|
|
||||||
timestamp: String!
|
|
||||||
log: [String]!
|
|
||||||
}
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Schema
|
# Schema
|
||||||
#
|
#
|
||||||
|
|
||||||
type Mutation {
|
|
||||||
action(command: String!): Result!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
system_status: SystemStatus!
|
logs(service: String!, incremental: Boolean): JSONResult!
|
||||||
|
app_status: JSONResult!
|
||||||
ipfs_status: JSONResult!
|
ipfs_status: JSONResult!
|
||||||
|
ipfs_swarm_status: JSONResult!
|
||||||
|
service_status: JSONResult!
|
||||||
|
signal_status: JSONResult!
|
||||||
|
system_status: JSONResult!
|
||||||
wns_status: JSONResult!
|
wns_status: JSONResult!
|
||||||
wns_records(type: String): JSONResult!
|
|
||||||
wns_log: Log!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
mutation: Mutation
|
|
||||||
query: Query
|
query: Query
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2020 DxOS.org
|
|
||||||
#
|
|
||||||
|
|
||||||
type SystemStatus {
|
|
||||||
timestamp: String!
|
|
||||||
json: String!
|
|
||||||
}
|
|
@ -7,7 +7,9 @@ import defaultsDeep from 'lodash.defaultsdeep';
|
|||||||
|
|
||||||
import { ipfsResolvers } from './ipfs';
|
import { ipfsResolvers } from './ipfs';
|
||||||
import { systemResolvers } from './system';
|
import { systemResolvers } from './system';
|
||||||
|
import { logResolvers } from './log';
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
const log = debug('dxos:console:server:resolvers');
|
const log = debug('dxos:console:server:resolvers');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,14 +21,4 @@ export const resolvers = defaultsDeep({
|
|||||||
// TODO(burdon): Auth.
|
// TODO(burdon): Auth.
|
||||||
// https://www.apollographql.com/docs/apollo-server/data/errors/#codes
|
// https://www.apollographql.com/docs/apollo-server/data/errors/#codes
|
||||||
|
|
||||||
Mutation: {
|
}, ipfsResolvers, systemResolvers, logResolvers);
|
||||||
action: async (_, { command }) => {
|
|
||||||
log(`WNS action: ${command}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
timestamp: new Date().toUTCString(),
|
|
||||||
code: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, ipfsResolvers, systemResolvers);
|
|
||||||
|
64
packages/console-server/src/resolvers/log.js
Normal file
64
packages/console-server/src/resolvers/log.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import { spawnSync } from 'child_process';
|
||||||
|
|
||||||
|
class LogCache {
|
||||||
|
constructor (maxLines = 500) {
|
||||||
|
// Sets in JS iterate in insertion order.
|
||||||
|
this.buffer = new Set();
|
||||||
|
this.maxLines = maxLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
append (lines) {
|
||||||
|
const added = [];
|
||||||
|
for (const line of lines) {
|
||||||
|
if (!this.buffer.has(line)) {
|
||||||
|
this.buffer.add(line);
|
||||||
|
added.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.buffer.size > this.maxLines) {
|
||||||
|
this.buffer = new Set(Array.from(this.buffer).slice(parseInt(this.maxLines / 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _caches = new Map();
|
||||||
|
|
||||||
|
const getLogCache = (name) => {
|
||||||
|
let cache = _caches.get(name);
|
||||||
|
if (!cache) {
|
||||||
|
cache = new LogCache();
|
||||||
|
_caches.set(name, cache);
|
||||||
|
}
|
||||||
|
return cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLogs = async (name, incremental = false, lines = 100) => {
|
||||||
|
const command = 'wire';
|
||||||
|
const args = ['service', 'logs', '--lines', lines, name];
|
||||||
|
|
||||||
|
const child = spawnSync(command, args, { encoding: 'utf8' });
|
||||||
|
const logLines = child.stdout.split(/\n/);
|
||||||
|
const cache = getLogCache(name);
|
||||||
|
const added = cache.append(logLines);
|
||||||
|
|
||||||
|
return incremental ? added : Array.from(cache.buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logResolvers = {
|
||||||
|
Query: {
|
||||||
|
logs: async (_, { service, incremental }) => {
|
||||||
|
const lines = await getLogs(service, incremental);
|
||||||
|
return {
|
||||||
|
timestamp: new Date().toUTCString(),
|
||||||
|
json: JSON.stringify({ incremental, lines })
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
@ -6,6 +6,7 @@ import moment from 'moment';
|
|||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import si from 'systeminformation';
|
import si from 'systeminformation';
|
||||||
|
import { spawnSync } from "child_process";
|
||||||
|
|
||||||
const num = new Intl.NumberFormat('en', { maximumSignificantDigits: 3 });
|
const num = new Intl.NumberFormat('en', { maximumSignificantDigits: 3 });
|
||||||
|
|
||||||
@ -77,6 +78,18 @@ const getSystemInfo = async () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get system inforamtion.
|
||||||
|
* https://www.npmjs.com/package/systeminformation
|
||||||
|
*/
|
||||||
|
const getServiceInfo = async () => {
|
||||||
|
const command = 'wire';
|
||||||
|
const args = ['service', '--json'];
|
||||||
|
|
||||||
|
const child = spawnSync(command, args, { encoding: 'utf8' });
|
||||||
|
return JSON.parse(child.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
export const systemResolvers = {
|
export const systemResolvers = {
|
||||||
Query: {
|
Query: {
|
||||||
system_status: async () => {
|
system_status: async () => {
|
||||||
@ -86,6 +99,14 @@ export const systemResolvers = {
|
|||||||
timestamp: new Date().toUTCString(),
|
timestamp: new Date().toUTCString(),
|
||||||
json: JSON.stringify(system)
|
json: JSON.stringify(system)
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
service_status: async () => {
|
||||||
|
const serviceInfo = await getServiceInfo();
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp: new Date().toUTCString(),
|
||||||
|
json: JSON.stringify(serviceInfo)
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,6 @@ import SYSTEM_STATUS from '@dxos/console-app/src/gql/system_status.graphql';
|
|||||||
import { resolvers } from '../resolvers';
|
import { resolvers } from '../resolvers';
|
||||||
|
|
||||||
import API_SCHEMA from '../gql/api.graphql';
|
import API_SCHEMA from '../gql/api.graphql';
|
||||||
import SYSTEM_SCHEMA from '../gql/system.graphql';
|
|
||||||
|
|
||||||
const argv = yargs
|
const argv = yargs
|
||||||
.option('config', {
|
.option('config', {
|
||||||
@ -113,10 +112,7 @@ app.get(publicUrl, (req, res) => {
|
|||||||
|
|
||||||
const server = new ApolloServer({
|
const server = new ApolloServer({
|
||||||
typeDefs: [
|
typeDefs: [
|
||||||
API_SCHEMA,
|
API_SCHEMA
|
||||||
SYSTEM_SCHEMA
|
|
||||||
// WNS_EXTENSIONS,
|
|
||||||
// WNS_SCHEMA
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// https://www.apollographql.com/docs/graphql-tools/resolvers
|
// https://www.apollographql.com/docs/graphql-tools/resolvers
|
||||||
|
Loading…
Reference in New Issue
Block a user