diff --git a/packages/console-client/src/components/Log.js b/packages/console-client/src/components/Log.js
new file mode 100644
index 0000000..1770a87
--- /dev/null
+++ b/packages/console-client/src/components/Log.js
@@ -0,0 +1,120 @@
+//
+// Copyright 2020 DxOS
+//
+
+import clsx from 'clsx';
+import moment from 'moment';
+import React, { Fragment } from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ display: 'flex',
+ flex: 1,
+ flexDirection: 'column'
+ },
+
+ container: {
+ display: 'flex',
+ flex: 1,
+ overflowX: 'scroll',
+ overflowY: 'scroll'
+ },
+
+ log: {
+ padding: theme.spacing(1),
+ fontSize: 16,
+ // fontFamily: 'monospace',
+ whiteSpace: 'nowrap'
+ },
+
+ level: {
+ display: 'inline-block',
+ width: 48,
+ marginRight: 8,
+ color: theme.palette.grey[500]
+ },
+ level_warn: {
+ color: theme.palette.warning.main
+ },
+ level_error: {
+ color: theme.palette.error.main
+ },
+
+ ts: {
+ marginRight: 8,
+ color: theme.palette.primary[500]
+ }
+}));
+
+const Log = ({ log = [], onClear }) => {
+ const classes = useStyles();
+
+ const levels = {
+ 'I': { label: 'INFO', className: classes.level_info },
+ 'W': { label: 'WARN', className: classes.level_warn },
+ 'E': { label: 'ERROR', className: classes.level_error }
+ };
+
+ // TODO(burdon): Parse in backend and normalize numbers.
+ const Line = ({ message }) => {
+ // https://regex101.com/
+ const patterns = [
+ {
+ // 2020-03-30T18:02:43.189Z bot-factory
+ pattern: /()(.+Z)\s+(.+)/,
+ transform: ([datetime]) => moment(datetime)
+ },
+ {
+ // I[2020-03-30|15:29:05.436] Executed block module=state height=11533 validTxs=0 invalidTxs=0
+ pattern: /(.)\[(.+)\|(.+)]\s+(.+)/,
+ transform: ([date, time]) => moment(`${date} ${time}`)
+ },
+ {
+ // [cors] 2020/03/30 15:28:53 Handler: Actual request
+ pattern: /\[(\w+)] (\S+) (\S+)\s+(.+)/,
+ transform: ([date, time]) => moment(`${date.replace(/\//g, '-')} ${time}`)
+ }
+ ];
+
+ patterns.some(({ pattern, transform }) => {
+ const match = message.match(pattern);
+ if (match) {
+ const [, level = 'I', ...rest] = match;
+ const datetime = transform(rest).format('YYYY-MM-DD HH:mm:ss');
+ const text = match[match.length - 1];
+
+ const { label, className } = levels[level] || levels['I'];
+ const pkg = levels[level] ? '' : `[${level}]: `;
+
+ message = (
+
+ {datetime}
+ {label || level}
+ {pkg}{text}
+
+ );
+
+ return true;
+ }
+
+ return false;
+ });
+
+ return (
+
{message}
+ );
+ };
+
+ return (
+
+
+
+ {log.reverse().map((line, i) => )}
+
+
+
+ );
+};
+
+export default Log;
diff --git a/packages/console-client/src/components/Panel.js b/packages/console-client/src/components/Panel.js
new file mode 100644
index 0000000..d651b93
--- /dev/null
+++ b/packages/console-client/src/components/Panel.js
@@ -0,0 +1,38 @@
+//
+// Copyright 2020 DxOS.org
+//
+
+import React from 'react';
+import { makeStyles } from '@material-ui/core';
+import Paper from '@material-ui/core/Paper';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ display: 'flex',
+ flexDirection: 'column',
+ flex: 1,
+ overflow: 'hidden'
+ },
+
+ container: {
+ display: 'flex',
+ flexDirection: 'column',
+ flex: 1,
+ overflowY: 'scroll'
+ }
+}));
+
+const Panel = ({ toolbar, children }) => {
+ const classes = useStyles();
+
+ return (
+
+ {toolbar}
+
+ {children}
+
+
+ );
+};
+
+export default Panel;
diff --git a/packages/console-client/src/containers/StatusBar.js b/packages/console-client/src/components/StatusBar.js
similarity index 100%
rename from packages/console-client/src/containers/StatusBar.js
rename to packages/console-client/src/components/StatusBar.js
diff --git a/packages/console-client/src/components/TableCell.js b/packages/console-client/src/components/TableCell.js
new file mode 100644
index 0000000..0392c35
--- /dev/null
+++ b/packages/console-client/src/components/TableCell.js
@@ -0,0 +1,28 @@
+//
+// Copyright 2020 DxOS
+//
+
+import React from 'react';
+
+import MuiTableCell from '@material-ui/core/TableCell';
+
+// TODO(burdon): Size for header.
+// TODO(burdon): Standardize table.
+
+const TableCell = ({ children, monospace = false, title, ...rest }) => (
+
+ {children}
+
+);
+
+export default TableCell;
diff --git a/packages/console-client/src/components/Toolbar.js b/packages/console-client/src/components/Toolbar.js
new file mode 100644
index 0000000..c1920b3
--- /dev/null
+++ b/packages/console-client/src/components/Toolbar.js
@@ -0,0 +1,30 @@
+//
+// Copyright 2020 DxOS
+//
+
+import React from 'react';
+import { makeStyles } from '@material-ui/core';
+import MuiToolbar from '@material-ui/core/Toolbar';
+
+const useStyles = makeStyles(theme => ({
+ toolbar: {
+ display: 'flex',
+ justifyContent: 'space-between',
+
+ '& > button': {
+ margin: theme.spacing(0.5),
+ }
+ }
+}));
+
+const Toolbar = ({ children }) => {
+ const classes = useStyles();
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default Toolbar;
diff --git a/packages/console-client/src/containers/IPFS.js b/packages/console-client/src/containers/IPFS.js
deleted file mode 100644
index 1c91f3d..0000000
--- a/packages/console-client/src/containers/IPFS.js
+++ /dev/null
@@ -1,25 +0,0 @@
-//
-// Copyright 2020 DxOS
-//
-
-import React from 'react';
-import { useQuery } from '@apollo/react-hooks';
-
-import Json from '../components/Json';
-
-import { useQueryStatusReducer } from '../hooks';
-
-import IPFS_STATUS from '../../gql/ipfs_status.graphql';
-
-const IPFS = () => {
- const data = useQueryStatusReducer(useQuery(IPFS_STATUS));
- if (!data) {
- return null;
- }
-
- return (
-
- );
-};
-
-export default IPFS;
diff --git a/packages/console-client/src/containers/Main.js b/packages/console-client/src/containers/Main.js
index cd0cf8c..8313d42 100644
--- a/packages/console-client/src/containers/Main.js
+++ b/packages/console-client/src/containers/Main.js
@@ -10,19 +10,17 @@ import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import config from '../../config.yml';
-
import { createTheme } from '../theme';
import { clientFactory } from '../client';
import modules from '../modules';
-import Config from '../components/Config';
import Layout from '../components/Layout';
-
import ConsoleContextProvider from './ConsoleContextProvider';
-import IPFS from './IPFS';
-import Status from './Status';
-import WNS from './WNS';
+import Config from './panels/Config';
+import IPFS from './panels/IPFS';
+import Status from './panels/Status';
+import WNS from './panels/WNS';
debug.enable(config.system.debug);
diff --git a/packages/console-client/src/containers/WNS.js b/packages/console-client/src/containers/WNS.js
deleted file mode 100644
index 288c27d..0000000
--- a/packages/console-client/src/containers/WNS.js
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-// Copyright 2020 DxOS
-//
-
-import React, { useContext } from 'react';
-import { useQuery } from '@apollo/react-hooks';
-import { Mutation } from '@apollo/react-components';
-import { makeStyles } from '@material-ui/core';
-import Button from '@material-ui/core/Button';
-
-import Json from '../components/Json';
-
-import { ConsoleContext, useQueryStatusReducer } from '../hooks';
-
-import WNS_STATUS from '../../gql/wns_status.graphql';
-import WNS_ACTION from '../../gql/wns_action.graphql';
-
-const useStyles = makeStyles((theme) => ({
- root: {
- display: 'flex',
- flexDirection: 'column',
- flex: 1,
- overflowY: 'scroll'
- }
-}));
-
-const WNS = () => {
- const classes = useStyles();
- const { config } = useContext(ConsoleContext);
- const data = useQueryStatusReducer(useQuery(WNS_STATUS, { pollInterval: config.api.pollInterval }));
- if (!data) {
- return null;
- }
-
- return (
-
-
- {(action, { data }) => (
-
-
-
-
Result: {JSON.stringify(data)}
-
- )}
-
-
-
-
- );
-};
-
-export default WNS;
diff --git a/packages/console-client/src/containers/panels/Config.js b/packages/console-client/src/containers/panels/Config.js
new file mode 100644
index 0000000..254e37f
--- /dev/null
+++ b/packages/console-client/src/containers/panels/Config.js
@@ -0,0 +1,27 @@
+//
+// Copyright 2020 DxOS
+//
+
+import React, { useContext } from 'react';
+
+import { ConsoleContext } from '../../hooks';
+
+import Panel from '../../components/Panel';
+import Toolbar from '../../components/Toolbar';
+import Json from '../../components/Json';
+
+const Config = () => {
+ const { config } = useContext(ConsoleContext);
+
+ return (
+
+ }
+ >
+
+
+ );
+};
+
+export default Config;
diff --git a/packages/console-client/src/containers/panels/IPFS.js b/packages/console-client/src/containers/panels/IPFS.js
new file mode 100644
index 0000000..c6e3c07
--- /dev/null
+++ b/packages/console-client/src/containers/panels/IPFS.js
@@ -0,0 +1,33 @@
+//
+// Copyright 2020 DxOS
+//
+
+import React from 'react';
+import { useQuery } from '@apollo/react-hooks';
+
+import IPFS_STATUS from '../../../gql/ipfs_status.graphql';
+
+import { useQueryStatusReducer } from '../../hooks';
+
+import Json from '../../components/Json';
+import Panel from '../../components/Panel';
+import Toolbar from '../../components/Toolbar';
+
+const IPFS = () => {
+ const data = useQueryStatusReducer(useQuery(IPFS_STATUS));
+ if (!data) {
+ return null;
+ }
+
+ return (
+
+ }
+ >
+
+
+ );
+};
+
+export default IPFS;
diff --git a/packages/console-client/src/containers/Status.js b/packages/console-client/src/containers/panels/Status.js
similarity index 50%
rename from packages/console-client/src/containers/Status.js
rename to packages/console-client/src/containers/panels/Status.js
index 836477d..445588b 100644
--- a/packages/console-client/src/containers/Status.js
+++ b/packages/console-client/src/containers/panels/Status.js
@@ -5,11 +5,14 @@
import React, { useContext } from 'react';
import { useQuery } from '@apollo/react-hooks';
-import Json from '../components/Json';
+import Json from '../../components/Json';
-import { ConsoleContext, useQueryStatusReducer } from '../hooks';
+import SYSTEM_STATUS from '../../../gql/system_status.graphql';
-import SYSTEM_STATUS from '../../gql/system_status.graphql';
+import { ConsoleContext, useQueryStatusReducer } from '../../hooks';
+
+import Panel from '../../components/Panel';
+import Toolbar from '../../components/Toolbar';
const Status = () => {
const { config } = useContext(ConsoleContext);
@@ -19,7 +22,13 @@ const Status = () => {
}
return (
-
+
+ }
+ >
+
+
);
};
diff --git a/packages/console-client/src/containers/panels/WNS.js b/packages/console-client/src/containers/panels/WNS.js
new file mode 100644
index 0000000..f566847
--- /dev/null
+++ b/packages/console-client/src/containers/panels/WNS.js
@@ -0,0 +1,123 @@
+//
+// Copyright 2020 DxOS
+//
+
+import React, { useContext, useState } from 'react';
+import { useQuery } from '@apollo/react-hooks';
+import { Mutation } from '@apollo/react-components';
+import { makeStyles } from '@material-ui/core';
+import Button from '@material-ui/core/Button';
+import ButtonGroup from '@material-ui/core/ButtonGroup';
+import Tab from '@material-ui/core/Tab';
+import Tabs from '@material-ui/core/Tabs';
+import TabContext from '@material-ui/lab/TabContext';
+import TabPanel from '@material-ui/lab/TabPanel';
+
+import ControlButtons from '../../components/ControlButtons';
+import Json from '../../components/Json';
+import Log from '../../components/Log';
+import Panel from '../../components/Panel';
+import Toolbar from '../../components/Toolbar';
+
+import { ConsoleContext, useQueryStatusReducer } from '../../hooks';
+
+import WNS_STATUS from '../../../gql/wns_status.graphql';
+import WNS_ACTION from '../../../gql/wns_action.graphql';
+
+const types = [
+ { key: null, label: 'ALL' },
+ { key: 'wrn:xbox', label: 'XBox' },
+ { key: 'wrn:resource', label: 'Resource' },
+ { key: 'wrn:app', label: 'App' },
+ { key: 'wrn:bot', label: 'Bot' },
+ { key: 'wrn:type', label: 'Type' },
+];
+
+const TAB_RECORDS = 'records';
+const TAB_LOG = 'log';
+const TAB_EXPLORER = 'explorer';
+
+const useStyles = makeStyles(() => ({
+ expand: {
+ flex: 1
+ }
+}));
+
+const WNS = () => {
+ const classes = useStyles();
+ const { config } = useContext(ConsoleContext);
+ const [type, setType] = useState(types[0].key);
+ const [tab, setTab] = useState(TAB_RECORDS);
+ const data = useQueryStatusReducer(useQuery(WNS_STATUS, { pollInterval: config.api.pollInterval }));
+ if (!data) {
+ return null;
+ }
+
+ return (
+
+ setTab(value)}>
+
+
+
+
+
+ {tab === TAB_RECORDS && (
+
+ {types.map(t => (
+
+ ))}
+
+ )}
+
+
+
+
+ {(action, { data }) => (
+
+ {
+ action({ variables: { command: 'start' } });
+ }}
+ onStop={() => {
+ action({ variables: { command: 'stop' } });
+ }}
+ />
+
+ )}
+
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default WNS;
diff --git a/packages/console-client/version.json b/packages/console-client/version.json
index 6ee6f87..f174238 100644
--- a/packages/console-client/version.json
+++ b/packages/console-client/version.json
@@ -1,7 +1,7 @@
{
"build": {
"name": "@dxos/console-client",
- "buildDate": "2020-05-24T13:13:49.317Z",
+ "buildDate": "2020-05-25T02:29:08.942Z",
"version": "1.0.0-beta.0"
}
}
diff --git a/packages/console-server/src/resolvers.js b/packages/console-server/src/resolvers.js
index d07e869..60e5de9 100644
--- a/packages/console-server/src/resolvers.js
+++ b/packages/console-server/src/resolvers.js
@@ -21,8 +21,8 @@ export const createResolvers = config => ({
// WNS
//
- wns_action: async (_, __, { action }) => {
- log(`WNS action: ${action}`);
+ wns_action: async (_, { command }) => {
+ log(`WNS action: ${command}`);
return {
timestamp: timestamp(),
diff --git a/yarn.lock b/yarn.lock
index 6eab81e..928ecfe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2178,7 +2178,7 @@
dependencies:
"@babel/runtime" "^7.4.4"
-"@material-ui/lab@^4.0.0-alpha.42":
+"@material-ui/lab@^4.0.0-alpha.42", "@material-ui/lab@^4.0.0-alpha.54":
version "4.0.0-alpha.54"
resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.54.tgz#f359fac05667549353e5e21e631ae22cb2c22996"
integrity sha512-BK/z+8xGPQoMtG6gWKyagCdYO1/2DzkBchvvXs2bbTVh3sbi/QQLIqWV6UA1KtMVydYVt22NwV3xltgPkaPKLg==
@@ -11379,6 +11379,11 @@ modify-values@^1.0.0:
resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==
+moment@^2.26.0:
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
+ integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==
+
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"