forked from cerc-io/laconic-console
Merge pull request #1 from dxos/burdon/express
Serves react app from server.
This commit is contained in:
commit
e3c548cbb2
3
.gitignore
vendored
3
.gitignore
vendored
@ -65,3 +65,6 @@ dist/
|
|||||||
|
|
||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
.idea/workspace.xml
|
||||||
|
|
||||||
|
12
.idea/console.iml
Normal file
12
.idea/console.iml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
11
.idea/inspectionProfiles/Project_Default.xml
Normal file
11
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
.idea/jsLibraryMappings.xml
Normal file
6
.idea/jsLibraryMappings.xml
Normal 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>
|
6
.idea/misc.xml
Normal file
6
.idea/misc.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="JSX" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/console.iml" filepath="$PROJECT_DIR$/.idea/console.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
39
README.md
39
README.md
@ -4,14 +4,39 @@ Apollo GraphQL client and server using express.
|
|||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
||||||
- [ ] Server React app from server.
|
### POC
|
||||||
- [ ] Trigger server-side commands (separate express path?)
|
|
||||||
|
|
||||||
- [ ] Material UI.
|
- [ ] Complete WNS functionality
|
||||||
- [ ] Router.
|
- [ ] Logging
|
||||||
- [ ] Shared config.
|
- [ ] Webpack and dynamic config
|
||||||
- [ ] IPFS request.
|
- [ ] Routes for services
|
||||||
- [ ] Port modules with dummy resolvers.
|
- [ ] Trigger server-side wire commands
|
||||||
|
- [ ] Test on device in production.
|
||||||
|
|
||||||
|
- [ ] IPFS
|
||||||
|
- [ ] Signal
|
||||||
|
- [ ] Apps
|
||||||
|
- [ ] Bots
|
||||||
|
- [ ] Meta
|
||||||
|
|
||||||
|
### Next
|
||||||
|
|
||||||
|
- [ ] Client/server API abstraction (error handler, etc.)
|
||||||
|
- [ ] Port dashboard API calls (resolve config first).
|
||||||
|
- [ ] Port dashboard react modules with dummy resolvers.
|
||||||
|
|
||||||
|
- [ ] https://github.com/standard/standardx (JSX)
|
||||||
|
|
||||||
|
### Done
|
||||||
|
|
||||||
|
- [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).
|
||||||
|
- [x] config from provider.
|
||||||
|
- [x] Error boundary.
|
||||||
|
- [x] Server React app from server.
|
||||||
|
- https://www.freecodecamp.org/news/how-to-set-up-deploy-your-react-app-from-scratch-using-webpack-and-babel-a669891033d4/
|
||||||
- [x] Monorepo for client/server.
|
- [x] Monorepo for client/server.
|
||||||
- [x] Basic React/Apollo component.
|
- [x] Basic React/Apollo component.
|
||||||
|
14
package.json
14
package.json
@ -32,21 +32,23 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-config-airbnb": "^18.0.0",
|
|
||||||
"eslint-loader": "^3.0.3",
|
"eslint-loader": "^3.0.3",
|
||||||
|
"eslint-config-semistandard": "^15.0.0",
|
||||||
|
"eslint-config-standard": "^14.1.1",
|
||||||
|
"eslint-plugin-babel": "^5.3.0",
|
||||||
"eslint-plugin-import": "^2.18.2",
|
"eslint-plugin-import": "^2.18.2",
|
||||||
|
"eslint-plugin-jest": "^23.13.1",
|
||||||
"eslint-plugin-jsdoc": "^21.0.0",
|
"eslint-plugin-jsdoc": "^21.0.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
"lint-staged": "^9.5.0",
|
"lint-staged": "^9.5.0",
|
||||||
"pre-commit": "^1.2.2",
|
"pre-commit": "^1.2.2"
|
||||||
"semistandard": "^14.2.0"
|
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"parser": "babel-eslint",
|
"parser": "babel-eslint",
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:jest/recommended",
|
"plugin:jest/recommended",
|
||||||
"semistandard",
|
"semistandard"
|
||||||
"standard-jsx"
|
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"babel"
|
"babel"
|
||||||
|
47
packages/console-client/README.md
Normal file
47
packages/console-client/README.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Console
|
||||||
|
|
||||||
|
Apollo GraphQL client.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
First start the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/consoe-server
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
Then start the Webpack devserver.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/consoe-client
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
Then load the app: http://localhost:8080.
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates the following folders:
|
||||||
|
|
||||||
|
```
|
||||||
|
/dist
|
||||||
|
/es # Module imports.
|
||||||
|
/production # Production build.
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: GQL and Production files and exported and may be used by the server.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import QUERY_STATUS from '@dxos/console-client/gql/system_status.graphql';
|
||||||
|
import config from '@dxos/console-client/config.json';
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
const file = path.join(__dirname + '../../../../node_modules/@dxos/console-client/dist/production', 'index.html');
|
||||||
|
res.sendFile(file);
|
||||||
|
```
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2020 DxOS
|
// Copyright 2020 DxOS.org
|
||||||
//
|
//
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"port": 4000,
|
|
||||||
"path": "/graphql"
|
|
||||||
}
|
|
44
packages/console-client/config.yml
Normal file
44
packages/console-client/config.yml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#
|
||||||
|
# NODE_ENV === production
|
||||||
|
# NOTE: Set CONFIG_FILE to swap out this config file.
|
||||||
|
#
|
||||||
|
|
||||||
|
app:
|
||||||
|
title: 'Console'
|
||||||
|
org': 'DxOS'
|
||||||
|
theme: 'light'
|
||||||
|
website: 'https://dxos.org'
|
||||||
|
publicUrl: '/console'
|
||||||
|
|
||||||
|
api:
|
||||||
|
path: '/api'
|
||||||
|
port: 4000
|
||||||
|
pollInterval: 10000
|
||||||
|
|
||||||
|
system:
|
||||||
|
debug: 'dxos:console:*'
|
||||||
|
xbox:
|
||||||
|
image: '/opt/xbox/IMAGE'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
prefix: '/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'
|
9
packages/console-client/gql/ipfs_status.graphql
Normal file
9
packages/console-client/gql/ipfs_status.graphql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2020 DxOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
{
|
||||||
|
ipfs_status {
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
10
packages/console-client/gql/system_status.graphql
Normal file
10
packages/console-client/gql/system_status.graphql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2020 DxOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
{
|
||||||
|
system_status {
|
||||||
|
timestamp
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
10
packages/console-client/gql/wns_action.graphql
Normal file
10
packages/console-client/gql/wns_action.graphql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2020 DxOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
mutation ($command: String!) {
|
||||||
|
wns_action(command: $command) {
|
||||||
|
timestamp
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
10
packages/console-client/gql/wns_log.graphql
Normal file
10
packages/console-client/gql/wns_log.graphql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2020 DxOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
{
|
||||||
|
wns_log @client {
|
||||||
|
timestamp
|
||||||
|
log
|
||||||
|
}
|
||||||
|
}
|
10
packages/console-client/gql/wns_records.graphql
Normal file
10
packages/console-client/gql/wns_records.graphql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2020 DxOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
query ($type: String) {
|
||||||
|
wns_records (type: $type) @client {
|
||||||
|
timestamp
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
10
packages/console-client/gql/wns_status.graphql
Normal file
10
packages/console-client/gql/wns_status.graphql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2020 DxOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
{
|
||||||
|
wns_status @client {
|
||||||
|
timestamp
|
||||||
|
json
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,16 @@
|
|||||||
"version": "1.0.0-beta.0",
|
"version": "1.0.0-beta.0",
|
||||||
"description": "DxOS Console Client",
|
"description": "DxOS Console Client",
|
||||||
"main": "dist/es/index.js",
|
"main": "dist/es/index.js",
|
||||||
|
"files": [
|
||||||
|
"config.yml",
|
||||||
|
"dist/production",
|
||||||
|
"gql"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyzer": "webpack --config webpack-analyzer.config.js",
|
"analyzer": "webpack --config webpack-analyzer.config.js",
|
||||||
"build": "npm run clean && babel ./src --out-dir ./dist/es --ignore \"**/*.test.js\" --source-maps inline",
|
"build": "npm run clean && npm run build:module && npm run build:production",
|
||||||
|
"build:module": "babel ./src --out-dir ./dist/es --ignore \"**/*.test.js\" --source-maps inline",
|
||||||
|
"build:production": "PUBLIC_URL=/console webpack --mode production",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"lint": "semistandard 'src/**/*.js'",
|
"lint": "semistandard 'src/**/*.js'",
|
||||||
"start": "VERBOSE=true webpack-dev-server --mode development",
|
"start": "VERBOSE=true webpack-dev-server --mode development",
|
||||||
@ -20,13 +27,33 @@
|
|||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@apollo/react-components": "^3.1.5",
|
||||||
"@apollo/react-hooks": "^3.1.5",
|
"@apollo/react-hooks": "^3.1.5",
|
||||||
"@babel/runtime": "^7.8.7",
|
"@babel/runtime": "^7.8.7",
|
||||||
"apollo-boost": "^0.4.9",
|
"@dxos/gem-core": "^1.0.0-beta.11",
|
||||||
|
"@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",
|
||||||
|
"apollo-link-http": "^1.5.17",
|
||||||
|
"build-url": "^2.0.0",
|
||||||
|
"clsx": "^1.1.0",
|
||||||
|
"compare-versions": "^3.6.0",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"graphql-tag": "^2.10.3",
|
"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",
|
||||||
|
"moment": "^2.26.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
|
"react-router": "^5.2.0",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
"source-map-support": "^0.5.12"
|
"source-map-support": "^0.5.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -34,7 +61,7 @@
|
|||||||
"@babel/core": "^7.4.5",
|
"@babel/core": "^7.4.5",
|
||||||
"@babel/node": "^7.8.7",
|
"@babel/node": "^7.8.7",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.5.2",
|
"@babel/plugin-proposal-export-default-from": "^7.8.3",
|
||||||
"@babel/preset-env": "^7.4.5",
|
"@babel/preset-env": "^7.4.5",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"babel-eslint": "^10.0.2",
|
"babel-eslint": "^10.0.2",
|
||||||
@ -43,20 +70,52 @@
|
|||||||
"babel-plugin-add-module-exports": "^1.0.2",
|
"babel-plugin-add-module-exports": "^1.0.2",
|
||||||
"babel-plugin-inline-import": "^3.0.0",
|
"babel-plugin-inline-import": "^3.0.0",
|
||||||
"dotenv-webpack": "^1.8.0",
|
"dotenv-webpack": "^1.8.0",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-config-semistandard": "^15.0.0",
|
||||||
|
"eslint-config-standard": "^14.1.1",
|
||||||
|
"eslint-config-standard-jsx": "^8.1.0",
|
||||||
|
"eslint-loader": "^3.0.3",
|
||||||
"eslint-plugin-babel": "^5.3.0",
|
"eslint-plugin-babel": "^5.3.0",
|
||||||
|
"eslint-plugin-import": "^2.18.2",
|
||||||
"eslint-plugin-jest": "^23.13.1",
|
"eslint-plugin-jest": "^23.13.1",
|
||||||
"eslint-plugin-react": "^7.17.0",
|
"eslint-plugin-jsdoc": "^21.0.0",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
"html-webpack-plugin": "^4.3.0",
|
"html-webpack-plugin": "^4.3.0",
|
||||||
"jest": "^24.8.0",
|
"jest": "^24.8.0",
|
||||||
|
"react-scripts": "^3.4.1",
|
||||||
"semistandard": "^14.2.0",
|
"semistandard": "^14.2.0",
|
||||||
"webpack": "^4.41.2",
|
"webpack": "^4.41.2",
|
||||||
"webpack-bundle-analyzer": "^3.6.0",
|
"webpack-bundle-analyzer": "^3.6.0",
|
||||||
"webpack-dev-server": "^3.11.0",
|
|
||||||
"webpack-cli": "^3.3.10",
|
"webpack-cli": "^3.3.10",
|
||||||
|
"webpack-dev-server": "^3.11.0",
|
||||||
"webpack-merge": "^4.2.2",
|
"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": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"extends": [
|
||||||
|
"plugin:jest/recommended",
|
||||||
|
"semistandard",
|
||||||
|
"standard-jsx"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"babel"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"babel/semi": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"semistandard": {
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"env": [
|
||||||
|
"jest",
|
||||||
|
"node",
|
||||||
|
"browser"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
packages/console-client/src/client.js
Normal file
44
packages/console-client/src/client.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
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 { api: { server = defaultServer, port = 80, path = '/graphql' } } = config;
|
||||||
|
|
||||||
|
return `${server}:${port}${path}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Craetes an Apollo client.
|
||||||
|
* @param {Object} config
|
||||||
|
* @returns {ApolloClient}
|
||||||
|
*/
|
||||||
|
export const clientFactory = config => {
|
||||||
|
|
||||||
|
// https://www.apollographql.com/docs/link/
|
||||||
|
const link = createHttpLink({
|
||||||
|
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
|
||||||
|
});
|
||||||
|
};
|
@ -1,26 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2020 DxOS
|
|
||||||
//
|
|
||||||
|
|
||||||
import { useQuery } from '@apollo/react-hooks';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import QUERY_STATUS from '../gql/status.graphql';
|
|
||||||
|
|
||||||
const App = () => {
|
|
||||||
const { loading, error, data } = useQuery(QUERY_STATUS);
|
|
||||||
if (loading) {
|
|
||||||
return <div>Loading...</div>;
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
return <div>Error: ${error}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<pre>
|
|
||||||
{JSON.stringify(data, undefined, 2)}
|
|
||||||
</pre>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
76
packages/console-client/src/components/AppBar.js
Normal file
76
packages/console-client/src/components/AppBar.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
import MuiAppBar from '@material-ui/core/AppBar';
|
||||||
|
import Link from '@material-ui/core/Link';
|
||||||
|
import Toolbar from '@material-ui/core/Toolbar';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import blueGrey from '@material-ui/core/colors/blueGrey';
|
||||||
|
import GraphQLIcon from '@material-ui/icons/Adb';
|
||||||
|
|
||||||
|
import DxOSIcon from '../icons/DXOS';
|
||||||
|
import { graphqlApi } from '../client';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
offset: theme.mixins.denseToolbar,
|
||||||
|
|
||||||
|
logo: {
|
||||||
|
paddingBottom: 2,
|
||||||
|
marginTop: 4,
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
color: '#333',
|
||||||
|
|
||||||
|
'& svg': {
|
||||||
|
width: 64,
|
||||||
|
height: 32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
title: {
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
|
||||||
|
link: {
|
||||||
|
color: blueGrey[900]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const AppBar = ({ config }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MuiAppBar position="fixed">
|
||||||
|
<Toolbar>
|
||||||
|
<Link href="/">
|
||||||
|
<div className={classes.logo}>
|
||||||
|
<DxOSIcon />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<div className={classes.title}>
|
||||||
|
<Typography variant="h6">{config.app.title}</Typography>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
className={classes.link}
|
||||||
|
href={graphqlApi(config)}
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
title="Console GraphQL"
|
||||||
|
>
|
||||||
|
<GraphQLIcon />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</Toolbar>
|
||||||
|
</MuiAppBar>
|
||||||
|
|
||||||
|
<div className={classes.offset} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppBar;
|
35
packages/console-client/src/components/ControlButtons.js
Normal file
35
packages/console-client/src/components/ControlButtons.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
{onStart && (
|
||||||
|
<IconButton onClick={onStart} title="Restart">
|
||||||
|
<StartIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{onStop && (
|
||||||
|
<IconButton onClick={onStop} title="Stop">
|
||||||
|
<StopIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{onOpen && (
|
||||||
|
<IconButton onClick={onOpen} title="Open console">
|
||||||
|
<OpenIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ControlButtons;
|
42
packages/console-client/src/components/Error.js
Normal file
42
packages/console-client/src/components/Error.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Snackbar
|
||||||
|
className={classes.root}
|
||||||
|
open={Boolean(message)}
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
|
TransitionProps={{ exit: false }}
|
||||||
|
>
|
||||||
|
<Alert severity="error" {...rest}>
|
||||||
|
<AlertTitle>Error</AlertTitle>
|
||||||
|
{messages.map((message, i) => (
|
||||||
|
<div key={i}>{message}</div>
|
||||||
|
))}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Error;
|
57
packages/console-client/src/components/ErrorBoundary.js
Normal file
57
packages/console-client/src/components/ErrorBoundary.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root-level error boundary.
|
||||||
|
* https://reactjs.org/docs/error-boundaries.html
|
||||||
|
*
|
||||||
|
* NOTE: Must currently be a Component.
|
||||||
|
* https://reactjs.org/docs/hooks-faq.html#do-hooks-cover-all-use-cases-for-classes
|
||||||
|
*/
|
||||||
|
class ErrorBoundary extends Component {
|
||||||
|
static getDerivedStateFromError (error) {
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidCatch (error, errorInfo) {
|
||||||
|
const { onError } = this.props;
|
||||||
|
|
||||||
|
// TODO(burdon): Show error indicator.
|
||||||
|
// TODO(burdon): Logging service; output error file.
|
||||||
|
onError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { children } = this.props;
|
||||||
|
const { error } = this.state;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Typography>Error</Typography>
|
||||||
|
<pre>{String(error)}</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorBoundary.defaultProps = {
|
||||||
|
onError: console.warn
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ErrorBoundary;
|
39
packages/console-client/src/components/Json.js
Normal file
39
packages/console-client/src/components/Json.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import isObject from 'lodash.isobject';
|
||||||
|
import omit from 'lodash.omit';
|
||||||
|
import transform from 'lodash.transform';
|
||||||
|
import React from 'react';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
|
||||||
|
import { JsonTreeView } from '@dxos/react-ux';
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
root: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove Apollo __typename directive.
|
||||||
|
* @param {Object} data
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const removeTypename = data => transform(data, (result, value, key) => {
|
||||||
|
if (key !== '__typename') {
|
||||||
|
result[key] = isObject(value) ? ('__typename' in value ? omit(value, '__typename') : value) : value;
|
||||||
|
}
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const Json = ({ data }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
// TODO(burdon): Bug expands when updated.
|
||||||
|
return (
|
||||||
|
<JsonTreeView className={classes.root} data={removeTypename(data)} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Json;
|
120
packages/console-client/src/components/Log.js
Normal file
120
packages/console-client/src/components/Log.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
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 = (
|
||||||
|
<Fragment>
|
||||||
|
<span className={classes.ts}>{datetime}</span>
|
||||||
|
<span className={clsx(classes.level, className)}>{label || level}</span>
|
||||||
|
<span>{pkg}{text}</span>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>{message}</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Log;
|
57
packages/console-client/src/components/PackageLink.js
Normal file
57
packages/console-client/src/components/PackageLink.js
Normal file
@ -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 <Link href={ipfsUrl} target="ipfs">{pkg}</Link>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
<Fragment>
|
||||||
|
<Link
|
||||||
|
key={cid}
|
||||||
|
href={ipfsUrl}
|
||||||
|
title={cid}
|
||||||
|
target="ipfs"
|
||||||
|
>
|
||||||
|
{platform}/{arch}: {cid}
|
||||||
|
</Link>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>{packageLinks}</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PackageLink;
|
37
packages/console-client/src/components/Panel.js
Normal file
37
packages/console-client/src/components/Panel.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
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 (
|
||||||
|
<div className={classes.root}>
|
||||||
|
{toolbar}
|
||||||
|
<div className={classes.container}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Panel;
|
65
packages/console-client/src/components/Sidebar.js
Normal file
65
packages/console-client/src/components/Sidebar.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
import { useHistory, useParams } from 'react-router';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
import List from '@material-ui/core/List';
|
||||||
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
|
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
root: {
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
// backgroundColor: theme.palette.grey[100]
|
||||||
|
},
|
||||||
|
|
||||||
|
list: {
|
||||||
|
padding: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
icon: {
|
||||||
|
minWidth: 40,
|
||||||
|
color: theme.palette.grey[500]
|
||||||
|
},
|
||||||
|
|
||||||
|
selected: {
|
||||||
|
color: theme.palette.primary.main
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Sidebar = ({ modules: { services, settings } }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const history = useHistory();
|
||||||
|
const { module } = useParams();
|
||||||
|
|
||||||
|
const isSelected = path => path === `/${module}`;
|
||||||
|
|
||||||
|
const Modules = ({ modules }) => (
|
||||||
|
<List aria-label="items" className={classes.list}>
|
||||||
|
{modules.map(({ path, title, icon: Icon }) => (
|
||||||
|
<ListItem button selected={isSelected(path)} key={path} onClick={() => history.push(path)}>
|
||||||
|
<ListItemIcon classes={{ root: classes.icon }}>
|
||||||
|
<Icon className={clsx(classes.icon, isSelected(path) && classes.selected)} />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={title} />
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<Modules modules={services} />
|
||||||
|
<Modules modules={settings} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sidebar;
|
41
packages/console-client/src/components/Table.js
Normal file
41
packages/console-client/src/components/Table.js
Normal file
@ -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 (
|
||||||
|
<TableContainer className={classes.root}>
|
||||||
|
<MuiTable stickyHeader size="small" className={classes.table}>
|
||||||
|
{children}
|
||||||
|
</MuiTable>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Table;
|
38
packages/console-client/src/components/TableCell.js
Normal file
38
packages/console-client/src/components/TableCell.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// 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';
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
small: {
|
||||||
|
width: 160
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const TableCell = ({ children, size, monospace = false, title, ...rest }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MuiTableCell
|
||||||
|
{...rest}
|
||||||
|
className={clsx(size && classes[size])}
|
||||||
|
style={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
fontFamily: monospace ? 'monospace' : 'inherit',
|
||||||
|
fontSize: monospace ? 14 : 'inherit'
|
||||||
|
}}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</MuiTableCell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableCell;
|
32
packages/console-client/src/components/Toolbar.js
Normal file
32
packages/console-client/src/components/Toolbar.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
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',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
|
||||||
|
'& > button': {
|
||||||
|
margin: theme.spacing(0.5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO(burdon): Tabs.
|
||||||
|
const Toolbar = ({ children }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MuiToolbar disableGutters className={classes.toolbar}>
|
||||||
|
{children}
|
||||||
|
</MuiToolbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Toolbar;
|
@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React, { useEffect, useReducer } from 'react';
|
||||||
|
import defaultsDeep from 'lodash.defaultsdeep';
|
||||||
|
|
||||||
|
import ErrorBoundary from '../components/ErrorBoundary';
|
||||||
|
|
||||||
|
import { ConsoleContext, statusReducer, SET_STATUS } from '../hooks';
|
||||||
|
|
||||||
|
const defaultState = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions reducer.
|
||||||
|
* https://reactjs.org/docs/hooks-reference.html#usereducer
|
||||||
|
* @param {Object} state
|
||||||
|
* @param {string} action
|
||||||
|
*/
|
||||||
|
const appReducer = (state, action) => ({
|
||||||
|
// TODO(burdon): Key shouldn't be same as action type.
|
||||||
|
[SET_STATUS]: statusReducer(state[SET_STATUS], action)
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Console framework context, which provides the global UX state.
|
||||||
|
* Wraps children with a React ErrorBoundary component, which catches runtime errors and enables reset.
|
||||||
|
*
|
||||||
|
* @param {function} children
|
||||||
|
* @param {Object} config
|
||||||
|
* @param {Object} modules
|
||||||
|
* @param {Object} [initialState]
|
||||||
|
* @param {function} [errorHandler]
|
||||||
|
* @returns {function}
|
||||||
|
*/
|
||||||
|
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(() => {
|
||||||
|
errorHandler.on('error', error => {
|
||||||
|
dispatch({
|
||||||
|
type: SET_STATUS,
|
||||||
|
payload: {
|
||||||
|
exceptions: [error, ...exceptions]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConsoleContext.Provider value={{ config, modules, state, dispatch }}>
|
||||||
|
<ErrorBoundary>
|
||||||
|
{children}
|
||||||
|
</ErrorBoundary>
|
||||||
|
</ConsoleContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConsoleContextProvider;
|
74
packages/console-client/src/containers/Layout.js
Normal file
74
packages/console-client/src/containers/Layout.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
|
||||||
|
import { FullScreen } from '@dxos/gem-core';
|
||||||
|
|
||||||
|
import { ConsoleContext } from '../hooks';
|
||||||
|
|
||||||
|
import AppBar from '../components/AppBar';
|
||||||
|
import Sidebar from '../components/Sidebar';
|
||||||
|
import StatusBar from './StatusBar';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
flex: 1,
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flex: 1,
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
main: {
|
||||||
|
display: 'flex',
|
||||||
|
flex: 1,
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
flexShrink: 0,
|
||||||
|
width: 200,
|
||||||
|
borderRight: `1px solid ${theme.palette.divider}`
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexShrink: 0
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main layout for app.
|
||||||
|
*/
|
||||||
|
const Layout = ({ children }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { config, modules } = useContext(ConsoleContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullScreen>
|
||||||
|
<div className={classes.root}>
|
||||||
|
<AppBar config={config} />
|
||||||
|
<div className={classes.container}>
|
||||||
|
<div className={classes.sidebar}>
|
||||||
|
<Sidebar modules={modules} />
|
||||||
|
</div>
|
||||||
|
<div className={classes.main}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={classes.footer}>
|
||||||
|
<StatusBar />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FullScreen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
68
packages/console-client/src/containers/Main.js
Normal file
68
packages/console-client/src/containers/Main.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
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.yml';
|
||||||
|
import { build } from '../../version.json';
|
||||||
|
|
||||||
|
import { createTheme } from '../theme';
|
||||||
|
import { clientFactory } from '../client';
|
||||||
|
import modules from '../modules';
|
||||||
|
|
||||||
|
import Layout from './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/IPFS';
|
||||||
|
import Metadata from './panels/Metadata';
|
||||||
|
import Signaling from './panels/Signaling';
|
||||||
|
import Status from './panels/Status';
|
||||||
|
import WNS from './panels/wns/WNS';
|
||||||
|
|
||||||
|
// TODO(burdon): Config object.
|
||||||
|
Object.assign(config, { build });
|
||||||
|
|
||||||
|
debug.enable(config.system.debug);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root application.
|
||||||
|
*/
|
||||||
|
const Main = () => {
|
||||||
|
return (
|
||||||
|
<ApolloProvider client={clientFactory(config)}>
|
||||||
|
<ConsoleContextProvider config={config} modules={modules}>
|
||||||
|
<ThemeProvider theme={createTheme(config.app.theme)}>
|
||||||
|
<CssBaseline />
|
||||||
|
<HashRouter>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/:module">
|
||||||
|
<Layout>
|
||||||
|
<Route path="/apps" component={AppRecords} />
|
||||||
|
<Route path="/bots" component={Bots} />
|
||||||
|
<Route path="/config" component={Config} />
|
||||||
|
<Route path="/ipfs" component={IPFS} />
|
||||||
|
<Route path="/metadata" component={Metadata} />
|
||||||
|
<Route path="/signaling" component={Signaling} />
|
||||||
|
<Route path="/status" component={Status} />
|
||||||
|
<Route path="/wns" component={WNS} />
|
||||||
|
</Layout>
|
||||||
|
</Route>
|
||||||
|
<Redirect to="/status" />
|
||||||
|
</Switch>
|
||||||
|
</HashRouter>
|
||||||
|
</ThemeProvider>
|
||||||
|
</ConsoleContextProvider>
|
||||||
|
</ApolloProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Main;
|
122
packages/console-client/src/containers/StatusBar.js
Normal file
122
packages/console-client/src/containers/StatusBar.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import moment from 'moment';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
import Link from '@material-ui/core/Link';
|
||||||
|
import Toolbar from '@material-ui/core/Toolbar';
|
||||||
|
import ErrorIcon from '@material-ui/icons/Error';
|
||||||
|
import LoadingIcon from '@material-ui/icons/Wifi';
|
||||||
|
import RunningIcon from '@material-ui/icons/CheckCircle';
|
||||||
|
import PublicIcon from '@material-ui/icons/Public';
|
||||||
|
import grey from '@material-ui/core/colors/grey';
|
||||||
|
import green from '@material-ui/core/colors/green';
|
||||||
|
import red from '@material-ui/core/colors/red';
|
||||||
|
|
||||||
|
import { ConsoleContext, useStatusReducer } from '../hooks';
|
||||||
|
|
||||||
|
import VersionCheck from './VersionCheck';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
backgroundColor: grey[900],
|
||||||
|
color: grey[400]
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
width: 160
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
width: 160,
|
||||||
|
textAlign: 'right'
|
||||||
|
},
|
||||||
|
center: {
|
||||||
|
flex: 1,
|
||||||
|
textAlign: 'center'
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
margin: '0 2px'
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
color: grey[400]
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
color: red[500]
|
||||||
|
},
|
||||||
|
running: {
|
||||||
|
color: green[500]
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
color: theme.palette.primary.dark
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
display: 'flex',
|
||||||
|
'& div': {
|
||||||
|
margin: 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays status indicators at the bottom of the page.
|
||||||
|
*/
|
||||||
|
const StatusBar = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [{ loading, error }] = useStatusReducer();
|
||||||
|
const [isLoading, setLoading] = useState(loading);
|
||||||
|
const { config } = useContext(ConsoleContext);
|
||||||
|
const { build: { name, buildDate, version } } = config;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let t;
|
||||||
|
if (loading) {
|
||||||
|
setLoading(loading);
|
||||||
|
t = setTimeout(() => {
|
||||||
|
setLoading(false);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}, [loading]);
|
||||||
|
|
||||||
|
const StatusIcon = ({ error }) => {
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<ErrorIcon className={clsx(classes.icon, classes.error)} title={String(error)} />
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<RunningIcon className={clsx(classes.icon, classes.running)} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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.right}>
|
||||||
|
<LoadingIcon className={clsx(classes.icon, isLoading && classes.loading)} />
|
||||||
|
<StatusIcon error={error} />
|
||||||
|
</div>
|
||||||
|
</Toolbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusBar;
|
67
packages/console-client/src/containers/VersionCheck.js
Normal file
67
packages/console-client/src/containers/VersionCheck.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import compareVersions from 'compare-versions';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useQuery } from '@apollo/react-hooks';
|
||||||
|
import { makeStyles } from '@material-ui/core';
|
||||||
|
|
||||||
|
import SYSTEM_STATUS from '../../gql/system_status.graphql';
|
||||||
|
import WNS_RECORDS from '../../gql/wns_records.graphql';
|
||||||
|
|
||||||
|
import { useQueryStatusReducer } from '../hooks';
|
||||||
|
|
||||||
|
const CHECK_INTERVAL = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
update: {
|
||||||
|
color: theme.palette.error.light
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for a system upgrade.
|
||||||
|
*/
|
||||||
|
const VersionCheck = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [{ current, latest }, setUpgrade] = useState({});
|
||||||
|
const status = useQueryStatusReducer(useQuery(SYSTEM_STATUS));
|
||||||
|
const data = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
|
pollInterval: CHECK_INTERVAL,
|
||||||
|
variables: { type: 'wrn:resource' }
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Check version.
|
||||||
|
useEffect(() => {
|
||||||
|
if (status && data) {
|
||||||
|
const { dxos: { image: current } } = JSON.parse(status.system_status.json);
|
||||||
|
let latest = current;
|
||||||
|
data.wns_records.json.forEach(({ attributes: { name, version } }) => {
|
||||||
|
if (name.startsWith('dxos/xbox:')) {
|
||||||
|
if (compareVersions(version, latest) > 0) {
|
||||||
|
latest = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (latest !== current) {
|
||||||
|
setUpgrade({ current, latest });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [status, data]);
|
||||||
|
|
||||||
|
// TODO(burdon): Link to Github page with upgrade info.
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{current && (
|
||||||
|
<div>SYS: {current}</div>
|
||||||
|
)}
|
||||||
|
{latest && (
|
||||||
|
<div className={classes.update}>LATEST: {latest}</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VersionCheck;
|
27
packages/console-client/src/containers/panels/Config.js
Normal file
27
packages/console-client/src/containers/panels/Config.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Panel
|
||||||
|
toolbar={
|
||||||
|
<Toolbar />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Json data={config} />
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Config;
|
21
packages/console-client/src/containers/panels/Metadata.js
Normal file
21
packages/console-client/src/containers/panels/Metadata.js
Normal file
@ -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 (
|
||||||
|
<div className={classes.root}>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Signal;
|
21
packages/console-client/src/containers/panels/Signaling.js
Normal file
21
packages/console-client/src/containers/panels/Signaling.js
Normal file
@ -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 (
|
||||||
|
<div className={classes.root}>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Signaling;
|
35
packages/console-client/src/containers/panels/Status.js
Normal file
35
packages/console-client/src/containers/panels/Status.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { useQuery } from '@apollo/react-hooks';
|
||||||
|
|
||||||
|
import Json from '../../components/Json';
|
||||||
|
|
||||||
|
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);
|
||||||
|
const data = useQueryStatusReducer(useQuery(SYSTEM_STATUS, { pollInterval: config.api.pollInterval }));
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel
|
||||||
|
toolbar={
|
||||||
|
<Toolbar />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Json data={JSON.parse(data.system_status.json)} />
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Status;
|
@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// 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, useSorter } 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 [sorter, sortBy] = useSorter('id');
|
||||||
|
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): 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 (
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell onClick={sortBy('name')}>ID</TableCell>
|
||||||
|
<TableCell onClick={sortBy('version')} size="small">Version</TableCell>
|
||||||
|
<TableCell onClick={sortBy('attributes.displayName')}>Name</TableCell>
|
||||||
|
<TableCell>Link</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{records.sort(sorter).map(({ id, name, version, attributes: { displayName, publicUrl } }) => {
|
||||||
|
const link = getAppUrl({ id, name, version, publicUrl });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow key={id} size="small">
|
||||||
|
<TableCell monospace>{name}</TableCell>
|
||||||
|
<TableCell monospace>{version}</TableCell>
|
||||||
|
<TableCell>{displayName}</TableCell>
|
||||||
|
<TableCell monospace>
|
||||||
|
{link && (
|
||||||
|
<Link href={link} target={name}>{link}</Link>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppRecords;
|
42
packages/console-client/src/containers/panels/apps/Apps.js
Normal file
42
packages/console-client/src/containers/panels/apps/Apps.js
Normal file
@ -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 (
|
||||||
|
<Panel
|
||||||
|
toolbar={
|
||||||
|
<Toolbar>
|
||||||
|
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
|
||||||
|
<Tab value={TAB_RECORDS} label="Records" />
|
||||||
|
</Tabs>
|
||||||
|
</Toolbar>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{tab === TAB_RECORDS && (
|
||||||
|
<AppRecords />
|
||||||
|
)}
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Apps;
|
@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// 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, useSorter } 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 [sorter, sortBy] = useSorter('id');
|
||||||
|
const data = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
|
pollInterval: config.api.pollInterval,
|
||||||
|
variables: { type: 'wrn:bot' }
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = data.wns_records.json;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell onClick={sortBy('name')}>ID</TableCell>
|
||||||
|
<TableCell onClick={sortBy('version')} size="small">Version</TableCell>
|
||||||
|
<TableCell onClick={sortBy('attributes.displayName')}>Name</TableCell>
|
||||||
|
<TableCell />
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{records.sort(sorter).map(({ id, name, version, attributes: { displayName } }) => {
|
||||||
|
return (
|
||||||
|
<TableRow key={id} size="small">
|
||||||
|
<TableCell monospace>{name}</TableCell>
|
||||||
|
<TableCell monospace>{version}</TableCell>
|
||||||
|
<TableCell>{displayName}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppRecords;
|
42
packages/console-client/src/containers/panels/bots/Bots.js
Normal file
42
packages/console-client/src/containers/panels/bots/Bots.js
Normal file
@ -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 (
|
||||||
|
<Panel
|
||||||
|
toolbar={
|
||||||
|
<Toolbar>
|
||||||
|
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
|
||||||
|
<Tab value={TAB_RECORDS} label="Records" />
|
||||||
|
</Tabs>
|
||||||
|
</Toolbar>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{tab === TAB_RECORDS && (
|
||||||
|
<BotRecords />
|
||||||
|
)}
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Apps;
|
33
packages/console-client/src/containers/panels/ipfs/IPFS.js
Normal file
33
packages/console-client/src/containers/panels/ipfs/IPFS.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Panel
|
||||||
|
toolbar={
|
||||||
|
<Toolbar />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Json data={JSON.parse(data.ipfs_status.json)} />
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IPFS;
|
108
packages/console-client/src/containers/panels/wns/WNS.js
Normal file
108
packages/console-client/src/containers/panels/wns/WNS.js
Normal file
@ -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 (
|
||||||
|
<Panel
|
||||||
|
toolbar={
|
||||||
|
<Toolbar>
|
||||||
|
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
|
||||||
|
<Tab value={TAB_RECORDS} label="Records" />
|
||||||
|
<Tab value={TAB_STATUS} label="Status" />
|
||||||
|
<Tab value={TAB_LOG} label="Log" />
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
{tab === TAB_RECORDS && (
|
||||||
|
<WNSRecordType type={type} onChanged={setType} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={classes.expand} />
|
||||||
|
|
||||||
|
<Mutation mutation={WNS_ACTION}>
|
||||||
|
{(action, { data }) => (
|
||||||
|
<div>
|
||||||
|
<ControlButtons
|
||||||
|
onStart={() => {
|
||||||
|
action({ variables: { command: 'start' } });
|
||||||
|
}}
|
||||||
|
onStop={() => {
|
||||||
|
action({ variables: { command: 'stop' } });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Mutation>
|
||||||
|
</Toolbar>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TabContext value={tab}>
|
||||||
|
{tab === TAB_RECORDS && (
|
||||||
|
<div className={classes.panel}>
|
||||||
|
<WNSRecords type={type} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tab === TAB_STATUS && (
|
||||||
|
<div className={classes.panel}>
|
||||||
|
<Paper className={classes.paper}>
|
||||||
|
<WNSStatus />
|
||||||
|
</Paper>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tab === TAB_LOG && (
|
||||||
|
<div className={classes.panel}>
|
||||||
|
<WNSLog />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</TabContext>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WNS;
|
26
packages/console-client/src/containers/panels/wns/WNSLog.js
Normal file
26
packages/console-client/src/containers/panels/wns/WNSLog.js
Normal file
@ -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 (
|
||||||
|
<Log log={data.wns_log.log} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WNSLog;
|
112
packages/console-client/src/containers/panels/wns/WNSRecords.js
Normal file
112
packages/console-client/src/containers/panels/wns/WNSRecords.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
//
|
||||||
|
// 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, useSorter } 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 (
|
||||||
|
<ButtonGroup
|
||||||
|
disableRipple
|
||||||
|
disableFocusRipple
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
aria-label="text primary button group"
|
||||||
|
>
|
||||||
|
{types.map(t => (
|
||||||
|
<Button
|
||||||
|
key={t.key}
|
||||||
|
className={t.key === type && classes.selected}
|
||||||
|
onClick={() => onChanged(t.key)}
|
||||||
|
>
|
||||||
|
{t.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const WNSRecords = ({ type }) => {
|
||||||
|
const { config } = useContext(ConsoleContext);
|
||||||
|
const [sorter, sortBy] = useSorter('id');
|
||||||
|
const data = useQueryStatusReducer(useQuery(WNS_RECORDS, {
|
||||||
|
pollInterval: config.api.pollInterval,
|
||||||
|
variables: { type }
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = data.wns_records.json;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell onClick={sortBy('type')} size="small">Type</TableCell>
|
||||||
|
<TableCell onClick={sortBy('name')}>ID</TableCell>
|
||||||
|
<TableCell onClick={sortBy('version')} size="small">Version</TableCell>
|
||||||
|
<TableCell onClick={sortBy('attributes.displayName')}>Name</TableCell>
|
||||||
|
<TableCell onClick={sortBy('attributes.package')}>Package Hash</TableCell>
|
||||||
|
<TableCell onClick={sortBy('createTime')} size="small">Created</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{records.sort(sorter)
|
||||||
|
.map(({ id, type, name, version, createTime, attributes: { displayName, package: pkg } }) => (
|
||||||
|
<TableRow key={id} size="small">
|
||||||
|
<TableCell monospace>{type}</TableCell>
|
||||||
|
<TableCell monospace>{name}</TableCell>
|
||||||
|
<TableCell monospace>{version}</TableCell>
|
||||||
|
<TableCell>{displayName}</TableCell>
|
||||||
|
<TableCell title={JSON.stringify(pkg)} monospace>
|
||||||
|
{pkg && (
|
||||||
|
<PackageLink config={config} type={type} pkg={pkg} />
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{moment.utc(createTime).fromNow()}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WNSRecords;
|
@ -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 (
|
||||||
|
<Json data={data.wns_status.json} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WNSStatus;
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
status {
|
|
||||||
version
|
|
||||||
}
|
|
||||||
}
|
|
11
packages/console-client/src/hooks/context.js
Normal file
11
packages/console-client/src/hooks/context.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://reactjs.org/docs/context.html#reactcreatecontext
|
||||||
|
* @type {React.Context}
|
||||||
|
*/
|
||||||
|
export const ConsoleContext = createContext({});
|
8
packages/console-client/src/hooks/index.js
Normal file
8
packages/console-client/src/hooks/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
export * from './context';
|
||||||
|
export * from './registry';
|
||||||
|
export * from './sorter';
|
||||||
|
export * from './status';
|
19
packages/console-client/src/hooks/registry.js
Normal file
19
packages/console-client/src/hooks/registry.js
Normal file
@ -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 })
|
||||||
|
};
|
||||||
|
};
|
25
packages/console-client/src/hooks/sorter.js
Normal file
25
packages/console-client/src/hooks/sorter.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import get from 'lodash.get';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
// TODO(burdon): Enable multiple sort order (e.g., id, version).
|
||||||
|
export const useSorter = (initial) => {
|
||||||
|
const [{ sort, ascend }, setSort] = useState({ sort: initial, 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortBy = field => () => setSort({ sort: field, ascend: (field === sort ? !ascend : true) });
|
||||||
|
|
||||||
|
return [
|
||||||
|
sorter,
|
||||||
|
sortBy
|
||||||
|
];
|
||||||
|
};
|
51
packages/console-client/src/hooks/status.js
Normal file
51
packages/console-client/src/hooks/status.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019 DxOS
|
||||||
|
//
|
||||||
|
|
||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
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: [] } })
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Apollo queries.
|
||||||
|
*/
|
||||||
|
export const useQueryStatusReducer = ({ loading, error, data }) => {
|
||||||
|
const [, setStatus] = useStatusReducer();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
setTimeout(() => setStatus({ loading }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setTimeout(() => setStatus({ error }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const statusReducer = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_STATUS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.payload
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
40
packages/console-client/src/icons/DXOS.js
Normal file
40
packages/console-client/src/icons/DXOS.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||||
|
|
||||||
|
const Icon = (props) => (
|
||||||
|
<SvgIcon {...props} viewBox="0 0 512 256">
|
||||||
|
<g transform="matrix(1,0,0,1,-187.374,-7.5)">
|
||||||
|
<g transform="matrix(0.936373,0,0,0.906495,295.947,135.5)">
|
||||||
|
<path
|
||||||
|
d="M0,-0.233C0,-27.435 -16.74,-48.591 -48.359,-48.591L-76.722,-48.591L-76.722,48.358L-48.59,48.358C-17.902,48.358 0,26.271 0,-0.233M-109.736,77.419L-109.736,-77.652L-48.59,-77.652C0,-77.652 33.711,-46.731 33.711,-0.233C33.711,46.73 0,77.419 -48.359,77.419L-109.736,77.419Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.936373,0,0,0.906495,530.058,135.499)">
|
||||||
|
<path
|
||||||
|
d="M0,0.002C0,-29.06 -18.367,-50.914 -46.964,-50.914C-75.793,-50.914 -94.16,-29.06 -94.16,0.002C-94.16,28.83 -75.793,50.917 -46.964,50.917C-18.367,50.917 0,28.83 0,0.002M-128.104,0.002C-128.104,-46.729 -93.927,-80.208 -46.964,-80.208C-0.233,-80.208 33.942,-46.729 33.942,0.002C33.942,46.731 -0.233,80.21 -46.964,80.21C-93.927,80.21 -128.104,46.731 -128.104,0.002"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.936373,0,0,0.906495,573.603,85.3412)">
|
||||||
|
<path
|
||||||
|
d="M0,110.898L18.134,85.092C29.062,96.484 46.034,106.249 67.422,106.249C85.789,106.249 94.392,98.111 94.392,89.044C94.392,62.075 5.115,80.907 5.115,22.551C5.115,-3.255 27.434,-24.644 63.935,-24.644C88.579,-24.644 109.039,-17.204 124.383,-3.022L106.017,21.622C93.462,9.997 76.723,4.65 60.913,4.65C46.964,4.65 39.059,10.695 39.059,19.994C39.059,44.406 128.103,27.898 128.103,85.789C128.103,114.153 107.644,135.542 66.028,135.542C36.036,135.542 14.648,125.544 0,110.898"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.936373,0,0,0.906495,368.158,65.109)">
|
||||||
|
<path
|
||||||
|
d="M0,56.844L-36.496,0L36.496,0L0,56.844Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.936373,0,0,0.906495,368.158,205.681)">
|
||||||
|
<path
|
||||||
|
d="M0,-56.845L-36.496,0L36.496,0L0,-56.845Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Icon;
|
37
packages/console-client/src/icons/Logo.js
Normal file
37
packages/console-client/src/icons/Logo.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||||
|
|
||||||
|
// TODO(burdon): Fixed color?
|
||||||
|
|
||||||
|
const Icon = (props) => (
|
||||||
|
<SvgIcon {...props} viewBox="0 0 256 256">
|
||||||
|
<g transform="matrix(1,0,0,1,-160,-124)">
|
||||||
|
<path
|
||||||
|
d="M282.254,147.134L195.254,194.589C191.399,196.692 189,200.732 189,205.124L189,298.876C189,303.268 191.399,307.308 195.254,309.411L282.254,356.866C285.836,358.819 290.164,358.819 293.746,356.866L380.746,309.411C384.601,307.308 387,303.268 387,298.876L387,205.124C387,200.732 384.601,196.692 380.746,194.589L293.746,147.134C290.164,145.181 285.836,145.181 282.254,147.134Z"
|
||||||
|
style={{ fill: 'none', fillRule: 'nonzero', stroke: 'rgb(0,68,121)', strokeWidth: '12px' }}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M288,252L216,216"
|
||||||
|
style={{ fill: 'none', fillRule: 'nonzero', stroke: 'rgb(0,68,121)', strokeWidth: '8px', strokeLinejoin: 'round' }}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M216,288L288,252"
|
||||||
|
style={{ fill: 'none', fillRule: 'nonzero', stroke: 'rgb(0,68,121)', strokeWidth: '8px', strokeLinejoin: 'round' }}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M360,288L288,252"
|
||||||
|
style={{ fill: 'none', fillRule: 'nonzero', stroke: 'rgb(0,68,121)', strokeWidth: '8px', strokeLinejoin: 'round' }}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M360,216L288,252"
|
||||||
|
style={{ fill: 'none', fillRule: 'nonzero', stroke: 'rgb(0,68,121)', strokeWidth: '8px', strokeLinejoin: 'round' }}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Icon;
|
5
packages/console-client/src/index.js
Normal file
5
packages/console-client/src/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
export * from './hooks';
|
@ -1,31 +1,10 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2020 DxOS
|
// Copyright 2020 DxOS.org
|
||||||
//
|
//
|
||||||
|
|
||||||
import { ApolloProvider } from '@apollo/react-hooks';
|
|
||||||
import ApolloClient from 'apollo-boost';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
|
|
||||||
import App from './components/App';
|
import Main from './containers/Main';
|
||||||
|
|
||||||
const PORT = 4000;
|
|
||||||
|
|
||||||
const client = new ApolloClient({
|
|
||||||
uri: `http://localhost:${PORT}/graphql`
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO(burdon): Error handling for server errors.
|
|
||||||
|
|
||||||
// TODO(burdon): Auth
|
|
||||||
// https://www.apollographql.com/docs/react/networking/authentication/
|
|
||||||
|
|
||||||
const Main = () => {
|
|
||||||
return (
|
|
||||||
<ApolloProvider client={client}>
|
|
||||||
<App />
|
|
||||||
</ApolloProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render(<Main />, document.getElementById('root'));
|
render(<Main />, document.getElementById('root'));
|
||||||
|
60
packages/console-client/src/modules.js
Normal file
60
packages/console-client/src/modules.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import AppsIcon from '@material-ui/icons/Apps';
|
||||||
|
import BotsIcon from '@material-ui/icons/Android';
|
||||||
|
import StatsIcon from '@material-ui/icons/Equalizer';
|
||||||
|
import RegistryIcon from '@material-ui/icons/Language';
|
||||||
|
import IPFSIcon from '@material-ui/icons/GraphicEq';
|
||||||
|
import ConfigIcon from '@material-ui/icons/Settings';
|
||||||
|
import SignalIcon from '@material-ui/icons/Traffic';
|
||||||
|
import ServicesIcon from '@material-ui/icons/Storage';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
path: '/status',
|
||||||
|
title: 'Status',
|
||||||
|
icon: StatsIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/wns',
|
||||||
|
title: 'WNS',
|
||||||
|
icon: RegistryIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/apps',
|
||||||
|
title: 'Apps',
|
||||||
|
icon: AppsIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/bots',
|
||||||
|
title: 'Bots',
|
||||||
|
icon: BotsIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/signal',
|
||||||
|
title: 'Signal Server',
|
||||||
|
icon: SignalIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/ipfs',
|
||||||
|
title: 'IPFS',
|
||||||
|
icon: IPFSIcon
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
path: '/metadata',
|
||||||
|
title: 'Metadata',
|
||||||
|
icon: ServicesIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/config',
|
||||||
|
title: 'Config',
|
||||||
|
icon: ConfigIcon
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
64
packages/console-client/src/resolvers.js
Normal file
64
packages/console-client/src/resolvers.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// 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');
|
||||||
|
|
||||||
|
const timestamp = () => new Date().toUTCString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolvers
|
||||||
|
* https://www.apollographql.com/docs/tutorial/local-state/#local-resolvers
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
export const createResolvers = config => {
|
||||||
|
const endpoint = getServiceUrl(config, 'wns.server', { absolute: true });
|
||||||
|
const registry = new Registry(endpoint);
|
||||||
|
|
||||||
|
return {
|
||||||
|
Query: {
|
||||||
|
wns_status: async () => {
|
||||||
|
log('WNS status...');
|
||||||
|
const data = await registry.getStatus();
|
||||||
|
|
||||||
|
return {
|
||||||
|
__typename: 'JSONResult',
|
||||||
|
timestamp: timestamp(),
|
||||||
|
|
||||||
|
// NOTE: Hack since this should be a string according to the schema.
|
||||||
|
json: data
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
wns_records: async (_, { type }) => {
|
||||||
|
log('WNS records...');
|
||||||
|
const data = await registry.queryRecords({ type });
|
||||||
|
|
||||||
|
return {
|
||||||
|
__typename: 'JSONResult',
|
||||||
|
timestamp: timestamp(),
|
||||||
|
|
||||||
|
// NOTE: Hack since this should be a string according to the schema.
|
||||||
|
json: data
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
wns_log: async () => {
|
||||||
|
log('WNS log...');
|
||||||
|
|
||||||
|
// TODO(burdon): Use Registry API rather than from CLI?
|
||||||
|
return {
|
||||||
|
__typename: 'JSONLog',
|
||||||
|
timestamp: timestamp(),
|
||||||
|
log: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
76
packages/console-client/src/theme.js
Normal file
76
packages/console-client/src/theme.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2019 DxOS
|
||||||
|
//
|
||||||
|
|
||||||
|
import { createMuiTheme } from '@material-ui/core/styles';
|
||||||
|
import teal from '@material-ui/core/colors/teal';
|
||||||
|
import orange from '@material-ui/core/colors/orange';
|
||||||
|
|
||||||
|
export const createTheme = (theme) => createMuiTheme({
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/60567673/reactjs-material-ui-theme-mixins-toolbar-offset-is-not-adapting-when-toolbar
|
||||||
|
mixins: {
|
||||||
|
denseToolbar: {
|
||||||
|
minHeight: 48
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://material-ui.com/customization/globals/#default-props
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://material-ui.com/customization/palette/
|
||||||
|
palette: theme === 'dark' ? {
|
||||||
|
type: 'dark',
|
||||||
|
primary: orange
|
||||||
|
} : {
|
||||||
|
primary: teal
|
||||||
|
},
|
||||||
|
|
||||||
|
// https://material-ui.com/customization/theming/#theme-configuration-variables
|
||||||
|
|
||||||
|
// https://material-ui.com/customization/globals/
|
||||||
|
overrides: {
|
||||||
|
MuiCssBaseline: {
|
||||||
|
'@global': {
|
||||||
|
body: {
|
||||||
|
margin: 0,
|
||||||
|
overflow: 'hidden'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
40
packages/console-client/src/util/config.js
Normal file
40
packages/console-client/src/util/config.js
Normal file
@ -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);
|
||||||
|
};
|
42
packages/console-client/src/util/config.test.js
Normal file
42
packages/console-client/src/util/config.test.js
Normal file
@ -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');
|
||||||
|
});
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"name": "@dxos/console-client",
|
"name": "@dxos/console-client",
|
||||||
"buildDate": "2020-05-23T17:02:38.800Z",
|
"buildDate": "2020-05-26T01:16:30.514Z",
|
||||||
"version": "1.0.0-beta.0"
|
"version": "1.0.0-beta.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,9 @@ module.exports = {
|
|||||||
fs: 'empty'
|
fs: 'empty'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO(burdon): Config production path for apollo (diff webpack config).
|
||||||
output: {
|
output: {
|
||||||
path: `${__dirname}/dist`,
|
path: `${__dirname}/dist/production`,
|
||||||
filename: '[name].bundle.js',
|
filename: '[name].bundle.js',
|
||||||
publicPath: PUBLIC_URL
|
publicPath: PUBLIC_URL
|
||||||
},
|
},
|
||||||
@ -45,10 +46,8 @@ module.exports = {
|
|||||||
cacheGroups: {
|
cacheGroups: {
|
||||||
vendor: {
|
vendor: {
|
||||||
test: /[\\/]node_modules[\\/]/,
|
test: /[\\/]node_modules[\\/]/,
|
||||||
// name: 'vendor',
|
name (module) {
|
||||||
name(module) {
|
|
||||||
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
|
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
|
||||||
// return `vendor-${packageName.replace('@', '')}`;
|
|
||||||
|
|
||||||
if (packageName.startsWith('@dxos')) {
|
if (packageName.startsWith('@dxos')) {
|
||||||
return 'dxos';
|
return 'dxos';
|
||||||
@ -104,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)$/,
|
test: /\.(graphql|gql)$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
loader: 'graphql-tag/loader',
|
loader: 'graphql-tag/loader'
|
||||||
},
|
},
|
||||||
|
|
||||||
// fonts
|
// fonts
|
||||||
|
@ -16,7 +16,7 @@ module.exports = merge(commonConfig, {
|
|||||||
new HtmlWebPackPlugin({
|
new HtmlWebPackPlugin({
|
||||||
template: './public/index.html',
|
template: './public/index.html',
|
||||||
templateParameters: {
|
templateParameters: {
|
||||||
title: 'Planner'
|
title: 'DxOS Console'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
12
packages/console-server/README.md
Normal file
12
packages/console-server/README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Console
|
||||||
|
|
||||||
|
Apollo GraphQL client.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
http://localhost:4000
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2020 DxOS
|
// Copyright 2020 DxOS.org
|
||||||
//
|
//
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"port": 4000,
|
|
||||||
"path": "/graphql"
|
|
||||||
}
|
|
@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "semistandard 'src/**/*.js'",
|
"lint": "semistandard 'src/**/*.js'",
|
||||||
"test": "jest --rootDir ./src --passWithNoTests --no-cache",
|
"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",
|
"author": "DxOS.org",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@ -26,6 +26,9 @@
|
|||||||
"express-graphql": "^0.9.0",
|
"express-graphql": "^0.9.0",
|
||||||
"graphql": "^15.0.0",
|
"graphql": "^15.0.0",
|
||||||
"graphql-tag": "^2.10.3",
|
"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"
|
"source-map-support": "^0.5.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -35,17 +38,46 @@
|
|||||||
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
"@babel/plugin-proposal-class-properties": "^7.5.5",
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.5.2",
|
"@babel/plugin-proposal-export-default-from": "^7.5.2",
|
||||||
"@babel/preset-env": "^7.4.5",
|
"@babel/preset-env": "^7.4.5",
|
||||||
"babel-eslint": "^10.0.2",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-jest": "^24.8.0",
|
"babel-jest": "^24.8.0",
|
||||||
"babel-plugin-add-module-exports": "^1.0.2",
|
"babel-plugin-add-module-exports": "^1.0.2",
|
||||||
"babel-plugin-inline-import": "^3.0.0",
|
"babel-plugin-inline-import": "^3.0.0",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"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-babel": "^5.3.0",
|
||||||
|
"eslint-plugin-import": "^2.18.2",
|
||||||
"eslint-plugin-jest": "^23.13.1",
|
"eslint-plugin-jest": "^23.13.1",
|
||||||
|
"eslint-plugin-jsdoc": "^21.0.0",
|
||||||
|
"eslint-plugin-node": "^11.1.0",
|
||||||
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
"jest": "^24.8.0",
|
"jest": "^24.8.0",
|
||||||
"nodemon": "^2.0.4",
|
"nodemon": "^2.0.4",
|
||||||
"semistandard": "^14.2.0"
|
"semistandard": "^14.2.0"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"extends": [
|
||||||
|
"plugin:jest/recommended",
|
||||||
|
"semistandard"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"babel"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"babel/semi": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"semistandard": {
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"env": [
|
||||||
|
"jest",
|
||||||
|
"node",
|
||||||
|
"browser"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,41 @@
|
|||||||
type Status {
|
#
|
||||||
version: String
|
# Copyright 2020 DxOS.org
|
||||||
|
#
|
||||||
|
|
||||||
|
type Result {
|
||||||
|
timestamp: String!
|
||||||
|
code: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Log {
|
||||||
status: Status
|
timestamp: String!
|
||||||
|
log: [String]!
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO(burdon): Generic result.
|
||||||
|
type JSONResult {
|
||||||
|
timestamp: String!
|
||||||
|
json: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Schema
|
||||||
|
#
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
system_status: JSONResult!
|
||||||
|
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!
|
||||||
|
}
|
||||||
|
|
||||||
|
schema {
|
||||||
|
mutation: Mutation
|
||||||
|
query: Query
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
status {
|
|
||||||
version
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +1,38 @@
|
|||||||
//
|
//
|
||||||
// Copyright 2020 DxOS
|
// Copyright 2020 DxOS.org
|
||||||
//
|
//
|
||||||
|
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
import { ApolloServer, gql } from 'apollo-server-express';
|
import { ApolloServer, gql } from 'apollo-server-express';
|
||||||
import { print } from 'graphql/language';
|
import { print } from 'graphql/language';
|
||||||
|
|
||||||
|
import SYSTEM_STATUS from '@dxos/console-client/gql/system_status.graphql';
|
||||||
|
|
||||||
|
import { createResolvers } from './resolvers';
|
||||||
|
|
||||||
import SCHEMA from './gql/api.graphql';
|
import SCHEMA from './gql/api.graphql';
|
||||||
import QUERY_STATUS from './gql/status.graphql';
|
|
||||||
|
|
||||||
import config from '../config.json';
|
const config = yaml.safeLoad(
|
||||||
import { version } from '../package.json';
|
fs.readFileSync(path.join(__dirname, '../../../node_modules/@dxos/console-client/config.yml')));
|
||||||
|
|
||||||
const log = debug('c2:src');
|
const log = debug('dxos:console:server');
|
||||||
debug.enable('c2:*');
|
|
||||||
|
|
||||||
// Resolver
|
debug.enable(config.system.debug);
|
||||||
const resolvers = {
|
|
||||||
Query: {
|
//
|
||||||
status: () => ({
|
// Express server.
|
||||||
version
|
//
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Server
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
//
|
||||||
// CORS
|
// CORS
|
||||||
|
//
|
||||||
|
|
||||||
// import cors from 'cors'
|
// import cors from 'cors'
|
||||||
// https://expressjs.com/en/resources/middleware/cors.html
|
// https://expressjs.com/en/resources/middleware/cors.html
|
||||||
// https://www.prisma.io/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d
|
// https://www.prisma.io/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d
|
||||||
@ -38,17 +41,27 @@ const app = express();
|
|||||||
// credentials: true
|
// credentials: true
|
||||||
// }));
|
// }));
|
||||||
|
|
||||||
|
//
|
||||||
// React app
|
// React app
|
||||||
// TODO(burdon): Create HTML file.
|
//
|
||||||
// TODO(burdon): Load JS.
|
|
||||||
app.get('/app', (req,res) =>{
|
const { app: { publicUrl } } = config;
|
||||||
res.sendFile(path.join(__dirname + '/../../../node_modules/@dxos/console-client/dist/es/main.js'));
|
|
||||||
|
app.get(`${publicUrl}(/:filePath)?`, (req, res) => {
|
||||||
|
const { filePath = 'index.html' } = req.params;
|
||||||
|
const file = path.join(__dirname, '../../../node_modules/@dxos/console-client/dist/production', filePath);
|
||||||
|
res.sendFile(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Apollo Server
|
||||||
// https://www.apollographql.com/docs/apollo-server/api/apollo-server
|
// https://www.apollographql.com/docs/apollo-server/api/apollo-server
|
||||||
|
//
|
||||||
|
|
||||||
const server = new ApolloServer({
|
const server = new ApolloServer({
|
||||||
typeDefs: SCHEMA,
|
typeDefs: SCHEMA,
|
||||||
resolvers,
|
|
||||||
|
resolvers: createResolvers(config),
|
||||||
|
|
||||||
// https://www.apollographql.com/docs/apollo-server/testing/graphql-playground
|
// https://www.apollographql.com/docs/apollo-server/testing/graphql-playground
|
||||||
// https://github.com/prisma-labs/graphql-playground#usage
|
// https://github.com/prisma-labs/graphql-playground#usage
|
||||||
@ -59,19 +72,29 @@ const server = new ApolloServer({
|
|||||||
},
|
},
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
endpoint: config.path,
|
name: 'Status',
|
||||||
query: print(gql(QUERY_STATUS))
|
endpoint: config.api.path,
|
||||||
|
query: print(gql(SYSTEM_STATUS))
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Apollo middleware
|
||||||
// https://www.apollographql.com/docs/apollo-server/api/apollo-server/#apolloserverapplymiddleware
|
// https://www.apollographql.com/docs/apollo-server/api/apollo-server/#apolloserverapplymiddleware
|
||||||
|
//
|
||||||
|
|
||||||
server.applyMiddleware({
|
server.applyMiddleware({
|
||||||
app,
|
app,
|
||||||
path: config.path
|
path: config.api.path
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen({ port: config.port }, () => {
|
//
|
||||||
log(`Running: http://localhost:${config.port}`);
|
// Start server
|
||||||
|
//
|
||||||
|
|
||||||
|
const { api: { port } } = config;
|
||||||
|
app.listen({ port }, () => {
|
||||||
|
log(`Running: http://localhost:${port}`);
|
||||||
});
|
});
|
||||||
|
75
packages/console-server/src/resolvers.js
Normal file
75
packages/console-server/src/resolvers.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2020 DxOS.org
|
||||||
|
//
|
||||||
|
|
||||||
|
import debug from 'debug';
|
||||||
|
import IpfsHttpClient from 'ipfs-http-client';
|
||||||
|
|
||||||
|
const log = debug('dxos:console:server:resolvers');
|
||||||
|
|
||||||
|
const timestamp = () => new Date().toUTCString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolvers
|
||||||
|
* https://www.apollographql.com/docs/graphql-tools/resolvers
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
export const createResolvers = config => ({
|
||||||
|
Mutation: {
|
||||||
|
//
|
||||||
|
// WNS
|
||||||
|
//
|
||||||
|
|
||||||
|
wns_action: async (_, { command }) => {
|
||||||
|
log(`WNS action: ${command}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp: timestamp(),
|
||||||
|
code: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Query: {
|
||||||
|
//
|
||||||
|
// System
|
||||||
|
//
|
||||||
|
|
||||||
|
system_status: () => ({
|
||||||
|
timestamp: timestamp(),
|
||||||
|
json: JSON.stringify({
|
||||||
|
dxos: {
|
||||||
|
image: '0.0.1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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_status: async () => {
|
||||||
|
log('Calling IPFS...');
|
||||||
|
|
||||||
|
// TODO(burdon): Config.
|
||||||
|
// NOTE: Hangs if server not running.
|
||||||
|
const ipfs = new IpfsHttpClient('/ip4/127.0.0.1/tcp/5001');
|
||||||
|
|
||||||
|
const version = await ipfs.version();
|
||||||
|
const status = await ipfs.id();
|
||||||
|
|
||||||
|
console.log(version);
|
||||||
|
log('Done');
|
||||||
|
|
||||||
|
return {
|
||||||
|
json: JSON.stringify({
|
||||||
|
version,
|
||||||
|
status
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user