Merge branch 'master' into dev

This commit is contained in:
Nick O'Leary
2021-05-04 11:19:05 +01:00
53 changed files with 581 additions and 205 deletions

View File

@@ -90,7 +90,7 @@ function getToken(req,res,next) {
return server.token()(req,res,next);
}
function login(req,res) {
async function login(req,res) {
var response = {};
if (settings.adminAuth) {
var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module);
@@ -116,8 +116,9 @@ function login(req,res) {
response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image);
}
}
if (theme.context().login && theme.context().login.image) {
response.image = theme.context().login.image;
let themeContext = await theme.context();
if (themeContext.login && themeContext.login.image) {
response.image = themeContext.login.image;
}
}
res.json(response);

View File

@@ -129,6 +129,14 @@ module.exports = {
}
themeContext.page.title = theme.page.title || themeContext.page.title;
// Store the resolved urls to these resources so nodes (such as Debug)
// can access them
theme.page._ = {
css: themeContext.page.css,
scripts: themeContext.page.scripts,
favicon: themeContext.page.favicon
}
}
if (theme.header) {
@@ -223,6 +231,8 @@ module.exports = {
themePlugin.path
);
themeContext.page.css = cssFiles.concat(themeContext.page.css || [])
theme.page = theme.page || {_:{}}
theme.page._.css = cssFiles.concat(theme.page._.css || [])
}
if (themePlugin.scripts) {
const scriptFiles = serveFilesFromTheme(
@@ -232,6 +242,8 @@ module.exports = {
themePlugin.path
)
themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || [])
theme.page = theme.page || {_:{}}
theme.page._.scripts = cssFiles.concat(theme.page._.scripts || [])
}
}
activeThemeInitialised = true;

View File

