Local resolver.

This commit is contained in:
richburdon 2020-05-23 22:37:52 -04:00
parent b24fdfc57c
commit ead184c3a9
23 changed files with 960 additions and 71 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

View File

@ -6,11 +6,13 @@ Apollo GraphQL client and server using express.
### POC
- [ ] Test backend IPFS client request.
- [ ] Trigger server-side wire commands (separate express path?)
### Next
- [ ] Routes.
- [ ] Fix JsonTree (yarn link).
- [ ] Client/server API abstraction (error handler, etc.)
- [ ] Webpack config (remove dynamic config?)
- [ ] https://github.com/standard/standardx (JSX)
- [ ] Shared config.
@ -18,6 +20,8 @@ Apollo GraphQL client and server using express.
### Done
- [c] Client resolvers: https://www.apollographql.com/docs/tutorial/local-state/
- [x] Test backend IPFS client request.
- [x] Hash Router.
- [x] Layout (with Material UI).
- [x] config from provider.

View File

@ -1,14 +0,0 @@
{
"app": {
"org": "DxOS",
"theme": "dark",
"title": "Console",
"website": "https://dxos.org",
"publicUrl": "/console"
},
"graphql": {
"path": "/api",
"port": 4000,
"pollInterval": 10000
}
}

View File

@ -0,0 +1,43 @@
#
# NODE_ENV === production
# NOTE: Set CONFIG_FILE to swap out this config file.
#
app:
title: 'Console'
org': 'DxOS'
theme: 'dark'
website: 'https://dxos.org'
publicUrl: '/console'
api:
path: '/api'
port: 4000
pollInterval: 10000
system:
debug: 'dxos:console:*'
xbox:
image: '/opt/xbox/IMAGE'
services:
app:
server: 'http://127.0.0.1:5999' # TODO(burdon): ???
wns:
server: 'https://node1.dxos.network/wns/api'
webui: 'https://node1.dxos.network/wns/webui'
# server: 'http://127.0.0.1:9473/api'
# webui: 'http://127.0.0.1:9473/webui'
signal:
server: 'http://127.0.0.1:4000'
api: 'http://127.0.0.1:4000' # TODO(burdon): ???
ipfs:
server: '/ip4/127.0.0.1/tcp/5001'
gateway: '/ip4//127.0.0.1:8888/ipfs/'
webui: 'http://127.0.0.1:5001/webui'
wellknown:
endpoint: 'http://127.0.0.1:9000/.well-known/dxos'

View File

@ -0,0 +1,9 @@
#
# Copyright 2020 DxOS
#
{
ipfs {
json
}
}

View File

@ -0,0 +1,9 @@
#
# Copyright 2020 DxOS
#
{
wns {
json @client
}
}

View File

@ -4,7 +4,7 @@
"description": "DxOS Console Client",
"main": "dist/es/index.js",
"files": [
"config.json",
"config.yml",
"dist/production",
"gql"
],
@ -33,6 +33,7 @@
"@dxos/react-ux": "^1.0.0-beta.20",
"@material-ui/core": "^4.10.0",
"@material-ui/icons": "^4.9.1",
"@wirelineio/registry-client": "^0.4.8",
"apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.10",
"apollo-link-http": "^1.5.17",
@ -83,7 +84,8 @@
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2",
"webpack-version-file-plugin": "^0.4.0"
"webpack-version-file-plugin": "^0.4.0",
"yaml-loader": "^0.6.0"
},
"publishConfig": {
"access": "public"

View File

@ -6,26 +6,39 @@ import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createResolvers } from './resolvers';
const defaultServer = `${window.location.protocol}//${window.location.hostname}`;
export const graphqlApi = config => {
const { graphql: { server = defaultServer, port = 80, path = '/graphql' } } = config;
const { api: { server = defaultServer, port = 80, path = '/graphql' } } = config;
return `${server}:${port}${path}`;
};
// https://www.apollographql.com/docs/react/api/apollo-client/
/**
* Craetes an Apollo client.
* @param {Object} config
* @returns {ApolloClient}
*/
export const clientFactory = config => {
// TODO(burdon): Authentication: send signed message to server (from client wallet).
// https://www.apollographql.com/docs/react/networking/authentication/
// https://www.apollographql.com/docs/link/
const link = createHttpLink({
uri: graphqlApi(config)
uri: graphqlApi(config),
// TODO(burdon): Authentication: send signed message to server (from client wallet).
// https://www.apollographql.com/docs/react/networking/authentication/
headers: {
authorization: 'HALO_TOKEN'
}
});
// https://www.apollographql.com/docs/react/api/apollo-client/
return new ApolloClient({
connectToDevTools: true,
cache: new InMemoryCache(),
resolvers: createResolvers(config),
link
});
};

View File

