Merge branch 'node-red:dev' into dev

This commit is contained in:
ralphwetzel 2022-01-13 23:36:23 +01:00 committed by GitHub
commit eb1b8b577f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 1920 additions and 3479 deletions

File diff suppressed because it is too large Load Diff

View File

@ -26,13 +26,13 @@
} }
], ],
"dependencies": { "dependencies": {
"acorn": "8.6.0", "acorn": "8.7.0",
"acorn-walk": "8.2.0", "acorn-walk": "8.2.0",
"ajv": "8.8.2", "ajv": "8.8.2",
"async-mutex": "0.3.2", "async-mutex": "0.3.2",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.19.0", "body-parser": "1.19.1",
"cheerio": "1.0.0-rc.10", "cheerio": "1.0.0-rc.10",
"clone": "2.1.2", "clone": "2.1.2",
"content-type": "1.0.4", "content-type": "1.0.4",
@ -41,7 +41,7 @@
"cors": "2.8.5", "cors": "2.8.5",
"cronosjs": "1.7.1", "cronosjs": "1.7.1",
"denque": "2.0.1", "denque": "2.0.1",
"express": "4.17.1", "express": "4.17.2",
"express-session": "1.17.2", "express-session": "1.17.2",
"form-data": "4.0.0", "form-data": "4.0.0",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
@ -50,7 +50,7 @@
"hash-sum": "2.0.0", "hash-sum": "2.0.0",
"hpagent": "0.1.2", "hpagent": "0.1.2",
"https-proxy-agent": "5.0.0", "https-proxy-agent": "5.0.0",
"i18next": "21.5.4", "i18next": "21.6.6",
"iconv-lite": "0.6.3", "iconv-lite": "0.6.3",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.14.1", "js-yaml": "3.14.1",
@ -61,21 +61,21 @@
"memorystore": "1.6.6", "memorystore": "1.6.6",
"mime": "2.5.2", "mime": "2.5.2",
"moment-timezone": "0.5.34", "moment-timezone": "0.5.34",
"mqtt": "4.2.8", "mqtt": "4.3.4",
"multer": "1.4.3", "multer": "1.4.3",
"mustache": "4.2.0", "mustache": "4.2.0",
"node-red-admin": "^2.2.1", "node-red-admin": "^2.2.1",
"nopt": "5.0.0", "nopt": "5.0.0",
"oauth2orize": "1.11.1", "oauth2orize": "1.11.1",
"on-headers": "1.0.2", "on-headers": "1.0.2",
"passport": "0.5.0", "passport": "0.5.2",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"raw-body": "2.4.2", "raw-body": "2.4.2",
"semver": "7.3.5", "semver": "7.3.5",
"tar": "6.1.11", "tar": "6.1.11",
"tough-cookie": "4.0.0", "tough-cookie": "4.0.0",
"uglify-js": "3.14.4", "uglify-js": "3.14.5",
"uuid": "8.3.2", "uuid": "8.3.2",
"ws": "7.5.1", "ws": "7.5.1",
"xml2js": "0.4.23" "xml2js": "0.4.23"
@ -84,7 +84,7 @@
"bcrypt": "5.0.1" "bcrypt": "5.0.1"
}, },
"devDependencies": { "devDependencies": {
"dompurify": "2.3.3", "dompurify": "2.3.4",
"grunt": "1.4.1", "grunt": "1.4.1",
"grunt-chmod": "~1.1.1", "grunt-chmod": "~1.1.1",
"grunt-cli": "~1.4.3", "grunt-cli": "~1.4.3",
@ -113,11 +113,11 @@
"node-red-node-test-helper": "^0.2.7", "node-red-node-test-helper": "^0.2.7",
"nodemon": "2.0.15", "nodemon": "2.0.15",
"proxy": "^1.0.2", "proxy": "^1.0.2",
"sass": "1.44.0", "sass": "1.48.0",
"should": "13.2.3", "should": "13.2.3",
"sinon": "11.1.2", "sinon": "11.1.2",
"stoppable": "^1.1.0", "stoppable": "^1.1.0",
"supertest": "6.1.6" "supertest": "6.2.1"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"

View File

@ -146,7 +146,7 @@ function authenticateUserToken(req) {
} else { } else {
reject(); reject();
} }
}); }).catch(reject);
} else { } else {
reject(); reject();
} }
@ -163,6 +163,9 @@ TokensStrategy.prototype.authenticate = function(req) {
authenticateUserToken(req).then(user => { authenticateUserToken(req).then(user => {
this.success(user,{scope:user.permissions}); this.success(user,{scope:user.permissions});
}).catch(err => { }).catch(err => {
if (err) {
log.trace("token authentication failure: "+err.stack?err.stack:err)
}
this.fail(401); this.fail(401);
}); });
} }

View File

@ -90,6 +90,8 @@ function init(settings,_server,storage,runtimeAPI) {
auth.getToken, auth.getToken,
auth.errorHandler auth.errorHandler
); );
} else if (settings.adminAuth.tokens) {
adminApp.use(passport.initialize());
} }
adminApp.post("/auth/revoke",auth.needsPermission(""),auth.revoke,apiUtil.errorHandler); adminApp.post("/auth/revoke",auth.needsPermission(""),auth.revoke,apiUtil.errorHandler);
} }

View File

@ -19,11 +19,11 @@
"@node-red/util": "2.2.0-beta.1", "@node-red/util": "2.2.0-beta.1",
"@node-red/editor-client": "2.2.0-beta.1", "@node-red/editor-client": "2.2.0-beta.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.19.0", "body-parser": "1.19.1",
"clone": "2.1.2", "clone": "2.1.2",
"cors": "2.8.5", "cors": "2.8.5",
"express-session": "1.17.2", "express-session": "1.17.2",
"express": "4.17.1", "express": "4.17.2",
"memorystore": "1.6.6", "memorystore": "1.6.6",
"mime": "2.5.2", "mime": "2.5.2",
"multer": "1.4.3", "multer": "1.4.3",
@ -31,7 +31,7 @@
"oauth2orize": "1.11.1", "oauth2orize": "1.11.1",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"passport": "0.5.0", "passport": "0.5.2",
"ws": "7.5.1" "ws": "7.5.1"
}, },
"optionalDependencies": { "optionalDependencies": {

View File

@ -894,6 +894,8 @@
"addTitle": "add an item" "addTitle": "add an item"
}, },
"search": { "search": {
"history": "Search history",
"clear": "clear all",
"empty": "No matches found", "empty": "No matches found",
"addNode": "add a node..." "addNode": "add a node..."
}, },

View File

