mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
commit
5de078dc61
@ -165,6 +165,7 @@ module.exports = function(grunt) {
|
|||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/common/autoComplete.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
|
||||||
|
"packages/node_modules/@node-red/editor-client/src/js/ui/diagnostics.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js",
|
||||||
"packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
|
"packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js",
|
||||||
|
23
packages/node_modules/@node-red/editor-api/lib/admin/diagnostics.js
vendored
Normal file
23
packages/node_modules/@node-red/editor-api/lib/admin/diagnostics.js
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
let runtimeAPI;
|
||||||
|
let settings;
|
||||||
|
const apiUtil = require("../util");
|
||||||
|
module.exports = {
|
||||||
|
init: function(_settings, _runtimeAPI) {
|
||||||
|
settings = _settings;
|
||||||
|
runtimeAPI = _runtimeAPI;
|
||||||
|
},
|
||||||
|
getReport: function(req, res) {
|
||||||
|
const diagnosticsOpts = settings.diagnostics || {};
|
||||||
|
const opts = {
|
||||||
|
user: req.user,
|
||||||
|
scope: diagnosticsOpts.level || "basic"
|
||||||
|
}
|
||||||
|
if(diagnosticsOpts.enabled === false || diagnosticsOpts.enabled === "false") {
|
||||||
|
apiUtil.rejectHandler(req, res, {message: "diagnostics are disabled", status: 403, code: "diagnostics.disabled" })
|
||||||
|
} else {
|
||||||
|
runtimeAPI.diagnostics.get(opts)
|
||||||
|
.then(function(result) { res.json(result); })
|
||||||
|
.catch(err => apiUtil.rejectHandler(req, res, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ var context = require("./context");
|
|||||||
var auth = require("../auth");
|
var auth = require("../auth");
|
||||||
var info = require("./settings");
|
var info = require("./settings");
|
||||||
var plugins = require("./plugins");
|
var plugins = require("./plugins");
|
||||||
|
var diagnostics = require("./diagnostics");
|
||||||
|
|
||||||
var apiUtil = require("../util");
|
var apiUtil = require("../util");
|
||||||
|
|
||||||
@ -34,6 +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(settings, runtimeAPI);
|
||||||
|
|
||||||
var needsPermission = auth.needsPermission;
|
var needsPermission = auth.needsPermission;
|
||||||
|
|
||||||
@ -95,6 +97,8 @@ 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", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler);
|
||||||
|
|
||||||
return adminApp;
|
return adminApp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -940,6 +940,8 @@
|
|||||||
"format": "format JSON",
|
"format": "format JSON",
|
||||||
"rawMode": "Edit JSON",
|
"rawMode": "Edit JSON",
|
||||||
"uiMode": "Visual editor",
|
"uiMode": "Visual editor",
|
||||||
|
"rawMode-readonly": "JSON",
|
||||||
|
"uiMode-readonly": "Visual",
|
||||||
"insertAbove": "Insert above",
|
"insertAbove": "Insert above",
|
||||||
"insertBelow": "Insert below",
|
"insertBelow": "Insert below",
|
||||||
"addItem": "Add item",
|
"addItem": "Add item",
|
||||||
@ -1154,6 +1156,9 @@
|
|||||||
"start": "Start",
|
"start": "Start",
|
||||||
"next": "Next"
|
"next": "Next"
|
||||||
},
|
},
|
||||||
|
"diagnostics": {
|
||||||
|
"title": "System Info"
|
||||||
|
},
|
||||||
"languages" : {
|
"languages" : {
|
||||||
"de": "German",
|
"de": "German",
|
||||||
"en-US": "English",
|
"en-US": "English",
|
||||||
|
@ -940,6 +940,8 @@
|
|||||||
"format": "JSONフォーマット",
|
"format": "JSONフォーマット",
|
||||||
"rawMode": "JSONを編集",
|
"rawMode": "JSONを編集",
|
||||||
"uiMode": "ビジュアルエディタ",
|
"uiMode": "ビジュアルエディタ",
|
||||||
|
"rawMode-readonly": "JSON",
|
||||||
|
"uiMode-readonly": "ビジュアル",
|
||||||
"insertAbove": "上に挿入",
|
"insertAbove": "上に挿入",
|
||||||
"insertBelow": "下に挿入",
|
"insertBelow": "下に挿入",
|
||||||
"addItem": "要素を追加",
|
"addItem": "要素を追加",
|
||||||
@ -1298,22 +1300,23 @@
|
|||||||
"zoom-in": "ズームイン",
|
"zoom-in": "ズームイン",
|
||||||
"zoom-out": "ズームアウト",
|
"zoom-out": "ズームアウト",
|
||||||
"zoom-reset": "ズームリセット",
|
"zoom-reset": "ズームリセット",
|
||||||
"toggle-navigator": "ナビゲータ表示切替"
|
"toggle-navigator": "ナビゲータ表示切替",
|
||||||
|
"show-system-info": "システムインフォメーション"
|
||||||
},
|
},
|
||||||
"validator": {
|
"validator": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalid-json": "JSONデータが不正: __error__",
|
"invalid-json": "JSONデータが不正: __error__",
|
||||||
"invalid-json-prop": "__prop__: JSONデータが不正: __error__",
|
"invalid-json-prop": "__prop__: JSONデータが不正: __error__",
|
||||||
"invalid-prop": "プロパティ式が不正",
|
"invalid-prop": "プロパティ式が不正",
|
||||||
"invalid-prop-prop": "__prop__: プロパティ式が不正",
|
"invalid-prop-prop": "__prop__: プロパティ式が不正",
|
||||||
"invalid-num": "数値が不正",
|
"invalid-num": "数値が不正",
|
||||||
"invalid-num-prop": "__prop__: 数値が不正",
|
"invalid-num-prop": "__prop__: 数値が不正",
|
||||||
"invalid-regexp": "入力パターンが不正",
|
"invalid-regexp": "入力パターンが不正",
|
||||||
"invalid-regex-prop": "__prop__: 入力パターンが不正",
|
"invalid-regex-prop": "__prop__: 入力パターンが不正",
|
||||||
"missing-required-prop": "__prop__: プロパティが未設定",
|
"missing-required-prop": "__prop__: プロパティが未設定",
|
||||||
"invalid-config": "__prop__: 設定ノードが不正",
|
"invalid-config": "__prop__: 設定ノードが不正",
|
||||||
"missing-config": "__prop__: 設定ノードが存在しません",
|
"missing-config": "__prop__: 設定ノードが存在しません",
|
||||||
"validation-error": "__prop__: チェックエラー: __node__, __id__: __error__"
|
"validation-error": "__prop__: チェックエラー: __node__, __id__: __error__"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -730,6 +730,7 @@ var RED = (function() {
|
|||||||
RED.search.init();
|
RED.search.init();
|
||||||
RED.actionList.init();
|
RED.actionList.init();
|
||||||
RED.editor.init();
|
RED.editor.init();
|
||||||
|
RED.diagnostics.init();
|
||||||
RED.diff.init();
|
RED.diff.init();
|
||||||
|
|
||||||
|
|
||||||
|
61
packages/node_modules/@node-red/editor-client/src/js/ui/diagnostics.js
vendored
Normal file
61
packages/node_modules/@node-red/editor-client/src/js/ui/diagnostics.js
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
RED.diagnostics = (function () {
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (RED.settings.get('diagnostics.ui', true) === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RED.actions.add("core:show-system-info", function () { show(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
url: 'diagnostics',
|
||||||
|
success: function (data) {
|
||||||
|
var json = JSON.stringify(data || {}, "", 4);
|
||||||
|
if (json === "{}") {
|
||||||
|
json = "{\n\n}";
|
||||||
|
}
|
||||||
|
RED.editor.editJSON({
|
||||||
|
title: RED._('diagnostics.title'),
|
||||||
|
value: json,
|
||||||
|
requireValid: true,
|
||||||
|
readOnly: true,
|
||||||
|
toolbarButtons: [
|
||||||
|
{
|
||||||
|
text: RED._('clipboard.export.copy'),
|
||||||
|
icon: 'fa fa-copy',
|
||||||
|
click: function () {
|
||||||
|
RED.clipboard.copyText(json, $(this), RED._('clipboard.copyMessageValue'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: RED._('clipboard.download'),
|
||||||
|
icon: 'fa fa-download',
|
||||||
|
click: function () {
|
||||||
|
var element = document.createElement('a');
|
||||||
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(json));
|
||||||
|
element.setAttribute('download', "system-info.json");
|
||||||
|
element.style.display = 'none';
|
||||||
|
document.body.appendChild(element);
|
||||||
|
element.click();
|
||||||
|
document.body.removeChild(element);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
console.log("Unexpected error loading system info:", jqXHR.status, textStatus, errorThrown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
};
|
||||||
|
})();
|
@ -21,7 +21,9 @@
|
|||||||
'<ul id="red-ui-editor-type-json-tabs"></ul>'+
|
'<ul id="red-ui-editor-type-json-tabs"></ul>'+
|
||||||
'<div id="red-ui-editor-type-json-tab-raw" class="red-ui-editor-type-json-tab-content hide">'+
|
'<div id="red-ui-editor-type-json-tab-raw" class="red-ui-editor-type-json-tab-content hide">'+
|
||||||
'<div class="form-row" style="margin-bottom: 3px; text-align: right;">'+
|
'<div class="form-row" style="margin-bottom: 3px; text-align: right;">'+
|
||||||
'<button id="node-input-json-reformat" class="red-ui-button red-ui-button-small"><span data-i18n="jsonEditor.format"></span></button>'+
|
'<span class="button-group">'+
|
||||||
|
'<button id="node-input-json-reformat" class="red-ui-button red-ui-button-small"><span data-i18n="jsonEditor.format"></span></button>'+
|
||||||
|
'<span class="button-group">'+
|
||||||
'</div>'+
|
'</div>'+
|
||||||
'<div class="form-row node-text-editor-row">'+
|
'<div class="form-row node-text-editor-row">'+
|
||||||
'<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>'+
|
'<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>'+
|
||||||
@ -34,7 +36,7 @@
|
|||||||
|
|
||||||
var activeTab;
|
var activeTab;
|
||||||
|
|
||||||
function insertNewItem(parent,index,copyIndex) {
|
function insertNewItem(parent,index,copyIndex,readOnly) {
|
||||||
var newValue = "";
|
var newValue = "";
|
||||||
|
|
||||||
if (parent.children.length > 0) {
|
if (parent.children.length > 0) {
|
||||||
@ -60,26 +62,26 @@
|
|||||||
newKey = keyRoot+"-"+(keySuffix++);
|
newKey = keyRoot+"-"+(keySuffix++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var newItem = handleItem(newKey,newValue,parent.depth+1,parent);
|
var newItem = handleItem(newKey,newValue,parent.depth+1,parent,readOnly);
|
||||||
parent.treeList.insertChildAt(newItem, index, true);
|
parent.treeList.insertChildAt(newItem, index, true);
|
||||||
parent.treeList.expand();
|
parent.treeList.expand();
|
||||||
}
|
}
|
||||||
function showObjectMenu(button,item) {
|
function showObjectMenu(button,item,readOnly) {
|
||||||
var elementPos = button.offset();
|
var elementPos = button.offset();
|
||||||
var options = [];
|
var options = [];
|
||||||
if (item.parent) {
|
if (item.parent) {
|
||||||
options.push({id:"red-ui-editor-type-json-menu-insert-above", icon:"fa fa-toggle-up", label:RED._('jsonEditor.insertAbove'),onselect:function(){
|
options.push({id:"red-ui-editor-type-json-menu-insert-above", icon:"fa fa-toggle-up", label:RED._('jsonEditor.insertAbove'),onselect:function(){
|
||||||
var index = item.parent.children.indexOf(item);
|
var index = item.parent.children.indexOf(item);
|
||||||
insertNewItem(item.parent,index,index);
|
insertNewItem(item.parent,index,index,readOnly);
|
||||||
}});
|
}});
|
||||||
options.push({id:"red-ui-editor-type-json-menu-insert-below", icon:"fa fa-toggle-down", label:RED._('jsonEditor.insertBelow'),onselect:function(){
|
options.push({id:"red-ui-editor-type-json-menu-insert-below", icon:"fa fa-toggle-down", label:RED._('jsonEditor.insertBelow'),onselect:function(){
|
||||||
var index = item.parent.children.indexOf(item)+1;
|
var index = item.parent.children.indexOf(item)+1;
|
||||||
insertNewItem(item.parent,index,index-1);
|
insertNewItem(item.parent,index,index-1,readOnly);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
if (item.type === 'array' || item.type === 'object') {
|
if (item.type === 'array' || item.type === 'object') {
|
||||||
options.push({id:"red-ui-editor-type-json-menu-add-child", icon:"fa fa-plus", label:RED._('jsonEditor.addItem'),onselect:function(){
|
options.push({id:"red-ui-editor-type-json-menu-add-child", icon:"fa fa-plus", label:RED._('jsonEditor.addItem'),onselect:function(){
|
||||||
insertNewItem(item,item.children.length,item.children.length-1);
|
insertNewItem(item,item.children.length,item.children.length-1,readOnly);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
if (item.parent) {
|
if (item.parent) {
|
||||||
@ -121,7 +123,7 @@
|
|||||||
newKey = keyRoot+"-"+(keySuffix++);
|
newKey = keyRoot+"-"+(keySuffix++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var newItem = handleItem(newKey,convertToObject(item),item.parent.depth+1,item.parent);
|
var newItem = handleItem(newKey,convertToObject(item),item.parent.depth+1,item.parent,readOnly);
|
||||||
var index = item.parent.children.indexOf(item)+1;
|
var index = item.parent.children.indexOf(item)+1;
|
||||||
|
|
||||||
item.parent.treeList.insertChildAt(newItem, index, true);
|
item.parent.treeList.insertChildAt(newItem, index, true);
|
||||||
@ -171,24 +173,24 @@
|
|||||||
menuOptionMenu.show();
|
menuOptionMenu.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseObject(obj,depth,parent) {
|
function parseObject(obj,depth,parent,readOnly) {
|
||||||
var result = [];
|
var result = [];
|
||||||
for (var prop in obj) {
|
for (var prop in obj) {
|
||||||
if (obj.hasOwnProperty(prop)) {
|
if (obj.hasOwnProperty(prop)) {
|
||||||
result.push(handleItem(prop,obj[prop],depth,parent));
|
result.push(handleItem(prop,obj[prop],depth,parent,readOnly));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
function parseArray(obj,depth,parent) {
|
function parseArray(obj,depth,parent,readOnly) {
|
||||||
var result = [];
|
var result = [];
|
||||||
var l = obj.length;
|
var l = obj.length;
|
||||||
for (var i=0;i<l;i++) {
|
for (var i=0;i<l;i++) {
|
||||||
result.push(handleItem(i,obj[i],depth,parent));
|
result.push(handleItem(i,obj[i],depth,parent,readOnly));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
function handleItem(key,val,depth,parent) {
|
function handleItem(key,val,depth,parent,readOnly) {
|
||||||
var item = {depth:depth, type: typeof val};
|
var item = {depth:depth, type: typeof val};
|
||||||
var container = $('<span class="red-ui-editor-type-json-editor-label">');
|
var container = $('<span class="red-ui-editor-type-json-editor-label">');
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
@ -204,11 +206,14 @@
|
|||||||
if (parent && parent.type === "array") {
|
if (parent && parent.type === "array") {
|
||||||
keyLabel.addClass("red-ui-editor-type-json-editor-label-array-key")
|
keyLabel.addClass("red-ui-editor-type-json-editor-label-array-key")
|
||||||
}
|
}
|
||||||
|
if(readOnly) {
|
||||||
|
keyLabel.addClass("readonly")
|
||||||
|
}
|
||||||
keyLabel.on("click", function(evt) {
|
keyLabel.on("click", function(evt) {
|
||||||
if (item.parent.type === 'array') {
|
if (item.parent.type === 'array') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (readOnly) { return; }
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
var w = Math.max(150,keyLabel.width());
|
var w = Math.max(150,keyLabel.width());
|
||||||
@ -253,10 +258,10 @@
|
|||||||
item.expanded = depth < 2;
|
item.expanded = depth < 2;
|
||||||
item.type = "array";
|
item.type = "array";
|
||||||
item.deferBuild = depth >= 2;
|
item.deferBuild = depth >= 2;
|
||||||
item.children = parseArray(val,depth+1,item);
|
item.children = parseArray(val,depth+1,item,readOnly);
|
||||||
} else if (val !== null && item.type === "object") {
|
} else if (val !== null && item.type === "object") {
|
||||||
item.expanded = depth < 2;
|
item.expanded = depth < 2;
|
||||||
item.children = parseObject(val,depth+1,item);
|
item.children = parseObject(val,depth+1,item,readOnly);
|
||||||
item.deferBuild = depth >= 2;
|
item.deferBuild = depth >= 2;
|
||||||
} else {
|
} else {
|
||||||
item.value = val;
|
item.value = val;
|
||||||
@ -287,7 +292,11 @@
|
|||||||
//
|
//
|
||||||
var orphanedChildren;
|
var orphanedChildren;
|
||||||
var valueLabel = $('<span class="red-ui-editor-type-json-editor-label-value">').addClass(valClass).text(valValue).appendTo(container);
|
var valueLabel = $('<span class="red-ui-editor-type-json-editor-label-value">').addClass(valClass).text(valValue).appendTo(container);
|
||||||
|
if (readOnly) {
|
||||||
|
valueLabel.addClass("readonly")
|
||||||
|
}
|
||||||
valueLabel.on("click", function(evt) {
|
valueLabel.on("click", function(evt) {
|
||||||
|
if (readOnly) { return; }
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
if (valType === 'str') {
|
if (valType === 'str') {
|
||||||
@ -395,17 +404,19 @@
|
|||||||
valueLabel.hide();
|
valueLabel.hide();
|
||||||
})
|
})
|
||||||
item.gutter = $('<span class="red-ui-editor-type-json-editor-item-gutter"></span>');
|
item.gutter = $('<span class="red-ui-editor-type-json-editor-item-gutter"></span>');
|
||||||
|
if(!readOnly) {
|
||||||
if (parent) {//red-ui-editor-type-json-editor-item-handle
|
if (parent) {
|
||||||
$('<span class="red-ui-editor-type-json-editor-item-handle"><i class="fa fa-bars"></span>').appendTo(item.gutter);
|
$('<span class="red-ui-editor-type-json-editor-item-handle"><i class="fa fa-bars"></span>').appendTo(item.gutter);
|
||||||
} else {
|
} else {
|
||||||
$('<span></span>').appendTo(item.gutter);
|
$('<span></span>').appendTo(item.gutter);
|
||||||
|
}
|
||||||
|
$('<button type="button" class="editor-button editor-button-small"><i class="fa fa-caret-down"></button>').appendTo(item.gutter).on("click", function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
showObjectMenu($(this), item, readOnly);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
$('<button type="button" class="editor-button editor-button-small"><i class="fa fa-caret-down"></button>').appendTo(item.gutter).on("click", function(evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
evt.stopPropagation();
|
|
||||||
showObjectMenu($(this), item);
|
|
||||||
});
|
|
||||||
item.element = container;
|
item.element = container;
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -501,7 +512,25 @@
|
|||||||
open: function(tray) {
|
open: function(tray) {
|
||||||
var trayBody = tray.find('.red-ui-tray-body');
|
var trayBody = tray.find('.red-ui-tray-body');
|
||||||
var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
|
var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
|
||||||
|
var toolbarButtons = options.toolbarButtons || [];
|
||||||
|
if (toolbarButtons.length) {
|
||||||
|
toolbarButtons.forEach(function (button) {
|
||||||
|
var element = $('<button type="button" class="red-ui-button red-ui-button-small"> </button>')
|
||||||
|
.insertBefore("#node-input-json-reformat")
|
||||||
|
.on("click", function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (button.click !== undefined) {
|
||||||
|
button.click.call(element, evt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (button.id) { element.attr("id", button.id); }
|
||||||
|
if (button.title) { element.attr("title", button.title); }
|
||||||
|
if (button.icon) { element.append($("<i></i>").attr("class", button.icon)); }
|
||||||
|
if (button.label || button.text) {
|
||||||
|
element.append($("<span></span>").text(" " + (button.label || button.text)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
var container = $("#red-ui-editor-type-json-tab-ui-container").css({"height":"100%"});
|
var container = $("#red-ui-editor-type-json-tab-ui-container").css({"height":"100%"});
|
||||||
var filterDepth = Infinity;
|
var filterDepth = Infinity;
|
||||||
var list = $('<div class="red-ui-debug-msg-payload red-ui-editor-type-json-editor">').appendTo(container).treeList({
|
var list = $('<div class="red-ui-debug-msg-payload red-ui-editor-type-json-editor">').appendTo(container).treeList({
|
||||||
@ -531,11 +560,13 @@
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
expressionEditor = RED.editor.createEditor({
|
expressionEditor = RED.editor.createEditor({
|
||||||
id: 'node-input-json',
|
id: 'node-input-json',
|
||||||
|
value: "",
|
||||||
|
mode:"ace/mode/json",
|
||||||
value: value||"",
|
value: value||"",
|
||||||
mode:"ace/mode/json",
|
mode:"ace/mode/json",
|
||||||
|
readOnly: !!options.readOnly,
|
||||||
stateId: options.stateId,
|
stateId: options.stateId,
|
||||||
focus: true
|
focus: true
|
||||||
});
|
});
|
||||||
@ -576,7 +607,7 @@
|
|||||||
var raw = expressionEditor.getValue().trim() ||"{}";
|
var raw = expressionEditor.getValue().trim() ||"{}";
|
||||||
try {
|
try {
|
||||||
var parsed = JSON.parse(raw);
|
var parsed = JSON.parse(raw);
|
||||||
rootNode = handleItem(null,parsed,0,null);
|
rootNode = handleItem(null,parsed,0,null,options.readOnly);
|
||||||
rootNode.class = "red-ui-editor-type-json-root-node"
|
rootNode.class = "red-ui-editor-type-json-root-node"
|
||||||
list.treeList('data',[rootNode]);
|
list.treeList('data',[rootNode]);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
@ -594,12 +625,12 @@
|
|||||||
|
|
||||||
tabs.addTab({
|
tabs.addTab({
|
||||||
id: 'json-raw',
|
id: 'json-raw',
|
||||||
label: RED._('jsonEditor.rawMode'),
|
label: options.readOnly ? RED._('jsonEditor.rawMode-readonly') : RED._('jsonEditor.rawMode'),
|
||||||
content: $("#red-ui-editor-type-json-tab-raw")
|
content: $("#red-ui-editor-type-json-tab-raw")
|
||||||
});
|
});
|
||||||
tabs.addTab({
|
tabs.addTab({
|
||||||
id: 'json-ui',
|
id: 'json-ui',
|
||||||
label: RED._('jsonEditor.uiMode'),
|
label: options.readOnly ? RED._('jsonEditor.uiMode-readonly') : RED._('jsonEditor.uiMode'),
|
||||||
content: $("#red-ui-editor-type-json-tab-ui")
|
content: $("#red-ui-editor-type-json-tab-ui")
|
||||||
});
|
});
|
||||||
finishedBuild = true;
|
finishedBuild = true;
|
||||||
|
@ -701,6 +701,10 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle {
|
|||||||
border-color: $list-item-background-hover;
|
border-color: $list-item-background-hover;
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
}
|
}
|
||||||
|
&.readonly {
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.red-ui-editor-type-json-editor-item-gutter {
|
.red-ui-editor-type-json-editor-item-gutter {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
@ -720,6 +724,10 @@ div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle {
|
|||||||
> span, > button {
|
> span, > button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
&.readonly {
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
202
packages/node_modules/@node-red/runtime/lib/api/diagnostics.js
vendored
Normal file
202
packages/node_modules/@node-red/runtime/lib/api/diagnostics.js
vendored
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
const os = require('os');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
let runtime;
|
||||||
|
let isContainerCached;
|
||||||
|
let isWSLCached;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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 {locale, timeZone} = Intl.DateTimeFormat().resolvedOptions();
|
||||||
|
const report = {
|
||||||
|
report: "diagnostics",
|
||||||
|
scope: scope,
|
||||||
|
time: {
|
||||||
|
utc: now.toUTCString(),
|
||||||
|
local: now.toLocaleString(),
|
||||||
|
},
|
||||||
|
intl: {
|
||||||
|
locale, timeZone
|
||||||
|
},
|
||||||
|
nodejs: {
|
||||||
|
version: process.version,
|
||||||
|
arch: process.arch,
|
||||||
|
platform: process.platform,
|
||||||
|
memoryUsage: process.memoryUsage(),
|
||||||
|
},
|
||||||
|
os: {
|
||||||
|
containerised: isInContainer(),
|
||||||
|
wsl: isInWsl(),
|
||||||
|
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: {
|
||||||
|
isStarted: runtime.isStarted(),
|
||||||
|
modules: modules,
|
||||||
|
version: runtime.settings.version,
|
||||||
|
settings: {
|
||||||
|
available: runtime.settings.available(),
|
||||||
|
apiMaxLength: runtime.settings.apiMaxLength || "UNSET",
|
||||||
|
//coreNodesDir: runtime.settings.coreNodesDir,
|
||||||
|
disableEditor: runtime.settings.disableEditor,
|
||||||
|
contextStorage: listContextModules(),
|
||||||
|
debugMaxLength: runtime.settings.debugMaxLength || "UNSET",
|
||||||
|
editorTheme: runtime.settings.editorTheme || "UNSET",
|
||||||
|
flowFile: runtime.settings.flowFile || "UNSET",
|
||||||
|
mqttReconnectTime: runtime.settings.mqttReconnectTime || "UNSET",
|
||||||
|
serialReconnectTime: runtime.settings.serialReconnectTime || "UNSET",
|
||||||
|
|
||||||
|
adminAuth: runtime.settings.adminAuth ? "SET" : "UNSET",
|
||||||
|
|
||||||
|
httpAdminRoot: runtime.settings.httpAdminRoot || "UNSET",
|
||||||
|
httpAdminCors: runtime.settings.httpAdminCors ? "SET" : "UNSET",
|
||||||
|
httpNodeAuth: runtime.settings.httpNodeAuth ? "SET" : "UNSET",
|
||||||
|
|
||||||
|
httpNodeRoot: runtime.settings.httpNodeRoot || "UNSET",
|
||||||
|
httpNodeCors: runtime.settings.httpNodeCors ? "SET" : "UNSET",
|
||||||
|
|
||||||
|
httpStatic: runtime.settings.httpStatic ? "SET" : "UNSET",
|
||||||
|
httpStaticRoot: runtime.settings.httpStaticRoot || "UNSET",
|
||||||
|
httpStaticCors: runtime.settings.httpStaticCors ? "SET" : "UNSET",
|
||||||
|
|
||||||
|
uiHost: runtime.settings.uiHost ? "SET" : "UNSET",
|
||||||
|
uiPort: runtime.settings.uiPort ? "SET" : "UNSET",
|
||||||
|
userDir: runtime.settings.userDir ? "SET" : "UNSET",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (scope == "admin") {
|
||||||
|
// const moreSettings = {
|
||||||
|
// adminAuth_type: (runtime.settings.adminAuth && runtime.settings.adminAuth.type) ? runtime.settings.adminAuth.type : "UNSET",
|
||||||
|
// httpAdminCors: runtime.settings.httpAdminCors ? runtime.settings.httpAdminCors : "UNSET",
|
||||||
|
// httpNodeCors: runtime.settings.httpNodeCors ? runtime.settings.httpNodeCors : "UNSET",
|
||||||
|
// httpStaticCors: runtime.settings.httpStaticCors ? "SET" : "UNSET",
|
||||||
|
// settingsFile: runtime.settings.settingsFile ? runtime.settings.settingsFile : "UNSET",
|
||||||
|
// uiHost: runtime.settings.uiHost ? runtime.settings.uiHost : "UNSET",
|
||||||
|
// uiPort: runtime.settings.uiPort ? runtime.settings.uiPort : "UNSET",
|
||||||
|
// userDir: runtime.settings.userDir ? runtime.settings.userDir : "UNSET",
|
||||||
|
// }
|
||||||
|
// 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() {
|
||||||
|
const keys = Object.keys(runtime.settings.contextStorage);
|
||||||
|
const result = {};
|
||||||
|
keys.forEach(e => {
|
||||||
|
result[e] = {
|
||||||
|
module: String(runtime.settings.contextStorage[e].module)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function (_runtime) {
|
||||||
|
runtime = _runtime;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Gets the node-red diagnostics report
|
||||||
|
* @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 || {}
|
||||||
|
try {
|
||||||
|
runtime.log.audit({ event: "diagnostics.get", scope: opts.scope }, opts.req);
|
||||||
|
buildDiagnosticReport(opts.scope, (report) => resolve(report));
|
||||||
|
} catch (error) {
|
||||||
|
error.status = 500;
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
@ -29,6 +29,7 @@ var api = module.exports = {
|
|||||||
api.projects.init(runtime);
|
api.projects.init(runtime);
|
||||||
api.context.init(runtime);
|
api.context.init(runtime);
|
||||||
api.plugins.init(runtime);
|
api.plugins.init(runtime);
|
||||||
|
api.diagnostics.init(runtime);
|
||||||
},
|
},
|
||||||
|
|
||||||
comms: require("./comms"),
|
comms: require("./comms"),
|
||||||
@ -39,6 +40,7 @@ var api = module.exports = {
|
|||||||
projects: require("./projects"),
|
projects: require("./projects"),
|
||||||
context: require("./context"),
|
context: require("./context"),
|
||||||
plugins: require("./plugins"),
|
plugins: require("./plugins"),
|
||||||
|
diagnostics: require("./diagnostics"),
|
||||||
|
|
||||||
isStarted: async function(opts) {
|
isStarted: async function(opts) {
|
||||||
return runtime.isStarted();
|
return runtime.isStarted();
|
||||||
|
@ -142,6 +142,13 @@ var api = module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType();
|
safeSettings.flowEncryptionType = runtime.nodes.getCredentialKeyType();
|
||||||
|
|
||||||
|
safeSettings.diagnostics = {
|
||||||
|
//unless diagnostics.ui and diagnostics.enabled are explicitly false, they will default to true.
|
||||||
|
enabled: (runtime.settings.diagnostics && runtime.settings.diagnostics.enabled === false) ? false : true,
|
||||||
|
ui: (runtime.settings.diagnostics && runtime.settings.diagnostics.ui === false) ? false : true
|
||||||
|
}
|
||||||
|
|
||||||
runtime.settings.exportNodeSettings(safeSettings);
|
runtime.settings.exportNodeSettings(safeSettings);
|
||||||
runtime.plugins.exportPluginSettings(safeSettings);
|
runtime.plugins.exportPluginSettings(safeSettings);
|
||||||
}
|
}
|
||||||
|
@ -400,6 +400,11 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
version: externalAPI.version,
|
version: externalAPI.version,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @memberof @node-red/diagnostics
|
||||||
|
*/
|
||||||
|
diagnostics:externalAPI.diagnostics,
|
||||||
|
|
||||||
storage: storage,
|
storage: storage,
|
||||||
events: events,
|
events: events,
|
||||||
hooks: hooks,
|
hooks: hooks,
|
||||||
|
9
packages/node_modules/node-red/lib/red.js
vendored
9
packages/node_modules/node-red/lib/red.js
vendored
@ -229,5 +229,12 @@ module.exports = {
|
|||||||
* @see @node-red/editor-api_auth
|
* @see @node-red/editor-api_auth
|
||||||
* @memberof node-red
|
* @memberof node-red
|
||||||
*/
|
*/
|
||||||
auth: api.auth
|
auth: api.auth,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The editor authentication api.
|
||||||
|
* @see @node-red/editor-api_auth
|
||||||
|
* @memberof node-red
|
||||||
|
*/
|
||||||
|
get diagnostics() { return api.diagnostics }
|
||||||
};
|
};
|
||||||
|
14
packages/node_modules/node-red/settings.js
vendored
14
packages/node_modules/node-red/settings.js
vendored
@ -242,6 +242,7 @@ module.exports = {
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Runtime Settings
|
* Runtime Settings
|
||||||
* - lang
|
* - lang
|
||||||
|
* - diagnostics
|
||||||
* - logging
|
* - logging
|
||||||
* - contextStorage
|
* - contextStorage
|
||||||
* - exportGlobalContextKeys
|
* - exportGlobalContextKeys
|
||||||
@ -254,6 +255,19 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
// lang: "de",
|
// lang: "de",
|
||||||
|
|
||||||
|
/** Configure diagnostics options
|
||||||
|
* - enabled: When `enabled` is `true` (or unset), diagnostics data will
|
||||||
|
* be available at http://localhost:1880/diagnostics
|
||||||
|
* - ui: When `ui` is `true` (or unset), the action `show-system-info` will
|
||||||
|
* be available to logged in users of node-red editor
|
||||||
|
*/
|
||||||
|
diagnostics: {
|
||||||
|
/** enable or disable diagnostics endpoint. Must be set to `false` to disable */
|
||||||
|
enabled: true,
|
||||||
|
/** enable or disable diagnostics display in the node-red editor. Must be set to `false` to disable */
|
||||||
|
ui: true,
|
||||||
|
},
|
||||||
|
|
||||||
/** Configure the logging output */
|
/** Configure the logging output */
|
||||||
logging: {
|
logging: {
|
||||||
/** Only console logging is currently supported */
|
/** Only console logging is currently supported */
|
||||||
|
119
test/unit/@node-red/editor-api/lib/admin/diagnostics_spec.js
Normal file
119
test/unit/@node-red/editor-api/lib/admin/diagnostics_spec.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
const should = require("should");
|
||||||
|
const request = require('supertest');
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
|
const sinon = require('sinon');
|
||||||
|
|
||||||
|
let app;
|
||||||
|
|
||||||
|
const NR_TEST_UTILS = require("nr-test-utils");
|
||||||
|
const diagnostics = NR_TEST_UTILS.require("@node-red/editor-api/lib/admin/diagnostics");
|
||||||
|
|
||||||
|
describe("api/editor/diagnostics", function() {
|
||||||
|
before(function() {
|
||||||
|
app = express();
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.get("/diagnostics",diagnostics.getReport);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the diagnostics report when explicitly enabled', function(done) {
|
||||||
|
const settings = { diagnostics: { ui: true, enabled: true } }
|
||||||
|
const runtimeAPI = {
|
||||||
|
diagnostics: {
|
||||||
|
get: async function (opts) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
opts = opts || {}
|
||||||
|
try {
|
||||||
|
resolve({ opts: opts, a:1, b:2});
|
||||||
|
} catch (error) {
|
||||||
|
error.status = 500;
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics.init(settings, runtimeAPI);
|
||||||
|
|
||||||
|
request(app)
|
||||||
|
.get("/diagnostics")
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err || typeof res.error === "object") {
|
||||||
|
return done(err || res.error);
|
||||||
|
}
|
||||||
|
res.should.have.property("statusCode",200);
|
||||||
|
res.body.should.have.property("a",1);
|
||||||
|
res.body.should.have.property("b",2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns the diagnostics report when not explicitly enabled (implicitly enabled)', function(done) {
|
||||||
|
const settings = { diagnostics: { enabled: undefined } }
|
||||||
|
const runtimeAPI = {
|
||||||
|
diagnostics: {
|
||||||
|
get: async function (opts) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
opts = opts || {}
|
||||||
|
try {
|
||||||
|
resolve({ opts: opts, a:3, b:4});
|
||||||
|
} catch (error) {
|
||||||
|
error.status = 500;
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics.init(settings, runtimeAPI);
|
||||||
|
|
||||||
|
request(app)
|
||||||
|
.get("/diagnostics")
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err || typeof res.error === "object") {
|
||||||
|
return done(err || res.error);
|
||||||
|
}
|
||||||
|
res.should.have.property("statusCode",200);
|
||||||
|
res.body.should.have.property("a",3);
|
||||||
|
res.body.should.have.property("b",4);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should error when setting is disabled', function(done) {
|
||||||
|
const settings = { diagnostics: { ui: true, enabled: false } }
|
||||||
|
const runtimeAPI = {
|
||||||
|
diagnostics: {
|
||||||
|
get: async function (opts) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
opts = opts || {}
|
||||||
|
try {
|
||||||
|
resolve({ opts: opts});
|
||||||
|
} catch (error) {
|
||||||
|
error.status = 500;
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics.init(settings, runtimeAPI);
|
||||||
|
|
||||||
|
request(app)
|
||||||
|
.get("/diagnostics")
|
||||||
|
.expect(403)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (!err && typeof res.error !== "object") {
|
||||||
|
return done(new Error("accessing diagnostics endpoint while disabled should raise error"));
|
||||||
|
}
|
||||||
|
res.should.have.property("statusCode",403);
|
||||||
|
res.body.should.have.property("message","diagnostics are disabled");
|
||||||
|
res.body.should.have.property("code","diagnostics.disabled");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
126
test/unit/@node-red/runtime/lib/api/diagnostics_spec.js
Normal file
126
test/unit/@node-red/runtime/lib/api/diagnostics_spec.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
var should = require("should");
|
||||||
|
var sinon = require("sinon");
|
||||||
|
var NR_TEST_UTILS = require("nr-test-utils");
|
||||||
|
var diagnostics = NR_TEST_UTILS.require("@node-red/runtime/lib/api/diagnostics")
|
||||||
|
|
||||||
|
var mockLog = () => ({
|
||||||
|
log: sinon.stub(),
|
||||||
|
debug: sinon.stub(),
|
||||||
|
trace: sinon.stub(),
|
||||||
|
warn: sinon.stub(),
|
||||||
|
info: sinon.stub(),
|
||||||
|
metric: sinon.stub(),
|
||||||
|
audit: sinon.stub(),
|
||||||
|
_: function() { return "abc"}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("runtime-api/diagnostics", function() {
|
||||||
|
|
||||||
|
describe("get", function() {
|
||||||
|
before(function() {
|
||||||
|
diagnostics.init({
|
||||||
|
isStarted: () => true,
|
||||||
|
nodes: {
|
||||||
|
getNodeList: () => [{module:"node-red", version:"9.9.9"},{module:"node-red-node-inject", version:"8.8.8"}]
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
version: "7.7.7",
|
||||||
|
available: () => true,
|
||||||
|
//apiMaxLength: xxx, deliberately left blank. Should arrive in report as "UNSET"
|
||||||
|
debugMaxLength: 1111,
|
||||||
|
disableEditor: false,
|
||||||
|
flowFile: "flows.json",
|
||||||
|
mqttReconnectTime: 321,
|
||||||
|
serialReconnectTime: 432,
|
||||||
|
adminAuth: {},//should be sanitised to "SET"
|
||||||
|
httpAdminRoot: "/admin/root/",
|
||||||
|
httpAdminCors: {},//should be sanitised to "SET"
|
||||||
|
httpNodeAuth: {},//should be sanitised to "SET"
|
||||||
|
httpNodeRoot: "/node/root/",
|
||||||
|
httpNodeCors: {},//should be sanitised to "SET"
|
||||||
|
httpStatic: "/var/static/",//should be sanitised to "SET"
|
||||||
|
httpStaticRoot: "/static/root/",
|
||||||
|
httpStaticCors: {},//should be sanitised to "SET"
|
||||||
|
uiHost: "something.secret.com",//should be sanitised to "SET"
|
||||||
|
uiPort: 1337,//should be sanitised to "SET"
|
||||||
|
userDir: "/var/super/secret/",//should be sanitised to "SET",
|
||||||
|
contextStorage: {
|
||||||
|
default : { module: "memory" },
|
||||||
|
file: { module: "localfilesystem" },
|
||||||
|
secured: { module: "secure_store", user: "fred", pass: "super-duper-secret" },
|
||||||
|
},
|
||||||
|
editorTheme: {}
|
||||||
|
},
|
||||||
|
log: mockLog()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
it("returns basic user settings", function() {
|
||||||
|
return diagnostics.get({scope:"fake_scope"}).then(result => {
|
||||||
|
should(result).be.type("object");
|
||||||
|
|
||||||
|
//result.xxxxx
|
||||||
|
Object.keys(result)
|
||||||
|
const reportPropCount = Object.keys(result).length;
|
||||||
|
reportPropCount.should.eql(7);//ensure no more than 7 keys are present in the report (avoid leakage of extra info)
|
||||||
|
result.should.have.property("report","diagnostics");
|
||||||
|
result.should.have.property("scope","fake_scope");
|
||||||
|
result.should.have.property("time").type("object");
|
||||||
|
result.should.have.property("intl").type("object");
|
||||||
|
result.should.have.property("nodejs").type("object");
|
||||||
|
result.should.have.property("os").type("object");
|
||||||
|
result.should.have.property("runtime").type("object");
|
||||||
|
|
||||||
|
//result.runtime.xxxxx
|
||||||
|
const runtimeCount = Object.keys(result.runtime).length;
|
||||||
|
runtimeCount.should.eql(4);//ensure no more than 4 keys are present in runtime
|
||||||
|
result.runtime.should.have.property('isStarted',true)
|
||||||
|
result.runtime.should.have.property('modules').type("object");
|
||||||
|
result.runtime.should.have.property('settings').type("object");
|
||||||
|
result.runtime.should.have.property('version','7.7.7');
|
||||||
|
|
||||||
|
//result.runtime.modules.xxxxx
|
||||||
|
const moduleCount = Object.keys(result.runtime.modules).length;
|
||||||
|
moduleCount.should.eql(2);//ensure no more than the 2 modules specified are present
|
||||||
|
result.runtime.modules.should.have.property('node-red','9.9.9');
|
||||||
|
result.runtime.modules.should.have.property('node-red-node-inject','8.8.8');
|
||||||
|
|
||||||
|
//result.runtime.settings.xxxxx
|
||||||
|
const settingsCount = Object.keys(result.runtime.settings).length;
|
||||||
|
settingsCount.should.eql(21);//ensure no more than the 21 settings listed below are present in the settings object
|
||||||
|
result.runtime.settings.should.have.property('available',true);
|
||||||
|
result.runtime.settings.should.have.property('apiMaxLength', "UNSET");//deliberately disabled to ensure UNSET is returned
|
||||||
|
result.runtime.settings.should.have.property('debugMaxLength', 1111);
|
||||||
|
result.runtime.settings.should.have.property('disableEditor', false);
|
||||||
|
result.runtime.settings.should.have.property('editorTheme', {});
|
||||||
|
result.runtime.settings.should.have.property('flowFile', "flows.json");
|
||||||
|
result.runtime.settings.should.have.property('mqttReconnectTime', 321);
|
||||||
|
result.runtime.settings.should.have.property('serialReconnectTime', 432);
|
||||||
|
result.runtime.settings.should.have.property("adminAuth", "SET"); //should be sanitised to "SET"
|
||||||
|
result.runtime.settings.should.have.property("httpAdminCors", "SET"); //should be sanitised to "SET"
|
||||||
|
result.runtime.settings.should.have.property('httpAdminRoot', "/admin/root/");
|
||||||
|
result.runtime.settings.should.have.property("httpNodeAuth", "SET"); //should be sanitised to "SET"
|
||||||
|
result.runtime.settings.should.have.property("httpNodeCors", "SET"); //should be sanitised to "SET"
|
||||||
|
result.runtime.settings.should.have.property('httpNodeRoot', "/node/root/");
|
||||||
|
result.runtime.settings.should.have.property("httpStatic", "SET"); //should be sanitised to "SET"
|
||||||
|
result.runtime.settings.should.have.property('httpStaticRoot', "/static/root/");
|
||||||
|
result.runtime.settings.should.have.property("httpStaticCors", "SET"); //should be sanitised to "SET"
|
||||||
|
result.runtime.settings.should.have.property("uiHost", "SET"); //should be sanitised to "SET"
|
||||||
|
result.runtime.settings.should.have.property("uiPort", "SET"); //should be sanitised to "SET"
|
||||||
|
result.runtime.settings.should.have.property("userDir", "SET"); //should be sanitised to "SET"
|
||||||
|
result.runtime.settings.should.have.property('contextStorage').type("object");
|
||||||
|
|
||||||
|
//result.runtime.settings.contextStorage.xxxxx
|
||||||
|
const contextCount = Object.keys(result.runtime.settings.contextStorage).length;
|
||||||
|
contextCount.should.eql(3);//ensure no more than the 3 settings listed below are present in the contextStorage object
|
||||||
|
result.runtime.settings.contextStorage.should.have.property('default', {module:"memory"});
|
||||||
|
result.runtime.settings.contextStorage.should.have.property('file', {module:"localfilesystem"});
|
||||||
|
result.runtime.settings.contextStorage.should.have.property('secured', {module:"secure_store"}); //only module should be present, other fields are dropped for security
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user