@ -23,6 +23,7 @@ const removeTypename = data => transform(data, (result, value, key) => {
const Json = ({ data }) => {
const classes = useStyles();
// TODO(burdon): Bug expands when updated.
return (
<JsonTreeView className={classes.root} data={removeTypename(data)} />
);

View File

@ -22,7 +22,8 @@ const useStyles = makeStyles((theme) => ({
container: {
display: 'flex',
flexDirection: 'row',
flex: 1
flex: 1,
overflow: 'hidden'
},
sidebar: {
display: 'flex',
@ -33,7 +34,8 @@ const useStyles = makeStyles((theme) => ({
},
main: {
display: 'flex',
flex: 1
flex: 1,
overflow: 'hidden'
},
cooter: {
display: 'flex',

View File

@ -0,0 +1,27 @@
//
// Copyright 2020 DxOS
//
import React, { useContext } from 'react';
import { useQuery } from '@apollo/react-hooks';
import Json from '../components/Json';
import { ConsoleContext, useQueryStatusReducer } from '../hooks';
import QUERY from '../../gql/ipfs.graphql';
const IPFS = () => {
const { config } = useContext(ConsoleContext);
const data = useQueryStatusReducer(useQuery(QUERY, { pollInterval: config.api.pollInterval }));
if (!data) {
return null;
}
// TODO(burdon): Return structured GraphQL.
return (
<Json data={{ ipfs: JSON.parse(data.ipfs.json) }} />
);
};
export default IPFS;

View File

@ -2,13 +2,14 @@
// Copyright 2020 DxOS
//
import debug from 'debug';
import React from 'react';
import { HashRouter, Redirect, Route, Switch } from 'react-router-dom';
import { ApolloProvider } from '@apollo/react-hooks';
import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import config from '../../config.json';
import config from '../../config.yml';
import { createTheme } from '../theme';
import { clientFactory } from '../client';
@ -19,7 +20,11 @@ import Layout from '../components/Layout';
import ConsoleContextProvider from './ConsoleContextProvider';
import IPFS from './IPFS';
import Status from './Status';
import WNS from './WNS';
debug.enable(config.system.debug);
const Main = () => {
return (
@ -31,8 +36,10 @@ const Main = () => {
<Switch>
<Route path="/:module">
<Layout>
<Route path="/status" component={Status} />
<Route path="/config" component={Config} />
<Route path="/ipfs" component={IPFS} />
<Route path="/status" component={Status} />
<Route path="/wns" component={WNS} />
</Layout>
</Route>
<Redirect to="/status" />

View File

@ -2,9 +2,6 @@
// Copyright 2020 DxOS
//
import isObject from 'lodash.isobject';
import omit from 'lodash.omit';
import transform from 'lodash.transform';
import React, { useContext } from 'react';
import { useQuery } from '@apollo/react-hooks';
@ -12,22 +9,17 @@ import Json from '../components/Json';
import { ConsoleContext, useQueryStatusReducer } from '../hooks';
import QUERY_STATUS from '../../gql/status.graphql';
const removeTypename = data => transform(data, (result, value, key) => {
result[key] = isObject(value) && '__typename' in value ? omit(value, '__typename') : value;
});
import QUERY from '../../gql/status.graphql';
const Status = () => {
const { config } = useContext(ConsoleContext);
const data = useQueryStatusReducer(useQuery(QUERY_STATUS, { pollInterval: config.graphql.pollInterval }));
const data = useQueryStatusReducer(useQuery(QUERY, { pollInterval: config.api.pollInterval }));
if (!data) {
return null;
}
return (
<Json data={removeTypename(data)} />
<Json data={data} />
);
};

View File

@ -0,0 +1,45 @@
//
// Copyright 2020 DxOS
//
import React, { useContext } from 'react';
import { useQuery } from '@apollo/react-hooks';
import { makeStyles } from '@material-ui/core';
import Json from '../components/Json';
import { ConsoleContext, useQueryStatusReducer } from '../hooks';
import QUERY from '../../gql/wns.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(QUERY, { pollInterval: config.api.pollInterval }));
if (!data) {
return null;
}
// TODO(burdon): peers causes issues.
// Warning: Failed prop type: Invalid prop `children` supplied to `ForwardRef(Typography)`, expected a ReactNode.
const d = JSON.parse(data.wns.json);
d.peers = [];
// TODO(burdon): Return structured GraphQL.
return (
<div className={classes.root}>
<Json data={{ wns: d }} />
</div>
);
};
export default WNS;

View File

@ -2,12 +2,9 @@
// Copyright 2020 DxOS
//
import debug from 'debug';
import React from 'react';
import { render } from 'react-dom';
import Main from './containers/Main';
debug.enable('dxos:console:client:*');
render(<Main />, document.getElementById('root'));

View File

@ -0,0 +1,36 @@
//
// Copyright 2020 DxOS
//
import debug from 'debug';
import { Registry } from '@wirelineio/registry-client';
const log = debug('dxos:console:client:resolvers');
//
// Resolvers
// https://www.apollographql.com/docs/tutorial/local-state/#local-resolvers
//
export const createResolvers = config => {
// TODO(burdon): Get route if served from xbox.
const { services: { wns: { server } } } = config;
const registry = new Registry(server);
return {
Query: {
wns: async () => {
log('Querying WNS...');
const status = await registry.getStatus();
return {
__typename: 'JSONResult',
json: JSON.stringify(status)
};
}
}
};
};

View File

@ -1,7 +1,7 @@
{
"build": {
"name": "@dxos/console-client",
"buildDate": "2020-05-24T00:17:36.206Z",
"buildDate": "2020-05-24T02:00:10.452Z",
"version": "1.0.0-beta.0"
}
}

View File

@ -103,11 +103,18 @@ module.exports = {
}
},
// https://www.apollographql.com/docs/react/integrations/webpack/
// https://github.com/eemeli/yaml-loader
{
test: /\.ya?ml$/,
type: 'json',
use: 'yaml-loader'
},
// https://www.apollographql.com/docs/react/integrations/webpack
{
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
loader: 'graphql-tag/loader',
loader: 'graphql-tag/loader'
},
// fonts

View File

@ -6,7 +6,7 @@
"scripts": {
"lint": "semistandard 'src/**/*.js'",
"test": "jest --rootDir ./src --passWithNoTests --no-cache",
"start": "nodemon --exec babel-node ./src/main.js"
"start": "BABEL_DISABLE_CACHE=1 nodemon --exec babel-node ./src/main.js"
},
"author": "DxOS.org",
"license": "GPL-3.0",
@ -26,6 +26,8 @@
"express-graphql": "^0.9.0",
"graphql": "^15.0.0",
"graphql-tag": "^2.10.3",
"ipfs-http-client": "^44.1.0",
"js-yaml": "^3.14.0",
"react-dom": "^16.13.1",
"source-map-support": "^0.5.12"
},
@ -41,9 +43,9 @@
"babel-plugin-add-module-exports": "^1.0.2",
"babel-plugin-inline-import": "^3.0.0",
"eslint": "^6.7.2",
"eslint-loader": "^3.0.3",
"eslint-config-semistandard": "^15.0.0",
"eslint-config-standard": "^14.1.1",
"eslint-loader": "^3.0.3",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jest": "^23.13.1",

View File

@ -2,11 +2,21 @@
# Copyright 2020 DxOS
#
type JSONResult {
json: String!
}
type Status {
timestamp: String
version: String
timestamp: String!
version: String!
}
type Query {
status: Status
ipfs: JSONResult
wns: JSONResult
}
schema {
query: Query
}

View File

@ -4,21 +4,24 @@
import debug from 'debug';
import express from 'express';
import fs from 'fs';
import path from 'path';
import yaml from 'js-yaml';
import { ApolloServer, gql } from 'apollo-server-express';
import { print } from 'graphql/language';
import QUERY_STATUS from '@dxos/console-client/gql/status.graphql';
import config from '@dxos/console-client/config.json';
import { resolvers } from './resolvers';
import SCHEMA from './gql/api.graphql';
const config = yaml.safeLoad(
fs.readFileSync(path.join(__dirname, '../../../node_modules/@dxos/console-client/config.yml')));
const log = debug('dxos:console:server');
// TODO(burdon): Config.
debug.enable('dxos:console:*');
debug.enable(config.system.debug);
//
// Express server.
@ -69,7 +72,7 @@ const server = new ApolloServer({
tabs: [
{
name: 'Status',
endpoint: config.graphql.path,
endpoint: config.api.path,
query: print(gql(QUERY_STATUS))
}
]
@ -83,13 +86,14 @@ const server = new ApolloServer({
server.applyMiddleware({
app,
path: config.graphql.path
path: config.api.path
});
//
// Start server
//
app.listen({ port: config.graphql.port }, () => {
log(`Running: http://localhost:${config.graphql.port}`);
const { api: { port } } = config;
app.listen({ port }, () => {
log(`Running: http://localhost:${port}`);
});

View File

@ -2,21 +2,48 @@
// Copyright 2020 DxOS
//
// import debug from 'debug';
import debug from 'debug';
import IpfsHttpClient from 'ipfs-http-client';
import { version } from '../package.json';
// const log = debug('dxos:console:resolver');
const log = debug('dxos:console:server:resolvers');
//
// Resolver
// Resolvers
//
export const resolvers = {
Query: {
//
// Status
//
status: () => ({
timestamp: new Date().toUTCString(),
version
})
}),
//
// IPFS
// TODO(burdon): Call from client?
// https://github.com/ipfs/js-ipfs
// https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-client#api
//
ipfs: async () => {
log('Calling IPFS...');
// TODO(burdon): Config.
const ipfs = new IpfsHttpClient('/ip4/127.0.0.1/tcp/5001');
const version = await ipfs.version();
const status = await ipfs.id();
return {
json: JSON.stringify({
version,
status
})
};
}
}
};

686
yarn.lock

File diff suppressed because it is too large Load Diff