From f8f35c5dcf3e2b36bb298f4815e59b75ce10fed9 Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Sun, 21 Apr 2019 18:31:10 +0200 Subject: [PATCH] WIP: Integrate GraphQL API in React templater --- package.json | 11 +- src/app.js | 40 +- src/routes/storage-devices.js | 37 +- {views => src/views}/disk-images/add.pug | 0 {views => src/views}/disk-images/list.pug | 0 src/views/error.jsx | 15 + {views => src/views}/error.pug | 0 src/views/hardware/storage-devices/list.jsx | 109 + .../views}/hardware/storage-devices/list.pug | 0 {views => src/views}/index.pug | 0 {views => src/views}/instances/list.pug | 0 src/views/layout.jsx | 45 + {views => src/views}/layout.pug | 0 yarn.lock | 2028 +++++++++-------- 14 files changed, 1357 insertions(+), 928 deletions(-) rename {views => src/views}/disk-images/add.pug (100%) rename {views => src/views}/disk-images/list.pug (100%) create mode 100644 src/views/error.jsx rename {views => src/views}/error.pug (100%) create mode 100644 src/views/hardware/storage-devices/list.jsx rename {views => src/views}/hardware/storage-devices/list.pug (100%) rename {views => src/views}/index.pug (100%) rename {views => src/views}/instances/list.pug (100%) create mode 100644 src/views/layout.jsx rename {views => src/views}/layout.pug (100%) diff --git a/package.json b/package.json index 1049a7c..f37a394 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "A VPS management panel", "main": "index.js", "scripts": { - "dev": "NODE_ENV=development nodemon --ext js,pug --ignore node_modules --ignore src/client bin/server.js" + "dev": "NODE_ENV=development nodemon --ext js,pug,jsx --ignore node_modules --ignore src/client bin/server.js" }, "repository": { "type": "git", @@ -13,6 +13,8 @@ "author": "Sven Slootweg", "license": "WTFPL", "dependencies": { + "@babel/register": "^7.4.0", + "@joepie91/express-react-views": "^1.0.1", "@joepie91/gulp-partial-patch-livereload-logger": "^1.0.1", "JSONStream": "^1.1.4", "array.prototype.flat": "^1.2.1", @@ -23,13 +25,16 @@ "body-parser": "^1.15.2", "capitalize": "^2.0.0", "checkit": "^0.7.0", + "classnames": "^2.2.6", "create-error": "^0.3.1", "create-event-emitter": "^1.0.0", "dataloader": "^1.4.0", "debounce": "^1.0.0", "debug": "^4.1.1", "default-value": "^1.0.0", + "dotty": "^0.1.0", "end-of-stream": "^1.1.0", + "escape-string-regexp": "^2.0.0", "execall": "^1.0.0", "express": "^4.14.0", "express-promise-router": "^1.1.0", @@ -40,8 +45,10 @@ "joi": "^14.3.0", "knex": "^0.13.0", "map-obj": "^3.0.0", + "memoizee": "^0.4.14", "pg": "^6.1.0", "pug": "^2.0.0-beta6", + "react-dom": "^16.8.6", "rfr": "^1.2.3", "scrypt-for-humans": "^2.0.5", "snake-case": "^2.1.0", @@ -76,7 +83,7 @@ "json-loader": "^0.5.4", "listening": "^0.1.0", "nodemon": "^1.18.11", - "react": "^16.6.3", + "react": "^16.8.6", "react-hot-loader": "^4.3.12", "riot": "^3.6.1", "riotjs-loader": "^4.0.0", diff --git a/src/app.js b/src/app.js index 0ba7796..85c6035 100644 --- a/src/app.js +++ b/src/app.js @@ -1,11 +1,14 @@ 'use strict'; +const Promise = require("bluebird"); const express = require("express"); // const expressWs = require("express-ws"); const knex = require("knex"); const path = require("path"); const bodyParser = require("body-parser"); +const expressAsyncReact = require("./express-async-react"); + function projectPath(targetPath) { return path.join(__dirname, "..", targetPath); } @@ -14,14 +17,45 @@ module.exports = function () { let db = knex(require("../knexfile")); let imageStore = require("./image-store")(projectPath("./images")); let taskTracker = require("../lib/tasks/tracker")(); + let apiQuery = require("./api")(); let state = {db, imageStore, taskTracker}; let app = express(); // expressWs(app); - app.set("view engine", "pug"); - app.set("views", projectPath("views")); + app.engine("jsx", expressAsyncReact.createEngine({ + prepare: (template, locals) => { + return Promise.try(() => { + if (template.query != null) { + let queryArguments = (template.queryArguments != null) + ? template.queryArguments(locals) + : {}; + + return apiQuery(template.query, queryArguments); + } + }).then((result) => { + if (result == null) { + return {}; + } else { + if (result.errors != null && result.errors.length > 0) { + throw result.errors[0]; + } else { + return { + data: result.data + }; + } + } + }); + } + })); + + app.set("view engine", "jsx"); + app.set("views", projectPath("src/views")); + + app.locals[expressAsyncReact.Settings] = { + componentPath: "template" + }; app.use((req, res, next) => { res.locals.isUnderPrefix = function isUnderPrefix(path, resultingClass) { @@ -31,7 +65,7 @@ module.exports = function () { } else { return ""; } - } + }; next(); }); diff --git a/src/routes/storage-devices.js b/src/routes/storage-devices.js index ebc43df..38887ac 100644 --- a/src/routes/storage-devices.js +++ b/src/routes/storage-devices.js @@ -53,26 +53,27 @@ module.exports = function({db}) { let router = require("express-promise-router")(); router.get("/", (req, res) => { - return Promise.try(() => { - return getStorageDevices(); - }).then((devices) => { - /* FIXME: Auto-formatting of total sizes and units */ - let fixedDrives = devices.filter((drive) => drive.removable === false); - let removableDrives = devices.filter((drive) => drive.removable === true); + // return Promise.try(() => { + // return getStorageDevices(); + // }).then((devices) => { + // /* FIXME: Auto-formatting of total sizes and units */ + // let fixedDrives = devices.filter((drive) => drive.removable === false); + // let removableDrives = devices.filter((drive) => drive.removable === true); - let healthyFixedDrives = fixedDrives.filter((drive) => drive.smartStatus === "healthy"); - let deterioratingFixedDrives = fixedDrives.filter((drive) => drive.smartStatus === "deteriorating"); - let failingFixedDrives = fixedDrives.filter((drive) => drive.smartStatus === "failing"); + // let healthyFixedDrives = fixedDrives.filter((drive) => drive.smartStatus === "healthy"); + // let deterioratingFixedDrives = fixedDrives.filter((drive) => drive.smartStatus === "deteriorating"); + // let failingFixedDrives = fixedDrives.filter((drive) => drive.smartStatus === "failing"); - res.render("hardware/storage-devices/list", { - devices: devices, - totalFixedStorage: roundUnit(B(sumDriveSizes(fixedDrives)).toTiB()), - totalHealthyFixedStorage: roundUnit(B(sumDriveSizes(healthyFixedDrives)).toTiB()), - totalDeterioratingFixedStorage: roundUnit(B(sumDriveSizes(deterioratingFixedDrives)).toTiB()), - totalFailingFixedStorage: roundUnit(B(sumDriveSizes(failingFixedDrives)).toTiB()), - totalRemovableStorage: roundUnit(B(sumDriveSizes(removableDrives)).toGiB()) - }); - }); + // res.render("hardware/storage-devices/list", { + // devices: devices, + // totalFixedStorage: roundUnit(B(sumDriveSizes(fixedDrives)).toTiB()), + // totalHealthyFixedStorage: roundUnit(B(sumDriveSizes(healthyFixedDrives)).toTiB()), + // totalDeterioratingFixedStorage: roundUnit(B(sumDriveSizes(deterioratingFixedDrives)).toTiB()), + // totalFailingFixedStorage: roundUnit(B(sumDriveSizes(failingFixedDrives)).toTiB()), + // totalRemovableStorage: roundUnit(B(sumDriveSizes(removableDrives)).toGiB()) + // }); + // }); + res.render("hardware/storage-devices/list"); }); return router; diff --git a/views/disk-images/add.pug b/src/views/disk-images/add.pug similarity index 100% rename from views/disk-images/add.pug rename to src/views/disk-images/add.pug diff --git a/views/disk-images/list.pug b/src/views/disk-images/list.pug similarity index 100% rename from views/disk-images/list.pug rename to src/views/disk-images/list.pug diff --git a/src/views/error.jsx b/src/views/error.jsx new file mode 100644 index 0000000..95ca28c --- /dev/null +++ b/src/views/error.jsx @@ -0,0 +1,15 @@ +"use strict"; + +const React = require("react"); + +module.exports = { + template: function ErrorPage({ error }) { + return ( +
+

An error occurred.

+

{ error.message }

+
{ error.stack }
+
+ ); + } +}; diff --git a/views/error.pug b/src/views/error.pug similarity index 100% rename from views/error.pug rename to src/views/error.pug diff --git a/src/views/hardware/storage-devices/list.jsx b/src/views/hardware/storage-devices/list.jsx new file mode 100644 index 0000000..c34b0e9 --- /dev/null +++ b/src/views/hardware/storage-devices/list.jsx @@ -0,0 +1,109 @@ +"use strict"; + +const React = require("react"); + +const Layout = require("../../layout"); +const gql = require("../../../graphql/tag"); + +module.exports = { + query: gql` + query { + hardware { + drives { + path + model + modelFamily + } + } + } + `, + template: function StorageDeviceList({data}) { + return ( + +
+
    + {data.hardware.drives.map((drive) => { + return
  • + {drive.path}: {drive.model} ({drive.modelFamily}) +
  • ; + })} +
+
+
+ ); + } +}; + + + + +// extends ../../layout + +// block content + +// h2 Fixed drives + +// //- FIXME: Partitions with mountpoints +// table.drives +// tr +// th SMART +// th Device +// th Total size +// th RPM +// th Serial number +// th Model +// th Family +// th Firmware version +// for device in devices.filter((device) => device.removable === false) +// tr(class=(device.children.length > 0 ? "hasPartitions" : null)) +// td(class=`smart ${device.smartStatus}`, rowspan=(1 + device.children.length)) +// td= device.name +// td= device.size +// td #{device.information.rpm} RPM +// td= device.information.serialNumber +// td= device.information.model +// td= device.information.modelFamily +// td= device.information.firmwareVersion + +// for partition, i in device.children +// tr.partition(class=(i === device.children.length - 1) ? "last" : null) +// td= partition.name +// td= partition.size +// td(colspan=5) +// if partition.mountpoint != null +// = partition.mountpoint +// else +// span.notMounted (not mounted) + + +// //- tr.partition +// //- td(colspan=8)= JSON.stringify(partition) +// tr +// th(colspan=2) Total +// td= totalFixedStorage +// td(colspan=5).hidden +// tr.smartStatus +// th(colspan=2).healthy Healthy +// td= totalHealthyFixedStorage +// td(colspan=5).hidden +// tr.smartStatus +// th(colspan=2).atRisk At-risk +// td= totalDeterioratingFixedStorage +// td(colspan=5).hidden +// tr.smartStatus +// th(colspan=2).failing Failing +// td= totalFailingFixedStorage +// td(colspan=5).hidden + +// h2 Removable drives + +// table +// tr +// th Path +// th Total size +// th Mounted at +// for device in devices.filter((device) => device.type === "loopDevice") +// tr +// td= device.path +// td= device.size +// td= device.mountpoint \ No newline at end of file diff --git a/views/hardware/storage-devices/list.pug b/src/views/hardware/storage-devices/list.pug similarity index 100% rename from views/hardware/storage-devices/list.pug rename to src/views/hardware/storage-devices/list.pug diff --git a/views/index.pug b/src/views/index.pug similarity index 100% rename from views/index.pug rename to src/views/index.pug diff --git a/views/instances/list.pug b/src/views/instances/list.pug similarity index 100% rename from views/instances/list.pug rename to src/views/instances/list.pug diff --git a/src/views/layout.jsx b/src/views/layout.jsx new file mode 100644 index 0000000..8edb322 --- /dev/null +++ b/src/views/layout.jsx @@ -0,0 +1,45 @@ +"use strict"; + +const React = require("react"); +const classnames = require("classnames"); +// const {LocalsContext} = require("../express-async-react"); + +function MenuItem({ path, children }) { + let isActive = false; // FIXME + + return ( +
+ + {children} + +
+ ); +} + +module.exports = function Layout({ children }) { + // let locals = React.useContext(LocalsContext); + + return ( + + + CVM + + + +
+

CVM

+ Disk Images + Instances + Users +
+ +
+ {children} +
+ +