1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Improve diagnostics content

This commit is contained in:
Steve-Mcl 2022-03-28 18:49:56 +01:00
parent 3388f699a0
commit a2fd705153
3 changed files with 176 additions and 94 deletions

View File

@ -1,27 +1,23 @@
var apiUtils = require("@node-red/editor-api/lib/util"); let runtimeAPI;
/** @type {runtime.RuntimeModule} */var runtimeAPI; let settings;
const apiUtil = require("../util");
module.exports = { module.exports = {
init: function(/** @type {runtime.RuntimeModule} */_runtimeAPI) { init: function(_settings, _runtimeAPI) {
settings = _settings;
runtimeAPI = _runtimeAPI; runtimeAPI = _runtimeAPI;
}, },
getBasicReport: function(req, res) { getReport: function(req, res) {
var opts = { const diagnosticsOptions = settings.diagnosticsOptions || {};
const opts = {
user: req.user, user: req.user,
scope: "basic" scope: diagnosticsOptions.level || "basic"
} }
runtimeAPI.diagnostics.get(opts).then(function(result) { if(diagnosticsOptions.enabled === false || diagnosticsOptions.enabled === "false") {
res.json(result); apiUtil.rejectHandler(req, res, {message: "disabled", status: 403, code: "diagnosticsOptions.enabled" })
}); } else {
}, runtimeAPI.diagnostics.get(opts)
getAdminReport: function(req, res) { .then(function(result) { res.json(result); })
var opts = { .catch(err => apiUtil.rejectHandler(req, res, err))
user: req.user,
scope: "admin"
} }
runtimeAPI.diagnostics.get(opts).then(function(result) {
res.json(result);
});
} }
} }

View File

@ -35,7 +35,7 @@ module.exports = {
context.init(runtimeAPI); context.init(runtimeAPI);
info.init(settings,runtimeAPI); info.init(settings,runtimeAPI);
plugins.init(runtimeAPI); plugins.init(runtimeAPI);
diagnostics.init(runtimeAPI); diagnostics.init(settings, runtimeAPI);
var needsPermission = auth.needsPermission; var needsPermission = auth.needsPermission;
@ -97,8 +97,7 @@ module.exports = {
adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler); adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, 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", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler);
adminApp.get("/diagnostics/admin", needsPermission("flows.write"), diagnostics.getAdminReport, apiUtil.errorHandler);
return adminApp; return adminApp;
} }

View File

@ -1,73 +1,176 @@
/** const os = require('os');
* @mixin @node-red/diagnostics const fs = require('fs');
* @namespace RED.runtime.diagnostics
*/
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) { function buildDiagnosticReport(scope, callback) {
var basic = { const modules = {};
"report": "diagnostics", const nl = runtime.nodes.getNodeList();
"scope": scope, for (let i = 0; i < nl.length; i++) {
"runtime": { if (modules[nl[i].module]) {
version: runtime.settings.version, continue;
isStarted: runtime.isStarted() }
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: { intl: Intl.DateTimeFormat().resolvedOptions(),
available: runtime.settings.available(), nodejs: {
apiMaxLength: runtime.settings.apiMaxLength || "NO SETTING", version: process.version,
coreNodesDir: runtime.settings.coreNodesDir, arch: process.arch,
contextStorage: listContextModules(), platform: process.platform,
debugMaxLength: runtime.settings.debugMaxLength, memoryUsage: process.memoryUsage(),
editorTheme: runtime.settings.editorTheme, },
flowFile: runtime.settings.flowFile, os: {
disableEditor:runtime.settings.disableEditor, totalmem: os.totalmem(),
debugMaxLength:runtime.settings.debugMaxLength, freemem: os.freemem(),
arch: os.arch(),
httpAdminRoot: runtime.settings.httpAdminRoot, loadavg: os.loadavg(),
httpAdminCors: runtime.settings.httpAdminCors ? "HAS SETTING": "NOT SET", platform: os.platform(),
httpNodeAuth: runtime.settings.httpNodeAuth ? "HAS SETTING": "NOT SET", release: os.release(),
type: os.type(),
httpNodeRoot: runtime.settings.httpNodeRoot, uptime: os.uptime(),
httpNodeCors: runtime.settings.httpNodeCors ? "HAS SETTING": "NOT SET", 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, adminAuth: runtime.settings.adminAuth ? "HAS SETTING" : "NO SETTING",
httpStaticCors: runtime.settings.httpStaticCors,
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", httpNodeRoot: runtime.settings.httpNodeRoot || "NO SETTING",
uiPort: runtime.settings.uiPort ? "HAS SETTING": "NOT SET", httpNodeCors: runtime.settings.httpNodeCors ? "HAS SETTING" : "NO SETTING",
userDir: runtime.settings.userDir ? "HAS SETTING": "NOT SET",
version: runtime.settings.version httpStatic: runtime.settings.httpStatic ? "HAS SETTING" : "NO SETTING",
} httpStatic: runtime.settings.httpStaticRoot || "NO SETTING",
} httpStaticCors: runtime.settings.httpStaticCors ? "HAS SETTING" : "NO SETTING",
var admin = {};
if(scope == "admin") { uiHost: runtime.settings.uiHost ? "HAS SETTING" : "NO SETTING",
admin = { uiPort: runtime.settings.uiPort ? "HAS SETTING" : "NO SETTING",
httpAdminCors: runtime.settings.httpAdminCors ? runtime.settings.httpAdminCors : "NOT SET", userDir: runtime.settings.userDir ? "HAS SETTING" : "NO SETTING",
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",
} }
} }
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); callback(report);
/** gets a sanitised list containing only the module name */
function listContextModules() { function listContextModules() {
var keys = Object.keys(runtime.settings.contextStorage); const keys = Object.keys(runtime.settings.contextStorage);
var result = {}; const result = {};
keys.forEach(e => { keys.forEach(e => {
result[e] = { result[e] = {
module: runtime.settings.contextStorage[e].module module: String(runtime.settings.contextStorage[e].module)
} }
}) })
return result; return result;
@ -75,40 +178,24 @@ function buildDiagnosticReport(scope, callback) {
} }
var api = module.exports = { module.exports = {
init: function (_runtime) { init: function (_runtime) {
runtime = _runtime; runtime = _runtime;
}, },
/** /**
* Gets the node-red diagnostics report * Gets the node-red diagnostics report
* @param {{scope: string}} - settings * @param {{scope: string}} opts - settings
* @return {Promise} - the diagnostics information * @return {Promise} the diagnostics information
* @memberof @node-red/diagnostics * @memberof @node-red/diagnostics
*/ */
get: async function (opts) { get: async function (opts) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
opts = opts || {} opts = opts || {}
var scope = opts.scope;
try { try {
if (scope === 'admin') { runtime.log.audit({ event: "diagnostics.get", scope: opts.scope }, opts.req);
//admin level info buildDiagnosticReport(opts.scope, (report) => resolve(report));
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({});
}
} catch (error) { } catch (error) {
runtime.log.audit({ event: "diagnostics.get", scope: scope }, opts.req); error.status = 500;
reject(error); reject(error);
} }
}) })