forked from cerc-io/laconic-console
Resolve hook problems.
Fixed logging.
This commit is contained in:
parent
bc177cc1a1
commit
38efa6a030
@ -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).
|
||||
|
@ -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",
|
||||
|
@ -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 (
|
||||
<Snackbar
|
||||
className={classes.root}
|
||||
classes={{ root: classes.root }}
|
||||
open={Boolean(message)}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
TransitionProps={{ exit: false }}
|
||||
autoHideDuration={1000}
|
||||
>
|
||||
<Alert severity="error" {...rest}>
|
||||
<Alert classes={{ root: classes.alert }} severity="error" {...rest} onClose={handleClose}>
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
{messages.map((message, i) => (
|
||||
<div key={i}>{message}</div>
|
||||
))}
|
||||
<div>{message}</div>
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
);
|
||||
|
@ -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 = (
|
||||
<Fragment>
|
||||
<>
|
||||
<span className={classes.ts}>{datetime}</span>
|
||||
<span className={clsx(classes.level, className)}>{label || level}</span>
|
||||
<span>{pkg}{text}</span>
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
|
||||
return true;
|
||||
@ -111,10 +108,8 @@ const Log = ({ log = [], onClear }) => {
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.container}>
|
||||
<div className={classes.log}>
|
||||
{log.reverse().map((line, i) => <Line key={i} message={line} />)}
|
||||
</div>
|
||||
<div className={classes.log}>
|
||||
{log.map((line, i) => <Line key={i} message={line} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -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(
|
||||
<Fragment>
|
||||
<Link
|
||||
key={cid}
|
||||
href={ipfsUrl}
|
||||
title={cid}
|
||||
target="ipfs"
|
||||
>
|
||||
{platform}/{arch}: {cid}
|
||||
</Link>
|
||||
</Fragment>
|
||||
<Link
|
||||
key={`${cid}`}
|
||||
href={ipfsUrl}
|
||||
title={cid}
|
||||
target="ipfs"
|
||||
>
|
||||
{platform}/{arch}: {cid}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>{packageLinks}</Fragment>
|
||||
<>{packageLinks}</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -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 (
|
||||
<ApolloProvider client={clientFactory(config)}>
|
||||
<ConsoleContextProvider config={config} modules={modules}>
|
||||
<ConsoleContextProvider config={config} modules={modules} errorHandler={errorHandler}>
|
||||
<ThemeProvider theme={createTheme(config.app.theme)}>
|
||||
<CssBaseline />
|
||||
<HashRouter>
|
||||
|
@ -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 (
|
||||
<ErrorIcon className={clsx(classes.icon, classes.error)} title={String(error)} />
|
||||
<ErrorIcon className={clsx(classes.icon, classes.error)} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
@ -100,24 +100,28 @@ const StatusBar = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Toolbar className={classes.root}>
|
||||
<div className={classes.left}>
|
||||
<Link className={classes.link} href={config.app.website} rel="noreferrer" target="_blank">
|
||||
<PublicIcon />
|
||||
</Link>
|
||||
</div>
|
||||
<>
|
||||
<Toolbar className={classes.root}>
|
||||
<div className={classes.left}>
|
||||
<Link className={classes.link} href={config.app.website} rel="noreferrer" target="_blank">
|
||||
<PublicIcon />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className={classes.info}>
|
||||
<div>{name} ({version})</div>
|
||||
<div>{moment(buildDate).format('L')}</div>
|
||||
<VersionCheck />
|
||||
</div>
|
||||
<div className={classes.center}>
|
||||
<div>{name} ({version})</div>
|
||||
<div>{moment(buildDate).format('L')}</div>
|
||||
<VersionCheck />
|
||||
</div>
|
||||
|
||||
<div className={classes.right}>
|
||||
<LoadingIcon className={clsx(classes.icon, isLoading && classes.loading)} />
|
||||
<StatusIcon error={error} />
|
||||
</div>
|
||||
</Toolbar>
|
||||
<div className={classes.right}>
|
||||
<LoadingIcon className={clsx(classes.icon, isLoading && classes.loading)} />
|
||||
<StatusIcon error={error} />
|
||||
</div>
|
||||
</Toolbar>
|
||||
|
||||
<Error error={error} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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(() => ({
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
20
yarn.lock
20
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==
|
||||
|
Loading…
Reference in New Issue
Block a user