diff --git a/CHANGELOG.md b/CHANGELOG.md index 0891d4d..5455058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- [x] Fixed hook that causes 100x rerendering. +- [x] Logging. +- [x] Error handling. - [x] Table sorting. - [x] Version check. - [x] Fix JsonTree (yarn link). @@ -23,9 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [x] Monorepo for client/server. - [x] Basic React/Apollo component. -## Tasks - -### POC +### Next - [ ] Webpack and dynamic config - [ ] Complete WNS functionality @@ -38,8 +39,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [ ] Signal - [ ] Metadata -### Next - - [ ] https://github.com/standard/standardx (JSX) - [ ] Client/server API abstraction (error handler, etc.) - [ ] Port dashboard API calls (resolve config first). diff --git a/packages/console-client/package.json b/packages/console-client/package.json index 0b8ef00..5fe81c3 100644 --- a/packages/console-client/package.json +++ b/packages/console-client/package.json @@ -30,8 +30,9 @@ "@apollo/react-components": "^3.1.5", "@apollo/react-hooks": "^3.1.5", "@babel/runtime": "^7.8.7", + "@dxos/debug": "^1.0.0-beta.20", "@dxos/gem-core": "^1.0.0-beta.11", - "@dxos/react-ux": "^1.0.0-beta.20", + "@dxos/react-ux": "^1.0.0-beta.37", "@material-ui/core": "^4.10.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.54", diff --git a/packages/console-client/src/components/Error.js b/packages/console-client/src/components/Error.js index 93c07c7..deb5215 100644 --- a/packages/console-client/src/components/Error.js +++ b/packages/console-client/src/components/Error.js @@ -2,7 +2,7 @@ // Copyright 2020 DxOS.org // -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Alert from '@material-ui/lab/Alert'; import AlertTitle from '@material-ui/lab/AlertTitle'; @@ -10,30 +10,37 @@ import Snackbar from '@material-ui/core/Snackbar'; const useStyles = makeStyles(() => ({ root: { - marginBottom: 60 + marginBottom: 48 + }, + + alert: { + minWidth: 400 } })); -const Error = ({ message, ...rest }) => { +const Error = ({ error, ...rest }) => { const classes = useStyles(); - if (!message) { - return null; - } + const [message, setMessage] = useState(error); - const messages = Array.isArray(message) ? message : [message]; + useEffect(() => { + setMessage(error ? error.message || String(error) : null); + }, [error]); + + const handleClose = () => { + setMessage(null); + }; return ( - + Error - {messages.map((message, i) => ( -
{message}
- ))} +
{message}
); diff --git a/packages/console-client/src/components/Log.js b/packages/console-client/src/components/Log.js index b25c6f4..c65644e 100644 --- a/packages/console-client/src/components/Log.js +++ b/packages/console-client/src/components/Log.js @@ -4,31 +4,28 @@ import clsx from 'clsx'; import moment from 'moment'; -import React, { Fragment } from 'react'; +import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; const useStyles = makeStyles(theme => ({ root: { display: 'flex', flex: 1, - flexDirection: 'column', overflow: 'hidden' }, - container: { + log: { display: 'flex', // Pin to bottom (render in time order). flexDirection: 'column-reverse', - flex: 1, - overflowX: 'scroll', - overflowY: 'scroll' - }, - - log: { + overflow: 'scroll', padding: theme.spacing(1), - fontSize: 16, - fontFamily: 'monospace', - whiteSpace: 'nowrap' + + '& div': { + fontSize: 16, + fontFamily: 'monospace', + whiteSpace: 'nowrap' + } }, level: { @@ -50,7 +47,7 @@ const useStyles = makeStyles(theme => ({ } })); -const Log = ({ log = [], onClear }) => { +const Log = ({ log = [] }) => { const classes = useStyles(); const levels = { @@ -91,11 +88,11 @@ const Log = ({ log = [], onClear }) => { const pkg = levels[level] ? '' : `[${level}]: `; message = ( - + <> {datetime} {label || level} {pkg}{text} - + ); return true; @@ -111,10 +108,8 @@ const Log = ({ log = [], onClear }) => { return (
-
-
- {log.reverse().map((line, i) => )} -
+
+ {log.map((line, i) => )}
); diff --git a/packages/console-client/src/components/PackageLink.js b/packages/console-client/src/components/PackageLink.js index 1bca09d..27223cc 100644 --- a/packages/console-client/src/components/PackageLink.js +++ b/packages/console-client/src/components/PackageLink.js @@ -2,7 +2,7 @@ // Copyright 2020 DxOS.org.org // -import React, { Fragment } from 'react'; +import React from 'react'; import Link from '@material-ui/core/Link'; import { getServiceUrl } from '../util/config'; @@ -14,7 +14,6 @@ import { getServiceUrl } from '../util/config'; * @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}` }); @@ -25,28 +24,26 @@ const PackageLink = ({ config, type, pkg }) => { switch (type) { case 'wrn:bot': { const packageLinks = []; - Object.keys(pkg).forEach(platform => { + Object.keys(pkg).forEach((platform, i) => { Object.keys(pkg[platform]).forEach(arch => { const cid = pkg[platform][arch]; const ipfsUrl = getServiceUrl(config, 'ipfs.gateway', { path: `${cid}` }); packageLinks.push( - - - {platform}/{arch}: {cid} - - + + {platform}/{arch}: {cid} + ); }); }); return ( - {packageLinks} + <>{packageLinks} ); } } diff --git a/packages/console-client/src/containers/ConsoleContextProvider.js b/packages/console-client/src/containers/ConsoleContextProvider.js index 1d08ee5..2095147 100644 --- a/packages/console-client/src/containers/ConsoleContextProvider.js +++ b/packages/console-client/src/containers/ConsoleContextProvider.js @@ -7,7 +7,7 @@ import defaultsDeep from 'lodash.defaultsdeep'; import ErrorBoundary from '../components/ErrorBoundary'; -import { ConsoleContext, statusReducer, SET_STATUS } from '../hooks'; +import { ConsoleContext, statusReducer, STATUS, SET_STATUS } from '../hooks'; const defaultState = {}; @@ -18,8 +18,7 @@ const defaultState = {}; * @param {string} action */ const appReducer = (state, action) => ({ - // TODO(burdon): Key shouldn't be same as action type. - [SET_STATUS]: statusReducer(state[SET_STATUS], action) + [STATUS]: statusReducer(state[STATUS], action) }); /** @@ -36,8 +35,6 @@ const appReducer = (state, action) => ({ const ConsoleContextProvider = ({ children, config, modules, initialState = {}, errorHandler }) => { const [state, dispatch] = useReducer(appReducer, defaultsDeep({}, initialState, defaultState)); - const { errors: { exceptions = [] } = {} } = state[SET_STATUS] || {}; - // Bind the error handler. if (errorHandler) { useEffect(() => { @@ -45,7 +42,7 @@ const ConsoleContextProvider = ({ children, config, modules, initialState = {}, dispatch({ type: SET_STATUS, payload: { - exceptions: [error, ...exceptions] + error } }); }); diff --git a/packages/console-client/src/containers/Main.js b/packages/console-client/src/containers/Main.js index 9ce9d12..d0d3b9a 100644 --- a/packages/console-client/src/containers/Main.js +++ b/packages/console-client/src/containers/Main.js @@ -9,6 +9,8 @@ import { ApolloProvider } from '@apollo/react-hooks'; import { ThemeProvider } from '@material-ui/core/styles'; import CssBaseline from '@material-ui/core/CssBaseline'; +import { ErrorHandler } from '@dxos/debug'; + import config from '../../config.yml'; import { build } from '../../version.json'; @@ -33,13 +35,16 @@ Object.assign(config, { build }); debug.enable(config.system.debug); +// Global error handler. +const errorHandler = new ErrorHandler(); + /** * Root application. */ const Main = () => { return ( - + diff --git a/packages/console-client/src/containers/StatusBar.js b/packages/console-client/src/containers/StatusBar.js index 1a8da72..f32d40a 100644 --- a/packages/console-client/src/containers/StatusBar.js +++ b/packages/console-client/src/containers/StatusBar.js @@ -18,6 +18,8 @@ import red from '@material-ui/core/colors/red'; import { ConsoleContext, useStatusReducer } from '../hooks'; +import Error from '../components/Error'; + import VersionCheck from './VersionCheck'; const useStyles = makeStyles((theme) => ({ @@ -30,17 +32,15 @@ const useStyles = makeStyles((theme) => ({ color: grey[400] }, left: { + display: 'flex', width: 160 }, right: { + display: 'flex', width: 160, - textAlign: 'right' + justifyContent: 'flex-end' }, center: { - flex: 1, - textAlign: 'center' - }, - info: { display: 'flex', fontFamily: 'monospace', fontSize: 'large', @@ -90,7 +90,7 @@ const StatusBar = () => { const StatusIcon = ({ error }) => { if (error) { return ( - + ); } else { return ( @@ -100,24 +100,28 @@ const StatusBar = () => { }; return ( - -
- - - -
+ <> + +
+ + + +
-
-
{name} ({version})
-
{moment(buildDate).format('L')}
- -
+
+
{name} ({version})
+
{moment(buildDate).format('L')}
+ +
-
- - -
-
+
+ + +
+
+ + + ); }; diff --git a/packages/console-client/src/containers/panels/wns/WNS.js b/packages/console-client/src/containers/panels/wns/WNS.js index dc6e529..95fdaa5 100644 --- a/packages/console-client/src/containers/panels/wns/WNS.js +++ b/packages/console-client/src/containers/panels/wns/WNS.js @@ -20,8 +20,8 @@ import WNSLog from './WNSLog'; import WNSRecords, { WNSRecordType } from './WNSRecords'; import WNSStatus from './WNSStatus'; -const TAB_RECORDS = 'explorer'; -const TAB_STATUS = 'records'; +const TAB_RECORDS = 'records'; +const TAB_STATUS = 'status'; const TAB_LOG = 'log'; const useStyles = makeStyles(() => ({ diff --git a/packages/console-client/src/hooks/status.js b/packages/console-client/src/hooks/status.js index 96cdf42..5bc0e21 100644 --- a/packages/console-client/src/hooks/status.js +++ b/packages/console-client/src/hooks/status.js @@ -2,20 +2,21 @@ // Copyright 2019 DxOS // -import { useContext } from 'react'; +import { useContext, useEffect } from 'react'; import { ConsoleContext } from './context'; -export const SET_STATUS = 'errors'; +export const STATUS = 'xxx'; +export const SET_STATUS = 'set.status'; /** - * + * Dispatcher for app status. */ export const useStatusReducer = () => { const { state, dispatch } = useContext(ConsoleContext); return [ - state[SET_STATUS] || {}, + state[STATUS] || {}, value => dispatch({ type: SET_STATUS, payload: value || { exceptions: [] } }) ]; }; @@ -26,13 +27,15 @@ export const useStatusReducer = () => { export const useQueryStatusReducer = ({ loading, error, data }) => { const [, setStatus] = useStatusReducer(); - if (loading) { - setTimeout(() => setStatus({ loading })); - } + useEffect(() => { + if (loading) { + setTimeout(() => setStatus({ loading })); + } - if (error) { - setTimeout(() => setStatus({ error })); - } + if (error) { + setTimeout(() => setStatus({ error })); + } + }, [loading, error]); return data; }; diff --git a/packages/console-client/src/resolvers.js b/packages/console-client/src/resolvers.js index 2447af4..82cbbd7 100644 --- a/packages/console-client/src/resolvers.js +++ b/packages/console-client/src/resolvers.js @@ -12,6 +12,8 @@ const log = debug('dxos:console:client:resolvers'); const timestamp = () => new Date().toUTCString(); +const MAX_LOG_LENGTH = 200; + /** * Resolvers * https://www.apollographql.com/docs/tutorial/local-state/#local-resolvers @@ -21,6 +23,11 @@ export const createResolvers = config => { const endpoint = getServiceUrl(config, 'wns.server', { absolute: true }); const registry = new Registry(endpoint); + // TODO(burdon): Errors swallowed! + + // Oldest to latest. + let cachedLog = []; + return { Query: { wns_status: async () => { @@ -52,13 +59,29 @@ export const createResolvers = config => { wns_log: async () => { log('WNS log...'); - // TODO(burdon): Cache and merge previous state. const data = await registry.getLogs(); + // TODO(burdon): Bug returns blank line at end. + const filtered = data.map(line => line).filter(Boolean); + + // Cache and merge previous state. + let i = filtered.findIndex(line => line === cachedLog[cachedLog.length - 1]); + if (i === -1) { + cachedLog = filtered; + } else { + i++; + for (; i < filtered.length - 1; i++) { + cachedLog.push(filtered[i]); + } + + // Trim. + cachedLog.splice(0, cachedLog.length - MAX_LOG_LENGTH); + } + return { __typename: 'JSONLog', timestamp: timestamp(), - log: data + log: [...cachedLog].reverse() }; } } diff --git a/packages/console-client/version.json b/packages/console-client/version.json index 9e9a3ed..1c14713 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-26T17:38:45.688Z", + "buildDate": "2020-05-27T01:16:27.565Z", "version": "1.0.0-beta.0" } } diff --git a/packages/console-server/src/resolvers.js b/packages/console-server/src/resolvers.js index a7364e2..de5a764 100644 --- a/packages/console-server/src/resolvers.js +++ b/packages/console-server/src/resolvers.js @@ -15,6 +15,10 @@ const timestamp = () => new Date().toUTCString(); * @param config */ export const createResolvers = config => ({ + + // TODO(burdon): Auth mutations. + // https://www.apollographql.com/docs/apollo-server/data/errors/#codes + Mutation: { // // WNS diff --git a/yarn.lock b/yarn.lock index e635012..b6c2a74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1179,6 +1179,13 @@ dependencies: debug "^4.1.1" +"@dxos/debug@^1.0.0-beta.37": + version "1.0.0-beta.37" + resolved "https://registry.yarnpkg.com/@dxos/debug/-/debug-1.0.0-beta.37.tgz#0fc3a1d9d0b2616c5c22da6fcea7c369610ec282" + integrity sha512-65jOaqMrjv1Comepqq2U0k9olpAiKtk6pVEnhZHb+FutPMnjIkA+TPedNfTpxWQEs3MpG0IVyj64JugGl2KhQA== + dependencies: + debug "^4.1.1" + "@dxos/gem-core@^1.0.0-beta.11": version "1.0.0-beta.11" resolved "https://registry.yarnpkg.com/@dxos/gem-core/-/gem-core-1.0.0-beta.11.tgz#b4b8e82b3cfe7a9dd06fac8596ccf04fa8028e21" @@ -1194,17 +1201,16 @@ react-dom "^16.13.1" react-resize-aware "^3.0.0" -"@dxos/react-ux@^1.0.0-beta.20": - version "1.0.0-beta.20" - resolved "https://registry.yarnpkg.com/@dxos/react-ux/-/react-ux-1.0.0-beta.20.tgz#0f0801ae2ddb9089428034cc4b466ee98206a180" - integrity sha512-larSB5cNCbyvP55/qan9uzioGXrcbtvrmy+fBRx7eLlsFs/eTgD/zrNMSVtq5mvMAn/oKKCOX1wipxm4gK6fdA== +"@dxos/react-ux@^1.0.0-beta.37": + version "1.0.0-beta.37" + resolved "https://registry.yarnpkg.com/@dxos/react-ux/-/react-ux-1.0.0-beta.37.tgz#4a6003ce52f0afa156e0af457162d2d14695f153" + integrity sha512-QuRIAc4xY57uHRqRnXwGZEW5VD6NVmpIQWRB1T2XsmRdyIzwWDHgK6pBzd7zeNQI/6nqzHr0fkeRY0Lsqou8Cw== dependencies: "@dxos/crypto" "^1.0.0-beta.1" - "@dxos/debug" "^1.0.0-beta.20" + "@dxos/debug" "^1.0.0-beta.37" "@material-ui/core" "^4.9.0" "@material-ui/icons" "^4.5.1" "@material-ui/lab" "^4.0.0-alpha.42" - "@material-ui/styles" "^4.9.0" clsx "^1.0.4" lodash.isplainobject "^4.0.6" uuid "^3.3.3" @@ -2189,7 +2195,7 @@ prop-types "^15.7.2" react-is "^16.8.0" -"@material-ui/styles@^4.10.0", "@material-ui/styles@^4.9.0": +"@material-ui/styles@^4.10.0": version "4.10.0" resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071" integrity sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==