Merge branch 'main' into release

This commit is contained in:
Thomas E Lackey 2020-12-02 16:13:26 -06:00
commit f748f0236c
33 changed files with 2206 additions and 109 deletions

View File

@ -36,11 +36,12 @@ jobs:
yarn lerna publish from-package --force-publish -y
# Publish to WNS
yarn wire profile init --name $WIRE_PROFILE --template-url https://apollo1.kube.moon.dxos.network/dxos/ipfs/gateway/QmcVUWB3a5JAhc8nJD1UYoWpkPXiqpuXWCBzemyBvzUCXD
yarn wire profile init --name $WIRE_PROFILE --template-url "$WIRE_PROFILE_URL"
export PKG_CHANNEL="@beta"
scripts/deploy_apps_to_wns.sh
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
WIRE_WNS_USER_KEY: ${{secrets.wns_user_key}}
WIRE_WNS_BOND_ID: ${{secrets.wns_bond_id}}
WIRE_PROFILE_URL: ${{secrets.wire_profile_url}}
WIRE_PROFILE: ci

View File

@ -31,11 +31,12 @@ jobs:
yarn lerna publish -y prerelease --dist-tag="alpha" --force-publish
# Publish to WNS
yarn wire profile init --name $WIRE_PROFILE --template-url https://apollo1.kube.moon.dxos.network/dxos/ipfs/gateway/QmcVUWB3a5JAhc8nJD1UYoWpkPXiqpuXWCBzemyBvzUCXD
yarn wire profile init --name $WIRE_PROFILE --template-url "$WIRE_PROFILE_URL"
export PKG_CHANNEL="@alpha"
scripts/deploy_apps_to_wns.sh
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
WIRE_WNS_USER_KEY: ${{secrets.wns_user_key}}
WIRE_WNS_BOND_ID: ${{secrets.wns_bond_id}}
WIRE_PROFILE_URL: ${{secrets.wire_profile_url}}
WIRE_PROFILE: ci

View File

@ -37,10 +37,11 @@ jobs:
yarn lerna publish from-package --force-publish -y
# Publish to WNS
yarn wire profile init --name $WIRE_PROFILE --template-url https://apollo1.kube.moon.dxos.network/dxos/ipfs/gateway/QmcVUWB3a5JAhc8nJD1UYoWpkPXiqpuXWCBzemyBvzUCXD
yarn wire profile init --name $WIRE_PROFILE --template-url "$WIRE_PROFILE_URL"
scripts/deploy_apps_to_wns.sh
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
WIRE_WNS_USER_KEY: ${{secrets.wns_user_key}}
WIRE_WNS_BOND_ID: ${{secrets.wns_bond_id}}
WIRE_PROFILE_URL: ${{secrets.wire_profile_url}}
WIRE_PROFILE: ci

View File

@ -1,5 +1,5 @@
{
"version": "1.2.2",
"version": "1.2.3-alpha.1",
"useWorkspaces": true,
"npmClient": "yarn"
}

View File