@@ -524,8 +524,8 @@
"title": "パレットの管理",
"palette": "パレット",
"times": {
"seconds": "秒前",
"minutes": "分前",
"seconds": "秒前",
"minutes": "分前",
"minutesV": "__count__ 分前",
"hoursV": "__count__ 時間前",
"hoursV_plural": "__count__ 時間前",

View File

@@ -1424,6 +1424,8 @@ RED.nodes = (function() {
nid = getID();
workspace_map[n.id] = nid;
n.id = nid;
} else {
workspace_map[n.id] = n.id;
}
addWorkspace(n);
RED.workspaces.add(n);
@@ -1523,7 +1525,7 @@ RED.nodes = (function() {
}
}
} else {
if (n.z && !workspaces[n.z] && !subflow_map[n.z]) {
if (n.z && !workspace_map[n.z] && !subflow_map[n.z]) {
n.z = activeWorkspace;
}
}
@@ -1621,7 +1623,7 @@ RED.nodes = (function() {
node.id = getID();
} else {
node.id = n.id;
if (node.z == null || (!workspaces[node.z] && !subflow_map[node.z])) {
if (node.z == null || (!workspace_map[node.z] && !subflow_map[node.z])) {
if (createMissingWorkspace) {
if (missingWorkspace === null) {
missingWorkspace = RED.workspaces.add(null,true);

View File

@@ -1186,22 +1186,6 @@ RED.clipboard = (function() {
}
}
function getNodeLabelText(n) {
var label = n.name || n.type+": "+n.id;
if (n._def.label) {
try {
label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
} catch(err) {
console.log("Definition error: "+n.type+".label",err);
}
}
var newlineIndex = label.indexOf("\\n");
if (newlineIndex > -1) {
label = label.substring(0,newlineIndex)+"...";
}
return label;
}
function getFlowLabel(n) {
n = JSON.parse(JSON.stringify(n));
n._def = RED.nodes.getType(n.type) || {};
@@ -1227,16 +1211,8 @@ RED.clipboard = (function() {
if (n._def) {
n._ = n._def._;
}
var div = $('<div>',{class:"red-ui-info-outline-item"});
RED.utils.createNodeIcon(n).appendTo(div);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
var labelText = getNodeLabelText(n);
var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
if (labelText) {
label.text(labelText)
} else {
label.html(n.type)
}
var div = $('<div>',{class:"red-ui-node-list-item"});
RED.utils.createNodeIcon(n,true).appendTo(div);
return div;
}

View File

@@ -71,7 +71,7 @@
var buttons = this.options.buttons || [];
if (this.options.addButton !== false) {
var addLabel, addTittle;
var addLabel, addTitle;
if (typeof this.options.addButton === 'string') {
addLabel = this.options.addButton
} else {
@@ -102,7 +102,7 @@
button.click(evt);
}
});
if (button.title) {
element.attr("title", button.title);
}
@@ -113,7 +113,7 @@
element.append($("<span></span>").text(" " + button.label));
}
});
if (this.element.css("position") === "absolute") {
["top","left","bottom","right"].forEach(function(s) {
var v = that.element.css(s);

View File

@@ -312,7 +312,7 @@
}
if (child.depth !== parent.depth+1) {
child.depth = parent.depth+1;
var labelPaddingWidth = ((child.gutter?child.gutter.width()+2:0)+(child.depth*20));
var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20));
child.treeList.labelPadding.width(labelPaddingWidth+'px');
if (child.element) {
$(child.element).css({
@@ -348,6 +348,18 @@
that._selected.delete(item);
delete item.treeList;
delete that._items[item.id];
if(item.depth === 0) {
for(var key in that._items) {
if (that._items.hasOwnProperty(key)) {
var child = that._items[key];
if(child.parent && child.parent.id === item.id) {
delete that._items[key].treeList;
delete that._items[key];
}
}
}
that._data = that._data.filter(function(data) { return data.id !== item.id})
}
}
item.treeList.insertChildAt = function(newItem,position,select) {
newItem.parent = item;
@@ -480,7 +492,10 @@
if (item.treeList.container) {
$(item.element).remove();
$(element).appendTo(item.treeList.label);
var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(item.depth*20);
// using the JQuery Object, the gutter width will
// be wrong when the element is reattached the second time
var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (item.depth * 20);
$(element).css({
width: "calc(100% - "+(labelPaddingWidth+20+(item.icon?20:0))+"px)"
})
@@ -516,7 +531,7 @@
}).appendTo(label)
}
var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(depth*20);
var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (depth * 20)
item.treeList.labelPadding = $('<span>').css({
display: "inline-block",
width: labelPaddingWidth+'px'

View File

@@ -15,7 +15,7 @@
**/
(function($) {
var contextParse = function(v,defaultStore) {
var parts = RED.utils.parseContextKey(v, defaultStore);
var parts = RED.utils.parseContextKey(v, defaultStore&&defaultStore.value);
return {
option: parts.store,
value: parts.key
@@ -279,6 +279,14 @@
var contextStores = RED.settings.context.stores;
var contextOptions = contextStores.map(function(store) {
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database"></i>'}
}).sort(function(A,B) {
if (A.value === RED.settings.context.default) {
return -1;
} else if (B.value === RED.settings.context.default) {
return 1;
} else {
return A.value.localeCompare(B.value);
}
})
if (contextOptions.length < 2) {
allOptions.flow.options = [];
@@ -389,6 +397,11 @@
evt.stopPropagation();
}).on('focus', function() {
that.uiSelect.addClass('red-ui-typedInput-focus');
}).on('blur', function() {
var opt = that.typeMap[that.propertyType];
if (opt.hasValue === false) {
that.uiSelect.removeClass('red-ui-typedInput-focus');
}
})
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
@@ -438,7 +451,11 @@
});
this._showMenu(this.optionMenu,this.optionSelectTrigger);
var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']");
var targetValue = this.optionValue;
if (this.optionValue === null || this.optionValue === undefined) {
targetValue = this.value();
}
var selectedOption = this.optionMenu.find("[value='"+targetValue+"']");
if (selectedOption.length === 0) {
selectedOption = this.optionMenu.children(":first");
}
@@ -669,6 +686,11 @@
that.typeMap[result.value] = result;
return result;
});
if (this.typeList.length < 2) {
this.selectTrigger.attr("tabindex", -1)
} else {
this.selectTrigger.attr("tabindex", 0)
}
this.selectTrigger.toggleClass("disabled", this.typeList.length === 1);
this.selectTrigger.find(".fa-caret-down").toggle(this.typeList.length > 1)
if (this.menu) {
@@ -768,6 +790,11 @@
if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) {
this.selectLabel.text(opt.label);
}
if (opt.label) {
this.selectTrigger.attr("title",opt.label);
} else {
this.selectTrigger.attr("title","");
}
if (opt.hasValue === false) {
this.selectTrigger.addClass("red-ui-typedInput-full-width");
} else {
@@ -1004,16 +1031,17 @@
this.uiSelect.hide();
},
disable: function(val) {
if(val === true) {
if(val === undefined || !!val ) {
this.uiSelect.attr("disabled", "disabled");
} else if (val === false) {
this.uiSelect.attr("disabled", null); //remove attr
} else {
this.uiSelect.attr("disabled", val); //user value
this.uiSelect.attr("disabled", null); //remove attr
}
},
enable: function() {
this.uiSelect.attr("disabled", null); //remove attr
},
disabled: function() {
return this.uiSelect.attr("disabled");
return this.uiSelect.attr("disabled") === "disabled";
}
});
})(jQuery);

View File

@@ -119,7 +119,7 @@ RED.keyboard = (function() {
} else {
mergedKeymap[action] = [{
scope: themeKeymap[action].scope || "*",
key: [themeKeymap[action].key],
key: themeKeymap[action].key,
user: false
}]
if (mergedKeymap[action][0].scope === "workspace") {

View File

@@ -331,7 +331,7 @@ RED.palette.editor = (function() {
nodeEntry.versionSpan.html(moduleInfo.version+' <i class="fa fa-long-arrow-right"></i> '+moduleInfo.pending_version).appendTo(nodeEntry.metaRow)
nodeEntry.updateButton.text(RED._('palette.editor.updated')).addClass('disabled').css('display', 'inline-block');
} else if (loadedIndex.hasOwnProperty(module)) {
if (semVerCompare(loadedIndex[module].version,moduleInfo.version) === 1) {
if (semVerCompare(loadedIndex[module].version,moduleInfo.version) > 0) {
nodeEntry.updateButton.show();
nodeEntry.updateButton.text(RED._('palette.editor.update',{version:loadedIndex[module].version}));
} else {

View File

@@ -928,11 +928,11 @@ RED.projects.settings = (function() {
saveDisabled = isFlowInvalid || credFileLabelText.text()==="";
if (credentialSecretExistingInput.is(":visible")) {
if (credentialSecretExistingRow.is(":visible")) {
credentialSecretExistingInput.toggleClass("input-error", credentialSecretExistingInput.val() === "");
saveDisabled = saveDisabled || credentialSecretExistingInput.val() === "";
}
if (credentialSecretNewInput.is(":visible")) {
if (credentialSecretNewRow.is(":visible")) {
credentialSecretNewInput.toggleClass("input-error", credentialSecretNewInput.val() === "");
saveDisabled = saveDisabled || credentialSecretNewInput.val() === "";
}
@@ -1130,7 +1130,7 @@ RED.projects.settings = (function() {
}
if (credentialSecretResetButton.hasClass('selected') || credentialSecretEditButton.hasClass('selected')) {
payload.credentialSecret = credentialSecretNewInput.val();
if (credentialSecretExistingInput.is(":visible")) {
if (credentialSecretExistingRow.is(":visible")) {
payload.currentCredentialSecret = credentialSecretExistingInput.val();
}
}

View File

@@ -230,9 +230,8 @@ RED.sidebar.help = (function() {
}
function getNodeLabel(n) {
var div = $('<div>',{class:"red-ui-info-outline-item"});
var div = $('<div>',{class:"red-ui-node-list-item"});
RED.utils.createNodeIcon(n).appendTo(div);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
var label = n.name;
if (!label && n._def.paletteLabel) {
try {
@@ -241,7 +240,7 @@ RED.sidebar.help = (function() {
}
}
label = label || n.type;
$('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).text(label).appendTo(contentDiv);
$('<div>',{class:"red-ui-node-label"}).text(n.name||n.type).appendTo(div);
return div;
}

View File

@@ -73,36 +73,11 @@ RED.sidebar.info.outliner = (function() {
return item;
}
function getNodeLabelText(n) {
var label = n.name || n.type+": "+n.id;
if (n._def.label) {
try {
label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
} catch(err) {
console.log("Definition error: "+n.type+".label",err);
}
}
var newlineIndex = label.indexOf("\\n");
if (newlineIndex > -1) {
label = label.substring(0,newlineIndex)+"...";
}
return label;
}
function getNodeLabel(n) {
var div = $('<div>',{class:"red-ui-info-outline-item"});
RED.utils.createNodeIcon(n).appendTo(div);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
var labelText = getNodeLabelText(n);
var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
if (labelText) {
label.text(labelText)
} else {
label.html("&nbsp;")
}
var div = $('<div>',{class:"red-ui-node-list-item red-ui-info-outline-item"});
RED.utils.createNodeIcon(n, true).appendTo(div);
div.find(".red-ui-node-label").addClass("red-ui-info-outline-item-label")
addControls(n, div);
return div;
}
@@ -430,7 +405,7 @@ RED.sidebar.info.outliner = (function() {
var existingObject = objects[n.id];
var parent = n.g||n.z||"__global__";
var nodeLabelText = getNodeLabelText(n);
var nodeLabelText = RED.utils.getNodeLabel(n,n.name || (n.type+": "+n.id));
if (nodeLabelText) {
existingObject.element.find(".red-ui-info-outline-item-label").text(nodeLabelText);
} else {

View File

@@ -1125,9 +1125,9 @@ RED.utils = (function() {
imageIconElement.css("backgroundImage", "url("+iconUrl+")");
}
function createNodeIcon(node) {
function createNodeIcon(node, includeLabel) {
var def = node._def;
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"})
var nodeDiv = $('<div>',{class:"red-ui-node-icon"})
if (node.type === "_selection_") {
nodeDiv.addClass("red-ui-palette-icon-selection");
} else if (node.type === "group") {
@@ -1147,8 +1147,20 @@ RED.utils = (function() {
}
var icon_url = RED.utils.getNodeIcon(def,node);
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, true);
RED.utils.createIconElement(icon_url, nodeDiv, true);
if (includeLabel) {
var container = $('<span>');
nodeDiv.appendTo(container);
var labelText = RED.utils.getNodeLabel(node,node.name || (node.type+": "+node.id));
var label = $('<div>',{class:"red-ui-node-label"}).appendTo(container);
if (labelText) {
label.text(labelText)
} else {
label.html("&nbsp;")
}
return container;
}
return nodeDiv;
}

View File

@@ -1751,7 +1751,6 @@ RED.view = (function() {
}
}
if (mouse_mode == RED.state.IMPORT_DRAGGING) {
RED.keyboard.remove("escape");
updateActiveNodes();
RED.nodes.dirty(true);
}
@@ -1786,6 +1785,9 @@ RED.view = (function() {
}
function selectNone() {
if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
return;
}
if (mouse_mode === RED.state.IMPORT_DRAGGING) {
clearSelection();
RED.history.pop();

View File

@@ -134,7 +134,7 @@
&:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):first-child {
margin-top: 15px;
}
&:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):first-child {
&:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):last-child {
margin-bottom: 15px;
}
}
@@ -229,3 +229,47 @@
left: 1px;
}
}
////////////////
.red-ui-node-list-item {
display: inline-block;
padding: 0;
font-size: 13px;
border: none;
}
.red-ui-node-icon {
display: inline-block;
float:left;
width: 24px;
height: 20px;
margin-top: 1px;
// width: 30px;
// height: 25px;
border-radius: 3px;
border: 1px solid $node-border;
background-position: 5% 50%;
background-repeat: no-repeat;
background-size: contain;
position: relative;
background-color: $node-icon-background-color;
text-align: center;
.red-ui-palette-icon {
width: 20px;
}
.red-ui-palette-icon-fa {
font-size: 14px;
position: relative;
top: -1px;
left: 0px;
}
}
.red-ui-node-label {
margin-left: 32px;
line-height: 23px;
white-space: nowrap;
color: $secondary-text-color;
}

View File

@@ -326,13 +326,17 @@ div.red-ui-info-table {
border-bottom: 1px solid $secondary-border-color;
}
}
.red-ui-info-outline,.red-ui-sidebar-help-toc, #red-ui-clipboard-dialog-import-conflicts-list, #red-ui-clipboard-dialog-export-tab-clipboard-preview {
.red-ui-info-outline,
// TODO: remove these classes for 2.0. Keeping in 1.x for backwards compatibility
// of theme generators.
.red-ui-sidebar-help-toc, #red-ui-clipboard-dialog-import-conflicts-list, #red-ui-clipboard-dialog-export-tab-clipboard-preview
{
.red-ui-info-outline-item {
display: inline-block;
padding: 0;
font-size: 13px;
border: none;
.red-ui-palette-icon-fa {
&:not(.red-ui-node-list-item) .red-ui-palette-icon-fa {
position: relative;
top: 1px;
left: 0px;

View File

@@ -24,7 +24,7 @@
margin: 0;
vertical-align: middle;
box-sizing: border-box;
overflow:visible;
overflow: hidden;
position: relative;
&[disabled] {
input, button {
@@ -33,7 +33,7 @@
cursor: not-allowed;
}
}
.red-ui-typedInput-input-wrap {
flex-grow: 1;
}

View File

@@ -214,7 +214,7 @@
for (var i=0,l=props.length; i<l; i++) {
if (i > 0) lab += "\n";
if (i === 5) {
lab += " + "+(props.length-4);
lab += "... +"+(props.length-5);
break;
}
lab += props[i].p+": ";

View File

@@ -2,7 +2,8 @@ module.exports = function(RED) {
"use strict";
var util = require("util");
var events = require("events");
//var path = require("path");
const fs = require("fs-extra");
const path = require("path");
var debuglength = RED.settings.debugMaxLength || 1000;
var useColors = RED.settings.debugUseColors || false;
util.inspect.styles.boolean = "red";
@@ -249,11 +250,34 @@ module.exports = function(RED) {
}
});
let cachedDebugView;
RED.httpAdmin.get("/debug/view/view.html", function(req,res) {
if (!cachedDebugView) {
fs.readFile(path.join(__dirname,"lib","debug","view.html")).then(data => {
let customStyles = "";
try {
let customStyleList = RED.settings.editorTheme.page._.css || [];
customStyleList.forEach(style => {
customStyles += `<link rel="stylesheet" href="../../${style}">\n`
})
} catch(err) {}
cachedDebugView = data.toString().replace("<!-- INSERT-THEME-CSS -->",customStyles)
res.set('Content-Type', 'text/html');
res.send(cachedDebugView).end();
}).catch(err => {
res.sendStatus(404);
})
} else {
res.send(cachedDebugView).end();
}
});
// As debug/view/debug-utils.js is loaded via <script> tag, it won't get
// the auth header attached. So do not use RED.auth.needsPermission here.
RED.httpAdmin.get("/debug/view/*",function(req,res) {
var options = {
root: __dirname + '/lib/debug/',
root: path.join(__dirname,"lib","debug"),
dotfiles: 'deny'
};
res.sendFile(req.params[0], options);

View File

@@ -2,6 +2,7 @@
<head>
<link rel="stylesheet" href="../../red/style.min.css">
<link rel="stylesheet" href="../../vendor/font-awesome/css/font-awesome.min.css">
<!-- INSERT-THEME-CSS -->
<title>Node-RED Debug Tools</title>
</head>
<body class="red-ui-editor red-ui-debug-window">

View File

@@ -74,21 +74,21 @@
<div id="func-tab-init" style="display:none">
<div class="form-row node-text-editor-row" style="position:relative">
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-init-editor" ></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px);"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-init-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
</div>
</div>
<div id="func-tab-body" style="display:none">
<div class="form-row node-text-editor-row" style="position:relative">
<div style="height: 220px; min-height:150px;" class="node-text-editor" id="node-input-func-editor" ></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px);"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-function-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
</div>
</div>
<div id="func-tab-finalize" style="display:none">
<div class="form-row node-text-editor-row" style="position:relative">
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-finalize-editor" ></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px);"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
<div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 10;"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div>
</div>
</div>

View File

@@ -302,7 +302,7 @@ module.exports = function(RED) {
}
});
if (moduleErrors) {
throw new Error("Function node failed to load external modules");
throw new Error(RED._("function.error.externalModuleLoadError"));
}
}

View File

@@ -45,9 +45,9 @@
</div>
<div id="random-details" class="form-row">
<label for="node-input-randomFirst"><i class="fa fa-clock-o"></i> <span data-i18n="delay.between"></span></label>
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:30px !important">
<span data-i18n="delay.and"></span>
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:30px !important">
<input type="text" id="node-input-randomFirst" placeholder="" style="text-align:end; width:50px !important">
&nbsp;<span data-i18n="delay.and"></span>&nbsp;
<input type="text" id="node-input-randomLast" placeholder="" style="text-align:end; width:50px !important">
<select id="node-input-randomUnits" style="width:140px !important">
<option value="milliseconds" data-i18n="delay.milisecs"></option>
<option value="seconds" data-i18n="delay.secs"></option>

View File

@@ -186,8 +186,8 @@
<input type="text" id="node-config-input-port" data-i18n="[placeholder]mqtt.label.port" style="width:55px">
</div>
<div class="form-row" style="height: 34px;">
<input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 0 0 104px; display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-usetls" style="width: 80px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label>
<input type="checkbox" id="node-config-input-usetls" style="height: 34px; margin: 0 5px 0 104px; display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-usetls" style="width: 100px; line-height: 34px;"><span data-i18n="mqtt.label.use-tls"></span></label>
<span id="node-config-row-tls" class="hide"><input style="width: 320px;" type="text" id="node-config-input-tls"></span>
</div>
<div class="form-row">

View File

@@ -400,7 +400,15 @@ module.exports = function(RED) {
}
if (Object.keys(node.users).length === 0) {
if (node.client && node.client.connected) {
return node.client.end(done);
// Send close message
if (node.closeMessage) {
node.publish(node.closeMessage,function(err) {
node.client.end(done);
});
} else {
node.client.end(done);
}
return;
} else {
node.client.end();
return done();
@@ -639,10 +647,6 @@ module.exports = function(RED) {
this.on('close', function(done) {
this.closing = true;
if (this.connected) {
// Send close message
if (node.closeMessage) {
node.publish(node.closeMessage);
}
this.client.once('close', function() {
done();
});
@@ -873,4 +877,4 @@ module.exports = function(RED) {
}
}
RED.nodes.registerType("mqtt out",MQTTOutNode);
};
};

View File

@@ -46,7 +46,9 @@ module.exports = function(RED) {
isText = true;
} else if (parsedType.type !== "application") {
isText = false;
} else if ((parsedType.subtype !== "octet-stream") && (parsedType.subtype !== "cbor")) {
} else if ((parsedType.subtype !== "octet-stream")
&& (parsedType.subtype !== "cbor")
&& (parsedType.subtype !== "x-protobuf")) {
checkUTF = true;
} else {
// application/octet-stream or application/cbor

View File

@@ -38,17 +38,18 @@ module.exports = function(RED) {
if (this.hdrout === true) { this.hdrout = "all"; }
var tmpwarn = true;
var node = this;
var re = new RegExp(',(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
var re = new RegExp(node.sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') + '(?=(?:(?:[^"]*"){2})*[^"]*$)','g');
// pass in an array of column names to be trimed, de-quoted and retrimed
var clean = function(col) {
// pass in an array of column names to be trimmed, de-quoted and retrimmed
var clean = function(col,sep) {
if (sep) { re = new RegExp(sep.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g,'\\$&') +'(?=(?:(?:[^"]*"){2})*[^"]*$)','g'); }
col = col.trim().split(re) || [""];
col = col.map(x => x.replace(/"/g,'').trim());
if ((col.length === 1) && (col[0] === "")) { node.goodtmpl = false; }
else { node.goodtmpl = true; }
return col;
}
var template = clean(node.template);
var template = clean(node.template,',');
var notemplate = template.length === 1 && template[0] === '';
node.hdrSent = false;
@@ -67,7 +68,7 @@ module.exports = function(RED) {
if (node.hdrout !== "none" && node.hdrSent === false) {
if ((template.length === 1) && (template[0] === '')) {
if (msg.hasOwnProperty("columns")) {
template = clean(msg.columns || "");
template = clean(msg.columns || "",",");
}
else {
template = Object.keys(msg.payload[0]);
@@ -80,7 +81,7 @@ module.exports = function(RED) {
if ((Array.isArray(msg.payload[s])) || (typeof msg.payload[s] !== "object")) {
if (typeof msg.payload[s] !== "object") { msg.payload = [ msg.payload ]; }
for (var t = 0; t < msg.payload[s].length; t++) {
if (!msg.payload[s][t] && (msg.payload[s][t] !== 0)) { msg.payload[s][t] = ""; }
if (msg.payload[s][t] === undefined) { msg.payload[s][t] = ""; }
if (msg.payload[s][t].toString().indexOf(node.quo) !== -1) { // add double quotes if any quotes
msg.payload[s][t] = msg.payload[s][t].toString().replace(/"/g, '""');
msg.payload[s][t] = node.quo + msg.payload[s][t].toString() + node.quo;
@@ -93,7 +94,7 @@ module.exports = function(RED) {
}
else {
if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
template = clean(msg.columns || "");
template = clean(msg.columns || "",",");
}
if ((template.length === 1) && (template[0] === '')) {
/* istanbul ignore else */
@@ -184,7 +185,7 @@ module.exports = function(RED) {
if ((node.hdrin === true) && first) { // if the template is in the first line
if ((line[i] === "\n")||(line[i] === "\r")||(line.length - i === 1)) { // look for first line break
if (line.length - i === 1) { tmp += line[i]; }
template = clean(tmp);
template = clean(tmp,node.sep);
first = false;
}
else { tmp += line[i]; }

View File

@@ -219,6 +219,10 @@
value: "none",
label: label
}).text(label).appendTo(encSel);
$("<option/>", {
value: "setbymsg",
label: node._("file.encoding.setbymsg")
}).text(label).appendTo(encSel);
encodings.forEach(function(item) {
if(Array.isArray(item)) {
var group = $("<optgroup/>", {

View File

@@ -69,7 +69,8 @@ module.exports = function(RED) {
fs.unlink(fullFilename, function (err) {
if (err) {
node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
} else {
}
else {
if (RED.settings.verbose) {
node.log(RED._("file.status.deletedfile",{file:filename}));
}
@@ -82,7 +83,8 @@ module.exports = function(RED) {
if (node.createDir) {
try {
fs.ensureDirSync(dir);
} catch(err) {
}
catch(err) {
node.error(RED._("file.errors.createfail",{error:err.toString()}),msg);
done();
return;
@@ -96,7 +98,11 @@ module.exports = function(RED) {
if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
var buf = encode(data, node.encoding);
var buf;
if (node.encoding === "setbymsg") {
buf = encode(data, msg.encoding || "none");
}
else { buf = encode(data, node.encoding); }
if (node.overwriteFile === "true") {
var wstream = fs.createWriteStream(fullFilename, { encoding:'binary', flags:'w', autoClose:true });
node.wstream = wstream;
@@ -105,10 +111,11 @@ module.exports = function(RED) {
done();
});
wstream.on("open", function() {
wstream.end(buf, function() {
wstream.once("close", function() {
nodeSend(msg);
done();
});
wstream.end(buf);
})
return;
}
@@ -130,7 +137,8 @@ module.exports = function(RED) {
delete node.wstream;
delete node.wstreamIno;
}
} catch(err) {
}
catch(err) {
// File does not exist
recreateStream = true;
node.wstream.end();
@@ -158,14 +166,16 @@ module.exports = function(RED) {
nodeSend(msg);
done();
});
} else {
}
else {
// Dynamic filename - write and close the stream
node.wstream.end(buf, function() {
node.wstream.once("close", function() {
nodeSend(msg);
delete node.wstream;
delete node.wstreamIno;
done();
});
node.wstream.end(buf);
}
}
}
@@ -284,7 +294,6 @@ module.exports = function(RED) {
ch = "\n";
type = "string";
}
var hwm;
var getout = false;
var rs = fs.createReadStream(fullFilename)
@@ -348,16 +357,17 @@ module.exports = function(RED) {
nodeSend(msg);
}
else if (node.format === "lines") {
var m = { payload: spare,
topic:msg.topic,
parts: {
index: count,
count: count+1,
ch: ch,
type: type,
id: msg._msgid
}
};
var m = {
payload: spare,
topic:msg.topic,
parts: {
index: count,
count: count+1,
ch: ch,
type: type,
id: msg._msgid
}
};
nodeSend(m);
}
else if (getout) { // last chunk same size as high water mark - have to send empty extra packet.

View File

@@ -363,7 +363,7 @@
"keepalive": "Keep-Alive",
"cleansession": "Bereinigte Sitzung (clean session) verwenden",
"cleanstart": "Verwende bereinigten Start",
"use-tls": "Sichere Verbindung (SSL/TLS) verwenden",
"use-tls": "TLS",
"tls-config": "TLS-Konfiguration",
"verify-server-cert": "Server-Zertifikat überprüfen",
"compatmode": "MQTT 3.1 unterstützen",

View File

@@ -52,10 +52,11 @@
pass <code>msg</code> as a second argument to <code>node.error</code>:</p>
<pre>node.error("Error",msg);</pre>
<h4>Accessing Node Information</h4>
<p>In the function block, id and name of the node can be referenced using the following properties:</p>
<p>The following properties are available to access information about the node:</p>
<ul>
<li><code>node.id</code> - id of the node</li>
<li><code>node.name</code> - name of the node</li>
<li><code>node.outputCount</code> - number of node outputs</li>
</ul>
<h4>Using environment variables</h4>
<p>Environment variables can be accessed using <code>env.get("MY_ENV_VAR")</code>.</p>

View File

@@ -227,6 +227,7 @@
"error": {
"externalModuleNotAllowed": "Function node not allowed to load external modules",
"moduleNotAllowed": "Module __module__ not allowed",
"externalModuleLoadError": "Function node failed to load external modules",
"moduleLoadError": "Failed to load module __module__: __error__",
"moduleNameError": "Invalid module variable name: __name__",
"moduleNameReserved": "Reserved variable name: __name__",
@@ -877,6 +878,7 @@
},
"encoding": {
"none": "default",
"setbymsg": "set by msg.encoding",
"native": "Native",
"unicode": "Unicode",
"japanese": "Japanese",

View File

@@ -91,7 +91,7 @@
</ul>
</dd>
<dt class="optional">complete</dt>
<dd>If set, the node will append the payload, and then send the output message in its current state.
<dd>If set, the node will append the payload, and then send the output message in its current state.
If you don't wish to append the payload, delete it from the msg.</dd>
</dl>
<h3>Details</h3>
@@ -150,6 +150,7 @@
<p>By default, the reduce expression is applied in order, from the first
to the last message of the sequence. It can optionally be applied in
reverse order.</p>
<p>$N is the number of messages that arrive - even if they are identical.</p>
</dl>
<p><b>Example:</b> the following settings, given a sequence of numeric values,
calculates the average value:

View File

@@ -21,6 +21,8 @@
<dl class="message-properties">
<dt class="optional">filename <span class="property-type">string</span></dt>
<dd>If not configured in the node, this optional property sets the name of the file to be updated.</dd>
<dt class="optional">encoding <span class="property-type">string</span></dt>
<dd>If encoding is configured to be set by msg, then this optional property can set the encoding.</dt>
</dl>
<h3>Output</h3>
<p>On completion of write, input message is sent to output port.</p>

View File

@@ -44,10 +44,11 @@
<p>catchードを用いてエラー処理が可能ですcatchードで処理させるためには<code>msg</code><code>node.error</code>:</p>
<pre>node.error("エラー",msg);</pre>
<h4>ノード情報の参照</h4>
<p>コード中ではードのIDおよび名前を以下のプロパティで参照できます:</p>
<p>ノードに関する情報を参照するための以下のプロパティを利用できます:</p>
<ul>
<li><code>node.id</code> - ID</li>
<li><code>node.name</code> - </li>
<li><code>node.outputCount</code> - </li>
</ul>
<h4>環境変数の利用</h4>
<p>環境変数は<code>env.get("MY_ENV_VAR")</code></p>

View File

@@ -227,6 +227,7 @@
"error": {
"externalModuleNotAllowed": "Functionードは、外部モジュールを読み込みできません",
"moduleNotAllowed": "モジュール __module__ は利用できません",
"externalModuleLoadError": "Functionードは、外部モジュールの読み込みに失敗しました",
"moduleLoadError": "モジュール __module__ の読み込みに失敗しました: __error__",
"moduleNameError": "モジュール変数名が不正です: __name__",
"moduleNameReserved": "予約された変数名です: __name__",
@@ -875,6 +876,7 @@
},
"encoding": {
"none": "デフォルト",
"setbymsg": "msg.encodingで設定",
"native": "ネイティブ",
"unicode": "UNICODE",
"japanese": "日本",

View File

@@ -20,6 +20,8 @@
<dl class="message-properties">
<dt class="optional">filename <span class="property-type">文字列</span></dt>
<dd>対象ファイル名をノードに設定していない場合このプロパティでファイルを指定できます</dd>
<dt class="optional">encoding <span class="property-type">文字列</span></dt>
<dd>エンコーディングをmsgで設定する構成にした際はこの任意のプロパティでエンコーディングを設定できます</dt>
</dl>
<h3>出力</h3>
<p>書き込みの完了時入力メッセージを出力端子に送出します</p>

View File

@@ -329,7 +329,7 @@
"port": "포트",
"keepalive": "킵 얼라이브 시간",
"cleansession": "세션 초기화",
"use-tls": "SSL/TLS접속을 사용",
"use-tls": "사용TLS",
"tls-config": "TLS설정",
"verify-server-cert": "서버인증서를 확인",
"compatmode": "구 MQTT 3.1서포트"

View File

@@ -352,7 +352,7 @@
"port": "Порт",
"keepalive": "Keep-alive время (сек)",
"cleansession": "Использовать чистую сессию",
"use-tls": "Включить безопасное (SSL/TLS) соединение",
"use-tls": "TLS",
"tls-config":"Конфигурация TLS",
"verify-server-cert":"Проверить сертификат сервера",
"compatmode": "Использовать устаревшую поддержку MQTT 3.1"

View File

@@ -349,7 +349,7 @@
"port": "端口",
"keepalive": "Keepalive计时(秒)",
"cleansession": "使用新的会话",
"use-tls": "使用安全连接 (SSL/TLS)",
"use-tls": "使用 TLS",
"tls-config": "TLS 设置",
"verify-server-cert": "验证服务器证书",
"compatmode": "使用旧式MQTT 3.1支持"

View File

@@ -353,7 +353,7 @@
"port": "埠",
"keepalive": "Keepalive計時(秒)",
"cleansession": "使用新的會話",
"use-tls": "使用安全連接 (SSL/TLS)",
"use-tls": "使用 TLS",
"tls-config": "TLS 設置",
"verify-server-cert": "驗證伺服器憑證",
"compatmode": "使用舊式MQTT 3.1支援"

View File

@@ -472,7 +472,7 @@ function getPackageList() {
try {
var userPackage = path.join(settings.userDir,"package.json");
var pkg = JSON.parse(fs.readFileSync(userPackage,"utf-8"));
return pkg.dependencies;
return pkg.dependencies || {};
} catch(err) {
log.error(err);
}

View File

@@ -95,6 +95,9 @@ function createNode(flow,config) {
} else if (nodeTypeConstructor) {
// console.log(nodeTypeConstructor)
var subflowConfig = parseConfig([nodeTypeConstructor.subflow].concat(nodeTypeConstructor.subflow.flow));
var subflowInstanceConfig = subflowConfig.subflows[nodeTypeConstructor.subflow.id];
delete subflowConfig.subflows[nodeTypeConstructor.subflow.id];
subflowInstanceConfig.subflows = subflowConfig.subflows;
var instanceConfig = clone(config);
instanceConfig.env = clone(nodeTypeConstructor.subflow.env);
@@ -124,7 +127,7 @@ function createNode(flow,config) {
nodeTypeConstructor.type,
flow,
flow.global,
subflowConfig.subflows[nodeTypeConstructor.subflow.id],
subflowInstanceConfig,
instanceConfig
);
subflow.start();

View File

@@ -63,6 +63,27 @@ var server;
*/
function init(userSettings,httpServer,_adminApi) {
server = httpServer;
if (server && server.on) {
// Add a listener to the upgrade event so that we can properly timeout connection
// attempts that do not get handled by any nodes in the user's flow.
// See #2956
server.on('upgrade',(request, socket, head) => {
// Add a no-op handler to the error event in case nothing upgrades this socket
// before the remote end closes it. This ensures we don't get as uncaughtException
socket.on("error", err => {})
setTimeout(function() {
// If this request has been handled elsewhere, the upgrade will have
// been completed and bytes written back to the client.
// If nothing has been written on the socket, nothing has handled the
// upgrade, so we can consider this an unhandled upgrade.
if (socket.bytesWritten === 0) {
socket.destroy();
}
},userSettings.inboundWebSocketTimeout || 5000)
});
}
userSettings.version = getVersion();
settings.init(userSettings);

View File

@@ -343,7 +343,11 @@ var api = module.exports = {
if (newCreds) {
delete node.credentials;
var savedCredentials = credentialCache[nodeID] || {};
if (/^subflow(:|$)/.test(nodeType)) {
// Need to check the type of constructor for this node.
// - Function : regular node
// - !Function: subflow module
if (/^subflow(:|$)/.test(nodeType) || typeof runtime.nodes.getType(nodeType) !== 'function') {
for (cred in newCreds) {
if (newCreds.hasOwnProperty(cred)) {
if (newCreds[cred] === "__PWRD__") {

View File

@@ -612,8 +612,15 @@ async function saveFlows(flows, user) {
var workflowMode = (gitSettings.workflow||{}).mode || settings.editorTheme.projects.workflow.mode;
if (workflowMode === 'auto') {
return activeProject.stageFile([flowsFullPath, credentialsFile]).then(() => {
return activeProject.commit(user,{message:"Update flow files"})
})
return activeProject.status(user, false).then((result) => {
const items = Object.values(result.files || {});
// check if saved flow make modification to repository
if (items.findIndex((item) => (item.status === "M ")) < 0) {
return Promise.resolve();
}
return activeProject.commit(user,{message:"Update flow files"})
});
});
}
}
});

View File

@@ -57,6 +57,11 @@ module.exports = {
// defaults to the working directory of the Node-RED process.
//fileWorkingDirectory: "",
// Timeout in milliseconds for inbound WebSocket connections that do not
// match any configured node.
// defaults to 5000
//inboundWebSocketTimeout: 5000,
// The maximum length, in characters, of any message sent to the debug sidebar tab
debugMaxLength: 1000,