Compare commits

..

2 Commits

Author SHA1 Message Date
Nick O'Leary
37524e05bb Do not select parent group after completing quick-add inside group 2023-03-03 15:58:38 +00:00
Nick O'Leary
3fbfd47089 Show quick-add dialog when draggin wire to empty space 2023-03-03 15:52:59 +00:00
68 changed files with 333 additions and 894 deletions

View File

@@ -19,9 +19,9 @@ jobs:
matrix:
node-version: [16, 18]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install Dependencies
@@ -29,8 +29,8 @@ jobs:
- name: Run tests
run: |
npm run test
# - name: Publish to coveralls.io
# if: ${{ matrix.node-version == 16 }}
# uses: coverallsapp/github-action@v1.1.2
# with:
# github-token: ${{ github.token }}
- name: Publish to coveralls.io
if: ${{ matrix.node-version == 16 }}
uses: coverallsapp/github-action@v1.1.2
with:
github-token: ${{ github.token }}

View File

@@ -1,55 +1,6 @@
#### 3.1.0-beta.2: Beta Release
Editor
- NEW: Add change icon to tabs (#4068) @knolleary
- NEW: Complete overhaul of Group UX (#4079) @knolleary
- NEW: Add link to node help in node edit dialog footer (#4065) @knolleary
- NEW: Added editor feature for connecting multiple nodes to single node (#4051) @sonntam
- NEW: Increase workspace size to 8000x8000 (#4094) @knolleary
- Ensure node buttons are redrawn when flow lock state is changed (#4091) @knolleary
- Prevent loops being created with junction nodes (#4087) @knolleary
- Prevent opening locked node's edit dialog (#4069) @knolleary
- Reverse direction of tab scroll to expected direction (#4064) @knolleary
- Add cancel operation to editableList (#4077) @HiroyasuNishiyama
- Apply Mermaid diagram for project settings UI (#4054) @kazuhitoyokoi
- Add tooltip for show/hide button on info sidebar (#4050) @kazuhitoyokoi
- Fix align nodes on locked tab (#4072) @HiroyasuNishiyama
- Fix importing connected link nodes into a subflow (#4082) @knolleary
- Fix to add empty marker to empty group (#4060) @HiroyasuNishiyama
- Fix image URLs for v3.0 tour (#4053) @kazuhitoyokoi
- Show scrollbar in notification dialog only when needed (#4048) @kazuhitoyokoi
- Update-monaco-and-typings (#4089) @Steve-Mcl
- Update jquery UI (#4088) @knolleary
- Support i18n of lock/unlock buttons in flow property UI (#4049) @kazuhitoyokoi
- Translation kr (#3895) @hae-iotplatform
- Translation zhcn (请懂中文的帮忙review) (#3952) @cliyr
- Add French translation of nodes (#3964) @GogoVega
- Add French translation (#3962) @GogoVega
- Portuguese Brazilian (pt-BR) translation (#3804) @FabsMuller
Runtime
- NEW: Generate stable ids for subflow instance internal nodes (#4093) @knolleary
- NEW: Change default file name to flows.json in project feature (#4073) @kazuhitoyokoi
- NEW: Deprecate synchronous access to jsonata (#4090) @knolleary
- Add Node 18 to test matrix (#4084) @knolleary
- Bump minimum nodejs version supported to match documented value (#4086) @knolleary
- Update monaco docs link in settings.js (#4075) @Steve-Mcl
- Remove duplicated messages in the message catalog (#4066) @kazuhitoyokoi
- Ensure errors in preDeliver callback are handled (#3911) @knolleary
- Fix "EADDRINUSE" error (#4046) @bggbr
Nodes
- Link Call: Clear link-call timeouts when node is closed (#4085) @knolleary
- Join: ensure inflight status is cleared when in auto mode (#4083) @knolleary
- File Out: Fix extra newline append for multipart file write (#3915) @dceejay
- Add validators for complete and link call nodes (#4056) @kazuhitoyokoi
#### 3.1.0-beta.1: Beta Release
Editor
- NEW: Locking Flows (#3938) @knolleary

View File

@@ -2,7 +2,8 @@
http://nodered.org
[![Build Status](https://github.com/node-red/node-red/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/node-red/node-red/actions?query=branch%3Amaster)
[![Build Status](https://travis-ci.org/node-red/node-red.svg?branch=master)](https://travis-ci.org/node-red/node-red)
[![Coverage Status](https://coveralls.io/repos/node-red/node-red/badge.svg?branch=master)](https://coveralls.io/r/node-red/node-red?branch=master)
Low-code programming for event-driven applications.

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "3.1.0-beta.2",
"version": "3.1.0-beta.1",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -44,8 +44,8 @@
"express": "4.18.2",
"express-session": "1.17.3",
"form-data": "4.0.0",
"fs-extra": "11.1.1",
"got": "12.6.0",
"fs-extra": "10.1.0",
"got": "11.8.6",
"hash-sum": "2.0.0",
"hpagent": "1.2.0",
"https-proxy-agent": "5.0.1",
@@ -60,7 +60,7 @@
"memorystore": "1.6.7",
"mime": "3.0.0",
"moment": "2.29.4",
"moment-timezone": "0.5.43",
"moment-timezone": "0.5.41",
"mqtt": "4.3.7",
"multer": "1.4.5-lts.1",
"mustache": "4.2.0",
@@ -73,13 +73,13 @@
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.5.2",
"semver": "7.5.0",
"semver": "7.3.8",
"tar": "6.1.13",
"tough-cookie": "4.1.2",
"uglify-js": "3.17.4",
"uuid": "9.0.0",
"ws": "7.5.6",
"xml2js": "0.5.0"
"xml2js": "0.4.23"
},
"optionalDependencies": {
"bcrypt": "5.1.0"
@@ -108,14 +108,14 @@
"i18next-http-backend": "1.4.1",
"jquery-i18next": "1.2.1",
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "4.3.0",
"mermaid": "^9.4.3",
"marked": "4.2.12",
"mermaid": "^9.3.0",
"minami": "1.2.3",
"mocha": "9.2.2",
"node-red-node-test-helper": "^0.3.1",
"node-red-node-test-helper": "^0.3.0",
"nodemon": "2.0.20",
"proxy": "^1.0.2",
"sass": "1.62.1",
"sass": "1.58.3",
"should": "13.2.3",
"sinon": "11.1.2",
"stoppable": "^1.1.0",

View File

@@ -14,6 +14,8 @@
* limitations under the License.
**/
var express = require("express");
var nodes = require("./nodes");
var flows = require("./flows");
var flow = require("./flow");
@@ -35,9 +37,18 @@ module.exports = {
plugins.init(runtimeAPI);
diagnostics.init(settings, runtimeAPI);
const needsPermission = auth.needsPermission;
var needsPermission = auth.needsPermission;
var adminApp = express();
var defaultServerSettings = {
"x-powered-by": false
}
var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
for (var eOption in serverSettings) {
adminApp.set(eOption, serverSettings[eOption]);
}
const adminApp = apiUtil.createExpressApp(settings)
// Flows
adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler);

View File

@@ -46,15 +46,14 @@ module.exports = {
runtimeAPI = _runtimeAPI;
needsPermission = auth.needsPermission;
if (!settings.disableEditor) {
info.init(settings, runtimeAPI);
info.init(runtimeAPI);
comms.init(server,settings,runtimeAPI);
var ui = require("./ui");
ui.init(runtimeAPI);
const editorApp = apiUtil.createExpressApp(settings)
var editorApp = express();
if (settings.requireHttps === true) {
editorApp.enable('trust proxy');
editorApp.use(function (req, res, next) {
@@ -87,7 +86,7 @@ module.exports = {
//Projects
var projects = require("./projects");
projects.init(settings, runtimeAPI);
projects.init(runtimeAPI);
editorApp.use("/projects",projects.app());
// Locales

View File

@@ -14,9 +14,9 @@
* limitations under the License.
**/
var express = require("express");
var apiUtils = require("../util");
var settings;
var runtimeAPI;
var needsPermission = require("../auth").needsPermission;
@@ -77,12 +77,11 @@ function getProjectRemotes(req,res) {
})
}
module.exports = {
init: function(_settings, _runtimeAPI) {
settings = _settings;
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
},
app: function() {
var app = apiUtils.createExpressApp(settings)
var app = express();
app.use(function(req,res,next) {
runtimeAPI.projects.available().then(function(available) {

View File

@@ -18,9 +18,9 @@ var runtimeAPI;
var sshkeys = require("./sshkeys");
module.exports = {
init: function(settings, _runtimeAPI) {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
sshkeys.init(settings, runtimeAPI);
sshkeys.init(runtimeAPI);
},
userSettings: function(req, res) {
var opts = {

View File

@@ -17,15 +17,13 @@
var apiUtils = require("../util");
var express = require("express");
var runtimeAPI;
var settings;
module.exports = {
init: function(_settings, _runtimeAPI) {
init: function(_runtimeAPI) {
runtimeAPI = _runtimeAPI;
settings = _settings;
},
app: function() {
const app = apiUtils.createExpressApp(settings);
var app = express();
// List all SSH keys
app.get("/", function(req,res) {

View File

@@ -19,7 +19,6 @@ var util = require("util");
var path = require("path");
var fs = require("fs");
var clone = require("clone");
const apiUtil = require("../util")
var defaultContext = {
page: {
@@ -28,7 +27,8 @@ var defaultContext = {
tabicon: {
icon: "red/images/node-red-icon-black.svg",
colour: "#8f0000"
}
},
version: require(path.join(__dirname,"../../package.json")).version
},
header: {
title: "Node-RED",
@@ -40,7 +40,6 @@ var defaultContext = {
vendorMonaco: ""
}
};
var settings;
var theme = null;
var themeContext = clone(defaultContext);
@@ -93,8 +92,7 @@ function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) {
}
module.exports = {
init: function(_settings, _runtimeAPI) {
settings = _settings;
init: function(settings, _runtimeAPI) {
runtimeAPI = _runtimeAPI;
themeContext = clone(defaultContext);
if (process.env.NODE_ENV == "development") {
@@ -115,15 +113,7 @@ module.exports = {
var url;
themeSettings = {};
themeApp = apiUtil.createExpressApp(settings);
const defaultServerSettings = {
"x-powered-by": false
}
const serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
for (const eOption in serverSettings) {
themeApp.set(eOption, serverSettings[eOption]);
}
themeApp = express();
if (theme.page) {

View File

@@ -37,6 +37,7 @@ var adminApp;
var server;
var editor;
/**
* Initialise the module.
* @param {Object} settings The runtime settings
@@ -48,7 +49,7 @@ var editor;
function init(settings,_server,storage,runtimeAPI) {
server = _server;
if (settings.httpAdminRoot !== false) {
adminApp = apiUtil.createExpressApp(settings);
adminApp = express();
var cors = require('cors');
var corsHandler = cors({
@@ -63,6 +64,14 @@ function init(settings,_server,storage,runtimeAPI) {
}
}
var defaultServerSettings = {
"x-powered-by": false
}
var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
for (var eOption in serverSettings) {
adminApp.set(eOption, serverSettings[eOption]);
}
auth.init(settings,storage);
var maxApiRequestSize = settings.apiMaxLength || '5mb';
@@ -127,11 +136,10 @@ async function stop() {
editor.stop();
}
}
module.exports = {
init,
start,
stop,
init: init,
start: start,
stop: stop,
/**
* @memberof @node-red/editor-api

View File

@@ -14,9 +14,10 @@
* limitations under the License.
**/
const express = require("express");
const { log, i18n } = require("@node-red/util");
var log = require("@node-red/util").log; // TODO: separate module
var i18n = require("@node-red/util").i18n; // TODO: separate module
module.exports = {
errorHandler: function(err,req,res,next) {
@@ -63,17 +64,5 @@ module.exports = {
path: req.path,
ip: (req.headers && req.headers['x-forwarded-for']) || (req.connection && req.connection.remoteAddress) || undefined
}
},
createExpressApp: function(settings) {
const app = express();
const defaultServerSettings = {
"x-powered-by": false
}
const serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{});
for (let eOption in serverSettings) {
app.set(eOption, serverSettings[eOption]);
}
return app
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "3.1.0-beta.2",
"version": "3.1.0-beta.1",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/util": "3.1.0-beta.2",
"@node-red/editor-client": "3.1.0-beta.2",
"@node-red/util": "3.1.0-beta.1",
"@node-red/editor-client": "3.1.0-beta.1",
"bcryptjs": "2.4.3",
"body-parser": "1.20.2",
"clone": "2.1.2",

View File

@@ -504,7 +504,6 @@
"unassigned": "Unassigned",
"global": "global",
"workspace": "workspace",
"editor": "edit dialog",
"selectAll": "Select all",
"selectNone": "Select none",
"selectAllConnected": "Select connected",

View File

@@ -504,7 +504,6 @@
"unassigned": "未割当",
"global": "グローバル",
"workspace": "ワークスペース",
"editor": "編集ダイアログ",
"selectAll": "全てのノードを選択",
"selectNone": "選択を外す",
"selectAllConnected": "接続されたノードを選択",
@@ -1204,7 +1203,7 @@
"fr": "フランス語",
"ja": "日本語",
"ko": "韓国語",
"pt-BR": "ポルトガル語",
"pt-BR":"ポルトガル語",
"ru": "ロシア語",
"zh-CN": "中国語(簡体)",
"zh-TW": "中国語(繁体)"

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "3.1.0-beta.2",
"version": "3.1.0-beta.1",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@@ -1468,7 +1468,7 @@ RED.nodes = (function() {
}
}
if (node.type !== "subflow") {
var convertedNode = RED.nodes.convertNode(node, { credentials: false });
var convertedNode = RED.nodes.convertNode(node);
for (var d in node._def.defaults) {
if (node._def.defaults[d].type) {
var nodeList = node[d];
@@ -1501,7 +1501,7 @@ RED.nodes = (function() {
nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes));
}
} else {
var convertedSubflow = convertSubflow(node, { credentials: false });
var convertedSubflow = convertSubflow(node);
nns.push(convertedSubflow);
}
}
@@ -2201,27 +2201,16 @@ RED.nodes = (function() {
} else if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1];
var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (!subflow){
node._def = {
color:"#fee",
defaults: {},
label: "unknown: "+n.type,
labelStyle: "red-ui-flow-node-label-italic",
outputs: n.outputs|| (n.wires && n.wires.length) || 0,
set: registry.getNodeSet("node-red/unknown")
}
} else {
if (createNewIds || options.importMap[n.id] === "copy") {
parentId = subflow.id;
node.type = "subflow:"+parentId;
node._def = registry.getNodeType(node.type);
delete node.i;
}
node.name = n.name;
node.outputs = subflow.out.length;
node.inputs = subflow.in.length;
node.env = n.env;
if (createNewIds || options.importMap[n.id] === "copy") {
parentId = subflow.id;
node.type = "subflow:"+parentId;
node._def = registry.getNodeType(node.type);
delete node.i;
}
node.name = n.name;
node.outputs = subflow.out.length;
node.inputs = subflow.in.length;
node.env = n.env;
} else if (n.type === 'junction') {
node._def = {defaults:{}}
node._config.x = node.x

View File

@@ -33,8 +33,8 @@ RED.settings = (function () {
if (!hasLocalStorage()) {
return;
}
if (key.startsWith("auth-tokens")) {
localStorage.setItem(key+this.authTokensSuffix, JSON.stringify(value));
if (key === "auth-tokens") {
localStorage.setItem(key, JSON.stringify(value));
} else {
RED.utils.setMessageProperty(userSettings,key,value);
saveUserSettings();
@@ -52,8 +52,8 @@ RED.settings = (function () {
if (!hasLocalStorage()) {
return undefined;
}
if (key.startsWith("auth-tokens")) {
return JSON.parse(localStorage.getItem(key+this.authTokensSuffix));
if (key === "auth-tokens") {
return JSON.parse(localStorage.getItem(key));
} else {
var v;
try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {}
@@ -71,8 +71,8 @@ RED.settings = (function () {
if (!hasLocalStorage()) {
return;
}
if (key.startsWith("auth-tokens")) {
localStorage.removeItem(key+this.authTokensSuffix);
if (key === "auth-tokens") {
localStorage.removeItem(key);
} else {
delete userSettings[key];
saveUserSettings();
@@ -99,8 +99,6 @@ RED.settings = (function () {
var init = function (options, done) {
var accessTokenMatch = /[?&]access_token=(.*?)(?:$|&)/.exec(window.location.search);
var path=window.location.pathname.slice(0,-1);
RED.settings.authTokensSuffix=path.replace(/\//g, '-');
if (accessTokenMatch) {
var accessToken = accessTokenMatch[1];
RED.settings.set("auth-tokens",{access_token: accessToken});

View File

@@ -47,7 +47,7 @@ RED.actionList = (function() {
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
searchInput = $('<input type="text" data-i18n="[placeholder]keyboard.filterActions">').appendTo(searchDiv).searchBox({
change: function() {
filterTerm = $(this).val().trim().toLowerCase();
filterTerm = $(this).val().trim();
filterTerms = filterTerm.split(" ");
searchResults.editableList('filter');
searchResults.find("li.selected").removeClass("selected");

View File

@@ -37,13 +37,13 @@ RED.clipboard = (function() {
// IE11 workaround
// IE does not support data uri scheme for downloading data
var blob = new Blob([data], {
type: "data:application/json;charset=utf-8"
type: "data:text/plain;charset=utf-8"
});
navigator.msSaveBlob(blob, file);
}
else {
var element = document.createElement('a');
element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(data));
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data));
element.setAttribute('download', file);
element.style.display = 'none';
document.body.appendChild(element);
@@ -731,7 +731,7 @@ RED.clipboard = (function() {
nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes);
} else if (type === 'full') {
nodes = RED.nodes.createCompleteNodeSet({ credentials: false });
nodes = RED.nodes.createCompleteNodeSet(false);
}
if (nodes !== null) {
if (format === "red-ui-clipboard-dialog-export-fmt-full") {

View File

@@ -45,13 +45,11 @@ RED.editor = (function() {
var hasChanged;
if (node.type.indexOf("subflow:")===0) {
subflow = RED.nodes.subflow(node.type.substring(8));
if (subflow){
isValid = subflow.valid;
isValid = subflow.valid;
hasChanged = subflow.changed;
if (isValid === undefined) {
isValid = validateNode(subflow);
hasChanged = subflow.changed;
if (isValid === undefined) {
isValid = validateNode(subflow);
hasChanged = subflow.changed;
}
}
validationErrors = validateNodeProperties(node, node._def.defaults, node);
node.valid = isValid && validationErrors.length === 0;

View File

@@ -108,7 +108,7 @@ RED.editor.codeEditor.monaco = (function() {
"node-red-util": {package: "node-red", module: "util", path: "node-red/util.d.ts" },
"node-red-func": {package: "node-red", module: "func", path: "node-red/func.d.ts" },
}
const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"], knownModules["timers"] , knownModules["util"] ];
const defaultServerSideTypes = [ knownModules["node-red-util"], knownModules["node-red-func"], knownModules["globals"], knownModules["console"], knownModules["buffer"] , knownModules["util"] ];
const modulesCache = {};

View File

@@ -294,7 +294,7 @@
}
try {
expr.evaluate(legacyMode?{msg:parsedData}:parsedData, null, (err, result) => {
expr.evaluate(legacyMode?{msg:parsedData}:parsedData, (err, result) => {
if (err) {
testResultEditor.setValue(RED._("expressionEditor.errors.eval",{message:err.message}),-1);
} else {

View File

@@ -50,11 +50,7 @@ RED.envVar = (function() {
var new_env = [];
var items = list.editableList('items');
var credentials = gconf ? gconf.credentials : null;
if (!gconf && list.editableList('length') === 0) {
// No existing global-config node and nothing in the list,
// so no need to do anything more
return
}
if (!credentials) {
credentials = {
_ : {},
@@ -82,12 +78,6 @@ RED.envVar = (function() {
if (gconf === null) {
gconf = getGlobalConf(true);
}
if (!gconf.credentials) {
gconf.credentials = {
_ : {},
map: {}
};
}
if ((JSON.stringify(new_env) !== JSON.stringify(gconf.env)) ||
(JSON.stringify(credentials) !== JSON.stringify(gconf.credentials))) {
gconf.env = new_env;

View File

@@ -401,7 +401,7 @@ RED.group = (function() {
}
}
var existingGroup;
var mergedEnv = {}
// Second pass, ungroup any groups in the selection and add their contents
// to the selection
for (var i=0; i<selection.nodes.length; i++) {
@@ -410,11 +410,6 @@ RED.group = (function() {
if (!existingGroup) {
existingGroup = n;
}
if (n.env && n.env.length > 0) {
n.env.forEach(env => {
mergedEnv[env.name] = env
})
}
ungroupHistoryEvent.groups.push(n);
nodes = nodes.concat(ungroup(n));
} else {
@@ -432,7 +427,6 @@ RED.group = (function() {
group.style = existingGroup.style;
group.name = existingGroup.name;
}
group.env = Object.values(mergedEnv)
RED.view.select({nodes:[group]})
}
historyEvent.events.push({

View File

@@ -491,11 +491,7 @@ RED.keyboard = (function() {
okButton.attr("disabled",!valid);
});
var scopeSelect = $('<select>'+
'<option value="*" data-i18n="keyboard.global"></option>'+
'<option value="red-ui-workspace" data-i18n="keyboard.workspace"></option>'+
'<option value="red-ui-editor-stack" data-i18n="keyboard.editor"></option>'+
'</select>').appendTo(scope);
var scopeSelect = $('<select><option value="*" data-i18n="keyboard.global"></option><option value="red-ui-workspace" data-i18n="keyboard.workspace"></option></select>').appendTo(scope);
scopeSelect.i18n();
if (object.scope === "workspace") {
object.scope = "red-ui-workspace";

View File

@@ -171,15 +171,23 @@ RED.palette = (function() {
}
metaData += type;
const safeType = type.replace(/'/g,"\\'");
const searchType = type.indexOf(' ') > -1 ? '&quot;' + type + '&quot;' : type
if (/^subflow:/.test(type)) {
$('<button type="button" onclick="RED.workspaces.show(\''+type.substring(8).replace(/'/g,"\\'")+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-pencil"></i></button>').appendTo(popOverContent)
}
$('<button type="button" onclick="RED.search.show(\'type:'+searchType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>').appendTo(popOverContent)
const safeType = type.replace(/'/g,"\\'");
const wrapStr = function (str) {
if(str.indexOf(' ') >= 0) {
return '"' + str + '"'
}
return str
}
$('<button type="button"; return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-search"></i></button>')
.appendTo(popOverContent)
.on('click', function() {
RED.search.show('type:' + wrapStr(safeType))
})
$('<button type="button" onclick="RED.sidebar.help.show(\''+safeType+'\'); return false;" class="red-ui-button red-ui-button-small" style="float: right; margin-left: 5px;"><i class="fa fa-book"></i></button>').appendTo(popOverContent)
$('<p>',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent);

View File

@@ -747,14 +747,14 @@ RED.projects = (function() {
var row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.default-files.flow-file")+'</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow) || "flows.json";
var defaultFlowFile = (createProjectOptions.files &&createProjectOptions.files.flow) || (RED.settings.files && RED.settings.files.flow)||"flow.json";
projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val(defaultFlowFile)
.on("change keyup paste",validateForm)
.appendTo(subrow);
$('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);
$('<label class="red-ui-projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials) || "flows_cred.json";
var defaultCredentialsFile = (createProjectOptions.files &&createProjectOptions.files.credentials) || (RED.settings.files && RED.settings.files.credentials)||"flow_cred.json";
row = $('<div class="form-row"></div>').appendTo(body);
$('<label for="red-ui-projects-dialog-screen-create-project-credfile">'+RED._("projects.default-files.credentials-file")+'</label>').appendTo(row);
subrow = $('<div style="position:relative;"></div>').appendTo(row);
@@ -1257,7 +1257,7 @@ RED.projects = (function() {
row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label for="red-ui-projects-dialog-screen-create-project-file">'+RED._("projects.create.flow-file")+'</label>').appendTo(row);
subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val("flows.json")
projectFlowFileInput = $('<input id="red-ui-projects-dialog-screen-create-project-file" type="text">').val("flow.json")
.on("change keyup paste",validateForm)
.appendTo(subrow);
$('<div class="red-ui-projects-dialog-screen-input-status"></div>').appendTo(subrow);

View File

@@ -681,23 +681,24 @@ RED.subflow = (function() {
var candidateOutputs = [];
var candidateInputNodes = {};
var boundingBox = [nodeList[0].x-(nodeList[0].w/2),
nodeList[0].y-(nodeList[0].h/2),
nodeList[0].x+(nodeList[0].w/2),
nodeList[0].y+(nodeList[0].h/2)];
var boundingBox = [nodeList[0].x,
nodeList[0].y,
nodeList[0].x,
nodeList[0].y];
for (i=0;i<nodeList.length;i++) {
n = nodeList[i];
nodes[n.id] = {n:n,outputs:{}};
boundingBox = [
Math.min(boundingBox[0],n.x-(n.w/2)),
Math.min(boundingBox[1],n.y-(n.h/2)),
Math.max(boundingBox[2],n.x+(n.w/2)),
Math.max(boundingBox[3],n.y+(n.h/2))
Math.min(boundingBox[0],n.x),
Math.min(boundingBox[1],n.y),
Math.max(boundingBox[2],n.x),
Math.max(boundingBox[3],n.y)
]
}
var offsetX = snapToGrid(boundingBox[0] - 140);
var offsetY = snapToGrid(boundingBox[1] - 60);
var offsetX = snapToGrid(boundingBox[0] - 200);
var offsetY = snapToGrid(boundingBox[1] - 80);
var center = [
snapToGrid((boundingBox[2]+boundingBox[0]) / 2),

View File

@@ -17,9 +17,9 @@
RED.view.navigator = (function() {
var nav_scale = 50;
var nav_width = 8000/nav_scale;
var nav_height = 8000/nav_scale;
var nav_scale = 25;
var nav_width = 5000/nav_scale;
var nav_height = 5000/nav_scale;
var navContainer;
var navBox;

View File

@@ -30,8 +30,8 @@
*/
RED.view = (function() {
var space_width = 8000,
space_height = 8000,
var space_width = 5000,
space_height = 5000,
lineCurveScale = 0.75,
scaleFactor = 1,
node_width = 100,
@@ -189,13 +189,7 @@ RED.view = (function() {
set.unshift(...removed)
}
},
find: function(func) { return set.find(func) },
dump: function () {
console.log('MovingSet Contents')
api.forEach((n, i) => {
console.log(`${i+1}\t${n.n.id}\t${n.n.type}`)
})
}
find: function(func) { return set.find(func) }
}
return api;
})();
@@ -232,63 +226,6 @@ RED.view = (function() {
return api
})();
const selectedGroups = (function() {
let groups = new Set()
const api = {
add: function(g, includeNodes, addToMovingSet) {
groups.add(g)
if (!g.selected) {
g.selected = true;
g.dirty = true;
}
if (addToMovingSet !== false) {
movingSet.add(g);
}
if (includeNodes) {
var currentSet = new Set(movingSet.nodes());
var allNodes = RED.group.getNodes(g,true);
allNodes.forEach(function(n) {
if (!currentSet.has(n)) {
movingSet.add(n)
}
n.dirty = true;
})
}
selectedLinks.clearUnselected()
},
remove: function(g) {
groups.delete(g)
if (g.selected) {
g.selected = false;
g.dirty = true;
}
const allNodes = RED.group.getNodes(g,true);
const nodeSet = new Set(allNodes);
nodeSet.add(g);
for (let i = movingSet.length()-1; i >= 0; i -= 1) {
const msn = movingSet.get(i);
if (nodeSet.has(msn.n) || msn.n === g) {
msn.n.selected = false;
msn.n.dirty = true;
movingSet.remove(msn.n,i)
}
}
selectedLinks.clearUnselected()
},
length: () => groups.length,
forEach: (func) => { groups.forEach(func) },
toArray: () => [...groups],
clear: function () {
groups.forEach(g => {
g.selected = false
g.dirty = true
})
groups.clear()
}
}
return api
})()
function init() {
@@ -1193,7 +1130,7 @@ RED.view = (function() {
var touchTrigger = options.touchTrigger;
if (targetGroup) {
selectedGroups.add(targetGroup,false);
selectGroup(targetGroup,false);
RED.view.redraw();
}
@@ -1518,9 +1455,6 @@ RED.view = (function() {
// auto select dropped node - so info shows (if visible)
clearSelection();
nn.selected = true;
if (targetGroup) {
selectedGroups.add(targetGroup,false);
}
movingSet.add(nn);
updateActiveNodes();
updateSelection();
@@ -1968,6 +1902,7 @@ RED.view = (function() {
}
showQuickAddDialog({ position: point, group: clickedGroup });
}
hideDragLines();
}
if (lasso) {
@@ -1983,7 +1918,10 @@ RED.view = (function() {
if (!movingSet.has(n) && !n.selected) {
// group entirely within lasso
if (n.x > x && n.y > y && n.x + n.w < x2 && n.y + n.h < y2) {
selectedGroups.add(n, true)
n.selected = true
n.dirty = true
var groupNodes = RED.group.getNodes(n,true);
groupNodes.forEach(gn => movingSet.add(gn))
}
}
})
@@ -2333,7 +2271,7 @@ RED.view = (function() {
clearSelection();
activeGroups.forEach(function(g) {
if (!g.g) {
selectedGroups.add(g, true);
selectGroup(g, true);
if (!g.selected) {
g.selected = true;
g.dirty = true;
@@ -2403,7 +2341,10 @@ RED.view = (function() {
}
movingSet.clear();
selectedLinks.clear();
selectedGroups.clear();
activeGroups.forEach(function(g) {
g.selected = false;
g.dirty = true;
})
}
var lastSelection = null;
@@ -2660,16 +2601,6 @@ RED.view = (function() {
var result = RED.nodes.removeJunction(node)
removedJunctions.push(node);
removedLinks = removedLinks.concat(result.links);
if (node.g) {
var group = RED.nodes.group(node.g);
if (selectedGroups.indexOf(group) === -1) {
// Don't use RED.group.removeFromGroup as that emits
// a change event on the node - but we're deleting it
var index = group.nodes.indexOf(node);
group.nodes.splice(index,1);
RED.group.markDirty(group);
}
}
} else {
if (node.direction === "out") {
removedSubflowOutputs.push(node);
@@ -3492,7 +3423,7 @@ RED.view = (function() {
if (!groupNodeSelectPrimed && !d.selected && d.g && RED.nodes.group(d.g).selected) {
clearSelection();
selectedGroups.add(RED.nodes.group(d.g), false);
selectGroup(RED.nodes.group(d.g), false);
mousedown_node.selected = true;
movingSet.add(mousedown_node);
@@ -3913,14 +3844,14 @@ RED.view = (function() {
lastClickNode = g;
if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) {
selectedGroups.remove(g);
deselectGroup(g);
d3.event.stopPropagation();
} else {
if (!g.selected) {
if (!d3.event.ctrlKey && !d3.event.metaKey) {
clearSelection();
}
selectedGroups.add(g,true);//!wasSelected);
selectGroup(g,true);//!wasSelected);
}
if (d3.event.button != 2) {
@@ -3936,6 +3867,45 @@ RED.view = (function() {
d3.event.stopPropagation();
}
function selectGroup(g, includeNodes, addToMovingSet) {
if (!g.selected) {
g.selected = true;
g.dirty = true;
}
if (addToMovingSet !== false) {
movingSet.add(g);
}
if (includeNodes) {
var currentSet = new Set(movingSet.nodes());
var allNodes = RED.group.getNodes(g,true);
allNodes.forEach(function(n) {
if (!currentSet.has(n)) {
movingSet.add(n)
}
n.dirty = true;
})
}
selectedLinks.clearUnselected()
}
function deselectGroup(g) {
if (g.selected) {
g.selected = false;
g.dirty = true;
}
const allNodes = RED.group.getNodes(g,true);
const nodeSet = new Set(allNodes);
nodeSet.add(g);
for (let i = movingSet.length()-1; i >= 0; i -= 1) {
const msn = movingSet.get(i);
if (nodeSet.has(msn.n) || msn.n === g) {
msn.n.selected = false;
msn.n.dirty = true;
movingSet.remove(msn.n,i)
}
}
selectedLinks.clearUnselected()
}
function getGroupAt(x, y, ignoreSelected) {
// x,y expected to be in node-co-ordinate space
var candidateGroups = {};
@@ -5916,10 +5886,11 @@ RED.view = (function() {
if (movingSet.length() > 0) {
movingSet.forEach(function(n) {
if (n.n.type !== 'group') {
allNodes.add(n.n);
allNodes.add(n.n);
}
});
}
var selectedGroups = activeGroups.filter(function(g) { return g.selected });
selectedGroups.forEach(function(g) {
var groupNodes = RED.group.getNodes(g,true);
groupNodes.forEach(function(n) {
@@ -6128,7 +6099,7 @@ RED.view = (function() {
n.dirty = true;
movingSet.add(n);
} else {
selectedGroups.add(n,true);
selectGroup(n,true);
}
})
}

View File

@@ -126,7 +126,7 @@
list-style-type: none;
margin: 0;
padding:0;
overflow-wrap: anywhere;
li {
display: inline-block;
padding:0;

View File

@@ -55,7 +55,7 @@
.red-ui-palette-search {
position: relative;
overflow: hidden;
background: var(--red-ui-form-input-background);
background: var(--red-ui-secondary-background);
text-align: center;
height: 35px;
padding: 3px;

View File

@@ -35,7 +35,6 @@
padding: 8px;
border-radius: 2px;
background: var(--red-ui-popover-background);
overflow-wrap: anywhere;
}
.red-ui-popover:after, .red-ui-popover:before {
border: solid transparent;

View File

@@ -108,8 +108,6 @@
}
.red-ui-search-result-node-label {
color: var(--red-ui-secondary-text-color);
width: 240px;
overflow-wrap: anywhere;
}
}

View File

@@ -31,7 +31,6 @@
> span {
display: inline-block;
margin-left: 5px;
overflow-wrap: anywhere;
}
border-bottom: 1px solid var(--red-ui-secondary-border-color);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,90 +1,15 @@
export default {
version: "3.1.0-beta.2",
version: "3.1.0-beta.1",
steps: [
{
titleIcon: "fa fa-map-o",
title: {
"en-US": "Welcome to Node-RED 3.1 Beta 2!",
"ja": "Node-RED 3.1 ベータ2へようこそ!"
"en-US": "Welcome to Node-RED 3.1 Beta 1!",
"ja": "Node-RED 3.1 ベータ1へようこそ!"
},
description: {
"en-US": "<p>This is the second beta release for 3.1.0 and we have a few new features to tell you about.</p>",
"ja": "<p>これは3.1.0の2回目のベータリリースです。いくつかの新機能について説明します。</p>"
}
},
{
title: {
"en-US": "New ways to work with groups",
"ja": "グループの新たな操作方法"
},
description: {
"en-US": `<p>We have changed how you interact with groups in the editor.</p>
<ul>
<li>They don't get in the way when clicking on a node</li>
<li>They can be reordered using the Moving Forwards and Move Backwards actions</li>
<li>Multiple nodes can be dragged into a group in one go</li>
<li>Holding <code>Alt</code> when dragging a node will *remove* it from its group</li>
</ul>`,
"ja": `<p>エディタ上のグループの操作が変更されました。</p>
<ul>
<li>グループ内のノードをクリックする時に、グループが邪魔をすることが無くなりました。</li>
<li>「前面へ移動」と「背面へ移動」の動作を用いて、複数のグループの表示順序を変えることができます。</li>
<li>グループ内へ一度に複数のノードをドラッグできるようになりました。</li>
<li><code>Alt</code> を押したまま、グループ内のノードをドラッグすると、そのグループから *除く* ことができます。</li>
</ul>`
}
},
{
title: {
"en-US": "Change notification on tabs",
"ja": "タブ上の変更通知"
},
image: 'images/tab-changes.png',
description: {
"en-US": `<p>When a tab contains undeployed changes it now shows the
same style of change icon used by nodes.</p>
<p>This will make it much easier to track down changes when you're
working across multiple flows.</p>`,
"ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p>
<p>これによって複数のフローを編集している時に、変更を見つけるのが簡単になりました。</p>`
}
},
{
title: {
"en-US": "A bigger canvas to work with",
"ja": "より広くなった作業キャンバス"
},
description: {
"en-US": `<p>The default canvas size has been increased so you can fit more
into one flow.</p>
<p>We still recommend using tools such as subflows and Link Nodes to help
keep things organised, but now you have more room to work in.</p>`,
"ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p>
<p>引き続き、サブフローやリンクノードなどの方法を用いて整理することをお勧めしますが、作業できる場所が増えました。</p>`
}
},
{
title: {
"en-US": "Finding help",
"ja": "ヘルプを見つける"
},
image: 'images/node-help.png',
description: {
"en-US": `<p>All node edit dialogs now include a link to that node's help
in the footer.</p>
<p>Clicking it will open up the Help sidebar showing the help for that node.</p>`,
"ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p>
<p>これをクリックすると、ノードのヘルプサイドバーが表示されます。</p>`
}
},
{
title: {
"en-US": "And lots more...",
"ja": "そしてさらに沢山あります..."
},
description: {
"en-US": `<p>Of course we have everything from 3.1.0-beta.1 as well....</p>`,
"ja": `<p>もちろん3.1.0 ベータ1の全ての機能があります....</p>`
"en-US": "<p>This is the first beta release for 3.1.0 and we have a few new features to tell you about.</p>",
"ja": "<p>これは3.1.0の最初のベータリリースです。いくつかの新機能について説明します。</p>"
}
},
{
@@ -153,13 +78,13 @@ export default {
{
title: {
"en-US": "Adding Mermaid Diagrams",
"ja": "Mermaid図を追加"
"ja": "Mermaid図を追加"
},
image: 'images/mermaid.png',
description: {
"en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p>
<p>This gives you much richer options for documenting your flows.</p>`,
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
<p>これによって、フローを説明する文書作成の選択肢がより多くなります。</p>`
},
},

View File

@@ -35,11 +35,7 @@ module.exports = function(RED) {
}
else { node.previous = {}; }
}
var value;
try {
value = RED.util.getMessageProperty(msg,node.property);
}
catch(e) { }
var value = RED.util.getMessageProperty(msg,node.property);
if (value !== undefined) {
var t = "_no_topic";
if (node.septopics) { t = topic || t; }

View File

@@ -249,12 +249,6 @@
<span id="node-config-input-cleansession-label" data-i18n="mqtt.label.cleansession"></span>
</label>
</div>
<div class="form-row mqtt-persistence">
<label for="node-config-input-autoUnsubscribe" style="width: auto;">
<input type="checkbox" id="node-config-input-autoUnsubscribe" style="position: relative;vertical-align: bottom; top: -2px; width: 15px;height: 15px;">
<span id="node-config-input-autoUnsubscribe-label" data-i18n="mqtt.label.autoUnsubscribe"></span>
</label>
</div>
<div class="form-row mqtt5">
<label style="width:auto" for="node-config-input-sessionExpiry"><span data-i18n="mqtt.label.sessionExpiry"></span></label>
<input type="number" min="0" id="node-config-input-sessionExpiry" style="width: 100px" >
@@ -489,23 +483,17 @@
tls: {type:"tls-config",required: false,
label:RED._("node-red:mqtt.label.use-tls") },
clientid: {value:"", validate: function(v, opt) {
let ok = true;
var ok = false;
if ($("#node-config-input-clientid").length) {
// Currently editing the node
let needClientId = !$("#node-config-input-cleansession").is(":checked") || !$("#node-config-input-autoUnsubscribe").is(":checked")
if (needClientId) {
ok = (v||"").length > 0;
}
ok = $("#node-config-input-cleansession").is(":checked") || (v||"").length > 0;
} else {
let needClientId = !(this.cleansession===undefined || this.cleansession) || this.autoUnsubscribe;
if (needClientId) {
ok = (v||"").length > 0;
}
ok = (this.cleansession===undefined || this.cleansession) || (v||"").length > 0;
}
if (!ok) {
return RED._("node-red:mqtt.errors.invalid-client-id");
if (ok) {
return ok;
}
return true;
return RED._("node-red:mqtt.errors.invalid-client-id");
}},
autoConnect: {value: true},
usetls: {value: false},
@@ -517,7 +505,6 @@
label: RED._("node-red:mqtt.label.keepalive"),
validate:RED.validators.number(false)},
cleansession: {value: true},
autoUnsubscribe: {value: true},
birthTopic: {value:"", validate:validateMQTTPublishTopic},
birthQos: {value:"0"},
birthRetain: {value:"false"},
@@ -633,10 +620,6 @@
this.cleansession = true;
$("#node-config-input-cleansession").prop("checked",true);
}
if (typeof this.autoUnsubscribe === 'undefined') {
this.autoUnsubscribe = true;
$("#node-config-input-autoUnsubscribe").prop("checked",true);
}
if (typeof this.usetls === 'undefined') {
this.usetls = false;
$("#node-config-input-usetls").prop("checked",false);
@@ -652,14 +635,6 @@
if (typeof this.protocolVersion === 'undefined') {
this.protocolVersion = 4;
}
$("#node-config-input-cleansession").on("change", function() {
const useCleanSession = $("#node-config-input-cleansession").is(':checked');
if(useCleanSession) {
$("div.form-row.mqtt-persistence").hide();
} else {
$("div.form-row.mqtt-persistence").show();
}
});
$("#node-config-input-protocolVersion").on("change", function() {
var v5 = $("#node-config-input-protocolVersion").val() == "5";
if(v5) {

View File

@@ -482,7 +482,6 @@ module.exports = function(RED) {
setIfHasProperty(opts, node, "protocolVersion", init);
setIfHasProperty(opts, node, "keepalive", init);
setIfHasProperty(opts, node, "cleansession", init);
setIfHasProperty(opts, node, "autoUnsubscribe", init);
setIfHasProperty(opts, node, "topicAliasMaximum", init);
setIfHasProperty(opts, node, "maximumPacketSize", init);
setIfHasProperty(opts, node, "receiveMaximum", init);
@@ -591,9 +590,6 @@ module.exports = function(RED) {
if (typeof node.cleansession === 'undefined') {
node.cleansession = true;
}
if (typeof node.autoUnsubscribe === 'undefined') {
node.autoUnsubscribe = true;
}
//use url or build a url from usetls://broker:port
if (node.url && node.brokerurl !== node.url) {
@@ -664,7 +660,6 @@ module.exports = function(RED) {
node.options.password = node.password;
node.options.keepalive = node.keepalive;
node.options.clean = node.cleansession;
node.options.autoUnsubscribe = node.autoUnsubscribe;
node.options.clientId = node.clientid || 'nodered_' + RED.util.generateId();
node.options.reconnectPeriod = RED.settings.mqttReconnectTime||5000;
delete node.options.protocolId; //V4+ default
@@ -1233,16 +1228,12 @@ module.exports = function(RED) {
node.on('close', function(removed, done) {
if (node.brokerConn) {
if(node.isDynamic) {
if (node.brokerConn.options.autoUnsubscribe) {
Object.keys(node.dynamicSubs).forEach(function (topic) {
node.brokerConn.unsubscribe(topic, node.id, removed);
});
node.dynamicSubs = {};
}
Object.keys(node.dynamicSubs).forEach(function (topic) {
node.brokerConn.unsubscribe(topic, node.id, removed);
});
node.dynamicSubs = {};
} else {
if (node.brokerConn.options.autoUnsubscribe) {
node.brokerConn.unsubscribe(node.topic, node.id, removed);
}
node.brokerConn.unsubscribe(node.topic,node.id, removed);
}
node.brokerConn.deregister(node, done, removed);
node.brokerConn = null;

View File

@@ -14,9 +14,9 @@
* limitations under the License.
**/
module.exports = async function(RED) {
module.exports = function(RED) {
"use strict";
const { got } = await import('got')
const got = require("got");
const {CookieJar} = require("tough-cookie");
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
const FormData = require('form-data');
@@ -210,24 +210,24 @@ in your Node-RED user directory (${RED.settings.userDir}).
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
// Had to remove this to get http->https redirect to work
// opts.defaultPort = isHttps?443:80;
opts.timeout = { request: node.reqTimeout || 5000 };
opts.timeout = node.reqTimeout;
opts.throwHttpErrors = false;
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
opts.decompress = false;
opts.method = method;
opts.retry = { limit: 0 };
opts.retry = 0;
opts.responseType = 'buffer';
opts.maxRedirects = 21;
opts.cookieJar = new CookieJar();
opts.ignoreInvalidCookies = true;
// opts.forever = nodeHTTPPersistent;
opts.forever = nodeHTTPPersistent;
if (msg.requestTimeout !== undefined) {
if (isNaN(msg.requestTimeout)) {
node.warn(RED._("httpin.errors.timeout-isnan"));
} else if (msg.requestTimeout < 1) {
node.warn(RED._("httpin.errors.timeout-isnegative"));
} else {
opts.timeout = { request: msg.requestTimeout };
opts.timeout = msg.requestTimeout;
}
}
const originalHeaderMap = {};
@@ -245,12 +245,9 @@ in your Node-RED user directory (${RED.settings.userDir}).
delete options.headers[h];
}
})
if (node.insecureHTTPParser) {
// Setting the property under _unixOptions as pretty
// much the only hack available to get got to apply
// a core http option it doesn't think we should be
// allowed to set
options._unixOptions = { ...options.unixOptions, insecureHTTPParser: true }
options.insecureHTTPParser = true
}
}
],
@@ -406,16 +403,15 @@ in your Node-RED user directory (${RED.settings.userDir}).
return response
}
const requestUrl = new URL(response.request.requestUrl);
const options = { headers: {} }
const options = response.request.options;
const normalisedHeaders = {};
Object.keys(response.headers).forEach(k => {
normalisedHeaders[k.toLowerCase()] = response.headers[k]
})
if (normalisedHeaders['www-authenticate']) {
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, response.request.options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
options.headers.Authorization = authHeader;
}
// response.request.options.merge(options)
sentCreds = true;
return retry(options);
}
@@ -703,43 +699,25 @@ in your Node-RED user directory (${RED.settings.userDir}).
});
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
const sha256 = (value) => { return crypto.createHash('sha256').update(value).digest('hex') }
const sha512 = (value) => { return crypto.createHash('sha512').update(value).digest('hex') }
function digestCompute(algorithm, value) {
var lowercaseAlgorithm = ""
if (algorithm) {
lowercaseAlgorithm = algorithm.toLowerCase().replace(/-sess$/, '')
}
if (lowercaseAlgorithm === "sha-256") {
return sha256(value)
} else if (lowercaseAlgorithm === "sha-512-256") {
var hash = sha512(value)
return hash.slice(0, 64) // Only use the first 256 bits
} else {
return md5(value)
}
}
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
/**
* RFC 2617: handle both standard and -sess algorithms.
* RFC 2617: handle both MD5 and MD5-sess algorithms.
*
* If the algorithm directive's value ends with "-sess", then HA1 is
* HA1=digestCompute(digestCompute(username:realm:password):nonce:cnonce)
*
* If the algorithm directive's value does not end with "-sess", then HA1 is
* HA1=digestCompute(username:realm:password)
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
* HA1=MD5(username:realm:password)
* If the algorithm directive's value is "MD5-sess", then HA1 is
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
*/
var ha1 = digestCompute(algorithm, user + ':' + realm + ':' + pass)
if (algorithm && /-sess$/i.test(algorithm)) {
return digestCompute(algorithm, ha1 + ':' + nonce + ':' + cnonce)
var ha1 = md5(user + ':' + realm + ':' + pass)
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
return md5(ha1 + ':' + nonce + ':' + cnonce)
} else {
return ha1
}
}
function buildDigestHeader(user, pass, method, path, authHeader) {
var challenge = {}
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
@@ -754,10 +732,10 @@ in your Node-RED user directory (${RED.settings.userDir}).
var nc = qop && '00000001'
var cnonce = qop && uuid().replace(/-/g, '')
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
var ha2 = digestCompute(challenge.algorithm, method + ':' + path)
var ha2 = md5(method + ':' + path)
var digestResponse = qop
? digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
: digestCompute(challenge.algorithm, ha1 + ':' + challenge.nonce + ':' + ha2)
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
var authValues = {
username: user,
realm: challenge.realm,

View File

@@ -33,13 +33,7 @@ module.exports = function(RED) {
parseString(value, options, function (err, result) {
if (err) { done(err); }
else {
// TODO: With xml2js@0.5.0, they return an object with
// a null prototype. This could cause unexpected
// issues. So for now, we have to reconstruct
// the object with a proper prototype.
// Once https://github.com/Leonidas-from-XIV/node-xml2js/pull/674
// is merged, we can revisit and hopefully remove this hack
value = fixObj(result)
value = result;
RED.util.setMessageProperty(msg,node.property,value);
send(msg);
done();
@@ -52,18 +46,4 @@ module.exports = function(RED) {
});
}
RED.nodes.registerType("xml",XMLNode);
function fixObj(obj) {
const res = {}
const keys = Object.keys(obj)
keys.forEach(k => {
if (typeof obj[k] === 'object' && obj[k]) {
res[k] = fixObj(obj[k])
} else {
res[k] = obj[k]
}
})
return res
}
}

View File

@@ -117,9 +117,7 @@ module.exports = function(RED) {
}
if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); }
var aflg = true;
if (msg.hasOwnProperty("parts") && msg.parts.type === "string" && (msg.parts.count === msg.parts.index + 1)) { aflg = false; }
if ((node.appendNewline) && (!Buffer.isBuffer(data)) && aflg) { data += os.EOL; }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
var buf;
if (node.encoding === "setbymsg") {
buf = encode(data, msg.encoding || "none");
@@ -316,6 +314,7 @@ module.exports = function(RED) {
});
filename = filename || "";
var fullFilename = filename;
var filePath = "";
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory,filename));
}

View File

@@ -362,7 +362,6 @@
"port": "Port",
"keepalive": "Keep-Alive",
"cleansession": "Bereinigte Sitzung (clean session) verwenden",
"autoUnsubscribe": "Abonnement bei Verbindungsende automatisch beenden",
"cleanstart": "Verwende bereinigten Start",
"use-tls": "TLS",
"tls-config": "TLS-Konfiguration",

View File

@@ -414,7 +414,6 @@
"port": "Port",
"keepalive": "Keep Alive",
"cleansession": "Use clean session",
"autoUnsubscribe": "Automatically unsubscribe when disconnecting",
"cleanstart": "Use clean start",
"use-tls": "Use TLS",
"tls-config": "TLS Configuration",

View File

@@ -414,7 +414,6 @@
"port": "ポート",
"keepalive": "キープアライブ時間",
"cleansession": "セッションの初期化",
"autoUnsubscribe": "切断時に購読を自動解除",
"cleanstart": "クリーンスタート",
"use-tls": "TLSを使用",
"tls-config": "TLS設定",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "3.1.0-beta.2",
"version": "3.1.0-beta.1",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -27,8 +27,8 @@
"cronosjs": "1.7.1",
"denque": "2.1.0",
"form-data": "4.0.0",
"fs-extra": "11.1.1",
"got": "12.6.0",
"fs-extra": "10.1.0",
"got": "11.8.6",
"hash-sum": "2.0.0",
"hpagent": "1.2.0",
"https-proxy-agent": "5.0.1",
@@ -44,7 +44,7 @@
"tough-cookie": "4.1.2",
"uuid": "9.0.0",
"ws": "7.5.6",
"xml2js": "0.5.0",
"xml2js": "0.4.23",
"iconv-lite": "0.6.3"
}
}

View File

@@ -242,68 +242,63 @@ async function ensureModuleDir() {
}
}
let installLock = Promise.resolve()
async function installModule(moduleDetails) {
const result = installLock.then(async () => {
let installSpec = moduleDetails.module;
if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
const e = new Error("Install not allowed");
e.code = "install_not_allowed";
let installSpec = moduleDetails.module;
if (!registryUtil.checkModuleAllowed( moduleDetails.module, moduleDetails.version,installAllowList,installDenyList)) {
const e = new Error("Install not allowed");
e.code = "install_not_allowed";
throw e;
}
if (moduleDetails.version) {
installSpec = installSpec+"@"+moduleDetails.version;
}
log.info(log._("server.install.installing",{name: moduleDetails.module,version: moduleDetails.version||"latest"}));
const installDir = getInstallDir();
await ensureModuleDir();
let triggerPayload = {
"module": moduleDetails.module,
"version": moduleDetails.version,
"dir": installDir,
"args": ["--production","--engine-strict"]
}
return hooks.trigger("preInstall", triggerPayload).then((result) => {
// preInstall passed
// - run install
if (result !== false) {
let extraArgs = triggerPayload.args || [];
let args = ['install', ...extraArgs, installSpec]
log.trace(NPM_COMMAND + JSON.stringify(args));
return exec.run(NPM_COMMAND, args, { cwd: installDir },true)
} else {
log.trace("skipping npm install");
}
}).then(() => {
return hooks.trigger("postInstall", triggerPayload)
}).then(() => {
log.info(log._("server.install.installed", { name: installSpec }));
const runtimeInstalledModules = settings.get("modules") || {};
runtimeInstalledModules[moduleDetails.module] = moduleDetails;
settings.set("modules",runtimeInstalledModules)
}).catch(result => {
var output = result.stderr || result.toString();
var e;
if (/E404/.test(output) || /ETARGET/.test(output)) {
log.error(log._("server.install.install-failed-not-found",{name:installSpec}));
e = new Error("Module not found");
e.code = 404;
throw e;
} else {
log.error(log._("server.install.install-failed-long",{name:installSpec}));
log.error("------------------------------------------");
log.error(output);
log.error("------------------------------------------");
e = new Error(log._("server.install.install-failed"));
e.code = "unexpected_error";
throw e;
}
if (moduleDetails.version) {
installSpec = installSpec+"@"+moduleDetails.version;
}
log.info(log._("server.install.installing",{name: moduleDetails.module,version: moduleDetails.version||"latest"}));
const installDir = getInstallDir();
await ensureModuleDir();
let triggerPayload = {
"module": moduleDetails.module,
"version": moduleDetails.version,
"dir": installDir,
"args": ["--production","--engine-strict"]
}
return hooks.trigger("preInstall", triggerPayload).then((result) => {
// preInstall passed
// - run install
if (result !== false) {
let extraArgs = triggerPayload.args || [];
let args = ['install', ...extraArgs, installSpec]
log.trace(NPM_COMMAND + JSON.stringify(args));
return exec.run(NPM_COMMAND, args, { cwd: installDir },true)
} else {
log.trace("skipping npm install");
}
}).then(() => {
return hooks.trigger("postInstall", triggerPayload)
}).then(() => {
log.info(log._("server.install.installed", { name: installSpec }));
const runtimeInstalledModules = settings.get("modules") || {};
runtimeInstalledModules[moduleDetails.module] = moduleDetails;
settings.set("modules",runtimeInstalledModules)
}).catch(result => {
var output = result.stderr || result.toString();
var e;
if (/E404/.test(output) || /ETARGET/.test(output)) {
log.error(log._("server.install.install-failed-not-found",{name:installSpec}));
e = new Error("Module not found");
e.code = 404;
throw e;
} else {
log.error(log._("server.install.install-failed-long",{name:installSpec}));
log.error("------------------------------------------");
log.error(output);
log.error("------------------------------------------");
e = new Error(log._("server.install.install-failed"));
e.code = "unexpected_error";
throw e;
}
})
})
installLock = result.catch(() => {})
return result
}
module.exports = {

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "3.1.0-beta.2",
"version": "3.1.0-beta.1",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,10 +16,10 @@
}
],
"dependencies": {
"@node-red/util": "3.1.0-beta.2",
"@node-red/util": "3.1.0-beta.1",
"clone": "2.1.2",
"fs-extra": "11.1.1",
"semver": "7.5.0",
"fs-extra": "10.1.0",
"semver": "7.3.8",
"tar": "6.1.13",
"uglify-js": "3.17.4"
}

View File

@@ -818,16 +818,6 @@ function handlePreRoute(flow, sendEvent, reportError) {
})
}
function deliverMessageToDestination(sendEvent) {
if (sendEvent?.destination?.node) {
try {
sendEvent.destination.node.receive(sendEvent.msg);
} catch(err) {
Log.error(`Error delivering message to node:${sendEvent.destination.node._path} [${sendEvent.destination.node.type}]`)
Log.error(err.stack)
}
}
}
function handlePreDeliver(flow,sendEvent, reportError) {
// preDeliver - the local router has identified the node it is going to send to. At this point, the message has been cloned if needed.
hooks.trigger("preDeliver",sendEvent,(err) => {
@@ -837,10 +827,15 @@ function handlePreDeliver(flow,sendEvent, reportError) {
} else if (err !== false) {
if (asyncMessageDelivery) {
setImmediate(function() {
deliverMessageToDestination(sendEvent)
if (sendEvent.destination.node) {
sendEvent.destination.node.receive(sendEvent.msg);
}
})
} else {
deliverMessageToDestination(sendEvent)
if (sendEvent.destination.node) {
sendEvent.destination.node.receive(sendEvent.msg);
}
}
// postDeliver - the message has been dispatched to be delivered asynchronously (unless the sync delivery flag is set, in which case it would be continue as synchronous delivery)
hooks.trigger("postDeliver", sendEvent, function(err) {

View File

@@ -474,7 +474,7 @@ class Subflow extends Flow {
*/
function createNodeInSubflow(subflowInstanceId, def) {
let node = clone(def);
let nid = `${subflowInstanceId}-${node.id}` //redUtil.generateId();
let nid = redUtil.generateId();
// console.log("Create Node In subflow",node._alias, "--->",nid, "(",node.type,")")
// node_map[node.id] = node;
node._alias = node.id;

View File

@@ -201,9 +201,7 @@ function parseConfig(config) {
if (subflowDetails) {
var subflowType = subflowDetails[1]
n.subflow = subflowType;
if (flow.subflows[subflowType]) {
flow.subflows[subflowType].instances.push(n)
}
flow.subflows[subflowType].instances.push(n)
}
if (container) {
container.nodes[n.id] = n;

View File

@@ -89,15 +89,6 @@ function init(userSettings,httpServer,_adminApi) {
nodeApp = express();
adminApp = express();
const defaultServerSettings = {
"x-powered-by": false
}
const serverSettings = Object.assign({},defaultServerSettings,userSettings.httpServerOptions||{});
for (let eOption in serverSettings) {
nodeApp.set(eOption, serverSettings[eOption]);
adminApp.set(eOption, serverSettings[eOption]);
}
if (_adminApi) {
adminApi = _adminApi;

View File

@@ -589,28 +589,17 @@ function deleteContext(id,flowId) {
* If flowConfig is undefined, all flow/node contexts will be removed
**/
function clean(flowConfig) {
flowConfig = flowConfig || { allNodes: {}, subflows: {} };
const knownNodes = new Set(Object.keys(flowConfig.allNodes))
// We need to alias all of the subflow instance contents
for (const subflow of Object.values(flowConfig.subflows || {})) {
subflow.instances.forEach(instance => {
for (const nodeId of Object.keys(subflow.nodes || {})) {
knownNodes.add(`${instance.id}-${nodeId}`)
}
for (const nodeId of Object.keys(subflow.configs || {})) {
knownNodes.add(`${instance.id}-${nodeId}`)
}
})
}
flowConfig = flowConfig || { allNodes: {} };
var promises = [];
for (const store of Object.values(stores)){
promises.push(store.clean(Array.from(knownNodes)));
for(var plugin in stores){
if(stores.hasOwnProperty(plugin)){
promises.push(stores[plugin].clean(Object.keys(flowConfig.allNodes)));
}
}
for (const id of Object.keys(contexts)) {
if (id !== "global") {
for (var id in contexts) {
if (contexts.hasOwnProperty(id) && id !== "global") {
var idParts = id.split(":");
if (!knownNodes.has(idParts[0])) {
if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) {
delete contexts[id];
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "3.1.0-beta.2",
"version": "3.1.0-beta.1",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,12 +16,12 @@
}
],
"dependencies": {
"@node-red/registry": "3.1.0-beta.2",
"@node-red/util": "3.1.0-beta.2",
"@node-red/registry": "3.1.0-beta.1",
"@node-red/util": "3.1.0-beta.1",
"async-mutex": "0.4.0",
"clone": "2.1.2",
"express": "4.18.2",
"fs-extra": "11.1.1",
"fs-extra": "10.1.0",
"json-stringify-safe": "5.0.1"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "3.1.0-beta.2",
"version": "3.1.0-beta.1",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -15,12 +15,12 @@
}
],
"dependencies": {
"fs-extra": "11.1.1",
"fs-extra": "10.1.0",
"i18next": "21.10.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.6",
"lodash.clonedeep": "^4.5.0",
"moment": "2.29.4",
"moment-timezone": "0.5.43"
"moment-timezone": "0.5.41"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "3.1.0-beta.2",
"version": "3.1.0-beta.1",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -31,17 +31,17 @@
"flow"
],
"dependencies": {
"@node-red/editor-api": "3.1.0-beta.2",
"@node-red/runtime": "3.1.0-beta.2",
"@node-red/util": "3.1.0-beta.2",
"@node-red/nodes": "3.1.0-beta.2",
"@node-red/editor-api": "3.1.0-beta.1",
"@node-red/runtime": "3.1.0-beta.1",
"@node-red/util": "3.1.0-beta.1",
"@node-red/nodes": "3.1.0-beta.1",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"express": "4.18.2",
"fs-extra": "11.1.1",
"fs-extra": "10.1.0",
"node-red-admin": "^3.0.0",
"nopt": "5.0.0",
"semver": "7.5.0"
"semver": "7.3.8"
},
"optionalDependencies": {
"bcrypt": "5.1.0"

View File

@@ -458,7 +458,7 @@ httpsPromise.then(function(startupHttps) {
RED.start().then(function() {
if (settings.httpAdminRoot !== false || settings.httpNodeRoot !== false || settings.httpStatic) {
server.on('error', function(err) {
if (err.code === "EADDRINUSE") {
if (err.errno === "EADDRINUSE") {
RED.log.error(RED.log._("server.unable-to-listen", {listenpath:getListenPath()}));
RED.log.error(RED.log._("server.port-in-use"));
} else {

View File

@@ -416,7 +416,7 @@ module.exports = {
*/
// theme: "vs",
/** other overrides can be set e.g. fontSize, fontFamily, fontLigatures etc.
* for the full list, see https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IStandaloneEditorConstructionOptions.html
* for the full list, see https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html
*/
//fontSize: 14,
//fontFamily: "Cascadia Code, Fira Code, Consolas, 'Courier New', monospace",

View File

@@ -854,7 +854,7 @@ describe('inject node', function() {
});
n1.on("call:error", function(err) {
count++;
if (count == 1) {
if (count == 2) {
done();
}
});

View File

@@ -31,7 +31,6 @@ var multer = require("multer");
var RED = require("nr-test-utils").require("node-red/lib/red");
var fs = require('fs-extra');
var auth = require('basic-auth');
var crypto = require("crypto");
const { version } = require("os");
const net = require('net')
@@ -164,100 +163,6 @@ describe('HTTP Request Node', function() {
delete process.env.NO_PROXY;
}
function getDigestPassword() {
return 'digest-test-password';
}
function getDigest(algorithm, value) {
var hash;
if (algorithm === 'SHA-256') {
hash = crypto.createHash('sha256');
} else if (algorithm === 'SHA-512-256') {
hash = crypto.createHash('sha512');
} else {
hash = crypto.createHash('md5');
}
var hex = hash.update(value).digest('hex');
if (algorithm === 'SHA-512-256') {
hex = hex.slice(0, 64);
}
return hex;
}
function getDigestResponse(req, algorithm, sess, realm, username, nonce, nc, cnonce, qop) {
var ha1 = getDigest(algorithm, username + ':' + realm + ':' + getDigestPassword());
if (sess) {
ha1 = getDigest(algorithm, ha1 + ':' + nonce + ':' + cnonce)
}
let ha2 = getDigest(algorithm, req.method + ':' + req.path);
return qop
? getDigest(algorithm, ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
: getDigest(algorithm, ha1 + ':' + nonce + ':' + ha2);
}
function handleDigestResponse(req, res, algorithm, sess, qop) {
let realm = "node-red";
let nonce = "123456";
let nc = '00000001';
let algorithmValue = sess ? `${algorithm}-sess` : algorithm;
let authHeader = req.headers['authorization'];
if (!authHeader) {
let qopField = qop ? `qop="${qop}", ` : '';
res.setHeader(
'WWW-Authenticate',
`Digest ${qopField}realm="${realm}", nonce="${nonce}", algorithm="${algorithmValue}"`
);
res.status(401).end();
return;
}
var authFields = {};
let re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi;
for (;;) {
var match = re.exec(authHeader);
if (!match) {
break;
}
authFields[match[1]] = match[2] || match[3];
}
// console.log(JSON.stringify(authFields));
if (qop && authFields['qop'] != qop) {
console.log('test1');
res.status(401).end();
return;
}
if (
!authFields['username'] ||
!authFields['response'] ||
authFields['realm'] != realm ||
authFields['nonce'] != nonce ||
authFields['algorithm'] != algorithmValue
) {
console.log('test2');
res.status(401).end();
return;
}
let username = authFields['username'];
let response = authFields['response'];
let cnonce = authFields['cnonce'] || '';
let expectedResponse = getDigestResponse(
req, algorithm, sess, realm, username, nonce, nc, cnonce, qop
);
if (!response || expectedResponse.toLowerCase() !== response.toLowerCase()) {
console.log('test3', response, expectedResponse);
res.status(401).end();
return;
}
res.status(201).end();
}
before(function(done) {
testApp = express();
@@ -317,21 +222,6 @@ describe('HTTP Request Node', function() {
}
res.json(result);
});
testApp.get('/authenticate-digest-md5', function(req, res){
handleDigestResponse(req, res, "MD5", false, false);
});
testApp.get('/authenticate-digest-md5-sess', function(req, res){
handleDigestResponse(req, res, "MD5", true, 'auth');
});
testApp.get('/authenticate-digest-md5-qop', function(req, res){
handleDigestResponse(req, res, "MD5", false, 'auth');
});
testApp.get('/authenticate-digest-sha-256', function(req, res){
handleDigestResponse(req, res, "SHA-256", false, 'auth');
});
testApp.get('/authenticate-digest-sha-512-256', function(req, res){
handleDigestResponse(req, res, "SHA-512-256", false, 'auth');
});
testApp.get('/proxyAuthenticate', function(req, res){
// var user = auth.parse(req.headers['proxy-authorization']);
var result = {
@@ -2128,100 +2018,6 @@ describe('HTTP Request Node', function() {
});
*/
it('should authenticate on server - digest MD5', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-md5')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should authenticate on server - digest MD5 sess', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-md5-sess')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should authenticate on server - digest MD5 qop', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-md5-qop')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should authenticate on server - digest SHA-256', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-sha-256')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should authenticate on server - digest SHA-512-256', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"digest", url:getTestURL('/authenticate-digest-sha-512-256')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n1.credentials = {user:'xxxuser', password:getDigestPassword()};
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',201);
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
});
describe('file-upload', function() {

View File

@@ -53,7 +53,7 @@ describe('MQTT Nodes', function () {
mqttBroker.should.have.property('broker', BROKER_HOST);
mqttBroker.should.have.property('port', BROKER_PORT);
mqttBroker.should.have.property('brokerurl');
mqttBroker.should.have.property('autoUnsubscribe', true); //default: true
// mqttBroker.should.have.property('autoUnsubscribe', true);//default: true
mqttBroker.should.have.property('autoConnect', false);//Set "autoConnect:false" in brokerOptions
mqttBroker.should.have.property('options');
mqttBroker.options.should.have.property('clean', true);
@@ -96,8 +96,8 @@ describe('MQTT Nodes', function () {
mqttBroker.should.have.property('broker', BROKER_HOST);
mqttBroker.should.have.property('port', BROKER_PORT);
mqttBroker.should.have.property('brokerurl');
mqttBroker.should.have.property('autoUnsubscribe', true);
mqttBroker.should.have.property('autoConnect', false); //Set "autoConnect:false" in brokerOptions
// mqttBroker.should.have.property('autoUnsubscribe', true);//default: true
mqttBroker.should.have.property('autoConnect', false);//Set "autoConnect:false" in brokerOptions
mqttBroker.should.have.property('options');
mqttBroker.options.should.have.property('clean', false);
mqttBroker.options.should.have.property('clientId', 'clientid');

View File

@@ -194,55 +194,6 @@ describe('file Nodes', function() {
});
});
it('should append to a file and add newline, except last line of multipart input', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false, wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];
try {
fs.unlinkSync(fileToTest);
} catch(err) {
}
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
var n2 = helper.getNode("helperNode1");
var count = 0;
//var data = ["Line1", "Line2"];
n2.on("input", function (msg) {
try {
msg.should.have.property("payload");
//data.should.containDeep([msg.payload]);
if (count === 3) {
var f = fs.readFileSync(fileToTest).toString();
if (os.type() !== "Windows_NT") {
f.should.have.length(23);
f.should.equal("Line1\nLine2\nLine3\nLine4");
}
else {
f.should.have.length(23);
f.should.equal("Line1\r\nLine2\r\nLine3\r\nLine4");
}
done();
}
count++;
}
catch (e) {
done(e);
}
});
n1.receive({payload:"Line1",parts:{index:0,type:"string"}}); // string
setTimeout(function() {
n1.receive({payload:"Line2",parts:{index:1,type:"string"}}); // string
},30);
setTimeout(function() {
n1.receive({payload:"Line3",parts:{index:2,type:"string"}}); // string
},60);
setTimeout(function() {
n1.receive({payload:"Line4",parts:{index:3,type:"string",count:4}}); // string
},90);
});
});
it('should append to a file after it has been deleted ', function(done) {
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false, wires: [["helperNode1"]]},
{id:"helperNode1", type:"helper"}];

View File

@@ -61,14 +61,12 @@ describe("api/editor/index", function() {
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m),"init").callsFake(function(){});
});
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme"),"app").callsFake(function(){ return express()});
sinon.stub(NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/settings"),"sshkeys").callsFake(function(){ return express()});
});
after(function() {
mockList.forEach(function(m) {
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/"+m).init.restore();
})
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/theme").app.restore();
NR_TEST_UTILS.require("@node-red/editor-api/lib/editor/settings").sshkeys.restore();
auth.needsPermission.restore();
log.error.restore();
});

View File

@@ -41,7 +41,7 @@ describe("api/editor/settings", function() {
});
it('returns the user settings', function(done) {
info.init({}, {
info.init({
settings: {
getUserSettings: function(opts) {
if (opts.user !== "fred") {
@@ -67,7 +67,7 @@ describe("api/editor/settings", function() {
});
it('updates the user settings', function(done) {
var update;
info.init({}, {
info.init({
settings: {
updateUserSettings: function(opts) {
if (opts.user !== "fred") {

View File

@@ -34,7 +34,7 @@ describe("api/editor/sshkeys", function() {
}
}
before(function() {
sshkeys.init({}, mockRuntime);
sshkeys.init(mockRuntime);
app = express();
app.use(bodyParser.json());
app.use("/settings/user/keys", sshkeys.app());