From ea9a7b2c435a517a4d8fbd9ba3a80a13e5172f7d Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Wed, 5 Jul 2023 01:53:36 +0200 Subject: [PATCH] WIP --- .gitignore | 2 + api.txt | 4 + package.json | 5 +- public/css/style.css | 26 +++++++ public/css/style.css.map | 2 +- src/api/data-sources/index.js | 1 + src/api/index.js | 38 +++++++-- src/api/types/lvm/physical-volume.js | 4 +- src/app.js | 1 + src/packages/{dlayer => dlayer-old}/cursor.js | 0 .../{dlayer => dlayer-old}/deep-merge.js | 0 src/packages/{dlayer => dlayer-old}/docs.md | 0 src/packages/{dlayer => dlayer-old}/index.js | 4 +- .../{dlayer => dlayer-old}/load-modules.js | 4 +- src/packages/{dlayer => dlayer-old}/notes.txt | 0 .../{dlayer => dlayer-old}/test-modules.js | 0 src/packages/{dlayer => dlayer-old}/test.js | 2 +- src/packages/dlayer-source/index.js | 2 +- src/packages/dlayer-wrap/index.js | 4 +- .../commands/create-physical-volume.js | 2 + .../exec-lvm/commands/get-physical-volumes.js | 11 +-- src/packages/exec-lvm/errors.js | 3 +- .../modifiers/handle-device-not-found.js | 16 +++- .../modifiers/handle-device-too-small.js | 17 ++++ .../exec-nvme-cli/controller-field-mapping.js | 5 +- src/packages/make-units/index.js | 1 - src/packages/{result => result-old}/index.js | 2 - src/packages/sysquery-lvm/index.js | 64 +++++++++++++++ src/scss/style.scss | 28 +++++++ src/views/hardware/lvm/list.jsx | 55 ++++++++++++- src/views/hardware/storage-devices/list.jsx | 2 + try/api.js | 23 ++---- virtual-disks/mount.sh | 2 + virtual-disks/setup.sh | 2 + yarn.lock | 77 +++++++++++++++++++ 35 files changed, 361 insertions(+), 48 deletions(-) create mode 100644 api.txt rename src/packages/{dlayer => dlayer-old}/cursor.js (100%) rename src/packages/{dlayer => dlayer-old}/deep-merge.js (100%) rename src/packages/{dlayer => dlayer-old}/docs.md (100%) rename src/packages/{dlayer => dlayer-old}/index.js (99%) rename src/packages/{dlayer => dlayer-old}/load-modules.js (97%) rename src/packages/{dlayer => dlayer-old}/notes.txt (100%) rename src/packages/{dlayer => dlayer-old}/test-modules.js (100%) rename src/packages/{dlayer => dlayer-old}/test.js (96%) create mode 100644 src/packages/exec-lvm/modifiers/handle-device-too-small.js rename src/packages/{result => result-old}/index.js (98%) create mode 100644 src/packages/sysquery-lvm/index.js create mode 100755 virtual-disks/mount.sh create mode 100755 virtual-disks/setup.sh diff --git a/.gitignore b/.gitignore index bee10a1..bc8178e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ images disks yarn-error.log junk +virtual-disks/vdisk1 +virtual-disks/vdisk2 diff --git a/api.txt b/api.txt new file mode 100644 index 0000000..35482fb --- /dev/null +++ b/api.txt @@ -0,0 +1,4 @@ +root + hardware + blockDevices + \ No newline at end of file diff --git a/package.json b/package.json index 9d70e50..de5e189 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,12 @@ "url": "git@git.cryto.net:cvm" }, "author": "Sven Slootweg", - "license": "WTFPL", + "license": "WTFPL OR CC0-1.0", "dependencies": { "@babel/register": "^7.8.3", "@invisible/pegjs-import": "^1.1.1", "@joepie91/express-react-views": "^1.0.1", + "@joepie91/result": "^0.1.0", "@joepie91/unreachable": "^1.0.0", "@validatem/allow-extra-properties": "^0.1.0", "@validatem/anything": "^0.1.0", @@ -57,6 +58,7 @@ "debounce": "^1.0.0", "debug": "^4.1.1", "default-value": "^1.0.0", + "dlayer": "^0.1.0", "dotty": "^0.1.0", "end-of-stream": "^1.1.0", "entities": "^2.0.0", @@ -78,6 +80,7 @@ "map-obj": "^4.2.1", "match-value": "^1.1.0", "memoizee": "^0.4.14", + "merge-by-template": "^0.1.4", "nanoid": "^2.1.11", "object.fromentries": "^2.0.2", "pegjs": "^0.10.0", diff --git a/public/css/style.css b/public/css/style.css index ad6b59b..9611246 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -139,4 +139,30 @@ table.drives th.unknown { color: gray; } +.volumeGroup { + max-width: 960px; + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + gap: 0.2em 0.4em; +} + +.bar { + border: 1px solid black; + display: grid; +} + +.barSegment { + box-sizing: border-box; + font-size: 0.8em; + background-color: rgb(205, 205, 205); + padding: 0.3em 0.4em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.barSegment:nth-child(even) { + background-color: rgb(177, 177, 177); +} + /*# sourceMappingURL=style.css.map */ diff --git a/public/css/style.css.map b/public/css/style.css.map index 9f30158..6d10574 100644 --- a/public/css/style.css.map +++ b/public/css/style.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../src/scss/style.scss"],"names":[],"mappings":"AAIA;EACC,kBALqB;EAMrB;EACA;;;AAGD;EACC;;;AAOD;EACC;;;AAGD;EACC,kBAtBqB;;AAwBrB;EACC;EACA;;AAGD;EACC;;AAIA;EACC;EACA;EACA;;AAIA;AAEC;EACA,kBA3CqB;EA4CrB;;AAKD;EACC;EACA;;;AAMJ;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;EACA;;AAKD;EACC,kBArFmB;EAsFnB;EACA;;;AAKH;EACC;;AAEA;EACC;EACA;;AAGD;EACC;;AAGD;EACC;;;AAKD;EACC;;AAIA;EACC;;AAGD;EACC;;AAGD;EACC;;AAGD;EACC;;AAKD;EACC;;AAIF;EACC;EACA;;AAEA;EACC;;AAGD;EACC;;AAIF;EACC;;AAEA;EACC;;AAKD;EACC;;AAGD;EACC;;AAGD;EACC;;AAGD;EACC;;;AAKH;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC","file":"style.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../src/scss/style.scss"],"names":[],"mappings":"AAIA;EACC,kBALqB;EAMrB;EACA;;;AAGD;EACC;;;AAOD;EACC;;;AAGD;EACC,kBAtBqB;;AAwBrB;EACC;EACA;;AAGD;EACC;;AAIA;EACC;EACA;EACA;;AAIA;AAEC;EACA,kBA3CqB;EA4CrB;;AAKD;EACC;EACA;;;AAMJ;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAEA;EACC;EACA;;AAKD;EACC,kBArFmB;EAsFnB;EACA;;;AAKH;EACC;;AAEA;EACC;EACA;;AAGD;EACC;;AAGD;EACC;;;AAKD;EACC;;AAIA;EACC;;AAGD;EACC;;AAGD;EACC;;AAGD;EACC;;AAKD;EACC;;AAIF;EACC;EACA;;AAEA;EACC;;AAGD;EACC;;AAIF;EACC;;AAEA;EACC;;AAKD;EACC;;AAGD;EACC;;AAGD;EACC;;AAGD;EACC;;;AAKH;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;;AAIF;EACC;EACA;EACA;EAEA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC","file":"style.css"} \ No newline at end of file diff --git a/src/api/data-sources/index.js b/src/api/data-sources/index.js index 957d3ad..4c9efa1 100644 --- a/src/api/data-sources/index.js +++ b/src/api/data-sources/index.js @@ -29,6 +29,7 @@ function makeListCommand({ command, selectResult, selectID }) { if (id === All) { return items; } else { + // TODO: Can this be more performant? Currently it is a nested loop return items.find((item) => selectID(item) === id); } }); diff --git a/src/api/index.js b/src/api/index.js index 2e64229..a135d6b 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -1,10 +1,16 @@ "use strict"; const Promise = require("bluebird"); -const dlayer = require("../packages/dlayer"); +const dlayer = require("dlayer"); const All = require("../packages/graphql-interface/symbols/all"); const loaders = require("./data-sources"); const types = require("./types"); +const execLVM = require("../packages/exec-lvm"); +const { validateValue } = require("@validatem/core"); +const isString = require("@validatem/is-string"); +const isBoolean = require("@validatem/is-boolean"); +const required = require("@validatem/required"); +const defaultTo = require("@validatem/default-to"); function typeFromSource(source, ids, factoryFunction) { return Promise.try(() => { @@ -36,15 +42,35 @@ module.exports = function () { return typeFromSource(sources.lsblk, names, (device) => types.BlockDevice({ name: device.name })); }, lvm: { - physicalVolumes: { - $get: ({ paths }, { sources }) => { - return typeFromSource(sources.lvmPhysicalVolumes, paths, (volume) => types.LVMPhysicalVolume({ path: volume.path })); - }, - $methods: {} + physicalVolumes: ({ paths }, { sources }) => { + return typeFromSource(sources.lvmPhysicalVolumes, paths, (volume) => types.LVMPhysicalVolume({ path: volume.path })); }, volumeGroups: ({ names }, { sources }) => { return typeFromSource(sources.lvmVolumeGroups, names, (group) => types.LVMVolumeGroup({ name: group.name })); + }, + logicalVolumes: ({ paths }, { sources }) => { + return typeFromSource(sources.lvmLogicalVolumes, paths, (volume) => types.LVMPhysicalVolume({ path: volume.path })); + }, + createPhysicalVolume: (args, { sources }) => { + let { path, force } = validateValue(args, { + path: [ required, isString ], + force: [ defaultTo(false), isBoolean ] + }); + + return Promise.try(() => { + return execLVM.createPhysicalVolume({ devicePath: path, force: force }); + }).then(() => { + return types.LVMPhysicalVolume({ path }); + }); + }, + createVolumeGroup: ({ name, physicalVolumes }) => { + return Promise.try(() => { + return execLVM.createVolumeGroup({ name, physicalVolumes }); + }).then(() => { + return types.LVMVolumeGroup({ name }); + }); } + // createLogicalVolume: , }, images: { installationMedia: [], diff --git a/src/api/types/lvm/physical-volume.js b/src/api/types/lvm/physical-volume.js index 7dbbc82..87791e3 100644 --- a/src/api/types/lvm/physical-volume.js +++ b/src/api/types/lvm/physical-volume.js @@ -13,9 +13,11 @@ module.exports = function LVMPhysicalVolume ({ path }) { format: "format", totalSpace: "totalSpace", freeSpace: "freeSpace", - status: "status", isExported: "isExported", isMissing: "isMissing", + isAllocatable: "isAllocatable", + isDuplicate: "isDuplicate", + isUsed: "isUsed", volumeGroup: (volume) => types.LVMVolumeGroup({ name: volume.volumeGroup }) } } diff --git a/src/app.js b/src/app.js index 7ef71f7..8c7a068 100644 --- a/src/app.js +++ b/src/app.js @@ -39,6 +39,7 @@ module.exports = function () { return api.query(template.query, queryArguments); } }).then((result) => { + console.dir({ templateInput: result }, { depth: null }); if (result == null) { return {}; } else { diff --git a/src/packages/dlayer/cursor.js b/src/packages/dlayer-old/cursor.js similarity index 100% rename from src/packages/dlayer/cursor.js rename to src/packages/dlayer-old/cursor.js diff --git a/src/packages/dlayer/deep-merge.js b/src/packages/dlayer-old/deep-merge.js similarity index 100% rename from src/packages/dlayer/deep-merge.js rename to src/packages/dlayer-old/deep-merge.js diff --git a/src/packages/dlayer/docs.md b/src/packages/dlayer-old/docs.md similarity index 100% rename from src/packages/dlayer/docs.md rename to src/packages/dlayer-old/docs.md diff --git a/src/packages/dlayer/index.js b/src/packages/dlayer-old/index.js similarity index 99% rename from src/packages/dlayer/index.js rename to src/packages/dlayer-old/index.js index fa3d032..d671cda 100644 --- a/src/packages/dlayer/index.js +++ b/src/packages/dlayer-old/index.js @@ -4,7 +4,7 @@ const Promise = require("bluebird"); const mapObject = require("map-obj"); const syncpipe = require("syncpipe"); -const Result = require("../result"); +const Result = require("../result-old"); const createCursor = require("./cursor"); const deepMerge = require("./deep-merge"); const loadModules = require("./load-modules"); @@ -226,7 +226,7 @@ function makeEnvironment(context, getContextForModule) { module.exports = function createDLayer(options) { // options = { schema, makeContext } - let loaded = loadModules(options.modules); + let loaded = loadModules(options.modules ?? []); let schema = deepMerge(loaded.root, options.schema); return { diff --git a/src/packages/dlayer/load-modules.js b/src/packages/dlayer-old/load-modules.js similarity index 97% rename from src/packages/dlayer/load-modules.js rename to src/packages/dlayer-old/load-modules.js index 2700b4d..093e4c9 100644 --- a/src/packages/dlayer/load-modules.js +++ b/src/packages/dlayer-old/load-modules.js @@ -95,11 +95,11 @@ module.exports = function (modules) { let schemaRoots = modules.map((module) => module.root ?? {}); for (let module of modules) { - for (let [ type, factory ] of Object.entries(module.types)) { + for (let [ type, factory ] of Object.entries(module.types ?? {})) { types.add(module, type, factory); } - for (let [ type, extensions ] of Object.entries(module.extensions)) { + for (let [ type, extensions ] of Object.entries(module.extensions ?? {})) { for (let [ name, method ] of Object.entries(extensions)) { typeExtensions.add(module, type, name, method); } diff --git a/src/packages/dlayer/notes.txt b/src/packages/dlayer-old/notes.txt similarity index 100% rename from src/packages/dlayer/notes.txt rename to src/packages/dlayer-old/notes.txt diff --git a/src/packages/dlayer/test-modules.js b/src/packages/dlayer-old/test-modules.js similarity index 100% rename from src/packages/dlayer/test-modules.js rename to src/packages/dlayer-old/test-modules.js diff --git a/src/packages/dlayer/test.js b/src/packages/dlayer-old/test.js similarity index 96% rename from src/packages/dlayer/test.js rename to src/packages/dlayer-old/test.js index 57d2d80..b19605b 100644 --- a/src/packages/dlayer/test.js +++ b/src/packages/dlayer-old/test.js @@ -1,7 +1,7 @@ "use strict"; const Promise = require("bluebird"); -const dlayer = require("./"); +const dlayer = require("."); const loaders = require("../../api/data-sources"); diff --git a/src/packages/dlayer-source/index.js b/src/packages/dlayer-source/index.js index 8b8a467..5de77ce 100644 --- a/src/packages/dlayer-source/index.js +++ b/src/packages/dlayer-source/index.js @@ -3,7 +3,7 @@ const Promise = require("bluebird"); const syncpipe = require("syncpipe"); const util = require("util"); -const Result = require("../result"); +const Result = require("@joepie91/result"); const ID = Symbol("dlayer-source object ID"); const AllowErrors = Symbol("dlayer-source allow-errors marker"); diff --git a/src/packages/dlayer-wrap/index.js b/src/packages/dlayer-wrap/index.js index 457cb3f..be6a0e0 100644 --- a/src/packages/dlayer-wrap/index.js +++ b/src/packages/dlayer-wrap/index.js @@ -1,8 +1,8 @@ "use strict"; const Promise = require("bluebird"); -const dlayer = require("../dlayer"); -const Result = require("../result"); +const dlayer = require("dlayer"); +const Result = require("@joepie91/result"); module.exports = function dlayerWrap(callback, options = {}) { return Promise.try(() => { diff --git a/src/packages/exec-lvm/commands/create-physical-volume.js b/src/packages/exec-lvm/commands/create-physical-volume.js index 1aaba48..b7be4ae 100644 --- a/src/packages/exec-lvm/commands/create-physical-volume.js +++ b/src/packages/exec-lvm/commands/create-physical-volume.js @@ -8,6 +8,7 @@ const unattendedFlags = require("../modifiers/unattended-flags"); const handleDeviceNotFound = require("../modifiers/handle-device-not-found"); const handlePartitionExists = require("../modifiers/handle-partition-exists"); const handleDeviceInUse = require("../modifiers/handle-device-in-use"); +const handleDeviceTooSmall = require("../modifiers/handle-device-too-small"); module.exports = function ({ devicePath, force }) { return Promise.try(() => { @@ -17,6 +18,7 @@ module.exports = function ({ devicePath, force }) { .withModifier((force === true) ? forceFlags : unattendedFlags) .withModifier(handleDeviceNotFound(devicePath)) .withModifier(handleDeviceInUse(devicePath)) + .withModifier(handleDeviceTooSmall(devicePath)) .withModifier(handlePartitionExists(devicePath, "create a Physical Volume")) .execute(); }).then((_output) => { diff --git a/src/packages/exec-lvm/commands/get-physical-volumes.js b/src/packages/exec-lvm/commands/get-physical-volumes.js index 3c585a2..618f9f1 100644 --- a/src/packages/exec-lvm/commands/get-physical-volumes.js +++ b/src/packages/exec-lvm/commands/get-physical-volumes.js @@ -11,6 +11,9 @@ module.exports = function () { return Promise.try(() => { return execBinary("pvs") .asRoot() + .withFlags({ + options: "pv_all,vg_name" + }) .withModifier(asJson((result) => { return { volumes: result.report[0].pv.map((volume) => { @@ -21,11 +24,9 @@ module.exports = function () { // FIXME: These amounts can contain commas depending on locale (eg. https://serverfault.com/a/648302) totalSpace: parseIECBytes(volume.pv_size), freeSpace: parseIECBytes(volume.pv_free), - status: mapFlag(volume.pv_attr, 0, { - d: "DUPLICATE", - a: "ALLOCATABLE", - u: "USED" - }), + isAllocatable: (volume.pv_allocatable === "allocatable"), + isDuplicate: (volume.pv_duplicate === "duplicate"), + isUsed: (volume.pv_in_use === "used"), isExported: mapFlag(volume.pv_attr, 1, { x: true, "-": false diff --git a/src/packages/exec-lvm/errors.js b/src/packages/exec-lvm/errors.js index 7c7c2e6..ed32b49 100644 --- a/src/packages/exec-lvm/errors.js +++ b/src/packages/exec-lvm/errors.js @@ -10,5 +10,6 @@ module.exports = { InvalidVolumeGroup: errorChain.create("InvalidVolumeGroup"), PhysicalVolumeInUse: errorChain.create("PhysicalVolumeInUse"), DeviceInUse: errorChain.create("PhysicalVolumeInUse"), - IncompatibleDevice: errorChain.create("IncompatibleDevice") + IncompatibleDevice: errorChain.create("IncompatibleDevice"), + DeviceTooSmall: errorChain.create("DeviceTooSmall"), }; diff --git a/src/packages/exec-lvm/modifiers/handle-device-not-found.js b/src/packages/exec-lvm/modifiers/handle-device-not-found.js index f8bb965..1b58af4 100644 --- a/src/packages/exec-lvm/modifiers/handle-device-not-found.js +++ b/src/packages/exec-lvm/modifiers/handle-device-not-found.js @@ -6,11 +6,19 @@ const createRegexParser = require("../../text-parser-regex"); const errors = require("../errors"); module.exports = function (devicePath) { + function makeError() { + return errorResult(new errors.InvalidPath(`Specified device '${devicePath}' does not exist`, { + path: devicePath + })); + } + return function handleDeviceNotFound(command) { - return command.expectOnStderr(createRegexParser(/Device .+ not found\./, () => { - return errorResult(new errors.InvalidPath(`Specified device '${devicePath}' does not exist`, { - path: devicePath + return command + .expectOnStderr(createRegexParser(/Device .+ not found\./, () => { + return makeError(); + })) + .expectOnStderr(createRegexParser(/No device found for .+\./, () => { + return makeError(); })); - })); }; }; diff --git a/src/packages/exec-lvm/modifiers/handle-device-too-small.js b/src/packages/exec-lvm/modifiers/handle-device-too-small.js new file mode 100644 index 0000000..7e11f51 --- /dev/null +++ b/src/packages/exec-lvm/modifiers/handle-device-too-small.js @@ -0,0 +1,17 @@ +"use strict"; + +const { errorResult } = require("../../text-parser"); +const createRegexParser = require("../../text-parser-regex"); + +const errors = require("../errors"); + +module.exports = function (devicePath) { + return function handleDeviceTooSmall(command) { + return command + .expectOnStderr(createRegexParser(/Cannot use .+: device is too small \(pv_min_size\)/, () => { + return errorResult(new errors.DeviceTooSmall(`Specified device '${devicePath}' is too small to create a physical volume on`, { + path: devicePath + })); + })); + }; +}; diff --git a/src/packages/exec-nvme-cli/controller-field-mapping.js b/src/packages/exec-nvme-cli/controller-field-mapping.js index 04078e7..88cc489 100644 --- a/src/packages/exec-nvme-cli/controller-field-mapping.js +++ b/src/packages/exec-nvme-cli/controller-field-mapping.js @@ -80,8 +80,9 @@ module.exports = { // mtfa, // hmpre, // hmmin, - tnvmcap: { name: "totalSpace", transform: (value) => B(value) }, - unvmcap: { name: "freeSpace", transform: (value) => B(value) }, + // FIXME: The following two values are exported as string (in newer versions of nvme-cli), presumably for precision reasons. Eventually the units package should probably be updated to support bigints, to avoid precision loss in our own code. + tnvmcap: { name: "totalSpace", transform: (value) => B(parseInt(value)) }, + unvmcap: { name: "freeSpace", transform: (value) => B(parseInt(value)) }, // TOOD: // rpmbs, // edstt, diff --git a/src/packages/make-units/index.js b/src/packages/make-units/index.js index 6dc57fd..b5d09bf 100644 --- a/src/packages/make-units/index.js +++ b/src/packages/make-units/index.js @@ -1,7 +1,6 @@ "use strict"; /* TODO: -toDisplay conversion between unit scales (eg. IEC -> metric bytes) ensure NaN is handled correctly Track the originally-constructed value internally, so that stacked conversions can be done losslessly? diff --git a/src/packages/result/index.js b/src/packages/result-old/index.js similarity index 98% rename from src/packages/result/index.js rename to src/packages/result-old/index.js index 7d1b30b..9be77de 100644 --- a/src/packages/result/index.js +++ b/src/packages/result-old/index.js @@ -1,7 +1,5 @@ "use strict"; -const assert = require("assert"); - function createResultObject(isSuccessful, containedValue) { return { __isResultType: true, diff --git a/src/packages/sysquery-lvm/index.js b/src/packages/sysquery-lvm/index.js new file mode 100644 index 0000000..cf55538 --- /dev/null +++ b/src/packages/sysquery-lvm/index.js @@ -0,0 +1,64 @@ +"use strict"; + +module.exports = { + name: "LVM", + initialize: function () { + let types = { + "sysquery.lvm.PhysicalVolume": function PhysicalVolume({ path }) { + + }, + "sysquery.lvm.VolumeGroup": function VolumeGroup({ name }) { + + }, + "sysquery.lvm.LogicalVolume": function LogicalVolume({ path }) { + + } + }; + + return { + sources: () => { + return { + physicalVolumes: , + volumeGroups: , + logicalVolumes: + }; + }, + types: types, + extensions: { + "sysquery.core.BlockDevice": { + lvmPhysicalVolume: function (_, { $getProperty, sources }) { + + } + } + }, + root: { + resources: { + lvm: { + physicalVolumes: ({ paths }, { sources }) => { + return makeTypeFromSource({ + source: sources.physicalVolumes, + ids: paths, + make: (result) => types.PhysicalVolume({ path: result.path }), + }); + }, + volumeGroups: ({ names }, { sources }) => { + return makeTypeFromSource({ + source: sources.volumeGroups, + ids: names, + make: (result) => types.VolumeGroup({ name: result.name }), + }); + }, + logicalVolumes: ({ paths }, { sources }) => { + // FIXME: Aren't these scoped to a volume group? + return makeTypeFromSource({ + source: sources.logicalVolumes, + ids: paths, + make: (result) => types.LogicalVolume({ path: result.path }), + }); + } + } + } + } + }; + } +}; diff --git a/src/scss/style.scss b/src/scss/style.scss index e7ba345..f1edff3 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -189,3 +189,31 @@ table.drives { color: gray; } } + +.volumeGroup { + max-width: 960px; + display: grid; + grid-template-columns: auto 1fr; + + align-items: center; + gap: .2em .4em; +} + +.bar { + border: 1px solid black; + display: grid; +} + +.barSegment { + box-sizing: border-box; + font-size: .8em; + background-color: rgb(205, 205, 205); + padding: .3em .4em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &:nth-child(even) { + background-color: rgb(177, 177, 177); + } +} diff --git a/src/views/hardware/lvm/list.jsx b/src/views/hardware/lvm/list.jsx index da633a9..dd9bdf8 100644 --- a/src/views/hardware/lvm/list.jsx +++ b/src/views/hardware/lvm/list.jsx @@ -18,7 +18,9 @@ module.exports = { format: true, totalSpace: true, freeSpace: true, - status: true + isAllocatable: true, + isUsed: true, + isDuplicate: true, }, logicalVolumes: { name: true, @@ -35,8 +37,59 @@ module.exports = { } }, template: function ({ data }) { + function SegmentBar({ items }) { + let columns = items + .map((item) => `${item.value}fr`) + .join(" "); + + return ( +
+ {items.map((item) => { + return ( +
{item.label}
+ ); + })} +
+ ); + } + return ( + {data.resources.lvm.volumeGroups.map((volumeGroup) => { + return (<> +

{volumeGroup.name}

+
+
Physical volumes:
+ { + return { + key: physicalVolume.path, + value: Math.round(physicalVolume.totalSpace.toMiB().amount), + label: `${physicalVolume.path} (${physicalVolume.totalSpace.toDisplay(2)})` + }; + })} + /> +
Volume group:
+ +
Logical volumes:
+ { + return { + key: logicalVolume.name, + value: Math.round(logicalVolume.size.toMiB().amount), + label: `${logicalVolume.name} (${logicalVolume.size.toDisplay(2)})` + }; + })} + /> +
+ ); + })}
); diff --git a/src/views/hardware/storage-devices/list.jsx b/src/views/hardware/storage-devices/list.jsx index 40493f5..4a088b2 100644 --- a/src/views/hardware/storage-devices/list.jsx +++ b/src/views/hardware/storage-devices/list.jsx @@ -147,6 +147,8 @@ module.exports = { let totalFailingStorage = sumDriveSizes(drivesByStatus.FAILING); let totalUnknownStorage = sumDriveSizes(drivesByStatus.UNKNOWN); + // TODO: Show unallocated space + return ( diff --git a/try/api.js b/try/api.js index dd35e9c..ff72c92 100644 --- a/try/api.js +++ b/try/api.js @@ -2,6 +2,7 @@ require("@babel/register"); const Promise = require("bluebird"); +const errorChain = require("error-chain"); const createAPI = require("../src/api"); // const query = { @@ -41,23 +42,13 @@ const createAPI = require("../src/api"); const query = { resources: { lvm: { - physicalVolumes: { + createPhysicalVolume: { + $arguments: { + path: "/dev/loop3" + }, path: true, totalSpace: true, - freeSpace: true, - status: true, - - volumeGroup: { - name: true, - totalSpace: true, - freeSpace: true, - logicalVolumeCount: true, - mode: true, - - physicalVolumes: { - path: true - } - } + freeSpace: true } } } @@ -69,4 +60,6 @@ return Promise.try(() => { return api.query(query); }).then((result) => { console.dir(result, { depth: null }); +}).catch((error) => { + console.error(errorChain.render(error)); }); diff --git a/virtual-disks/mount.sh b/virtual-disks/mount.sh new file mode 100755 index 0000000..0d2d1e6 --- /dev/null +++ b/virtual-disks/mount.sh @@ -0,0 +1,2 @@ +sudo losetup -P /dev/loop1 vdisk1 +sudo losetup -P /dev/loop2 vdisk2 diff --git a/virtual-disks/setup.sh b/virtual-disks/setup.sh new file mode 100755 index 0000000..4402108 --- /dev/null +++ b/virtual-disks/setup.sh @@ -0,0 +1,2 @@ +fallocate -l 5G vdisk1 +fallocate -l 5G vdisk2 diff --git a/yarn.lock b/yarn.lock index ba36d1c..f319d84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -938,6 +938,11 @@ lodash.escaperegexp "^4.1.2" object-assign "^4.1.1" +"@joepie91/result@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@joepie91/result/-/result-0.1.0.tgz#187b97033edf200698ad159ea0edc907fce2cad0" + integrity sha512-2qjcinMrUV1FSA4g5AG6t32ijGTmcUzY5XIFJoNP0zQYtlM/C2NaLDcFHtwgASTMW0p3ZIkgueGlvwQe0S7Kxg== + "@joepie91/unreachable@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@joepie91/unreachable/-/unreachable-1.0.0.tgz#8032bb8a5813e81bbbe516cb3031d60818526687" @@ -1037,6 +1042,32 @@ supports-color "^7.1.0" syncpipe "^1.0.0" +"@validatem/core@^0.3.3": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@validatem/core/-/core-0.3.17.tgz#1756a7eca0523a3657794d2060273f7d42c083ef" + integrity sha512-VahE9TAKpaU13BcVQI/Dc9j/xsm/BgloRM0v1HjOMpoJ16tOkKQkUdOgiDCG4zmEek1bG3v9Zu4lS1lubgjLMw== + dependencies: + "@validatem/annotate-errors" "^0.1.2" + "@validatem/any-property" "^0.1.0" + "@validatem/error" "^1.0.0" + "@validatem/match-validation-error" "^0.1.0" + "@validatem/match-versioned-special" "^0.1.0" + "@validatem/match-virtual-property" "^0.1.0" + "@validatem/normalize-rules" "^0.1.0" + "@validatem/required" "^0.1.0" + "@validatem/validation-result" "^0.1.1" + "@validatem/virtual-property" "^0.1.0" + as-expression "^1.0.0" + assure-array "^1.0.0" + create-error "^0.3.1" + default-value "^1.0.0" + execall "^2.0.0" + flatten "^1.0.3" + indent-string "^4.0.0" + is-arguments "^1.0.4" + supports-color "^7.1.0" + syncpipe "^1.0.0" + "@validatem/default-to@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@validatem/default-to/-/default-to-0.1.0.tgz#62766a3ca24d2f61a96c713bcb629a5b3c6427c5" @@ -1194,6 +1225,11 @@ dependencies: "@validatem/error" "^1.0.0" +"@validatem/remove-nullish-items@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/remove-nullish-items/-/remove-nullish-items-0.1.0.tgz#fe1a8b64d11276b506fae2bd2c41da4985a5b5ff" + integrity sha512-cs4YSF47TA/gHnV5muSUUqGi5PwybP5ztu5SYnPKxQVTyubvcbrFat51nOvJ2PmUasyrIccoYMmATiviXkTi6g== + "@validatem/require-either@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@validatem/require-either/-/require-either-0.1.0.tgz#250e35ab06f124ea90f3925d74b5f53a083923b0" @@ -1252,6 +1288,15 @@ default-value "^1.0.0" split-filter-n "^1.1.2" +"@validatem/wrap-path@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@validatem/wrap-path/-/wrap-path-0.1.0.tgz#777998b62d3e74f2b2897c992dae9b3675161c33" + integrity sha512-6hOqydnr4u8FA0iRv8fyXxsr64T99+w/XL/fixmsgN0uqulEIwGMxCre3y9YkFNcEtysyPHkQl0CrGPcASsZxw== + dependencies: + "@validatem/annotate-errors" "^0.1.2" + "@validatem/combinator" "^0.1.2" + "@validatem/validation-result" "^0.1.2" + JSONStream@^1.0.3, JSONStream@^1.1.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -2859,6 +2904,16 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dlayer@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dlayer/-/dlayer-0.1.0.tgz#cef49d283e1a9d83a8bd69a4c01d468ddd0b898c" + integrity sha512-UrdQdihmoNbWv49YFD/MBspaS2ILEfrNBoIVvZb1fhwvnPoRQ3ZOAbyiyV1sdfTrCASWVMz4i2NFczOVoVWJxw== + dependencies: + "@joepie91/result" "^0.1.0" + bluebird "^3.4.6" + map-obj "^4.2.1" + syncpipe "^1.0.0" + dns-packet@^5.1.2: version "5.2.1" resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.2.1.tgz#26cec0be92252a1b97ed106482921192a7e08f72" @@ -5157,6 +5212,23 @@ memoizee@^0.4.14: next-tick "^1.1.0" timers-ext "^0.1.7" +merge-by-template@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/merge-by-template/-/merge-by-template-0.1.4.tgz#8a03e6383a4e2f2e4a6460bff0d6d3e7b468a535" + integrity sha512-10h5HyGLJJu1F1z02oMqpvMa6oraLr7Vp0gPxlw6Od8xlvzTFr0TQGPZXMLBmZlhZRY910AXGJ6AFc2iXGZ7uQ== + dependencies: + "@validatem/core" "^0.3.3" + "@validatem/default-to" "^0.1.0" + "@validatem/is-array" "^0.1.1" + "@validatem/is-boolean" "^0.1.1" + "@validatem/is-plain-object" "^0.1.1" + "@validatem/remove-nullish-items" "^0.1.0" + "@validatem/virtual-property" "^0.1.0" + "@validatem/wrap-path" "^0.1.0" + default-value "^1.0.0" + fromentries "^1.2.0" + range "^0.0.3" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -6346,6 +6418,11 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== +range@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/range/-/range-0.0.3.tgz#b5b8eb2463a516b624a563bd32b18fe89e70151b" + integrity sha512-OxK2nY2bmeEB4NxoBraQIBOOeOIxoBvm6yt8MA1kLappgkG3SyLf173iOtT5woWycrtESDD2g0Nl2yt8YPoUnw== + raw-body@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"