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:
Egor Gripasov
2020-12-02 05:32:49 -05:00
committed by GitHub
parent 801d54f0c5
commit 3c731b3b2b
28 changed files with 2195 additions and 101 deletions
+1
View File
@@ -52,6 +52,7 @@
"react-dom": "^16.13.1",
"source-map-support": "^0.5.12",
"systeminformation": "^4.26.5",
"tree-kill": "^1.2.2",
"yargs": "^15.3.1"
},
"devDependencies": {
@@ -21,8 +21,14 @@ type Query {
signal_status: JSONResult!
system_status: JSONResult!
wns_status: JSONResult!
bot_list: JSONResult!
}
type Mutation {
bot_kill(botId: String!): JSONResult!
}
schema {
query: Query
mutation: Mutation
}
@@ -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 { systemResolvers } from './system';
import { logResolvers } from './log';
import { botsResolvers } from './bots';
// eslint-disable-next-line
const log = debug('dxos:console:server:resolvers');
@@ -21,4 +22,4 @@ export const resolvers = defaultsDeep({
// TODO(burdon): Auth.
// https://www.apollographql.com/docs/apollo-server/data/errors/#codes
}, ipfsResolvers, systemResolvers, logResolvers);
}, ipfsResolvers, systemResolvers, logResolvers, botsResolvers);