diff --git a/README.md b/README.md index 0eb5768..5cedbab 100644 --- a/README.md +++ b/README.md @@ -6,23 +6,31 @@ Apollo GraphQL client and server using express. ### POC -- [ ] Trigger server-side wire commands (mutation or separate express path?) +- [ ] Complete WNS functionality + - [ ] Logging + - [ ] Webpack and dynamic config + - [ ] Routes for services + - [ ] Trigger server-side wire commands + - [ ] Test on device in production. + +- [ ] IPFS +- [ ] Signal +- [ ] Apps +- [ ] Bots +- [ ] Meta ### Next -- [ ] Config routes for services (test). -- [ ] Webpack config (remove dynamic config?) - - [ ] Client/server API abstraction (error handler, etc.) - [ ] Port dashboard API calls (resolve config first). - [ ] Port dashboard react modules with dummy resolvers. -- [ ] Fix JsonTree (yarn link). - [ ] https://github.com/standard/standardx (JSX) ### Done -- [c] Client resolvers: https://www.apollographql.com/docs/tutorial/local-state/ +- [x] Fix JsonTree (yarn link). +- [x] Client resolvers: https://www.apollographql.com/docs/tutorial/local-state/ - [x] Test backend IPFS client request. - [x] Hash Router. - [x] Layout (with Material UI). diff --git a/packages/console-client/babel.config.js b/packages/console-client/babel.config.js index ff13653..3ce227c 100644 --- a/packages/console-client/babel.config.js +++ b/packages/console-client/babel.config.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // module.exports = { diff --git a/packages/console-client/config.yml b/packages/console-client/config.yml index 11686e8..0542f06 100644 --- a/packages/console-client/config.yml +++ b/packages/console-client/config.yml @@ -22,6 +22,7 @@ system: services: app: + prefix: '/app' server: 'http://127.0.0.1:5999' # TODO(burdon): ??? wns: diff --git a/packages/console-client/gql/ipfs_status.graphql b/packages/console-client/gql/ipfs_status.graphql index 4dedb98..5621aca 100644 --- a/packages/console-client/gql/ipfs_status.graphql +++ b/packages/console-client/gql/ipfs_status.graphql @@ -1,5 +1,5 @@ # -# Copyright 2020 DxOS +# Copyright 2020 DxOS.org # { diff --git a/packages/console-client/gql/system_status.graphql b/packages/console-client/gql/system_status.graphql index 683f2ce..6f8c171 100644 --- a/packages/console-client/gql/system_status.graphql +++ b/packages/console-client/gql/system_status.graphql @@ -1,5 +1,5 @@ # -# Copyright 2020 DxOS +# Copyright 2020 DxOS.org # { diff --git a/packages/console-client/gql/wns_action.graphql b/packages/console-client/gql/wns_action.graphql index ee0014b..8c349b9 100644 --- a/packages/console-client/gql/wns_action.graphql +++ b/packages/console-client/gql/wns_action.graphql @@ -1,8 +1,8 @@ # -# Copyright 2020 DxOS +# Copyright 2020 DxOS.org # -mutation Action($command: String!) { +mutation ($command: String!) { wns_action(command: $command) { timestamp code diff --git a/packages/console-client/gql/wns_log.graphql b/packages/console-client/gql/wns_log.graphql new file mode 100644 index 0000000..ad3e2ea --- /dev/null +++ b/packages/console-client/gql/wns_log.graphql @@ -0,0 +1,9 @@ +# +# Copyright 2020 DxOS.org +# + +{ + wns_log @client { + log + } +} diff --git a/packages/console-client/gql/wns_records.graphql b/packages/console-client/gql/wns_records.graphql new file mode 100644 index 0000000..2d88d69 --- /dev/null +++ b/packages/console-client/gql/wns_records.graphql @@ -0,0 +1,9 @@ +# +# Copyright 2020 DxOS.org +# + +query ($type: String) { + wns_records (type: $type) @client { + json + } +} diff --git a/packages/console-client/gql/wns_status.graphql b/packages/console-client/gql/wns_status.graphql index 26492b5..098eaf9 100644 --- a/packages/console-client/gql/wns_status.graphql +++ b/packages/console-client/gql/wns_status.graphql @@ -1,5 +1,5 @@ # -# Copyright 2020 DxOS +# Copyright 2020 DxOS.org # { diff --git a/packages/console-client/package.json b/packages/console-client/package.json index 7e10ad3..01a741b 100644 --- a/packages/console-client/package.json +++ b/packages/console-client/package.json @@ -39,10 +39,12 @@ "apollo-cache-inmemory": "^1.6.6", "apollo-client": "^2.6.10", "apollo-link-http": "^1.5.17", + "build-url": "^2.0.0", "clsx": "^1.1.0", "debug": "^4.1.1", "graphql-tag": "^2.10.3", "lodash.defaultsdeep": "^4.6.1", + "lodash.get": "^4.4.2", "lodash.isobject": "^3.0.2", "lodash.omit": "^4.5.0", "lodash.transform": "^4.6.0", diff --git a/packages/console-client/src/client.js b/packages/console-client/src/client.js index d05f3d1..670ed00 100644 --- a/packages/console-client/src/client.js +++ b/packages/console-client/src/client.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import { ApolloClient } from 'apollo-client'; diff --git a/packages/console-client/src/components/AppBar.js b/packages/console-client/src/components/AppBar.js index b4ed0f3..88cf1b3 100644 --- a/packages/console-client/src/components/AppBar.js +++ b/packages/console-client/src/components/AppBar.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React from 'react'; @@ -45,7 +45,7 @@ const AppBar = ({ config }) => { return ( <> - +
diff --git a/packages/console-client/src/components/ControlButtons.js b/packages/console-client/src/components/ControlButtons.js index 081617e..9113515 100644 --- a/packages/console-client/src/components/ControlButtons.js +++ b/packages/console-client/src/components/ControlButtons.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React from 'react'; diff --git a/packages/console-client/src/components/Error.js b/packages/console-client/src/components/Error.js index 72159d2..93c07c7 100644 --- a/packages/console-client/src/components/Error.js +++ b/packages/console-client/src/components/Error.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React from 'react'; diff --git a/packages/console-client/src/components/ErrorBoundary.js b/packages/console-client/src/components/ErrorBoundary.js index 0ab89dd..98a6da6 100644 --- a/packages/console-client/src/components/ErrorBoundary.js +++ b/packages/console-client/src/components/ErrorBoundary.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React, { Component } from 'react'; diff --git a/packages/console-client/src/components/Json.js b/packages/console-client/src/components/Json.js index ff0fbcb..ce87470 100644 --- a/packages/console-client/src/components/Json.js +++ b/packages/console-client/src/components/Json.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import isObject from 'lodash.isobject'; diff --git a/packages/console-client/src/components/Layout.js b/packages/console-client/src/components/Layout.js index 25dd3b3..c8fd7c5 100644 --- a/packages/console-client/src/components/Layout.js +++ b/packages/console-client/src/components/Layout.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React, { useContext } from 'react'; @@ -36,7 +36,7 @@ const useStyles = makeStyles((theme) => ({ flexDirection: 'column', flexShrink: 0, width: 200, - borderRight: `1px solid ${theme.palette.primary.dark}` + borderRight: `1px solid ${theme.palette.divider}` }, footer: { display: 'flex', diff --git a/packages/console-client/src/components/Log.js b/packages/console-client/src/components/Log.js index 1770a87..4c41453 100644 --- a/packages/console-client/src/components/Log.js +++ b/packages/console-client/src/components/Log.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import clsx from 'clsx'; diff --git a/packages/console-client/src/components/PackageLink.js b/packages/console-client/src/components/PackageLink.js new file mode 100644 index 0000000..1bca09d --- /dev/null +++ b/packages/console-client/src/components/PackageLink.js @@ -0,0 +1,57 @@ +// +// Copyright 2020 DxOS.org.org +// + +import React, { Fragment } from 'react'; +import Link from '@material-ui/core/Link'; + +import { getServiceUrl } from '../util/config'; + +/** + * Render IPFS links in package. + * @param {Object} config + * @param {string} [type] + * @param {string} pkg + */ +const PackageLink = ({ config, type, pkg }) => { + + // TODO(burdon): Pass in expected arg types. + if (typeof pkg === 'string') { + const ipfsUrl = getServiceUrl(config, 'ipfs.gateway', { path: `${pkg}` }); + return {pkg}; + } + + // eslint-disable-next-line default-case + switch (type) { + case 'wrn:bot': { + const packageLinks = []; + Object.keys(pkg).forEach(platform => { + Object.keys(pkg[platform]).forEach(arch => { + const cid = pkg[platform][arch]; + const ipfsUrl = getServiceUrl(config, 'ipfs.gateway', { path: `${cid}` }); + + packageLinks.push( + + + {platform}/{arch}: {cid} + + + ); + }); + }); + + return ( + {packageLinks} + ); + } + } + + return null; +}; + +export default PackageLink; diff --git a/packages/console-client/src/components/Panel.js b/packages/console-client/src/components/Panel.js index d651b93..a7070df 100644 --- a/packages/console-client/src/components/Panel.js +++ b/packages/console-client/src/components/Panel.js @@ -4,9 +4,8 @@ import React from 'react'; import { makeStyles } from '@material-ui/core'; -import Paper from '@material-ui/core/Paper'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles(() => ({ root: { display: 'flex', flexDirection: 'column', @@ -28,9 +27,9 @@ const Panel = ({ toolbar, children }) => { return (
{toolbar} - +
{children} - +
); }; diff --git a/packages/console-client/src/components/Sidebar.js b/packages/console-client/src/components/Sidebar.js index ab0a4d5..8594a47 100644 --- a/packages/console-client/src/components/Sidebar.js +++ b/packages/console-client/src/components/Sidebar.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import clsx from 'clsx'; @@ -16,7 +16,8 @@ const useStyles = makeStyles(theme => ({ display: 'flex', flex: 1, flexDirection: 'column', - justifyContent: 'space-between' + justifyContent: 'space-between', + // backgroundColor: theme.palette.grey[100] }, list: { diff --git a/packages/console-client/src/components/StatusBar.js b/packages/console-client/src/components/StatusBar.js index 8368db4..c2dc1be 100644 --- a/packages/console-client/src/components/StatusBar.js +++ b/packages/console-client/src/components/StatusBar.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import clsx from 'clsx'; @@ -86,7 +86,7 @@ const StatusBar = () => { }; return ( - +
diff --git a/packages/console-client/src/components/Table.js b/packages/console-client/src/components/Table.js new file mode 100644 index 0000000..f4b93a5 --- /dev/null +++ b/packages/console-client/src/components/Table.js @@ -0,0 +1,41 @@ +// +// Copyright 2020 DxOS.org +// + +import React from 'react'; +import { makeStyles } from '@material-ui/core'; +import MuiTable from '@material-ui/core/Table'; +import TableContainer from '@material-ui/core/TableContainer'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + flex: 1, + overflowY: 'scroll', + backgroundColor: theme.palette.background.paper + }, + + table: { + tableLayout: 'fixed', + + '& th': { + fontVariant: 'all-small-caps', + fontSize: 18, + cursor: 'ns-resize' + } + } +})); + +const Table = ({ children }) => { + const classes = useStyles(); + + return ( + + + {children} + + + ); +}; + +export default Table; diff --git a/packages/console-client/src/components/TableCell.js b/packages/console-client/src/components/TableCell.js index 0392c35..fa63db1 100644 --- a/packages/console-client/src/components/TableCell.js +++ b/packages/console-client/src/components/TableCell.js @@ -1,28 +1,38 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // +import clsx from 'clsx'; import React from 'react'; import MuiTableCell from '@material-ui/core/TableCell'; +import { makeStyles } from '@material-ui/core'; -// TODO(burdon): Size for header. -// TODO(burdon): Standardize table. +const useStyles = makeStyles(() => ({ + small: { + width: 160 + } +})); -const TableCell = ({ children, monospace = false, title, ...rest }) => ( - - {children} - -); +const TableCell = ({ children, size, monospace = false, title, ...rest }) => { + const classes = useStyles(); + + return ( + + {children} + + ); +}; export default TableCell; diff --git a/packages/console-client/src/components/Toolbar.js b/packages/console-client/src/components/Toolbar.js index c1920b3..82c42e0 100644 --- a/packages/console-client/src/components/Toolbar.js +++ b/packages/console-client/src/components/Toolbar.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React from 'react'; @@ -10,18 +10,20 @@ const useStyles = makeStyles(theme => ({ toolbar: { display: 'flex', justifyContent: 'space-between', + whiteSpace: 'nowrap', '& > button': { - margin: theme.spacing(0.5), + margin: theme.spacing(0.5) } } })); +// TODO(burdon): Tabs. const Toolbar = ({ children }) => { const classes = useStyles(); return ( - + {children} ); diff --git a/packages/console-client/src/containers/ConsoleContextProvider.js b/packages/console-client/src/containers/ConsoleContextProvider.js index 7a92203..1d08ee5 100644 --- a/packages/console-client/src/containers/ConsoleContextProvider.js +++ b/packages/console-client/src/containers/ConsoleContextProvider.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React, { useEffect, useReducer } from 'react'; diff --git a/packages/console-client/src/containers/Main.js b/packages/console-client/src/containers/Main.js index 8313d42..1f1a398 100644 --- a/packages/console-client/src/containers/Main.js +++ b/packages/console-client/src/containers/Main.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import debug from 'debug'; @@ -17,10 +17,14 @@ import modules from '../modules'; import Layout from '../components/Layout'; import ConsoleContextProvider from './ConsoleContextProvider'; +import AppRecords from './panels/apps/Apps'; +import Bots from './panels/bots/Bots'; import Config from './panels/Config'; -import IPFS from './panels/IPFS'; +import IPFS from './panels/ipfs/IPFS'; +import Metadata from './panels/Metadata'; +import Signaling from './panels/Signaling'; import Status from './panels/Status'; -import WNS from './panels/WNS'; +import WNS from './panels/wns/WNS'; debug.enable(config.system.debug); @@ -34,8 +38,12 @@ const Main = () => { + + + + diff --git a/packages/console-client/src/containers/panels/Config.js b/packages/console-client/src/containers/panels/Config.js index 254e37f..c58cf41 100644 --- a/packages/console-client/src/containers/panels/Config.js +++ b/packages/console-client/src/containers/panels/Config.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React, { useContext } from 'react'; diff --git a/packages/console-client/src/containers/panels/Metadata.js b/packages/console-client/src/containers/panels/Metadata.js new file mode 100644 index 0000000..8e9b762 --- /dev/null +++ b/packages/console-client/src/containers/panels/Metadata.js @@ -0,0 +1,21 @@ +// +// Copyright 2020 DxOS.org +// + +import React from 'react'; +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + root: {} +})); + +const Signal = () => { + const classes = useStyles(); + + return ( +
+
+ ); +}; + +export default Signal; diff --git a/packages/console-client/src/containers/panels/Signaling.js b/packages/console-client/src/containers/panels/Signaling.js new file mode 100644 index 0000000..062e3b9 --- /dev/null +++ b/packages/console-client/src/containers/panels/Signaling.js @@ -0,0 +1,21 @@ +// +// Copyright 2020 DxOS.org +// + +import React from 'react'; +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + root: {} +})); + +const Signaling = () => { + const classes = useStyles(); + + return ( +
+
+ ); +}; + +export default Signaling; diff --git a/packages/console-client/src/containers/panels/Status.js b/packages/console-client/src/containers/panels/Status.js index 445588b..f3dc2f9 100644 --- a/packages/console-client/src/containers/panels/Status.js +++ b/packages/console-client/src/containers/panels/Status.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React, { useContext } from 'react'; diff --git a/packages/console-client/src/containers/panels/WNS.js b/packages/console-client/src/containers/panels/WNS.js deleted file mode 100644 index f566847..0000000 --- a/packages/console-client/src/containers/panels/WNS.js +++ /dev/null @@ -1,123 +0,0 @@ -// -// 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/src/containers/panels/apps/AppRecords.js b/packages/console-client/src/containers/panels/apps/AppRecords.js new file mode 100644 index 0000000..7decfbc --- /dev/null +++ b/packages/console-client/src/containers/panels/apps/AppRecords.js @@ -0,0 +1,87 @@ +// +// Copyright 2020 DxOS.org +// + +import React, { useContext } from 'react'; +import { useQuery } from '@apollo/react-hooks'; + +import WNS_RECORDS from '../../../../gql/wns_records.graphql'; + +import { ConsoleContext, useQueryStatusReducer } from '../../../hooks'; + +import Link from '@material-ui/core/Link'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import TableBody from '@material-ui/core/TableBody'; + +import { getServiceUrl } from '../../../util/config'; + +import Table from '../../../components/Table'; +import TableCell from '../../../components/TableCell'; + +const AppRecords = () => { + const { config } = useContext(ConsoleContext); + const data = useQueryStatusReducer(useQuery(WNS_RECORDS, { + pollInterval: config.api.pollInterval, + variables: { type: 'wrn:app' } + })); + + if (!data) { + return null; + } + + const records = data.wns_records.json; + + // TODO(burdon): Factor out. + const sorter = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0); + + // TODO(burdon): Test if app is deployed. + const getAppUrl = ({ name, version }) => { + const base = getServiceUrl(config, 'app.server'); + const pathComponents = [base]; + + // TODO(burdon): Fix. + // `wire app serve` expects the /wrn/ prefix. + // That is OK in the production config where we can make it part of the the route, + // but in development it must be prepended since we don't want to make it part of services.app.server. + if (!base.startsWith(`/${config.services.app.prefix}`) && !base.endsWith(`/${config.services.app.prefix}`)) { + pathComponents.push(config.services.app.prefix.substring(1)); + } + + pathComponents.push(`${name}@${version}`); + return pathComponents.join('/'); + }; + + return ( + + + + Name + Version + Description + Link + + + + {records.sort(sorter).map(({ id, name, version, attributes: { displayName, publicUrl } }) => { + const link = getAppUrl({ id, name, version, publicUrl }); + + return ( + + {name} + {version} + {displayName} + + {link && ( + {link} + )} + + + ); + })} + +
+ ); +}; + +export default AppRecords; diff --git a/packages/console-client/src/containers/panels/apps/Apps.js b/packages/console-client/src/containers/panels/apps/Apps.js new file mode 100644 index 0000000..ec7c861 --- /dev/null +++ b/packages/console-client/src/containers/panels/apps/Apps.js @@ -0,0 +1,42 @@ +// +// Copyright 2020 DxOS.org +// + +import React, { useState } from 'react'; +import { makeStyles } from '@material-ui/core'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; + +import Panel from '../../../components/Panel'; +import Toolbar from '../../../components/Toolbar'; + +import AppRecords from './AppRecords'; + +const TAB_RECORDS = 'records'; + +const useStyles = makeStyles(theme => ({ + root: {} +})); + +const Apps = () => { + const classes = useStyles(); + const [tab, setTab] = useState(TAB_RECORDS); + + return ( + + setTab(value)}> + + + + } + > + {tab === TAB_RECORDS && ( + + )} + + ); +}; + +export default Apps; diff --git a/packages/console-client/src/containers/panels/bots/BotRecords.js b/packages/console-client/src/containers/panels/bots/BotRecords.js new file mode 100644 index 0000000..65f19ad --- /dev/null +++ b/packages/console-client/src/containers/panels/bots/BotRecords.js @@ -0,0 +1,59 @@ +// +// Copyright 2020 DxOS.org +// + +import React, { useContext } from 'react'; +import { useQuery } from '@apollo/react-hooks'; + +import WNS_RECORDS from '../../../../gql/wns_records.graphql'; + +import { ConsoleContext, useQueryStatusReducer } from '../../../hooks'; + +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import TableBody from '@material-ui/core/TableBody'; + +import Table from '../../../components/Table'; +import TableCell from '../../../components/TableCell'; + +const AppRecords = () => { + const { config } = useContext(ConsoleContext); + const data = useQueryStatusReducer(useQuery(WNS_RECORDS, { + pollInterval: config.api.pollInterval, + variables: { type: 'wrn:bot' } + })); + + if (!data) { + return null; + } + + const records = data.wns_records.json; + + // TODO(burdon): Factor out. + const sorter = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0); + + return ( + + + + Name + Version + Description + + + + {records.sort(sorter).map(({ id, name, version, attributes: { displayName } }) => { + return ( + + {name} + {version} + {displayName} + + ); + })} + +
+ ); +}; + +export default AppRecords; diff --git a/packages/console-client/src/containers/panels/bots/Bots.js b/packages/console-client/src/containers/panels/bots/Bots.js new file mode 100644 index 0000000..48433de --- /dev/null +++ b/packages/console-client/src/containers/panels/bots/Bots.js @@ -0,0 +1,42 @@ +// +// Copyright 2020 DxOS.org +// + +import React, { useState } from 'react'; +import { makeStyles } from '@material-ui/core'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; + +import Panel from '../../../components/Panel'; +import Toolbar from '../../../components/Toolbar'; + +import BotRecords from './BotRecords'; + +const TAB_RECORDS = 'records'; + +const useStyles = makeStyles(theme => ({ + root: {} +})); + +const Apps = () => { + const classes = useStyles(); + const [tab, setTab] = useState(TAB_RECORDS); + + return ( + + setTab(value)}> + + + + } + > + {tab === TAB_RECORDS && ( + + )} + + ); +}; + +export default Apps; diff --git a/packages/console-client/src/containers/panels/IPFS.js b/packages/console-client/src/containers/panels/ipfs/IPFS.js similarity index 56% rename from packages/console-client/src/containers/panels/IPFS.js rename to packages/console-client/src/containers/panels/ipfs/IPFS.js index c6e3c07..593593f 100644 --- a/packages/console-client/src/containers/panels/IPFS.js +++ b/packages/console-client/src/containers/panels/ipfs/IPFS.js @@ -1,17 +1,17 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React from 'react'; import { useQuery } from '@apollo/react-hooks'; -import IPFS_STATUS from '../../../gql/ipfs_status.graphql'; +import IPFS_STATUS from '../../../../gql/ipfs_status.graphql'; -import { useQueryStatusReducer } from '../../hooks'; +import { useQueryStatusReducer } from '../../../hooks'; -import Json from '../../components/Json'; -import Panel from '../../components/Panel'; -import Toolbar from '../../components/Toolbar'; +import Json from '../../../components/Json'; +import Panel from '../../../components/Panel'; +import Toolbar from '../../../components/Toolbar'; const IPFS = () => { const data = useQueryStatusReducer(useQuery(IPFS_STATUS)); diff --git a/packages/console-client/src/containers/panels/wns/WNS.js b/packages/console-client/src/containers/panels/wns/WNS.js new file mode 100644 index 0000000..dc6e529 --- /dev/null +++ b/packages/console-client/src/containers/panels/wns/WNS.js @@ -0,0 +1,108 @@ +// +// Copyright 2020 DxOS.org +// + +import React, { useState } from 'react'; +import { Mutation } from '@apollo/react-components'; +import { makeStyles } from '@material-ui/core'; +import Paper from '@material-ui/core/Paper'; +import Tab from '@material-ui/core/Tab'; +import Tabs from '@material-ui/core/Tabs'; +import TabContext from '@material-ui/lab/TabContext'; + +import WNS_ACTION from '../../../../gql/wns_action.graphql'; + +import ControlButtons from '../../../components/ControlButtons'; +import Panel from '../../../components/Panel'; +import Toolbar from '../../../components/Toolbar'; + +import WNSLog from './WNSLog'; +import WNSRecords, { WNSRecordType } from './WNSRecords'; +import WNSStatus from './WNSStatus'; + +const TAB_RECORDS = 'explorer'; +const TAB_STATUS = 'records'; +const TAB_LOG = 'log'; + +const useStyles = makeStyles(() => ({ + expand: { + flex: 1 + }, + + panel: { + display: 'flex', + overflow: 'hidden', + flex: 1 + }, + + paper: { + display: 'flex', + overflow: 'hidden', + flex: 1 + } +})); + +const WNS = () => { + const classes = useStyles(); + const [tab, setTab] = useState(TAB_RECORDS); + const [type, setType] = useState(); + + return ( + + setTab(value)}> + + + + + + {tab === TAB_RECORDS && ( + + )} + +
+ + + {(action, { data }) => ( +
+ { + action({ variables: { command: 'start' } }); + }} + onStop={() => { + action({ variables: { command: 'stop' } }); + }} + /> +
+ )} +
+ + } + > + + {tab === TAB_RECORDS && ( +
+ +
+ )} + + {tab === TAB_STATUS && ( +
+ + + +
+ )} + + {tab === TAB_LOG && ( +
+ +
+ )} +
+ + ); +}; + +export default WNS; diff --git a/packages/console-client/src/containers/panels/wns/WNSLog.js b/packages/console-client/src/containers/panels/wns/WNSLog.js new file mode 100644 index 0000000..af4fac2 --- /dev/null +++ b/packages/console-client/src/containers/panels/wns/WNSLog.js @@ -0,0 +1,26 @@ +// +// Copyright 2020 DxOS.org +// + +import React, { useContext } from 'react'; +import { useQuery } from '@apollo/react-hooks'; + +import WNS_LOG from '../../../../gql/wns_log.graphql'; + +import { ConsoleContext, useQueryStatusReducer } from '../../../hooks'; + +import Log from '../../../components/Log'; + +const WNSLog = () => { + const { config } = useContext(ConsoleContext); + const data = useQueryStatusReducer(useQuery(WNS_LOG, { pollInterval: config.api.pollInterval })); + if (!data) { + return null; + } + + return ( + + ); +}; + +export default WNSLog; diff --git a/packages/console-client/src/containers/panels/wns/WNSRecords.js b/packages/console-client/src/containers/panels/wns/WNSRecords.js new file mode 100644 index 0000000..75354b9 --- /dev/null +++ b/packages/console-client/src/containers/panels/wns/WNSRecords.js @@ -0,0 +1,121 @@ +// +// Copyright 2020 DxOS.org +// + +import get from 'lodash.get'; +import moment from 'moment'; +import React, { useContext, useState } from 'react'; +import { useQuery } from '@apollo/react-hooks'; +import { makeStyles } from '@material-ui/core'; +import ButtonGroup from '@material-ui/core/ButtonGroup'; +import Button from '@material-ui/core/Button'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import TableBody from '@material-ui/core/TableBody'; + +import WNS_RECORDS from '../../../../gql/wns_records.graphql'; + +import { ConsoleContext, useQueryStatusReducer } from '../../../hooks'; + +import Table from '../../../components/Table'; +import TableCell from '../../../components/TableCell'; + +import PackageLink from '../../../components/PackageLink'; + +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 useStyles = makeStyles(theme => ({ + selected: { + color: theme.palette.text.primary + } +})); + +export const WNSRecordType = ({ type = types[0].key, onChanged }) => { + const classes = useStyles(); + + return ( + + {types.map(t => ( + + ))} + + ); +}; + +const WNSRecords = ({ type }) => { + const { config } = useContext(ConsoleContext); + const [{ sort, ascend }, setSort] = useState({ sort: 'type', ascend: true }); + const data = useQueryStatusReducer(useQuery(WNS_RECORDS, { + pollInterval: config.api.pollInterval, + variables: { type } + })); + + if (!data) { + return null; + } + + const records = data.wns_records.json; + + // TODO(burdon): Factor out. + const sortBy = field => () => setSort({ sort: field, ascend: (field === sort ? !ascend : true) }); + const sorter = (item1, item2) => { + const a = get(item1, sort); + const b = get(item2, sort); + const dir = ascend ? 1 : -1; + return (a < b) ? -1 * dir : (a > b) ? dir : 0; + }; + + return ( + + + + Type + Name + Version + Description + Package Hash + Created + + + + {records.sort(sorter) + .map(({ id, type, name, version, createTime, attributes: { displayName, package: pkg } }) => ( + + {type} + {name} + {version} + {displayName} + + {pkg && ( + + )} + + {moment.utc(createTime).fromNow()} + + ))} + +
+ ); +}; + +export default WNSRecords; diff --git a/packages/console-client/src/containers/panels/wns/WNSStatus.js b/packages/console-client/src/containers/panels/wns/WNSStatus.js new file mode 100644 index 0000000..7efac28 --- /dev/null +++ b/packages/console-client/src/containers/panels/wns/WNSStatus.js @@ -0,0 +1,26 @@ +// +// Copyright 2020 DxOS.org +// + +import React, { useContext } from 'react'; +import { useQuery } from '@apollo/react-hooks'; + +import WNS_STATUS from '../../../../gql/wns_status.graphql'; + +import { ConsoleContext, useQueryStatusReducer } from '../../../hooks'; + +import Json from '../../../components/Json'; + +const WNSStatus = () => { + const { config } = useContext(ConsoleContext); + const data = useQueryStatusReducer(useQuery(WNS_STATUS, { pollInterval: config.api.pollInterval })); + if (!data) { + return null; + } + + return ( + + ); +}; + +export default WNSStatus; diff --git a/packages/console-client/src/hooks/context.js b/packages/console-client/src/hooks/context.js index bd63347..3fd01ff 100644 --- a/packages/console-client/src/hooks/context.js +++ b/packages/console-client/src/hooks/context.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import { createContext } from 'react'; diff --git a/packages/console-client/src/hooks/index.js b/packages/console-client/src/hooks/index.js index a8a1540..14b5c2f 100644 --- a/packages/console-client/src/hooks/index.js +++ b/packages/console-client/src/hooks/index.js @@ -1,6 +1,7 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // export * from './context'; +export * from './registry'; export * from './status'; diff --git a/packages/console-client/src/hooks/registry.js b/packages/console-client/src/hooks/registry.js new file mode 100644 index 0000000..a76d333 --- /dev/null +++ b/packages/console-client/src/hooks/registry.js @@ -0,0 +1,19 @@ +// +// Copyright 2020 DxOS.org +// + +import { Registry } from '@wirelineio/registry-client'; + +import { getServiceUrl } from '../util/config'; + +export const useRegistry = (config) => { + const endpoint = getServiceUrl(config, 'wns.server', { absolute: true }); + const registry = new Registry(endpoint); + + return { + registry, + + // TODO(burdon): Separate hook. + webui: getServiceUrl(config, 'wns.webui', { absolute: true }) + }; +}; diff --git a/packages/console-client/src/hooks/status.js b/packages/console-client/src/hooks/status.js index 3cec19a..96cdf42 100644 --- a/packages/console-client/src/hooks/status.js +++ b/packages/console-client/src/hooks/status.js @@ -8,8 +8,12 @@ import { ConsoleContext } from './context'; export const SET_STATUS = 'errors'; +/** + * + */ export const useStatusReducer = () => { const { state, dispatch } = useContext(ConsoleContext); + return [ state[SET_STATUS] || {}, value => dispatch({ type: SET_STATUS, payload: value || { exceptions: [] } }) diff --git a/packages/console-client/src/icons/DXOS.js b/packages/console-client/src/icons/DXOS.js index ca14b44..18708ac 100644 --- a/packages/console-client/src/icons/DXOS.js +++ b/packages/console-client/src/icons/DXOS.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React from 'react'; diff --git a/packages/console-client/src/icons/Logo.js b/packages/console-client/src/icons/Logo.js index 2d06ba3..51b3304 100644 --- a/packages/console-client/src/icons/Logo.js +++ b/packages/console-client/src/icons/Logo.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React from 'react'; diff --git a/packages/console-client/src/index.js b/packages/console-client/src/index.js index 3659b8c..7a340ac 100644 --- a/packages/console-client/src/index.js +++ b/packages/console-client/src/index.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // export * from './hooks'; diff --git a/packages/console-client/src/main.js b/packages/console-client/src/main.js index 0288c53..87e279e 100644 --- a/packages/console-client/src/main.js +++ b/packages/console-client/src/main.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import React from 'react'; diff --git a/packages/console-client/src/modules.js b/packages/console-client/src/modules.js index 5025163..d47f2a6 100644 --- a/packages/console-client/src/modules.js +++ b/packages/console-client/src/modules.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import AppsIcon from '@material-ui/icons/Apps'; diff --git a/packages/console-client/src/resolvers.js b/packages/console-client/src/resolvers.js index 323b21e..aadb113 100644 --- a/packages/console-client/src/resolvers.js +++ b/packages/console-client/src/resolvers.js @@ -1,34 +1,53 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import debug from 'debug'; import { Registry } from '@wirelineio/registry-client'; +import { getServiceUrl } from './util/config'; + const log = debug('dxos:console:client:resolvers'); -// -// Resolvers -// https://www.apollographql.com/docs/tutorial/local-state/#local-resolvers -// - +/** + * Resolvers + * https://www.apollographql.com/docs/tutorial/local-state/#local-resolvers + * @param config + */ export const createResolvers = config => { - // TODO(burdon): Get route if served from xbox. - const { services: { wns: { server } } } = config; - - const registry = new Registry(server); + const endpoint = getServiceUrl(config, 'wns.server', { absolute: true }); + const registry = new Registry(endpoint); return { Query: { wns_status: async () => { - log('Querying WNS...'); - - const status = await registry.getStatus(); + log('WNS status...'); + const data = await registry.getStatus(); return { __typename: 'JSONResult', - json: JSON.stringify(status) + json: data + }; + }, + + wns_records: async (_, { type }) => { + log('WNS records...'); + const data = await registry.queryRecords({ type }); + + return { + __typename: 'JSONResult', + json: data + }; + }, + + wns_log: async () => { + log('WNS log...'); + + // TODO(burdon): Use Registry API rather than from CLI? + return { + __typename: 'JSONLog', + log: [] }; } } diff --git a/packages/console-client/src/theme.js b/packages/console-client/src/theme.js index eb803f3..6a06e2e 100644 --- a/packages/console-client/src/theme.js +++ b/packages/console-client/src/theme.js @@ -19,6 +19,36 @@ export const createTheme = (theme) => createMuiTheme({ props: { MuiButtonBase: { disableRipple: true + }, + MuiButton: { + size: 'small' + }, + MuiFilledInput: { + margin: 'dense' + }, + MuiFormControl: { + margin: 'dense' + }, + MuiFormHelperText: { + margin: 'dense' + }, + MuiIconButton: { + size: 'small' + }, + MuiInputBase: { + margin: 'dense' + }, + MuiInputLabel: { + margin: 'dense' + }, + MuiTable: { + size: 'small' + }, + MuiTextField: { + margin: 'dense' + }, + MuiToolbar: { + variant: 'dense' } }, diff --git a/packages/console-client/src/util/config.js b/packages/console-client/src/util/config.js new file mode 100644 index 0000000..318a830 --- /dev/null +++ b/packages/console-client/src/util/config.js @@ -0,0 +1,40 @@ +// +// Copyright 2020 DxOS.org +// + +import assert from 'assert'; +import buildUrl from 'build-url'; +import get from 'lodash.get'; + +/** + * Returns the service URL that can be used by the client. + * @param {Object} config + * @param {string} service + * @param {Object} [options] + * @param {string} [options.path] + * @param {boolean} [options.absolute] + * @returns {string|*} + */ +export const getServiceUrl = (config, service, options = {}) => { + const { path, absolute = false } = options; + const { routes, services } = config; + + const appendPath = (url) => buildUrl(url, { path }); + + // Relative route. + const routePath = get(routes, service); + if (routePath) { + if (absolute) { + assert(typeof window !== 'undefined'); + return buildUrl(window.location.origin, { path: appendPath(routePath) }); + } + + // Relative. + return appendPath(routePath); + } + + // Absolute service path. + const serviceUrl = get(services, service); + assert(serviceUrl, `Invalid service definition: ${service}`); + return appendPath(serviceUrl); +}; diff --git a/packages/console-client/src/util/config.test.js b/packages/console-client/src/util/config.test.js new file mode 100644 index 0000000..a580ae6 --- /dev/null +++ b/packages/console-client/src/util/config.test.js @@ -0,0 +1,42 @@ +// +// Copyright 2020 DxOS.org +// + +import { getServiceUrl } from './config'; + +// noinspection JSConstantReassignment +global.window = { + location: { + origin: 'http://localhost' + } +}; + +const config = { + services: { + foo: { + server: 'http://localhost:3000/foo' + }, + + bar: { + server: 'http://localhost:3000/bar' + } + }, + + routes: { + foo: { + server: '/foo' + } + } +}; + +test('getServiceUrl', () => { + expect(() => getServiceUrl({}, 'foo.server')).toThrow(); + + expect(getServiceUrl(config, 'foo.server')).toEqual('/foo'); + expect(getServiceUrl(config, 'foo.server', { path: '/123' })).toEqual('/foo/123'); + expect(getServiceUrl(config, 'foo.server', { path: '/123', absolute: true })).toEqual('http://localhost/foo/123'); + + expect(getServiceUrl(config, 'bar.server')).toEqual('http://localhost:3000/bar'); + expect(getServiceUrl(config, 'bar.server', { path: '/123' })).toEqual('http://localhost:3000/bar/123'); + expect(getServiceUrl(config, 'bar.server', { path: '/123', absolute: true })).toEqual('http://localhost:3000/bar/123'); +}); diff --git a/packages/console-client/version.json b/packages/console-client/version.json index f174238..93867b5 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-25T02:29:08.942Z", + "buildDate": "2020-05-25T22:04:20.163Z", "version": "1.0.0-beta.0" } } diff --git a/packages/console-server/babel.config.js b/packages/console-server/babel.config.js index f3ccce9..0aa7e84 100644 --- a/packages/console-server/babel.config.js +++ b/packages/console-server/babel.config.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // module.exports = { diff --git a/packages/console-server/src/gql/api.graphql b/packages/console-server/src/gql/api.graphql index bc510c9..ed8d0ee 100644 --- a/packages/console-server/src/gql/api.graphql +++ b/packages/console-server/src/gql/api.graphql @@ -1,5 +1,5 @@ # -# Copyright 2020 DxOS +# Copyright 2020 DxOS.org # type JSONResult { @@ -25,14 +25,16 @@ type Result { # type Query { - system_status: Status - ipfs_status: JSONResult - wns_status: JSONResult - wns_log: Log + system_status: Status! + ipfs_status: JSONResult! + wns_status: JSONResult! + # TODO(burdon): Import WNS schema! + wns_records(type: String): JSONResult! + wns_log: Log! } type Mutation { - wns_action(command: String!): Result + wns_action(command: String!): Result! } schema { diff --git a/packages/console-server/src/main.js b/packages/console-server/src/main.js index 7872ebf..b156427 100644 --- a/packages/console-server/src/main.js +++ b/packages/console-server/src/main.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import debug from 'debug'; diff --git a/packages/console-server/src/resolvers.js b/packages/console-server/src/resolvers.js index 60e5de9..c6e0cff 100644 --- a/packages/console-server/src/resolvers.js +++ b/packages/console-server/src/resolvers.js @@ -1,5 +1,5 @@ // -// Copyright 2020 DxOS +// Copyright 2020 DxOS.org // import debug from 'debug'; @@ -9,12 +9,13 @@ import { version } from '../package.json'; const log = debug('dxos:console:server:resolvers'); -// -// Resolvers -// - const timestamp = () => new Date().toUTCString(); +/** + * Resolvers + * https://www.apollographql.com/docs/graphql-tools/resolvers + * @param config + */ export const createResolvers = config => ({ Mutation: { // diff --git a/yarn.lock b/yarn.lock index 928ecfe..1ab7b67 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4672,6 +4672,11 @@ buffer@^5.2.1, buffer@^5.4.2, buffer@^5.4.3, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.0.2" ieee754 "^1.1.4" +build-url@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/build-url/-/build-url-2.0.0.tgz#7bdd4045e51caa96c1586990e4ca514937598fc2" + integrity sha512-LYvvOlDc9jT07wFXTQTKoQLYaXIJriVl/DgatTsSzY963+ip1O7M6G/jWBrlKKJ1L7HGD3oK+WykmOvbcSYXlQ== + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"