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