diff --git a/package.json b/package.json index 5dc2d88..1049a7c 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "pug": "^2.0.0-beta6", "rfr": "^1.2.3", "scrypt-for-humans": "^2.0.5", + "snake-case": "^2.1.0", "split": "^1.0.0", "sse-channel": "^3.1.1", "through2": "^2.0.1", diff --git a/src/graphql-test.js b/src/graphql-test.js index 8d8057e..668af6a 100644 --- a/src/graphql-test.js +++ b/src/graphql-test.js @@ -12,6 +12,7 @@ const matchOrError = require("./match-or-error"); const lsblk = require("./wrappers/lsblk"); const smartctl = require("./wrappers/smartctl"); const lvm = require("./wrappers/lvm"); +const upperSnakeCase = require("./upper-snake-case"); function gql(strings) { return strings.join(""); @@ -312,6 +313,34 @@ function createDrive({ path }) { formFactor: "formFactor", ataVersion: "ataVersion", sataVersion: "sataVersion" + }, + smartctlAttributes: { + [ID]: path, + smartAttributes: (attributes) => { + return attributes.map((attribute) => { + return Object.assign({}, attribute, { + type: upperSnakeCase(attribute.type), + updatedWhen: upperSnakeCase(attribute.updatedWhen) + }); + }); + }, + smartHealth: (attributes) => { + let failed = attributes.filter((item) => { + return (item.failingNow === true || item.failedBefore === true); + }); + + let deteriorating = attributes.filter((item) => { + return (item.type === "preFail" && item.worstValueSeen < 100); + }); + + if (failed.length > 0) { + return "FAILING"; + } else if (deteriorating.length > 0) { + return "DETERIORATING"; + } else { + return "HEALTHY"; + } + } } }); } @@ -402,39 +431,52 @@ return Promise.try(() => { let query = gql` # query SomeDrives($drivePaths: [String]) { query SomeDrives { - # hardware { - # drives(paths: $drivePaths) { - # path - # interface + hardware { + drives { + path + interface - # model - # modelFamily - # smartAvailable - # smartEnabled - # serialNumber - # wwn - # firmwareVersion - # size - # rpm - # logicalSectorSize - # physicalSectorSize - # formFactor - # ataVersion - # sataVersion - - # blockDevice { - # removable - - # children { - # name - # mountpoint - # size - # } - # } - # } - # } + model + modelFamily + smartAvailable + smartEnabled + serialNumber + wwn + firmwareVersion + size + rpm + logicalSectorSize + physicalSectorSize + formFactor + ataVersion + sataVersion + + smartHealth + # smartAttributes { + # name + # type + # value + # failingNow + + # flags { + # affectsPerformance + # indicatesFailure + # } + # } + + # blockDevice { + # removable + + # children { + # name + # mountpoint + # size + # } + # } + } + } - resources { + # resources { # blockDevices { # name # mountpoint @@ -455,30 +497,30 @@ return Promise.try(() => { # } # } - lvm { - physicalVolumes { - path - - blockDevice { - name - deviceNumber - } - - volumeGroup { - name - } - - format - size - freeSpace - duplicate - allocatable - used - exported - missing - } - } - } + # lvm { + # physicalVolumes { + # path + + # blockDevice { + # name + # deviceNumber + # } + + # volumeGroup { + # name + # } + + # format + # size + # freeSpace + # duplicate + # allocatable + # used + # exported + # missing + # } + # } + # } } `; diff --git a/src/routes/storage-devices.js b/src/routes/storage-devices.js index 90ac99a..ebc43df 100644 --- a/src/routes/storage-devices.js +++ b/src/routes/storage-devices.js @@ -7,29 +7,11 @@ const smartctl = require("../wrappers/smartctl"); const lvm = require("../wrappers/lvm"); const {B} = require("../units/bytes/iec"); -/* FIXME: Move this to GraphQL API */ -function getSmartStatus(smartData) { - let failed = smartData.filter((item) => { - return (item.failingNow === true || item.failedBefore === true); - }); - - let deteriorating = smartData.filter((item) => { - return (item.type === "preFail" && item.worstValueSeen < 100); - }); - - if (failed.length > 0) { - return "failed"; - } else if (deteriorating.length > 0) { - return "deteriorating"; - } else { - return "healthy"; - } -} - 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, { diff --git a/src/schemas/main.gql b/src/schemas/main.gql index 68e746e..948ca59 100644 --- a/src/schemas/main.gql +++ b/src/schemas/main.gql @@ -252,10 +252,34 @@ type SmartAttributeFlags { indicatesFailure: Boolean! } +enum SmartAttributeType { + PRE_FAIL + OLD_AGE +} + +enum SmartAttributeUpdateType { + ALWAYS + OFFLINE +} + +enum SmartHealth { + HEALTHY + DETERIORATING + FAILING +} + type SmartAttribute { id: Int! name: String! - flags: SmartAttributeFlags + flags: SmartAttributeFlags! + value: Int! + rawValue: String! + worstValueSeen: Int! + failureThreshold: Int! + type: SmartAttributeType! + failingNow: Boolean! + failedBefore: Boolean! + updatedWhen: SmartAttributeUpdateType! } type BlockDevice { @@ -276,6 +300,8 @@ type PhysicalDrive { blockDevice: BlockDevice! smartAvailable: Boolean! smartEnabled: Boolean + smartHealth: SmartHealth + smartAttributes: [SmartAttribute!]! model: String modelFamily: String serialNumber: String @@ -288,7 +314,6 @@ type PhysicalDrive { formFactor: String ataVersion: String sataVersion: String - smartAttributes: [SmartAttribute!]! } type LVMPhysicalVolume { diff --git a/src/upper-snake-case.js b/src/upper-snake-case.js new file mode 100644 index 0000000..4071278 --- /dev/null +++ b/src/upper-snake-case.js @@ -0,0 +1,7 @@ +"use strict"; + +const snakeCase = require("snake-case"); + +module.exports = function upperSnakeCase(value) { + return snakeCase(value).toUpperCase(); +}; \ No newline at end of file diff --git a/src/wrappers/smartctl.js b/src/wrappers/smartctl.js index 1b542e4..72dae69 100644 --- a/src/wrappers/smartctl.js +++ b/src/wrappers/smartctl.js @@ -53,6 +53,7 @@ module.exports = { "Old_age": "oldAge" }), failingNow: (failedWhen === "FAILING_NOW"), + /* TODO: Should the below include the FAILING_NOW state? */ failedBefore: (failedWhen === "In_the_past"), updatedWhen: mapValue(updatedWhen, { "Always": "always", diff --git a/yarn.lock b/yarn.lock index a76fd7a..361416e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6193,6 +6193,11 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= + lowercase-keys@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -6624,6 +6629,13 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + node-gyp@^3.3.1: version "3.8.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" @@ -8671,6 +8683,13 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +snake-case@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f" + integrity sha1-Qb2xtz8w7GagTU4srRt2OH1NbZ8= + dependencies: + no-case "^2.2.0" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"