diff --git a/packages/console-client/package.json b/packages/console-client/package.json index e0bb6ef..7e10ad3 100644 --- a/packages/console-client/package.json +++ b/packages/console-client/package.json @@ -34,6 +34,7 @@ "@dxos/react-ux": "^1.0.0-beta.20", "@material-ui/core": "^4.10.0", "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "^4.0.0-alpha.54", "@wirelineio/registry-client": "^0.4.8", "apollo-cache-inmemory": "^1.6.6", "apollo-client": "^2.6.10", @@ -45,6 +46,7 @@ "lodash.isobject": "^3.0.2", "lodash.omit": "^4.5.0", "lodash.transform": "^4.6.0", + "moment": "^2.26.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "^5.2.0", diff --git a/packages/console-client/src/components/Config.js b/packages/console-client/src/components/Config.js deleted file mode 100644 index 755674d..0000000 --- a/packages/console-client/src/components/Config.js +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright 2020 DxOS -// - -import React, { useContext } from 'react'; - -import { JsonTreeView } from '@dxos/react-ux'; - -import { ConsoleContext } from '../hooks'; - -const Config = () => { - const { config } = useContext(ConsoleContext); - - return ( - - ); -}; - -export default Config; diff --git a/packages/console-client/src/components/ControlButtons.js b/packages/console-client/src/components/ControlButtons.js new file mode 100644 index 0000000..081617e --- /dev/null +++ b/packages/console-client/src/components/ControlButtons.js @@ -0,0 +1,35 @@ +// +// Copyright 2020 DxOS +// + +import React from 'react'; +import OpenIcon from '@material-ui/icons/OpenInBrowser'; +import StartIcon from '@material-ui/icons/PlayCircleOutline'; +import StopIcon from '@material-ui/icons/HighlightOff'; +import IconButton from '@material-ui/core/IconButton'; + +const ControlButtons = ({ onStart, onStop, onOpen }) => { + return ( +
+ {onStart && ( + + + + )} + + {onStop && ( + + + + )} + + {onOpen && ( + + + + )} +
+ ); +}; + +export default ControlButtons; diff --git a/packages/console-client/src/components/Error.js b/packages/console-client/src/components/Error.js new file mode 100644 index 0000000..72159d2 --- /dev/null +++ b/packages/console-client/src/components/Error.js @@ -0,0 +1,42 @@ +// +// Copyright 2020 DxOS +// + +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import Alert from '@material-ui/lab/Alert'; +import AlertTitle from '@material-ui/lab/AlertTitle'; +import Snackbar from '@material-ui/core/Snackbar'; + +const useStyles = makeStyles(() => ({ + root: { + marginBottom: 60 + } +})); + +const Error = ({ message, ...rest }) => { + const classes = useStyles(); + if (!message) { + return null; + } + + const messages = Array.isArray(message) ? message : [message]; + + return ( + + + Error + {messages.map((message, i) => ( +
{message}
+ ))} +
+
+ ); +}; + +export default Error; diff --git a/packages/console-client/src/components/Layout.js b/packages/console-client/src/components/Layout.js index e427bcc..25dd3b3 100644 --- a/packages/console-client/src/components/Layout.js +++ b/packages/console-client/src/components/Layout.js @@ -4,14 +4,14 @@ import React, { useContext } from 'react'; import { makeStyles } from '@material-ui/core'; -import Paper from '@material-ui/core/Paper'; import { FullScreen } from '@dxos/gem-core'; -import StatusBar from '../containers/StatusBar'; +import { ConsoleContext } from '../hooks'; + import AppBar from './AppBar'; import Sidebar from './Sidebar'; -import { ConsoleContext } from '../hooks'; +import StatusBar from './StatusBar'; const useStyles = makeStyles((theme) => ({ root: { @@ -56,9 +56,9 @@ const Layout = ({ children }) => {
- +
{children} - +
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"