From b3c7bcfab067c96987bc980ac63f9615b7050704 Mon Sep 17 00:00:00 2001 From: Jason Clark Date: Thu, 21 Dec 2017 09:21:36 -0800 Subject: [PATCH] Create views for platform information Create CpuDetailsView to show model and speed of cpus Create EnvDetailsView to show environment variables Create NodeDetailsView to show running node version Create SystemDetailsView to show running platform details Create UserDetailsView to show user information --- lib/constants.js | 3 + lib/layout-config-schema.json | 100 +++++++++++++++++++++ lib/views/base-details-view.js | 54 +++++++++++ lib/views/cpu-details-view.js | 37 ++++++++ lib/views/env-details-view.js | 47 ++++++++++ lib/views/index.js | 11 +++ lib/views/node-details-view.js | 59 ++++++++++++ lib/views/system-details-view.js | 54 +++++++++++ lib/views/user-details-view.js | 59 ++++++++++++ test/lib/views/cpu-details-view.spec.js | 44 +++++++++ test/lib/views/env-details-view.spec.js | 40 +++++++++ test/lib/views/node-details-view.spec.js | 48 ++++++++++ test/lib/views/system-details-view.spec.js | 52 +++++++++++ test/lib/views/user-details-view.spec.js | 40 +++++++++ 14 files changed, 648 insertions(+) create mode 100644 lib/views/base-details-view.js create mode 100644 lib/views/cpu-details-view.js create mode 100644 lib/views/env-details-view.js create mode 100644 lib/views/node-details-view.js create mode 100644 lib/views/system-details-view.js create mode 100644 lib/views/user-details-view.js create mode 100644 test/lib/views/cpu-details-view.spec.js create mode 100644 test/lib/views/env-details-view.spec.js create mode 100644 test/lib/views/node-details-view.spec.js create mode 100644 test/lib/views/system-details-view.spec.js create mode 100644 test/lib/views/user-details-view.spec.js diff --git a/lib/constants.js b/lib/constants.js index 2ea66c1..5a638ca 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -23,6 +23,8 @@ var AGGREGATE_TIME_LEVELS = [ "3600000" ]; +var MILLISECONDS_PER_SECOND = 1000; + // this array object is used to reduce ms to its highest human-readable form // see lib/providers/metrics-provider.js::getTimeIndexLabel var TIME_SCALES = [ @@ -49,5 +51,6 @@ var TIME_SCALES = [ module.exports = { AGGREGATE_TIME_LEVELS: AGGREGATE_TIME_LEVELS, + MILLISECONDS_PER_SECOND: MILLISECONDS_PER_SECOND, TIME_SCALES: TIME_SCALES }; diff --git a/lib/layout-config-schema.json b/lib/layout-config-schema.json index ee34fef..a93d8fd 100644 --- a/lib/layout-config-schema.json +++ b/lib/layout-config-schema.json @@ -27,6 +27,16 @@ "$ref": "#/definitions/lineGraphView" }, { "$ref": "#/definitions/customView" + }, { + "$ref": "#/definitions/cpuDetailsView" + }, { + "$ref": "#/definitions/envDetailsView" + }, { + "$ref": "#/definitions/nodeDetailsView" + }, { + "$ref": "#/definitions/systemDetailsView" + }, { + "$ref": "#/definitions/userDetailsView" }] } } @@ -125,6 +135,96 @@ }, "required": ["module"] }, + "cpuDetailsView": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "borderColor": { + "type": "string" + }, + "type": { + "enum": ["cpuDetails"] + }, + "position": { + "$ref": "#/definitions/position" + } + }, + "required": ["type"] + }, + "envDetailsView": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "borderColor": { + "type": "string" + }, + "type": { + "enum": ["envDetails"] + }, + "position": { + "$ref": "#/definitions/position" + } + }, + "required": ["type"] + }, + "nodeDetailsView": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "borderColor": { + "type": "string" + }, + "type": { + "enum": ["nodeDetails"] + }, + "position": { + "$ref": "#/definitions/position" + } + }, + "required": ["type"] + }, + "systemDetailsView": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "borderColor": { + "type": "string" + }, + "type": { + "enum": ["systemDetails"] + }, + "position": { + "$ref": "#/definitions/position" + } + }, + "required": ["type"] + }, + "userDetailsView": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "borderColor": { + "type": "string" + }, + "type": { + "enum": ["userDetails"] + }, + "position": { + "$ref": "#/definitions/position" + } + }, + "required": ["type"] + }, "position": { "oneOf": [{ "type": "object", diff --git a/lib/views/base-details-view.js b/lib/views/base-details-view.js new file mode 100644 index 0000000..2713921 --- /dev/null +++ b/lib/views/base-details-view.js @@ -0,0 +1,54 @@ +"use strict"; + +var blessed = require("blessed"); +var _ = require("lodash"); +var BaseView = require("./base-view"); + +var BaseDetailsView = function BaseDetailsView(options) { + BaseView.call(this, options); + + this.screen = options.parent.screen; + this.node = blessed.box(this.layoutConfig); + this.parent.append(this.node); + + this.refreshContent(); + this.recalculatePosition(); +}; + +BaseDetailsView.prototype = Object.create(BaseView.prototype); + +BaseDetailsView.prototype.refreshContent = function () { + this.node.setContent(this._getBoxContent(this.getDetails())); + this.screen.render(); +}; + +BaseDetailsView.prototype.getDetails = function () { + return []; +}; + +/** + * Given data and optional filters, return the content for a box. + * + * @param {Object[]} data + * This is the array of label/data objects that define each data + * point for the box. + * + * @returns {String} + * The content string for the box is returned. + */ +BaseDetailsView.prototype._getBoxContent = function (data) { + var longestLabel = _.reduce(data, function (prev, detail) { + return Math.max(prev, detail.label.length); + }, 0); + + var getFormattedContent = function (prev, details) { + prev += "{cyan-fg}{bold}" + details.label + "{/}" + + _.repeat(" ", longestLabel - details.label.length + 1) + + "{green-fg}" + details.data + "{/}\n"; + return prev; + }; + + return _.trimEnd(_.reduce(data, getFormattedContent, ""), "\n"); +}; + +module.exports = BaseDetailsView; diff --git a/lib/views/cpu-details-view.js b/lib/views/cpu-details-view.js new file mode 100644 index 0000000..4f6528c --- /dev/null +++ b/lib/views/cpu-details-view.js @@ -0,0 +1,37 @@ +"use strict"; + +var os = require("os"); +var _ = require("lodash"); +var BaseDetailsView = require("./base-details-view"); + +var CpuDetailsView = function CpuDetailsView(options) { + BaseDetailsView.call(this, options); +}; + +CpuDetailsView.prototype = Object.create(BaseDetailsView.prototype); + +CpuDetailsView.prototype.getDetails = function () { + var cpuInfo = os.cpus(); + + return _.map(cpuInfo, function (info, index) { + return { + label: "[" + index + "]", + data: info.model + " " + info.speed + }; + }); +}; + +CpuDetailsView.prototype.getDefaultLayoutConfig = function () { + return { + label: " CPU(s) ", + border: "line", + tags: true, + style: { + border: { + fg: "white" + } + } + }; +}; + +module.exports = CpuDetailsView; diff --git a/lib/views/env-details-view.js b/lib/views/env-details-view.js new file mode 100644 index 0000000..17fa26f --- /dev/null +++ b/lib/views/env-details-view.js @@ -0,0 +1,47 @@ +"use strict"; + +var _ = require("lodash"); +var BaseDetailsView = require("./base-details-view"); + +var EnvDetailsView = function EnvDetailsView(options) { + BaseDetailsView.call(this, options); +}; + +EnvDetailsView.prototype = Object.create(BaseDetailsView.prototype); + +EnvDetailsView.prototype.getDefaultLayoutConfig = function () { + return { + label: " Environment Variables ", + border: "line", + style: { + border: { + fg: "white" + } + }, + tags: true, + scrollable: true, + keys: true, + input: true, + scrollbar: { + style: { + fg: "white", + inverse: true + }, + track: { + ch: ":", + fg: "cyan" + } + } + }; +}; + +EnvDetailsView.prototype.getDetails = function () { + return _.map(process.env, function (value, key) { + return { + label: key, + data: value + }; + }); +}; + +module.exports = EnvDetailsView; diff --git a/lib/views/index.js b/lib/views/index.js index 050542b..4159dea 100644 --- a/lib/views/index.js +++ b/lib/views/index.js @@ -6,8 +6,18 @@ var MemoryGaugeView = require("./memory-gauge-view"); var MemoryGraphView = require("./memory-graph-view"); var CpuView = require("./cpu-view"); var BaseView = require("./base-view"); +var CpuDetailsView = require("./cpu-details-view"); +var EnvDetailsView = require("./env-details-view"); +var NodeDetailsView = require("./node-details-view"); +var SystemDetailsView = require("./system-details-view"); +var UserDetailsView = require("./user-details-view"); var VIEW_MAP = { + cpuDetails: CpuDetailsView, + envDetails: EnvDetailsView, + nodeDetails: NodeDetailsView, + systemDetails: SystemDetailsView, + userDetails: UserDetailsView, log: StreamView, cpu: CpuView, memory: MemoryGaugeView, @@ -16,6 +26,7 @@ var VIEW_MAP = { }; var getConstructor = function (options) { + options = options || {}; if (VIEW_MAP[options.type]) { return VIEW_MAP[options.type]; } else if (options.module) { diff --git a/lib/views/node-details-view.js b/lib/views/node-details-view.js new file mode 100644 index 0000000..dafa332 --- /dev/null +++ b/lib/views/node-details-view.js @@ -0,0 +1,59 @@ +"use strict"; + +var BaseDetailsView = require("./base-details-view"); +var time = require("../time"); +var MILLISECONDS_PER_SECOND = require("../constants").MILLISECONDS_PER_SECOND; +var UPTIME_INTERVAL_MS = MILLISECONDS_PER_SECOND; + +var NodeDetailsView = function NodeDetailsView(options) { + BaseDetailsView.call(this, options); + + this.setupdate(); + this.node.on("attach", this.setupdate.bind(this)); + + this.node.on("detach", function () { + if (this.uptimeInterval) { + clearInterval(this.uptimeInterval); + delete this.uptimeInterval; + } + }.bind(this)); +}; + +NodeDetailsView.prototype = Object.create(BaseDetailsView.prototype); + +NodeDetailsView.prototype.setupdate = function () { + this.uptimeInterval = this.uptimeInterval || setInterval(function () { + this.refreshContent(); + }.bind(this), UPTIME_INTERVAL_MS); +}; + +NodeDetailsView.prototype.getDetails = function () { + return [ + { + label: "Version", + data: process.version + }, { + label: "LTS", + data: process.release.lts + }, { + label: "Uptime", + data: time.getLabel(process.uptime() * MILLISECONDS_PER_SECOND) + } + ]; +}; + +NodeDetailsView.prototype.getDefaultLayoutConfig = function () { + return { + label: " Node ", + border: "line", + tags: true, + height: "shrink", + style: { + border: { + fg: "white" + } + } + }; +}; + +module.exports = NodeDetailsView; diff --git a/lib/views/system-details-view.js b/lib/views/system-details-view.js new file mode 100644 index 0000000..55682ee --- /dev/null +++ b/lib/views/system-details-view.js @@ -0,0 +1,54 @@ +"use strict"; + +var os = require("os"); +var prettyBytes = require("pretty-bytes"); +var BaseDetailsView = require("./base-details-view"); + +var SystemDetailsView = function SystemDetailsView(options) { + BaseDetailsView.call(this, options); +}; + +SystemDetailsView.prototype = Object.create(BaseDetailsView.prototype); + +SystemDetailsView.prototype.getDetails = function () { + return [ + { + label: "Architecture", + data: os.arch() + }, { + label: "Endianness", + data: os.endianness() === "BE" ? "Big Endian" : "Little Endian" + }, { + label: "Host Name", + data: os.hostname() + }, { + label: "Total Memory", + data: prettyBytes(os.totalmem()) + }, { + label: "Platform", + data: os.platform() + }, { + label: "Release", + data: os.release() + }, { + label: "Type", + data: os.type() + } + ]; +}; + +SystemDetailsView.prototype.getDefaultLayoutConfig = function () { + return { + label: " System ", + border: "line", + tags: true, + height: "shrink", + style: { + border: { + fg: "white" + } + } + }; +}; + +module.exports = SystemDetailsView; diff --git a/lib/views/user-details-view.js b/lib/views/user-details-view.js new file mode 100644 index 0000000..216de27 --- /dev/null +++ b/lib/views/user-details-view.js @@ -0,0 +1,59 @@ +"use strict"; + +var os = require("os"); +var BaseDetailsView = require("./base-details-view"); + +var UserDetailsView = function UserDetailsView(options) { + BaseDetailsView.call(this, options); +}; + +UserDetailsView.prototype = Object.create(BaseDetailsView.prototype); + +UserDetailsView.prototype.getDefaultLayoutConfig = function () { + return { + label: " User ", + border: "line", + tags: true, + height: "shrink", + style: { + border: { + fg: "white" + } + } + }; +}; + +UserDetailsView.prototype.getDetails = function () { + // Node version 6 added userInfo function + if (!os.userInfo) { + return [ + { + label: "User Information", + data: "Not supported on this version of Node" + } + ]; + } + + var userInfo = os.userInfo({ encoding: "utf8" }); + + return [ + { + label: "User Name", + data: userInfo.username + }, { + label: "Home", + data: userInfo.homedir + }, { + label: "User ID", + data: userInfo.uid + }, { + label: "Group ID", + data: userInfo.gid + }, { + label: "Shell", + data: userInfo.shell + } + ]; +}; + +module.exports = UserDetailsView; diff --git a/test/lib/views/cpu-details-view.spec.js b/test/lib/views/cpu-details-view.spec.js new file mode 100644 index 0000000..81d9c5e --- /dev/null +++ b/test/lib/views/cpu-details-view.spec.js @@ -0,0 +1,44 @@ +"use strict"; + +var expect = require("chai").expect; +var sinon = require("sinon"); + +var CpuDetailsView = require("../../../lib/views/cpu-details-view"); +var utils = require("../../utils"); + +describe("CpuDetailsView", function () { + + var sandbox; + var testContainer; + var view; + + before(function () { + sandbox = sinon.sandbox.create(); + }); + + beforeEach(function () { + utils.stubWidgets(sandbox); + testContainer = utils.getTestContainer(sandbox); + view = new CpuDetailsView({ + layoutConfig: { + getPosition: sandbox.stub() + }, + parent: testContainer + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe("getDetails", function () { + it("should include labels", function () { + var details = view.getDetails(); + expect(details).to.be.an("array"); + var labels = details.map(function (detail) { + return detail.label; + }).sort(); + expect(labels).to.include("[0]"); + }); + }); +}); diff --git a/test/lib/views/env-details-view.spec.js b/test/lib/views/env-details-view.spec.js new file mode 100644 index 0000000..c3ac4f1 --- /dev/null +++ b/test/lib/views/env-details-view.spec.js @@ -0,0 +1,40 @@ +"use strict"; + +var expect = require("chai").expect; +var sinon = require("sinon"); + +var EnvDetailsView = require("../../../lib/views/env-details-view"); +var utils = require("../../utils"); + +describe("EnvDetailsView", function () { + + var sandbox; + var testContainer; + var view; + + before(function () { + sandbox = sinon.sandbox.create(); + }); + + beforeEach(function () { + utils.stubWidgets(sandbox); + testContainer = utils.getTestContainer(sandbox); + view = new EnvDetailsView({ + layoutConfig: { + getPosition: sandbox.stub() + }, + parent: testContainer + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe("getDetails", function () { + it("should include labels", function () { + var details = view.getDetails(); + expect(details).to.be.an("array"); + }); + }); +}); diff --git a/test/lib/views/node-details-view.spec.js b/test/lib/views/node-details-view.spec.js new file mode 100644 index 0000000..ca630cc --- /dev/null +++ b/test/lib/views/node-details-view.spec.js @@ -0,0 +1,48 @@ +"use strict"; + +var expect = require("chai").expect; +var sinon = require("sinon"); + +var NodeDetailsView = require("../../../lib/views/node-details-view"); +var utils = require("../../utils"); + +describe("NodeDetailsView", function () { + + var sandbox; + var testContainer; + var view; + + before(function () { + sandbox = sinon.sandbox.create(); + }); + + beforeEach(function () { + utils.stubWidgets(sandbox); + testContainer = utils.getTestContainer(sandbox); + view = new NodeDetailsView({ + layoutConfig: { + getPosition: sandbox.stub() + }, + parent: testContainer + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe("getDetails", function () { + it("should include labels", function () { + var details = view.getDetails(); + expect(details).to.be.an("array"); + var labels = details.map(function (detail) { + return detail.label; + }).sort(); + expect(labels).to.eql([ + "LTS", + "Uptime", + "Version" + ]); + }); + }); +}); diff --git a/test/lib/views/system-details-view.spec.js b/test/lib/views/system-details-view.spec.js new file mode 100644 index 0000000..997f463 --- /dev/null +++ b/test/lib/views/system-details-view.spec.js @@ -0,0 +1,52 @@ +"use strict"; + +var expect = require("chai").expect; +var sinon = require("sinon"); + +var SystemDetailsView = require("../../../lib/views/system-details-view"); +var utils = require("../../utils"); + +describe("SystemDetailsView", function () { + + var sandbox; + var testContainer; + var view; + + before(function () { + sandbox = sinon.sandbox.create(); + }); + + beforeEach(function () { + utils.stubWidgets(sandbox); + testContainer = utils.getTestContainer(sandbox); + view = new SystemDetailsView({ + layoutConfig: { + getPosition: sandbox.stub() + }, + parent: testContainer + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe("getDetails", function () { + it("should include labels", function () { + var details = view.getDetails(); + expect(details).to.be.an("array"); + var labels = details.map(function (detail) { + return detail.label; + }).sort(); + expect(labels).to.eql([ + "Architecture", + "Endianness", + "Host Name", + "Platform", + "Release", + "Total Memory", + "Type" + ]); + }); + }); +}); diff --git a/test/lib/views/user-details-view.spec.js b/test/lib/views/user-details-view.spec.js new file mode 100644 index 0000000..eb9ea37 --- /dev/null +++ b/test/lib/views/user-details-view.spec.js @@ -0,0 +1,40 @@ +"use strict"; + +var expect = require("chai").expect; +var sinon = require("sinon"); + +var UserDetailsView = require("../../../lib/views/user-details-view"); +var utils = require("../../utils"); + +describe("UserDetailsView", function () { + + var sandbox; + var testContainer; + var view; + + before(function () { + sandbox = sinon.sandbox.create(); + }); + + beforeEach(function () { + utils.stubWidgets(sandbox); + testContainer = utils.getTestContainer(sandbox); + view = new UserDetailsView({ + layoutConfig: { + getPosition: sandbox.stub() + }, + parent: testContainer + }); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe("getDetails", function () { + it("should include labels", function () { + var details = view.getDetails(); + expect(details).to.be.an("array"); + }); + }); +});