@ -1,6 +1,6 @@
{
"name": "@dxos/console",
"version": "1.2.2-alpha.0",
"version": "1.2.3-alpha.0",
"description": "Console",
"main": "index.js",
"private": true,
@ -30,9 +30,11 @@
"lerna": "^3.19.0"
},
"devDependencies": {
"@dxos/cli": "^2.0.8",
"@dxos/cli-app": "^2.0.8",
"@dxos/cli-wns": "^2.0.8",
"@dxos/cli": "2.0.20-alpha.0",
"@dxos/cli-app": "2.0.20-alpha.0",
"@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",
"eslint": "^6.7.2",
"eslint-config-semistandard": "^15.0.0",

View File

@ -11,7 +11,7 @@ app:
publicUrl: '/console'
api:
server: 'https://alpha.kube.moon.dxos.network'
server: 'https://apollo1.kube.moon.dxos.network'
path: '/api'
intervalLog: 5000
pollInterval: 10000
@ -22,19 +22,19 @@ system:
services:
app:
prefix: '/app'
server: 'https://alpha.kube.moon.dxos.network'
server: 'https://apollo1.kube.moon.dxos.network'
wns:
server: 'https://alpha.kube.moon.dxos.network/dxos/wns/api'
webui: 'https://alpha.kube.moon.dxos.network/dxos/wns/console'
server: 'https://apollo1.kube.moon.dxos.network/dxos/wns/api'
webui: 'https://apollo1.kube.moon.dxos.network/dxos/wns/console'
signal:
server: 'wss://alpha.kube.moon.dxos.network/dxos/signal'
api: 'https://alpha.kube.moon.dxos.network/dxos/signal/api'
server: 'wss://apollo1.kube.moon.dxos.network/dxos/signal'
api: 'https://apollo1.kube.moon.dxos.network/dxos/signal/api'
ipfs:
server: 'https://alpha.kube.moon.dxos.network/dxos/ipfs/api'
gateway: 'https://alpha.kube.moon.dxos.network/dxos/ipfs/gateway'
server: 'https://apollo1.kube.moon.dxos.network/dxos/ipfs/api'
gateway: 'https://apollo1.kube.moon.dxos.network/dxos/ipfs/gateway'
wellknown:
endpoint: 'https://alpha.kube.moon.dxos.network/.well-known/dxos'
endpoint: 'https://apollo1.kube.moon.dxos.network/.well-known/dxos'

View File

@ -1,6 +1,6 @@
{
"name": "@dxos/console-app",
"version": "1.2.2",
"version": "1.2.3-alpha.1",
"description": "Kubenet Console Client",
"repository": "https://github.com/dxos/console",
"main": "dist/es/index.js",

View 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;

View File

@ -26,7 +26,7 @@ const getLogBuffer = (name) => {
const LogPoller = ({ service }) => {
const { config } = useContext(ConsoleContext);
const logBuffer = getLogBuffer(service);
const data = useQueryStatusReducer(useQuery(LOGS, {
const { data } = useQueryStatusReducer(useQuery(LOGS, {
pollInterval: config.api.intervalLog,
variables: { service, incremental: logBuffer.length !== 0 }
}));

View File

@ -30,8 +30,8 @@ const useStyles = makeStyles(theme => ({
const VersionCheck = () => {
const classes = useStyles();
const [{ current, latest }, setUpgrade] = useState({});
const statusResponse = useQueryStatusReducer(useQuery(SYSTEM_STATUS));
const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
const { data: statusResponse } = useQueryStatusReducer(useQuery(SYSTEM_STATUS));
const { data: wnsResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
pollInterval: CHECK_INTERVAL,
variables: { attributes: { type: 'wrn:resource' } }
}));

View File

@ -24,13 +24,13 @@ import AppLink from '../../../components/AppLink';
const AppRecords = () => {
const { config } = useContext(ConsoleContext);
const [sorter, sortBy] = useSorter('createTime', false);
const appResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
const { data: appResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
pollInterval: config.api.intervalQuery,
variables: { attributes: { type: 'wrn:app' } }
}));
// TODO(telackey): Does this also need an interval?
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
const { data: ipfsResponse } = useQueryStatusReducer(useQuery(IPFS_STATUS));
if (!appResponse || !ipfsResponse) {
return null;
}

View File

@ -20,7 +20,7 @@ import moment from 'moment';
const BotRecords = () => {
const { config } = useContext(ConsoleContext);
const [sorter, sortBy] = useSorter('createTime', false);
const data = useQueryStatusReducer(useQuery(WNS_RECORDS, {
const { data } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
pollInterval: config.api.intervalQuery,
variables: { attributes: { type: 'wrn:bot' } }
}));

View File

@ -12,9 +12,11 @@ import Toolbar from '../../../components/Toolbar';
import BotRecords from './BotRecords';
import LogPoller from '../../../components/LogPoller';
import RunningBots from './RunningBots';
const TAB_RECORDS = 'records';
const TAB_LOG = 'log';
const TAB_DATA = 'running bots';
const useStyles = makeStyles(theme => ({
root: {}
@ -32,6 +34,7 @@ const Bots = () => {
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
<Tab value={TAB_RECORDS} label='Records' />
<Tab value={TAB_LOG} label='Log' />
<Tab value={TAB_DATA} label='Running Bots' />
</Tabs>
</Toolbar>
}
@ -43,6 +46,9 @@ const Bots = () => {
{tab === TAB_LOG && (
<LogPoller service='bot-factory' />
)}
{tab === TAB_DATA && (
<RunningBots />
)}
</Panel>
);
};

View File

@ -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;

View File

@ -68,8 +68,8 @@ const useStyles = makeStyles((theme) => ({
const IPFSStatus = () => {
const classes = useStyles();
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
const { data: ipfsResponse } = useQueryStatusReducer(useQuery(IPFS_STATUS));
const { data: wnsResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } }
}));

View File

@ -17,8 +17,8 @@ const RECORD_TYPE = 'wrn:service';
const SERVICE_TYPE = 'ipfs';
const IPFSStatus = () => {
const ipfsResponse = useQueryStatusReducer(useQuery(IPFS_STATUS));
const wnsResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
const { data: ipfsResponse } = useQueryStatusReducer(useQuery(IPFS_STATUS));
const { data: wnsResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
variables: { attributes: { type: RECORD_TYPE, service: SERVICE_TYPE } }
}));

View File

@ -21,7 +21,7 @@ import AppLink from '../../../components/AppLink';
const KubeRecords = () => {
const { config } = useContext(ConsoleContext);
const [sorter, sortBy] = useSorter('names[0]');
const appResponse = useQueryStatusReducer(useQuery(WNS_RECORDS, {
const { data: appResponse } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
pollInterval: config.api.intervalQuery,
variables: { attributes: { type: 'wrn:kube' } }
}));

View File

@ -74,7 +74,7 @@ const RegistryLookup = ({ scope }) => {
const [result, setResult] = useState({});
const [inputValue, setInputValue] = useState('');
const data = useQueryStatusReducer(useQuery(WNS_RECORDS, {
const { data } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
pollInterval: config.api.intervalQuery
}));

View File

@ -67,7 +67,7 @@ export const RecordType = ({ type = types[0].key, onChange }) => {
const RegistryRecords = ({ type }) => {
const { config } = useContext(ConsoleContext);
const [sorter, sortBy] = useSorter('createTime', false);
const data = useQueryStatusReducer(useQuery(WNS_RECORDS, {
const { data } = useQueryStatusReducer(useQuery(WNS_RECORDS, {
pollInterval: config.api.intervalQuery,
variables: { attributes: { type } }
}));

View File

@ -13,7 +13,7 @@ import Json from '../../../components/Json';
const RegistryStatus = () => {
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) {
return null;
}

View File

@ -18,7 +18,7 @@ import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
const SignalChannels = () => {
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) {
return null;
}

View File

@ -115,7 +115,7 @@ function Row (props) {
function SignalServers () {
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);

View File

@ -13,7 +13,7 @@ import { ConsoleContext, useQueryStatusReducer } from '../../../hooks';
const Info = () => {
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) {
return null;
}

View File

@ -20,7 +20,7 @@ const format = (value, unit, symbol = '') => Math.floor(value / unit).toLocaleSt
const SignalServers = () => {
const { config } = useContext(ConsoleContext);
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) {
return null;
}

View File

@ -0,0 +1,10 @@
#
# Copyright 2020 DXOS.org
#
mutation ($botId: String!) {
bot_kill(botId: $botId) {
timestamp
json
}
}

View File

@ -0,0 +1,10 @@
#
# Copyright 2020 DXOS.org
#
query {
bot_list {
timestamp
json
}
}

View File

@ -24,7 +24,7 @@ export const useStatusReducer = () => {
/**
* Handle Apollo queries.
*/
export const useQueryStatusReducer = ({ loading, error, data }) => {
export const useQueryStatusReducer = ({ loading, error, data, refetch }) => {
const [, setStatus] = useStatusReducer();
useEffect(() => {
@ -37,7 +37,7 @@ export const useQueryStatusReducer = ({ loading, error, data }) => {
}
}, [loading, error]);
return data;
return { data, refetch };
};
export const statusReducer = (state, action) => {

View File

@ -1,7 +1,7 @@
{
"build": {
"name": "@dxos/console-app",
"buildDate": "2020-11-19T22:22:10.567Z",
"version": "1.2.2-alpha.3"
"buildDate": "2020-12-02T18:30:08.007Z",
"version": "1.2.3-alpha.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@dxos/console-server",
"version": "1.2.2",
"version": "1.2.3-alpha.1",
"description": "Kubenet Console Server",
"main": "dist/es/index.js",
"bin": {
@ -31,7 +31,7 @@
"dependencies": {
"@babel/polyfill": "^7.8.7",
"@babel/runtime": "^7.8.7",
"@dxos/console-app": "^1.2.2",
"@dxos/console-app": "^1.2.3-alpha.1",
"@wirelineio/wns-schema": "^0.1.1",
"apollo-boost": "^0.4.9",
"apollo-server-express": "^2.13.1",
@ -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": {

View File

@ -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
}

View 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)
};
}
}
};

View File

@ -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);

1962
yarn.lock

File diff suppressed because it is too large Load Diff