@ -59,6 +59,8 @@
"hideOtherFlows": "他のフローを非表示", "hideOtherFlows": "他のフローを非表示",
"showAllFlows": "全てのフローを表示", "showAllFlows": "全てのフローを表示",
"hideAllFlows": "全てのフローを非表示", "hideAllFlows": "全てのフローを非表示",
"hiddenFlows": "__count__ 個の非表示のフロー一覧",
"hiddenFlows_plural": "__count__ 個の非表示のフロー一覧",
"showLastHiddenFlow": "最後に非表示にしたフローを表示", "showLastHiddenFlow": "最後に非表示にしたフローを表示",
"listFlows": "フロー一覧", "listFlows": "フロー一覧",
"listSubflows": "サブフロー一覧", "listSubflows": "サブフロー一覧",
@ -669,7 +671,8 @@
"unusedConfigNodes": "未使用の設定ノード", "unusedConfigNodes": "未使用の設定ノード",
"invalidNodes": "不正なノード", "invalidNodes": "不正なノード",
"uknownNodes": "未知のノード", "uknownNodes": "未知のノード",
"unusedSubflows": "未使用のサブフロー" "unusedSubflows": "未使用のサブフロー",
"hiddenFlows": "非表示のフロー"
} }
}, },
"help": { "help": {

View File

@ -684,6 +684,13 @@ RED.history = (function() {
peek: function() { peek: function() {
return undoHistory[undoHistory.length-1]; return undoHistory[undoHistory.length-1];
}, },
replace: function(ev) {
if (undoHistory.length === 0) {
RED.history.push(ev);
} else {
undoHistory[undoHistory.length-1] = ev;
}
},
clear: function() { clear: function() {
undoHistory = []; undoHistory = [];
redoHistory = []; redoHistory = [];

View File

@ -38,7 +38,9 @@
}, },
"red-ui-workspace": { "red-ui-workspace": {
"backspace": "core:delete-selection", "backspace": "core:delete-selection",
"ctrl-backspace": "core:delete-selection-and-reconnect",
"delete": "core:delete-selection", "delete": "core:delete-selection",
"ctrl-delete": "core:delete-selection-and-reconnect",
"enter": "core:edit-selected-node", "enter": "core:edit-selected-node",
"ctrl-enter": "core:go-to-selection", "ctrl-enter": "core:go-to-selection",
"ctrl-c": "core:copy-selection-to-internal-clipboard", "ctrl-c": "core:copy-selection-to-internal-clipboard",

View File

@ -15,6 +15,9 @@
**/ **/
RED.nodes = (function() { RED.nodes = (function() {
var PORT_TYPE_INPUT = 1;
var PORT_TYPE_OUTPUT = 0;
var node_defs = {}; var node_defs = {};
var linkTabMap = {}; var linkTabMap = {};
@ -2458,6 +2461,144 @@ RED.nodes = (function() {
return helpContent; return helpContent;
} }
function getNodeIslands(nodes) {
var selectedNodes = new Set(nodes);
// Maps node => island index
var nodeToIslandIndex = new Map();
// Maps island index => [nodes in island]
var islandIndexToNodes = new Map();
var internalLinks = new Set();
nodes.forEach((node, index) => {
nodeToIslandIndex.set(node,index);
islandIndexToNodes.set(index, [node]);
var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
inboundLinks.forEach(l => {
if (selectedNodes.has(l.source)) {
internalLinks.add(l)
}
})
outboundLinks.forEach(l => {
if (selectedNodes.has(l.target)) {
internalLinks.add(l)
}
})
})
internalLinks.forEach(l => {
let source = l.source;
let target = l.target;
if (nodeToIslandIndex.get(source) !== nodeToIslandIndex.get(target)) {
let sourceIsland = nodeToIslandIndex.get(source);
let islandToMove = nodeToIslandIndex.get(target);
let nodesToMove = islandIndexToNodes.get(islandToMove);
nodesToMove.forEach(n => {
nodeToIslandIndex.set(n,sourceIsland);
islandIndexToNodes.get(sourceIsland).push(n);
})
islandIndexToNodes.delete(islandToMove);
}
})
const result = [];
islandIndexToNodes.forEach((nodes,index) => {
result.push(nodes);
})
return result;
}
function detachNodes(nodes) {
let allSelectedNodes = [];
nodes.forEach(node => {
if (node.type === 'group') {
let groupNodes = RED.group.getNodes(node,true,true);
allSelectedNodes = allSelectedNodes.concat(groupNodes);
} else {
allSelectedNodes.push(node);
}
})
if (allSelectedNodes.length > 0 ) {
const nodeIslands = RED.nodes.getNodeIslands(allSelectedNodes);
let removedLinks = [];
let newLinks = [];
let createdLinkIds = new Set();
nodeIslands.forEach(nodes => {
let selectedNodes = new Set(nodes);
let allInboundLinks = [];
let allOutboundLinks = [];
// Identify links that enter or exit this island of nodes
nodes.forEach(node => {
var inboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_INPUT);
var outboundLinks = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
inboundLinks.forEach(l => {
if (!selectedNodes.has(l.source)) {
allInboundLinks.push(l)
}
})
outboundLinks.forEach(l => {
if (!selectedNodes.has(l.target)) {
allOutboundLinks.push(l)
}
})
});
// Identify the links to restore
allInboundLinks.forEach(inLink => {
// For Each inbound link,
// - get source node.
// - trace through to all outbound links
let sourceNode = inLink.source;
let targetNodes = new Set();
let visited = new Set();
let stack = [inLink.target];
while (stack.length > 0) {
let node = stack.pop(stack);
visited.add(node)
let links = RED.nodes.getNodeLinks(node, PORT_TYPE_OUTPUT);
links.forEach(l => {
if (visited.has(l.target)) {
return
}
visited.add(l.target);
if (selectedNodes.has(l.target)) {
// internal link
stack.push(l.target)
} else {
targetNodes.add(l.target)
}
})
}
targetNodes.forEach(target => {
let linkId = `${sourceNode.id}[${inLink.sourcePort}] -> ${target.id}`
if (!createdLinkIds.has(linkId)) {
createdLinkIds.add(linkId);
let link = {
source: sourceNode,
sourcePort: inLink.sourcePort,
target: target
}
let existingLinks = RED.nodes.filterLinks(link)
if (existingLinks.length === 0) {
newLinks.push(link);
}
}
})
})
// 2. delete all those links
allInboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
allOutboundLinks.forEach(l => { RED.nodes.removeLink(l); removedLinks.push(l)})
})
newLinks.forEach(l => RED.nodes.addLink(l));
return {
newLinks,
removedLinks
}
}
}
return { return {
init: function() { init: function() {
RED.events.on("registry:node-type-added",function(type) { RED.events.on("registry:node-type-added",function(type) {
@ -2539,7 +2680,7 @@ RED.nodes = (function() {
add: addNode, add: addNode,
remove: removeNode, remove: removeNode,
clear: clear, clear: clear,
detachNodes: detachNodes,
moveNodesForwards: moveNodesForwards, moveNodesForwards: moveNodesForwards,
moveNodesBackwards: moveNodesBackwards, moveNodesBackwards: moveNodesBackwards,
moveNodesToFront: moveNodesToFront, moveNodesToFront: moveNodesToFront,
@ -2551,7 +2692,20 @@ RED.nodes = (function() {
addLink: addLink, addLink: addLink,
removeLink: removeLink, removeLink: removeLink,
getNodeLinks: function(id, portType) {
if (typeof id !== 'string') {
id = id.id;
}
if (nodeLinks[id]) {
if (portType === 1) {
// Return cloned arrays so they can be safely modified by caller
return [].concat(nodeLinks[id].in)
} else {
return [].concat(nodeLinks[id].out)
}
}
return [];
},
addWorkspace: addWorkspace, addWorkspace: addWorkspace,
removeWorkspace: removeWorkspace, removeWorkspace: removeWorkspace,
getWorkspaceOrder: function() { return workspacesOrder }, getWorkspaceOrder: function() { return workspacesOrder },
@ -2625,6 +2779,7 @@ RED.nodes = (function() {
getAllFlowNodes: getAllFlowNodes, getAllFlowNodes: getAllFlowNodes,
getAllUpstreamNodes: getAllUpstreamNodes, getAllUpstreamNodes: getAllUpstreamNodes,
getAllDownstreamNodes: getAllDownstreamNodes, getAllDownstreamNodes: getAllDownstreamNodes,
getNodeIslands: getNodeIslands,
createExportableNodeSet: createExportableNodeSet, createExportableNodeSet: createExportableNodeSet,
createCompleteNodeSet: createCompleteNodeSet, createCompleteNodeSet: createCompleteNodeSet,
updateConfigNodeUsers: updateConfigNodeUsers, updateConfigNodeUsers: updateConfigNodeUsers,

View File

@ -71,6 +71,7 @@ RED.clipboard = (function() {
text: RED._("common.label.cancel"), text: RED._("common.label.cancel"),
click: function() { click: function() {
$( this ).dialog( "close" ); $( this ).dialog( "close" );
RED.view.focus();
} }
}, },
{ // red-ui-clipboard-dialog-download { // red-ui-clipboard-dialog-download
@ -81,6 +82,7 @@ RED.clipboard = (function() {
var data = $("#red-ui-clipboard-dialog-export-text").val(); var data = $("#red-ui-clipboard-dialog-export-text").val();
downloadData("flows.json", data); downloadData("flows.json", data);
$( this ).dialog( "close" ); $( this ).dialog( "close" );
RED.view.focus();
} }
}, },
{ // red-ui-clipboard-dialog-export { // red-ui-clipboard-dialog-export
@ -95,6 +97,7 @@ RED.clipboard = (function() {
$( this ).dialog( "close" ); $( this ).dialog( "close" );
copyText(flowData); copyText(flowData);
RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"}); RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"});
RED.view.focus();
} else { } else {
var flowToExport = $("#red-ui-clipboard-dialog-export-text").val(); var flowToExport = $("#red-ui-clipboard-dialog-export-text").val();
var selectedPath = activeLibraries[activeTab].getSelected(); var selectedPath = activeLibraries[activeTab].getSelected();
@ -110,6 +113,7 @@ RED.clipboard = (function() {
contentType: "application/json; charset=utf-8" contentType: "application/json; charset=utf-8"
}).done(function() { }).done(function() {
$(dialog).dialog( "close" ); $(dialog).dialog( "close" );
RED.view.focus();
RED.notify(RED._("library.exportedToLibrary"),"success"); RED.notify(RED._("library.exportedToLibrary"),"success");
}).fail(function(xhr,textStatus,err) { }).fail(function(xhr,textStatus,err) {
if (xhr.status === 401) { if (xhr.status === 401) {
@ -171,6 +175,7 @@ RED.clipboard = (function() {
} }
} }
$( this ).dialog( "close" ); $( this ).dialog( "close" );
RED.view.focus();
} }
}, },
{ // red-ui-clipboard-dialog-import-conflict { // red-ui-clipboard-dialog-import-conflict
@ -203,6 +208,7 @@ RED.clipboard = (function() {
// console.table(pendingImportConfig.importNodes.map(function(n) { return {id:n.id,type:n.type,result:importMap[n.id]}})) // console.table(pendingImportConfig.importNodes.map(function(n) { return {id:n.id,type:n.type,result:importMap[n.id]}}))
RED.view.importNodes(newNodes, pendingImportConfig.importOptions); RED.view.importNodes(newNodes, pendingImportConfig.importOptions);
$( this ).dialog( "close" ); $( this ).dialog( "close" );
RED.view.focus();
} }
} }
], ],
@ -940,28 +946,25 @@ RED.clipboard = (function() {
if (truncated) { if (truncated) {
msg += "_truncated"; msg += "_truncated";
} }
$("#red-ui-clipboard-hidden").val(value).focus().select(); navigator.clipboard.writeText(value).then(function () {
var result = document.execCommand("copy"); if (element) {
if (result && element) { var popover = RED.popover.create({
var popover = RED.popover.create({ target: element,
target: element, direction: 'left',
direction: 'left', size: 'small',
size: 'small', content: RED._(msg)
content: RED._(msg) });
}); setTimeout(function() {
setTimeout(function() { popover.close();
popover.close(); },1000);
},1000); popover.open();
popover.open(); }
} if (currentFocus) {
$("#red-ui-clipboard-hidden").val(""); $(currentFocus).focus();
if (currentFocus) { }
$(currentFocus).focus(); }).catch(err => { console.error("Failed to copy:",err) });
}
return result;
} }
function importNodes(nodesStr,addFlow) { function importNodes(nodesStr,addFlow) {
var newNodes = nodesStr; var newNodes = nodesStr;
if (typeof nodesStr === 'string') { if (typeof nodesStr === 'string') {
@ -1236,8 +1239,6 @@ RED.clipboard = (function() {
init: function() { init: function() {
setupDialogs(); setupDialogs();
$('<textarea type="text" id="red-ui-clipboard-hidden" tabIndex="-1">').appendTo("#red-ui-editor");
RED.actions.add("core:show-export-dialog",showExportNodes); RED.actions.add("core:show-export-dialog",showExportNodes);
RED.actions.add("core:show-import-dialog",showImportNodes); RED.actions.add("core:show-import-dialog",showImportNodes);

View File

@ -578,7 +578,7 @@ RED.tabs = (function() {
function findPreviousVisibleTab(li) { function findPreviousVisibleTab(li) {
if (!li) { if (!li) {
li = ul.find("li.active").parent(); li = ul.find("li.active");
} }
var previous = li.prev(); var previous = li.prev();
while(previous.length > 0 && previous.hasClass("hide-tab")) { while(previous.length > 0 && previous.hasClass("hide-tab")) {
@ -588,9 +588,9 @@ RED.tabs = (function() {
} }
function findNextVisibleTab(li) { function findNextVisibleTab(li) {
if (!li) { if (!li) {
li = ul.find("li.active").parent(); li = ul.find("li.active");
} }
var next = ul.find("li.active").next(); var next = li.next();
while(next.length > 0 && next.hasClass("hide-tab")) { while(next.length > 0 && next.hasClass("hide-tab")) {
next = next.next(); next = next.next();
} }

View File

@ -333,6 +333,16 @@ RED.deploy = (function() {
var unknownNodes = []; var unknownNodes = [];
var invalidNodes = []; var invalidNodes = [];
RED.nodes.eachConfig(function(node) {
if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node));
}
if (node.type === "unknown") {
if (unknownNodes.indexOf(node.name) == -1) {
unknownNodes.push(node.name);
}
}
});
RED.nodes.eachNode(function(node) { RED.nodes.eachNode(function(node) {
if (!node.valid && !node.d) { if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node)); invalidNodes.push(getNodeInfo(node));

View File

@ -247,7 +247,7 @@
var currentExpression = expressionEditor.getValue(); var currentExpression = expressionEditor.getValue();
var expr; var expr;
var usesContext = false; var usesContext = false;
var legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression); var legacyMode = /(^|[^a-zA-Z0-9_'".])msg([^a-zA-Z0-9_'"]|$)/.test(currentExpression);
$(".red-ui-editor-type-expression-legacy").toggle(legacyMode); $(".red-ui-editor-type-expression-legacy").toggle(legacyMode);
try { try {
expr = jsonata(currentExpression); expr = jsonata(currentExpression);

View File

@ -81,7 +81,8 @@
clearTimeout: true, clearTimeout: true,
setInterval: true, setInterval: true,
clearInterval: true clearInterval: true
} },
extraLibs: options.extraLibs
}); });
if (options.cursor) { if (options.cursor) {
expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false); expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);

View File

@ -55,7 +55,9 @@
} }
}); });
} }
if (!isSameObj(old_env, new_env)) { if (!old_env && new_env.length === 0) {
delete node.env;
} else if (!isSameObj(old_env, new_env)) {
editState.changes.env = node.env; editState.changes.env = node.env;
if (new_env.length === 0) { if (new_env.length === 0) {
delete node.env; delete node.env;

View File

@ -590,12 +590,14 @@ RED.group = (function() {
markDirty(group); markDirty(group);
} }
function getNodes(group,recursive) { function getNodes(group,recursive,excludeGroup) {
var nodes = []; var nodes = [];
group.nodes.forEach(function(n) { group.nodes.forEach(function(n) {
nodes.push(n); if (n.type !== 'group' || !excludeGroup) {
nodes.push(n);
}
if (recursive && n.type === 'group') { if (recursive && n.type === 'group') {
nodes = nodes.concat(getNodes(n,recursive)) nodes = nodes.concat(getNodes(n,recursive,excludeGroup))
} }
}) })
return nodes; return nodes;

View File

@ -22,6 +22,7 @@ RED.search = (function() {
var selected = -1; var selected = -1;
var visible = false; var visible = false;
var searchHistory = [];
var index = {}; var index = {};
var currentResults = []; var currentResults = [];
var previousActiveElement; var previousActiveElement;
@ -205,6 +206,20 @@ RED.search = (function() {
} }
} }
function populateSearchHistory() {
if (searchHistory.length > 0) {
searchResults.editableList('addItem',{
historyHeader: true
});
searchHistory.forEach(function(entry) {
searchResults.editableList('addItem',{
history: true,
value: entry
});
})
}
}
function createDialog() { function createDialog() {
dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#red-ui-main-container"); dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#red-ui-main-container");
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog); var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
@ -213,7 +228,12 @@ RED.search = (function() {
change: function() { change: function() {
searchResults.editableList('empty'); searchResults.editableList('empty');
selected = -1; selected = -1;
currentResults = search($(this).val()); var value = $(this).val();
if (value === "") {
populateSearchHistory();
return;
}
currentResults = search(value);
if (currentResults.length > 0) { if (currentResults.length > 0) {
for (i=0;i<Math.min(currentResults.length,25);i++) { for (i=0;i<Math.min(currentResults.length,25);i++) {
searchResults.editableList('addItem',currentResults[i]) searchResults.editableList('addItem',currentResults[i])
@ -285,7 +305,12 @@ RED.search = (function() {
}) })
} }
} }
} else { } if ($(children[selected]).hasClass("red-ui-search-history")) {
var object = $(children[selected]).find(".red-ui-editableList-item-content").data('data');
if (object) {
searchInput.searchBox('value',object.value)
}
} else if (!$(children[selected]).hasClass("red-ui-search-historyHeader")) {
if (currentResults.length > 0) { if (currentResults.length > 0) {
reveal(currentResults[Math.max(0,selected)].node); reveal(currentResults[Math.max(0,selected)].node);
} }
@ -301,7 +326,32 @@ RED.search = (function() {
addItem: function(container,i,object) { addItem: function(container,i,object) {
var node = object.node; var node = object.node;
var div; var div;
if (object.more) { if (object.historyHeader) {
container.parent().addClass("red-ui-search-historyHeader")
$('<div>',{class:"red-ui-search-empty"}).text(RED._("search.history")).appendTo(container);
$('<button type="button" class="red-ui-button red-ui-button-small"></button>').text(RED._("search.clear")).appendTo(container).on("click", function(evt) {
evt.preventDefault();
searchHistory = [];
searchResults.editableList('empty');
});
} else if (object.history) {
container.parent().addClass("red-ui-search-history")
div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
div.text(object.value);
div.on("click", function(evt) {
evt.preventDefault();
searchInput.searchBox('value',object.value)
searchInput.focus();
})
$('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-remove"></i></button>').appendTo(container).on("click", function(evt) {
evt.preventDefault();
var index = searchHistory.indexOf(object.value);
searchHistory.splice(index,1);
searchResults.editableList('removeItem', object);
});
} else if (object.more) {
container.parent().addClass("red-ui-search-more") container.parent().addClass("red-ui-search-more")
div = $('<a>',{href:'#',class:"red-ui-search-result red-ui-search-empty"}).appendTo(container); div = $('<a>',{href:'#',class:"red-ui-search-result red-ui-search-empty"}).appendTo(container);
div.text(RED._("palette.editor.more",{count:object.more.results.length-object.more.start})); div.text(RED._("palette.editor.more",{count:object.more.results.length-object.more.start}));
@ -356,6 +406,12 @@ RED.search = (function() {
} }
function reveal(node) { function reveal(node) {
var searchVal = searchInput.val();
var existingIndex = searchHistory.indexOf(searchVal);
if (existingIndex > -1) {
searchHistory.splice(existingIndex,1);
}
searchHistory.unshift(searchInput.val());
hide(); hide();
RED.view.reveal(node.id); RED.view.reveal(node.id);
} }
@ -374,9 +430,14 @@ RED.search = (function() {
if (dialog === null) { if (dialog === null) {
createDialog(); createDialog();
} else {
searchResults.editableList('empty');
} }
dialog.slideDown(300); dialog.slideDown(300);
searchInput.searchBox('value',v) searchInput.searchBox('value',v)
if (!v || v === "") {
populateSearchHistory();
}
RED.events.emit("search:open"); RED.events.emit("search:open");
visible = true; visible = true;
} }

View File

@ -27,5 +27,7 @@ RED.state = {
PANNING: 10, PANNING: 10,
SELECTING_NODE: 11, SELECTING_NODE: 11,
GROUP_DRAGGING: 12, GROUP_DRAGGING: 12,
GROUP_RESIZE: 13 GROUP_RESIZE: 13,
DETACHED_DRAGGING: 14,
SLICING: 15
} }

View File

@ -256,6 +256,10 @@ RED.tourGuide = (function() {
} }
$('<div>').css("text-align","left").html(getLocaleText(step.description)).appendTo(stepDescription); $('<div>').css("text-align","left").html(getLocaleText(step.description)).appendTo(stepDescription);
if (step.image) {
$(`<img src="red/tours/${step.image}" />`).appendTo(stepDescription)
}
var stepToolbar = $('<div>',{class:"red-ui-tourGuide-toolbar"}).appendTo(stepContent); var stepToolbar = $('<div>',{class:"red-ui-tourGuide-toolbar"}).appendTo(stepContent);
// var breadcrumbs = $('<div>',{class:"red-ui-tourGuide-breadcrumbs"}).appendTo(stepToolbar); // var breadcrumbs = $('<div>',{class:"red-ui-tourGuide-breadcrumbs"}).appendTo(stepToolbar);

View File

@ -725,6 +725,90 @@ RED.view.tools = (function() {
} }
} }
function wireSeriesOfNodes() {
var selection = RED.view.selection();
if (selection.nodes) {
if (selection.nodes.length > 1) {
var i = 0;
var newLinks = [];
while (i < selection.nodes.length - 1) {
var nodeA = selection.nodes[i];
var nodeB = selection.nodes[i+1];
if (nodeA.outputs > 0 && nodeB.inputs > 0) {
var existingLinks = RED.nodes.filterLinks({
source: nodeA,
target: nodeB,
sourcePort: 0
})
if (existingLinks.length === 0) {
var newLink = {
source: nodeA,
target: nodeB,
sourcePort: 0
}
RED.nodes.addLink(newLink);
newLinks.push(newLink);
}
}
i++;
}
if (newLinks.length > 0) {
RED.history.push({
t: 'add',
links: newLinks,
dirty: RED.nodes.dirty()
})
RED.nodes.dirty(true);
RED.view.redraw(true);
}
}
}
}
function wireNodeToMultiple() {
var selection = RED.view.selection();
if (selection.nodes) {
if (selection.nodes.length > 1) {
var sourceNode = selection.nodes[0];
if (sourceNode.outputs === 0) {
return;
}
var i = 1;
var newLinks = [];
while (i < selection.nodes.length) {
var targetNode = selection.nodes[i];
if (targetNode.inputs > 0) {
var existingLinks = RED.nodes.filterLinks({
source: sourceNode,
target: targetNode,
sourcePort: Math.min(sourceNode.outputs-1,i-1)
})
if (existingLinks.length === 0) {
var newLink = {
source: sourceNode,
target: targetNode,
sourcePort: Math.min(sourceNode.outputs-1,i-1)
}
RED.nodes.addLink(newLink);
newLinks.push(newLink);
}
}
i++;
}
if (newLinks.length > 0) {
RED.history.push({
t: 'add',
links: newLinks,
dirty: RED.nodes.dirty()
})
RED.nodes.dirty(true);
RED.view.redraw(true);
}
}
}
}
return { return {
init: function() { init: function() {
RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
@ -783,7 +867,8 @@ RED.view.tools = (function() {
RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') }) RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') })
RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') }) RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') })
RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })
// RED.actions.add("core:add-node", function() { addNode() }) // RED.actions.add("core:add-node", function() { addNode() })
}, },

View File

@ -63,7 +63,6 @@ RED.view = (function() {
var activeGroups = []; var activeGroups = [];
var dirtyGroups = {}; var dirtyGroups = {};
var selected_link = null;
var mousedown_link = null; var mousedown_link = null;
var mousedown_node = null; var mousedown_node = null;
var mousedown_group = null; var mousedown_group = null;
@ -75,6 +74,8 @@ RED.view = (function() {
var mouse_mode = 0; var mouse_mode = 0;
var mousedown_group_handle = null; var mousedown_group_handle = null;
var lasso = null; var lasso = null;
var slicePath = null;
var slicePathLast = null;
var ghostNode = null; var ghostNode = null;
var showStatus = false; var showStatus = false;
var lastClickNode = null; var lastClickNode = null;
@ -129,6 +130,14 @@ RED.view = (function() {
if (!setIds.has(node.id)) { if (!setIds.has(node.id)) {
set.push({n:node}); set.push({n:node});
setIds.add(node.id); setIds.add(node.id);
var links = RED.nodes.getNodeLinks(node.id,PORT_TYPE_INPUT).concat(RED.nodes.getNodeLinks(node.id,PORT_TYPE_OUTPUT))
for (var i=0,l=links.length;i<l;i++) {
var link = links[i]
if (link.source === node && setIds.has(link.target.id) ||
link.target === node && setIds.has(link.source.id)) {
selectedLinks.add(link)
}
}
} }
} }
}, },
@ -145,6 +154,10 @@ RED.view = (function() {
} }
} }
} }
var links = RED.nodes.getNodeLinks(node.id,PORT_TYPE_INPUT).concat(RED.nodes.getNodeLinks(node.id,PORT_TYPE_OUTPUT))
for (var i=0,l=links.length;i<l;i++) {
selectedLinks.remove(links[i]);
}
} }
}, },
clear: function() { clear: function() {
@ -159,6 +172,31 @@ RED.view = (function() {
return api; return api;
})(); })();
var selectedLinks = (function() {
var links = new Set();
return {
add: function(link) {
links.add(link);
link.selected = true;
},
remove: function(link) {
links.delete(link);
link.selected = false;
},
clear: function() {
links.forEach(function(link) { link.selected = false })
links.clear();
},
length: function() {
return links.size;
},
forEach: function(func) { links.forEach(func) },
has: function(link) { return links.has(link) },
toArray: function() { return Array.from(links) }
}
})();
function init() { function init() {
chart = $("#red-ui-workspace-chart"); chart = $("#red-ui-workspace-chart");
@ -193,6 +231,12 @@ RED.view = (function() {
} }
} else if (mouse_mode === RED.state.PANNING && d3.event.buttons !== 4) { } else if (mouse_mode === RED.state.PANNING && d3.event.buttons !== 4) {
resetMouseVars(); resetMouseVars();
} else if (slicePath) {
if (d3.event.buttons !== 2) {
slicePath.remove();
slicePath = null;
resetMouseVars()
}
} }
}) })
.on("touchend", function() { .on("touchend", function() {
@ -412,26 +456,56 @@ RED.view = (function() {
var historyEvent = result.historyEvent; var historyEvent = result.historyEvent;
var nn = result.node; var nn = result.node;
RED.nodes.add(nn);
var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label"); var showLabel = RED.utils.getMessageProperty(RED.settings.get('editor'),"view.view-node-show-label");
if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) { if (showLabel !== undefined && (nn._def.hasOwnProperty("showLabel")?nn._def.showLabel:true) && !nn._def.defaults.hasOwnProperty("l")) {
nn.l = showLabel; nn.l = showLabel;
} }
var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0)); var helperOffset = d3.touches(ui.helper.get(0))[0]||d3.mouse(ui.helper.get(0));
var helperWidth = ui.helper.width();
var helperHeight = ui.helper.height();
var mousePos = d3.touches(this)[0]||d3.mouse(this); var mousePos = d3.touches(this)[0]||d3.mouse(this);
mousePos[1] += this.scrollTop + ((nn.h/2)-helperOffset[1]); try {
mousePos[0] += this.scrollLeft + ((nn.w/2)-helperOffset[0]); var isLink = (nn.type === "link in" || nn.type === "link out")
var hideLabel = nn.hasOwnProperty('l')?!nn.l : isLink;
var label = RED.utils.getNodeLabel(nn, nn.type);
var labelParts = getLabelParts(label, "red-ui-flow-node-label");
if (hideLabel) {
nn.w = node_height;
nn.h = Math.max(node_height,(nn.outputs || 0) * 15);
} else {
nn.w = Math.max(node_width,20*(Math.ceil((labelParts.width+50+(nn._def.inputs>0?7:0))/20)) );
nn.h = Math.max(6+24*labelParts.lines.length,(nn.outputs || 0) * 15, 30);
}
} catch(err) {
}
mousePos[1] += this.scrollTop + ((helperHeight/2)-helperOffset[1]);
mousePos[0] += this.scrollLeft + ((helperWidth/2)-helperOffset[0]);
mousePos[1] /= scaleFactor; mousePos[1] /= scaleFactor;
mousePos[0] /= scaleFactor; mousePos[0] /= scaleFactor;
if (snapGrid) {
mousePos[0] = gridSize*(Math.ceil(mousePos[0]/gridSize));
mousePos[1] = gridSize*(Math.ceil(mousePos[1]/gridSize));
}
nn.x = mousePos[0]; nn.x = mousePos[0];
nn.y = mousePos[1]; nn.y = mousePos[1];
if (snapGrid) {
var gridOffset = [0,0];
var offsetLeft = nn.x-(gridSize*Math.round((nn.x-nn.w/2)/gridSize)+nn.w/2);
var offsetRight = nn.x-(gridSize*Math.round((nn.x+nn.w/2)/gridSize)-nn.w/2);
if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
gridOffset[0] = offsetLeft
} else {
gridOffset[0] = offsetRight
}
gridOffset[1] = nn.y-(gridSize*Math.round(nn.y/gridSize));
nn.x -= gridOffset[0];
nn.y -= gridOffset[1];
}
var spliceLink = $(ui.helper).data("splice"); var spliceLink = $(ui.helper).data("splice");
if (spliceLink) { if (spliceLink) {
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog
@ -452,7 +526,6 @@ RED.view = (function() {
historyEvent.removedLinks = [spliceLink]; historyEvent.removedLinks = [spliceLink];
} }
RED.nodes.add(nn);
var group = $(ui.helper).data("group"); var group = $(ui.helper).data("group");
if (group) { if (group) {
@ -502,6 +575,8 @@ RED.view = (function() {
RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();}); RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});}); RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});});
RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
RED.events.on("view:selection-changed", function(selection) { RED.events.on("view:selection-changed", function(selection) {
var hasSelection = (selection.nodes && selection.nodes.length > 0); var hasSelection = (selection.nodes && selection.nodes.length > 0);
var hasMultipleSelection = hasSelection && selection.nodes.length > 1; var hasMultipleSelection = hasSelection && selection.nodes.length > 1;
@ -524,6 +599,7 @@ RED.view = (function() {
}) })
RED.actions.add("core:delete-selection",deleteSelection); RED.actions.add("core:delete-selection",deleteSelection);
RED.actions.add("core:delete-selection-and-reconnect",function() { deleteSelection(true) });
RED.actions.add("core:edit-selected-node",editSelection); RED.actions.add("core:edit-selected-node",editSelection);
RED.actions.add("core:go-to-selection",function() { RED.actions.add("core:go-to-selection",function() {
if (movingSet.length() > 0) { if (movingSet.length() > 0) {
@ -909,7 +985,7 @@ RED.view = (function() {
return; return;
} }
if (!mousedown_node && !mousedown_link && !mousedown_group) { if (!mousedown_node && !mousedown_link && !mousedown_group) {
selected_link = null; selectedLinks.clear();
updateSelection(); updateSelection();
} }
if (mouse_mode === 0) { if (mouse_mode === 0) {
@ -918,19 +994,18 @@ RED.view = (function() {
lasso = null; lasso = null;
} }
} }
if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) { if ((mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) && (d3.event.touches || d3.event.button === 0) && (d3.event.metaKey || d3.event.ctrlKey)) {
if (d3.event.metaKey || d3.event.ctrlKey) { // Trigger quick add dialog
d3.event.stopPropagation(); d3.event.stopPropagation();
clearSelection(); clearSelection();
point = d3.mouse(this); point = d3.mouse(this);
var clickedGroup = getGroupAt(point[0],point[1]); var clickedGroup = getGroupAt(point[0],point[1]);
if (drag_lines.length > 0) { if (drag_lines.length > 0) {
clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g) clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g)
}
showQuickAddDialog({position:point, group:clickedGroup});
} }
} showQuickAddDialog({position:point, group:clickedGroup});
if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { } else if (mouse_mode === 0 && (d3.event.touches || d3.event.button === 0) && !(d3.event.metaKey || d3.event.ctrlKey)) {
// Tigger lasso
if (!touchStartTime) { if (!touchStartTime) {
point = d3.mouse(this); point = d3.mouse(this);
lasso = eventLayer.append("rect") lasso = eventLayer.append("rect")
@ -945,6 +1020,13 @@ RED.view = (function() {
.attr("class","nr-ui-view-lasso"); .attr("class","nr-ui-view-lasso");
d3.event.preventDefault(); d3.event.preventDefault();
} }
} else if (mouse_mode === 0 && d3.event.button === 2 && (d3.event.metaKey || d3.event.ctrlKey)) {
clearSelection();
mouse_mode = RED.state.SLICING;
point = d3.mouse(this);
slicePath = eventLayer.append("path").attr("class","nr-ui-view-slice").attr("d",`M${point[0]} ${point[1]}`)
slicePathLast = point;
RED.view.redraw();
} }
} }
@ -1341,6 +1423,17 @@ RED.view = (function() {
.attr("height",h) .attr("height",h)
; ;
return; return;
} else if (mouse_mode === RED.state.SLICING) {
if (slicePath) {
var delta = Math.max(1,Math.abs(slicePathLast[0]-mouse_position[0]))*Math.max(1,Math.abs(slicePathLast[1]-mouse_position[1]))
if (delta > 20) {
var currentPath = slicePath.attr("d")
currentPath += " L"+mouse_position[0]+" "+mouse_position[1]
slicePath.attr("d",currentPath);
slicePathLast = mouse_position
}
}
return
} }
if (mouse_mode === RED.state.SELECTING_NODE) { if (mouse_mode === RED.state.SELECTING_NODE) {
@ -1348,7 +1441,7 @@ RED.view = (function() {
return; return;
} }
if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) { if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && mouse_mode != RED.state.DETACHED_DRAGGING && !mousedown_node && !mousedown_group && selectedLinks.length() === 0) {
return; return;
} }
@ -1372,16 +1465,18 @@ RED.view = (function() {
// Get all the wires we need to detach. // Get all the wires we need to detach.
var links = []; var links = [];
var existingLinks = []; var existingLinks = [];
if (selected_link && if (selectedLinks.length() > 0) {
((mousedown_port_type === PORT_TYPE_OUTPUT && selectedLinks.forEach(function(link) {
selected_link.source === mousedown_node && if (((mousedown_port_type === PORT_TYPE_OUTPUT &&
selected_link.sourcePort === mousedown_port_index link.source === mousedown_node &&
) || link.sourcePort === mousedown_port_index
(mousedown_port_type === PORT_TYPE_INPUT && ) ||
selected_link.target === mousedown_node (mousedown_port_type === PORT_TYPE_INPUT &&
)) link.target === mousedown_node
) { ))) {
existingLinks = [selected_link]; existingLinks.push(link);
}
})
} else { } else {
var filter; var filter;
if (mousedown_port_type === PORT_TYPE_OUTPUT) { if (mousedown_port_type === PORT_TYPE_OUTPUT) {
@ -1419,7 +1514,7 @@ RED.view = (function() {
} else if (mousedown_node && !quickAddLink) { } else if (mousedown_node && !quickAddLink) {
showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]); showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
} }
selected_link = null; selectedLinks.clear();
} }
mousePos = mouse_position; mousePos = mouse_position;
for (i=0;i<drag_lines.length;i++) { for (i=0;i<drag_lines.length;i++) {
@ -1451,7 +1546,7 @@ RED.view = (function() {
RED.nodes.filterLinks({ target: node.n }).length === 0; RED.nodes.filterLinks({ target: node.n }).length === 0;
} }
} }
} else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING) { } else if (mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
mousePos = mouse_position; mousePos = mouse_position;
var minX = 0; var minX = 0;
var minY = 0; var minY = 0;
@ -1515,8 +1610,16 @@ RED.view = (function() {
gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2; gridOffset[0] = node.n.x-(gridSize*Math.floor(node.n.x/gridSize))-gridSize/2;
gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2; gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize))-gridSize/2;
} else { } else {
gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2); var offsetLeft = node.n.x-(gridSize*Math.round((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize)); var offsetRight = node.n.x-(gridSize*Math.round((node.n.x+node.n.w/2)/gridSize)-node.n.w/2);
// gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2);
if (Math.abs(offsetLeft) < Math.abs(offsetRight)) {
gridOffset[0] = offsetLeft
} else {
gridOffset[0] = offsetRight
}
gridOffset[1] = node.n.y-(gridSize*Math.round(node.n.y/gridSize));
// console.log(offsetLeft, offsetRight);
} }
if (gridOffset[0] !== 0 || gridOffset[1] !== 0) { if (gridOffset[0] !== 0 || gridOffset[1] !== 0) {
for (i = 0; i<movingSet.length(); i++) { for (i = 0; i<movingSet.length(); i++) {
@ -1736,6 +1839,11 @@ RED.view = (function() {
} else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) { } else if (mouse_mode == RED.state.DEFAULT && mousedown_link == null && !d3.event.ctrlKey && !d3.event.metaKey ) {
clearSelection(); clearSelection();
updateSelection(); updateSelection();
} else if (slicePath) {
deleteSelection();
slicePath.remove();
slicePath = null;
RED.view.redraw(true);
} }
if (mouse_mode == RED.state.MOVING_ACTIVE) { if (mouse_mode == RED.state.MOVING_ACTIVE) {
if (movingSet.length() > 0) { if (movingSet.length() > 0) {
@ -1807,10 +1915,30 @@ RED.view = (function() {
// movingSet.add(mousedown_node); // movingSet.add(mousedown_node);
// } // }
// } // }
if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) { if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE || mouse_mode == RED.state.DETACHED_DRAGGING) {
// if (mousedown_node) { // if (mousedown_node) {
// delete mousedown_node.gSelected; // delete mousedown_node.gSelected;
// } // }
if (mouse_mode === RED.state.DETACHED_DRAGGING) {
var ns = [];
for (var j=0;j<movingSet.length();j++) {
var n = movingSet.get(j);
if (n.ox !== n.n.x || n.oy !== n.n.y) {
ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved});
n.n.dirty = true;
n.n.moved = true;
}
}
var detachEvent = RED.history.peek();
// The last event in the stack *should* be a multi-event from
// where the links were added/removed
var historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}
if (detachEvent.t === "multi") {
detachEvent.events.push(historyEvent)
} else {
RED.history.push(historyEvent)
}
}
for (i=0;i<movingSet.length();i++) { for (i=0;i<movingSet.length();i++) {
var node = movingSet.get(i); var node = movingSet.get(i);
delete node.ox; delete node.ox;
@ -1855,10 +1983,29 @@ RED.view = (function() {
if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) { if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
return; return;
} }
if (mouse_mode === RED.state.IMPORT_DRAGGING) { if (mouse_mode === RED.state.DETACHED_DRAGGING) {
for (var j=0;j<movingSet.length();j++) {
var n = movingSet.get(j);
n.n.x = n.ox;
n.n.y = n.oy;
}
clearSelection(); clearSelection();
RED.history.pop(); RED.history.pop();
mouse_mode = 0; mouse_mode = 0;
} else if (mouse_mode === RED.state.IMPORT_DRAGGING) {
clearSelection();
RED.history.pop();
mouse_mode = 0;
} else if (mouse_mode === RED.state.SLICING) {
if (slicePath) {
slicePath.remove();
slicePath = null;
resetMouseVars()
}
clearSelection();
} else if (lasso) {
lasso.remove();
lasso = null;
} else if (activeGroup) { } else if (activeGroup) {
exitActiveGroup() exitActiveGroup()
} else { } else {
@ -1870,6 +2017,7 @@ RED.view = (function() {
if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) { if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions.single) {
return; return;
} }
selectedLinks.clear();
if (activeGroup) { if (activeGroup) {
var ag = activeGroup; var ag = activeGroup;
@ -1941,7 +2089,6 @@ RED.view = (function() {
} }
} }
} }
selected_link = null;
if (mouse_mode !== RED.state.SELECTING_NODE) { if (mouse_mode !== RED.state.SELECTING_NODE) {
updateSelection(); updateSelection();
} }
@ -1956,7 +2103,7 @@ RED.view = (function() {
n.n.selected = false; n.n.selected = false;
} }
movingSet.clear(); movingSet.clear();
selected_link = null; selectedLinks.clear();
if (activeGroup) { if (activeGroup) {
activeGroup.active = false activeGroup.active = false
activeGroup.dirty = true; activeGroup.dirty = true;
@ -2050,12 +2197,16 @@ RED.view = (function() {
} }
} }
} }
if (activeFlowLinks.length === 0 && selected_link !== null && selected_link.link) { if (activeFlowLinks.length === 0 && selectedLinks.length() > 0) {
activeLinks.push(selected_link); selectedLinks.forEach(function(link) {
activeLinkNodes[selected_link.source.id] = selected_link.source; if (link.link) {
selected_link.source.dirty = true; activeLinks.push(link);
activeLinkNodes[selected_link.target.id] = selected_link.target; activeLinkNodes[link.source.id] = link.source;
selected_link.target.dirty = true; link.source.dirty = true;
activeLinkNodes[link.target.id] = link.target;
link.target.dirty = true;
}
})
} }
} else { } else {
selection.flows = workspaceSelection; selection.flows = workspaceSelection;
@ -2066,6 +2217,10 @@ RED.view = (function() {
return value.map(function(n) { return n.id }) return value.map(function(n) { return n.id })
} else if (key === 'link') { } else if (key === 'link') {
return value.source.id+":"+value.sourcePort+":"+value.target.id; return value.source.id+":"+value.sourcePort+":"+value.target.id;
} else if (key === 'links') {
return value.map(function(link) {
return link.source.id+":"+link.sourcePort+":"+link.target.id;
});
} }
return value; return value;
}); });
@ -2087,7 +2242,7 @@ RED.view = (function() {
} }
} }
} }
function deleteSelection() { function deleteSelection(reconnectWires) {
if (mouse_mode === RED.state.SELECTING_NODE) { if (mouse_mode === RED.state.SELECTING_NODE) {
return; return;
} }
@ -2135,7 +2290,7 @@ RED.view = (function() {
updateActiveNodes(); updateActiveNodes();
updateSelection(); updateSelection();
redraw(); redraw();
} else if (movingSet.length() > 0 || selected_link != null) { } else if (movingSet.length() > 0 || selectedLinks.length() > 0) {
var result; var result;
var node; var node;
var removedNodes = []; var removedNodes = [];
@ -2145,6 +2300,16 @@ RED.view = (function() {
var removedSubflowInputs = []; var removedSubflowInputs = [];
var removedSubflowStatus; var removedSubflowStatus;
var subflowInstances = []; var subflowInstances = [];
var historyEvents = [];
if (reconnectWires) {
var reconnectResult = RED.nodes.detachNodes(movingSet.nodes())
var addedLinks = reconnectResult.newLinks;
if (addedLinks.length > 0) {
historyEvents.push({ t:'add', links: addedLinks })
}
removedLinks = removedLinks.concat(reconnectResult.removedLinks)
}
var startDirty = RED.nodes.dirty(); var startDirty = RED.nodes.dirty();
var startChanged = false; var startChanged = false;
@ -2234,71 +2399,71 @@ RED.view = (function() {
RED.nodes.dirty(true); RED.nodes.dirty(true);
} }
} }
var historyEvent;
if (selected_link && selected_link.link) { if (selectedLinks.length() > 0) {
var sourceId = selected_link.source.id; selectedLinks.forEach(function(link) {
var targetId = selected_link.target.id; if (link.link) {
var sourceIdIndex = selected_link.target.links.indexOf(sourceId); var sourceId = link.source.id;
var targetIdIndex = selected_link.source.links.indexOf(targetId); var targetId = link.target.id;
var sourceIdIndex = link.target.links.indexOf(sourceId);
historyEvent = { var targetIdIndex = link.source.links.indexOf(targetId);
t:"multi", historyEvents.push({
events: [
{
t: "edit", t: "edit",
node: selected_link.source, node: link.source,
changed: selected_link.source.changed, changed: link.source.changed,
changes: { changes: {
links: $.extend(true,{},{v:selected_link.source.links}).v links: $.extend(true,{},{v:link.source.links}).v
} }
}, })
{ historyEvents.push({
t: "edit", t: "edit",
node: selected_link.target, node: link.target,
changed: selected_link.target.changed, changed: link.target.changed,
changes: { changes: {
links: $.extend(true,{},{v:selected_link.target.links}).v links: $.extend(true,{},{v:link.target.links}).v
} }
} })
link.source.changed = true;
link.target.changed = true;
link.target.links.splice(sourceIdIndex,1);
link.source.links.splice(targetIdIndex,1);
link.source.dirty = true;
link.target.dirty = true;
], } else {
dirty:RED.nodes.dirty() RED.nodes.removeLink(link);
} removedLinks.push(link);
RED.nodes.dirty(true); }
selected_link.source.changed = true; })
selected_link.target.changed = true; }
selected_link.target.links.splice(sourceIdIndex,1); RED.nodes.dirty(true);
selected_link.source.links.splice(targetIdIndex,1); var historyEvent = {
selected_link.source.dirty = true; t:"delete",
selected_link.target.dirty = true; nodes:removedNodes,
links:removedLinks,
} else { groups: removedGroups,
if (selected_link) { subflowOutputs:removedSubflowOutputs,
RED.nodes.removeLink(selected_link); subflowInputs:removedSubflowInputs,
removedLinks.push(selected_link); subflow: {
} id: activeSubflow?activeSubflow.id:undefined,
RED.nodes.dirty(true); instances: subflowInstances
historyEvent = { },
t:"delete", dirty:startDirty
nodes:removedNodes, };
links:removedLinks, if (removedSubflowStatus) {
groups: removedGroups, historyEvent.subflow.status = removedSubflowStatus;
subflowOutputs:removedSubflowOutputs, }
subflowInputs:removedSubflowInputs, if (historyEvents.length > 0) {
subflow: { historyEvents.unshift(historyEvent);
id: activeSubflow?activeSubflow.id:undefined, RED.history.push({
instances: subflowInstances t:"multi",
}, events: historyEvents
dirty:startDirty })
}; } else {
if (removedSubflowStatus) { RED.history.push(historyEvent);
historyEvent.subflow.status = removedSubflowStatus;
}
} }
RED.history.push(historyEvent);
selected_link = null; selectedLinks.clear();
updateActiveNodes(); updateActiveNodes();
updateSelection(); updateSelection();
redraw(); redraw();
@ -2375,6 +2540,28 @@ RED.view = (function() {
} }
} }
function detachSelectedNodes() {
var selection = RED.view.selection();
if (selection.nodes) {
const {newLinks, removedLinks} = RED.nodes.detachNodes(selection.nodes);
if (removedLinks.length || newLinks.length) {
RED.history.push({
t: "multi",
events: [
{ t:'delete', links: removedLinks },
{ t:'add', links: newLinks }
],
dirty: RED.nodes.dirty()
})
RED.nodes.dirty(true)
}
prepareDrag([selection.nodes[0].x,selection.nodes[0].y]);
mouse_mode = RED.state.DETACHED_DRAGGING;
RED.view.redraw(true);
}
}
function calculateTextWidth(str, className) { function calculateTextWidth(str, className) {
var result = convertLineBreakCharacter(str); var result = convertLineBreakCharacter(str);
var width = 0; var width = 0;
@ -2461,7 +2648,7 @@ RED.view = (function() {
activeHoverGroup.hovered = false; activeHoverGroup.hovered = false;
activeHoverGroup = null; activeHoverGroup = null;
} }
d3.select(".red-ui-flow-link-splice").classed("red-ui-flow-link-splice",false); d3.selectAll(".red-ui-flow-link-splice").classed("red-ui-flow-link-splice",false);
if (spliceTimer) { if (spliceTimer) {
clearTimeout(spliceTimer); clearTimeout(spliceTimer);
spliceTimer = null; spliceTimer = null;
@ -2709,10 +2896,13 @@ RED.view = (function() {
} else { } else {
resetMouseVars(); resetMouseVars();
} }
selected_link = select_link;
mousedown_link = select_link; mousedown_link = select_link;
if (select_link) { if (select_link) {
selectedLinks.clear();
selectedLinks.add(select_link);
updateSelection(); updateSelection();
} else {
selectedLinks.clear();
} }
} }
redraw(); redraw();
@ -2721,7 +2911,10 @@ RED.view = (function() {
resetMouseVars(); resetMouseVars();
hideDragLines(); hideDragLines();
selected_link = select_link; if (select_link) {
selectedLinks.clear();
selectedLinks.add(select_link);
}
mousedown_link = select_link; mousedown_link = select_link;
if (select_link) { if (select_link) {
updateSelection(); updateSelection();
@ -2894,10 +3087,13 @@ RED.view = (function() {
msn.dx = msn.n.x-mouse[0]; msn.dx = msn.n.x-mouse[0];
msn.dy = msn.n.y-mouse[1]; msn.dy = msn.n.y-mouse[1];
} }
try {
mouse_offset = d3.mouse(document.body); mouse_offset = d3.mouse(document.body);
if (isNaN(mouse_offset[0])) { if (isNaN(mouse_offset[0])) {
mouse_offset = d3.touches(document.body)[0]; mouse_offset = d3.touches(document.body)[0];
}
} catch(err) {
mouse_offset = [0,0]
} }
} }
@ -2978,7 +3174,7 @@ RED.view = (function() {
//var touch0 = d3.event; //var touch0 = d3.event;
//var pos = [touch0.pageX,touch0.pageY]; //var pos = [touch0.pageX,touch0.pageY];
//RED.touch.radialMenu.show(d3.select(this),pos); //RED.touch.radialMenu.show(d3.select(this),pos);
if (mouse_mode == RED.state.IMPORT_DRAGGING) { if (mouse_mode == RED.state.IMPORT_DRAGGING || mouse_mode == RED.state.DETACHED_DRAGGING) {
var historyEvent = RED.history.peek(); var historyEvent = RED.history.peek();
if (activeSpliceLink) { if (activeSpliceLink) {
// TODO: DRY - droppable/nodeMouseDown/canvasMouseUp // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp
@ -3016,6 +3212,18 @@ RED.view = (function() {
activeHoverGroup = null; activeHoverGroup = null;
} }
if (mouse_mode == RED.state.DETACHED_DRAGGING) {
var ns = [];
for (var j=0;j<movingSet.length();j++) {
var n = movingSet.get(j);
if (n.ox !== n.n.x || n.oy !== n.n.y) {
ns.push({n:n.n,ox:n.ox,oy:n.oy,moved:n.n.moved});
n.n.dirty = true;
n.n.moved = true;
}
}
RED.history.replace({t:"multi",events:[historyEvent,{t:"move",nodes:ns}],dirty: historyEvent.dirty})
}
updateSelection(); updateSelection();
RED.nodes.dirty(true); RED.nodes.dirty(true);
@ -3184,7 +3392,9 @@ RED.view = (function() {
// } // }
// } else // } else
if (d3.event.shiftKey) { if (d3.event.shiftKey) {
clearSelection(); if (!(d3.event.ctrlKey||d3.event.metaKey)) {
clearSelection();
}
var clickPosition = (d3.event.offsetX/scaleFactor - mousedown_node.x) var clickPosition = (d3.event.offsetX/scaleFactor - mousedown_node.x)
var edgeDelta = (mousedown_node.w/2) - Math.abs(clickPosition); var edgeDelta = (mousedown_node.w/2) - Math.abs(clickPosition);
var cnodes; var cnodes;
@ -3212,7 +3422,7 @@ RED.view = (function() {
mousedown_node.selected = true; mousedown_node.selected = true;
movingSet.add(mousedown_node); movingSet.add(mousedown_node);
} }
selected_link = null; // selectedLinks.clear();
if (d3.event.button != 2) { if (d3.event.button != 2) {
mouse_mode = RED.state.MOVING; mouse_mode = RED.state.MOVING;
var mouse = d3.touches(this)[0]||d3.mouse(this); var mouse = d3.touches(this)[0]||d3.mouse(this);
@ -3338,19 +3548,35 @@ RED.view = (function() {
d3.event.stopPropagation(); d3.event.stopPropagation();
return; return;
} }
mousedown_link = d; if (d3.event.button === 2) {
clearSelection(); return
selected_link = mousedown_link; }
updateSelection(); mousedown_link = d;
redraw();
focusView(); if (!(d3.event.metaKey || d3.event.ctrlKey)) {
d3.event.stopPropagation(); clearSelection();
if (d3.event.metaKey || d3.event.ctrlKey) { }
d3.select(this).classed("red-ui-flow-link-splice",true); if (d3.event.metaKey || d3.event.ctrlKey) {
var point = d3.mouse(this); if (!selectedLinks.has(mousedown_link)) {
var clickedGroup = getGroupAt(point[0],point[1]); selectedLinks.add(mousedown_link);
showQuickAddDialog({position:point, splice:selected_link, group:clickedGroup}); } else {
} if (selectedLinks.length() !== 1) {
selectedLinks.remove(mousedown_link);
}
}
} else {
selectedLinks.add(mousedown_link);
}
updateSelection();
redraw();
focusView();
d3.event.stopPropagation();
if (!mousedown_link.link && movingSet.length() === 0 && (d3.event.touches || d3.event.button === 0) && selectedLinks.length() === 1 && selectedLinks.has(mousedown_link) && (d3.event.metaKey || d3.event.ctrlKey)) {
d3.select(this).classed("red-ui-flow-link-splice",true);
var point = d3.mouse(this);
var clickedGroup = getGroupAt(point[0],point[1]);
showQuickAddDialog({position:point, splice:mousedown_link, group:clickedGroup});
}
} }
function linkTouchStart(d) { function linkTouchStart(d) {
if (mouse_mode === RED.state.SELECTING_NODE) { if (mouse_mode === RED.state.SELECTING_NODE) {
@ -3359,7 +3585,8 @@ RED.view = (function() {
} }
mousedown_link = d; mousedown_link = d;
clearSelection(); clearSelection();
selected_link = mousedown_link; selectedLinks.clear();
selectedLinks.add(mousedown_link);
updateSelection(); updateSelection();
redraw(); redraw();
focusView(); focusView();
@ -3595,7 +3822,7 @@ RED.view = (function() {
function showTouchMenu(obj,pos) { function showTouchMenu(obj,pos) {
var mdn = mousedown_node; var mdn = mousedown_node;
var options = []; var options = [];
options.push({name:"delete",disabled:(movingSet.length()===0 && selected_link === null),onselect:function() {deleteSelection();}}); options.push({name:"delete",disabled:(movingSet.length()===0 && selectedLinks.length() === 0),onselect:function() {deleteSelection();}});
options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}});
options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}});
options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}}); options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard, {generateIds: true, touchImport: true});}});
@ -4471,6 +4698,13 @@ RED.view = (function() {
d3.select(pathBack) d3.select(pathBack)
.on("mousedown",linkMouseDown) .on("mousedown",linkMouseDown)
.on("touchstart",linkTouchStart) .on("touchstart",linkTouchStart)
.on("mousemove", function(d) {
if (mouse_mode === RED.state.SLICING) {
selectedLinks.add(d)
l.classed("red-ui-flow-link-splice",true)
redraw()
}
})
var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path"); var pathOutline = document.createElementNS("http://www.w3.org/2000/svg","path");
pathOutline.__data__ = d; pathOutline.__data__ = d;
@ -4491,7 +4725,7 @@ RED.view = (function() {
link.exit().remove(); link.exit().remove();
link.each(function(d) { link.each(function(d) {
var link = d3.select(this); var link = d3.select(this);
if (d.added || d===selected_link || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) { if (d.added || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) {
var numOutputs = d.source.outputs || 1; var numOutputs = d.source.outputs || 1;
var sourcePort = d.sourcePort || 0; var sourcePort = d.sourcePort || 0;
var y = -((numOutputs-1)/2)*13 +13*sourcePort; var y = -((numOutputs-1)/2)*13 +13*sourcePort;
@ -4514,7 +4748,8 @@ RED.view = (function() {
this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d)); this.__pathLine__.classList.toggle("red-ui-flow-node-disabled",!!(d.source.d || d.target.d));
this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow); this.__pathLine__.classList.toggle("red-ui-flow-subflow-link", !d.link && activeSubflow);
} }
this.classList.toggle("red-ui-flow-link-selected", !!(d===selected_link||d.selected));
this.classList.toggle("red-ui-flow-link-selected", !!d.selected);
var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown"); var connectedToUnknown = !!(d.target.type == "unknown" || d.source.type == "unknown");
this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown")) this.classList.toggle("red-ui-flow-link-unknown",!!(d.target.type == "unknown" || d.source.type == "unknown"))
@ -5222,8 +5457,9 @@ RED.view = (function() {
if (allNodes.size > 0) { if (allNodes.size > 0) {
selection.nodes = Array.from(allNodes); selection.nodes = Array.from(allNodes);
} }
if (selected_link != null) { if (selectedLinks.length() > 0) {
selection.link = selected_link; selection.links = selectedLinks.toArray();
selection.link = selection.links[0];
} }
return selection; return selection;
} }

View File

@ -66,7 +66,7 @@ RED.workspaces = (function() {
var tabId = RED.nodes.id(); var tabId = RED.nodes.id();
do { do {
workspaceIndex += 1; workspaceIndex += 1;
} while ($("#red-ui-workspace-tabs a[title='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0); } while ($("#red-ui-workspace-tabs li[flowname='"+RED._('workspace.defaultName',{number:workspaceIndex})+"']").size() !== 0);
ws = { ws = {
type: "tab", type: "tab",
@ -79,12 +79,15 @@ RED.workspaces = (function() {
}; };
RED.nodes.addWorkspace(ws,targetIndex); RED.nodes.addWorkspace(ws,targetIndex);
workspace_tabs.addTab(ws,targetIndex); workspace_tabs.addTab(ws,targetIndex);
workspace_tabs.activateTab(tabId); workspace_tabs.activateTab(tabId);
if (!skipHistoryEntry) { if (!skipHistoryEntry) {
RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()}); RED.history.push({t:'add',workspaces:[ws],dirty:RED.nodes.dirty()});
RED.nodes.dirty(true); RED.nodes.dirty(true);
} }
} }
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
RED.view.focus(); RED.view.focus();
return ws; return ws;
} }
@ -208,10 +211,20 @@ RED.workspaces = (function() {
}, },
onhide: function(tab) { onhide: function(tab) {
hideStack.push(tab.id); hideStack.push(tab.id);
var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
hiddenTabs[tab.id] = true;
RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
RED.events.emit("workspace:hide",{workspace: tab.id}) RED.events.emit("workspace:hide",{workspace: tab.id})
}, },
onshow: function(tab) { onshow: function(tab) {
removeFromHideStack(tab.id); removeFromHideStack(tab.id);
var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
delete hiddenTabs[tab.id];
RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
RED.events.emit("workspace:show",{workspace: tab.id}) RED.events.emit("workspace:show",{workspace: tab.id})
}, },
minimumActiveTabWidth: 150, minimumActiveTabWidth: 150,
@ -542,9 +555,6 @@ RED.workspaces = (function() {
} }
if (workspace_tabs.contains(id)) { if (workspace_tabs.contains(id)) {
workspace_tabs.hideTab(id); workspace_tabs.hideTab(id);
var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
hiddenTabs[id] = true;
RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
} }
}, },
isHidden: function(id) { isHidden: function(id) {
@ -572,14 +582,11 @@ RED.workspaces = (function() {
} }
workspace_tabs.activateTab(id); workspace_tabs.activateTab(id);
} }
var hiddenTabs = JSON.parse(RED.settings.getLocal("hiddenTabs")||"{}");
delete hiddenTabs[id];
RED.settings.setLocal("hiddenTabs",JSON.stringify(hiddenTabs));
}, },
refresh: function() { refresh: function() {
RED.nodes.eachWorkspace(function(ws) { RED.nodes.eachWorkspace(function(ws) {
workspace_tabs.renameTab(ws.id,ws.label); workspace_tabs.renameTab(ws.id,ws.label);
$("#red-ui-tab-"+(ws.id.replace(".","-"))).attr("flowname",ws.label)
}) })
RED.nodes.eachSubflow(function(sf) { RED.nodes.eachSubflow(function(sf) {
if (workspace_tabs.contains(sf.id)) { if (workspace_tabs.contains(sf.id)) {

View File

@ -356,10 +356,6 @@ button.red-ui-button-small
background: $secondary-background; background: $secondary-background;
} }
#red-ui-clipboard-hidden {
position: absolute;
top: -3000px;
}
.form-row .red-ui-editor-node-label-form-row { .form-row .red-ui-editor-node-label-form-row {
margin: 5px 0 0 50px; margin: 5px 0 0 50px;
label { label {

View File

@ -21,6 +21,13 @@
stroke-dasharray: 10 5; stroke-dasharray: 10 5;
} }
.nr-ui-view-slice {
stroke-width: 1px;
stroke: $view-lasso-stroke;
fill: none;
stroke-dasharray: 10 5;
}
.node_label_italic, // deprecated: use red-ui-flow-node-label-italic .node_label_italic, // deprecated: use red-ui-flow-node-label-italic
.red-ui-flow-node-label-italic { .red-ui-flow-node-label-italic {
font-style: italic; font-style: italic;

View File

@ -204,6 +204,28 @@
font-style: italic; font-style: italic;
color: $form-placeholder-color; color: $form-placeholder-color;
} }
.red-ui-search-history {
button {
display: none;
position: absolute;
top: 8px;
right: 7px;
}
&:hover button {
display: inline;
}
}
.red-ui-search-historyHeader {
button {
position: absolute;
top: 10px;
right: 7px;
}
}
.red-ui-search-history-result {
}
.red-ui-search-result-action { .red-ui-search-result-action {
color: $primary-text-color; color: $primary-text-color;

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -1,12 +1,12 @@
export default { export default {
version: "2.1.0", version: "2.2.0",
steps: [ steps: [
{ {
titleIcon: "fa fa-map-o", titleIcon: "fa fa-map-o",
title: { title: {
"en-US": "Welcome to Node-RED 2.1!", "en-US": "Welcome to Node-RED 2.2!",
"ja": "Node-RED 2.1へようこそ!" "ja": "Node-RED 2.2へようこそ!"
}, },
description: { description: {
"en-US": "Let's take a moment to discover the new features in this release.", "en-US": "Let's take a moment to discover the new features in this release.",
"ja": "本リリースの新機能を見つけてみましょう。" "ja": "本リリースの新機能を見つけてみましょう。"
@ -14,215 +14,84 @@ export default {
}, },
{ {
title: { title: {
"en-US": "A new Tour Guide", "en-US": "Search history",
"ja": "新しいツアーガイド"
}, },
description: { description: {
"en-US": "<p>First, as you've already found, we now have this tour of new features. We'll only show the tour the first time you open the editor for each new version of Node-RED.</p>" + "en-US": "<p>The Search dialog now keeps a history of your searches, making it easier to go back to a previous search.</p>"
"<p>You can choose not to see this tour in the future by disabling it under the View tab of User Settings.</p>",
"ja": "<p>最初に、既に見つけている様に、新機能の本ツアーがあります。本ツアーは、新バージョンのNode-REDフローエディタを初めて開いた時のみ表示されます。</p>" +
"<p>ユーザ設定の表示タブの中で、この機能を無効化することで、本ツアーを表示しないようにすることもできます。</p>"
}
},
{
title: {
"en-US": "New Edit menu",
"ja": "新しい編集メニュー"
}, },
prepare() { element: "#red-ui-search .red-ui-searchBox-form",
$("#red-ui-header-button-sidemenu").trigger("click"); prepare(done) {
$("#menu-item-edit-menu").parent().addClass("open"); RED.search.show();
setTimeout(done,400);
}, },
complete() { complete() {
$("#menu-item-edit-menu").parent().removeClass("open"); RED.search.hide();
},
element: "#menu-item-edit-menu-submenu",
interactive: false,
direction: "left",
description: {
"en-US": "<p>The main menu has been updated with a new 'Edit' section. This includes all of the familar options, like cut/paste and undo/redo.</p>" +
"<p>The menu now displays keyboard shortcuts for the options.</p>",
"ja": "<p>メインメニューに「編集」セクションが追加されました。本セクションには、切り取り/貼り付けや、変更操作を戻す/やり直しの様な使い慣れたオプションが含まれています。</p>" +
"<p>本メニューには、オプションのためのキーボードショートカットも表示されるようになりました。</p>"
}
},
{
title: {
"en-US": "Arranging nodes",
"ja": "ノードの配置"
},
prepare() {
$("#red-ui-header-button-sidemenu").trigger("click");
$("#menu-item-arrange-menu").parent().addClass("open");
},
complete() {
$("#menu-item-arrange-menu").parent().removeClass("open");
},
element: "#menu-item-arrange-menu-submenu",
interactive: false,
direction: "left",
description: {
"en-US": "<p>The new 'Arrange' section of the menu provides new options to help arrange your nodes. You can align them to a common edge, spread them out evenly or change their order.</p>",
"ja": "<p>メニューの新しい「配置」セクションには、ノードの配置を助ける新しいオプションが提供されています。ノードの端を揃えたり、均等に配置したり、表示順序を変更したりできます。</p>"
}
},
{
title: {
"en-US": "Hiding tabs",
"ja": "タブの非表示"
},
element: "#red-ui-workspace-tabs > li.active",
description: {
"en-US": '<p>Tabs can now be hidden by clicking their <i class="fa fa-times"></i> icon.</p><p>The Info Sidebar will still list all of your tabs, and tell you which ones are currently hidden.',
"ja": '<p><i class="fa fa-times"></i> アイコンをクリックすることで、タブを非表示にできます。</p><p>情報サイドバーには、全てのタブが一覧表示されており、現在非表示になっているタブを確認できます。'
},
interactive: false,
prepare() {
$("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display","block");
},
complete() {
$("#red-ui-workspace-tabs > li.active .red-ui-tab-close").css("display","");
}
},
{
title: {
"en-US": "Tab menu",
"ja": "タブメニュー"
},
element: "#red-ui-workspace-tabs-menu",
description: {
"en-US": "<p>The new tab menu also provides lots of new options for your tabs.</p>",
"ja": "<p>新しいタブメニューには、タブに関する沢山の新しいオプションが提供されています。</p>"
},
interactive: false,
direction: "left",
prepare() {
$("#red-ui-workspace > .red-ui-tabs > .red-ui-tabs-menu a").trigger("click");
},
complete() {
$(document).trigger("click");
}
},
{
title: {
"en-US": "Flow and Group level environment variables",
"ja": "フローとグループの環境変数"
},
element: "#red-ui-workspace-tabs > li.active",
interactive: false,
description: {
"en-US": "<p>Flows and Groups can now have their own environment variables that can be referenced by nodes inside them.</p>",
"ja": "<p>フローとグループには、内部のノードから参照できる環境変数を設定できるようになりました。</p>"
}
},
{
prepare(done) {
RED.editor.editFlow(RED.nodes.workspace(RED.workspaces.active()),"editor-tab-envProperties");
setTimeout(done,700);
},
element: "#red-ui-tab-editor-tab-envProperties-link-button",
description: {
"en-US": "<p>Their edit dialogs have a new Environment Variables section.</p>",
"ja": "<p>編集ダイアログに環境変数セクションが追加されました。</p>"
}
},
{
element: ".node-input-env-container-row",
direction: "left",
description: {
"en-US": '<p>The environment variables are listed in this table and new ones can be added by clicking the <i class="fa fa-plus"></i> button.</p>',
"ja": '<p>この表に環境変数が一覧表示されており、<i class="fa fa-plus"></i>ボタンをクリックすることで新しい変数を追加できます。</p>'
},
complete(done) {
$("#node-dialog-cancel").trigger("click");
setTimeout(done,500);
}
},
{
title: {
"en-US": "Link Call node added",
"ja": "Link Callードを追加"
},
prepare(done) {
this.paletteWasClosed = $("#red-ui-main-container").hasClass("red-ui-palette-closed");
RED.actions.invoke("core:toggle-palette",true)
$('[data-palette-type="link call"]')[0].scrollIntoView({block:"center"})
setTimeout(done,100);
},
element: '[data-palette-type="link call"]',
direction: "right",
description: {
"en-US": "<p>The <code>Link Call</code> node lets you call another flow that begins with a <code>Link In</code> node and get the result back when the message reaches a <code>Link Out</code> node.</p>",
"ja": "<p><code>Link Call</code>ノードを用いることで、<code>Link In</code>ノードから始まるフローを呼び出し、<code>Link Out</code>ノードに到達した時に、結果を取得できます。</p>"
}
},
{
title: {
"en-US": "MQTT nodes support dynamic connections",
"ja": "MQTTードが動的接続をサポート"
},
prepare(done) {
$('[data-palette-type="mqtt out"]')[0].scrollIntoView({block:"center"})
setTimeout(done,100);
},
element: '[data-palette-type="mqtt out"]',
direction: "right",
description: {
"en-US": '<p>The <code>MQTT</code> nodes now support creating their connections and subscriptions dynamically.</p>',
"ja": '<p><code>MQTT</code>ノードは、動的な接続や購読ができるようになりました。</p>'
}, },
}, },
{ {
title: { title: {
"en-US": "File nodes renamed", "en-US": "New wiring actions",
"ja": "ファイルノードの名前変更"
}, },
prepare(done) { // image: "images/",
$('[data-palette-type="file"]')[0].scrollIntoView({block:"center"});
setTimeout(done,100);
},
complete() {
if (this.paletteWasClosed) {
RED.actions.invoke("core:toggle-palette",false)
}
},
element: '[data-palette-type="file"]',
direction: "right",
description: { description: {
"en-US": "<p>The file nodes have been renamed to make it clearer which node does what.</p>", "en-US": `<p>A pair of new actions have been added to help with wiring nodes together:</p>
"ja": "<p>fileードの名前が変更され、どのードが何を行うかが明確になりました。</p>" <ul>
<li><b><code>Wire Series Of Nodes</code></b> - adds a wire (if necessary) between each pair of nodes in the order they were selected.</li>
<li><b><code>Wire Node To Multiple</code></b> - wires the first node selected to all of the other selected nodes.</li>
</ul>
<p>Actions can be accessed from the Action List in the main menu.</p>`
},
},
{
title: {
"en-US": "Deleting nodes and reconnecting wires",
},
image: "images/delete-repair.gif",
description: {
"en-US": `<p>It is now possible to delete a selection of nodes and automatically repair the wiring behind them.</p>
<p>This is really useful if you want to remove a node from the middle of the flow.</p>
<p>Hold the Ctrl (or Cmd) key when you press Delete and the nodes will be gone and the wires repaired.</p>
`
} }
}, },
{ {
title: { title: {
"en-US": "Deep copy option on Change node", "en-US": "Detaching nodes from a flow",
"ja": "Changeードのディープコピーオプション"
},
prepare(done) {
var def = RED.nodes.getType('change');
RED.editor.edit({id:"test",type:"change",rules:[{t:"set",p:"payload",pt:"msg", tot:"msg",to:"anotherProperty"}],_def:def, _:def._});
setTimeout(done,700);
},
complete(done) {
$("#node-dialog-cancel").trigger("click");
setTimeout(done,500);
},
element: function() {
return $(".node-input-rule-property-deepCopy").next();
}, },
image: "images/detach-repair.gif",
description: { description: {
"en-US": "<p>The Set rule has a new option to create a deep copy of the value. This ensures a complete copy is made, rather than using a reference.</p>", "en-US": `<p>If you want to remove a node from a flow without deleting it,
"ja": "<p>値を代入に、値のディープコピーを作成するオプションが追加されました。これによって参照ではなく、完全なコピーが作成されます。</p>" you can use the <b><code>Detach Selected Nodes</code></b> action.</p>
<p>The nodes will be removed from their flow, the wiring repaired behind them, and then attached to the mouse
so you can drop them wherever you want in the workspace.</p>
<p>There isn't a default keyboard shortcut assigned for this new action, but
you can add your own via the Keyboard pane of the main Settings dialog.</p>`
} }
}, },
{ {
title: { title: {
"en-US": "And that's not all...", "en-US": "More wiring tricks",
"ja": "これが全てではありません..."
}, },
image: "images/slice.gif",
description: { description: {
"en-US": "<p>There are many more smaller changes, including:</p><ul><li>Auto-complete suggestions in the <code>msg</code> TypedInput.</li><li>Support for <code>msg.resetTimeout</code> in the <code>Join</code> node.</li><li>Pushing messages to the front of the queue in the <code>Delay</code> node's rate limiting mode.</li><li>An optional second output on the <code>Delay</code> node for rate limited messages.</li></ul>", "en-US": `<p>A couple more wiring tricks to share.</p>
"ja": "<p>以下の様な小さな変更が沢山あります:</p><ul><li><code>msg</code> TypedInputの自動補完提案</li><li><code>Join</code>ノードで<code>msg.resetTimeout</code>のサポート</li><li><code>Delay</code>ノードの流量制御モードにおいて先頭メッセージをキューに追加</li><li><code>Delay</code>ードで流量制限されたメッセージ向けの任意の2つ目の出力</li></ul>" <p>You can now select multiple wires by holding the Ctrl (or Cmd) key
when clicking on a wire. This makes it easier to delete multiple wires in one go.</p>
<p>If you hold the Ctrl (or Cmd) key, then click and drag with the right-hand mouse button,
you can slice through wires to remove them.</p>`
}
},
{
title: {
"en-US": "Node Updates",
},
// image: "images/",
description: {
"en-US": `<ul>
<li>The JSON node will now handle parsing Buffer payloads</li>
<li>The TCP Client nodes support TLS connections</li>
<li>The WebSocket node allows you to specify a sub-protocol when connecting</li>
</ul>`
} }
} }
] ]

View File

@ -690,9 +690,9 @@
this.topic = ""; this.topic = "";
var result = getProps(items, true); var result = getProps(items, true);
this.props = result.props; this.props = result.props;
if(result.payloadType) { this.payloadType = result.payloadType; }; if(result.hasOwnProperty('payloadType')) { this.payloadType = result.payloadType; };
if(result.payload) { this.payload = result.payload; }; if(result.hasOwnProperty('payload')) { this.payload = result.payload; };
if(result.topic) { this.topic = result.topic; }; if(result.hasOwnProperty('topic')) { this.topic = result.topic; };
}, },
button: { button: {
enabled: function() { enabled: function() {

View File

@ -75,16 +75,12 @@ module.exports = function(RED) {
node.repeaterSetup = function () { node.repeaterSetup = function () {
if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) { if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) {
this.repeat = this.repeat * 1000; this.repeat = this.repeat * 1000;
if (RED.settings.verbose) { this.debug(RED._("inject.repeat", this));
this.log(RED._("inject.repeat", this));
}
this.interval_id = setInterval(function() { this.interval_id = setInterval(function() {
node.emit("input", {}); node.emit("input", {});
}, this.repeat); }, this.repeat);
} else if (this.crontab) { } else if (this.crontab) {
if (RED.settings.verbose) { this.debug(RED._("inject.crontab", this));
this.log(RED._("inject.crontab", this));
}
this.cronjob = scheduleTask(this.crontab,() => { node.emit("input", {})}); this.cronjob = scheduleTask(this.crontab,() => { node.emit("input", {})});
} }
}; };
@ -148,10 +144,8 @@ module.exports = function(RED) {
} }
if (this.interval_id != null) { if (this.interval_id != null) {
clearInterval(this.interval_id); clearInterval(this.interval_id);
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
} else if (this.cronjob != null) { } else if (this.cronjob != null) {
this.cronjob.stop(); this.cronjob.stop();
if (RED.settings.verbose) { this.log(RED._("inject.stopped")); }
delete this.cronjob; delete this.cronjob;
} }
}; };

View File

@ -91,21 +91,21 @@
<div id="func-tab-init" style="display:none"> <div id="func-tab-init" style="display:none">
<div class="form-row node-text-editor-row" style="position:relative"> <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="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); z-Index: 5;"><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> </div>
<div id="func-tab-body" style="display:none"> <div id="func-tab-body" style="display:none">
<div class="form-row node-text-editor-row" style="position:relative"> <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="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); z-Index: 5;"><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> </div>
<div id="func-tab-finalize" style="display:none"> <div id="func-tab-finalize" style="display:none">
<div class="form-row node-text-editor-row" style="position:relative"> <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="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); z-Index: 5;"><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>
</div> </div>
@ -512,6 +512,7 @@
return function(e) { return function(e) {
e.preventDefault(); e.preventDefault();
var value = editor.getValue(); var value = editor.getValue();
var extraLibs = that.libs || [];
RED.editor.editJavaScript({ RED.editor.editJavaScript({
value: value, value: value,
width: "Infinity", width: "Infinity",
@ -523,7 +524,8 @@
setTimeout(function() { setTimeout(function() {
editor.focus(); editor.focus();
},300); },300);
} },
extraLibs: extraLibs
}) })
} }
} }

View File

@ -234,8 +234,7 @@ module.exports = function(RED) {
}, },
env: { env: {
get: function(envVar) { get: function(envVar) {
var flow = node._flow; return RED.util.getSetting(node, envVar);
return flow.getSetting(envVar);
} }
}, },
setTimeout: function () { setTimeout: function () {

View File

@ -86,7 +86,7 @@ module.exports = function(RED) {
}); });
var cmd = arg.shift(); var cmd = arg.shift();
/* istanbul ignore else */ /* istanbul ignore else */
if (RED.settings.verbose) { node.log(cmd+" ["+arg+"]"); } node.debug(cmd+" ["+arg+"]");
child = spawn(cmd,arg,node.spawnOpt); child = spawn(cmd,arg,node.spawnOpt);
node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid}); node.status({fill:"blue",shape:"dot",text:"pid:"+child.pid});
var unknownCommand = (child.pid === undefined); var unknownCommand = (child.pid === undefined);
@ -136,7 +136,7 @@ module.exports = function(RED) {
} }
else { else {
/* istanbul ignore else */ /* istanbul ignore else */
if (RED.settings.verbose) { node.log(arg); } node.debug(arg);
child = exec(arg, node.execOpt, function (error, stdout, stderr) { child = exec(arg, node.execOpt, function (error, stdout, stderr) {
var msg2, msg3; var msg2, msg3;
delete msg.payload; delete msg.payload;
@ -155,7 +155,7 @@ module.exports = function(RED) {
if (error.signal) { msg3.payload.signal = error.signal; } if (error.signal) { msg3.payload.signal = error.signal; }
if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); } if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); } else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
if (RED.settings.verbose) { node.log('error:' + error); } node.debug('error:' + error);
} }
else if (node.oldrc === "false") { else if (node.oldrc === "false") {
msg3 = RED.util.cloneMessage(msg); msg3 = RED.util.cloneMessage(msg);

View File

@ -58,7 +58,7 @@ module.exports = function(RED) {
else { else {
var n = parseFloat(value); var n = parseFloat(value);
if (!isNaN(n)) { if (!isNaN(n)) {
if ((typeof node.previous[t] === 'undefined') && (this.func === "narrowband")) { if ((typeof node.previous[t] === 'undefined') && (this.func === "narrowband" || this.func === "narrowbandEq")) {
if (node.start === '') { node.previous[t] = n; } if (node.start === '') { node.previous[t] = n; }
else { node.previous[t] = node.start; } else { node.previous[t] = node.start; }
} }

View File

@ -302,6 +302,8 @@ in your Node-RED user directory (${RED.settings.userDir}).
// var cred = "" // var cred = ""
if (this.credentials.user || this.credentials.password) { if (this.credentials.user || this.credentials.password) {
// cred = `${this.credentials.user}:${this.credentials.password}`; // cred = `${this.credentials.user}:${this.credentials.password}`;
if (this.credentials.user === undefined) { this.credentials.user = ""}
if (this.credentials.password === undefined) { this.credentials.password = ""}
opts.headers.Authorization = "Basic " + Buffer.from(`${this.credentials.user}:${this.credentials.password}`).toString("base64"); opts.headers.Authorization = "Basic " + Buffer.from(`${this.credentials.user}:${this.credentials.password}`).toString("base64");
} }
// build own basic auth header // build own basic auth header

View File

@ -177,7 +177,8 @@
path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)}, path: {value:"",required:true,validate:RED.validators.regex(/^((?!\/debug\/ws).)*$/)},
tls: {type:"tls-config",required: false}, tls: {type:"tls-config",required: false},
wholemsg: {value:"false"}, wholemsg: {value:"false"},
hb: {value: "", validate: RED.validators.number(/*blank allowed*/true) } hb: {value: "", validate: RED.validators.number(/*blank allowed*/true) },
subprotocol: {value:"",required: false}
}, },
inputs:0, inputs:0,
outputs:0, outputs:0,
@ -265,7 +266,10 @@
<label for="node-config-input-tls" data-i18n="httpin.tls-config"></label> <label for="node-config-input-tls" data-i18n="httpin.tls-config"></label>
<input type="text" id="node-config-input-tls"> <input type="text" id="node-config-input-tls">
</div> </div>
<div class="form-row">
<label for="node-config-input-subprotocol"><i class="fa fa-tag"></i> <span data-i18n="websocket.label.subprotocol"></span></label>
<input type="text" id="node-config-input-subprotocol">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-config-input-wholemsg" data-i18n="websocket.sendrec"></label> <label for="node-config-input-wholemsg" data-i18n="websocket.sendrec"></label>
<select type="text" id="node-config-input-wholemsg" style="width: 70%;"> <select type="text" id="node-config-input-wholemsg" style="width: 70%;">

View File

@ -46,6 +46,12 @@ module.exports = function(RED) {
// Store local copies of the node configuration (as defined in the .html) // Store local copies of the node configuration (as defined in the .html)
node.path = n.path; node.path = n.path;
if (typeof n.subprotocol === "string") {
// Split the string on comma and trim each result
node.subprotocol = n.subprotocol.split(",").map(v => v.trim())
} else {
node.subprotocol = [];
}
node.wholemsg = (n.wholemsg === "true"); node.wholemsg = (n.wholemsg === "true");
node._inputNodes = []; // collection of nodes that want to receive events node._inputNodes = []; // collection of nodes that want to receive events
@ -92,7 +98,7 @@ module.exports = function(RED) {
tlsNode.addTLSOptions(options); tlsNode.addTLSOptions(options);
} }
} }
var socket = new ws(node.path,options); var socket = new ws(node.path,node.subprotocol,options);
socket.setMaxListeners(0); socket.setMaxListeners(0);
node.server = socket; // keep for closing node.server = socket; // keep for closing
handleConnection(socket); handleConnection(socket);
@ -105,22 +111,24 @@ module.exports = function(RED) {
if (node.isServer) { if (node.isServer) {
node._clients[id] = socket; node._clients[id] = socket;
node.emit('opened',{count:Object.keys(node._clients).length,id:id}); node.emit('opened',{count:Object.keys(node._clients).length,id:id});
} else {
if (node.heartbeat) {
node.heartbeatInterval = setInterval(function() {
if (socket.nrPendingHeartbeat) {
// No pong received
socket.terminate();
socket.nrErrorHandler(new Error("timeout"));
return;
}
socket.nrPendingHeartbeat = true;
socket.ping();
},node.heartbeat);
}
} }
socket.on('open',function() { socket.on('open',function() {
if (!node.isServer) { if (!node.isServer) {
if (node.heartbeat) {
clearInterval(node.heartbeatInterval);
node.heartbeatInterval = setInterval(function() {
if (socket.nrPendingHeartbeat) {
// No pong received
socket.terminate();
socket.nrErrorHandler(new Error("timeout"));
return;
}
socket.nrPendingHeartbeat = true;
try {
socket.ping();
} catch(err) {}
},node.heartbeat);
}
node.emit('opened',{count:'',id:id}); node.emit('opened',{count:'',id:id});
} }
}); });

View File

@ -23,9 +23,17 @@
</select> </select>
<span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width:65px"> <span data-i18n="tcpin.label.port"></span> <input type="text" id="node-input-port" style="width:65px">
</div> </div>
<div class="form-row hidden" id="node-input-host-row" style="padding-left: 110px;"> <div class="form-row hidden" id="node-input-host-row" style="padding-left:110px;">
<span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;"> <span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" placeholder="localhost" style="width: 60%;">
</div> </div>
<div class="form-row" id="node-input-tls-enable">
<label> </label>
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width:auto; vertical-align:top;">
<label for="node-input-usetls" style="width:auto" data-i18n="httpin.use-tls"></label>
<div id="node-row-tls" class="hide">
<label style="width:auto; margin-left:20px; margin-right:10px;" for="node-input-tls"><span data-i18n="httpin.tls-config"></span></label><input type="text" style="width: 300px" id="node-input-tls">
</div>
</div>
<div class="form-row"> <div class="form-row">
<label><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.output"></span></label> <label><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.output"></span></label>
@ -42,7 +50,7 @@
</div> </div>
<div id="node-row-newline" class="form-row hidden" style="padding-left:110px;"> <div id="node-row-newline" class="form-row hidden" style="padding-left:110px;">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;"> <span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
</div> </div>
<div class="form-row"> <div class="form-row">
@ -58,17 +66,18 @@
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('tcp in',{ RED.nodes.registerType('tcp in',{
category: 'network', category: 'network',
color:"Silver", color: "Silver",
defaults: { defaults: {
name: {value:""}, name: {value:""},
server: {value:"server",required:true}, server: {value:"server", required:true},
host: {value:"",validate:function(v) { return (this.server == "server")||v.length > 0;} }, host: {value:"", validate:function(v) { return (this.server == "server")||v.length > 0;} },
port: {value:"",required:true,validate:RED.validators.number()}, port: {value:"", required:true, validate:RED.validators.number()},
datamode:{value:"stream"}, datamode:{value:"stream"},
datatype:{value:"buffer"}, datatype:{value:"buffer"},
newline:{value:""}, newline:{value:""},
topic: {value:""}, topic: {value:""},
base64: {/*deprecated*/ value:false,required:true} base64: {/*deprecated*/ value:false, required:true},
tls: {type:"tls-config", value:'', required:false}
}, },
inputs:0, inputs:0,
outputs:1, outputs:1,
@ -77,7 +86,7 @@
return this.name || "tcp:"+(this.host?this.host+":":"")+this.port; return this.name || "tcp:"+(this.host?this.host+":":"")+this.port;
}, },
labelStyle: function() { labelStyle: function() {
return this.name?"node_label_italic":""; return this.name ? "node_label_italic" : "";
}, },
oneditprepare: function() { oneditprepare: function() {
var updateOptions = function() { var updateOptions = function() {
@ -103,6 +112,27 @@
$("#node-input-server").change(updateOptions); $("#node-input-server").change(updateOptions);
$("#node-input-datatype").change(updateOptions); $("#node-input-datatype").change(updateOptions);
$("#node-input-datamode").change(updateOptions); $("#node-input-datamode").change(updateOptions);
function updateTLSOptions() {
if ($("#node-input-usetls").is(':checked')) {
$("#node-row-tls").show();
} else {
$("#node-row-tls").hide();
}
}
if (this.tls) {
$('#node-input-usetls').prop('checked', true);
} else {
$('#node-input-usetls').prop('checked', false);
}
updateTLSOptions();
$("#node-input-usetls").on("click",function() {
updateTLSOptions();
});
},
oneditsave: function() {
if (!$("#node-input-usetls").is(':checked')) {
$("#node-input-tls").val("_ADD_");
}
} }
}); });
</script> </script>
@ -123,6 +153,15 @@
<span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" style="width: 60%;"> <span data-i18n="tcpin.label.host"></span> <input type="text" id="node-input-host" style="width: 60%;">
</div> </div>
<div class="form-row" id="node-input-tls-enable">
<label> </label>
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-usetls" style="width: auto" data-i18n="httpin.use-tls"></label>
<div id="node-row-tls" class="hide">
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-tls"><span data-i18n="httpin.tls-config"></span></label><input type="text" style="width: 300px" id="node-input-tls">
</div>
</div>
<div class="form-row hidden" id="node-input-end-row"> <div class="form-row hidden" id="node-input-end-row">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-end" style="display: inline-block; width: auto; vertical-align: top;">
@ -144,14 +183,15 @@
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('tcp out',{ RED.nodes.registerType('tcp out',{
category: 'network', category: 'network',
color:"Silver", color: "Silver",
defaults: { defaults: {
name: {value:""},
host: {value:"",validate:function(v) { return (this.beserver != "client")||v.length > 0;} }, host: {value:"",validate:function(v) { return (this.beserver != "client")||v.length > 0;} },
port: {value:"",validate:function(v) { return (this.beserver == "reply")||RED.validators.number()(v); } }, port: {value:"",validate:function(v) { return (this.beserver == "reply")||RED.validators.number()(v); } },
beserver: {value:"client",required:true}, beserver: {value:"client", required:true},
base64: {value:false,required:true}, base64: {value:false, required:true},
end: {value:false,required:true}, end: {value:false, required:true},
name: {value:""} tls: {type:"tls-config", value:'', required:false}
}, },
inputs:1, inputs:1,
outputs:0, outputs:0,
@ -170,18 +210,42 @@
$("#node-input-port-row").hide(); $("#node-input-port-row").hide();
$("#node-input-host-row").hide(); $("#node-input-host-row").hide();
$("#node-input-end-row").hide(); $("#node-input-end-row").hide();
$("#node-input-tls-enable").hide();
} else if (sockettype == "client"){ } else if (sockettype == "client"){
$("#node-input-port-row").show(); $("#node-input-port-row").show();
$("#node-input-host-row").show(); $("#node-input-host-row").show();
$("#node-input-end-row").show(); $("#node-input-end-row").show();
$("#node-input-tls-enable").show();
} else { } else {
$("#node-input-port-row").show(); $("#node-input-port-row").show();
$("#node-input-host-row").hide(); $("#node-input-host-row").hide();
$("#node-input-end-row").show(); $("#node-input-end-row").show();
$("#node-input-tls-enable").show();
} }
}; };
updateOptions(); updateOptions();
$("#node-input-beserver").change(updateOptions); $("#node-input-beserver").change(updateOptions);
function updateTLSOptions() {
if ($("#node-input-usetls").is(':checked')) {
$("#node-row-tls").show();
} else {
$("#node-row-tls").hide();
}
}
if (this.tls) {
$('#node-input-usetls').prop('checked', true);
} else {
$('#node-input-usetls').prop('checked', false);
}
updateTLSOptions();
$("#node-input-usetls").on("click",function() {
updateTLSOptions();
});
},
oneditsave: function() {
if (!$("#node-input-usetls").is(':checked')) {
$("#node-input-tls").val("_ADD_");
}
} }
}); });
</script> </script>
@ -194,15 +258,23 @@
<span data-i18n="tcpin.label.port"></span> <span data-i18n="tcpin.label.port"></span>
<input type="text" id="node-input-port" style="width:60px"> <input type="text" id="node-input-port" style="width:60px">
</div> </div>
<div class="form-row" id="node-input-tls-enable">
<label> </label>
<input type="checkbox" id="node-input-usetls" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-usetls" style="width: auto" data-i18n="httpin.use-tls"></label>
<div id="node-row-tls" class="hide">
<label style="width: auto; margin-left: 20px; margin-right: 10px;" for="node-input-tls"><span data-i18n="httpin.tls-config"></span></label><input type="text" style="width: 300px" id="node-input-tls">
</div>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-out"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label> <label for="node-input-ret"><i class="fa fa-sign-out"></i> <span data-i18n="tcpin.label.return"></span></label>
<select type="text" id="node-input-ret" style="width:54%;"> <select type="text" id="node-input-ret" style="width:54%;">
<option value="buffer" data-i18n="tcpin.output.buffer"></option> <option value="buffer" data-i18n="tcpin.output.buffer"></option>
<option value="string" data-i18n="tcpin.output.string"></option> <option value="string" data-i18n="tcpin.output.string"></option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-out"> </label> <label for="node-input-out"><i class="fa fa-sign-out fa-rotate-90"></i> <span data-i18n="tcpin.label.close"></span></label>
<select type="text" id="node-input-out" style="width:54%;"> <select type="text" id="node-input-out" style="width:54%;">
<option value="time" data-i18n="tcpin.return.timeout"></option> <option value="time" data-i18n="tcpin.return.timeout"></option>
<option value="char" data-i18n="tcpin.return.character"></option> <option value="char" data-i18n="tcpin.return.character"></option>
@ -213,6 +285,9 @@
<input type="text" id="node-input-splitc" style="width:50px;"> <input type="text" id="node-input-splitc" style="width:50px;">
<span id="node-units"></span> <span id="node-units"></span>
</div> </div>
<div id="node-row-newline" class="form-row hidden" style="padding-left:162px;">
<span data-i18n="tcpin.label.delimited"></span> <input type="text" id="node-input-newline" style="width:110px;" data-i18n="[placeholder]tcpin.label.optional">
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@ -222,14 +297,16 @@
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType('tcp request',{ RED.nodes.registerType('tcp request',{
category: 'network', category: 'network',
color:"Silver", color: "Silver",
defaults: { defaults: {
name: {value:""},
server: {value:""}, server: {value:""},
port: {value:"",validate:RED.validators.regex(/^(\d*|)$/)}, port: {value:"", validate:RED.validators.regex(/^(\d*|)$/)},
out: {value:"time",required:true}, out: {value:"time", required:true},
ret: {value:"buffer"}, ret: {value:"buffer"},
splitc: {value:"0",required:true}, splitc: {value:"0", required:true},
name: {value:""} newline: {value:""},
tls: {type:"tls-config", value:'', required:false}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,
@ -246,6 +323,14 @@
$("#node-input-ret").val("buffer"); $("#node-input-ret").val("buffer");
this.ret = "buffer"; this.ret = "buffer";
} }
$("#node-input-ret").on("change", function() {
if ($("#node-input-ret").val() === "string" && $("#node-input-out").val() === "sit") { $("#node-row-newline").show(); }
else { $("#node-row-newline").hide(); }
});
$("#node-input-out").on("change", function() {
if ($("#node-input-ret").val() === "string" && $("#node-input-out").val() === "sit") { $("#node-row-newline").show(); }
else { $("#node-row-newline").hide(); }
});
$("#node-input-out").on('focus', function () { previous = this.value; }).on("change", function() { $("#node-input-out").on('focus', function () { previous = this.value; }).on("change", function() {
$("#node-input-splitc").show(); $("#node-input-splitc").show();
if (previous === null) { previous = $("#node-input-out").val(); } if (previous === null) { previous = $("#node-input-out").val(); }
@ -272,6 +357,27 @@
$("#node-input-splitc").hide(); $("#node-input-splitc").hide();
} }
}); });
function updateTLSOptions() {
if ($("#node-input-usetls").is(':checked')) {
$("#node-row-tls").show();
} else {
$("#node-row-tls").hide();
}
}
if (this.tls) {
$('#node-input-usetls').prop('checked', true);
} else {
$('#node-input-usetls').prop('checked', false);
}
updateTLSOptions();
$("#node-input-usetls").on("click",function() {
updateTLSOptions();
});
},
oneditsave: function() {
if (!$("#node-input-usetls").is(':checked')) {
$("#node-input-tls").val("_ADD_");
}
} }
}); });
</script> </script>

View File

@ -16,13 +16,46 @@
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
var reconnectTime = RED.settings.socketReconnectTime||10000; let reconnectTime = RED.settings.socketReconnectTime || 10000;
var socketTimeout = RED.settings.socketTimeout||null; let socketTimeout = RED.settings.socketTimeout || null;
const msgQueueSize = RED.settings.tcpMsgQueueSize || 1000; const msgQueueSize = RED.settings.tcpMsgQueueSize || 1000;
const Denque = require('denque'); const Denque = require('denque');
var net = require('net'); const net = require('net');
const tls = require('tls');
var connectionPool = {}; let connectionPool = {};
function normalizeConnectArgs(listArgs) {
const args = net._normalizeArgs(listArgs);
const options = args[0];
const cb = args[1];
// If args[0] was options, then normalize dealt with it.
// If args[0] is port, or args[0], args[1] is host, port, we need to
// find the options and merge them in, normalize's options has only
// the host/port/path args that it knows about, not the tls options.
// This means that options.host overrides a host arg.
if (listArgs[1] !== null && typeof listArgs[1] === 'object') {
ObjectAssign(options, listArgs[1]);
} else if (listArgs[2] !== null && typeof listArgs[2] === 'object') {
ObjectAssign(options, listArgs[2]);
}
return cb ? [options, cb] : [options];
}
function getAllowUnauthorized() {
const allowUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0';
if (allowUnauthorized) {
process.emitWarning(
'Setting the NODE_TLS_REJECT_UNAUTHORIZED ' +
'environment variable to \'0\' makes TLS connections ' +
'and HTTPS requests insecure by disabling ' +
'certificate verification.');
}
return allowUnauthorized;
}
/** /**
* Enqueue `item` in `queue` * Enqueue `item` in `queue`
@ -53,13 +86,14 @@ module.exports = function(RED) {
this.topic = n.topic; this.topic = n.topic;
this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/ this.stream = (!n.datamode||n.datamode=='stream'); /* stream,single*/
this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */ this.datatype = n.datatype||'buffer'; /* buffer,utf8,base64 */
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r"); this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
this.base64 = n.base64; this.base64 = n.base64;
this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server"); this.server = (typeof n.server == 'boolean')?n.server:(n.server == "server");
this.closing = false; this.closing = false;
this.connected = false; this.connected = false;
var node = this; var node = this;
var count = 0; var count = 0;
if (n.tls) { var tlsNode = RED.nodes.getNode(n.tls); }
if (!node.server) { if (!node.server) {
var buffer = null; var buffer = null;
@ -70,13 +104,25 @@ module.exports = function(RED) {
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port})); node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"}); node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
var id = RED.util.generateId(); var id = RED.util.generateId();
client = net.connect(node.port, node.host, function() { var connOpts = {host: node.host};
buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : ""; if (n.tls) {
node.connected = true; var connOpts = tlsNode.addTLSOptions({host: node.host});
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port})); client = tls.connect(node.port, connOpts, function() {
node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}}); buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
}); node.connected = true;
client.setKeepAlive(true,120000); node.log(RED._("status.connected", {host: node.host, port: node.port}));
node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}});
});
}
else {
client = net.connect(node.port, node.host, function() {
buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
node.connected = true;
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}});
});
}
client.setKeepAlive(true, 120000);
connectionPool[id] = client; connectionPool[id] = client;
client.on('data', function (data) { client.on('data', function (data) {
@ -89,7 +135,7 @@ module.exports = function(RED) {
buffer = buffer+data; buffer = buffer+data;
var parts = buffer.split(node.newline); var parts = buffer.split(node.newline);
for (var i = 0; i<parts.length-1; i+=1) { for (var i = 0; i<parts.length-1; i+=1) {
msg = {topic:node.topic, payload:parts[i]}; msg = {topic:node.topic, payload:parts[i] + node.newline.trimEnd()};
msg._session = {type:"tcp",id:id}; msg._session = {type:"tcp",id:id};
node.send(msg); node.send(msg);
} }
@ -150,7 +196,13 @@ module.exports = function(RED) {
}); });
} }
else { else {
var server = net.createServer(function (socket) { let srv = net;
let connOpts;
if (n.tls) {
srv = tls;
connOpts = tlsNode.addTLSOptions({});
}
var server = srv.createServer(connOpts, function (socket) {
socket.setKeepAlive(true,120000); socket.setKeepAlive(true,120000);
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); } if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
var id = RED.util.generateId(); var id = RED.util.generateId();
@ -177,7 +229,7 @@ module.exports = function(RED) {
buffer = buffer+data; buffer = buffer+data;
var parts = buffer.split(node.newline); var parts = buffer.split(node.newline);
for (var i = 0; i<parts.length-1; i+=1) { for (var i = 0; i<parts.length-1; i+=1) {
msg = {topic:node.topic, payload:parts[i], ip:socket.remoteAddress, port:socket.remotePort}; msg = {topic:node.topic, payload:parts[i] + node.newline.trimEnd(), ip:socket.remoteAddress, port:socket.remotePort};
msg._session = {type:"tcp",id:id}; msg._session = {type:"tcp",id:id};
node.send(msg); node.send(msg);
} }
@ -269,8 +321,9 @@ module.exports = function(RED) {
this.closing = false; this.closing = false;
this.connected = false; this.connected = false;
var node = this; var node = this;
if (n.tls) { var tlsNode = RED.nodes.getNode(n.tls); }
if (!node.beserver||node.beserver=="client") { if (!node.beserver || node.beserver == "client") {
var reconnectTimeout; var reconnectTimeout;
var client = null; var client = null;
var end = false; var end = false;
@ -278,11 +331,24 @@ module.exports = function(RED) {
var setupTcpClient = function() { var setupTcpClient = function() {
node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port})); node.log(RED._("tcpin.status.connecting",{host:node.host,port:node.port}));
node.status({fill:"grey",shape:"dot",text:"common.status.connecting"}); node.status({fill:"grey",shape:"dot",text:"common.status.connecting"});
client = net.connect(node.port, node.host, function() { if (n.tls) {
node.connected = true; // connOpts = tlsNode.addTLSOptions(connOpts);
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port})); // client = tls.connect(connOpts, function() {
node.status({fill:"green",shape:"dot",text:"common.status.connected"}); var connOpts = tlsNode.addTLSOptions({host: node.host});
}); client = tls.connect(node.port, connOpts, function() {
// buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : "";
node.connected = true;
node.log(RED._("status.connected", {host: node.host, port: node.port}));
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
}
else {
client = net.connect(node.port, node.host, function() {
node.connected = true;
node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port}));
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
});
}
client.setKeepAlive(true,120000); client.setKeepAlive(true,120000);
client.on('error', function (err) { client.on('error', function (err) {
node.log(RED._("tcpin.errors.error",{error:err.toString()})); node.log(RED._("tcpin.errors.error",{error:err.toString()}));
@ -368,7 +434,13 @@ module.exports = function(RED) {
else { else {
var connectedSockets = []; var connectedSockets = [];
node.status({text:RED._("tcpin.status.connections",{count:0})}); node.status({text:RED._("tcpin.status.connections",{count:0})});
var server = net.createServer(function (socket) { let srv = net;
let connOpts;
if (n.tls) {
srv = tls;
connOpts = tlsNode.addTLSOptions({});
}
var server = srv.createServer(connOpts, function (socket) {
socket.setKeepAlive(true,120000); socket.setKeepAlive(true,120000);
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); } if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort})); node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort}));
@ -445,7 +517,11 @@ module.exports = function(RED) {
this.port = Number(n.port); this.port = Number(n.port);
this.out = n.out; this.out = n.out;
this.ret = n.ret || "buffer"; this.ret = n.ret || "buffer";
this.newline = (n.newline||"").replace("\\n","\n").replace("\\r","\r").replace("\\t","\t");
this.splitc = n.splitc; this.splitc = n.splitc;
if (n.tls) {
var tlsNode = RED.nodes.getNode(n.tls);
}
if (this.out === "immed") { this.splitc = -1; this.out = "time"; } if (this.out === "immed") { this.splitc = -1; this.out = "time"; }
if (this.out !== "char") { this.splitc = Number(this.splitc); } if (this.out !== "char") { this.splitc = Number(this.splitc); }
@ -500,12 +576,48 @@ module.exports = function(RED) {
} }
else { buf = Buffer.alloc(65536); } // set it to 64k... hopefully big enough for most TCP packets.... but only hopefully else { buf = Buffer.alloc(65536); } // set it to 64k... hopefully big enough for most TCP packets.... but only hopefully
clients[connection_id].client = net.Socket(); var connOpts = {host:host, port:port};
if (n.tls) {
connOpts = tlsNode.addTLSOptions(connOpts);
const allowUnauthorized = getAllowUnauthorized();
let options = {
rejectUnauthorized: !allowUnauthorized,
ciphers: tls.DEFAULT_CIPHERS,
checkServerIdentity: tls.checkServerIdentity,
minDHSize: 1024,
...connOpts
};
if (!options.keepAlive) { options.singleUse = true; }
const context = options.secureContext || tls.createSecureContext(options);
clients[connection_id].client = new tls.TLSSocket(options.socket, {
allowHalfOpen: options.allowHalfOpen,
pipe: !!options.path,
secureContext: context,
isServer: false,
requestCert: false, // true,
rejectUnauthorized: false, // options.rejectUnauthorized !== false,
session: options.session,
ALPNProtocols: options.ALPNProtocols,
requestOCSP: options.requestOCSP,
enableTrace: options.enableTrace,
pskCallback: options.pskCallback,
highWaterMark: options.highWaterMark,
onread: options.onread,
signal: options.signal,
});
}
else {
clients[connection_id].client = net.Socket();
}
if (socketTimeout !== null) { clients[connection_id].client.setTimeout(socketTimeout);} if (socketTimeout !== null) { clients[connection_id].client.setTimeout(socketTimeout);}
if (host && port) { if (host && port) {
clients[connection_id].connecting = true; clients[connection_id].connecting = true;
clients[connection_id].client.connect(port, host, function() { clients[connection_id].client.connect(connOpts, function() {
//node.log(RED._("tcpin.errors.client-connected")); //node.log(RED._("tcpin.errors.client-connected"));
node.status({fill:"green",shape:"dot",text:"common.status.connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});
if (clients[connection_id] && clients[connection_id].client) { if (clients[connection_id] && clients[connection_id].client) {
@ -528,17 +640,32 @@ module.exports = function(RED) {
else { else {
node.warn(RED._("tcpin.errors.no-host")); node.warn(RED._("tcpin.errors.no-host"));
} }
var chunk = "";
clients[connection_id].client.on('data', function(data) { clients[connection_id].client.on('data', function(data) {
if (node.out === "sit") { // if we are staying connected just send the buffer if (node.out === "sit") { // if we are staying connected just send the buffer
if (clients[connection_id]) { if (clients[connection_id]) {
const msg = clients[connection_id].lastMsg || {}; const msg = clients[connection_id].lastMsg || {};
msg.payload = RED.util.cloneMessage(data); msg.payload = RED.util.cloneMessage(data);
if (node.ret === "string") { if (node.ret === "string") {
try { msg.payload = msg.payload.toString(); } try {
catch(e) { node.error("Failed to create string", msg); } if (node.newline && node.newline !== "" ) {
chunk += msg.payload.toString();
let parts = chunk.split(node.newline);
for (var p=0; p<parts.length-1; p+=1) {
let m = RED.util.cloneMessage(msg);
m.payload = parts[p] + node.newline.trimEnd();
nodeSend(m);
}
chunk = parts[parts.length-1];
}
else {
msg.payload = msg.payload.toString();
nodeSend(msg);
}
}
catch(e) { node.error(RED._("tcpin.errors.bad-string"), msg); }
} }
nodeSend(msg); else { nodeSend(msg); }
} }
} }
// else if (node.splitc === 0) { // else if (node.splitc === 0) {
@ -675,7 +802,13 @@ module.exports = function(RED) {
//node.warn(RED._("tcpin.errors.connect-timeout")); //node.warn(RED._("tcpin.errors.connect-timeout"));
if (clients[connection_id].client) { if (clients[connection_id].client) {
clients[connection_id].connecting = true; clients[connection_id].connecting = true;
clients[connection_id].client.connect(port, host, function() {
var connOpts = {host:host, port:port};
if (n.tls) {
connOpts = tlsNode.addTLSOptions(connOpts);
}
clients[connection_id].client.connect(connOpts, function() {
clients[connection_id].connected = true; clients[connection_id].connected = true;
clients[connection_id].connecting = false; clients[connection_id].connecting = false;
node.status({fill:"green",shape:"dot",text:"common.status.connected"}); node.status({fill:"green",shape:"dot",text:"common.status.connected"});

View File

@ -49,7 +49,11 @@ module.exports = function(RED) {
} }
var value = RED.util.getMessageProperty(msg,node.property); var value = RED.util.getMessageProperty(msg,node.property);
if (value !== undefined) { if (value !== undefined) {
if (typeof value === "string") { if (typeof value === "string" || Buffer.isBuffer(value)) {
// if (Buffer.isBuffer(value) && node.action !== "obj") {
// node.warn(RED._("json.errors.dropped")); done();
// }
// else
if (node.action === "" || node.action === "obj") { if (node.action === "" || node.action === "obj") {
try { try {
RED.util.setMessageProperty(msg,node.property,JSON.parse(value)); RED.util.setMessageProperty(msg,node.property,JSON.parse(value));

View File

@ -71,9 +71,7 @@ module.exports = function(RED) {
node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg); node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
} }
else { else {
if (RED.settings.verbose) { node.debug(RED._("file.status.deletedfile",{file:filename}));
node.log(RED._("file.status.deletedfile",{file:filename}));
}
nodeSend(msg); nodeSend(msg);
} }
done(); done();

View File

@ -0,0 +1,156 @@
[
{
"id": "62ea32aa.d73aac",
"type": "comment",
"z": "6312c0588348b2d4",
"name": "Example: Link Call Node",
"info": "Link call node can call link in node then get result from link out node.",
"x": 230,
"y": 180,
"wires": []
},
{
"id": "c588bc36.87fec",
"type": "comment",
"z": "6312c0588348b2d4",
"name": "↓ call link in node",
"info": "",
"x": 440,
"y": 220,
"wires": []
},
{
"id": "cd31efb4d2c6967e",
"type": "link call",
"z": "6312c0588348b2d4",
"name": "",
"links": [
"dbc46892c8d14c37"
],
"timeout": "30",
"x": 420,
"y": 260,
"wires": [
[
"c3db64d1d2260340"
]
]
},
{
"id": "dbc46892c8d14c37",
"type": "link in",
"z": "6312c0588348b2d4",
"name": "",
"links": [],
"x": 315,
"y": 340,
"wires": [
[
"e10575d73f2e5352"
]
]
},
{
"id": "6b61792143b3b0a3",
"type": "inject",
"z": "6312c0588348b2d4",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 240,
"y": 260,
"wires": [
[
"cd31efb4d2c6967e"
]
]
},
{
"id": "e10575d73f2e5352",
"type": "change",
"z": "6312c0588348b2d4",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "Hello, World!",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 450,
"y": 340,
"wires": [
[
"cf8438e7137bc0f0"
]
]
},
{
"id": "cf8438e7137bc0f0",
"type": "link out",
"z": "6312c0588348b2d4",
"name": "",
"mode": "return",
"links": [],
"x": 595,
"y": 340,
"wires": []
},
{
"id": "c3db64d1d2260340",
"type": "debug",
"z": "6312c0588348b2d4",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 600,
"y": 260,
"wires": []
},
{
"id": "6d077dfa0987febb",
"type": "comment",
"z": "6312c0588348b2d4",
"name": "↑called from link call node",
"info": "",
"x": 410,
"y": 380,
"wires": []
},
{
"id": "53b9a0adfd8c4217",
"type": "comment",
"z": "6312c0588348b2d4",
"name": "↑return to link call node",
"info": "",
"x": 680,
"y": 380,
"wires": []
}
]

View File

@ -2,7 +2,7 @@
{ {
"id": "84222b92.d65d18", "id": "84222b92.d65d18",
"type": "inject", "type": "inject",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"props": [ "props": [
{ {
@ -20,8 +20,8 @@
"topic": "", "topic": "",
"payload": "Hello, World!", "payload": "Hello, World!",
"payloadType": "str", "payloadType": "str",
"x": 230, "x": 190,
"y": 220, "y": 180,
"wires": [ "wires": [
[ [
"b4b9f603.739598" "b4b9f603.739598"
@ -31,25 +31,25 @@
{ {
"id": "7b014430.dfd94c", "id": "7b014430.dfd94c",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "Write string to a file, then read from the file", "name": "Write string to a file, then read from the file",
"info": "File-in node can read string from a file.", "info": "Read file node can read string from a file.",
"x": 260, "x": 220,
"y": 140, "y": 100,
"wires": [] "wires": []
}, },
{ {
"id": "b4b9f603.739598", "id": "b4b9f603.739598",
"type": "file", "type": "file",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"appendNewline": true, "appendNewline": true,
"createDir": false, "createDir": false,
"overwriteFile": "true", "overwriteFile": "true",
"encoding": "none", "encoding": "none",
"x": 420, "x": 380,
"y": 220, "y": 180,
"wires": [ "wires": [
[ [
"6dc01cac.5c4bf4" "6dc01cac.5c4bf4"
@ -59,7 +59,7 @@
{ {
"id": "2587adb9.7e60f2", "id": "2587adb9.7e60f2",
"type": "debug", "type": "debug",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"active": true, "active": true,
"tosidebar": true, "tosidebar": true,
@ -68,22 +68,22 @@
"complete": "false", "complete": "false",
"statusVal": "", "statusVal": "",
"statusType": "auto", "statusType": "auto",
"x": 810, "x": 770,
"y": 220, "y": 180,
"wires": [] "wires": []
}, },
{ {
"id": "6dc01cac.5c4bf4", "id": "6dc01cac.5c4bf4",
"type": "file in", "type": "file in",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"format": "utf8", "format": "utf8",
"chunk": false, "chunk": false,
"sendError": false, "sendError": false,
"encoding": "none", "encoding": "none",
"x": 620, "x": 580,
"y": 220, "y": 180,
"wires": [ "wires": [
[ [
"2587adb9.7e60f2" "2587adb9.7e60f2"
@ -93,21 +93,21 @@
{ {
"id": "f4b4309a.3b78a", "id": "f4b4309a.3b78a",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↑read result from file", "name": "↑read result from file",
"info": "", "info": "",
"x": 630, "x": 590,
"y": 260, "y": 220,
"wires": [] "wires": []
}, },
{ {
"id": "672d3693.3cabd8", "id": "672d3693.3cabd8",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↓write to /tmp/hello.txt", "name": "↓write to /tmp/hello.txt",
"info": "", "info": "",
"x": 440, "x": 400,
"y": 180, "y": 140,
"wires": [] "wires": []
} }
] ]

View File

@ -2,7 +2,7 @@
{ {
"id": "8997398f.c5d628", "id": "8997398f.c5d628",
"type": "inject", "type": "inject",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"props": [ "props": [
{ {
@ -20,8 +20,8 @@
"topic": "", "topic": "",
"payload": "😀", "payload": "😀",
"payloadType": "str", "payloadType": "str",
"x": 210, "x": 170,
"y": 480, "y": 260,
"wires": [ "wires": [
[ [
"56e32d23.050f44" "56e32d23.050f44"
@ -31,25 +31,25 @@
{ {
"id": "4e598e65.1799d", "id": "4e598e65.1799d",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "Read data in specified encoding", "name": "Read data in specified encoding",
"info": "File-in node can specify encoding of data read from a file.", "info": "Read file node can specify encoding of data read from a file.",
"x": 230, "x": 190,
"y": 400, "y": 180,
"wires": [] "wires": []
}, },
{ {
"id": "56e32d23.050f44", "id": "56e32d23.050f44",
"type": "file", "type": "file",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"appendNewline": true, "appendNewline": true,
"createDir": false, "createDir": false,
"overwriteFile": "true", "overwriteFile": "true",
"encoding": "none", "encoding": "none",
"x": 380, "x": 340,
"y": 480, "y": 260,
"wires": [ "wires": [
[ [
"38fa0579.f2cd8a" "38fa0579.f2cd8a"
@ -59,7 +59,7 @@
{ {
"id": "d28c8994.99c0a8", "id": "d28c8994.99c0a8",
"type": "debug", "type": "debug",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"active": true, "active": true,
"tosidebar": true, "tosidebar": true,
@ -68,22 +68,23 @@
"complete": "false", "complete": "false",
"statusVal": "", "statusVal": "",
"statusType": "auto", "statusType": "auto",
"x": 770, "x": 730,
"y": 480, "y": 260,
"wires": [] "wires": []
}, },
{ {
"id": "38fa0579.f2cd8a", "id": "38fa0579.f2cd8a",
"type": "file in", "type": "file in",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"format": "utf8", "format": "utf8",
"chunk": false, "chunk": false,
"sendError": false, "sendError": false,
"encoding": "base64", "encoding": "base64",
"x": 580, "allProps": false,
"y": 480, "x": 540,
"y": 260,
"wires": [ "wires": [
[ [
"d28c8994.99c0a8" "d28c8994.99c0a8"
@ -93,21 +94,21 @@
{ {
"id": "fa22ca20.ae4528", "id": "fa22ca20.ae4528",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↑read data from file as base64 string", "name": "↑read data from file as base64 string",
"info": "", "info": "",
"x": 640, "x": 600,
"y": 520, "y": 300,
"wires": [] "wires": []
}, },
{ {
"id": "148e25ad.98891a", "id": "148e25ad.98891a",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↓write to /tmp/hello.txt", "name": "↓write to /tmp/hello.txt",
"info": "", "info": "",
"x": 400, "x": 360,
"y": 440, "y": 220,
"wires": [] "wires": []
} }
] ]

View File

@ -2,7 +2,7 @@
{ {
"id": "6a0b1d03.d4cee4", "id": "6a0b1d03.d4cee4",
"type": "inject", "type": "inject",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"props": [ "props": [
{ {
@ -20,8 +20,8 @@
"topic": "", "topic": "",
"payload": "", "payload": "",
"payloadType": "date", "payloadType": "date",
"x": 220, "x": 160,
"y": 740, "y": 220,
"wires": [ "wires": [
[ [
"d4b00cb7.a5a23" "d4b00cb7.a5a23"
@ -31,25 +31,25 @@
{ {
"id": "f17ea1d1.8ecc3", "id": "f17ea1d1.8ecc3",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "Read data breaking lines into individual messages", "name": "Read data breaking lines into individual messages",
"info": "File-in node can break read text into messages with individual lines", "info": "Read file node can break read text into messages with individual lines",
"x": 290, "x": 230,
"y": 660, "y": 140,
"wires": [] "wires": []
}, },
{ {
"id": "99ae7806.1d6428", "id": "99ae7806.1d6428",
"type": "file", "type": "file",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"appendNewline": true, "appendNewline": true,
"createDir": false, "createDir": false,
"overwriteFile": "true", "overwriteFile": "true",
"encoding": "none", "encoding": "none",
"x": 540, "x": 480,
"y": 740, "y": 220,
"wires": [ "wires": [
[ [
"70d7892f.d27db8" "70d7892f.d27db8"
@ -59,7 +59,7 @@
{ {
"id": "7ed8282c.92b338", "id": "7ed8282c.92b338",
"type": "debug", "type": "debug",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"active": true, "active": true,
"tosidebar": true, "tosidebar": true,
@ -68,22 +68,22 @@
"complete": "false", "complete": "false",
"statusVal": "", "statusVal": "",
"statusType": "auto", "statusType": "auto",
"x": 810, "x": 750,
"y": 800, "y": 280,
"wires": [] "wires": []
}, },
{ {
"id": "70d7892f.d27db8", "id": "70d7892f.d27db8",
"type": "file in", "type": "file in",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"format": "lines", "format": "lines",
"chunk": false, "chunk": false,
"sendError": false, "sendError": false,
"encoding": "none", "encoding": "none",
"x": 620, "x": 560,
"y": 800, "y": 280,
"wires": [ "wires": [
[ [
"7ed8282c.92b338" "7ed8282c.92b338"
@ -93,27 +93,27 @@
{ {
"id": "c1b7e05.1d94b2", "id": "c1b7e05.1d94b2",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↑read data from file breaking lines into messages", "name": "↑read data from file breaking lines into messages",
"info": "", "info": "",
"x": 720, "x": 660,
"y": 840, "y": 320,
"wires": [] "wires": []
}, },
{ {
"id": "a5f647b2.cf27a8", "id": "a5f647b2.cf27a8",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↓write to /tmp/hello.txt", "name": "↓write to /tmp/hello.txt",
"info": "", "info": "",
"x": 560, "x": 500,
"y": 700, "y": 180,
"wires": [] "wires": []
}, },
{ {
"id": "d4b00cb7.a5a23", "id": "d4b00cb7.a5a23",
"type": "template", "type": "template",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "data", "name": "data",
"field": "payload", "field": "payload",
"fieldType": "msg", "fieldType": "msg",
@ -121,8 +121,8 @@
"syntax": "plain", "syntax": "plain",
"template": "one\ntwo\nthree!", "template": "one\ntwo\nthree!",
"output": "str", "output": "str",
"x": 370, "x": 310,
"y": 740, "y": 220,
"wires": [ "wires": [
[ [
"99ae7806.1d6428" "99ae7806.1d6428"

View File

@ -2,7 +2,7 @@
{ {
"id": "bdd57acc.2edc48", "id": "bdd57acc.2edc48",
"type": "inject", "type": "inject",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"props": [ "props": [
{ {
@ -20,8 +20,8 @@
"topic": "", "topic": "",
"payload": "", "payload": "",
"payloadType": "date", "payloadType": "date",
"x": 220, "x": 180,
"y": 1040, "y": 220,
"wires": [ "wires": [
[ [
"7a069b01.0c2324" "7a069b01.0c2324"
@ -31,25 +31,25 @@
{ {
"id": "1fd12220.33953e", "id": "1fd12220.33953e",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "Creating a message stream from lines of data", "name": "Creating a message stream from lines of data",
"info": "File-in node can break read text into messages with individual lines. The messages creates a stream of messages.", "info": "Read file node can break read text into messages with individual lines. The messages creates a stream of messages.",
"x": 270, "x": 230,
"y": 960, "y": 140,
"wires": [] "wires": []
}, },
{ {
"id": "ab6eb213.2a08d", "id": "ab6eb213.2a08d",
"type": "file", "type": "file",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"appendNewline": true, "appendNewline": true,
"createDir": false, "createDir": false,
"overwriteFile": "true", "overwriteFile": "true",
"encoding": "none", "encoding": "none",
"x": 540, "x": 500,
"y": 1040, "y": 220,
"wires": [ "wires": [
[ [
"b7ed49b0.649fb8" "b7ed49b0.649fb8"
@ -59,7 +59,7 @@
{ {
"id": "c48d8ae0.9ff3a8", "id": "c48d8ae0.9ff3a8",
"type": "debug", "type": "debug",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"active": true, "active": true,
"tosidebar": true, "tosidebar": true,
@ -68,22 +68,22 @@
"complete": "false", "complete": "false",
"statusVal": "", "statusVal": "",
"statusType": "auto", "statusType": "auto",
"x": 810, "x": 770,
"y": 1140, "y": 320,
"wires": [] "wires": []
}, },
{ {
"id": "b7ed49b0.649fb8", "id": "b7ed49b0.649fb8",
"type": "file in", "type": "file in",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"format": "lines", "format": "lines",
"chunk": false, "chunk": false,
"sendError": false, "sendError": false,
"encoding": "none", "encoding": "none",
"x": 280, "x": 240,
"y": 1140, "y": 320,
"wires": [ "wires": [
[ [
"83073ebe.fcce4" "83073ebe.fcce4"
@ -93,27 +93,27 @@
{ {
"id": "3c33e69f.6a04ba", "id": "3c33e69f.6a04ba",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↑read data from file breaking lines into messages", "name": "↑read data from file breaking lines into messages",
"info": "", "info": "",
"x": 380, "x": 340,
"y": 1180, "y": 360,
"wires": [] "wires": []
}, },
{ {
"id": "3598bf7d.5712a", "id": "3598bf7d.5712a",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↓write to /tmp/hello.txt", "name": "↓write to /tmp/hello.txt",
"info": "", "info": "",
"x": 560, "x": 520,
"y": 1000, "y": 180,
"wires": [] "wires": []
}, },
{ {
"id": "7a069b01.0c2324", "id": "7a069b01.0c2324",
"type": "template", "type": "template",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "data", "name": "data",
"field": "payload", "field": "payload",
"fieldType": "msg", "fieldType": "msg",
@ -121,8 +121,8 @@
"syntax": "plain", "syntax": "plain",
"template": "Apple\nBanana\nGrape\nOrange", "template": "Apple\nBanana\nGrape\nOrange",
"output": "str", "output": "str",
"x": 370, "x": 330,
"y": 1040, "y": 220,
"wires": [ "wires": [
[ [
"ab6eb213.2a08d" "ab6eb213.2a08d"
@ -132,7 +132,7 @@
{ {
"id": "8d4ed1d0.821fe", "id": "8d4ed1d0.821fe",
"type": "join", "type": "join",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "", "name": "",
"mode": "auto", "mode": "auto",
"build": "string", "build": "string",
@ -145,8 +145,8 @@
"timeout": "", "timeout": "",
"count": "", "count": "",
"reduceRight": false, "reduceRight": false,
"x": 630, "x": 590,
"y": 1140, "y": 320,
"wires": [ "wires": [
[ [
"c48d8ae0.9ff3a8" "c48d8ae0.9ff3a8"
@ -156,7 +156,7 @@
{ {
"id": "83073ebe.fcce4", "id": "83073ebe.fcce4",
"type": "switch", "type": "switch",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "< D", "name": "< D",
"property": "payload", "property": "payload",
"propertyType": "msg", "propertyType": "msg",
@ -170,8 +170,8 @@
"checkall": "true", "checkall": "true",
"repair": true, "repair": true,
"outputs": 1, "outputs": 1,
"x": 470, "x": 430,
"y": 1140, "y": 320,
"wires": [ "wires": [
[ [
"8d4ed1d0.821fe" "8d4ed1d0.821fe"
@ -181,21 +181,21 @@
{ {
"id": "2088e195.f7aebe", "id": "2088e195.f7aebe",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↓filter data before \"D\"", "name": "↓filter data before \"D\"",
"info": "", "info": "",
"x": 520, "x": 480,
"y": 1100, "y": 280,
"wires": [] "wires": []
}, },
{ {
"id": "b848cdc7.61e06", "id": "b848cdc7.61e06",
"type": "comment", "type": "comment",
"z": "194a3e4f.a92772", "z": "6312c0588348b2d4",
"name": "↑join to single string", "name": "↑join to single string",
"info": "", "info": "",
"x": 670, "x": 630,
"y": 1180, "y": 360,
"wires": [] "wires": []
} }
] ]

View File

@ -2,7 +2,7 @@
{ {
"id": "84222b92.d65d18", "id": "84222b92.d65d18",
"type": "inject", "type": "inject",
"z": "4b63452d.672afc", "z": "5132b95f037524f9",
"name": "", "name": "",
"props": [ "props": [
{ {
@ -20,8 +20,8 @@
"topic": "", "topic": "",
"payload": "Hello, World!", "payload": "Hello, World!",
"payloadType": "str", "payloadType": "str",
"x": 230, "x": 150,
"y": 200, "y": 220,
"wires": [ "wires": [
[ [
"b4b9f603.739598" "b4b9f603.739598"
@ -31,25 +31,25 @@
{ {
"id": "7b014430.dfd94c", "id": "7b014430.dfd94c",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "5132b95f037524f9",
"name": "Write string to a file, then read from the file", "name": "Write string to a file, then read from the file",
"info": "File node can write string to a file.", "info": "Write file node can write string from a file.",
"x": 260, "x": 180,
"y": 120, "y": 140,
"wires": [] "wires": []
}, },
{ {
"id": "b4b9f603.739598", "id": "b4b9f603.739598",
"type": "file", "type": "file",
"z": "4b63452d.672afc", "z": "5132b95f037524f9",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"appendNewline": true, "appendNewline": true,
"createDir": false, "createDir": false,
"overwriteFile": "true", "overwriteFile": "true",
"encoding": "none", "encoding": "none",
"x": 420, "x": 340,
"y": 200, "y": 220,
"wires": [ "wires": [
[ [
"6dc01cac.5c4bf4" "6dc01cac.5c4bf4"
@ -59,7 +59,7 @@
{ {
"id": "2587adb9.7e60f2", "id": "2587adb9.7e60f2",
"type": "debug", "type": "debug",
"z": "4b63452d.672afc", "z": "5132b95f037524f9",
"name": "", "name": "",
"active": true, "active": true,
"tosidebar": true, "tosidebar": true,
@ -68,22 +68,22 @@
"complete": "false", "complete": "false",
"statusVal": "", "statusVal": "",
"statusType": "auto", "statusType": "auto",
"x": 810, "x": 730,
"y": 200, "y": 220,
"wires": [] "wires": []
}, },
{ {
"id": "6dc01cac.5c4bf4", "id": "6dc01cac.5c4bf4",
"type": "file in", "type": "file in",
"z": "4b63452d.672afc", "z": "5132b95f037524f9",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"format": "utf8", "format": "utf8",
"chunk": false, "chunk": false,
"sendError": false, "sendError": false,
"encoding": "none", "encoding": "none",
"x": 620, "x": 540,
"y": 200, "y": 220,
"wires": [ "wires": [
[ [
"2587adb9.7e60f2" "2587adb9.7e60f2"
@ -93,21 +93,21 @@
{ {
"id": "f4b4309a.3b78a", "id": "f4b4309a.3b78a",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "5132b95f037524f9",
"name": "↑read result from file", "name": "↑read result from file",
"info": "", "info": "",
"x": 630, "x": 550,
"y": 240, "y": 260,
"wires": [] "wires": []
}, },
{ {
"id": "672d3693.3cabd8", "id": "672d3693.3cabd8",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "5132b95f037524f9",
"name": "↓write to /tmp/hello.txt", "name": "↓write to /tmp/hello.txt",
"info": "", "info": "",
"x": 440, "x": 360,
"y": 160, "y": 180,
"wires": [] "wires": []
} }
] ]

View File

@ -2,7 +2,7 @@
{ {
"id": "704479e1.399388", "id": "704479e1.399388",
"type": "inject", "type": "inject",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"props": [ "props": [
{ {
@ -25,8 +25,8 @@
"topic": "", "topic": "",
"payload": "Hello, World!", "payload": "Hello, World!",
"payloadType": "str", "payloadType": "str",
"x": 230, "x": 190,
"y": 400, "y": 260,
"wires": [ "wires": [
[ [
"402f3b7e.988014" "402f3b7e.988014"
@ -36,25 +36,25 @@
{ {
"id": "8e876a75.e9beb8", "id": "8e876a75.e9beb8",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "Write string to a file specied by filename property, the read from the file", "name": "Write string to a file specied by filename property, the read from the file",
"info": "File node can target file using `filename` property.", "info": "Write file node can target file using `filename` property.",
"x": 350, "x": 310,
"y": 320, "y": 180,
"wires": [] "wires": []
}, },
{ {
"id": "402f3b7e.988014", "id": "402f3b7e.988014",
"type": "file", "type": "file",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "", "filename": "",
"appendNewline": true, "appendNewline": true,
"createDir": false, "createDir": false,
"overwriteFile": "true", "overwriteFile": "true",
"encoding": "none", "encoding": "none",
"x": 390, "x": 350,
"y": 400, "y": 260,
"wires": [ "wires": [
[ [
"26e077d6.bbcd98" "26e077d6.bbcd98"
@ -64,7 +64,7 @@
{ {
"id": "97b6b6b2.a54b38", "id": "97b6b6b2.a54b38",
"type": "debug", "type": "debug",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"active": true, "active": true,
"tosidebar": true, "tosidebar": true,
@ -73,22 +73,22 @@
"complete": "false", "complete": "false",
"statusVal": "", "statusVal": "",
"statusType": "auto", "statusType": "auto",
"x": 770, "x": 730,
"y": 400, "y": 260,
"wires": [] "wires": []
}, },
{ {
"id": "26e077d6.bbcd98", "id": "26e077d6.bbcd98",
"type": "file in", "type": "file in",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"format": "utf8", "format": "utf8",
"chunk": false, "chunk": false,
"sendError": false, "sendError": false,
"encoding": "none", "encoding": "none",
"x": 580, "x": 540,
"y": 400, "y": 260,
"wires": [ "wires": [
[ [
"97b6b6b2.a54b38" "97b6b6b2.a54b38"
@ -98,21 +98,21 @@
{ {
"id": "85062297.da79", "id": "85062297.da79",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "↑read result from file", "name": "↑read result from file",
"info": "", "info": "",
"x": 590, "x": 550,
"y": 440, "y": 300,
"wires": [] "wires": []
}, },
{ {
"id": "7316c4fc.b1dcdc", "id": "7316c4fc.b1dcdc",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "↓write to file specified by filename property", "name": "↓write to file specified by filename property",
"info": "", "info": "",
"x": 500, "x": 460,
"y": 360, "y": 220,
"wires": [] "wires": []
} }
] ]

View File

@ -2,7 +2,7 @@
{ {
"id": "4ac00fb0.d5f52", "id": "4ac00fb0.d5f52",
"type": "inject", "type": "inject",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"props": [ "props": [
{ {
@ -20,8 +20,8 @@
"topic": "", "topic": "",
"payload": "", "payload": "",
"payloadType": "date", "payloadType": "date",
"x": 220, "x": 180,
"y": 600, "y": 220,
"wires": [ "wires": [
[ [
"542cc2f4.92857c" "542cc2f4.92857c"
@ -31,25 +31,25 @@
{ {
"id": "671f8295.0e6f6c", "id": "671f8295.0e6f6c",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "Delete a file", "name": "Delete a file",
"info": "File node can delete a file.", "info": "Write file node can delete a file.",
"x": 170, "x": 130,
"y": 540, "y": 160,
"wires": [] "wires": []
}, },
{ {
"id": "542cc2f4.92857c", "id": "542cc2f4.92857c",
"type": "file", "type": "file",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"appendNewline": true, "appendNewline": true,
"createDir": false, "createDir": false,
"overwriteFile": "delete", "overwriteFile": "delete",
"encoding": "none", "encoding": "none",
"x": 420, "x": 380,
"y": 600, "y": 220,
"wires": [ "wires": [
[ [
"a24da523.5babe8" "a24da523.5babe8"
@ -59,7 +59,7 @@
{ {
"id": "a24da523.5babe8", "id": "a24da523.5babe8",
"type": "debug", "type": "debug",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"active": true, "active": true,
"tosidebar": true, "tosidebar": true,
@ -68,18 +68,18 @@
"complete": "false", "complete": "false",
"statusVal": "", "statusVal": "",
"statusType": "auto", "statusType": "auto",
"x": 630, "x": 590,
"y": 600, "y": 220,
"wires": [] "wires": []
}, },
{ {
"id": "51157051.2f62", "id": "51157051.2f62",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "↓delete a file", "name": "↓delete a file",
"info": "", "info": "",
"x": 390, "x": 350,
"y": 560, "y": 180,
"wires": [] "wires": []
} }
] ]

View File

@ -2,8 +2,8 @@
{ {
"id": "e4ef1f5e.7cd82", "id": "e4ef1f5e.7cd82",
"type": "inject", "type": "inject",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "Base64 encoded string",
"props": [ "props": [
{ {
"p": "payload" "p": "payload"
@ -20,8 +20,8 @@
"topic": "", "topic": "",
"payload": "8J+YgA==", "payload": "8J+YgA==",
"payloadType": "str", "payloadType": "str",
"x": 220, "x": 200,
"y": 820, "y": 220,
"wires": [ "wires": [
[ [
"72b37cc8.177054" "72b37cc8.177054"
@ -31,25 +31,25 @@
{ {
"id": "f5997af4.5a9298", "id": "f5997af4.5a9298",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "Specify encoding of written data", "name": "Specify encoding of written data",
"info": "File node can specify encoding of data.", "info": "Write file node can specify encoding of data.",
"x": 230, "x": 170,
"y": 740, "y": 140,
"wires": [] "wires": []
}, },
{ {
"id": "72b37cc8.177054", "id": "72b37cc8.177054",
"type": "file", "type": "file",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"appendNewline": true, "appendNewline": true,
"createDir": false, "createDir": false,
"overwriteFile": "true", "overwriteFile": "true",
"encoding": "base64", "encoding": "base64",
"x": 400, "x": 420,
"y": 820, "y": 220,
"wires": [ "wires": [
[ [
"2da33ec.f45cac2" "2da33ec.f45cac2"
@ -59,7 +59,7 @@
{ {
"id": "2e814354.278c8c", "id": "2e814354.278c8c",
"type": "debug", "type": "debug",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"active": true, "active": true,
"tosidebar": true, "tosidebar": true,
@ -68,22 +68,22 @@
"complete": "false", "complete": "false",
"statusVal": "", "statusVal": "",
"statusType": "auto", "statusType": "auto",
"x": 790, "x": 810,
"y": 820, "y": 220,
"wires": [] "wires": []
}, },
{ {
"id": "2da33ec.f45cac2", "id": "2da33ec.f45cac2",
"type": "file in", "type": "file in",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "", "name": "",
"filename": "/tmp/hello.txt", "filename": "/tmp/hello.txt",
"format": "utf8", "format": "utf8",
"chunk": false, "chunk": false,
"sendError": false, "sendError": false,
"encoding": "none", "encoding": "none",
"x": 600, "x": 620,
"y": 820, "y": 220,
"wires": [ "wires": [
[ [
"2e814354.278c8c" "2e814354.278c8c"
@ -93,21 +93,21 @@
{ {
"id": "ec754c99.84bfd", "id": "ec754c99.84bfd",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "↓write string with base64 encoding", "name": "↓write string with base64 encoding",
"info": "", "info": "",
"x": 460, "x": 480,
"y": 780, "y": 180,
"wires": [] "wires": []
}, },
{ {
"id": "3e6704ff.4ce25c", "id": "3e6704ff.4ce25c",
"type": "comment", "type": "comment",
"z": "4b63452d.672afc", "z": "6312c0588348b2d4",
"name": "↑read result from file", "name": "↑read result from file",
"info": "", "info": "",
"x": 610, "x": 630,
"y": 860, "y": 260,
"wires": [] "wires": []
} }
] ]

View File

@ -499,7 +499,8 @@
"label": { "label": {
"type": "Typ", "type": "Typ",
"path": "Pfad", "path": "Pfad",
"url": "URL" "url": "URL",
"subprotocol": "Subprotokoll"
}, },
"listenon": "Lauschen (listen on)", "listenon": "Lauschen (listen on)",
"connectto": "Verbinden mit", "connectto": "Verbinden mit",

View File

@ -21,7 +21,7 @@
<dt>msg <span class="property-type">object</span></dt> <dt>msg <span class="property-type">object</span></dt>
<dd>A msg object containing information to populate the template.</dd> <dd>A msg object containing information to populate the template.</dd>
<dt class="optional">template <span class="property-type">string</span></dt> <dt class="optional">template <span class="property-type">string</span></dt>
<dd>A template to be populated from msg.payload. If not configured in the edit panel, <dd>A template to be populated from <code>msg.payload</code>. If not configured in the edit panel,
this can be set as a property of msg.</dd> this can be set as a property of msg.</dd>
</dl> </dl>
<h3>Outputs</h3> <h3>Outputs</h3>

View File

@ -60,5 +60,5 @@
for the next topic. for the next topic.
</p> </p>
<p><b>Note</b>: In rate limit mode the maximum queue depth can be set by a property in your <p><b>Note</b>: In rate limit mode the maximum queue depth can be set by a property in your
<i>settings.js</i> file. For example <code>nodeMessageBufferMaxLength: 1000,</code> <i>settings.js</i> file. For example <code>nodeMessageBufferMaxLength: 1000,</code></p>
</script> </script>

View File

@ -25,7 +25,7 @@
<h3>Details</h3> <h3>Details</h3>
<p>In RBE mode this node will block until the <code>msg.payload</code>, <p>In RBE mode this node will block until the <code>msg.payload</code>,
(or selected property) value is different to the previous one. (or selected property) value is different to the previous one.
If required it can ignore the intial value, so as not to send anything at start.</p> If required it can ignore the initial value, so as not to send anything at start.</p>
<p>The <a href="https://en.wikipedia.org/wiki/Deadband" target="_blank">Deadband</a> modes will block the incoming value <p>The <a href="https://en.wikipedia.org/wiki/Deadband" target="_blank">Deadband</a> modes will block the incoming value
<i>unless</i> its change is greater or greater-equal than &plusmn; the band gap away from a previous value.</p> <i>unless</i> its change is greater or greater-equal than &plusmn; the band gap away from a previous value.</p>
<p>The Narrowband modes will block the incoming value, <p>The Narrowband modes will block the incoming value,
@ -37,5 +37,5 @@
ignoring any values out of range, or the previous input value, which resets the set point, thus allowing ignoring any values out of range, or the previous input value, which resets the set point, thus allowing
gradual drift (deadband), or a step change (narrowband).</p> gradual drift (deadband), or a step change (narrowband).</p>
<p><b>Note:</b> This works on a per <code>msg.topic</code> basis, though this can be changed to another property if desired. <p><b>Note:</b> This works on a per <code>msg.topic</code> basis, though this can be changed to another property if desired.
This means that a single rbe node can handle multiple different topics at the same time.</p> This means that a single filter node can handle multiple different topics at the same time.</p>
</script> </script>

View File

@ -530,7 +530,8 @@
"label": { "label": {
"type": "Type", "type": "Type",
"path": "Path", "path": "Path",
"url": "URL" "url": "URL",
"subprotocol": "Subprotocol"
}, },
"listenon": "Listen on", "listenon": "Listen on",
"connectto": "Connect to", "connectto": "Connect to",
@ -579,7 +580,9 @@
"server": "Server", "server": "Server",
"return": "Return", "return": "Return",
"ms": "ms", "ms": "ms",
"chars": "chars" "chars": "chars",
"close": "Close",
"optional": "(optional)"
}, },
"type": { "type": {
"listen": "Listen on", "listen": "Listen on",
@ -596,7 +599,7 @@
"return": { "return": {
"timeout": "after a fixed timeout of", "timeout": "after a fixed timeout of",
"character": "when character received is", "character": "when character received is",
"number": "a fixed number of chars", "number": "after a fixed number of characters",
"never": "never - keep connection open", "never": "never - keep connection open",
"immed": "immediately - don't wait for reply" "immed": "immediately - don't wait for reply"
}, },
@ -616,11 +619,11 @@
"timeout": "timeout closed socket port __port__", "timeout": "timeout closed socket port __port__",
"cannot-listen": "unable to listen on port __port__, error: __error__", "cannot-listen": "unable to listen on port __port__, error: __error__",
"error": "error: __error__", "error": "error: __error__",
"socket-error": "socket error from __host__:__port__", "socket-error": "socket error from __host__:__port__",
"no-host": "Host and/or port not set", "no-host": "Host and/or port not set",
"connect-timeout": "connect timeout", "connect-timeout": "connect timeout",
"connect-fail": "connect failed" "connect-fail": "connect failed",
"bad-string": "failed to convert to string"
} }
}, },
"udp": { "udp": {

View File

@ -52,7 +52,7 @@
<dt class="optional">topic <span class="property-type">string|object|array</span></dt> <dt class="optional">topic <span class="property-type">string|object|array</span></dt>
<dd>For the <code>"subscribe"</code> and <code>"unsubscribe"</code> actions, this property <dd>For the <code>"subscribe"</code> and <code>"unsubscribe"</code> actions, this property
provides the topic. It can be set as either:<ul> provides the topic. It can be set as either:<ul>
<li>a String continaing the topic filter</li> <li>a String containing the topic filter</li>
<li>an Object containing <code>topic</code> and <code>qos</code> properties</li> <li>an Object containing <code>topic</code> and <code>qos</code> properties</li>
<li>an array of either strings or objects to handle multiple topics in one</li> <li>an array of either strings or objects to handle multiple topics in one</li>
</ul> </ul>

View File

@ -52,7 +52,7 @@
<dd>In case any redirects occurred while processing the request, this property is the final redirected url. <dd>In case any redirects occurred while processing the request, this property is the final redirected url.
Otherwise, the url of the original request.</dd> Otherwise, the url of the original request.</dd>
<dt>responseCookies <span class="property-type">object</span></dt> <dt>responseCookies <span class="property-type">object</span></dt>
<dd>If the response includes cookies, this propery is an object of name/value pairs for each cookie.</dd> <dd>If the response includes cookies, this property is an object of name/value pairs for each cookie.</dd>
<dt>redirectList <span class="property-type">array</span></dt> <dt>redirectList <span class="property-type">array</span></dt>
<dd>If the request was redirected one or more times, the accumulated information will be added to this property. `location` is the next redirect destination. `cookies` is the cookies returned from the redirect source.</dd> <dd>If the request was redirected one or more times, the accumulated information will be added to this property. `location` is the next redirect destination. `cookies` is the cookies returned from the redirect source.</dd>
</dl> </dl>

View File

@ -60,7 +60,7 @@
</p> </p>
<p>When operating in this mode, the node will not set the <code>msg.parts.count</code> <p>When operating in this mode, the node will not set the <code>msg.parts.count</code>
property as it does not know how many messages to expect in the stream. This property as it does not know how many messages to expect in the stream. This
means it cannot be used with the <b>join</b> node in its automatic mode</p> means it cannot be used with the <b>join</b> node in its automatic mode.</p>
</script> </script>
<script type="text/html" data-help-name="join"> <script type="text/html" data-help-name="join">

View File

@ -46,4 +46,6 @@
<code>{{global[store].名前}}</code>を用います。 <code>{{global[store].名前}}</code>を用います。
</p> </p>
<p><b>注: </b>デフォルトでは、<i>mustache</i>形式は置換対象のHTML要素をエスケープします。これを抑止するには<code>{{{三重}}}</code>括弧形式を使います。</p> <p><b>注: </b>デフォルトでは、<i>mustache</i>形式は置換対象のHTML要素をエスケープします。これを抑止するには<code>{{{三重}}}</code>括弧形式を使います。</p>
<p>もし、コンテンツの中で<code>{{ }}</code>を出力する必要がある場合は、テンプレートで使われる記号文字を変えることもできます。例えば、<code>[[ ]]</code>を代わりに用いるには、テンプレートの先頭に以下の行を追加します。</p>
<pre>{{=[[ ]]=}}</pre>
</script> </script>

View File

@ -25,11 +25,14 @@
<dt class="optional">reset</dt> <dt class="optional">reset</dt>
<dd>受信メッセージでこのプロパティを任意の値に設定すると、ノードが保持する全ての未送信メッセージをクリアします。</dd> <dd>受信メッセージでこのプロパティを任意の値に設定すると、ノードが保持する全ての未送信メッセージをクリアします。</dd>
<dt class="optional">flush</dt> <dt class="optional">flush</dt>
<dd>受信メッセージでこのプロパティを任意の値に設定すると、ノードが保持する全ての未送信メッセージを直ちに送信します。</dd> <dd>本プロパティに数値が設定されたメッセージを受信すると、直ちに指定された数のメッセージを送信します。もし他の型(例えば真偽型)が設定されている場合は、ノードが保持している全ての未送信メッセージを直ちに送信します。</dd>
<dt class="optional">toFront</dt>
<dd>流量制御モードにおいて、本プロパティに真偽型<code>true</code>が設定されたメッセージを受け取ると、キューの先頭に追加され、その後に送信されます。<code>msg.flush=1</code>と組み合わせて用いると、すぐに再送信できます。</dd>
</dl> </dl>
<h3>詳細</h3> <h3>詳細</h3>
<p>メッセージを遅延させるように設定する場合、遅延時間は固定値、範囲内の乱数値、メッセージ毎の動的な指定値のいずれかを指定できます。</p> <p>メッセージを遅延させるように設定する場合、遅延時間は固定値、範囲内の乱数値、メッセージ毎の動的な指定値のいずれかを指定できます。各メッセージは、到着時刻に基づいて、他のメッセージとは独立して遅延されます。</p>
<p>流量制御する場合、メッセージは指定した時間間隔内に分散して送信します。キューに残っているメッセージ数はノードのステータスに表示されます。受け取った中間メッセージを破棄することも可能です。</p> <p>流量制御する場合、メッセージは指定した時間間隔内に分散して送信します。キューに残っているメッセージ数はノードのステータスに表示されます。受け取った中間メッセージを破棄することも可能です。</p>
<p>流量値を上書きできるように設定されている場合、新しい流量値はすぐに適用されます。この流量値は、再度変更されるまで、本ノードがリセットされるまで、またはフローが再実行されるまで有効です。</p> <p>流量値を上書きできるように設定されている場合、新しい流量値はすぐに適用されます。この流量値は、再度変更されるまで、本ノードがリセットされるまで、またはフローが再実行されるまで有効です。</p>
<p>流量制御は全てのメッセージに適用することも、<code>msg.topic</code>値でグループ化して適用することも可能です。グループ化すると、中間メッセージは自動的に破棄されます。時間間隔毎に全てのトピックの最新メッセージを送信するか、次のトピックの最新メッセージを送信するかを指定できます。</p> <p>流量制御は全てのメッセージに適用することも、<code>msg.topic</code>値でグループ化して適用することも可能です。グループ化すると、中間メッセージは自動的に破棄されます。時間間隔毎に全てのトピックの最新メッセージを送信するか、次のトピックの最新メッセージを送信するかを指定できます。</p>
<p><b></b>: 流量制御モードでは、キューの大きさの最大値を<i>settings.js</i>ファイルのプロパティに設定できます。例えば、次の様な設定です。<code>nodeMessageBufferMaxLength: 1000,</code></p>
</script> </script>

View File

@ -27,5 +27,5 @@
<p>不感帯モードでは%による指定もサポートしています。入力と前の値の差分がX%より大きな場合に出力を行います。</p> <p>不感帯モードでは%による指定もサポートしています。入力と前の値の差分がX%より大きな場合に出力を行います。</p>
<p>狭帯域(narrowband)モードでは、前の値に対する差分が一定値より大きな場合に入力ペイロードをブロックします。このモードは、故障したセンサから発生する外れ値を無視する時などに有用です。</p> <p>狭帯域(narrowband)モードでは、前の値に対する差分が一定値より大きな場合に入力ペイロードをブロックします。このモードは、故障したセンサから発生する外れ値を無視する時などに有用です。</p>
<p>不感帯モードと狭帯域モードでは、以前の有効出力値、もしくは、以前の入力値との比較ができます。有効出力値を用いると範囲外の値を無視することが、入力値を用いると設定点がリセットされるため漸次的変化(不感帯モード)もしくは段階的変化(狭帯域モード)が可能です。</p> <p>不感帯モードと狭帯域モードでは、以前の有効出力値、もしくは、以前の入力値との比較ができます。有効出力値を用いると範囲外の値を無視することが、入力値を用いると設定点がリセットされるため漸次的変化(不感帯モード)もしくは段階的変化(狭帯域モード)が可能です。</p>
<p><b>注:</b> このノードは<code>msg.topic</code>毎に動作します。そのため、ひとつのrbeノードで複数の異なるトピックを同時に扱うことができます。</p> <p><b>注:</b> このノードは<code>msg.topic</code>毎に動作します。そのため、ひとつのfilterードで複数の異なるトピックを同時に扱うことができます。</p>
</script> </script>

View File

@ -530,7 +530,8 @@
"label": { "label": {
"type": "種類", "type": "種類",
"path": "パス", "path": "パス",
"url": "URL" "url": "URL",
"subprotocol": "サブプロトコル"
}, },
"listenon": "待ち受け", "listenon": "待ち受け",
"connectto": "接続", "connectto": "接続",

View File

@ -26,11 +26,46 @@
<dd>0: 最大1度到着, 1: 一度以上到着, 2: 1度のみ到着</dd> <dd>0: 最大1度到着, 1: 一度以上到着, 2: 1度のみ到着</dd>
<dt>retain <span class="property-type">真偽値</span></dt> <dt>retain <span class="property-type">真偽値</span></dt>
<dd>真の場合、メッセージを保持。メッセージが古い値の場合があります。</dd> <dd>真の場合、メッセージを保持。メッセージが古い値の場合があります。</dd>
<dt class="optional">responseTopic <span class="property-type">文字列</span></dt>
<dd><b>MQTTv5</b>: メッセージのMQTT応答トピック</dd>
<dt class="optional">correlationData <span class="property-type">バッファ</span></dt>
<dd><b>MQTTv5</b>: メッセージの相関データ</dd>
<dt class="optional">contentType <span class="property-type">文字列</span></dt>
<dd><b>MQTTv5</b>: ペイロードのコンテントタイプ</dd>
<dt class="optional">userProperties <span class="property-type">オブジェクト</span></dt>
<dd><b>MQTTv5</b>: メッセージのユーザプロパティ</dd>
<dt class="optional">messageExpiryInterval <span class="property-type">数値</span></dt>
<dd><b>MQTTv5</b>: 秒単位のメッセージの有効期限</dd>
</dl> </dl>
<h3>詳細</h3> <h3>詳細</h3>
<p>購読トピックにはMQTTのワイルドカード(+: 1レベル, #: 複数レベル)を含めることができます。</p> <p>購読トピックにはMQTTのワイルドカード(+: 1レベル, #: 複数レベル)を含めることができます。</p>
<p>このードの利用のためには、MQTTブローカへの接続設定が必要です。この設定は鉛筆アイコンをクリックすることで行えます。</p> <p>このードの利用のためには、MQTTブローカへの接続設定が必要です。この設定は鉛筆アイコンをクリックすることで行えます。</p>
<p>MQTT(inおよびout)ノードはブローカへの接続設定を必要に応じて共有できます。</p> <p>MQTT(inおよびout)ノードはブローカへの接続設定を必要に応じて共有できます。</p>
<h4>動的購読</h4>
ードは、MQTTの接続と購読を動的に制御するよう設定できます。有効にすると、本ードの入力にメッセージを渡すことで制御できます。
<h3>入力</h3>
<p>これらは、動的購読が設定されている場合のみ適用されます。</p>
<dl class="message-properties">
<dt>action <span class="property-type">文字列</span></dt>
<dd>本ノードが行う動作の名前。利用可能な動作は<code>"connect"</code><code>"disconnect"</code><code>"subscribe"</code><code>"unsubscribe"</code>です。</dd>
<dt class="optional">topic <span class="property-type">文字列|オブジェクト|配列</span></dt>
<dd><code>"subscribe"</code><code>"unsubscribe"</code>の動作に対して、本プロパティはトピックを提供します。次のいずれかを設定できます:<ul>
<li>トピックフィルターを含む文字列</li>
<li><code>topic</code><code>qos</code>プロパティを持つオブジェクト</li>
<li>複数のトピックを扱う文字列やオブジェクトの配列</li>
</ul>
</dd>
<dt class="optional">broker <span class="property-type">broker</span> </dt>
<dd><code>"connect"</code>の動作に対して、本プロパティは次の様な個々のブローカ設定を上書きします: <ul>
<li><code>broker</code></li>
<li><code>port</code></li>
<li><code>url</code> - 完全な接続URLを提供するために、brokerとportを上書き</li>
<li><code>username</code></li>
<li><code>password</code></li>
</ul>
<p>本プロパティが設定され既にブローカが接続されている場合、<code>force</code>プロパティを設定しない限り、エラーがログに記録されます。設定された場合はブローカから切断され、新しい設定を適用して再接続します。</p>
</dd>
</dl>
</script> </script>
<script type="text/html" data-help-name="mqtt out"> <script type="text/html" data-help-name="mqtt out">
@ -39,15 +74,24 @@
<dl class="message-properties"> <dl class="message-properties">
<dt>payload <span class="property-type">文字列 | バッファ</span></dt> <dt>payload <span class="property-type">文字列 | バッファ</span></dt>
<dd>発行するペイロード。プロパティが設定されていない場合には、メッセージは送信されません。空のメッセージを送信するには、プロパティに空文字列を設定します。</dd> <dd>発行するペイロード。プロパティが設定されていない場合には、メッセージは送信されません。空のメッセージを送信するには、プロパティに空文字列を設定します。</dd>
<dt class="optional">topic <span class="property-type">文字列</span></dt> <dt class="optional">topic <span class="property-type">文字列</span></dt>
<dd>発行対象のMQTTトピック</dd> <dd>発行対象のMQTTトピック</dd>
<dt class="optional">qos <span class="property-type">数値</span></dt> <dt class="optional">qos <span class="property-type">数値</span></dt>
<dd>0: 最大一度到着, 1: 一度以上到着, 2: 一度のみ到着。デフォルトは0です。</dd> <dd>0: 最大一度到着, 1: 一度以上到着, 2: 一度のみ到着。デフォルトは0です。</dd>
<dt class="optional">retain <span class="property-type">真偽値</span></dt> <dt class="optional">retain <span class="property-type">真偽値</span></dt>
<dd>真の場合、メッセージをブローカに保持します。デフォルトは偽です。</dd> <dd>真の場合、メッセージをブローカに保持します。デフォルトは偽です。</dd>
<dt class="optional">responseTopic <span class="property-type">文字列</span></dt>
<dd><b>MQTTv5</b>: メッセージのMQTT応答トピック</dd>
<dt class="optional">correlationData <span class="property-type">バッファ</span></dt>
<dd><b>MQTTv5</b>: メッセージの相関データ</dd>
<dt class="optional">contentType <span class="property-type">文字列</span></dt>
<dd><b>MQTTv5</b>: ペイロードのコンテントタイプ</dd>
<dt class="optional">userProperties <span class="property-type">オブジェクト</span></dt>
<dd><b>MQTTv5</b>: メッセージのユーザプロパティ</dd>
<dt class="optional">messageExpiryInterval <span class="property-type">数値</span></dt>
<dd><b>MQTTv5</b>: 秒単位のメッセージの有効期限</dd>
<dt class="optional">topicAlias <span class="property-type">数値</span></dt>
<dd><b>MQTTv5</b>: 使用するMQTTトピックエイリアス</dd>
</dl> </dl>
<h3>詳細</h3> <h3>詳細</h3>
<p><code>msg.payload</code>を発行するメッセージのペイロードとして用います。ペイロードがオブジェクトの場合、送信の際にJSON文字列に変換します。ペイロードがバイナリバッファの場合、そのまま送信します。</p> <p><code>msg.payload</code>を発行するメッセージのペイロードとして用います。ペイロードがオブジェクトの場合、送信の際にJSON文字列に変換します。ペイロードがバイナリバッファの場合、そのまま送信します。</p>
@ -55,6 +99,24 @@
<p>同様に、QoSとretainもードの設定、もしくは、ードの設定が空の場合には、それぞれ<code>msg.qos</code>および<code>msg.retain</code>で指定できます。以前ブローカに保存したトピックをクリアするには、retainフラグを設定して当該トピックに空のメッセージを発行します。</p> <p>同様に、QoSとretainもードの設定、もしくは、ードの設定が空の場合には、それぞれ<code>msg.qos</code>および<code>msg.retain</code>で指定できます。以前ブローカに保存したトピックをクリアするには、retainフラグを設定して当該トピックに空のメッセージを発行します。</p>
<p>このードの利用のためには、MQTTブローカへの接続設定が必要です。この設定は鉛筆アイコンをクリックすることで行えます。</p> <p>このードの利用のためには、MQTTブローカへの接続設定が必要です。この設定は鉛筆アイコンをクリックすることで行えます。</p>
<p>MQTT(inおよびout)ノードはブローカへの接続設定を必要に応じて共有できます。</p> <p>MQTT(inおよびout)ノードはブローカへの接続設定を必要に応じて共有できます。</p>
<h4>動的制御</h4>
本ノードによって接続を動的に制御できます。本ノードが以下の制御メッセージのいずれかを受け取った際は、ペイロードと同じ様にパブリッシュされることはありません。
<h3>入力</h3>
<dl class="message-properties">
<dt>action <span class="property-type">文字列</span></dt>
<dd>本ノードが行う動作の名前。利用可能な動作は<code>"connect"</code><code>"disconnect"</code><code>"subscribe"</code><code>"unsubscribe"</code>です。</dd>
<dt class="optional">broker <span class="property-type">broker</span> </dt>
<dd><code>"connect"</code>の動作に対して、本プロパティは次の様な個々のブローカ設定を上書きします: <ul>
<li><code>broker</code></li>
<li><code>port</code></li>
<li><code>url</code> - 完全な接続URLを提供するために、brokerとportを上書き</li>
<li><code>username</code></li>
<li><code>password</code></li>
</ul>
<p>本プロパティが設定され既にブローカが接続されている場合、<code>force</code>プロパティを設定しない限り、エラーがログに記録されます。設定された場合はブローカから切断され、新しい設定を適用して再接続します。</p>
</dd>
</dl>
</script> </script>
<script type="text/html" data-help-name="mqtt-broker"> <script type="text/html" data-help-name="mqtt-broker">
@ -70,5 +132,4 @@
<h4>WebSocket</h4> <h4>WebSocket</h4>
<p>WebSocketによる接続を行うように設定できます。WebSocketを利用するには、サーバフィールドに接続先のURIを完全な形式で記述します。以下に例を示します。</p> <p>WebSocketによる接続を行うように設定できます。WebSocketを利用するには、サーバフィールドに接続先のURIを完全な形式で記述します。以下に例を示します。</p>
<pre>ws://example.com:4000/mqtt</pre> <pre>ws://example.com:4000/mqtt</pre>
</script> </script>

View File

@ -36,7 +36,7 @@
<h3>詳細</h3> <h3>詳細</h3>
<p>「列名」にカラム名のリストを指定することができます。CSVからオブジェクトに変換を行う際、カラム名をプロパティ名として使用します。「列名」の代わりに、CSVデータの1行目にカラム名を含めることもできます。</p> <p>「列名」にカラム名のリストを指定することができます。CSVからオブジェクトに変換を行う際、カラム名をプロパティ名として使用します。「列名」の代わりに、CSVデータの1行目にカラム名を含めることもできます。</p>
<p>CSVへの変換を行う際には、オブジェクトから取り出すべきプロパティとその順序を「列名」を参照して決めます。</p> <p>CSVへの変換を行う際には、オブジェクトから取り出すべきプロパティとその順序を「列名」を参照して決めます。</p>
<p>列名がない場合、本ノードは<code>msg.columns</code>プロパティの単純なコンマ区切りリストを使用して、何を抽出するかを決定します。もしそれが存在しない場合、すべてのオブジェクトプロパティを見つけた順序で出力します。</p> <p>列名がない場合、本ノードは<code>msg.columns</code>プロパティの単純なコンマ区切りリストを使用して、何をどの順序で抽出するかを決定します。もし存在しない場合、すべてのオブジェクトプロパティを見つけた順序で出力します。</p>
<p>入力が配列の場合には、「列名」はカラム名を表す行の出力指定がされた場合だけ用います。</p> <p>入力が配列の場合には、「列名」はカラム名を表す行の出力指定がされた場合だけ用います。</p>
<p>「数値を変換する」オプションがチェックされている場合、文字列型の数値が数値として返されます。つまり「1,"1.5",2」の真ん中の値が数値になります。</p> <p>「数値を変換する」オプションがチェックされている場合、文字列型の数値が数値として返されます。つまり「1,"1.5",2」の真ん中の値が数値になります。</p>
<p>「空の文字を含む」オプションがチェックされている場合、空の文字列が結果に返されます。つまり「"1","",3」の真ん中の値が空の文字列になります。</p> <p>「空の文字を含む」オプションがチェックされている場合、空の文字列が結果に返されます。つまり「"1","",3」の真ん中の値が空の文字列になります。</p>

View File

@ -52,7 +52,6 @@
<p>このモードで処理する際には、メッセージ数を予め知ることができないため、<code>msg.parts.count</code>プロパティは設定されません。従って、<b>join</b>ノードの「自動モード」と組み合わせることはできません。</p> <p>このモードで処理する際には、メッセージ数を予め知ることができないため、<code>msg.parts.count</code>プロパティは設定されません。従って、<b>join</b>ノードの「自動モード」と組み合わせることはできません。</p>
</script> </script>
<script type="text/html" data-help-name="join"> <script type="text/html" data-help-name="join">
<p>メッセージ列を結合して一つのメッセージにします。</p> <p>メッセージ列を結合して一つのメッセージにします。</p>
<p>メッセージの結合には次の3つのモードが利用できます。</p> <p>メッセージの結合には次の3つのモードが利用できます。</p>
@ -80,6 +79,10 @@
</dd> </dd>
<dt class="optional">complete</dt> <dt class="optional">complete</dt>
<dd>設定されている場合、本ードはペイロードを追加し、保持しているメッセージを送信します。ペイロードを追加したくない場合は、msgから削除してください。</dd> <dd>設定されている場合、本ードはペイロードを追加し、保持しているメッセージを送信します。ペイロードを追加したくない場合は、msgから削除してください。</dd>
<dt class="optional">reset</dt>
<dd>設定されている場合、本ノードは部分的に完成したメッセージを送信せず、削除します。</dd>
<dt class="optional">restartTimeout</dt>
<dd>設定されている場合、本ノードにタイムアウトが設定され、そのタイムアウトを用いて処理が再開されます。</dd>
</dl> </dl>
<h3>詳細</h3> <h3>詳細</h3>
@ -96,7 +99,7 @@
</ul> </ul>
<p>出力メッセージのその他のプロパティはメッセージを送信する直前のメッセージをコピーします。</p> <p>出力メッセージのその他のプロパティはメッセージを送信する直前のメッセージをコピーします。</p>
<p><i>合計値</i>」には出力メッセージを送信する前に受信すべきメッセージ数を指定します。オブジェクト出力の場合、この合計値に達すると後続メッセージの到着毎にメッセージを出力するように設定することもできます。</p> <p><i>合計値</i>」には出力メッセージを送信する前に受信すべきメッセージ数を指定します。オブジェクト出力の場合、この合計値に達すると後続メッセージの到着毎にメッセージを出力するように設定することもできます。</p>
<p><i></i>」には新規メッセージを送信するまでの経過時間を設定します。</p> <p><i></i>」には新規メッセージを送信するまでの経過時間を設定します。<code>msg.restartTimeout</code>プロパティを設定したメッセージを渡すことで、指定した時間で再開できます。</p>
<p><code>msg.complete</code>プロパティを設定したメッセージを受信すると、出力メッセージを送信します。この時、メッセージ列の数をリセットします。</p> <p><code>msg.complete</code>プロパティを設定したメッセージを受信すると、出力メッセージを送信します。この時、メッセージ列の数をリセットします。</p>
<p><code>msg.reset</code>プロパティを設定したメッセージを受信すると、部分的に受信済みのメッセージを破棄します。これらのメッセージは送信されません。この時、メッセージ列の数をリセットします。</p> <p><code>msg.reset</code>プロパティを設定したメッセージを受信すると、部分的に受信済みのメッセージを破棄します。これらのメッセージは送信されません。この時、メッセージ列の数をリセットします。</p>

View File

@ -433,7 +433,8 @@
"label": { "label": {
"type": "종류", "type": "종류",
"path": "패스", "path": "패스",
"url": "URL" "url": "URL",
"subprotocol": "서브 프로토콜"
}, },
"listenon": "대기", "listenon": "대기",
"connectto": "접속", "connectto": "접속",

View File

@ -461,7 +461,8 @@
"label": { "label": {
"type": "Тип", "type": "Тип",
"path": "Путь", "path": "Путь",
"url": "URL" "url": "URL",
"subprotocol": "Подпротокол"
}, },
"listenon": "Слушать на ...", "listenon": "Слушать на ...",
"connectto": "Присоединиться к ...", "connectto": "Присоединиться к ...",

View File

@ -454,7 +454,8 @@
"label": { "label": {
"type": "类型", "type": "类型",
"path": "路径", "path": "路径",
"url": "URL" "url": "URL",
"subprotocol": "子协议"
}, },
"listenon": "监听", "listenon": "监听",
"connectto": "连接", "connectto": "连接",

View File

@ -458,7 +458,8 @@
"label": { "label": {
"type": "類型", "type": "類型",
"path": "路徑", "path": "路徑",
"url": "URL" "url": "URL",
"subprotocol": "子协议"
}, },
"listenon": "監聽", "listenon": "監聽",
"connectto": "連接", "connectto": "連接",

View File

@ -15,10 +15,10 @@
} }
], ],
"dependencies": { "dependencies": {
"acorn": "8.6.0", "acorn": "8.7.0",
"acorn-walk": "8.2.0", "acorn-walk": "8.2.0",
"ajv": "8.8.2", "ajv": "8.8.2",
"body-parser": "1.19.0", "body-parser": "1.19.1",
"cheerio": "1.0.0-rc.10", "cheerio": "1.0.0-rc.10",
"content-type": "1.0.4", "content-type": "1.0.4",
"cookie-parser": "1.4.6", "cookie-parser": "1.4.6",
@ -36,7 +36,7 @@
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.14.1", "js-yaml": "3.14.1",
"media-typer": "1.1.0", "media-typer": "1.1.0",
"mqtt": "4.2.8", "mqtt": "4.3.4",
"multer": "1.4.3", "multer": "1.4.3",
"mustache": "4.2.0", "mustache": "4.2.0",
"on-headers": "1.0.2", "on-headers": "1.0.2",

View File

@ -21,6 +21,6 @@
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"semver": "7.3.5", "semver": "7.3.5",
"tar": "6.1.11", "tar": "6.1.11",
"uglify-js": "3.14.4" "uglify-js": "3.14.5"
} }
} }

View File

@ -83,6 +83,7 @@ function createNode(flow,config) {
} }
} }
try { try {
Object.defineProperty(conf,'_module', {value: typeRegistry.getNodeInfo(type), enumerable: false, writable: true })
Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true }) Object.defineProperty(conf,'_flow', {value: flow, enumerable: false, writable: true })
newNode = new nodeTypeConstructor(conf); newNode = new nodeTypeConstructor(conf);
} catch (err) { } catch (err) {

View File

@ -59,6 +59,9 @@ function Node(n) {
// which we can tolerate as they are the same object. // which we can tolerate as they are the same object.
Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true }) Object.defineProperty(this,'_flow', {value: n._flow, enumerable: false, writable: true })
} }
if (n._module) {
Object.defineProperty(this,'_module', {value: n._module, enumerable: false, writable: true })
}
this.updateWires(n.wires); this.updateWires(n.wires);
} }
@ -496,7 +499,12 @@ function log_helper(self, level, msg) {
if (self.name) { if (self.name) {
o.name = self.name; o.name = self.name;
} }
self._flow.log(o); // See https://github.com/node-red/node-red/issues/3327
try {
self._flow.log(o);
} catch(err) {
logUnexpectedError(self, err)
}
} }
/** /**
* Log an INFO level message * Log an INFO level message
@ -576,4 +584,59 @@ Node.prototype.status = function(status) {
this._flow.handleStatus(this,status); this._flow.handleStatus(this,status);
}; };
function inspectObject(flow) {
try {
let properties = new Set()
let currentObj = flow
do {
if (!Object.getPrototypeOf(currentObj)) { break }
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))
} while ((currentObj = Object.getPrototypeOf(currentObj)))
let propList = [...properties.keys()].map(item => `${item}[${(typeof flow[item])[0]}]`)
propList.sort();
let result = [];
let line = "";
while (propList.length > 0) {
let prop = propList.shift()
if (line.length+prop.length > 80) {
result.push(line)
line = "";
} else {
line += " "+prop
}
}
if (line.length > 0) {
result.push(line);
}
return result.join("\n ")
} catch(err) {
return "Failed to capture object properties: "+err.toString()
}
}
function logUnexpectedError(node, error) {
let moduleInfo = node._module?`${node._module.module}@${node._module.version}`:"undefined"
Log.error(`
********************************************************************
Unexpected Node Error
${error.stack}
Node:
Type: ${node.type}
Module: ${moduleInfo}
ID: ${node._alias||node.id}
Properties:
${inspectObject(node)}
Flow: ${node._flow?node._flow.path:'undefined'}
Type: ${node._flow?node._flow.TYPE:'undefined'}
Properties:
${node._flow?inspectObject(node._flow):'undefined'}
Please report this issue, including the information logged above:
https://github.com/node-red/node-red/issues/
********************************************************************
`)
}
module.exports = Node; module.exports = Node;

View File

@ -20,7 +20,7 @@
"@node-red/util": "2.2.0-beta.1", "@node-red/util": "2.2.0-beta.1",
"async-mutex": "0.3.2", "async-mutex": "0.3.2",
"clone": "2.1.2", "clone": "2.1.2",
"express": "4.17.1", "express": "4.17.2",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"json-stringify-safe": "5.0.1" "json-stringify-safe": "5.0.1"
} }

View File

@ -32,8 +32,14 @@ function wrapEventFunction(obj,func) {
return function(eventName, listener) { return function(eventName, listener) {
if (deprecatedEvents.hasOwnProperty(eventName)) { if (deprecatedEvents.hasOwnProperty(eventName)) {
const log = require("@node-red/util").log; const log = require("@node-red/util").log;
const stack = (new Error().stack).split("\n")[2].split("(")[1].slice(0,-1);
log.warn(`[RED.events] Deprecated use of "${eventName}" event from "${stack}". Use "${deprecatedEvents[eventName]}" instead.`) const stack = (new Error().stack).split("\n");
let location = "(unknown)"
// See https://github.com/node-red/node-red/issues/3292
if (stack.length > 2) {
location = stack[2].split("(")[1].slice(0,-1);
}
log.warn(`[RED.events] Deprecated use of "${eventName}" event from "${location}". Use "${deprecatedEvents[eventName]}" instead.`)
} }
return events["_"+func].call(events,eventName,listener) return events["_"+func].call(events,eventName,listener)
} }

View File

@ -686,7 +686,7 @@ function prepareJSONataExpression(value,node) {
return moment(arg1, arg2, arg3, arg4); return moment(arg1, arg2, arg3, arg4);
}); });
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>'); expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value); expr._legacyMode = /(^|[^a-zA-Z0-9_'".])msg([^a-zA-Z0-9_'"]|$)/.test(value);
expr._node = node; expr._node = node;
return expr; return expr;
} }

View File

@ -16,7 +16,7 @@
], ],
"dependencies": { "dependencies": {
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"i18next": "21.5.4", "i18next": "21.6.6",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.8.5", "jsonata": "1.8.5",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",

View File

@ -37,7 +37,7 @@
"@node-red/nodes": "2.2.0-beta.1", "@node-red/nodes": "2.2.0-beta.1",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"express": "4.17.1", "express": "4.17.2",
"fs-extra": "10.0.0", "fs-extra": "10.0.0",
"node-red-admin": "^2.2.1", "node-red-admin": "^2.2.1",
"nopt": "5.0.0", "nopt": "5.0.0",

View File

@ -425,7 +425,7 @@ describe('rbe node', function() {
}); });
it('should not send output if x away or greater from original value (narrowbandEq)', function(done) { it('should not send output if x away or greater from original value (narrowbandEq)', function(done) {
var flow = [{"id":"n1", "type":"rbe", func:"narrowbandEq", gap:"10", inout:"out", wires:[["n2"]] }, var flow = [{"id":"n1", "type":"rbe", func:"narrowbandEq", gap:"10", inout:"out", start:"1", wires:[["n2"]] },
{id:"n2", type:"helper"} ]; {id:"n2", type:"helper"} ];
helper.load(testNode, flow, function() { helper.load(testNode, flow, function() {
var n1 = helper.getNode("n1"); var n1 = helper.getNode("n1");
@ -445,6 +445,7 @@ describe('rbe node', function() {
done(); done();
} }
}); });
n1.emit("input", {payload:100});
n1.emit("input", {payload:0}); n1.emit("input", {payload:0});
n1.emit("input", {payload:10}); n1.emit("input", {payload:10});
n1.emit("input", {payload:5}); n1.emit("input", {payload:5});

View File

@ -366,6 +366,18 @@ describe('websocket Node', function() {
}); });
}); });
it('should handle protocol property', function(done) {
var flow = [
{ id: "server", type: "websocket-listener", path: "/ws" },
{ id: "n1", type: "websocket-client", path: getWsUrl("/ws") },
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), subprotocol: "testprotocol1, testprotocol2" }];
helper.load(websocketNode, flow, function() {
helper.getNode("n1").should.have.property("subprotocol", []);
helper.getNode("n2").should.have.property("subprotocol", ["testprotocol1","testprotocol2"]);
done();
});
});
it('should connect to server', function(done) { it('should connect to server', function(done) {
var flow = [ var flow = [
{ id: "server", type: "websocket-listener", path: "/ws" }, { id: "server", type: "websocket-listener", path: "/ws" },
@ -378,6 +390,18 @@ describe('websocket Node', function() {
}); });
}); });
it('should initiate with subprotocol', function(done) {
var flow = [
{ id: "server", type: "websocket-listener", path: "/ws" },
{ id: "n2", type: "websocket-client", path: getWsUrl("/ws"), subprotocol: "testprotocol" }];
helper.load(websocketNode, flow, function() {
getSocket('server').on('connection', function (sock) {
sock.should.have.property("protocol", "testprotocol")
done();
});
});
});
it('should close on delete', function(done) { it('should close on delete', function(done) {
var flow = [ var flow = [
{ id: "server", type: "websocket-listener", path: "/ws" }, { id: "server", type: "websocket-listener", path: "/ws" },

View File

@ -84,9 +84,17 @@ describe('TCP Request Node', function() {
n2.on("input", msg => { n2.on("input", msg => {
try { try {
if (typeof result === 'object') { if (typeof result === 'object') {
msg.should.have.properties(Object.assign({}, result, {payload: Buffer.from(result.payload)})); if (flow[0].ret === "string") {
msg.should.have.properties(Object.assign({}, result, {payload: result.payload}));
} else {
msg.should.have.properties(Object.assign({}, result, {payload: Buffer.from(result.payload)}));
}
} else { } else {
msg.should.have.property('payload', Buffer.from(result)); if (flow[0].ret === "string") {
msg.should.have.property('payload', result);
} else {
msg.should.have.property('payload', Buffer.from(result));
}
} }
done(); done();
} catch(err) { } catch(err) {
@ -245,10 +253,41 @@ describe('TCP Request Node', function() {
}, done); }, done);
}); });
it('should send & receive, then keep connection, and not split return strings', function(done) {
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", ret:"string", newline:"", wires:[["n2"]] },
{id:"n2", type:"helper"}];
testTCPMany(flow, [{
payload: "foo",
topic: 'boo'
}, {
payload: "bar<A>\nfoo",
topic: 'boo'
}], {
payload: "ACK:foobar<A>\nfoo",
topic: 'boo'
}, done);
});
it('should send & receive, then keep connection, and split return strings', function(done) {
var flow = [{id:"n1", type:"tcp request", server:"localhost", port:port, out:"sit", ret:"string", newline:"<A>\\n", wires:[["n2"]] },
{id:"n2", type:"helper"}];
testTCPMany(flow, [{
payload: "foo",
topic: 'boo'
}, {
payload: "bar<A>\nfoo",
topic: 'boo'
}], {
payload: "ACK:foobar<A>",
topic: 'boo'
}, done);
});
it('should send & recv data to/from server:port from msg', function(done) { it('should send & recv data to/from server:port from msg', function(done) {
var flow = [{id:"n1", type:"tcp request", server:"", port:"", out:"time", splitc: "0", wires:[["n2"]] }, var flow = [{id:"n1", type:"tcp request", server:"", port:"", out:"time", splitc: "0", wires:[["n2"]] },
{id:"n2", type:"helper"}]; {id:"n2", type:"helper"}];
testTCPMany(flow, [{ testTCPMany(flow, [
{
payload: "f", payload: "f",
host: "localhost", host: "localhost",
port: port port: port

View File

@ -50,6 +50,24 @@ describe('JSON node', function() {
}); });
}); });
it('should convert a buffer of a valid json string to a javascript object', function(done) {
var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
msg.should.have.property('topic', 'bar');
msg.payload.should.have.property('employees');
msg.payload.employees[0].should.have.property('firstName', 'John');
msg.payload.employees[0].should.have.property('lastName', 'Smith');
done();
});
var jsonString = Buffer.from('{"employees":[{"firstName":"John", "lastName":"Smith"}]}');
jn1.receive({payload:jsonString,topic: "bar"});
});
});
it('should convert a javascript object to a json string', function(done) { it('should convert a javascript object to a json string', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];
@ -166,29 +184,55 @@ describe('JSON node', function() {
}); });
}); });
it('should log an error if asked to parse something thats not json or js', function(done) { it('should log an error if asked to parse an invalid json string in a buffer', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, var flow = [{id:"jn1",type:"json",action:"obj",wires:[["jn2"]]},
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() { helper.load(jsonNode, flow, function() {
var jn1 = helper.getNode("jn1"); try {
var jn2 = helper.getNode("jn2"); var jn1 = helper.getNode("jn1");
setTimeout(function() { var jn2 = helper.getNode("jn2");
try { jn1.receive({payload:Buffer.from('{"name":foo}'),topic: "bar"});
var logEvents = helper.log().args.filter(function(evt) { setTimeout(function() {
return evt[0].type == "json"; try {
}); var logEvents = helper.log().args.filter(function(evt) {
logEvents.should.have.length(1); return evt[0].type == "json";
logEvents[0][0].should.have.a.property('msg'); });
logEvents[0][0].msg.toString().should.eql('json.errors.dropped-object'); logEvents.should.have.length(1);
done(); logEvents[0][0].should.have.a.property('msg');
} catch(err) { logEvents[0][0].msg.should.startWith("Unexpected token o");
done(err); logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
} done();
},50); } catch(err) { done(err) }
jn1.receive({payload:Buffer.from("a")}); },20);
} catch(err) {
done(err);
}
}); });
}); });
// it('should log an error if asked to parse something thats not json or js and not in force object mode', function(done) {
// var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
// {id:"jn2", type:"helper"}];
// helper.load(jsonNode, flow, function() {
// var jn1 = helper.getNode("jn1");
// var jn2 = helper.getNode("jn2");
// setTimeout(function() {
// try {
// var logEvents = helper.log().args.filter(function(evt) {
// return evt[0].type == "json";
// });
// logEvents.should.have.length(1);
// logEvents[0][0].should.have.a.property('msg');
// logEvents[0][0].msg.toString().should.eql('json.errors.dropped');
// done();
// } catch(err) {
// done(err);
// }
// },50);
// jn1.receive({payload:Buffer.from("abcd")});
// });
// });
it('should pass straight through if no payload set', function(done) { it('should pass straight through if no payload set', function(done) {
var flow = [{id:"jn1",type:"json",wires:[["jn2"]]}, var flow = [{id:"jn1",type:"json",wires:[["jn2"]]},
{id:"jn2", type:"helper"}]; {id:"jn2", type:"helper"}];