WIP, simplify architecture for now
parent
7bc156469f
commit
71d48f06cd
@ -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)
|
||||||
|
];
|
||||||
|
});
|
||||||
|
};
|
@ -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())
|
|
||||||
];
|
|
||||||
});
|
|
||||||
};
|
|
@ -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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
@ -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;
|
|
||||||
}
|
|
@ -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, "<").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 <pre className="debugPrint" dangerouslySetInnerHTML={{ __html: ansiHTML(escape(inspected)) }} />;
|
||||||
|
}
|
||||||
|
};
|
@ -1,6 +0,0 @@
|
|||||||
extends layout
|
|
||||||
|
|
||||||
block content
|
|
||||||
h1 An error occurred.
|
|
||||||
h2= error.message
|
|
||||||
pre= error.stack
|
|
@ -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 (
|
||||||
|
<Layout title="LVM">
|
||||||
|
<DebugView value={data} />
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -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
|
|
Loading…
Reference in New Issue