From 71d48f06cdd60fa98ca2d443986dbe050e41c276 Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Sat, 12 Nov 2022 18:54:23 +0100 Subject: [PATCH] WIP, simplify architecture for now --- src/api/data-sources/index.js | 71 ++++++++++++++++ src/api/index.js | 2 +- src/api/loaders.js | 25 ------ src/api/test.js | 2 +- src/api/types/index.js | 1 + src/api/types/lvm/logical-volume.js | 58 ++++++++++++++ src/api/types/lvm/volume-group.js | 1 - src/app.js | 3 - src/packages/dlayer/index.js | 2 + src/packages/dlayer/test.js | 2 +- .../exec-lvm/commands/get-logical-volumes.js | 4 +- src/routes/index.js | 16 ++++ src/routes/instances.js | 27 ------- src/routes/resource-pools.js | 13 --- src/routes/storage-devices.js | 80 ------------------- src/views/components/debug-view.jsx | 20 +++++ src/views/error.pug | 6 -- src/views/hardware/lvm/list.jsx | 44 ++++++++++ src/views/hardware/storage-devices/list.jsx | 2 +- src/views/hardware/storage-devices/list.pug | 70 ---------------- 20 files changed, 219 insertions(+), 230 deletions(-) create mode 100644 src/api/data-sources/index.js delete mode 100644 src/api/loaders.js create mode 100644 src/api/types/lvm/logical-volume.js delete mode 100644 src/routes/instances.js delete mode 100644 src/routes/resource-pools.js delete mode 100644 src/routes/storage-devices.js create mode 100644 src/views/components/debug-view.jsx delete mode 100644 src/views/error.pug create mode 100644 src/views/hardware/lvm/list.jsx delete mode 100644 src/views/hardware/storage-devices/list.pug diff --git a/src/api/data-sources/index.js b/src/api/data-sources/index.js new file mode 100644 index 0000000..920cc7e --- /dev/null +++ b/src/api/data-sources/index.js @@ -0,0 +1,71 @@ +"use strict"; + +const Promise = require("bluebird"); +const memoizee = require("memoizee"); +const DataLoader = require("dataloader"); +const mapObj = require("map-obj"); + +const lvm = require("../../packages/exec-lvm"); +const All = require("../../packages/graphql-interface/symbols/all"); + +// This generates a (memoized) source function for commands that always produce an entire list, that needs to be filtered for the desired item(s) +function makeListCommand({ command, selectResult, selectID }) { + let commandOnce = memoizee(command); + + return function (ids) { + return Promise.try(() => { + return commandOnce(); + }).then((result) => { + if (selectResult != null) { + return selectResult(result); + } else { + return result; + } + }).then((items) => { + return ids.map((id) => { + if (id === All) { + return items; + } else { + return items.find((item) => selectID(item, id)); + } + }); + }); + }; +} + +module.exports = function createSources() { + let sources = { + lvmLogicalVolumes: makeListCommand({ + command: lvm.getLogicalVolumes, + selectResult: (result) => result.volumes, + selectID: (volume, path) => (volume.path === path) + }), + lvmPhysicalVolumes: makeListCommand({ + command: lvm.getPhysicalVolumes, + selectResult: (result) => result.volumes, + selectID: (device, path) => (device.path === path) + }), + lvmVolumeGroups: makeListCommand({ + command: lvm.getVolumeGroups, + selectResult: (result) => result.groups, + selectID: (group, name) => (group.name === name) + }), + }; + + let factoryModules = { + lsblk: require("./lsblk")(), + findmnt: require("./findmnt")(), + smartctlInfo: require("./smartctl/info")(), + smartctlScan: require("./smartctl/scan")(), + smartctlAttributes: require("./smartctl/attributes")(), + nvmeListNamespaces: require("./nvme/list-namespaces")(), + nvmeIdentifyController: require("./nvme/identify-controller")(), + }; + + return mapObj({ ... sources, ... factoryModules }, (name, factory) => { + return [ + name, + new DataLoader(factory) + ]; + }); +}; diff --git a/src/api/index.js b/src/api/index.js index 6fa7b8a..2e64229 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -3,7 +3,7 @@ const Promise = require("bluebird"); const dlayer = require("../packages/dlayer"); const All = require("../packages/graphql-interface/symbols/all"); -const loaders = require("./loaders"); +const loaders = require("./data-sources"); const types = require("./types"); function typeFromSource(source, ids, factoryFunction) { diff --git a/src/api/loaders.js b/src/api/loaders.js deleted file mode 100644 index 9002bc8..0000000 --- a/src/api/loaders.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; - -const DataLoader = require("dataloader"); -const mapObj = require("map-obj"); - -let dataSourceFactories = { - lsblk: require("./data-sources/lsblk"), - findmnt: require("./data-sources/findmnt"), - smartctlInfo: require("./data-sources/smartctl/info"), - smartctlScan: require("./data-sources/smartctl/scan"), - smartctlAttributes: require("./data-sources/smartctl/attributes"), - lvmPhysicalVolumes: require("./data-sources/lvm/physical-volumes"), - lvmVolumeGroups: require("./data-sources/lvm/volume-groups"), - nvmeListNamespaces: require("./data-sources/nvme/list-namespaces"), - nvmeIdentifyController: require("./data-sources/nvme/identify-controller"), -}; - -module.exports = function createLoaders() { - return mapObj(dataSourceFactories, (name, factory) => { - return [ - name, - new DataLoader(factory()) - ]; - }); -}; diff --git a/src/api/test.js b/src/api/test.js index c0630d4..a67f0a9 100644 --- a/src/api/test.js +++ b/src/api/test.js @@ -2,7 +2,7 @@ const Promise = require("bluebird"); const api = require("./"); -const loaders = require("./loaders"); +const loaders = require("./data-sources"); return Promise.try(() => { return api.query({ diff --git a/src/api/types/index.js b/src/api/types/index.js index 88259dc..dc4dfa1 100644 --- a/src/api/types/index.js +++ b/src/api/types/index.js @@ -6,4 +6,5 @@ Object.assign(module.exports, { Mount: require("./mount"), LVMPhysicalVolume: require("./lvm/physical-volume"), LVMVolumeGroup: require("./lvm/volume-group"), + LVMLogicalVolume: require("./lvm/logical-volume"), }); diff --git a/src/api/types/lvm/logical-volume.js b/src/api/types/lvm/logical-volume.js new file mode 100644 index 0000000..fa1d0bd --- /dev/null +++ b/src/api/types/lvm/logical-volume.js @@ -0,0 +1,58 @@ +"use strict"; + +const dlayerSource = require("../../../packages/dlayer-source"); + +const types = require(".."); + +module.exports = function LVMLogicalVolume ({ path }) { + return dlayerSource.withSources({ + $sources: { + lvmLogicalVolumes: { + [dlayerSource.ID]: path, + path: "path", + name: "name", + fullName: "fullName", + size: "size", + uuid: "uuid", + deviceMapperPath: "deviceMapperPath", + layoutAttributes: "layoutAttributes", + roles: "roles", + tags: "tags", + configurationProfile: "configurationProfile", + creationTime: "creationTime", + creationHost: "creationHost", + neededKernelModules: "neededKernelModules", + dataVolume: "dataVolume", // FIXME: Reference? + metadataVolume: "metadataVolume", // FIXME: Reference? + poolVolume: "poolVolume", // FIXME: Reference? + persistentMajorNumber: "persistentMajorNumber", + persistentMinorNumber: "persistentMinorNumber", + type: "type", + isReadOnly: "isReadOnly", + isCurrentlyReadOnly: "isCurrentlyReadOnly", + isAllocationLocked: "isAllocationLocked", + allocationPolicy: "allocationPolicy", + status: "status", + healthStatus: "healthStatus", + isInitiallySynchronized: "isInitiallySynchronized", + isCurrentlySynchronized: "isCurrentlySynchronized", + isMerging: "isMerging", + isConverting: "isConverting", + isSuspended: "isSuspended", + isActivationSkipped: "isActivationSkipped", + isOpened: "isOpened", + isActiveLocally: "isActiveLocally", + isActiveRemotely: "isActiveRemotely", + isActiveExclusively: "isActiveExclusively", + isMergeFailed: "isMergeFailed", + isSnapshotInvalid: "isSnapshotInvalid", + isLiveTablePresent: "isLiveTablePresent", + isInactiveTablePresent: "isInactiveTablePresent", + isZeroFilled: "isZeroFilled", + hasFixedMinorNumber: "hasFixedMinorNumber", + outOfSpacePolicy: "outOfSpacePolicy", + volumeGroup: (volume) => types.LVMVolumeGroup({ name: volume.volumeGroup }) + } + } + }); +}; diff --git a/src/api/types/lvm/volume-group.js b/src/api/types/lvm/volume-group.js index cdd5d16..e0d1d68 100644 --- a/src/api/types/lvm/volume-group.js +++ b/src/api/types/lvm/volume-group.js @@ -41,7 +41,6 @@ module.exports = function LVMVolumeGroup ({ name }) { isIncomplete: "isIncomplete", allocationPolicy: "allocationPolicy", mode: "mode" - // FIXME: physicalVolumes, logicalVolumes } } }); diff --git a/src/app.js b/src/app.js index 0e4bd18..7ef71f7 100644 --- a/src/app.js +++ b/src/app.js @@ -80,9 +80,6 @@ module.exports = function () { app.use(require("./routes/index")); app.use("/disk-images", require("./routes/disk-images")(state)); - app.use("/instances", require("./routes/instances")(state)); - app.use("/hardware/storage-devices", require("./routes/storage-devices")(state)); - app.use("/resource-pools", require("./routes/resource-pools")(state)); app.get("/hardware", (req, res) => { // FIXME: default to /hardware/system-information instead, when that is implemented diff --git a/src/packages/dlayer/index.js b/src/packages/dlayer/index.js index bbd112e..d747a88 100644 --- a/src/packages/dlayer/index.js +++ b/src/packages/dlayer/index.js @@ -101,6 +101,8 @@ function assignErrorPath(error, cursor) { } } +// MARKER: build a sample todo-list schema for testing out fetches, mutations, and combinations of them, including on collections + function evaluate(cursor, context) { // map query object -> result object return asyncMapObject(cursor.query, (queryKey, subquery) => { diff --git a/src/packages/dlayer/test.js b/src/packages/dlayer/test.js index 927c255..57d2d80 100644 --- a/src/packages/dlayer/test.js +++ b/src/packages/dlayer/test.js @@ -3,7 +3,7 @@ const Promise = require("bluebird"); const dlayer = require("./"); -const loaders = require("../../api/loaders"); +const loaders = require("../../api/data-sources"); let schema = { hardware: { diff --git a/src/packages/exec-lvm/commands/get-logical-volumes.js b/src/packages/exec-lvm/commands/get-logical-volumes.js index 80837f4..096c163 100644 --- a/src/packages/exec-lvm/commands/get-logical-volumes.js +++ b/src/packages/exec-lvm/commands/get-logical-volumes.js @@ -21,7 +21,7 @@ module.exports = function () { return execBinary("lvs") .asRoot() .withFlags({ - options: "lv_all" + options: "lv_all,vg_name" }) .withModifier(asJson((result) => { return { @@ -29,6 +29,8 @@ module.exports = function () { return { path: volume.lv_path, name: volume.lv_name, // NOTE: Not unique! + size: parseIECBytes(volume.lv_size), // NOTE: no totalSpace/freeSpace because that is handled by the filesystem + volumeGroup: volume.vg_name, fullName: volume.lv_full_name, uuid: volume.lv_uuid, deviceMapperPath: volume.lv_dm_path, diff --git a/src/routes/index.js b/src/routes/index.js index 2e12aea..6873c0d 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -6,4 +6,20 @@ router.get("/", (req, res) => { res.render("index"); }); +router.get("/hardware/storage-devices", (req, res) => { + res.render("hardware/storage-devices/list"); +}); + +router.get("/hardware/lvm", (req, res) => { + res.render("hardware/lvm/list"); +}); + +router.get("/resource-pools/storage", (req, res) => { + res.render("resource-pools/storage/list"); +}); + +router.get("/instances", (req, res) => { + res.render("instances/list"); +}); + module.exports = router; diff --git a/src/routes/instances.js b/src/routes/instances.js deleted file mode 100644 index fb9bea7..0000000 --- a/src/routes/instances.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -const Promise = require("bluebird"); - -module.exports = function({db}) { - let router = require("express-promise-router")(); - - router.get("/", (req, res) => { - return Promise.try(() => { - return db("instances"); - }).then((instances) => { - res.render("instances/list", { - instances: instances - }); - }); - }); - - router.get("/add", (req, res) => { - res.render("instances/add"); - }); - - router.post("/add", (req, res) => { - - }) - - return router; -} diff --git a/src/routes/resource-pools.js b/src/routes/resource-pools.js deleted file mode 100644 index 8c245b0..0000000 --- a/src/routes/resource-pools.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -const Promise = require("bluebird"); - -module.exports = function({db}) { - let router = require("express-promise-router")(); - - router.get("/storage", (req, res) => { - res.render("resource-pools/storage/list"); - }); - - return router; -}; diff --git a/src/routes/storage-devices.js b/src/routes/storage-devices.js deleted file mode 100644 index b530711..0000000 --- a/src/routes/storage-devices.js +++ /dev/null @@ -1,80 +0,0 @@ -'use strict'; - -const Promise = require("bluebird"); - -// const lsblk = require("../packages/exec-lsblk"); -// const smartctl = require("../packages/exec-smartctl"); -// const lvm = require("../packages/exec-lvm"); -// const {B} = require("../units/bytes/iec"); - -// function getStorageDevices() { -// return Promise.try(() => { -// return lsblk(); -// }).filter((device) => { -// /* FIXME: Move device type filter to GraphQL? */ -// return (device.type === "disk"); -// }).map((device) => { -// return Object.assign({}, device, { -// path: `/dev/${device.name}` -// }); -// }).map((device) => { -// /* FIXME: Check whether we need to iterate through child disks as well, when dealing with eg. RAID arrays */ -// return Promise.try(() => { -// return Promise.all([ -// smartctl.info({ devicePath: device.path }), -// smartctl.attributes({ devicePath: device.path }) -// ]); -// }).then(([info, attributes]) => { -// return Object.assign({}, device, { -// information: info, -// smartData: attributes, -// smartStatus: getSmartStatus(attributes) -// }); -// }); -// }).then((blockDevices) => { -// console.log(blockDevices); -// return blockDevices; -// }); -// } - -// function sumDriveSizes(drives) { -// return drives.reduce((total, device) => { -// return total + device.size.toB().amount; -// }, 0); -// } - -// function roundUnit(unit) { -// return Object.assign(unit, { -// amount: Math.round(unit.amount * 100) / 100 -// }); -// } - -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); - - // 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"); - }); - - return router; -} diff --git a/src/views/components/debug-view.jsx b/src/views/components/debug-view.jsx new file mode 100644 index 0000000..44aa693 --- /dev/null +++ b/src/views/components/debug-view.jsx @@ -0,0 +1,20 @@ +"use strict"; + +const React = require("react"); +const util = require("util"); +const ansiHTML = require("ansi-html-community"); +// const entities = require("entities"); + +function escape(input) { + // NOTE: This is not necessarily secure! This is just a stopgap solution (used in development-mode code only) to not break display of < and > characters until we can figure out why `entities` doesn't combine with `ansi-html-community`. + return input.replace(//g, ">"); +} + +module.exports = function DebugView({ value }) { + if (process.env.NODE_ENV !== "development") { + return "[Debug view is disabled outside of development mode. If you see this, please contact the administrator.]"; + } else { + let inspected = util.inspect(value, { colors: true, depth: null }); + return
;
+	}
+};
diff --git a/src/views/error.pug b/src/views/error.pug
deleted file mode 100644
index 10129a3..0000000
--- a/src/views/error.pug
+++ /dev/null
@@ -1,6 +0,0 @@
-extends layout
-
-block content
-	h1 An error occurred.
-	h2= error.message
-	pre= error.stack
diff --git a/src/views/hardware/lvm/list.jsx b/src/views/hardware/lvm/list.jsx
new file mode 100644
index 0000000..da633a9
--- /dev/null
+++ b/src/views/hardware/lvm/list.jsx
@@ -0,0 +1,44 @@
+"use strict";
+
+const React = require("react");
+
+const Layout = require("../layout.jsx");
+const DebugView = require("../../components/debug-view.jsx");
+
+module.exports = {
+	query: {
+		resources: {
+			lvm: {
+				volumeGroups: {
+					name: true,
+					totalSpace: true,
+					freeSpace: true,
+					physicalVolumes: {
+						path: true,
+						format: true,
+						totalSpace: true,
+						freeSpace: true,
+						status: true
+					},
+					logicalVolumes: {
+						name: true,
+						path: true,
+						size: true,
+						status: true,
+						type: true,
+						healthStatus: true,
+						isReadOnly: true,
+						creationTime: true
+					}
+				}
+			}
+		}
+	},
+	template: function ({ data }) {
+		return (
+			
+				
+			
+		);
+	}
+};
diff --git a/src/views/hardware/storage-devices/list.jsx b/src/views/hardware/storage-devices/list.jsx
index 5b5c3ee..40493f5 100644
--- a/src/views/hardware/storage-devices/list.jsx
+++ b/src/views/hardware/storage-devices/list.jsx
@@ -7,7 +7,7 @@ const splitFilterN = require("split-filter-n");
 const { B } = require("../../../packages/unit-bytes-iec");
 const treecutter = require("../../../packages/treecutter");
 
-const Layout = require("../layout");
+const Layout = require("../layout.jsx");
 
 function sum(values) {
 	return values.reduce((total, value) => total + value, 0);
diff --git a/src/views/hardware/storage-devices/list.pug b/src/views/hardware/storage-devices/list.pug
deleted file mode 100644
index bcb5e65..0000000
--- a/src/views/hardware/storage-devices/list.pug
+++ /dev/null
@@ -1,70 +0,0 @@
-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