Server Console App directly from the server.

Use Moustache to create template and set config in page.
Use babel plugins to process GQL (and fix GQL queries).
Added service type.
This commit is contained in:
richburdon
2020-05-27 19:09:20 -04:00
parent 0b0234761f
commit eb6bcd9c95
77 changed files with 586 additions and 210 deletions
+34 -2
View File
@@ -4,9 +4,41 @@ Apollo GraphQL client.
## Usage
Use the following command to run the server at: http://localhost:4000
```bash
yarn
yarn start
```
To test the Console app, the `@dxos/console-app` must be built first:
```bash
cd ../console-app
yarn build
```
Use the following command to run the client in development mode (via the `webpack-dev-server`) at http://localhost:8080
```bash
yarn dev
```
To build the client and serve it directly from the server:
```bash
yarn build
yarn start
```
http://localhost:4000
Then open the app at: http://localhost:4000/console
## Production
When running the Console server, either set the `CONFIG_FILE` environment variable or set the `--config` option.
```bash
./bin/console.js --config ./config.yml
```
NOTE: The server passes its configuration directly to the Console app when it is loaded.
+9 -6
View File
@@ -4,20 +4,23 @@
module.exports = {
presets: [
[
'@babel/preset-env'
]
'@babel/preset-env',
'@babel/preset-react'
],
plugins: [
[
'babel-plugin-inline-import', {
extensions: [
'.graphql',
'.proto',
'.txt'
'.mustache',
'.graphql'
]
}
],
// Allows export of components importing GQL files (without webpack).
'import-graphql',
'inline-json-import',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-export-default-from'
]
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env node
module.exports = require('../dist/es/server/main.js');
+43
View File
@@ -0,0 +1,43 @@
#
# NODE_ENV === production
# NOTE: Set CONFIG_FILE to swap out this config file.
#
# TODO(burdon): Set defaults.
app:
title: 'Console'
org': 'DxOS'
theme: 'dark'
website: 'https://dxos.org'
publicUrl: '/console'
api:
path: '/api'
port: 4000
intervalLog: 5000
pollInterval: 10000
system:
debug: 'dxos:console:*'
services:
app:
prefix: '/app'
server: 'http://127.0.0.1:5999'
wns:
server: 'https://node1.dxos.network/wns/api'
webui: 'https://node1.dxos.network/wns/webui'
signal:
server: 'http://127.0.0.1:4000'
api: 'http://127.0.0.1:4000'
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'
+33 -6
View File
@@ -1,12 +1,24 @@
{
"name": "@dxos/console-server",
"name": "@dxos/console",
"version": "1.0.0-beta.0",
"description": "DxOS Console Server",
"main": "index.js",
"main": "dist/es/index.js",
"bin": {
"console": "bin/console.js"
},
"files": [
"bin/",
"dist/es"
],
"scripts": {
"build": "npm run clean && npm run build:client && npm run build:server",
"build:client": "PUBLIC_URL=/console webpack --mode development",
"build:server": "babel ./src --out-dir ./dist/es --ignore \"**/*.test.js\" --copy-files",
"clean": "rm -rf ./dist",
"dev": "VERBOSE=true webpack-dev-server --mode development --watch",
"lint": "semistandard 'src/**/*.js'",
"test": "jest --rootDir ./src --passWithNoTests --no-cache",
"start": "BABEL_DISABLE_CACHE=1 nodemon --exec babel-node ./src/main.js"
"start": "CONFIG_FILE=./config.yml BABEL_DISABLE_CACHE=1 nodemon --exec babel-node src/server/main.js -- --verbose"
},
"author": "DxOS.org",
"license": "GPL-3.0",
@@ -17,8 +29,9 @@
"testEnvironment": "node"
},
"dependencies": {
"@babel/polyfill": "^7.8.7",
"@babel/runtime": "^7.8.7",
"@dxos/console-client": "^1.0.0-beta.0",
"@dxos/console-app": "^1.0.0-beta.0",
"apollo-boost": "^0.4.9",
"apollo-server-express": "^2.13.1",
"debug": "^4.1.1",
@@ -28,8 +41,11 @@
"graphql-tag": "^2.10.3",
"ipfs-http-client": "^44.1.0",
"js-yaml": "^3.14.0",
"mustache-express": "^1.3.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"source-map-support": "^0.5.12"
"source-map-support": "^0.5.12",
"yargs": "^15.3.1"
},
"devDependencies": {
"@babel/cli": "7.4.4",
@@ -40,8 +56,13 @@
"@babel/preset-env": "^7.4.5",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.8.0",
"babel-loader": "^8.1.0",
"babel-plugin-add-module-exports": "^1.0.2",
"babel-plugin-import-graphql": "^2.7.0",
"babel-plugin-inline-import": "^3.0.0",
"babel-plugin-inline-json-import": "^0.3.2",
"body-parser": "^1.19.0",
"dotenv-webpack": "^1.8.0",
"eslint": "^6.7.2",
"eslint-config-semistandard": "^15.0.0",
"eslint-config-standard": "^14.1.1",
@@ -54,7 +75,13 @@
"eslint-plugin-standard": "^4.0.1",
"jest": "^24.8.0",
"nodemon": "^2.0.4",
"semistandard": "^14.2.0"
"react": "^16.13.1",
"react-dom": "^16.13.1",
"semistandard": "^14.2.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2"
},
"publishConfig": {
"access": "public"
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
@@ -0,0 +1,16 @@
//
// Copyright 2020 DxOS.org
//
import debug from 'debug';
import React from 'react';
import { render } from 'react-dom';
import { Main } from '@dxos/console-client';
// Load from global printed into HTML page via template.
const { config } = window.__DXOS__;
debug.enable(config.system.debug);
render(<Main config={config} />, document.getElementById('root'));
-101
View File
@@ -1,101 +0,0 @@
//
// Copyright 2020 DxOS.org
//
import debug from 'debug';
import express from 'express';
import fs from 'fs';
import path from 'path';
import yaml from 'js-yaml';
import { ApolloServer, gql } from 'apollo-server-express';
import { print } from 'graphql/language';
import SYSTEM_STATUS from '@dxos/console-client/gql/system_status.graphql';
import { createResolvers } from './resolvers';
import SCHEMA from './gql/api.graphql';
const config = yaml.safeLoad(
fs.readFileSync(path.join(__dirname, '../../../node_modules/@dxos/console-client/config.yml')));
const log = debug('dxos:console:server');
debug.enable(config.system.debug);
//
// Express server.
//
const app = express();
//
// CORS
//
// import cors from 'cors'
// https://expressjs.com/en/resources/middleware/cors.html
// https://www.prisma.io/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d
// app.use(cors({
// origin: true,
// credentials: true
// }));
//
// React app
//
const { app: { publicUrl } } = config;
// TODO(burdon): Load via WNS.
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
//
const server = new ApolloServer({
typeDefs: SCHEMA,
resolvers: createResolvers(config),
// https://www.apollographql.com/docs/apollo-server/testing/graphql-playground
// https://github.com/prisma-labs/graphql-playground#usage
// introspection: true,
playground: {
settings: {
'editor.theme': 'light'
},
tabs: [
{
name: 'Status',
endpoint: config.api.path,
query: print(gql(SYSTEM_STATUS))
}
]
}
});
//
// Apollo middleware
// https://www.apollographql.com/docs/apollo-server/api/apollo-server/#apolloserverapplymiddleware
//
server.applyMiddleware({
app,
path: config.api.path
});
//
// Start server
//
const { api: { port } } = config;
app.listen({ port }, () => {
log(`Running: http://localhost:${port}`);
});
+138
View File
@@ -0,0 +1,138 @@
//
// Copyright 2020 DxOS.org
//
import bodyParser from 'body-parser';
import debug from 'debug';
import express from 'express';
import mustache from 'mustache-express';
import fs from 'fs';
import yaml from 'js-yaml';
import { ApolloServer, gql } from 'apollo-server-express';
import { print } from 'graphql/language';
import yargs from 'yargs';
import SYSTEM_STATUS from '@dxos/console-client/src/gql/system_status.graphql';
import { createResolvers } from './resolvers';
import SCHEMA from '../gql/api.graphql';
const argv = yargs
.option('config', {
alias: 'c',
description: 'Config file',
type: 'string'
})
.option('verbose', {
alias: 'v',
description: 'Verbse info',
type: 'boolean'
})
.help()
.alias('help', 'h')
.argv;
const configFile = argv.config || process.env.CONFIG_FILE;
if (!configFile) {
yargs.showHelp();
process.exit(1);
}
const config = yaml.safeLoad(fs.readFileSync(configFile));
const log = debug('dxos:console:server');
debug.enable(config.system.debug);
if (argv.verbose) {
log(JSON.stringify(config, undefined, 2));
}
//
// Express server.
//
const app = express();
app.set('views', `${__dirname}/views`);
app.set('view engine', 'mustache');
app.engine('mustache', mustache());
app.use(bodyParser.urlencoded({ extended: true }));
// TODO(burdon): Add react, webpack deps
// TODO(burdon): app.use(compression());
//
// CORS
//
// import cors from 'cors'
// https://expressjs.com/en/resources/middleware/cors.html
// https://www.prisma.io/blog/enabling-cors-for-express-graphql-apollo-server-1ef999bfb38d
// app.use(cors({
// origin: true,
// credentials: true
// }));
//
// React app
// TODO(burdon): Can we load this via WNS?
//
const { app: { publicUrl } } = config;
const bundles = [
'runtime', 'vendor', 'material-ui', 'dxos', 'main'
];
app.use(`${publicUrl}/lib`, express.static('./dist/client'));
app.get(publicUrl, (req, res) => {
res.render('console', {
title: 'Console',
container: 'root',
config: JSON.stringify(config),
scripts: bundles.map(bundle => ({ src: `${publicUrl}/lib/${bundle}.bundle.js` }))
});
});
//
// Apollo Server and middleware
// https://www.apollographql.com/docs/apollo-server/api/apollo-server
// https://www.apollographql.com/docs/apollo-server/api/apollo-server/#apolloserverapplymiddleware
//
const server = new ApolloServer({
typeDefs: SCHEMA,
resolvers: createResolvers(config),
// https://www.apollographql.com/docs/apollo-server/testing/graphql-playground
// https://github.com/prisma-labs/graphql-playground#usage
// introspection: true,
playground: {
settings: {
'editor.theme': config.app.theme
},
tabs: [
{
name: 'Status',
endpoint: config.api.path,
query: print(gql(SYSTEM_STATUS))
}
]
}
});
server.applyMiddleware({ app, path: config.api.path });
//
// Start server
//
const { api: { port } } = config;
app.listen({ port }, () => {
log(`Running: http://localhost:${port}`);
});
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
</head>
<body>
<div id="{{ container }}"></div>
<!-- Config loaded by client. -->
<script charset="utf-8" type="application/javascript">
window.__DXOS__ = { config: {{{ config }}} };
</script>
<!-- React bundles. -->
{{#scripts}}
<script charset="utf-8" type="application/javascript" src="{{src}}"></script>
{{/scripts}}
</body>
</html>
+110
View File
@@ -0,0 +1,110 @@
//
// Copyright 2019 DxOS
//
const path = require('path');
const Dotenv = require('dotenv-webpack');
const webpack = require('webpack');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const PUBLIC_URL = process.env.PUBLIC_URL || '';
module.exports = {
devtool: 'eval-source-map',
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
disableHostCheck: true,
port: 8080,
watchOptions: {
ignored: /node_modules/,
aggregateTimeout: 600
}
},
node: {
fs: 'empty'
},
entry: './src/client/main.js',
output: {
path: `${__dirname}/dist/client`,
filename: '[name].bundle.js',
publicPath: PUBLIC_URL
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name (module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
if (packageName.startsWith('@dxos')) {
return 'dxos';
}
if (packageName.startsWith('@material-ui')) {
return 'material-ui';
}
return 'vendor';
}
}
}
}
},
plugins: [
// https://github.com/jantimon/html-webpack-plugin#options
new HtmlWebPackPlugin({
template: './public/index.html',
templateParameters: {
title: 'DxOS Console'
}
}),
// https://www.npmjs.com/package/dotenv-webpack#properties
new Dotenv({
path: process.env.DOT_ENV || '.env'
}),
// NOTE: Must be defined below Dotenv (otherwise will override).
// https://webpack.js.org/plugins/environment-plugin
new webpack.EnvironmentPlugin({})
],
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader'
}
},
// https://github.com/eemeli/yaml-loader
{
test: /\.ya?ml$/,
type: 'json',
use: 'yaml-loader'
}
]
},
resolve: {
alias: {
'@material-ui/styles': path.resolve(__dirname, '..', '..', 'node_modules/@material-ui/styles'),
'react': path.resolve(__dirname, '..', '..', 'node_modules/react'),
'react-dom': path.resolve(__dirname, '..', '..', 'node_modules/react-dom')
}
}
};