forked from cerc-io/laconic-console
fix: Add running bots tab. (#56)
* fix: Add bots resolver. * minot fix. * test commit. * add bot kill * Integration. * add refetch. * Lint. * Formatting fix. * Better error handling.
This commit is contained in:
parent
801d54f0c5
commit
3c731b3b2b
@ -30,9 +30,11 @@
|
|||||||
"lerna": "^3.19.0"
|
"lerna": "^3.19.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dxos/cli": "^2.0.8",
|
"@dxos/cli": "2.0.20-alpha.0",
|
||||||
"@dxos/cli-app": "^2.0.8",
|
"@dxos/cli-app": "2.0.20-alpha.0",
|
||||||
"@dxos/cli-wns": "^2.0.8",
|
"@dxos/cli-bot": "2.0.20-alpha.0",
|
||||||
|
"@dxos/cli-data": "2.0.20-alpha.0",
|
||||||
|
"@dxos/cli-wns": "2.0.20-alpha.0",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-config-semistandard": "^15.0.0",
|
"eslint-config-semistandard": "^15.0.0",
|
||||||
|
@ -11,7 +11,7 @@ app:
|
|||||||
publicUrl: '/console'
|
publicUrl: '/console'
|
||||||
|
|
||||||
api:
|
api:
|
||||||
server: 'https://alpha.kube.moon.dxos.network'
|
server: 'https://apollo1.kube.moon.dxos.network'
|
||||||
path: '/api'
|
path: '/api'
|
||||||
intervalLog: 5000
|
intervalLog: 5000
|
||||||
pollInterval: 10000
|
pollInterval: 10000
|
||||||
@ -22,19 +22,19 @@ system:
|
|||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
prefix: '/app'
|
prefix: '/app'
|
||||||
server: 'https://alpha.kube.moon.dxos.network'
|
server: 'https://apollo1.kube.moon.dxos.network'
|
||||||
|
|
||||||
wns:
|
wns:
|
||||||
server: 'https://alpha.kube.moon.dxos.network/dxos/wns/api'
|
server: 'https://apollo1.kube.moon.dxos.network/dxos/wns/api'
|
||||||
webui: 'https://alpha.kube.moon.dxos.network/dxos/wns/console'
|
webui: 'https://apollo1.kube.moon.dxos.network/dxos/wns/console'
|
||||||
|
|
||||||
signal:
|
signal:
|
||||||
server: 'wss://alpha.kube.moon.dxos.network/dxos/signal'
|
server: 'wss://apollo1.kube.moon.dxos.network/dxos/signal'
|
||||||
api: 'https://alpha.kube.moon.dxos.network/dxos/signal/api'
|
api: 'https://apollo1.kube.moon.dxos.network/dxos/signal/api'
|
||||||
|
|
||||||
ipfs:
|
ipfs:
|
||||||
server: 'https://alpha.kube.moon.dxos.network/dxos/ipfs/api'
|
server: 'https://apollo1.kube.moon.dxos.network/dxos/ipfs/api'
|
||||||
gateway: 'https://alpha.kube.moon.dxos.network/dxos/ipfs/gateway'
|
gateway: 'https://apollo1.kube.moon.dxos.network/dxos/ipfs/gateway'
|
||||||
|
|
||||||
wellknown:
|
wellknown:
|
||||||
endpoint: 'https://alpha.kube.moon.dxos.network/.well-known/dxos'
|
endpoint: 'https://apollo1.kube.moon.dxos.network/.well-known/dxos'
|
||||||
|
40
packages/console-app/src/components/BotControls.js
Normal file
40
packages/console-app/src/components/BotControls.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DXOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import StopIcon from '@material-ui/icons/HighlightOff';
|
||||||
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||||
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
stop: {
|
||||||
|
color: theme.palette.error.main
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const BotControls = ({ onStop }) => {
|
||||||
|
const [stopPressed, setStopPressed] = useState(false);
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const stopBot = () => {
|
||||||
|
if (!stopPressed) {
|
||||||
|
setStopPressed(true);
|
||||||
|
onStop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{onStop && (
|
||||||
|
<IconButton onClick={stopBot} title='Stop'>
|
||||||
|
{!stopPressed && (<StopIcon className={classes.stop} />)}
|
||||||
|
{stopPressed && (<CircularProgress className={classes.stop} size={24} />)}
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BotControls;
|
@ -26,7 +26,7 @@ const getLogBuffer = (name) => {
|
|||||||
const LogPoller = ({ service }) => {
|
const LogPoller = ({ service }) => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const logBuffer = getLogBuffer(service);
|
const logBuffer = getLogBuffer(service);
|
||||||
const data = useQueryStatusReducer(useQuery(LOGS, {
|
const { data } = useQueryStatusReducer(useQuery(LOGS, {
|
||||||
pollInterval: config.api.intervalLog,
|
pollInterval: config.api.intervalLog,
|
||||||
variables: { service, incremental: logBuffer.length !== 0 }
|
variables: { service, incremental: logBuffer.length !== 0 }
|
||||||
}));
|
}));
|
||||||
|
@ -30,8 +30,8 @@ const useStyles = makeStyles(theme => ({
|
|||||||
const VersionCheck = () => {
|
const VersionCheck = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [{ current, latest }, setUpgrade] = useState({});
|
const [{ current, latest }, setUpgrade] = useState({});
|
||||||
const statusResponse = useQueryStatusReducer(useQuery(SYSTEM_STATUS));
|
const { data: statusResponse } = useQueryStatusReducer(useQuery(SYSTEM_STATUS));
|
||||||
const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
const { data: wnsResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
pollInterval: CHECK_INTERVAL,
|
pollInterval: CHECK_INTERVAL,
|
||||||
variables: { attributes: { type: 'wrn:resource' } }
|
variables: { attributes: { type: 'wrn:resource' } }
|
||||||
}));
|
}));
|
||||||
|
@ -24,13 +24,13 @@ import AppLink from '../../../components/AppLink';
|
|||||||
const AppRecords = () => {
|
const AppRecords = () => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const [sorter, sortBy] = useSorter('createTime', false);
|
const [sorter, sortBy] = useSorter('createTime', false);
|
||||||
const appResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
const { data: appResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
pollInterval: config.api.intervalQuery,
|
pollInterval: config.api.intervalQuery,
|
||||||
variables: { attributes: { type: 'wrn:app' } }
|
variables: { attributes: { type: 'wrn:app' } }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// TODO(telackey): Does this also need an interval?
|
// TODO(telackey): Does this also need an interval?
|
||||||
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
|
const { data: ipfsResponse } = useQueryStatusReducer(useQuery(IPFS_STATUS));
|
||||||
if (!appResponse || !ipfsResponse) {
|
if (!appResponse || !ipfsResponse) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import moment from 'moment';
|
|||||||
const BotRecords = () => {
|
const BotRecords = () => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const [sorter, sortBy] = useSorter('createTime', false);
|
const [sorter, sortBy] = useSorter('createTime', false);
|
||||||
const data = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
const { data } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
pollInterval: config.api.intervalQuery,
|
pollInterval: config.api.intervalQuery,
|
||||||
variables: { attributes: { type: 'wrn:bot' } }
|
variables: { attributes: { type: 'wrn:bot' } }
|
||||||
}));
|
}));
|
||||||
|
@ -12,9 +12,11 @@ import Toolbar from '../../../components/Toolbar';
|
|||||||
|
|
||||||
import BotRecords from './BotRecords';
|
import BotRecords from './BotRecords';
|
||||||
import LogPoller from '../../../components/LogPoller';
|
import LogPoller from '../../../components/LogPoller';
|
||||||
|
import RunningBots from './RunningBots';
|
||||||
|
|
||||||
const TAB_RECORDS = 'records';
|
const TAB_RECORDS = 'records';
|
||||||
const TAB_LOG = 'log';
|
const TAB_LOG = 'log';
|
||||||
|
const TAB_DATA = 'running bots';
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
root: {}
|
root: {}
|
||||||
@ -32,6 +34,7 @@ const Bots = () => {
|
|||||||
<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' />
|
<Tab value={TAB_LOG} label='Log' />
|
||||||
|
<Tab value={TAB_DATA} label='Running Bots' />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
}
|
}
|
||||||
@ -43,6 +46,9 @@ const Bots = () => {
|
|||||||
{tab === TAB_LOG && (
|
{tab === TAB_LOG && (
|
||||||
<LogPoller service='bot-factory' />
|
<LogPoller service='bot-factory' />
|
||||||
)}
|
)}
|
||||||
|
{tab === TAB_DATA && (
|
||||||
|
<RunningBots />
|
||||||
|
)}
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DXOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useQuery, useMutation } from '@apollo/react-hooks';
|
||||||
|
import TableHead from '@material-ui/core/TableHead';
|
||||||
|
import TableRow from '@material-ui/core/TableRow';
|
||||||
|
import TableBody from '@material-ui/core/TableBody';
|
||||||
|
|
||||||
|
import BOT_LIST from '../../../gql/bot_list.graphql';
|
||||||
|
import BOT_KILL from '../../../gql/bot_kill.graphql';
|
||||||
|
|
||||||
|
import { useQueryStatusReducer, useStatusReducer, useSorter } from '../../../hooks';
|
||||||
|
|
||||||
|
import BotControls from '../../../components/BotControls';
|
||||||
|
import Table from '../../../components/Table';
|
||||||
|
import TableCell from '../../../components/TableCell';
|
||||||
|
|
||||||
|
const RunningBots = () => {
|
||||||
|
const [sorter, sortBy] = useSorter('started', false);
|
||||||
|
const [botList, setBotList] = useState([]);
|
||||||
|
const [, setStatus] = useStatusReducer();
|
||||||
|
|
||||||
|
const { data: botListResponse, refetch } = useQueryStatusReducer(useQuery(BOT_LIST));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (botListResponse) {
|
||||||
|
const { error, bots = [] } = JSON.parse(botListResponse.bot_list.json);
|
||||||
|
if (error) {
|
||||||
|
setStatus({ error });
|
||||||
|
}
|
||||||
|
setBotList(bots);
|
||||||
|
}
|
||||||
|
}, [botListResponse]);
|
||||||
|
|
||||||
|
const [killBot] = useMutation(BOT_KILL);
|
||||||
|
|
||||||
|
const onKillBot = async (botId) => {
|
||||||
|
const botKillResponse = await killBot({ variables: { botId } });
|
||||||
|
if (botKillResponse && botKillResponse.data) {
|
||||||
|
const { error } = JSON.parse(botKillResponse.data.bot_kill.json);
|
||||||
|
if (error) {
|
||||||
|
setStatus({ error });
|
||||||
|
} else {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell onClick={sortBy('id')}>Identifier</TableCell>
|
||||||
|
<TableCell onClick={sortBy('botId')} size='small'>Bot Id</TableCell>
|
||||||
|
<TableCell onClick={sortBy('started')}>Started</TableCell>
|
||||||
|
<TableCell onClick={sortBy('stopped')}>Stopped</TableCell>
|
||||||
|
<TableCell onClick={sortBy('parties')} size='small'>Parties</TableCell>
|
||||||
|
<TableCell size='icon' />
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{botList.sort(sorter).map(({ id, botId, started, stopped, parties }) => {
|
||||||
|
return (
|
||||||
|
<TableRow key={botId} size='small'>
|
||||||
|
<TableCell monospace>{id}</TableCell>
|
||||||
|
<TableCell monospace>{botId}</TableCell>
|
||||||
|
<TableCell>{moment.utc(started).fromNow()}</TableCell>
|
||||||
|
<TableCell monospace>{String(stopped)}</TableCell>
|
||||||
|
<TableCell monospace>{parties && parties.map(partyId => <div key={partyId}>{partyId}</div>)}</TableCell>
|
||||||
|
<TableCell monospace>
|
||||||
|
<BotControls onStop={() => onKillBot(botId)} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RunningBots;
|
@ -68,8 +68,8 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
const IPFSStatus = () => {
|
const IPFSStatus = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
|
const { data: ipfsResponse } = useQueryStatusReducer(useQuery(IPFS_STATUS));
|
||||||
const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
const { data: wnsResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } }
|
variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ const RECORD_TYPE = 'wrn:service';
|
|||||||
const SERVICE_TYPE = 'ipfs';
|
const SERVICE_TYPE = 'ipfs';
|
||||||
|
|
||||||
const IPFSStatus = () => {
|
const IPFSStatus = () => {
|
||||||
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
|
const { data: ipfsResponse } = useQueryStatusReducer(useQuery(IPFS_STATUS));
|
||||||
const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
const { data: wnsResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } }
|
variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import AppLink from '../../../components/AppLink';
|
|||||||
const KubeRecords = () => {
|
const KubeRecords = () => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const [sorter, sortBy] = useSorter('names[0]');
|
const [sorter, sortBy] = useSorter('names[0]');
|
||||||
const appResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
const { data: appResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
pollInterval: config.api.intervalQuery,
|
pollInterval: config.api.intervalQuery,
|
||||||
variables: { attributes: { type: 'wrn:kube' } }
|
variables: { attributes: { type: 'wrn:kube' } }
|
||||||
}));
|
}));
|
||||||
|
@ -74,7 +74,7 @@ const RegistryLookup = ({ scope }) => {
|
|||||||
const [result, setResult] = useState({});
|
const [result, setResult] = useState({});
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
|
||||||
const data = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
const { data } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
pollInterval: config.api.intervalQuery
|
pollInterval: config.api.intervalQuery
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ export const RecordType = ({ type = types[0].key, onChange }) => {
|
|||||||
const RegistryRecords = ({ type }) => {
|
const RegistryRecords = ({ type }) => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const [sorter, sortBy] = useSorter('createTime', false);
|
const [sorter, sortBy] = useSorter('createTime', false);
|
||||||
const data = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
const { data } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
pollInterval: config.api.intervalQuery,
|
pollInterval: config.api.intervalQuery,
|
||||||
variables: { attributes: { type } }
|
variables: { attributes: { type } }
|
||||||
}));
|
}));
|
||||||
|
@ -13,7 +13,7 @@ import Json from '../../../components/Json';
|
|||||||
|
|
||||||
const RegistryStatus = () => {
|
const RegistryStatus = () => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const data = useQueryStatusReducer(useQuery(WNS_STATUS, { pollInterval: config.api.intervalQuery }));
|
const { data } = useQueryStatusReducer(useQuery(WNS_STATUS, { pollInterval: config.api.intervalQuery }));
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
|
|||||||
|
|
||||||
const SignalChannels = () => {
|
const SignalChannels = () => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const data = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { fetchPolicy: 'no-cache', pollInterval: config.api.pollInterval, context: { api: 'signal' } }));
|
const { data } = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { fetchPolicy: 'no-cache', pollInterval: config.api.pollInterval, context: { api: 'signal' } }));
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ function Row (props) {
|
|||||||
|
|
||||||
function SignalServers () {
|
function SignalServers () {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const response = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { fetchPolicy: 'no-cache', pollInterval: config.api.pollInterval, context: { api: 'signal' } }));
|
const { data: response } = useQueryStatusReducer(useQuery(SIGNAL_STATUS, { fetchPolicy: 'no-cache', pollInterval: config.api.pollInterval, context: { api: 'signal' } }));
|
||||||
|
|
||||||
const data = useDataGraph(response);
|
const data = useDataGraph(response);
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
|
|||||||
|
|
||||||
const Info = () => {
|
const Info = () => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const systemResponse = useQueryStatusReducer(useQuery(SYSTEM_STATUS, { pollInterval: config.api.intervalQuery }));
|
const { data: systemResponse } = useQueryStatusReducer(useQuery(SYSTEM_STATUS, { pollInterval: config.api.intervalQuery }));
|
||||||
if (!systemResponse) {
|
if (!systemResponse) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ const format = (value, unit, symbol = '') => Math.floor(value / unit).toLocaleSt
|
|||||||
const SignalServers = () => {
|
const SignalServers = () => {
|
||||||
const { config } = useContext(ConsoleContext);
|
const { config } = useContext(ConsoleContext);
|
||||||
const [sorter] = useSorter('name');
|
const [sorter] = useSorter('name');
|
||||||
const serviceResponse = useQueryStatusReducer(useQuery(SERVICE_STATUS, { pollInterval: config.api.intervalQuery }));
|
const { data: serviceResponse } = useQueryStatusReducer(useQuery(SERVICE_STATUS, { pollInterval: config.api.intervalQuery }));
|
||||||
if (!serviceResponse) {
|
if (!serviceResponse) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
10
packages/console-app/src/gql/bot_kill.graphql
Normal file
10
packages/console-app/src/gql/bot_kill.graphql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2020 DXOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
mutation ($botId: String!) {
|
||||||
|
bot_kill(botId: $botId) {
|
||||||
|
timestamp
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
10
packages/console-app/src/gql/bot_list.graphql
Normal file
10
packages/console-app/src/gql/bot_list.graphql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2020 DXOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
query {
|
||||||
|
bot_list {
|
||||||
|
timestamp
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ export const useStatusReducer = () => {
|
|||||||
/**
|
/**
|
||||||
* Handle Apollo queries.
|
* Handle Apollo queries.
|
||||||
*/
|
*/
|
||||||
export const useQueryStatusReducer = ({ loading, error, data }) => {
|
export const useQueryStatusReducer = ({ loading, error, data, refetch }) => {
|
||||||
const [, setStatus] = useStatusReducer();
|
const [, setStatus] = useStatusReducer();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -37,7 +37,7 @@ export const useQueryStatusReducer = ({ loading, error, data }) => {
|
|||||||
}
|
}
|
||||||
}, [loading, error]);
|
}, [loading, error]);
|
||||||
|
|
||||||
return data;
|
return { data, refetch };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const statusReducer = (state, action) => {
|
export const statusReducer = (state, action) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"name": "@dxos/console-app",
|
"name": "@dxos/console-app",
|
||||||
"buildDate": "2020-11-19T22:11:06.119Z",
|
"buildDate": "2020-12-02T09:05:39.888Z",
|
||||||
"version": "1.2.1-alpha.1"
|
"version": "1.2.2-alpha.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"source-map-support": "^0.5.12",
|
"source-map-support": "^0.5.12",
|
||||||
"systeminformation": "^4.26.5",
|
"systeminformation": "^4.26.5",
|
||||||
|
"tree-kill": "^1.2.2",
|
||||||
"yargs": "^15.3.1"
|
"yargs": "^15.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -21,8 +21,14 @@ type Query {
|
|||||||
signal_status: JSONResult!
|
signal_status: JSONResult!
|
||||||
system_status: JSONResult!
|
system_status: JSONResult!
|
||||||
wns_status: JSONResult!
|
wns_status: JSONResult!
|
||||||
|
bot_list: JSONResult!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
bot_kill(botId: String!): JSONResult!
|
||||||
}
|
}
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: Query
|
query: Query
|
||||||
|
mutation: Mutation
|
||||||
}
|
}
|
||||||
|
104
packages/console-server/src/resolvers/bots.js
Normal file
104
packages/console-server/src/resolvers/bots.js
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DXOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import debug from 'debug';
|
||||||
|
import fs from 'fs';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
import path from 'path';
|
||||||
|
import os from 'os';
|
||||||
|
import kill from 'tree-kill';
|
||||||
|
|
||||||
|
const DEFAULT_BOT_FACTORY_CWD = '.wire/bots';
|
||||||
|
const SERVICE_CONFIG_FILENAME = 'service.yml';
|
||||||
|
|
||||||
|
const log = debug('dxos:console:server:resolvers');
|
||||||
|
|
||||||
|
const getBotFactoryTopic = (botFactoryCwd) => {
|
||||||
|
// TODO(egorgripasov): Get topic from config or registry.
|
||||||
|
const serviceFilePath = path.join(os.homedir(), botFactoryCwd || DEFAULT_BOT_FACTORY_CWD, SERVICE_CONFIG_FILENAME);
|
||||||
|
if (fs.existsSync(serviceFilePath)) {
|
||||||
|
const { topic } = yaml.safeLoad(fs.readFileSync(serviceFilePath));
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const topic = getBotFactoryTopic();
|
||||||
|
|
||||||
|
const executeCommand = async (command, args, timeout = 10000) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const child = spawn(command, args, { encoding: 'utf8' });
|
||||||
|
|
||||||
|
const stdout = [];
|
||||||
|
const stderr = [];
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
try {
|
||||||
|
kill(child.pid, 'SIGKILL');
|
||||||
|
} catch (err) {
|
||||||
|
log(`Can not kill ${command} process: ${err}`);
|
||||||
|
}
|
||||||
|
stderr.push('Timeout.');
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
child.stdout.on('data', (data) => stdout.push(data));
|
||||||
|
|
||||||
|
child.stderr.on('data', (data) => stderr.push(data));
|
||||||
|
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve({
|
||||||
|
code: code === null ? 1 : code,
|
||||||
|
stdout: stdout.join('').trim(),
|
||||||
|
stderr: stderr.join('').trim()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRunningBots = async () => {
|
||||||
|
const command = 'wire';
|
||||||
|
const args = ['bot', 'factory', 'status', '--topic', topic];
|
||||||
|
|
||||||
|
const { code, stdout, stderr } = await executeCommand(command, args);
|
||||||
|
return {
|
||||||
|
success: !code,
|
||||||
|
bots: code ? [] : JSON.parse(stdout).bots || [],
|
||||||
|
error: (stderr || code) ? stderr || stdout : undefined
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendBotCommand = async (botId, botCommand) => {
|
||||||
|
const command = 'wire';
|
||||||
|
const args = ['bot', botCommand, '--topic', topic, '--bot-id', botId];
|
||||||
|
|
||||||
|
const { code, stdout, stderr } = await executeCommand(command, args);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: !code,
|
||||||
|
botId: code ? undefined : botId,
|
||||||
|
error: (stderr || code) ? stderr || stdout : undefined
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const botsResolvers = {
|
||||||
|
Query: {
|
||||||
|
bot_list: async () => {
|
||||||
|
const result = await getRunningBots();
|
||||||
|
return {
|
||||||
|
timestamp: new Date().toUTCString(),
|
||||||
|
json: JSON.stringify(result)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Mutation: {
|
||||||
|
bot_kill: async (_, { botId }) => {
|
||||||
|
const result = await sendBotCommand(botId, 'kill');
|
||||||
|
return {
|
||||||
|
timestamp: new Date().toUTCString(),
|
||||||
|
json: JSON.stringify(result)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -8,6 +8,7 @@ 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';
|
import { logResolvers } from './log';
|
||||||
|
import { botsResolvers } from './bots';
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const log = debug('dxos:console:server:resolvers');
|
const log = debug('dxos:console:server:resolvers');
|
||||||
@ -21,4 +22,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
|
||||||
|
|
||||||
}, ipfsResolvers, systemResolvers, logResolvers);
|
}, ipfsResolvers, systemResolvers, logResolvers, botsResolvers);
|
||||||
|
Loading…
Reference in New Issue
Block a user