From a2fd705153e4e301d2ca276092a8665ba7bf0e19 Mon Sep 17 00:00:00 2001 From: Steve-Mcl Date: Mon, 28 Mar 2022 18:49:56 +0100 Subject: [PATCH] Improve diagnostics content --- .../editor-api/lib/admin/diagnostics.js | 34 ++- .../@node-red/editor-api/lib/admin/index.js | 5 +- .../@node-red/runtime/lib/api/diagnostics.js | 231 ++++++++++++------ 3 files changed, 176 insertions(+), 94 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/diagnostics.js b/packages/node_modules/@node-red/editor-api/lib/admin/diagnostics.js index dd37e0fe4..3ad83fc63 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/diagnostics.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/diagnostics.js @@ -1,27 +1,23 @@ -var apiUtils = require("@node-red/editor-api/lib/util"); -/** @type {runtime.RuntimeModule} */var runtimeAPI; - - +let runtimeAPI; +let settings; +const apiUtil = require("../util"); module.exports = { - init: function(/** @type {runtime.RuntimeModule} */_runtimeAPI) { + init: function(_settings, _runtimeAPI) { + settings = _settings; runtimeAPI = _runtimeAPI; }, - getBasicReport: function(req, res) { - var opts = { + getReport: function(req, res) { + const diagnosticsOptions = settings.diagnosticsOptions || {}; + const opts = { user: req.user, - scope: "basic" + scope: diagnosticsOptions.level || "basic" } - runtimeAPI.diagnostics.get(opts).then(function(result) { - res.json(result); - }); - }, - getAdminReport: function(req, res) { - var opts = { - user: req.user, - scope: "admin" + if(diagnosticsOptions.enabled === false || diagnosticsOptions.enabled === "false") { + apiUtil.rejectHandler(req, res, {message: "disabled", status: 403, code: "diagnosticsOptions.enabled" }) + } else { + runtimeAPI.diagnostics.get(opts) + .then(function(result) { res.json(result); }) + .catch(err => apiUtil.rejectHandler(req, res, err)) } - runtimeAPI.diagnostics.get(opts).then(function(result) { - res.json(result); - }); } } diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js index a6e42fdfc..87fd0dec0 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js @@ -35,7 +35,7 @@ module.exports = { context.init(runtimeAPI); info.init(settings,runtimeAPI); plugins.init(runtimeAPI); - diagnostics.init(runtimeAPI); + diagnostics.init(settings, runtimeAPI); var needsPermission = auth.needsPermission; @@ -97,8 +97,7 @@ module.exports = { adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler); adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler); - adminApp.get("/diagnostics/basic", needsPermission("settings.read"), diagnostics.getBasicReport, apiUtil.errorHandler); - adminApp.get("/diagnostics/admin", needsPermission("flows.write"), diagnostics.getAdminReport, apiUtil.errorHandler); + adminApp.get("/diagnostics", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler); return adminApp; } diff --git a/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js b/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js index 8440bd85e..f8ab9bbaf 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js +++ b/packages/node_modules/@node-red/runtime/lib/api/diagnostics.js @@ -1,73 +1,176 @@ -/** - * @mixin @node-red/diagnostics - * @namespace RED.runtime.diagnostics - */ +const os = require('os'); +const fs = require('fs'); -var runtime; +let runtime; +let isContainerCached; +let isWSLCached; -var util = require("@node-red/util").util; +const isInWsl = () => { + if (isWSLCached === undefined) { + isWSLCached = getIsInWSL(); + } + return isWSLCached; + function getIsInWSL() { + if (process.platform !== 'linux') { + return false; + } + try { + if (os.release().toLowerCase().includes('microsoft')) { + if (isInContainer()) { + return false; + } + return true; + } + return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft') ? !isInContainer() : false; + } catch (_) { + return false; + } + } +}; + +const isInContainer = () => { + if (isContainerCached === undefined) { + isContainerCached = hasDockerEnv() || hasDockerCGroup(); + } + return isContainerCached; + function hasDockerEnv() { + try { + fs.statSync('/.dockerenv'); + return true; + } catch { + return false; + } + } + function hasDockerCGroup() { + try { + const s = fs.readFileSync('/proc/self/cgroup', 'utf8'); + if (s.includes('docker')) { + return "docker" + } else if (s.includes('kubepod')) { + return "kubepod" + } else if (s.includes('lxc')) { + return "lxc" + } + } catch { + return false; + } + } +} function buildDiagnosticReport(scope, callback) { - var basic = { - "report": "diagnostics", - "scope": scope, - "runtime": { - version: runtime.settings.version, - isStarted: runtime.isStarted() + const modules = {}; + const nl = runtime.nodes.getNodeList(); + for (let i = 0; i < nl.length; i++) { + if (modules[nl[i].module]) { + continue; + } + modules[nl[i].module] = nl[i].version + } + + const now = new Date(); + const report = { + report: "diagnostics", + scope: scope, + version: runtime.settings.version, + isStarted: runtime.isStarted(), + containerised: isInContainer(), + wsl: isInWsl(), + time: { + timestamp: now.valueOf(), + utc: "" + now, + locale: now.toLocaleString(), }, - settings: { - available: runtime.settings.available(), - apiMaxLength: runtime.settings.apiMaxLength || "NO SETTING", - coreNodesDir: runtime.settings.coreNodesDir, - contextStorage: listContextModules(), - debugMaxLength: runtime.settings.debugMaxLength, - editorTheme: runtime.settings.editorTheme, - flowFile: runtime.settings.flowFile, - disableEditor:runtime.settings.disableEditor, - debugMaxLength:runtime.settings.debugMaxLength, - - httpAdminRoot: runtime.settings.httpAdminRoot, - httpAdminCors: runtime.settings.httpAdminCors ? "HAS SETTING": "NOT SET", - httpNodeAuth: runtime.settings.httpNodeAuth ? "HAS SETTING": "NOT SET", - - httpNodeRoot: runtime.settings.httpNodeRoot, - httpNodeCors: runtime.settings.httpNodeCors ? "HAS SETTING": "NOT SET", + intl: Intl.DateTimeFormat().resolvedOptions(), + nodejs: { + version: process.version, + arch: process.arch, + platform: process.platform, + memoryUsage: process.memoryUsage(), + }, + os: { + totalmem: os.totalmem(), + freemem: os.freemem(), + arch: os.arch(), + loadavg: os.loadavg(), + platform: os.platform(), + release: os.release(), + type: os.type(), + uptime: os.uptime(), + version: os.version(), + }, + runtime: { + modules: modules, + settings: { + available: runtime.settings.available(), + apiMaxLength: runtime.settings.apiMaxLength || "NO SETTING", + //coreNodesDir: runtime.settings.coreNodesDir, + disableEditor: runtime.settings.disableEditor, + contextStorage: listContextModules(), + debugMaxLength: runtime.settings.debugMaxLength || "NO SETTING", + editorTheme: runtime.settings.editorTheme || "NO SETTING", + flowFile: runtime.settings.flowFile || "NO SETTING", + mqttReconnectTime: runtime.settings.mqttReconnectTime || "NO SETTING", + serialReconnectTime: runtime.settings.serialReconnectTime || "NO SETTING", - httpStatic: runtime.settings.httpStatic, - httpStaticCors: runtime.settings.httpStaticCors, + adminAuth: runtime.settings.adminAuth ? "HAS SETTING" : "NO SETTING", - mqttReconnectTime: runtime.settings.mqttReconnectTime, + httpAdminRoot: runtime.settings.adminAuth ? "HAS SETTING" : "NO SETTING", + httpAdminCors: runtime.settings.httpAdminCors ? "HAS SETTING" : "NO SETTING", + httpNodeAuth: runtime.settings.httpNodeAuth ? "HAS SETTING" : "NO SETTING", + httpAdminRoot: runtime.settings.httpAdminRoot || "NO SETTING", + httpAdminCors: runtime.settings.httpAdminCors ? "HAS SETTING" : "NO SETTING", - uiHost: runtime.settings.uiHost ? "HAS SETTING": "NOT SET", - uiPort: runtime.settings.uiPort ? "HAS SETTING": "NOT SET", - userDir: runtime.settings.userDir ? "HAS SETTING": "NOT SET", + httpNodeRoot: runtime.settings.httpNodeRoot || "NO SETTING", + httpNodeCors: runtime.settings.httpNodeCors ? "HAS SETTING" : "NO SETTING", - version: runtime.settings.version - } - } - var admin = {}; - if(scope == "admin") { - admin = { - httpAdminCors: runtime.settings.httpAdminCors ? runtime.settings.httpAdminCors : "NOT SET", - httpNodeCors: runtime.settings.httpNodeCors ? runtime.settings.httpNodeCors : "NOT SET", - uiHost: runtime.settings.uiHost ? runtime.settings.uiHost : "NOT SET", - uiPort: runtime.settings.uiPort ? runtime.settings.uiPort : "NOT SET", - userDir: runtime.settings.userDir ? runtime.settings.userDir : "NOT SET", + httpStatic: runtime.settings.httpStatic ? "HAS SETTING" : "NO SETTING", + httpStatic: runtime.settings.httpStaticRoot || "NO SETTING", + httpStaticCors: runtime.settings.httpStaticCors ? "HAS SETTING" : "NO SETTING", + + uiHost: runtime.settings.uiHost ? "HAS SETTING" : "NO SETTING", + uiPort: runtime.settings.uiPort ? "HAS SETTING" : "NO SETTING", + userDir: runtime.settings.userDir ? "HAS SETTING" : "NO SETTING", + } } } - var report = Object.assign({}, admin, basic); + if (scope == "admin") { + const moreSettings = { + adminAuth_type: (runtime.settings.adminAuth && runtime.settings.adminAuth.type) ? runtime.settings.adminAuth.type : "NO SETTING", + httpAdminCors: runtime.settings.httpAdminCors ? runtime.settings.httpAdminCors : "NO SETTING", + httpNodeCors: runtime.settings.httpNodeCors ? runtime.settings.httpNodeCors : "NO SETTING", + httpStaticCors: runtime.settings.httpStaticCors ? "HAS SETTING" : "NO SETTING", + settingsFile: runtime.settings.settingsFile ? runtime.settings.settingsFile : "NO SETTING", + uiHost: runtime.settings.uiHost ? runtime.settings.uiHost : "NO SETTING", + uiPort: runtime.settings.uiPort ? runtime.settings.uiPort : "NO SETTING", + userDir: runtime.settings.userDir ? runtime.settings.userDir : "NO SETTING", + } + const moreNodejs = { + execPath: process.execPath, + pid: process.pid, + } + const moreOs = { + cpus: os.cpus(), + homedir: os.homedir(), + hostname: os.hostname(), + networkInterfaces: os.networkInterfaces(), + } + report.runtime.settings = Object.assign({}, report.runtime.settings, moreSettings); + report.nodejs = Object.assign({}, report.nodejs, moreNodejs); + report.os = Object.assign({}, report.os, moreOs); + } callback(report); + /** gets a sanitised list containing only the module name */ function listContextModules() { - var keys = Object.keys(runtime.settings.contextStorage); - var result = {}; + const keys = Object.keys(runtime.settings.contextStorage); + const result = {}; keys.forEach(e => { result[e] = { - module: runtime.settings.contextStorage[e].module + module: String(runtime.settings.contextStorage[e].module) } }) return result; @@ -75,40 +178,24 @@ function buildDiagnosticReport(scope, callback) { } -var api = module.exports = { +module.exports = { init: function (_runtime) { runtime = _runtime; }, /** * Gets the node-red diagnostics report - * @param {{scope: string}} - settings - * @return {Promise} - the diagnostics information + * @param {{scope: string}} opts - settings + * @return {Promise} the diagnostics information * @memberof @node-red/diagnostics */ get: async function (opts) { return new Promise(function (resolve, reject) { opts = opts || {} - var scope = opts.scope; try { - if (scope === 'admin') { - //admin level info - runtime.log.audit({ event: "diagnostics.get", scope: "admin" }, opts.req); - buildDiagnosticReport(scope, (report) => resolve(report)); - } else if (scope === 'detail') { - //detail! - runtime.log.audit({ event: "diagnostics.get", scope: "detail" }, opts.req); - buildDiagnosticReport(scope, (report) => resolve(report)); - } else if (scope === 'basic') { - //basic! - runtime.log.audit({ event: "diagnostics.get", scope: "basic" }, opts.req); - buildDiagnosticReport(scope, (report) => resolve(report)); - - } else { - runtime.log.audit({ event: "diagnostics.get", scope: scope }, opts.req); - resolve({}); - } + runtime.log.audit({ event: "diagnostics.get", scope: opts.scope }, opts.req); + buildDiagnosticReport(opts.scope, (report) => resolve(report)); } catch (error) { - runtime.log.audit({ event: "diagnostics.get", scope: scope }, opts.req); + error.status = 500; reject(error); } })