Merge pull request #36 from dxos/burdon/tables

Updated UX
This commit is contained in:
Thomas E Lackey 2020-07-20 18:12:28 -05:00 committed by GitHub
commit e55d056844
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 398 additions and 222 deletions

View File

@ -23,7 +23,7 @@ const useStyles = makeStyles(theme => ({
padding: theme.spacing(1), padding: theme.spacing(1),
'& div': { '& div': {
fontSize: 16, fontSize: 14,
fontFamily: 'monospace', fontFamily: 'monospace',
whiteSpace: 'nowrap' whiteSpace: 'nowrap'
} }

View File

@ -4,37 +4,56 @@
import React from 'react'; import React from 'react';
import ExitToApp from '@material-ui/icons/ExitToApp';
import Link from '@material-ui/core/Link'; import Link from '@material-ui/core/Link';
import LinkIcon from '@material-ui/icons/ExitToApp';
import { getServiceUrl } from '../util/config'; import { getServiceUrl } from '../util/config';
const QUERY = `{ const QUERY = `
queryRecords(attributes: [ query {
{ key: "name", value: { string: "%NAME%" }}]) { queryRecords(attributes: [{ key: "name", value: { string: "%NAME%" } }]) {
id type name bondId createTime expiryTime owners attributes { key, value { string, json } } id
type
name
bondId
createTime
expiryTime
owners
attributes {
key
value {
string
json
} }
}`; }
}
}
`;
/** /**
* Render link to record in WNS. * Render link to record in WNS.
* @param {Object} config * @param {Object} config
* @param {string} name * @param {string} name
* @param {string} [text] * @param {string} [text]
* @param {boolean} icon
*/ */
const QueryLink = ({ config, name, text, icon = false }) => { const QueryLink = ({ config, name, text, icon = false }) => {
const baseURL = getServiceUrl(config, 'wns.webui'); const baseURL = getServiceUrl(config, 'wns.webui');
const query = QUERY.replace('%NAME%', name); const query = QUERY.replace('%NAME%', name);
// NOTE: Playground bug opens two tabs.
const fullURL = encodeURI(`${baseURL}?query=${query}`); const fullURL = encodeURI(`${baseURL}?query=${query}`);
if (icon) {
return ( return (
<Link href={fullURL} target='wns'> <Link href={fullURL} target='gql'>
<ExitToApp /> {icon && (
<LinkIcon />
)}
{!icon && (
text || name
)}
</Link> </Link>
); );
}
return <Link href={fullURL} target='wns'>{text || name}</Link>;
}; };
export default QueryLink; export default QueryLink;

View File

@ -5,6 +5,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import React from 'react'; import React from 'react';
import { useHistory, useParams } from 'react-router'; import { useHistory, useParams } from 'react-router';
import { makeStyles } from '@material-ui/core'; import { makeStyles } from '@material-ui/core';
import List from '@material-ui/core/List'; import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
@ -17,7 +18,6 @@ const useStyles = makeStyles(theme => ({
flex: 1, flex: 1,
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'space-between' justifyContent: 'space-between'
// backgroundColor: theme.palette.grey[100]
}, },
list: { list: {

View File

@ -9,18 +9,21 @@ import MuiTableCell from '@material-ui/core/TableCell';
import { makeStyles } from '@material-ui/core'; import { makeStyles } from '@material-ui/core';
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
icon: {
width: 48
},
small: { small: {
width: 160 width: 130
}, },
medium: { medium: {
width: 220 width: 170
}, },
icon: { large: {
width: 120 width: 400
} }
})); }));
const TableCell = ({ children, size, monospace = false, title, ...rest }) => { const TableCell = ({ children, size, monospace = false, style, title, ...rest }) => {
const classes = useStyles(); const classes = useStyles();
return ( return (
@ -31,8 +34,10 @@ const TableCell = ({ children, size, monospace = false, title, ...rest }) => {
overflow: 'hidden', overflow: 'hidden',
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
verticalAlign: 'top',
fontFamily: monospace ? 'monospace' : 'inherit', fontFamily: monospace ? 'monospace' : 'inherit',
fontSize: monospace ? 14 : 'inherit' fontSize: monospace ? 14 : 13,
...style
}} }}
title={title} title={title}
> >

View File

@ -25,7 +25,7 @@ import Config from './panels/Config';
import IPFS from './panels/ipfs/IPFS'; import IPFS from './panels/ipfs/IPFS';
import Metadata from './panels/Metadata'; import Metadata from './panels/Metadata';
import Signaling from './panels/signal/Signaling'; import Signaling from './panels/signal/Signaling';
import Status from './panels/Status'; import System from './panels/system/Status';
import WNS from './panels/wns/WNS'; import WNS from './panels/wns/WNS';
// Global error handler. // Global error handler.
@ -52,7 +52,7 @@ const Main = ({ config }) => {
<Route path='/ipfs' component={IPFS} /> <Route path='/ipfs' component={IPFS} />
<Route path='/metadata' component={Metadata} /> <Route path='/metadata' component={Metadata} />
<Route path='/signaling' component={Signaling} /> <Route path='/signaling' component={Signaling} />
<Route path='/status' component={Status} /> <Route path='/system' component={System} />
<Route path='/wns' component={WNS} /> <Route path='/wns' component={WNS} />
</Layout> </Layout>
</Route> </Route>

View File

@ -30,14 +30,12 @@ const AppRecords = () => {
// TODO(telackey): Does this also need an interval? // TODO(telackey): Does this also need an interval?
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS)); const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
if (!appResponse || !ipfsResponse) { if (!appResponse || !ipfsResponse) {
return null; return null;
} }
const appData = JSON.parse(appResponse.wns_records.json); const appData = JSON.parse(appResponse.wns_records.json);
const ipfsData = JSON.parse(ipfsResponse.ipfs_status.json); const ipfsData = JSON.parse(ipfsResponse.ipfs_status.json);
const localRefs = new Set(ipfsData.refs.local); const localRefs = new Set(ipfsData.refs.local);
return ( return (
@ -45,23 +43,25 @@ const AppRecords = () => {
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell onClick={sortBy('name')}>Identifier</TableCell> <TableCell onClick={sortBy('name')}>Identifier</TableCell>
<TableCell onClick={sortBy('attributes.displayName')}>Name</TableCell>
<TableCell onClick={sortBy('version')} size='small'>Version</TableCell> <TableCell onClick={sortBy('version')} size='small'>Version</TableCell>
<TableCell onClick={sortBy('attributes.displayName')}>Name</TableCell>
<TableCell onClick={sortBy('createTime')} size='small'>Created</TableCell> <TableCell onClick={sortBy('createTime')} size='small'>Created</TableCell>
<TableCell size='icon'>Downloaded</TableCell> <TableCell size='icon' />
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{appData.sort(sorter).map(({ id, name, version, createTime, attributes: { displayName, publicUrl, package: hash } }) => { {appData.sort(sorter).map(({ id, name, version, createTime, attributes: { displayName, package: hash } }) => {
return ( return (
<TableRow key={id} size='small'> <TableRow key={id} size='small'>
<TableCell monospace> <TableCell monospace>
<AppLink config={config} name={name} /> <AppLink config={config} name={name} />
</TableCell> </TableCell>
<TableCell>{displayName}</TableCell>
<TableCell monospace> <TableCell monospace>
<AppLink config={config} name={name} version={version} text={version} /> <AppLink config={config} name={name} version={version} text={version} />
</TableCell> </TableCell>
<TableCell>
{displayName}
</TableCell>
<TableCell>{moment.utc(createTime).fromNow()}</TableCell> <TableCell>{moment.utc(createTime).fromNow()}</TableCell>
<TableCell> <TableCell>
<BooleanIcon yes={localRefs && localRefs.has(hash)} /> <BooleanIcon yes={localRefs && localRefs.has(hash)} />

View File

@ -37,9 +37,9 @@ const BotRecords = () => {
<TableRow> <TableRow>
<TableCell onClick={sortBy('name')}>Identifier</TableCell> <TableCell onClick={sortBy('name')}>Identifier</TableCell>
<TableCell onClick={sortBy('version')} size='small'>Version</TableCell> <TableCell onClick={sortBy('version')} size='small'>Version</TableCell>
<TableCell onClick={sortBy('createTime')} size='small'>Created</TableCell>
<TableCell onClick={sortBy('attributes.displayName')}>Name</TableCell> <TableCell onClick={sortBy('attributes.displayName')}>Name</TableCell>
<TableCell /> <TableCell onClick={sortBy('createTime')} size='small'>Created</TableCell>
<TableCell size='icon' />
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -48,8 +48,8 @@ const BotRecords = () => {
<TableRow key={id} size='small'> <TableRow key={id} size='small'>
<TableCell monospace>{name}</TableCell> <TableCell monospace>{name}</TableCell>
<TableCell monospace>{version}</TableCell> <TableCell monospace>{version}</TableCell>
<TableCell>{moment.utc(createTime).fromNow()}</TableCell>
<TableCell>{displayName}</TableCell> <TableCell>{displayName}</TableCell>
<TableCell>{moment.utc(createTime).fromNow()}</TableCell>
<TableCell /> <TableCell />
</TableRow> </TableRow>
); );

View File

@ -13,9 +13,12 @@ import Panel from '../../../components/Panel';
import Toolbar from '../../../components/Toolbar'; import Toolbar from '../../../components/Toolbar';
import LogPoller from '../../../components/LogPoller'; import LogPoller from '../../../components/LogPoller';
import IPFSNetwork from './IPFSNetwork';
import IPFSStatus from './IPFSStatus'; import IPFSStatus from './IPFSStatus';
const TAB_STATUS = 'status'; const TAB_STATUS = 'status';
const TAB_NETWORK = 'network';
const TAB_LOG = 'log'; const TAB_LOG = 'log';
const TAB_SWARM_LOG = 'swarm'; const TAB_SWARM_LOG = 'swarm';
@ -47,6 +50,7 @@ const IPFS = () => {
<Toolbar> <Toolbar>
<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_NETWORK} label='Network' />
<Tab value={TAB_LOG} label='Log' /> <Tab value={TAB_LOG} label='Log' />
<Tab value={TAB_SWARM_LOG} label='Connection Log' /> <Tab value={TAB_SWARM_LOG} label='Connection Log' />
</Tabs> </Tabs>
@ -55,23 +59,27 @@ const IPFS = () => {
> >
<TabContext value={tab}> <TabContext value={tab}>
{tab === TAB_STATUS && ( {tab === TAB_STATUS && (
<div className={classes.panel}>
<Paper className={classes.paper}> <Paper className={classes.paper}>
<IPFSStatus /> <IPFSStatus />
</Paper> </Paper>
</div> )}
{tab === TAB_NETWORK && (
<Paper className={classes.paper}>
<IPFSNetwork />
</Paper>
)} )}
{tab === TAB_LOG && ( {tab === TAB_LOG && (
<div className={classes.panel}> <Paper className={classes.paper}>
<LogPoller service='ipfs' /> <LogPoller service='ipfs' />
</div> </Paper>
)} )}
{tab === TAB_SWARM_LOG && ( {tab === TAB_SWARM_LOG && (
<div className={classes.panel}> <Paper className={classes.paper}>
<LogPoller service='ipfs-swarm-connect' /> <LogPoller service='ipfs-swarm-connect' />
</div> </Paper>
)} )}
</TabContext> </TabContext>
</Panel> </Panel>

View File

@ -0,0 +1,142 @@
//
// 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 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) => {
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' });
}
// TODO(burdon): Get Address (currenlty truncated).
return (
<Table stickyHeader size='small' className={classes.table}>
<TableHead>
<TableRow>
<TableCell>Identifier</TableCell>
<TableCell size='medium'>Description</TableCell>
<TableCell>Address</TableCell>
<TableCell size='small'>Connected</TableCell>
</TableRow>
</TableHead>
<TableBody>
{displayServers.map(({ name, description, ipfs, connected }) => (
<TableRow key={name}>
<TableCell>{name}</TableCell>
<TableCell>{description}</TableCell>
<TableCell>
{ipfs.addresses}
</TableCell>
<TableCell>
<BooleanIcon yes={connected} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
};
export default IPFSStatus;

View File

@ -3,13 +3,8 @@
// //
import React from 'react'; import React from 'react';
import get from 'lodash.get';
import { useQuery } from '@apollo/react-hooks'; 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 IPFS_STATUS from '../../../gql/ipfs_status.graphql';
import WNS_RECORDS from '../../../gql/wns_records.graphql'; import WNS_RECORDS from '../../../gql/wns_records.graphql';
@ -17,59 +12,11 @@ import WNS_RECORDS from '../../../gql/wns_records.graphql';
import { useQueryStatusReducer } from '../../../hooks'; import { useQueryStatusReducer } from '../../../hooks';
import Json from '../../../components/Json'; 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 RECORD_TYPE = 'wrn:service';
const SERVICE_TYPE = 'ipfs'; 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 IPFSStatus = () => {
const classes = useStyles();
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS)); const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, { const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } } variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } }
@ -80,77 +27,21 @@ const IPFSStatus = () => {
} }
const ipfsData = JSON.parse(ipfsResponse.ipfs_status.json); const ipfsData = JSON.parse(ipfsResponse.ipfs_status.json);
const registeredServers = JSON.parse(wnsResponse.wns_records.json); const data = {
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, id: ipfsData.id.id,
version: ipfsData.id.agentVersion, version: ipfsData.id.agentVersion,
addresses: ipfsData.id.addresses, addresses: ipfsData.id.addresses,
peers: ipfsData.swarm.peers.length, swarm: {
peers: ipfsData.swarm.peers.length
},
repo: {
numObjects: ipfsData.repo.stats.numObjects, numObjects: ipfsData.repo.stats.numObjects,
repoSize: ipfsData.repo.stats.repoSize repoSize: ipfsData.repo.stats.repoSize
}} }
/> };
</Panel>
return (
<Json data={data} />
); );
}; };

View File

@ -0,0 +1,57 @@
//
// Copyright 2020 DXOS.org
//
import React, { useContext } from 'react';
import { useQuery } from '@apollo/react-hooks';
import TableBody from '@material-ui/core/TableBody';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Table from '../../../components/Table';
import TableCell from '../../../components/TableCell';
import SIGNAL_STATUS from '../../../gql/signal_status.graphql';
import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
const SignalServers = () => {
const { config } = useContext(ConsoleContext);
const data = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { pollInterval: config.api.intervalQuery }));
if (!data) {
return null;
}
const { json: { channels = [] } } = data.signal_status;
return (
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Server</TableCell>
<TableCell size='small' s>Peers</TableCell>
</TableRow>
</TableHead>
<TableBody>
{channels.map(({ channel, peers = [] }) => {
return (
<TableRow key={channel} size='small'>
<TableCell monospace>
{channel}
</TableCell>
<TableCell monospace>
{peers.length}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
);
};
export default SignalServers;

View File

@ -1,26 +0,0 @@
//
// Copyright 2020 DXOS.org
//
import React, { useContext } from 'react';
import { useQuery } from '@apollo/react-hooks';
import SIGNAL_STATUS from '../../../gql/signal_status.graphql';
import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
import Json from '../../../components/Json';
const SignalStatus = () => {
const { config } = useContext(ConsoleContext);
const data = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { pollInterval: config.api.intervalQuery }));
if (!data) {
return null;
}
return (
<Json data={data.signal_status.json} />
);
};
export default SignalStatus;

View File

@ -11,10 +11,10 @@ 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 SignalStatus from './SignalStatus';
import LogPoller from '../../../components/LogPoller'; import LogPoller from '../../../components/LogPoller';
import SignalServers from './SignalServers';
const TAB_STATUS = 'status'; const TAB_STATUS = 'status';
const TAB_LOG = 'log'; const TAB_LOG = 'log';
@ -55,7 +55,7 @@ const Signal = () => {
{tab === TAB_STATUS && ( {tab === TAB_STATUS && (
<div className={classes.panel}> <div className={classes.panel}>
<Paper className={classes.paper}> <Paper className={classes.paper}>
<SignalStatus /> <SignalServers />
</Paper> </Paper>
</div> </div>
)} )}

View File

@ -0,0 +1,74 @@
//
// Copyright 2020 DXOS.org
//
import React from 'react';
import TableBody from '@material-ui/core/TableBody';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Table from '../../../components/Table';
import TableCell from '../../../components/TableCell';
import { useSorter } from '../../../hooks';
const format = (value, unit, symbol = '') => Math.floor(value / unit).toLocaleString() + symbol;
const SignalServers = ({ services }) => {
const [sorter] = useSorter('name');
const total = services.reduce((value, { memory }) => value + memory, 0);
return (
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell>Service</TableCell>
<TableCell style={{ width: 120 }}>Status</TableCell>
<TableCell style={{ width: 140, textAlign: 'right' }}>Memory</TableCell>
<TableCell style={{ width: 120, textAlign: 'right' }}>CPU</TableCell>
<TableCell>Command</TableCell>
</TableRow>
</TableHead>
<TableBody>
{services.sort(sorter).map(({ name, memory, cpu, status, exec }) => {
return (
<TableRow key={name} size='small'>
<TableCell monospace>
{name}
</TableCell>
<TableCell>
{status}
</TableCell>
<TableCell style={{ textAlign: 'right' }} monospace>
{format(memory, 1000, 'K')}
</TableCell>
<TableCell style={{ textAlign: 'right' }} monospace>
{cpu.toFixed(1)}
</TableCell>
<TableCell monospace>
{exec}
</TableCell>
</TableRow>
);
})}
</TableBody>
<TableBody>
<TableRow>
<TableCell style={{ fontVariant: 'all-small-caps' }}>Total</TableCell>
<TableCell />
<TableCell style={{ textAlign: 'right' }} monospace>
{format(total, 1000, 'K')}
</TableCell>
<TableCell />
<TableCell />
</TableRow>
</TableBody>
</Table>
</TableContainer>
);
};
export default SignalServers;

View File

@ -9,17 +9,19 @@ 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 Json from '../../components/Json'; import Json from '../../../components/Json';
import SERVICE_STATUS from '../../gql/service_status.graphql'; 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';
import Panel from '../../components/Panel'; import Panel from '../../../components/Panel';
import Toolbar from '../../components/Toolbar'; import Toolbar from '../../../components/Toolbar';
const TAB_SYSTEM = 'system'; import Services from './Services';
const TAB_INFO = 'status';
const TAB_SERVICES = 'services'; const TAB_SERVICES = 'services';
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
@ -43,37 +45,37 @@ const useStyles = makeStyles(() => ({
const Status = () => { const Status = () => {
const classes = useStyles(); const classes = useStyles();
const { config } = useContext(ConsoleContext); const { config } = useContext(ConsoleContext);
const [tab, setTab] = useState(TAB_SYSTEM); const [tab, setTab] = useState(TAB_SERVICES);
const systemResponse = useQueryStatusReducer(useQuery(SYSTEM_STATUS, { pollInterval: config.api.intervalQuery })); const systemResponse = useQueryStatusReducer(useQuery(SYSTEM_STATUS, { pollInterval: config.api.intervalQuery }));
const serviceResponse = useQueryStatusReducer(useQuery(SERVICE_STATUS, { pollInterval: config.api.intervalQuery })); const serviceResponse = useQueryStatusReducer(useQuery(SERVICE_STATUS, { pollInterval: config.api.intervalQuery }));
if (!systemResponse || !serviceResponse) { if (!systemResponse || !serviceResponse) {
return null; return null;
} }
const systemData = JSON.parse(systemResponse.system_status.json); const status = JSON.parse(systemResponse.system_status.json);
const serviceData = JSON.parse(serviceResponse.service_status.json); const services = JSON.parse(serviceResponse.service_status.json);
return ( return (
<Panel <Panel
toolbar={ toolbar={
<Toolbar> <Toolbar>
<Tabs value={tab} onChange={(_, value) => setTab(value)}> <Tabs value={tab} onChange={(_, value) => setTab(value)}>
<Tab value={TAB_SYSTEM} label='System' />
<Tab value={TAB_SERVICES} label='Services' /> <Tab value={TAB_SERVICES} label='Services' />
<Tab value={TAB_INFO} label='Info' />
</Tabs> </Tabs>
</Toolbar> </Toolbar>
} }
> >
<TabContext value={tab}> <TabContext value={tab}>
{tab === TAB_SYSTEM && ( {tab === TAB_SERVICES && (
<div className={classes.panel}> <div className={classes.panel}>
<Json data={systemData} /> <Services services={services} />
</div> </div>
)} )}
{tab === TAB_SERVICES && ( {tab === TAB_INFO && (
<div className={classes.panel}> <div className={classes.panel}>
<Json data={serviceData} /> <Json data={status} />
</div> </div>
)} )}
</TabContext> </TabContext>

View File

@ -83,13 +83,13 @@ const WNSRecords = ({ type }) => {
<Table> <Table>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell onClick={sortBy('type')} size='small'>Type</TableCell> <TableCell onClick={sortBy('type')} size='medium'>Type</TableCell>
<TableCell onClick={sortBy('name')}>Identifier</TableCell> <TableCell onClick={sortBy('name')}>Identifier</TableCell>
<TableCell size='icon'>GraphQL</TableCell>
<TableCell onClick={sortBy('attributes.displayName')}>Name</TableCell>
<TableCell onClick={sortBy('version')} size='small'>Version</TableCell> <TableCell onClick={sortBy('version')} size='small'>Version</TableCell>
<TableCell onClick={sortBy('attributes.displayName')}>Name</TableCell>
<TableCell onClick={sortBy('createTime')} size='small'>Created</TableCell> <TableCell onClick={sortBy('createTime')} size='small'>Created</TableCell>
<TableCell onClick={sortBy('package')} size='small'>Package</TableCell> <TableCell onClick={sortBy('package')}>Package</TableCell>
<TableCell size='icon' />
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -116,17 +116,21 @@ const WNSRecords = ({ type }) => {
<TableCell monospace> <TableCell monospace>
{appLink || name} {appLink || name}
</TableCell> </TableCell>
<TableCell>
<QueryLink config={config} name={name} icon />
</TableCell>
<TableCell>{displayName || service || description}</TableCell>
<TableCell monospace> <TableCell monospace>
{verLink || version} {verLink || version}
</TableCell> </TableCell>
<TableCell>{moment.utc(createTime).fromNow()}</TableCell> <TableCell>
{displayName || service || description}
</TableCell>
<TableCell>
{moment.utc(createTime).fromNow()}
</TableCell>
<TableCell monospace> <TableCell monospace>
{pkgLink} {pkgLink}
</TableCell> </TableCell>
<TableCell>
<QueryLink config={config} name={name} icon />
</TableCell>
</TableRow> </TableRow>
); );
} }

View File

@ -6,7 +6,7 @@ import get from 'lodash.get';
import { useState } from 'react'; import { useState } from 'react';
// TODO(burdon): Enable multiple sort order (e.g., id, version). // TODO(burdon): Enable multiple sort order (e.g., id, version).
export const useSorter = (initSort, initAscend) => { export const useSorter = (initSort, initAscend = true) => {
const [{ sort, ascend }, setSort] = useState({ sort: initSort, ascend: initAscend }); const [{ sort, ascend }, setSort] = useState({ sort: initSort, ascend: initAscend });
const sorter = (item1, item2) => { const sorter = (item1, item2) => {

View File

@ -14,8 +14,8 @@ import ServicesIcon from '@material-ui/icons/Storage';
export default { export default {
services: [ services: [
{ {
path: '/status', path: '/system',
title: 'Status', title: 'System',
icon: StatsIcon icon: StatsIcon
}, },
{ {

View File

@ -1,7 +1,7 @@
{ {
"build": { "build": {
"name": "@dxos/console-app", "name": "@dxos/console-app",
"buildDate": "2020-07-20T20:55:14.656Z", "buildDate": "2020-07-20T22:54:23.462Z",
"version": "1.0.0-beta.17" "version": "1.0.0-beta.19"
} }
} }