feature/node-rewrite
Sven Slootweg 3 years ago
parent f08044baf3
commit a93e3cf8dd

@ -91,6 +91,9 @@ table.drives td.smart.DETERIORATING {
table.drives td.smart.FAILING {
background-color: rgb(230, 0, 0);
}
table.drives td.smart.UNKNOWN {
background-color: rgb(177, 177, 177);
}
table.drives .hasPartitions td:not(.smart), table.drives .partition:not(.last) td:not(.smart) {
border-bottom-color: transparent;
}
@ -119,6 +122,9 @@ table.drives th.atRisk {
table.drives th.failing {
color: rgb(194, 0, 0);
}
table.drives th.unknown {
color: rgb(59, 59, 59);
}
.stacktrace {
white-space: pre-wrap;

@ -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;;AAKD;EACC;;AAIF;EACC;EACA;;AAEA;EACC;;AAGD;EACC;;AAIF;EACC;;AAEA;EACC;;AAKD;EACC;;AAGD;EACC;;AAGD;EACC;;;AAKH;EACC;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC","file":"style.css"}
{"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;;AAEA;EACC","file":"style.css"}

@ -0,0 +1,13 @@
"use strict";
const Promise = require("bluebird");
const nvmeCli = require("../../../packages/exec-nvme-cli");
module.exports = function () {
return function (controllerPaths) {
return Promise.map(controllerPaths, (path) => {
return nvmeCli.identifyController({ devicePath: path });
});
};
};

@ -12,6 +12,7 @@ let dataSourceFactories = {
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() {

@ -4,6 +4,7 @@ const Promise = require("bluebird");
const dlayerSource = require("../../packages/dlayer-source");
const treecutter = require("../../packages/treecutter");
const upperSnakeCase = require("../../packages/upper-snake-case");
const { B } = require("../../packages/unit-bytes-iec");
const types = require("./");
@ -38,11 +39,22 @@ module.exports = function Drive ({ path }) {
return resultArray.flat();
});
},
$sources: {
lsblk: {
[dlayerSource.ID]: { path },
size: "size"
size: async function (_, { $getProperty, sources }) {
if (await $getProperty(this, "interface") === "nvme") {
return Promise.try(() => {
return sources.nvmeIdentifyController.load(path);
}).then((result) => {
return result.totalSpace;
});
} else {
return Promise.try(() => {
return sources.lsblk.load({ path: path });
}).then((result) => {
return result.size;
});
}
},
$sources: {
smartctlScan: {
[dlayerSource.ID]: path,
interface: "interface"
@ -72,8 +84,10 @@ module.exports = function Drive ({ path }) {
smartFunctioning: (attributes) => {
return (attributes.isOK);
},
smartAttributes: (attributes) => {
if (attributes.isOK) {
smartAttributes: (attributesResult) => {
if (attributesResult.isOK) {
let attributes = attributesResult.value();
return attributes.map((attribute) => {
return {
... attribute,
@ -85,10 +99,10 @@ module.exports = function Drive ({ path }) {
return [];
}
},
smartHealth: (attributes) => {
if (attributes.isOK) {
smartHealth: (attributesResult) => {
if (attributesResult.isOK) {
let attributes = attributesResult.value();
// FIXME: This is getting values in an inconsistent format? Different for SATA vs. NVMe
console.log("foo", {attributes});
let failed = attributes.filter((item) => {
return (item.failingNow === true || item.failedBefore === true);
});
@ -105,8 +119,8 @@ module.exports = function Drive ({ path }) {
return "HEALTHY";
}
} else {
// If we can't get SMART data, the only safe assumption is that it must be failing
return "FAILING";
// We can't get SMART data
return "UNKNOWN";
}
}
}

@ -9,6 +9,7 @@ const ID = Symbol("dlayer-source object ID");
const AllowErrors = Symbol("dlayer-source allow-errors marker");
// TODO: Make more readable
// TODO: Refactor allowErrors logic so that it's actually part of the internal $getProperty implementation in dlayer itself, and this abstraction uses that tool?
module.exports = {
withSources: function withSources(schemaObject) {
@ -35,24 +36,29 @@ module.exports = {
}
}).then((result) => {
// console.log(`Result [${source}|${util.inspect(properties[ID])}] ${util.inspect(result)}`);
// The AllowErrors option is set when a source definition has its own way to deal with (allowable) errors. Instead of simply propagating the error for all affected attributes, it calls the attribute handlers with the Result (or returns `undefined` if only a property is specified).
// TODO: How to deal with null results? Allow them or not? Make it an option?
if (result.isError) {
if (result.isOK && result.value() == null) {
// TODO: Change implementation to allow `Result.ok(null|undefined)` but not `null|undefined` directly?
throw new Error(`Null-ish result returned for ID ${util.inspect(properties[ID])} from source '${source}'; this is not allowed, and there is probably a bug in your code. Please file a ticket if you have a good usecase for null-ish results!`);
} else if (properties[AllowErrors] === true && typeof selector !== "string") {
// Custom selectors always receive the Result as-is
return selector(result);
} else if (result.isError) {
if (properties[AllowErrors] === true) {
// This option is set when a source definition has its own way to deal with (allowable) errors. Instead of simply propagating the error for all affected attributes, it calls the attribute handlers with the Result (or returns `undefined` if only a property is specified).
return (typeof selector === "string")
? undefined
: selector(result);
return undefined;
} else {
// This is equivalent to a `throw`, and so we just propagate it
return result;
}
} else if (result.value() != null) {
} else {
// This is to support property name shorthand used in place of a selector function
return (typeof selector === "string")
? result.value()[selector]
: selector(result.value());
if (typeof selector === "string") {
return result.value()[selector];
} else {
throw new Error(`Null-ish result returned for ID ${util.inspect(properties[ID])} from source '${source}'; this is not allowed, and there is probably a bug in your code. Please file a ticket if you have a good usecase for null-ish results!`);
return selector(result.value());
}
}
});
};

@ -119,6 +119,9 @@ function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
let { schemaKey, handler, args, isRecursive, allowErrors, isLeaf } = analyzeQueryKey(schemaObject, queryObject, queryKey);
if (handler != null) {
let nextQueryPath = queryPath.concat([ queryKey ]);
let nextSchemaPath = schemaPath.concat([ schemaKey ]);
let promise = Promise.try(() => {
// This calls the data provider in the schema
return Result.wrapAsync(() => maybeCall(handler, [ args, context ], schemaObject));
@ -126,9 +129,6 @@ function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
if (result.isOK) {
let value = result.value();
let nextQueryPath = queryPath.concat([ queryKey ]);
let nextSchemaPath = schemaPath.concat([ schemaKey ]);
return Promise.try(() => {
if (!isLeaf && value != null) {
let effectiveSubquery = (isRecursive)
@ -143,7 +143,8 @@ function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
return Promise.try(() => {
return evaluate(item, effectiveSubquery, context, elementQueryPath, elementSchemaPath);
}).tapCatch((error) => {
assignErrorPath(error, elementQueryPath, elementSchemaPath);
// FIXME: Verify that this is no longer needed, since moving the path-assigning logic
// assignErrorPath(error, elementQueryPath, elementSchemaPath);
});
} else {
return evaluate(item, effectiveSubquery, context, nextQueryPath, nextSchemaPath);
@ -154,14 +155,12 @@ function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
return value;
}
}).then((evaluated) => {
// FIXME: Verify that this is still necessary here
if (allowErrors) {
return Result.ok(evaluated);
} else {
return evaluated;
}
}).tapCatch((error) => {
// FIXME: Chain properly
assignErrorPath(error, nextQueryPath, nextSchemaPath);
});
} else {
let error = result.error();
@ -176,6 +175,9 @@ function evaluate(schemaObject, queryObject, context, queryPath, schemaPath) {
throw error;
}
}
}).tapCatch((error) => {
// FIXME: Chain properly
assignErrorPath(error, nextQueryPath, nextSchemaPath);
});
return [ queryKey, promise ];

@ -1,15 +1,12 @@
"use strict";
const Promise = require("bluebird");
const execBinary = require("../exec-binary");
const { B } = require("../unit-bytes-iec");
const createJSONParser = require("../text-parser-json");
const thirdFourthByteMask = parseInt("11111111111111110000000000000000", 2);
const secondByteMask = parseInt("00000000000000001111111100000000", 2);
const firstByteMask = parseInt("00000000000000000000000011111111", 2);
const fieldMapping = {
module.exports = {
vid: "vendorID",
ssvi: "subsystemVendorID",
sn: { name: "serialNumber", transform: (string) => string.trim() },
@ -83,8 +80,8 @@ const fieldMapping = {
// mtfa,
// hmpre,
// hmmin,
tnvmcap: { name: "totalSpace", transformer: (value) => B(value) },
unvmcap: { name: "freeSpace", transformer: (value) => B(value) },
tnvmcap: { name: "totalSpace", transform: (value) => B(value) },
unvmcap: { name: "freeSpace", transform: (value) => B(value) },
// TOOD:
// rpmbs,
// edstt,
@ -129,36 +126,3 @@ const fieldMapping = {
// ofcs,
// psds
};
module.exports = {
identifyController: function (path) {
return Promise.try(() => {
return execBinary([ "nvme", "id-ctrl" ], [ path ])
.asRoot()
.withFlags({ "output-format": "json" })
.requireOnStderr(createJSONParser())
.execute();
}).then((output) => {
let result = {};
for (let key of Object.keys(output)) {
let mapping = fieldMapping[key];
if (mapping != null) {
let { name, transform } = (typeof mapping === "string")
? { name: mapping, transform: (value) => value }
: { name: mapping.name, transform: mapping.transform };
result[name] = transform(output[key]);
}
}
// TODO: Warn on unrecognized keys
return result;
}).catch((error) => {
console.dir(error);
});
}
};
// module.exports.identifyController("/dev/nvme0").then((result) => console.dir(result, { depth: null }))

@ -3,6 +3,9 @@
const Promise = require("bluebird");
const execAll = require("execall");
const execBinary = require("../exec-binary");
const createJSONParser = require("../text-parser-json");
const controllerFieldMapping = require("./controller-field-mapping");
function createNamespaceParser() {
return {
@ -38,5 +41,33 @@ module.exports = {
}).then((output) => {
return output.result.namespaces;
});
},
identifyController: function ({ devicePath }) {
return Promise.try(() => {
return execBinary([ "nvme", "id-ctrl" ], [ devicePath ])
.asRoot()
.withFlags({ "output-format": "json" })
.requireOnStdout(createJSONParser())
.execute();
}).then(({ result }) => {
let transformed = {};
for (let key of Object.keys(result)) {
let mapping = controllerFieldMapping[key];
if (mapping != null) {
let { name, transform } = (typeof mapping === "string")
? { name: mapping, transform: (value) => value }
: { name: mapping.name, transform: mapping.transform };
transformed[name] = transform(result[key]);
}
}
// TODO: Warn on unrecognized keys
return transformed;
// }).catch((error) => {
// console.dir(error);
});
}
};

@ -124,6 +124,10 @@ table.drives {
&.FAILING {
background-color: rgb(230, 0, 0);
}
&.UNKNOWN {
background-color: rgb(177, 177, 177);
}
}
.hasPartitions, .partition:not(.last) {
@ -165,6 +169,10 @@ table.drives {
&.failing {
color: rgb(194, 0, 0);
}
&.unknown {
color: rgb(59, 59, 59);
}
}
}

@ -51,7 +51,7 @@ function PartitionEntry({partition, isLast}) {
</td>
<td>
<PartitionIndent>
{partition.size.toString()}
{partition.size.toDisplay(2).toString()}
</PartitionIndent>
</td>
<td colSpan={5}>
@ -139,12 +139,13 @@ module.exports = {
}
},
template: function StorageDeviceList({data}) {
let drivesByStatus = splitFilterN(data.hardware.drives, [ "HEALTHY", "DETERIORATING", "FAILING" ], (drive) => drive.smartHealth);
let drivesByStatus = splitFilterN(data.hardware.drives, [ "HEALTHY", "DETERIORATING", "FAILING", "UNKNOWN" ], (drive) => drive.smartHealth);
let totalStorage = sumDriveSizes(data.hardware.drives);
let totalHealthyStorage = sumDriveSizes(drivesByStatus.HEALTHY);
let totalAtRiskStorage = sumDriveSizes(drivesByStatus.DETERIORATING);
let totalFailingStorage = sumDriveSizes(drivesByStatus.FAILING);
let totalUnknownStorage = sumDriveSizes(drivesByStatus.UNKNOWN);
return (
<Layout title="Storage Devices">
@ -172,6 +173,9 @@ module.exports = {
<TallyRow label="Failing" rowClass="smartStatus" labelClass="failing">
{totalFailingStorage.toDisplay(2).toString()}
</TallyRow>
<TallyRow label="Unknown" rowClass="smartStatus" labelClass="unknown">
{totalUnknownStorage.toDisplay(2).toString()}
</TallyRow>
</table>
</Layout>
);

Loading…
Cancel
Save