mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
137 Commits
update-xml
...
4282-dirty
Author | SHA1 | Date | |
---|---|---|---|
|
2137211d42 | ||
|
f719e5ad49 | ||
|
20557f20db | ||
|
054d6870d5 | ||
|
04aa4897fe | ||
|
72b22a4845 | ||
|
3fd2b5f4e1 | ||
|
8a4128defb | ||
|
97470e94f1 | ||
|
0dd771351d | ||
|
ec86ec188b | ||
|
2b01a3fcd3 | ||
|
4d3e3a73fd | ||
|
1ffef393c2 | ||
|
8b7b3e22d7 | ||
|
877aa75e4e | ||
|
d21c0758b1 | ||
|
21be329008 | ||
|
e7b27ce7fb | ||
|
fb2c0e5441 | ||
|
29898ea68f | ||
|
9dcb8a729c | ||
|
b66237efa8 | ||
|
6d2f855227 | ||
|
638aa0372b | ||
|
d5baa402c8 | ||
|
ec7e594ec1 | ||
|
3fad690d1e | ||
|
15973768e2 | ||
|
0d73a4b013 | ||
|
50f11faf1f | ||
|
cfb7406fb8 | ||
|
9008f063c3 | ||
|
368ac4ed5c | ||
|
6ac2e703a6 | ||
|
cd9a5f112a | ||
|
f3847a17f3 | ||
|
1fa4aaf706 | ||
|
db108a37cf | ||
|
a3e41d4f35 | ||
|
80e33489d9 | ||
|
271b1327c7 | ||
|
5d990ff4c5 | ||
|
69aacc6256 | ||
|
a5066d529f | ||
|
5bf034f3c1 | ||
|
0743ff371c | ||
|
7481b78b16 | ||
|
bd589aa140 | ||
|
8fa1d4def5 | ||
|
fe3626035c | ||
|
c4019bd91d | ||
|
18e1b670ca | ||
|
cd76c934b6 | ||
|
db98641a32 | ||
|
59745fec0b | ||
|
8bcaea7830 | ||
|
56ed32e4a1 | ||
|
3209777aba | ||
|
11f9ad8ca3 | ||
|
26fc942c79 | ||
|
f196493402 | ||
|
1c5fdb6ab6 | ||
|
3ed530ed9e | ||
|
8db2972288 | ||
|
51a0b68d8e | ||
|
2fdbe12a7f | ||
|
2448e137c8 | ||
|
33899763ef | ||
|
d8f4f92e1d | ||
|
ce679f90ee | ||
|
8fb379079b | ||
|
b382d048de | ||
|
610cb170e7 | ||
|
4d9fcaeebf | ||
|
aa0225f59f | ||
|
2571949e90 | ||
|
20d2c11154 | ||
|
3f604e9d93 | ||
|
234e92db12 | ||
|
01c56f6515 | ||
|
026be5a990 | ||
|
aafb86ef09 | ||
|
1a24efe85d | ||
|
1ad67d5c73 | ||
|
0b3f7dbb1f | ||
|
e90007860c | ||
|
3b38669c04 | ||
|
74ab03288b | ||
|
502dacd865 | ||
|
31bc99cd61 | ||
|
5435c9ebd2 | ||
|
ceb9a320ba | ||
|
ee8b2a0b58 | ||
|
8202f1b7c6 | ||
|
4808cac89d | ||
|
c1ea3380eb | ||
|
694fdebc71 | ||
|
1cbd910e5d | ||
|
b102ef512e | ||
|
220a621dc6 | ||
|
0db288e6dc | ||
|
f8175fc325 | ||
|
5eee38e7de | ||
|
fb5b470966 | ||
|
876053f858 | ||
|
9714ef19de | ||
|
613a345771 | ||
|
4fe29dd33f | ||
|
4b24223290 | ||
|
a78da0db1e | ||
|
5e4fce1e12 | ||
|
4cb2624a5d | ||
|
58e045f25d | ||
|
26a770e490 | ||
|
dfe145a0ed | ||
|
6cb4c9224d | ||
|
4047612b96 | ||
|
1978a360af | ||
|
1f46b3fda9 | ||
|
47a945d92e | ||
|
59ec87a393 | ||
|
9423104dad | ||
|
614834090e | ||
|
2f9523a586 | ||
|
3a9f38a873 | ||
|
0697c26dd1 | ||
|
0528c12782 | ||
|
c2812b05a4 | ||
|
5d698d66d0 | ||
|
a790136164 | ||
|
8c191263c0 | ||
|
2253417459 | ||
|
dee68b903c | ||
|
940512fb2c | ||
|
02c7d014cb | ||
|
e16d5cf83d |
65
CHANGELOG.md
65
CHANGELOG.md
@@ -1,3 +1,68 @@
|
||||
#### 3.1.0-beta.4: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Add Japanese translation for 3.1.0 (#4252) @kazuhitoyokoi
|
||||
- Improve Catalogue visibility (#4248) @Steve-Mcl
|
||||
- Add support for wiring and moving junctions on touch device (#4244) @Steve-Mcl
|
||||
- Show errors and statuses of config nodes in the sidebar when no catch node is available (#4231) @bvmensvoort
|
||||
- Improve wiring for horizontally aligned nodes (#4232) @knolleary
|
||||
- French translation of Welcome Tours (#4200) @GogoVega
|
||||
- French translation of v3.1.0-beta.3 changes (#4199) @GogoVega
|
||||
- add Japanese message for 3.1.0 beta 3 (#4209) @HiroyasuNishiyama
|
||||
- Dont clone the group nodes `node` array when saving edits (#4208) @Steve-Mcl
|
||||
|
||||
Runtime
|
||||
|
||||
- Add NR_SUBFLOW_NAME/ID/PATH env vars (#4250) @knolleary
|
||||
- Evaluate all env vars as part of async flow start (#4230) @knolleary
|
||||
- Add support for httpStatic middleware (#4229) @knolleary
|
||||
|
||||
Nodes
|
||||
|
||||
- Fix JSONata in file nodes (#4246) @kazuhitoyokoi
|
||||
- Fix timeout icon in function and link call nodes (#4253) @kazuhitoyokoi
|
||||
- Fix connection keep-alive in http request node (#4228) @knolleary
|
||||
- adding timeout attribute to function node (#4177) @k1ln
|
||||
- Fix manual mode join when multiple sequences being handled (#4143) @BitCaesar
|
||||
- Fix delay node flush issue (#4203) @dceejay
|
||||
- Update status and catch node labels in group mode (#4207) @Steve-Mcl
|
||||
|
||||
#### 3.1.0-beta.3: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Select the item that is specified in a deep link URL (#4113) @Steve-Mcl
|
||||
- Update to Monaco 0.38.0 (#4189) @Steve-Mcl
|
||||
- Place subflow outputs/inputs relative to current view (#4183) @knolleary
|
||||
- Enable RED.view.select to select group by id (#4184) @knolleary
|
||||
- Combine existing env vars when merging groups (#4182) @knolleary
|
||||
- Avoid creating empty global-config node if not needed (#4153) @knolleary
|
||||
- Fix group selection when using lasso (#4108) @knolleary
|
||||
- Use editor path in generating localStorage keys (#4151) @mw75
|
||||
- Ensure no node credentials are included when exporting to clipboard (#4112) @knolleary
|
||||
- Fix jsonata expression test ui (#4097) @knolleary
|
||||
- Fix search button in palette popover (#4096) @knolleary
|
||||
|
||||
Runtime
|
||||
|
||||
- Allow options object on each httpStatic configuration (#4109) @kevinGodell
|
||||
- Ensure non-zero exit codes for errors (#4181) @knolleary
|
||||
- Ensure external modules are installed synchronously (#4180) @knolleary
|
||||
- Update dependecies include got (#4155) @knolleary
|
||||
- Add Japanese translations for v3.1 beta.2 (#4158) @kazuhitoyokoi
|
||||
- Ensure express server options are applied consistently (#4178) @knolleary
|
||||
- Remove version info from theme endpoint (#4179) @knolleary
|
||||
- Add Japanese translations for welcome tour of 3.1.0 beta.2 (#4145) @kazuhitoyokoi
|
||||
- Added SHA-256 and SHA-512-256 digest authentication (#4100) @sroebert
|
||||
- Add "timers" types to known types (#4103) @Steve-Mcl
|
||||
|
||||
Nodes
|
||||
|
||||
- Allow Catch/Status nodes to be scoped to their group (#4185) @NetHans
|
||||
- MQTT: Option to disable MQTT topic unsubscribe on disconnect (#4078) @flying7eleven
|
||||
|
||||
|
||||
#### 3.1.0-beta.2: Beta Release
|
||||
|
||||
Editor
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.0-beta.4",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "http://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
@@ -112,7 +112,7 @@
|
||||
"mermaid": "^9.4.3",
|
||||
"minami": "1.2.3",
|
||||
"mocha": "9.2.2",
|
||||
"node-red-node-test-helper": "^0.3.1",
|
||||
"node-red-node-test-helper": "^0.3.2",
|
||||
"nodemon": "2.0.20",
|
||||
"proxy": "^1.0.2",
|
||||
"sass": "1.62.1",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-api",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.0-beta.4",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,8 +16,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "3.1.0-beta.2",
|
||||
"@node-red/editor-client": "3.1.0-beta.2",
|
||||
"@node-red/util": "3.1.0-beta.4",
|
||||
"@node-red/editor-client": "3.1.0-beta.4",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.20.2",
|
||||
"clone": "2.1.2",
|
||||
|
@@ -416,6 +416,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
|
||||
"acrossMultipleGroups": "Cannot create subflow across multiple groups",
|
||||
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
|
||||
}
|
||||
},
|
||||
@@ -586,6 +587,7 @@
|
||||
"editor": {
|
||||
"title": "Manage palette",
|
||||
"palette": "Palette",
|
||||
"allCatalogs": "All Catalogs",
|
||||
"times": {
|
||||
"seconds": "seconds ago",
|
||||
"minutes": "minutes ago",
|
||||
|
@@ -504,6 +504,7 @@
|
||||
"unassigned": "Non attribué",
|
||||
"global": "global",
|
||||
"workspace": "espace de travail",
|
||||
"editor": "boîte de dialogue d'édition",
|
||||
"selectAll": "Tout sélectionner",
|
||||
"selectNone": "Ne rien sélectionner",
|
||||
"selectAllConnected": "Sélectionner tous les éléments connectés",
|
||||
|
@@ -416,6 +416,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません",
|
||||
"acrossMultipleGroups": "複数のグループをまたがるサブフローは作成できません",
|
||||
"multipleInputsToSelection": "<strong>サブフローを作成できません</strong>: 複数の入力が選択されています"
|
||||
}
|
||||
},
|
||||
@@ -586,6 +587,7 @@
|
||||
"editor": {
|
||||
"title": "パレットの管理",
|
||||
"palette": "パレット",
|
||||
"allCatalogs": "全カタログ",
|
||||
"times": {
|
||||
"seconds": "数秒前",
|
||||
"minutes": "数分前",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-client",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.0-beta.4",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -720,7 +720,10 @@ RED.editor = (function() {
|
||||
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
|
||||
oldValues[d] = editing_node[d];
|
||||
} else {
|
||||
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
|
||||
// Dont clone the group node `nodes` array
|
||||
if (editing_node.type !== 'group' || d !== "nodes") {
|
||||
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1168,19 +1168,19 @@ RED.editor.codeEditor.monaco = (function() {
|
||||
// Warning: 4
|
||||
// Error: 8
|
||||
ed.getAnnotations = function getAnnotations() {
|
||||
var aceCompatibleMarkers = [];
|
||||
let aceCompatibleMarkers;
|
||||
try {
|
||||
var _model = ed.getModel();
|
||||
const _model = ed.getModel();
|
||||
if (_model !== null) {
|
||||
var id = _model._languageId; // e.g. javascript
|
||||
var ra = _model._associatedResource.authority; //e.g. model
|
||||
var rp = _model._associatedResource.path; //e.g. /18
|
||||
var rs = _model._associatedResource.scheme; //e.g. inmemory
|
||||
var modelMarkers = monaco.editor.getModelMarkers(_model) || [];
|
||||
var thisEditorsMarkers = modelMarkers.filter(function (marker) {
|
||||
var _ra = marker.resource.authority; //e.g. model
|
||||
var _rp = marker.resource.path; //e.g. /18
|
||||
var _rs = marker.resource.scheme; //e.g. inmemory
|
||||
const id = _model.getLanguageId(); // e.g. javascript
|
||||
const ra = _model.uri.authority; // e.g. model
|
||||
const rp = _model.uri.path; // e.g. /18
|
||||
const rs = _model.uri.scheme; // e.g. inmemory
|
||||
const modelMarkers = monaco.editor.getModelMarkers(_model) || [];
|
||||
const thisEditorsMarkers = modelMarkers.filter(function (marker) {
|
||||
const _ra = marker.resource.authority; // e.g. model
|
||||
const _rp = marker.resource.path; // e.g. /18
|
||||
const _rs = marker.resource.scheme; // e.g. inmemory
|
||||
return marker.owner == id && _ra === ra && _rp === rp && _rs === rs;
|
||||
})
|
||||
aceCompatibleMarkers = thisEditorsMarkers.map(function (marker) {
|
||||
|
@@ -249,7 +249,10 @@ RED.keyboard = (function() {
|
||||
// One exception is shortcuts that include both Cmd and Ctrl. We don't
|
||||
// support them - but we need to make sure we don't block browser-specific
|
||||
// shortcuts (such as Cmd-Ctrl-F for fullscreen).
|
||||
if ((evt.ctrlKey || evt.metaKey) && (evt.ctrlKey !== evt.metaKey)) {
|
||||
if (evt.ctrlKey && evt.metaKey) {
|
||||
return null; // dont handle both cmd+ctrl - let browser handle this
|
||||
}
|
||||
if (evt.ctrlKey || evt.metaKey) {
|
||||
slot = slot.ctrl;
|
||||
}
|
||||
if (slot && evt.shiftKey) {
|
||||
|
@@ -16,15 +16,17 @@
|
||||
RED.palette.editor = (function() {
|
||||
|
||||
var disabled = false;
|
||||
|
||||
let catalogues = []
|
||||
const loadedCatalogs = []
|
||||
var editorTabs;
|
||||
var filterInput;
|
||||
var searchInput;
|
||||
var nodeList;
|
||||
var packageList;
|
||||
var loadedList = [];
|
||||
var filteredList = [];
|
||||
var loadedIndex = {};
|
||||
let filterInput;
|
||||
let searchInput;
|
||||
let nodeList;
|
||||
let packageList;
|
||||
let fullList = []
|
||||
let loadedList = [];
|
||||
let filteredList = [];
|
||||
let loadedIndex = {};
|
||||
|
||||
var typesInUse = {};
|
||||
var nodeEntries = {};
|
||||
@@ -162,7 +164,6 @@ RED.palette.editor = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getContrastingBorder(rgbColor){
|
||||
var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor);
|
||||
if (parts) {
|
||||
@@ -369,10 +370,10 @@ RED.palette.editor = (function() {
|
||||
var activeSort = sortModulesRelevance;
|
||||
|
||||
function handleCatalogResponse(err,catalog,index,v) {
|
||||
const url = catalog.url
|
||||
catalogueLoadStatus.push(err||v);
|
||||
if (!err) {
|
||||
if (v.modules) {
|
||||
var a = false;
|
||||
v.modules = v.modules.filter(function(m) {
|
||||
if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
|
||||
loadedIndex[m.id] = m;
|
||||
@@ -389,13 +390,14 @@ RED.palette.editor = (function() {
|
||||
m.timestamp = 0;
|
||||
}
|
||||
m.index = m.index.join(",").toLowerCase();
|
||||
m.catalog = catalog;
|
||||
m.catalogIndex = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
loadedList = loadedList.concat(v.modules);
|
||||
}
|
||||
searchInput.searchBox('count',loadedList.length);
|
||||
} else {
|
||||
catalogueLoadErrors = true;
|
||||
}
|
||||
@@ -404,7 +406,7 @@ RED.palette.editor = (function() {
|
||||
}
|
||||
if (catalogueLoadStatus.length === catalogueCount) {
|
||||
if (catalogueLoadErrors) {
|
||||
RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: catalog}),"error",false,8000);
|
||||
RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: url}),"error",false,8000);
|
||||
}
|
||||
var delta = 250-(Date.now() - catalogueLoadStart);
|
||||
setTimeout(function() {
|
||||
@@ -416,12 +418,13 @@ RED.palette.editor = (function() {
|
||||
|
||||
function initInstallTab() {
|
||||
if (loadedList.length === 0) {
|
||||
fullList = [];
|
||||
loadedList = [];
|
||||
loadedIndex = {};
|
||||
packageList.editableList('empty');
|
||||
|
||||
$(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading'));
|
||||
var catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json'];
|
||||
|
||||
catalogueLoadStatus = [];
|
||||
catalogueLoadErrors = false;
|
||||
catalogueCount = catalogues.length;
|
||||
@@ -431,23 +434,92 @@ RED.palette.editor = (function() {
|
||||
$("#red-ui-palette-module-install-shade").show();
|
||||
catalogueLoadStart = Date.now();
|
||||
var handled = 0;
|
||||
catalogues.forEach(function(catalog,index) {
|
||||
$.getJSON(catalog, {_: new Date().getTime()},function(v) {
|
||||
handleCatalogResponse(null,catalog,index,v);
|
||||
loadedCatalogs.length = 0; // clear the loadedCatalogs array
|
||||
for (let index = 0; index < catalogues.length; index++) {
|
||||
const url = catalogues[index];
|
||||
$.getJSON(url, {_: new Date().getTime()},function(v) {
|
||||
loadedCatalogs.push({ index: index, url: url, name: v.name, updated_at: v.updated_at, modules_count: (v.modules || []).length })
|
||||
handleCatalogResponse(null,{ url: url, name: v.name},index,v);
|
||||
refreshNodeModuleList();
|
||||
}).fail(function(jqxhr, textStatus, error) {
|
||||
console.warn("Error loading catalog",catalog,":",error);
|
||||
handleCatalogResponse(jqxhr,catalog,index);
|
||||
console.warn("Error loading catalog",url,":",error);
|
||||
handleCatalogResponse(jqxhr,url,index);
|
||||
}).always(function() {
|
||||
handled++;
|
||||
if (handled === catalogueCount) {
|
||||
searchInput.searchBox('change');
|
||||
//sort loadedCatalogs by e.index ascending
|
||||
loadedCatalogs.sort((a, b) => a.index - b.index)
|
||||
updateCatalogFilter(loadedCatalogs)
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the catalog filter dropdown and updates local variables
|
||||
* @param {[{url:String, name:String, updated_at:String, modules_count:Number}]} catalogEntries
|
||||
*/
|
||||
function updateCatalogFilter(catalogEntries, maxRetry = 3) {
|
||||
// clean up existing filters
|
||||
const catalogSelection = $('#red-catalogue-filter-select')
|
||||
if (catalogSelection.length === 0) {
|
||||
// sidebar not yet loaded (red-catalogue-filter-select is not in dom)
|
||||
if (maxRetry > 0) {
|
||||
// console.log("updateCatalogFilter: sidebar not yet loaded, retrying in 100ms")
|
||||
// try again in 100ms
|
||||
setTimeout(() => {
|
||||
updateCatalogFilter(catalogEntries, maxRetry - 1)
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
return; // give up
|
||||
}
|
||||
catalogSelection.off("change") // remove any existing event handlers
|
||||
catalogSelection.attr('disabled', 'disabled')
|
||||
catalogSelection.empty()
|
||||
catalogSelection.append($('<option>', { value: "loading", text: RED._('palette.editor.loading'), disabled: true, selected: true }));
|
||||
|
||||
fullList = loadedList.slice()
|
||||
catalogSelection.empty() // clear the select list
|
||||
|
||||
// loop through catalogTypes, and an option entry per catalog
|
||||
for (let index = 0; index < catalogEntries.length; index++) {
|
||||
const catalog = catalogEntries[index];
|
||||
catalogSelection.append(`<option value="${catalog.name}">${catalog.name}</option>`)
|
||||
}
|
||||
// select the 1st option in the select list
|
||||
catalogSelection.val(catalogSelection.find('option:first').val())
|
||||
|
||||
// if there is only 1 catalog, hide the select
|
||||
if (catalogEntries.length > 1) {
|
||||
catalogSelection.prepend(`<option value="all">${RED._('palette.editor.allCatalogs')}</option>`)
|
||||
catalogSelection.removeAttr('disabled') // permit the user to select a catalog
|
||||
}
|
||||
// refresh the searchInput counter and trigger a change
|
||||
filterByCatalog(catalogSelection.val())
|
||||
searchInput.searchBox('change');
|
||||
|
||||
// hook up the change event handler
|
||||
catalogSelection.on("change", function() {
|
||||
const selectedCatalog = $(this).val();
|
||||
filterByCatalog(selectedCatalog);
|
||||
searchInput.searchBox('change');
|
||||
})
|
||||
}
|
||||
|
||||
function filterByCatalog(selectedCatalog) {
|
||||
if (loadedCatalogs.length <= 1 || selectedCatalog === "all") {
|
||||
loadedList = fullList.slice();
|
||||
} else {
|
||||
loadedList = fullList.filter(function(m) {
|
||||
return (m.catalog.name === selectedCatalog);
|
||||
})
|
||||
}
|
||||
refreshFilteredItems();
|
||||
searchInput.searchBox('count',filteredList.length+" / "+loadedList.length);
|
||||
}
|
||||
|
||||
function refreshFilteredItems() {
|
||||
packageList.editableList('empty');
|
||||
var currentFilter = searchInput.searchBox('value').trim();
|
||||
@@ -462,7 +534,6 @@ RED.palette.editor = (function() {
|
||||
if (filteredList.length === 0) {
|
||||
packageList.editableList('addItem',{});
|
||||
}
|
||||
|
||||
if (filteredList.length > 10) {
|
||||
packageList.editableList('addItem',{start:10,more:filteredList.length-10})
|
||||
}
|
||||
@@ -492,6 +563,7 @@ RED.palette.editor = (function() {
|
||||
var updateDenyList = [];
|
||||
|
||||
function init() {
|
||||
catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json']
|
||||
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
|
||||
return;
|
||||
}
|
||||
@@ -669,7 +741,8 @@ RED.palette.editor = (function() {
|
||||
});
|
||||
|
||||
|
||||
nodeList = $('<ol>',{id:"red-ui-palette-module-list", style:"position: absolute;top: 35px;bottom: 0;left: 0;right: 0px;"}).appendTo(modulesTab).editableList({
|
||||
nodeList = $('<ol>',{id:"red-ui-palette-module-list"}).appendTo(modulesTab).editableList({
|
||||
class: "scrollable",
|
||||
addButton: false,
|
||||
scrollOnAdd: false,
|
||||
sort: function(A,B) {
|
||||
@@ -800,21 +873,20 @@ RED.palette.editor = (function() {
|
||||
$('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function createInstallTab(content) {
|
||||
var installTab = $('<div>',{class:"red-ui-palette-editor-tab hide"}).appendTo(content);
|
||||
|
||||
const installTab = $('<div>',{class:"red-ui-palette-editor-tab", style: "display: none;"}).appendTo(content);
|
||||
editorTabs.addTab({
|
||||
id: 'install',
|
||||
label: RED._('palette.editor.tab-install'),
|
||||
content: installTab
|
||||
})
|
||||
|
||||
var toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab);
|
||||
|
||||
var searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab);
|
||||
const toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab);
|
||||
|
||||
const searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab);
|
||||
searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>')
|
||||
.appendTo(searchDiv)
|
||||
.searchBox({
|
||||
@@ -831,19 +903,25 @@ RED.palette.editor = (function() {
|
||||
searchInput.searchBox('count',loadedList.length);
|
||||
packageList.editableList('empty');
|
||||
packageList.editableList('addItem',{count:loadedList.length});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBar);
|
||||
var sortGroup = $('<span class="button-group"></span>').appendTo(toolBar);
|
||||
var sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup);
|
||||
var sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortAZ"></a>').appendTo(sortGroup);
|
||||
var sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortRecent"></a>').appendTo(sortGroup);
|
||||
const catalogSelection = $('<select id="red-catalogue-filter-select">').appendTo(toolBar);
|
||||
catalogSelection.addClass('red-ui-palette-editor-catalogue-filter');
|
||||
|
||||
const toolBarActions = $('<div>',{class:"red-ui-palette-editor-toolbar-actions"}).appendTo(toolBar);
|
||||
|
||||
$('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBarActions);
|
||||
const sortGroup = $('<span class="button-group"></span>').appendTo(toolBarActions);
|
||||
const sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup);
|
||||
const sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-sort-alpha-asc"></i></a>').appendTo(sortGroup);
|
||||
const sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-calendar"></i></a>').appendTo(sortGroup);
|
||||
RED.popover.tooltip(sortAZ,RED._("palette.editor.sortAZ"));
|
||||
RED.popover.tooltip(sortRecent,RED._("palette.editor.sortRecent"));
|
||||
|
||||
|
||||
var sortOpts = [
|
||||
const sortOpts = [
|
||||
{button: sortRelevance, func: sortModulesRelevance},
|
||||
{button: sortAZ, func: sortModulesAZ},
|
||||
{button: sortRecent, func: sortModulesRecent}
|
||||
@@ -861,7 +939,7 @@ RED.palette.editor = (function() {
|
||||
});
|
||||
});
|
||||
|
||||
var refreshSpan = $('<span>').appendTo(toolBar);
|
||||
var refreshSpan = $('<span>').appendTo(toolBarActions);
|
||||
var refreshButton = $('<a href="#" class="red-ui-sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan);
|
||||
refreshButton.on("click", function(e) {
|
||||
e.preventDefault();
|
||||
@@ -871,7 +949,8 @@ RED.palette.editor = (function() {
|
||||
})
|
||||
RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh"));
|
||||
|
||||
packageList = $('<ol>',{style:"position: absolute;top: 79px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({
|
||||
packageList = $('<ol>').appendTo(installTab).editableList({
|
||||
class: "scrollable",
|
||||
addButton: false,
|
||||
scrollOnAdd: false,
|
||||
addItem: function(container,i,object) {
|
||||
@@ -906,6 +985,9 @@ RED.palette.editor = (function() {
|
||||
var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
|
||||
$('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
|
||||
$('<span class="red-ui-palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
|
||||
if (loadedCatalogs.length > 1) {
|
||||
$('<span class="red-ui-palette-module-updated"><i class="fa fa-cubes"></i>' + (entry.catalog.name || entry.catalog.url) + '</span>').appendTo(metaRow);
|
||||
}
|
||||
|
||||
var duplicateType = false;
|
||||
if (entry.types && entry.types.length > 0) {
|
||||
@@ -952,9 +1034,10 @@ RED.palette.editor = (function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) {
|
||||
var uploadSpan = $('<span class="button-group">').prependTo(toolBar);
|
||||
var uploadSpan = $('<span class="button-group">').prependTo(toolBarActions);
|
||||
var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan);
|
||||
|
||||
var uploadInput = uploadButton.find('input[type="file"]');
|
||||
|
@@ -667,7 +667,7 @@ RED.subflow = (function() {
|
||||
for (i=0; i<nodeList.length;i++) {
|
||||
if (nodeList[i].g && !includedGroups.has(nodeList[i].g)) {
|
||||
if (containingGroup !== nodeList[i].g) {
|
||||
RED.notify("Cannot create subflow across multiple groups","error");
|
||||
RED.notify(RED._("subflow.errors.acrossMultipleGroups"), "error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -1305,6 +1305,39 @@ RED.view.tools = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a point is within a node
|
||||
* @param {*} node - A Node or Junction node
|
||||
* @param {[Number,Number]} mouse_position The x,y position of the mouse
|
||||
* @param {Number} [marginX=0] - A margin to add or deduct from the x position (to increase the hit area)
|
||||
* @param {Number} [marginY=0] - A margin to add or deduct from the y position (to increase the hit area)
|
||||
* @returns
|
||||
*/
|
||||
function isPointInNode (node, [x, y], marginX, marginY) {
|
||||
marginX = marginX || 0
|
||||
marginY = marginY || 0
|
||||
|
||||
let w = node.w || 10 // junctions dont have any w or h value
|
||||
let h = node.h || 10
|
||||
let x1, x2, y1, y2
|
||||
|
||||
if (node.type === "junction" || node.type === "group") {
|
||||
// x/y is the top left of the node
|
||||
x1 = node.x
|
||||
y1 = node.y
|
||||
x2 = node.x + w
|
||||
y2 = node.y + h
|
||||
} else {
|
||||
// x/y is the center of the node
|
||||
const [xMid, yMid] = [w/2, h/2]
|
||||
x1 = node.x - xMid
|
||||
y1 = node.y - yMid
|
||||
x2 = node.x + xMid
|
||||
y2 = node.y + yMid
|
||||
}
|
||||
return (x >= (x1 - marginX) && x <= (x2 + marginX) && y >= (y1 - marginY) && y <= (y2 + marginY))
|
||||
}
|
||||
|
||||
return {
|
||||
init: function() {
|
||||
RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
|
||||
@@ -1387,7 +1420,8 @@ RED.view.tools = (function() {
|
||||
* @param {Number} dy
|
||||
*/
|
||||
moveSelection: moveSelection,
|
||||
calculateGridSnapOffsets: calculateGridSnapOffsets
|
||||
calculateGridSnapOffsets: calculateGridSnapOffsets,
|
||||
isPointInNode: isPointInNode
|
||||
}
|
||||
|
||||
})();
|
||||
|
@@ -101,7 +101,7 @@ RED.view = (function() {
|
||||
|
||||
// Note: these are the permitted status colour aliases. The actual RGB values
|
||||
// are set in the CSS - flow.scss/colors.scss
|
||||
var status_colours = {
|
||||
const status_colours = {
|
||||
"red": "#c00",
|
||||
"green": "#5a8",
|
||||
"yellow": "#F9DF31",
|
||||
@@ -110,19 +110,32 @@ RED.view = (function() {
|
||||
"gray": "#d3d3d3"
|
||||
}
|
||||
|
||||
var PORT_TYPE_INPUT = 1;
|
||||
var PORT_TYPE_OUTPUT = 0;
|
||||
const PORT_TYPE_INPUT = 1;
|
||||
const PORT_TYPE_OUTPUT = 0;
|
||||
|
||||
var chart;
|
||||
var outer;
|
||||
/**
|
||||
* The jQuery object for the workspace chart `#red-ui-workspace-chart` div element
|
||||
* @type {JQuery<HTMLElement>} #red-ui-workspace-chart HTML Element
|
||||
*/
|
||||
let chart;
|
||||
/**
|
||||
* The d3 object `#red-ui-workspace-chart` svg element
|
||||
* @type {d3.Selection<HTMLElement, Any, Any, Any>}
|
||||
*/
|
||||
let outer;
|
||||
/**
|
||||
* The d3 object `#red-ui-workspace-chart` svg element (specifically for events)
|
||||
* @type {d3.Selection<d3.BaseType, any, any, any>}
|
||||
*/
|
||||
var eventLayer;
|
||||
var gridLayer;
|
||||
var linkLayer;
|
||||
var junctionLayer;
|
||||
var dragGroupLayer;
|
||||
var groupSelectLayer;
|
||||
var nodeLayer;
|
||||
var groupLayer;
|
||||
|
||||
/** @type {SVGGElement} */ let gridLayer;
|
||||
/** @type {SVGGElement} */ let linkLayer;
|
||||
/** @type {SVGGElement} */ let junctionLayer;
|
||||
/** @type {SVGGElement} */ let dragGroupLayer;
|
||||
/** @type {SVGGElement} */ let groupSelectLayer;
|
||||
/** @type {SVGGElement} */ let nodeLayer;
|
||||
/** @type {SVGGElement} */ let groupLayer;
|
||||
var drag_lines;
|
||||
|
||||
const movingSet = (function() {
|
||||
@@ -391,16 +404,6 @@ RED.view = (function() {
|
||||
touchStartTime = setTimeout(function() {
|
||||
touchStartTime = null;
|
||||
showTouchMenu(obj,pos);
|
||||
//lasso = eventLayer.append("rect")
|
||||
// .attr("ox",point[0])
|
||||
// .attr("oy",point[1])
|
||||
// .attr("rx",2)
|
||||
// .attr("ry",2)
|
||||
// .attr("x",point[0])
|
||||
// .attr("y",point[1])
|
||||
// .attr("width",0)
|
||||
// .attr("height",0)
|
||||
// .attr("class","nr-ui-view-lasso");
|
||||
},touchLongPressTimeout);
|
||||
}
|
||||
d3.event.preventDefault();
|
||||
@@ -1033,7 +1036,7 @@ RED.view = (function() {
|
||||
})
|
||||
}
|
||||
|
||||
function generateLinkPath(origX,origY, destX, destY, sc) {
|
||||
function generateLinkPath(origX,origY, destX, destY, sc, hasStatus = false) {
|
||||
var dy = destY-origY;
|
||||
var dx = destX-origX;
|
||||
var delta = Math.sqrt(dy*dy+dx*dx);
|
||||
@@ -1050,62 +1053,110 @@ RED.view = (function() {
|
||||
} else {
|
||||
scale = 0.4-0.2*(Math.max(0,(node_width-Math.min(Math.abs(dx),Math.abs(dy)))/node_width));
|
||||
}
|
||||
function genCP(cp) {
|
||||
return ` M ${cp[0]-5} ${cp[1]} h 10 M ${cp[0]} ${cp[1]-5} v 10 `
|
||||
}
|
||||
if (dx*sc > 0) {
|
||||
return "M "+origX+" "+origY+
|
||||
" C "+(origX+sc*(node_width*scale))+" "+(origY+scaleY*node_height)+" "+
|
||||
(destX-sc*(scale)*node_width)+" "+(destY-scaleY*node_height)+" "+
|
||||
destX+" "+destY
|
||||
let cp = [
|
||||
[(origX+sc*(node_width*scale)), (origY+scaleY*node_height)],
|
||||
[(destX-sc*(scale)*node_width), (destY-scaleY*node_height)]
|
||||
]
|
||||
return `M ${origX} ${origY} C ${cp[0][0]} ${cp[0][1]} ${cp[1][0]} ${cp[1][1]} ${destX} ${destY}`
|
||||
// + ` ${genCP(cp[0])} ${genCP(cp[1])}`
|
||||
} else {
|
||||
let topX, topY, bottomX, bottomY
|
||||
let cp
|
||||
let midX = Math.floor(destX-dx/2);
|
||||
let midY = Math.floor(destY-dy/2);
|
||||
if (Math.abs(dy) < 10) {
|
||||
bottomY = Math.max(origY, destY) + (hasStatus?35:25)
|
||||
let startCurveHeight = bottomY - origY
|
||||
let endCurveHeight = bottomY - destY
|
||||
cp = [
|
||||
[ origX + sc*15 , origY ],
|
||||
[ origX + sc*25 , origY + 5 ],
|
||||
[ origX + sc*25 , origY + startCurveHeight/2 ],
|
||||
|
||||
var midX = Math.floor(destX-dx/2);
|
||||
var midY = Math.floor(destY-dy/2);
|
||||
//
|
||||
if (dy === 0) {
|
||||
midY = destY + node_height;
|
||||
}
|
||||
var cp_height = node_height/2;
|
||||
var y1 = (destY + midY)/2
|
||||
var topX =origX + sc*node_width*scale;
|
||||
var topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height);
|
||||
var bottomX = destX - sc*node_width*scale;
|
||||
var bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height);
|
||||
var x1 = (origX+topX)/2;
|
||||
var scy = dy>0?1:-1;
|
||||
var cp = [
|
||||
// Orig -> Top
|
||||
[x1,origY],
|
||||
[topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)],
|
||||
// Top -> Mid
|
||||
// [Mirror previous cp]
|
||||
[x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)],
|
||||
// Mid -> Bottom
|
||||
// [Mirror previous cp]
|
||||
[bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)],
|
||||
// Bottom -> Dest
|
||||
// [Mirror previous cp]
|
||||
[(destX+bottomX)/2,destY]
|
||||
];
|
||||
if (cp[2][1] === topY+scy*cp_height) {
|
||||
if (Math.abs(dy) < cp_height*10) {
|
||||
cp[1][1] = topY-scy*cp_height/2;
|
||||
cp[3][1] = bottomY-scy*cp_height/2;
|
||||
}
|
||||
cp[2][0] = topX;
|
||||
}
|
||||
return "M "+origX+" "+origY+
|
||||
" C "+
|
||||
cp[0][0]+" "+cp[0][1]+" "+
|
||||
cp[1][0]+" "+cp[1][1]+" "+
|
||||
topX+" "+topY+
|
||||
" S "+
|
||||
cp[2][0]+" "+cp[2][1]+" "+
|
||||
midX+" "+midY+
|
||||
" S "+
|
||||
cp[3][0]+" "+cp[3][1]+" "+
|
||||
bottomX+" "+bottomY+
|
||||
" S "+
|
||||
[ origX + sc*25 , origY + startCurveHeight - 5 ],
|
||||
[ origX + sc*15 , origY + startCurveHeight ],
|
||||
[ origX , origY + startCurveHeight ],
|
||||
|
||||
[ destX - sc*15, origY + startCurveHeight ],
|
||||
[ destX - sc*25, origY + startCurveHeight - 5 ],
|
||||
[ destX - sc*25, destY + endCurveHeight/2 ],
|
||||
|
||||
[ destX - sc*25, destY + 5 ],
|
||||
[ destX - sc*15, destY ],
|
||||
[ destX, destY ],
|
||||
]
|
||||
|
||||
return "M "+origX+" "+origY+
|
||||
" C "+
|
||||
cp[0][0]+" "+cp[0][1]+" "+
|
||||
cp[1][0]+" "+cp[1][1]+" "+
|
||||
cp[2][0]+" "+cp[2][1]+" "+
|
||||
" C " +
|
||||
cp[3][0]+" "+cp[3][1]+" "+
|
||||
cp[4][0]+" "+cp[4][1]+" "+
|
||||
destX+" "+destY
|
||||
cp[5][0]+" "+cp[5][1]+" "+
|
||||
" h "+dx+
|
||||
" C "+
|
||||
cp[6][0]+" "+cp[6][1]+" "+
|
||||
cp[7][0]+" "+cp[7][1]+" "+
|
||||
cp[8][0]+" "+cp[8][1]+" "+
|
||||
" C " +
|
||||
cp[9][0]+" "+cp[9][1]+" "+
|
||||
cp[10][0]+" "+cp[10][1]+" "+
|
||||
cp[11][0]+" "+cp[11][1]+" "
|
||||
// +genCP(cp[0])+genCP(cp[1])+genCP(cp[2])+genCP(cp[3])+genCP(cp[4])
|
||||
// +genCP(cp[5])+genCP(cp[6])+genCP(cp[7])+genCP(cp[8])+genCP(cp[9])+genCP(cp[10])
|
||||
} else {
|
||||
var cp_height = node_height/2;
|
||||
var y1 = (destY + midY)/2
|
||||
topX = origX + sc*node_width*scale;
|
||||
topY = dy>0?Math.min(y1 - dy/2 , origY+cp_height):Math.max(y1 - dy/2 , origY-cp_height);
|
||||
bottomX = destX - sc*node_width*scale;
|
||||
bottomY = dy>0?Math.max(y1, destY-cp_height):Math.min(y1, destY+cp_height);
|
||||
var x1 = (origX+topX)/2;
|
||||
var scy = dy>0?1:-1;
|
||||
cp = [
|
||||
// Orig -> Top
|
||||
[x1,origY],
|
||||
[topX,dy>0?Math.max(origY, topY-cp_height):Math.min(origY, topY+cp_height)],
|
||||
// Top -> Mid
|
||||
// [Mirror previous cp]
|
||||
[x1,dy>0?Math.min(midY, topY+cp_height):Math.max(midY, topY-cp_height)],
|
||||
// Mid -> Bottom
|
||||
// [Mirror previous cp]
|
||||
[bottomX,dy>0?Math.max(midY, bottomY-cp_height):Math.min(midY, bottomY+cp_height)],
|
||||
// Bottom -> Dest
|
||||
// [Mirror previous cp]
|
||||
[(destX+bottomX)/2,destY]
|
||||
];
|
||||
if (cp[2][1] === topY+scy*cp_height) {
|
||||
if (Math.abs(dy) < cp_height*10) {
|
||||
cp[1][1] = topY-scy*cp_height/2;
|
||||
cp[3][1] = bottomY-scy*cp_height/2;
|
||||
}
|
||||
cp[2][0] = topX;
|
||||
}
|
||||
return "M "+origX+" "+origY+
|
||||
" C "+
|
||||
cp[0][0]+" "+cp[0][1]+" "+
|
||||
cp[1][0]+" "+cp[1][1]+" "+
|
||||
topX+" "+topY+
|
||||
" S "+
|
||||
cp[2][0]+" "+cp[2][1]+" "+
|
||||
midX+" "+midY+
|
||||
" S "+
|
||||
cp[3][0]+" "+cp[3][1]+" "+
|
||||
bottomX+" "+bottomY+
|
||||
" S "+
|
||||
cp[4][0]+" "+cp[4][1]+" "+
|
||||
destX+" "+destY
|
||||
|
||||
// +genCP(cp[0])+genCP(cp[1])+genCP(cp[2])+genCP(cp[3])+genCP(cp[4])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1347,6 +1398,7 @@ RED.view = (function() {
|
||||
}
|
||||
historyEvent = {
|
||||
t:'add',
|
||||
dirty: RED.nodes.dirty(),
|
||||
junctions:[nn]
|
||||
}
|
||||
} else {
|
||||
@@ -1722,7 +1774,7 @@ RED.view = (function() {
|
||||
var portY = -((numOutputs-1)/2)*13 +13*sourcePort;
|
||||
|
||||
var sc = (drag_line.portType === PORT_TYPE_OUTPUT)?1:-1;
|
||||
drag_line.el.attr("d",generateLinkPath(drag_line.node.x+sc*drag_line.node.w/2,drag_line.node.y+portY,mousePos[0],mousePos[1],sc));
|
||||
drag_line.el.attr("d",generateLinkPath(drag_line.node.x+sc*drag_line.node.w/2,drag_line.node.y+portY,mousePos[0],mousePos[1],sc, !!drag_line.node.status));
|
||||
}
|
||||
d3.event.preventDefault();
|
||||
} else if (mouse_mode == RED.state.MOVING) {
|
||||
@@ -3046,22 +3098,38 @@ RED.view = (function() {
|
||||
}
|
||||
}
|
||||
document.body.style.cursor = "";
|
||||
|
||||
if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) {
|
||||
if (typeof TouchEvent != "undefined" && evt instanceof TouchEvent) {
|
||||
var found = false;
|
||||
RED.nodes.eachNode(function(n) {
|
||||
if (n.z == RED.workspaces.active()) {
|
||||
var hw = n.w/2;
|
||||
var hh = n.h/2;
|
||||
if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
|
||||
n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
|
||||
found = true;
|
||||
mouseup_node = n;
|
||||
portType = mouseup_node.inputs>0?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT;
|
||||
portIndex = 0;
|
||||
if (RED.view.DEBUG) { console.warn("portMouseUp: TouchEvent", mouse_mode,d,portType,portIndex); }
|
||||
const direction = drag_lines[0].portType === PORT_TYPE_INPUT ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT
|
||||
let found = false;
|
||||
for (let nodeIdx = 0; nodeIdx < activeNodes.length; nodeIdx++) {
|
||||
const n = activeNodes[nodeIdx];
|
||||
if (RED.view.tools.isPointInNode(n, mouse_position)) {
|
||||
found = true;
|
||||
mouseup_node = n;
|
||||
// portType = mouseup_node.inputs > 0 ? PORT_TYPE_INPUT : PORT_TYPE_OUTPUT;
|
||||
portType = direction;
|
||||
portIndex = 0;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && drag_lines.length > 0 && !drag_lines[0].virtualLink) {
|
||||
for (let juncIdx = 0; juncIdx < activeJunctions.length; juncIdx++) {
|
||||
// NOTE: a junction is 10px x 10px but the target area is expanded to 30wx20h by adding padding to the bounding box
|
||||
const jNode = activeJunctions[juncIdx];
|
||||
if (RED.view.tools.isPointInNode(jNode, mouse_position, 20, 10)) {
|
||||
found = true;
|
||||
mouseup_node = jNode;
|
||||
portType = direction;
|
||||
portIndex = 0;
|
||||
break
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!found && activeSubflow) {
|
||||
var subflowPorts = [];
|
||||
if (activeSubflow.status) {
|
||||
@@ -3073,16 +3141,13 @@ RED.view = (function() {
|
||||
if (activeSubflow.out) {
|
||||
subflowPorts = subflowPorts.concat(activeSubflow.out)
|
||||
}
|
||||
for (var i=0;i<subflowPorts.length;i++) {
|
||||
var n = subflowPorts[i];
|
||||
var hw = n.w/2;
|
||||
var hh = n.h/2;
|
||||
if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] &&
|
||||
n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) {
|
||||
found = true;
|
||||
mouseup_node = n;
|
||||
portType = mouseup_node.direction === "in"?PORT_TYPE_OUTPUT:PORT_TYPE_INPUT;
|
||||
portIndex = 0;
|
||||
for (var i = 0; i < subflowPorts.length; i++) {
|
||||
const sf = subflowPorts[i];
|
||||
if (RED.view.tools.isPointInNode(sf, mouse_position)) {
|
||||
found = true;
|
||||
mouseup_node = sf;
|
||||
portType = mouseup_node.direction === "in" ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT;
|
||||
portIndex = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -4112,21 +4177,27 @@ RED.view = (function() {
|
||||
nodeEl.__statusGroup__.style.display = "none";
|
||||
} else {
|
||||
nodeEl.__statusGroup__.style.display = "inline";
|
||||
let backgroundWidth = 12
|
||||
var fill = status_colours[d.status.fill]; // Only allow our colours for now
|
||||
if (d.status.shape == null && fill == null) {
|
||||
backgroundWidth = 0
|
||||
nodeEl.__statusShape__.style.display = "none";
|
||||
nodeEl.__statusBackground__.setAttribute("x", 17)
|
||||
nodeEl.__statusGroup__.setAttribute("transform","translate(-14,"+(d.h+3)+")");
|
||||
} else {
|
||||
nodeEl.__statusGroup__.setAttribute("transform","translate(3,"+(d.h+3)+")");
|
||||
var statusClass = "red-ui-flow-node-status-"+(d.status.shape||"dot")+"-"+d.status.fill;
|
||||
nodeEl.__statusShape__.style.display = "inline";
|
||||
nodeEl.__statusShape__.setAttribute("class","red-ui-flow-node-status "+statusClass);
|
||||
nodeEl.__statusBackground__.setAttribute("x", 3)
|
||||
}
|
||||
if (d.status.hasOwnProperty('text')) {
|
||||
nodeEl.__statusLabel__.textContent = d.status.text;
|
||||
} else {
|
||||
nodeEl.__statusLabel__.textContent = "";
|
||||
}
|
||||
const textSize = nodeEl.__statusLabel__.getBBox()
|
||||
nodeEl.__statusBackground__.setAttribute('width', backgroundWidth + textSize.width + 6)
|
||||
}
|
||||
delete d.dirtyStatus;
|
||||
}
|
||||
@@ -4532,17 +4603,30 @@ RED.view = (function() {
|
||||
statusEl.style.display = "none";
|
||||
node[0][0].__statusGroup__ = statusEl;
|
||||
|
||||
var statusRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
||||
statusRect.setAttribute("class","red-ui-flow-node-status");
|
||||
statusRect.setAttribute("x",6);
|
||||
statusRect.setAttribute("y",1);
|
||||
statusRect.setAttribute("width",9);
|
||||
statusRect.setAttribute("height",9);
|
||||
statusRect.setAttribute("rx",2);
|
||||
statusRect.setAttribute("ry",2);
|
||||
statusRect.setAttribute("stroke-width","3");
|
||||
statusEl.appendChild(statusRect);
|
||||
node[0][0].__statusShape__ = statusRect;
|
||||
var statusBackground = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
||||
statusBackground.setAttribute("class","red-ui-flow-node-status-background");
|
||||
statusBackground.setAttribute("x",3);
|
||||
statusBackground.setAttribute("y",-1);
|
||||
statusBackground.setAttribute("width",200);
|
||||
statusBackground.setAttribute("height",13);
|
||||
statusBackground.setAttribute("rx",1);
|
||||
statusBackground.setAttribute("ry",1);
|
||||
|
||||
statusEl.appendChild(statusBackground);
|
||||
node[0][0].__statusBackground__ = statusBackground;
|
||||
|
||||
|
||||
var statusIcon = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
||||
statusIcon.setAttribute("class","red-ui-flow-node-status");
|
||||
statusIcon.setAttribute("x",6);
|
||||
statusIcon.setAttribute("y",1);
|
||||
statusIcon.setAttribute("width",9);
|
||||
statusIcon.setAttribute("height",9);
|
||||
statusIcon.setAttribute("rx",2);
|
||||
statusIcon.setAttribute("ry",2);
|
||||
statusIcon.setAttribute("stroke-width","3");
|
||||
statusEl.appendChild(statusIcon);
|
||||
node[0][0].__statusShape__ = statusIcon;
|
||||
|
||||
var statusLabel = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||
statusLabel.setAttribute("class","red-ui-flow-node-status-label");
|
||||
@@ -4948,16 +5032,25 @@ RED.view = (function() {
|
||||
contents.appendChild(junctionOutput);
|
||||
junctionOutput.addEventListener("mouseup", portMouseUpProxy);
|
||||
junctionOutput.addEventListener("mousedown", portMouseDownProxy);
|
||||
|
||||
junctionOutput.addEventListener("mouseover", junctionMouseOverProxy);
|
||||
junctionOutput.addEventListener("mouseout", junctionMouseOutProxy);
|
||||
junctionOutput.addEventListener("touchmove", junctionMouseOverProxy);
|
||||
junctionOutput.addEventListener("touchend", portMouseUpProxy);
|
||||
junctionOutput.addEventListener("touchstart", portMouseDownProxy);
|
||||
|
||||
junctionInput.addEventListener("mouseover", junctionMouseOverProxy);
|
||||
junctionInput.addEventListener("mouseout", junctionMouseOutProxy);
|
||||
junctionInput.addEventListener("touchmove", junctionMouseOverProxy);
|
||||
junctionInput.addEventListener("touchend", portMouseUpProxy);
|
||||
junctionInput.addEventListener("touchstart", portMouseDownProxy);
|
||||
|
||||
junctionBack.addEventListener("mouseover", junctionMouseOverProxy);
|
||||
junctionBack.addEventListener("mouseout", junctionMouseOutProxy);
|
||||
junctionBack.addEventListener("touchmove", junctionMouseOverProxy);
|
||||
|
||||
// These handlers expect to be registered as d3 events
|
||||
d3.select(junctionBack).on("mousedown", nodeMouseDown).on("mouseup", nodeMouseUp);
|
||||
d3.select(junctionBack).on("touchstart", nodeMouseDown).on("touchend", nodeMouseUp);
|
||||
|
||||
junction[0][0].appendChild(contents);
|
||||
})
|
||||
@@ -5067,7 +5160,7 @@ RED.view = (function() {
|
||||
// " C "+(d.x1+scale*node_width)+" "+(d.y1+scaleY*node_height)+" "+
|
||||
// (d.x2-scale*node_width)+" "+(d.y2-scaleY*node_height)+" "+
|
||||
// d.x2+" "+d.y2;
|
||||
var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1);
|
||||
var path = generateLinkPath(d.x1,d.y1,d.x2,d.y2,1, !!(d.source.status || d.target.status));
|
||||
if (/NaN/.test(path)) {
|
||||
path = ""
|
||||
}
|
||||
|
@@ -304,7 +304,11 @@ g.red-ui-flow-node-selected {
|
||||
stroke: var(--red-ui-node-status-colors-#{"" + $current-color});
|
||||
}
|
||||
}
|
||||
|
||||
.red-ui-flow-node-status-background {
|
||||
stroke: none;
|
||||
fill: var(--red-ui-view-background);
|
||||
fill-opacity: 0.9;
|
||||
}
|
||||
.red-ui-flow-node-status-label {
|
||||
@include disable-selection;
|
||||
stroke-width: 0;
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
#red-ui-settings-tab-palette {
|
||||
#red-ui-settings-tab-palette {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,17 @@
|
||||
padding: 0;
|
||||
box-sizing:border-box;
|
||||
background: var(--red-ui-secondary-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.red-ui-tabs {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.red-ui-editableList.scrollable {
|
||||
overflow-y: auto;
|
||||
}
|
||||
.red-ui-editableList-container {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
@@ -72,11 +82,9 @@
|
||||
|
||||
}
|
||||
.red-ui-palette-editor-tab {
|
||||
position:absolute;
|
||||
top:35px;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:0
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
.red-ui-palette-editor-toolbar {
|
||||
background: var(--red-ui-primary-background);
|
||||
@@ -84,6 +92,24 @@
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--red-ui-primary-border-color);
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 3px 12px;
|
||||
.red-ui-palette-editor-toolbar-actions {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.red-ui-palette-editor-catalogue-filter {
|
||||
width: unset;
|
||||
margin: 0;
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
font-size: 12px;
|
||||
height: 26px;
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
.red-ui-palette-module-shade-status {
|
||||
color: var(--red-ui-secondary-text-color);
|
||||
|
@@ -54,7 +54,7 @@
|
||||
}
|
||||
.red-ui-palette-search {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
// overflow: hidden;
|
||||
background: var(--red-ui-form-input-background);
|
||||
text-align: center;
|
||||
height: 35px;
|
||||
|
@@ -5,17 +5,20 @@ export default {
|
||||
titleIcon: "fa fa-map-o",
|
||||
title: {
|
||||
"en-US": "Welcome to Node-RED 3.0!",
|
||||
"ja": "Node-RED 3.0へようこそ!"
|
||||
"ja": "Node-RED 3.0へようこそ!",
|
||||
"fr": "Bienvenue dans Node-RED 3.0 !"
|
||||
},
|
||||
description: {
|
||||
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
|
||||
"ja": "<p>本リリースの新機能を見つけてみましょう。</p>"
|
||||
"ja": "<p>本リリースの新機能を見つけてみましょう。</p>",
|
||||
"fr": "<p>Prenons un moment pour découvrir les nouvelles fonctionnalités de cette version.</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Context Menu",
|
||||
"ja": "コンテキストメニュー"
|
||||
"ja": "コンテキストメニュー",
|
||||
"fr": "Menu contextuel"
|
||||
},
|
||||
image: '3.0/images/context-menu.png',
|
||||
description: {
|
||||
@@ -24,13 +27,17 @@ export default {
|
||||
<p>This makes many of the built-in actions much easier
|
||||
to access.</p>`,
|
||||
"ja": `<p>ワークスペースで右クリックすると、エディタに独自のコンテキストメニューが表示されるようになりました。</p>
|
||||
<p>これによって多くの組み込み動作を、より簡単に利用できます。</p>`
|
||||
<p>これによって多くの組み込み動作を、より簡単に利用できます。</p>`,
|
||||
"fr": `<p>L'éditeur a maintenant son propre menu contextuel lorsque vous
|
||||
faites un clic droit dans l'espace de travail.</p>
|
||||
<p>Cela facilite l'accès à de nombreuses actions intégrées.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Wire Junctions",
|
||||
"ja": "分岐点をワイヤーに追加"
|
||||
"ja": "分岐点をワイヤーに追加",
|
||||
"fr": "Jonctions de fils"
|
||||
},
|
||||
image: '3.0/images/junction-slice.gif',
|
||||
description: {
|
||||
@@ -40,13 +47,18 @@ export default {
|
||||
<p>Junctions can be added to wires by holding both the Alt key and the Shift key
|
||||
then click and drag the mouse across the wires.</p>`,
|
||||
"ja": `<p>フローのワイヤーの経路をより制御しやすくするために、分岐点ノードを追加できるようになりました。</p>
|
||||
<p>Altキーとシフトキーを押しながらマウスをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。</p>`
|
||||
<p>Altキーとシフトキーを押しながらマウスをクリックし、ワイヤーを横切るようにドラッグすることで、分岐点を追加できます。</p>`,
|
||||
"fr": `<p>Pour faciliter le routage des câbles autour de vos flux, il est désormais possible d'ajouter des noeuds
|
||||
de jonction qui vous donnent plus de contrôle.</p>
|
||||
<p>Les jonctions peuvent être ajoutées aux fils en maintenant les touches Alt et Maj enfoncées, puis en cliquant
|
||||
et en faisant glisser la souris sur les fils.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Wire Junctions",
|
||||
"ja": "分岐点をワイヤーに追加"
|
||||
"ja": "分岐点をワイヤーに追加",
|
||||
"fr": "Jonctions de fils"
|
||||
},
|
||||
image: '3.0/images/junction-quick-add.png',
|
||||
description: {
|
||||
@@ -54,13 +66,16 @@ export default {
|
||||
<p>The dialog is opened by holding the Ctrl (or Cmd) key when
|
||||
clicking in the workspace.</p>`,
|
||||
"ja": `<p>クイック追加ダイアログを用いて、分岐点を追加することもできます。</p>
|
||||
<p>本ダイアログを開くには、Ctrl(またはCmd)キーを押しながら、ワークスペース上でクリックします。</p>`
|
||||
<p>本ダイアログを開くには、Ctrl(またはCmd)キーを押しながら、ワークスペース上でクリックします。</p>`,
|
||||
"fr": `<p>Les jonctions peuvent également être ajoutées à l'aide de la boîte de dialogue d'ajout rapide.</p>
|
||||
<p>La boîte de dialogue s'ouvre en maintenant la touche Ctrl (ou Cmd) enfoncée lors d'un clic dans l'espace de travail.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Debug Path Tooltip",
|
||||
"ja": "デバッグパスのツールチップ"
|
||||
"ja": "デバッグパスのツールチップ",
|
||||
"fr": "Info-bulle du chemin de débogage"
|
||||
},
|
||||
image: '3.0/images/debug-path-tooltip.png',
|
||||
description: {
|
||||
@@ -73,26 +88,33 @@ export default {
|
||||
the workspace.</p>`,
|
||||
"ja": `<p>デバックサイドバー内のノード名の上にマウスカーソルを乗せると、新たにツールチップが表示され、ノードの場所が分かるようになっています。</p>
|
||||
<p>これは、サブフローを用いる時に役立つ機能であり、メッセージがどのノードから出力されたかを正確に特定することが遥かに簡単になります。</p>
|
||||
<p>本リスト内の要素をクリックすると、ワークスペース内にその要素が表示されます。</p>`
|
||||
<p>本リスト内の要素をクリックすると、ワークスペース内にその要素が表示されます。</p>`,
|
||||
"fr": `<p>Lorsque vous passez la souris sur un nom de noeud dans la barre latérale de débogage, une nouvelle info-bulle affiche l'emplacement complet du noeud.</p>
|
||||
<p>C'est utile lorsque vous travaillez avec des sous-flux, ce qui facilite l'identification exacte du noeud qui a généré le message.</p>
|
||||
<p>Cliquer sur n'importe quel élément de la liste le révélera dans l'espace de travail.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Continuous Search",
|
||||
"ja": "連続した検索"
|
||||
"ja": "連続した検索",
|
||||
"fr": "Recherche continue"
|
||||
},
|
||||
image: '3.0/images/continuous-search.png',
|
||||
description: {
|
||||
"en-US": `<p>When searching for things in the editor, a new toolbar in
|
||||
the workspace provides options to quickly jump between
|
||||
the search results.</p>`,
|
||||
"ja": `<p>ワークスペース内の新しいツールバーにあるオプションによって、エディタ内を検索する際に、検索結果の間を素早く移動できます。</p>`
|
||||
"ja": `<p>ワークスペース内の新しいツールバーにあるオプションによって、エディタ内を検索する際に、検索結果の間を素早く移動できます。</p>`,
|
||||
"fr": `<p>Lorsque vous recherchez des éléments dans l'éditeur, une nouvelle barre d'outils dans l'espace de travail fournit des options pour passer
|
||||
rapidement d'un résultat de recherche à l'autre.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "New wiring actions",
|
||||
"ja": "新しいワイヤー操作"
|
||||
"ja": "新しいワイヤー操作",
|
||||
"fr": "Nouvelles actions de câblage"
|
||||
},
|
||||
image: "3.0/images/split-wire-with-links.gif",
|
||||
description: {
|
||||
@@ -106,12 +128,18 @@ export default {
|
||||
<li><b><code>ワイヤーをlinkノードで分割</code></b></li>
|
||||
</ul>
|
||||
<p>本アクションは、メインメニュー内の動作一覧から呼び出せます。</p>`,
|
||||
"fr": `<p>Une nouvelle action a été ajoutée pour remplacer un fil par une paire de noeuds de lien connectés :</p>
|
||||
<ul>
|
||||
<li><b><code>Diviser le fil avec les noeuds de liaison</code></b></li>
|
||||
</ul>
|
||||
<p>Les actions sont accessibles à partir de la liste d'actions dans le menu principal.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Default node names",
|
||||
"ja": "標準ノードの名前"
|
||||
"ja": "標準ノードの名前",
|
||||
"fr": "Noms de noeud par défaut"
|
||||
},
|
||||
// image: "images/",
|
||||
description: {
|
||||
@@ -129,13 +157,22 @@ export default {
|
||||
<ul>
|
||||
<li><b><code>ノード名を生成</code></b></li>
|
||||
</ul><p>本アクションは、メインメニュー内の動作一覧から呼び出せます。</p>
|
||||
`
|
||||
`,
|
||||
"fr": `<p>Certains noeuds ont été mis à jour pour générer un nom unique lorsque
|
||||
de nouvelles instances sont ajoutées à l'espace de travail. Ceci s'applique aux
|
||||
noeuds <code>Debug</code>, <code>Function</code> et <code>Link</code>.</p>
|
||||
<p>Une nouvelle action a également été ajoutée pour générer des noms par défaut pour les noeuds sélectionnés :</p>
|
||||
<ul>
|
||||
<li><b><code>Générer des noms de noeud</code></b></li>
|
||||
</ul>
|
||||
<p>Les actions sont accessibles à partir de la liste d'actions dans le menu principal.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Node Updates",
|
||||
"ja": "ノードの更新"
|
||||
"ja": "ノードの更新",
|
||||
"fr": "Mises à jour des noeuds"
|
||||
},
|
||||
// image: "images/",
|
||||
description: {
|
||||
@@ -148,6 +185,11 @@ export default {
|
||||
<li>Debugノードは、受信したメッセージの数をカウントするよう設定できるようになりました。</li>
|
||||
<li>Link Callノードは、メッセージのプロパティによって、呼び出し対象のlinkを動的に指定できるようになりました。</li>
|
||||
<li>HTTP Requestノードは、HTTPヘッダを事前設定できるようになりました。</li>
|
||||
</ul>`,
|
||||
"fr": `<ul>
|
||||
<li>Le noeud de débogage peut être configuré pour compter les messages qu'il reçoit</li>
|
||||
<li>Le noeud Link Call peut utiliser une propriété de message pour cibler dynamiquement le lien qu'il doit appeler</li>
|
||||
<li>Le noeud de requête HTTP peut être préconfiguré avec des en-têtes HTTP</li>
|
||||
</ul>`
|
||||
}
|
||||
}
|
||||
|
@@ -3,12 +3,14 @@ export default {
|
||||
{
|
||||
title: {
|
||||
'en-US': 'Create your first flow',
|
||||
'ja': 'はじめてのフローを作成'
|
||||
'ja': 'はじめてのフローを作成',
|
||||
'fr': "Créer votre premier flux"
|
||||
},
|
||||
width: 400,
|
||||
description: {
|
||||
'en-US': 'This tutorial will guide you through creating your first flow',
|
||||
'ja': '本チュートリアルでは、はじめてのフローを作成する方法について説明します。'
|
||||
'ja': '本チュートリアルでは、はじめてのフローを作成する方法について説明します。',
|
||||
'fr': "Ce didacticiel vous guidera dans la création de votre premier flux"
|
||||
},
|
||||
nextButton: 'start'
|
||||
},
|
||||
@@ -16,7 +18,8 @@ export default {
|
||||
element: "#red-ui-workspace .red-ui-tab-button.red-ui-tabs-add",
|
||||
description: {
|
||||
'en-US': 'To add a new tab, click the <i class="fa fa-plus"></i> button',
|
||||
'ja': '新しいタブを追加するため、 <i class="fa fa-plus"></i> ボタンをクリックします。'
|
||||
'ja': '新しいタブを追加するため、 <i class="fa fa-plus"></i> ボタンをクリックします。',
|
||||
'fr': 'Pour ajouter un nouvel onglet, cliquez sur le bouton <i class="fa fa-plus"></i>'
|
||||
},
|
||||
wait: {
|
||||
type: "dom-event",
|
||||
@@ -29,7 +32,8 @@ export default {
|
||||
direction: 'right',
|
||||
description: {
|
||||
'en-US': 'The palette lists all of the nodes available to use. Drag a new Inject node into the workspace.',
|
||||
'ja': 'パレットには、利用できる全てのノードが一覧表示されます。injectノードをワークスペースにドラッグします。'
|
||||
'ja': 'パレットには、利用できる全てのノードが一覧表示されます。injectノードをワークスペースにドラッグします。',
|
||||
'fr': "La palette répertorie tous les noeuds disponibles à utiliser. Faites glisser un nouveau noeud Inject dans l'espace de travail."
|
||||
},
|
||||
fallback: 'inset-bottom-right',
|
||||
wait: {
|
||||
@@ -52,7 +56,8 @@ export default {
|
||||
direction: 'right',
|
||||
description: {
|
||||
'en-US': 'Next, drag a new Debug node into the workspace.',
|
||||
'ja': '次に、debugノードをワークスペースにドラッグします。'
|
||||
'ja': '次に、debugノードをワークスペースにドラッグします。',
|
||||
'fr': "Ensuite, faites glisser un nouveau noeud Debug dans l'espace de travail."
|
||||
},
|
||||
fallback: 'inset-bottom-right',
|
||||
wait: {
|
||||
@@ -74,7 +79,8 @@ export default {
|
||||
element: function() { return $("#"+this.injectNode.id+" .red-ui-flow-port") },
|
||||
description: {
|
||||
'en-US': 'Add a wire from the output of the Inject node to the input of the Debug node',
|
||||
'ja': 'injectノードの出力から、debugノードの入力へワイヤーで接続します。'
|
||||
'ja': 'injectノードの出力から、debugノードの入力へワイヤーで接続します。',
|
||||
'fr': "Ajoutez un fil de la sortie du noeud Inject à l'entrée du noeud Debug"
|
||||
},
|
||||
fallback: 'inset-bottom-right',
|
||||
wait: {
|
||||
@@ -89,7 +95,8 @@ export default {
|
||||
element: "#red-ui-header-button-deploy",
|
||||
description: {
|
||||
'en-US': 'Deploy your changes so the flow is active in the runtime',
|
||||
'ja': 'フローをランタイムで実行させるため、変更をデプロイします。'
|
||||
'ja': 'フローをランタイムで実行させるため、変更をデプロイします。',
|
||||
'fr': "Déployez vos modifications afin que le flux soit actif dans le runtime"
|
||||
},
|
||||
width: 200,
|
||||
wait: {
|
||||
|
@@ -1,21 +1,24 @@
|
||||
export default {
|
||||
version: "3.1.0-beta.2",
|
||||
version: "3.1.0",
|
||||
steps: [
|
||||
{
|
||||
titleIcon: "fa fa-map-o",
|
||||
title: {
|
||||
"en-US": "Welcome to Node-RED 3.1 Beta 2!",
|
||||
"ja": "Node-RED 3.1 ベータ2へようこそ!"
|
||||
"en-US": "Welcome to Node-RED 3.1!",
|
||||
"ja": "Node-RED 3.1へようこそ!",
|
||||
"fr": "Bienvenue dans Node-RED 3.1!"
|
||||
},
|
||||
description: {
|
||||
"en-US": "<p>This is the second beta release for 3.1.0 and we have a few new features to tell you about.</p>",
|
||||
"ja": "<p>これは3.1.0の2回目のベータリリースです。いくつかの新機能について説明します。</p>"
|
||||
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
|
||||
"ja": "<p>本リリースの新機能を見つけてみましょう。</p>",
|
||||
"fr": "<p>Prenons un moment pour découvrir les nouvelles fonctionnalités de cette version.</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "New ways to work with groups",
|
||||
"ja": "グループの新たな操作方法"
|
||||
"ja": "グループの新たな操作方法",
|
||||
"fr": "De nouvelles façons de travailler avec les groupes"
|
||||
},
|
||||
description: {
|
||||
"en-US": `<p>We have changed how you interact with groups in the editor.</p>
|
||||
@@ -31,28 +34,41 @@ export default {
|
||||
<li>「前面へ移動」と「背面へ移動」の動作を用いて、複数のグループの表示順序を変えることができます。</li>
|
||||
<li>グループ内へ一度に複数のノードをドラッグできるようになりました。</li>
|
||||
<li><code>Alt</code> を押したまま、グループ内のノードをドラッグすると、そのグループから *除く* ことができます。</li>
|
||||
</ul>`,
|
||||
"fr": `<p>Nous avons modifié la façon dont vous interagissez avec les groupes dans l'éditeur.</p>
|
||||
<ul>
|
||||
<li>Ils ne gênent plus lorsque vous cliquez sur un noeud</li>
|
||||
<li>Ils peuvent être réorganisés à l'aide des actions Avancer et Reculer</li>
|
||||
<li>Plusieurs noeuds peuvent être glissés dans un groupe en une seule fois</li>
|
||||
<li>Maintenir <code>Alt</code> lors du déplacement d'un noeud le *supprimera* de son groupe</li>
|
||||
</ul>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Change notification on tabs",
|
||||
"ja": "タブ上の変更通知"
|
||||
"ja": "タブ上の変更通知",
|
||||
"fr": "Notification de changement sur les onglets"
|
||||
},
|
||||
image: 'images/tab-changes.png',
|
||||
description: {
|
||||
"en-US": `<p>When a tab contains undeployed changes it now shows the
|
||||
same style of change icon used by nodes.</p>
|
||||
<p>This will make it much easier to track down changes when you're
|
||||
working across multiple flows.</p>`,
|
||||
same style of change icon used by nodes.</p>
|
||||
<p>This will make it much easier to track down changes when you're
|
||||
working across multiple flows.</p>`,
|
||||
"ja": `<p>タブ内にデプロイされていない変更が存在する時は、ノードと同じスタイルで変更の印が表示されるようになりました。</p>
|
||||
<p>これによって複数のフローを編集している時に、変更を見つけるのが簡単になりました。</p>`
|
||||
<p>これによって複数のフローを編集している時に、変更を見つけるのが簡単になりました。</p>`,
|
||||
"fr": `<p>Lorsqu'un onglet contient des modifications non déployées, il affiche désormais le
|
||||
même style d'icône de changement utilisé par les noeuds.</p>
|
||||
<p>Cela facilitera grandement le suivi des modifications lorsque vous
|
||||
travaillez sur plusieurs flux.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "A bigger canvas to work with",
|
||||
"ja": "より広くなった作業キャンバス"
|
||||
"ja": "より広くなった作業キャンバス",
|
||||
"fr": "Un canevas plus grand pour travailler"
|
||||
},
|
||||
description: {
|
||||
"en-US": `<p>The default canvas size has been increased so you can fit more
|
||||
@@ -60,13 +76,18 @@ export default {
|
||||
<p>We still recommend using tools such as subflows and Link Nodes to help
|
||||
keep things organised, but now you have more room to work in.</p>`,
|
||||
"ja": `<p>標準のキャンバスが広くなったため、1つのフローに沢山のものを含めることができるようになりました。</p>
|
||||
<p>引き続き、サブフローやリンクノードなどの方法を用いて整理することをお勧めしますが、作業できる場所が増えました。</p>`
|
||||
<p>引き続き、サブフローやリンクノードなどの方法を用いて整理することをお勧めしますが、作業できる場所が増えました。</p>`,
|
||||
"fr": `<p>La taille par défaut du canevas a été augmentée pour que vous puissiez en mettre plus
|
||||
sur un seul flux.</p>
|
||||
<p>Nous recommandons toujours d'utiliser des outils tels que les sous-flux et les noeuds de lien pour vous aider
|
||||
à garder les choses organisées, mais vous avez maintenant plus d'espace pour travailler.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Finding help",
|
||||
"ja": "ヘルプを見つける"
|
||||
"ja": "ヘルプを見つける",
|
||||
"fr": "Trouver de l'aide"
|
||||
},
|
||||
image: 'images/node-help.png',
|
||||
description: {
|
||||
@@ -74,23 +95,17 @@ export default {
|
||||
in the footer.</p>
|
||||
<p>Clicking it will open up the Help sidebar showing the help for that node.</p>`,
|
||||
"ja": `<p>全てのノードの編集ダイアログの下に、ノードのヘルプへのリンクが追加されました。</p>
|
||||
<p>これをクリックすると、ノードのヘルプサイドバーが表示されます。</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "And lots more...",
|
||||
"ja": "そしてさらに沢山あります..."
|
||||
},
|
||||
description: {
|
||||
"en-US": `<p>Of course we have everything from 3.1.0-beta.1 as well....</p>`,
|
||||
"ja": `<p>もちろん3.1.0 ベータ1の全ての機能があります....</p>`
|
||||
<p>これをクリックすると、ノードのヘルプサイドバーが表示されます。</p>`,
|
||||
"fr": `<p>Toutes les boîtes de dialogue d'édition de noeud incluent désormais un lien vers l'aide de ce noeud
|
||||
dans le pied de page.</p>
|
||||
<p>Cliquer dessus ouvrira la barre latérale d'aide affichant l'aide pour ce noeud.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Improved Context Menu",
|
||||
"ja": "コンテキストメニューの改善"
|
||||
"ja": "コンテキストメニューの改善",
|
||||
"fr": "Menu contextuel amélioré"
|
||||
},
|
||||
image: 'images/context-menu.png',
|
||||
description: {
|
||||
@@ -102,13 +117,17 @@ export default {
|
||||
with your flows much easier.</p>`,
|
||||
"ja": `<p>より多くの組み込み動作を利用できるように、エディタのコンテキストメニューが拡張されました。</p>
|
||||
<p>ノードの追加、グループの操作、その他の便利なツールをクリックするだけで実行できるようになりました。</p>
|
||||
<p>フローのタブバーには、フローの操作をより簡単にする独自のコンテキストメニューもあります。</p>`
|
||||
<p>フローのタブバーには、フローの操作をより簡単にする独自のコンテキストメニューもあります。</p>`,
|
||||
"fr": `<p>Le menu contextuel de l'éditeur a été étendu pour faire beaucoup plus d'actions intégrées disponibles.</p>
|
||||
<p>Ajouter des noeuds, travailler avec des groupes et beaucoup d'autres outils utiles sont désormais à portée de clic.</p>
|
||||
<p>La barre d'onglets de flux possède également son propre menu contextuel pour faciliter l'utilisation de vos flux.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Hiding Flows",
|
||||
"ja": "フローを非表示"
|
||||
"ja": "フローを非表示",
|
||||
"fr": "Masquage de flux"
|
||||
},
|
||||
image: 'images/hiding-flows.png',
|
||||
description: {
|
||||
@@ -116,13 +135,17 @@ export default {
|
||||
<p>The 'hide' button in previous releases has been removed from the tabs
|
||||
as they were being clicked accidentally too often.</p>`,
|
||||
"ja": `<p>フローを非表示にする機能は、フローのコンテキストメニューから実行するようになりました。</p>
|
||||
<p>これまでのリリースでタブに存在していた「非表示」ボタンは、よく誤ってクリックされていたため、削除されました。</p>`
|
||||
<p>これまでのリリースでタブに存在していた「非表示」ボタンは、よく誤ってクリックされていたため、削除されました。</p>`,
|
||||
"fr": `<p>Le masquage des flux s'effectue désormais via le menu contextuel du flux.</p>
|
||||
<p>Le bouton "Masquer" des versions précédentes a été supprimé des onglets
|
||||
car il était cliqué accidentellement trop souvent.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Locking Flows",
|
||||
"ja": "フローを固定"
|
||||
"ja": "フローを固定",
|
||||
"fr": "Verrouillage de flux"
|
||||
},
|
||||
image: 'images/locking-flows.png',
|
||||
description: {
|
||||
@@ -132,13 +155,18 @@ export default {
|
||||
as well as in the Info sidebar explorer.</p>`,
|
||||
"ja": `<p>誤ってフローに変更が加えられてしまうのを防ぐために、フローを固定できるようになりました。</p>
|
||||
<p>固定されている時は、ノードを修正することはできません。</p>
|
||||
<p>フローのコンテキストメニューと、情報サイドバーのエクスプローラには、フローの固定や解除をするためのオプションが用意されています。</p>`
|
||||
<p>フローのコンテキストメニューと、情報サイドバーのエクスプローラには、フローの固定や解除をするためのオプションが用意されています。</p>`,
|
||||
"fr": `<p>Les flux peuvent désormais être verrouillés pour éviter toute modification accidentelle.</p>
|
||||
<p>Lorsqu'il est verrouillé, vous ne pouvez en aucun cas modifier les noeuds.</p>
|
||||
<p>Le menu contextuel du flux fournit les options pour verrouiller et déverrouiller les flux,
|
||||
ainsi que dans l'explorateur de la barre latérale d'informations.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Adding Images to node/flow descriptions",
|
||||
"ja": "ノードやフローの説明へ画像を追加"
|
||||
"ja": "ノードやフローの説明へ画像を追加",
|
||||
"fr": "Ajout d'images aux descriptions de noeud/flux"
|
||||
},
|
||||
// image: 'images/debug-path-tooltip.png',
|
||||
description: {
|
||||
@@ -147,44 +175,56 @@ export default {
|
||||
<p>When the description is shown in the Info sidebar, the image will be displayed.</p>`,
|
||||
"ja": `<p>ノードまたはフローの説明に、画像を追加できるようになりました。</p>
|
||||
<p>画像をテキストエディタにドラッグするだけで、行内に埋め込まれます。</p>
|
||||
<p>情報サイドバーの説明を開くと、その画像が表示されます。</p>`
|
||||
<p>情報サイドバーの説明を開くと、その画像が表示されます。</p>`,
|
||||
"fr": `<p>Vous pouvez désormais ajouter des images à la description d'un noeud ou d'un flux.</p>
|
||||
<p>Faites simplement glisser l'image dans l'éditeur de texte et elle sera ajoutée en ligne.</p>
|
||||
<p>Lorsque la description s'affiche dans la barre latérale d'informations, l'image s'affiche.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Adding Mermaid Diagrams",
|
||||
"ja": "Mermaid図を追加"
|
||||
"ja": "Mermaid図を追加",
|
||||
"fr": "Ajout de diagrammes Mermaid"
|
||||
},
|
||||
image: 'images/mermaid.png',
|
||||
description: {
|
||||
"en-US": `<p>You can also add <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> diagrams directly into your node or flow descriptions.</p>
|
||||
<p>This gives you much richer options for documenting your flows.</p>`,
|
||||
"ja": `<p>ノードやフローの説明に、<a href="https://github.com/mermaid-js/mermaid">Mermaid</a>図を直接追加することもできます。</p>
|
||||
<p>これによって、フローを説明する文書作成の選択肢がより多くなります。</p>`
|
||||
<p>これによって、フローを説明する文書作成の選択肢がより多くなります。</p>`,
|
||||
"fr": `<p>Vous pouvez également ajouter des diagrammes <a href="https://github.com/mermaid-js/mermaid">Mermaid</a> directement dans vos descriptions de noeud ou de flux.</p>
|
||||
<p>Cela vous offre des options beaucoup plus riches pour documenter vos flux.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Managing Global Environment Variables",
|
||||
"ja": "グローバル環境変数の管理"
|
||||
"ja": "グローバル環境変数の管理",
|
||||
"fr": "Gestion des variables d'environnement globales"
|
||||
},
|
||||
image: 'images/global-env-vars.png',
|
||||
description: {
|
||||
"en-US": `<p>You can set environment variables that apply to all nodes and flows in the new
|
||||
'Global Environment Variables' section of User Settings.</p>`,
|
||||
"ja": `<p>ユーザ設定に新しく追加された「大域環境変数」のセクションで、全てのノードとフローに適用される環境変数を登録できます。</p>`
|
||||
"ja": `<p>ユーザ設定に新しく追加された「大域環境変数」のセクションで、全てのノードとフローに適用される環境変数を登録できます。</p>`,
|
||||
"fr": `<p>Vous pouvez définir des variables d'environnement qui s'appliquent à tous les noeuds et flux dans la nouvelle
|
||||
section "Global Environment Variables" des paramètres utilisateur.</p>`
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Node Updates",
|
||||
"ja": "ノードの更新"
|
||||
"ja": "ノードの更新",
|
||||
"fr": "Mises à jour des noeuds"
|
||||
},
|
||||
// image: "images/",
|
||||
description: {
|
||||
"en-US": `<p>The core nodes have received lots of minor fixes, documentation updates and
|
||||
small enhancements. Check the full changelog in the Help sidebar for a full list.</p>`,
|
||||
"ja": `<p>コアノードにマイナーな修正、ドキュメント更新、小規模な拡張が数多く追加されています。全ての一覧は、ヘルプサイドバーの全ての更新履歴を確認してください。</p>`
|
||||
"ja": `<p>コアノードにマイナーな修正、ドキュメント更新、小規模な拡張が数多く追加されています。全ての一覧は、ヘルプサイドバーの全ての更新履歴を確認してください。</p>`,
|
||||
"fr": `<p>Les noeuds principaux ont reçu de nombreux correctifs mineurs, mises à jour de la documentation et
|
||||
petites améliorations. Consulter le journal des modifications complet dans la barre latérale d'aide.</p>`
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@@ -41,7 +41,9 @@
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.scope) {
|
||||
if (this.scope === "group") {
|
||||
return this._("catch.catchGroup");
|
||||
} else if (Array.isArray(this.scope)) {
|
||||
return this._("catch.catchNodes",{number:this.scope.length});
|
||||
}
|
||||
return this.uncaught?this._("catch.catchUncaught"):this._("catch.catch")
|
||||
|
@@ -33,7 +33,15 @@
|
||||
outputs:1,
|
||||
icon: "status.svg",
|
||||
label: function() {
|
||||
return this.name||(this.scope?this._("status.statusNodes",{number:this.scope.length}):this._("status.status"));
|
||||
if (this.name) {
|
||||
return this.name;
|
||||
}
|
||||
if (this.scope === "group") {
|
||||
return this._("status.statusGroup");
|
||||
} else if (Array.isArray(this.scope)) {
|
||||
return this._("status.statusNodes",{number:this.scope.length});
|
||||
}
|
||||
return this._("status.status")
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
|
@@ -28,7 +28,7 @@
|
||||
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-timeout"><span data-i18n="exec.label.timeout"></span></label>
|
||||
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="exec.label.timeout"></span></label>
|
||||
<input type="text" id="node-input-timeout" placeholder="30" style="width: 70px; margin-right: 5px;"><span data-i18n="inject.seconds"></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
|
@@ -82,6 +82,11 @@
|
||||
<input id="node-input-outputs" style="width: 60px;" value="1">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="function.label.timeout"></span></label>
|
||||
<input id="node-input-timeout" style="width: 60px;" data-i18n="[placeholder]join.seconds">
|
||||
</div>
|
||||
|
||||
<div class="form-row node-input-libs-row hide" style="margin-bottom: 0px;">
|
||||
<label><i class="fa fa-cubes"></i> <span data-i18n="function.label.modules"></span></label>
|
||||
</div>
|
||||
@@ -360,6 +365,7 @@
|
||||
name: {value:"_DEFAULT_"},
|
||||
func: {value:"\nreturn msg;"},
|
||||
outputs: {value:1},
|
||||
timeout:{value:0},
|
||||
noerr: {value:0,required:true,
|
||||
validate: function(v, opt) {
|
||||
if (!v) {
|
||||
@@ -464,6 +470,26 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 4294967 is max in node.js timeout.
|
||||
$( "#node-input-timeout" ).spinner({
|
||||
min: 0,
|
||||
max: 4294967,
|
||||
change: function(event, ui) {
|
||||
var value = this.value;
|
||||
if(value == ""){
|
||||
value = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = parseInt(value);
|
||||
}
|
||||
value = isNaN(value) ? 1 : value;
|
||||
value = Math.max(value, parseInt($(this).attr("aria-valuemin")));
|
||||
value = Math.min(value, parseInt($(this).attr("aria-valuemax")));
|
||||
if (value !== this.value) { $(this).spinner("value", value); }
|
||||
}
|
||||
});
|
||||
|
||||
var buildEditor = function(id, stateId, focus, value, defaultValue, extraLibs, offset) {
|
||||
var editor = RED.editor.createEditor({
|
||||
id: id,
|
||||
@@ -503,7 +529,7 @@
|
||||
editor:this.editor, // the field name the main text body goes to
|
||||
mode:"ace/mode/nrjavascript",
|
||||
fields:[
|
||||
'name', 'outputs',
|
||||
'name', 'outputs', 'timeout',
|
||||
{
|
||||
name: 'initialize',
|
||||
get: function() {
|
||||
|
@@ -96,6 +96,13 @@ module.exports = function(RED) {
|
||||
node.name = n.name;
|
||||
node.func = n.func;
|
||||
node.outputs = n.outputs;
|
||||
node.timeout = n.timeout*1000;
|
||||
if(node.timeout>0){
|
||||
node.timeoutOptions = {
|
||||
timeout:node.timeout,
|
||||
breakOnSigint:true
|
||||
}
|
||||
}
|
||||
node.ini = n.initialize ? n.initialize.trim() : "";
|
||||
node.fin = n.finalize ? n.finalize.trim() : "";
|
||||
node.libs = n.libs || [];
|
||||
@@ -362,6 +369,10 @@ module.exports = function(RED) {
|
||||
})(__initSend__);`;
|
||||
iniOpt = createVMOpt(node, " setup");
|
||||
iniScript = new vm.Script(iniText, iniOpt);
|
||||
if(node.timeout>0){
|
||||
iniOpt.timeout = node.timeout;
|
||||
iniOpt.breakOnSigint = true;
|
||||
}
|
||||
}
|
||||
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
||||
if (node.fin && (node.fin !== "")) {
|
||||
@@ -385,6 +396,10 @@ module.exports = function(RED) {
|
||||
})();`;
|
||||
finOpt = createVMOpt(node, " cleanup");
|
||||
finScript = new vm.Script(finText, finOpt);
|
||||
if(node.timeout>0){
|
||||
finOpt.timeout = node.timeout;
|
||||
finOpt.breakOnSigint = true;
|
||||
}
|
||||
}
|
||||
var promise = Promise.resolve();
|
||||
if (iniScript) {
|
||||
@@ -396,9 +411,12 @@ module.exports = function(RED) {
|
||||
var start = process.hrtime();
|
||||
context.msg = msg;
|
||||
context.__send__ = send;
|
||||
context.__done__ = done;
|
||||
|
||||
node.script.runInContext(context);
|
||||
context.__done__ = done;
|
||||
var opts = {};
|
||||
if (node.timeout>0){
|
||||
opts = node.timeoutOptions;
|
||||
}
|
||||
node.script.runInContext(context,opts);
|
||||
context.results.then(function(results) {
|
||||
sendResults(node,send,msg._msgid,results,false);
|
||||
if (handleNodeDoneCall) {
|
||||
|
@@ -103,6 +103,7 @@
|
||||
} else if (type === "istype") {
|
||||
r.v = rule.find(".node-input-rule-type-value").typedInput('type');
|
||||
r.vt = rule.find(".node-input-rule-type-value").typedInput('type');
|
||||
r.vt = (r.vt === "number") ? "num" : "str";
|
||||
} else if (type === "jsonata_exp") {
|
||||
r.v = rule.find(".node-input-rule-exp-value").typedInput('value');
|
||||
r.vt = rule.find(".node-input-rule-exp-value").typedInput('type');
|
||||
|
@@ -229,6 +229,7 @@ module.exports = function(RED) {
|
||||
node.on("input", function(msg, send, done) {
|
||||
if (!node.drop) {
|
||||
var m = RED.util.cloneMessage(msg);
|
||||
delete m.flush;
|
||||
if (Object.keys(m).length > 1) {
|
||||
if (node.intervalID !== -1) {
|
||||
if (node.allowrate && m.hasOwnProperty("rate") && !isNaN(parseFloat(m.rate)) && node.rate !== m.rate) {
|
||||
|
@@ -219,8 +219,10 @@ module.exports = function(RED) {
|
||||
* Handle the payload / packet recieved in MQTT In and MQTT Sub nodes
|
||||
*/
|
||||
function subscriptionHandler(node, datatype ,topic, payload, packet) {
|
||||
const v5 = node.brokerConn.options && node.brokerConn.options.protocolVersion == 5;
|
||||
var msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
|
||||
const msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain};
|
||||
const v5 = (node && node.brokerConn)
|
||||
? node.brokerConn.v5()
|
||||
: Object.prototype.hasOwnProperty.call(packet, "properties");
|
||||
if(v5 && packet.properties) {
|
||||
setStrProp(packet.properties, msg, "responseTopic");
|
||||
setBufferProp(packet.properties, msg, "correlationData");
|
||||
@@ -300,7 +302,7 @@ module.exports = function(RED) {
|
||||
//}
|
||||
}
|
||||
msg.payload = payload;
|
||||
if ((node.brokerConn.broker === "localhost")||(node.brokerConn.broker === "127.0.0.1")) {
|
||||
if (node.brokerConn && (node.brokerConn.broker === "localhost" || node.brokerConn.broker === "127.0.0.1")) {
|
||||
msg._topic = topic;
|
||||
}
|
||||
node.send(msg);
|
||||
@@ -412,6 +414,12 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the connect action
|
||||
* @param {MQTTInNode|MQTTOutNode} node
|
||||
* @param {Object} msg
|
||||
* @param {Function} done
|
||||
*/
|
||||
function handleConnectAction(node, msg, done) {
|
||||
let actionData = typeof msg.broker === 'object' ? msg.broker : null;
|
||||
if (node.brokerConn.canConnect()) {
|
||||
@@ -442,12 +450,17 @@ module.exports = function(RED) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the disconnect action
|
||||
* @param {MQTTInNode|MQTTOutNode} node
|
||||
* @param {Function} done
|
||||
*/
|
||||
function handleDisconnectAction(node, done) {
|
||||
node.brokerConn.disconnect(function () {
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
const unsubscribeCandidates = {}
|
||||
//#endregion "Supporting functions"
|
||||
|
||||
//#region "Broker node"
|
||||
@@ -591,10 +604,9 @@ module.exports = function(RED) {
|
||||
if (typeof node.cleansession === 'undefined') {
|
||||
node.cleansession = true;
|
||||
}
|
||||
if (typeof node.autoUnsubscribe === 'undefined') {
|
||||
if (typeof node.autoUnsubscribe !== 'boolean') {
|
||||
node.autoUnsubscribe = true;
|
||||
}
|
||||
|
||||
//use url or build a url from usetls://broker:port
|
||||
if (node.url && node.brokerurl !== node.url) {
|
||||
node.brokerurl = node.url;
|
||||
@@ -664,7 +676,6 @@ module.exports = function(RED) {
|
||||
node.options.password = node.password;
|
||||
node.options.keepalive = node.keepalive;
|
||||
node.options.clean = node.cleansession;
|
||||
node.options.autoUnsubscribe = node.autoUnsubscribe;
|
||||
node.options.clientId = node.clientid || 'nodered_' + RED.util.generateId();
|
||||
node.options.reconnectPeriod = RED.settings.mqttReconnectTime||5000;
|
||||
delete node.options.protocolId; //V4+ default
|
||||
@@ -702,7 +713,8 @@ module.exports = function(RED) {
|
||||
node.options.rejectUnauthorized = (node.verifyservercert == "true" || node.verifyservercert === true);
|
||||
}
|
||||
}
|
||||
|
||||
node.v5 = () => node.options && node.options.protocolVersion == 5
|
||||
node.subscriptionIdentifiersAvailable = () => node.v5() && node.serverProperties && node.serverProperties.subscriptionIdentifiersAvailable
|
||||
n.autoConnect = n.autoConnect === "false" || n.autoConnect === false ? false : true;
|
||||
node.setOptions(n, true);
|
||||
|
||||
@@ -784,18 +796,11 @@ module.exports = function(RED) {
|
||||
// Re-subscribe to stored topics
|
||||
for (var s in node.subscriptions) {
|
||||
if (node.subscriptions.hasOwnProperty(s)) {
|
||||
let topic = s;
|
||||
let qos = 0;
|
||||
let _options = {};
|
||||
for (var r in node.subscriptions[s]) {
|
||||
if (node.subscriptions[s].hasOwnProperty(r)) {
|
||||
qos = Math.max(qos,node.subscriptions[s][r].qos);
|
||||
_options = node.subscriptions[s][r].options;
|
||||
node._clientOn('message',node.subscriptions[s][r].handler);
|
||||
node.subscribe(node.subscriptions[s][r])
|
||||
}
|
||||
}
|
||||
_options.qos = _options.qos || qos;
|
||||
node.client.subscribe(topic, _options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,22 +862,28 @@ module.exports = function(RED) {
|
||||
if(!node.client) { return _callback(); }
|
||||
if(node.closing) { return _callback(); }
|
||||
|
||||
/**
|
||||
* Call end and wait for the client to end (or timeout)
|
||||
* @param {mqtt.MqttClient} client The broker client
|
||||
* @param {number} ms The time to wait for the client to end
|
||||
* @returns
|
||||
*/
|
||||
let waitEnd = (client, ms) => {
|
||||
return new Promise( (resolve, reject) => {
|
||||
node.closing = true;
|
||||
if(!client) {
|
||||
if (!client) {
|
||||
resolve();
|
||||
} else {
|
||||
} else {
|
||||
const t = setTimeout(() => {
|
||||
//clean end() has exceeded WAIT_END, lets force end!
|
||||
client && client.end(true);
|
||||
reject();
|
||||
resolve();
|
||||
}, ms);
|
||||
client.end(() => {
|
||||
clearTimeout(t);
|
||||
resolve()
|
||||
});
|
||||
}
|
||||
clearTimeout(t);
|
||||
resolve()
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
if(node.connected && node.closeMessage) {
|
||||
@@ -893,64 +904,222 @@ module.exports = function(RED) {
|
||||
}
|
||||
node.subscriptionIds = {};
|
||||
node.subid = 1;
|
||||
node.subscribe = function (topic,options,callback,ref) {
|
||||
ref = ref||0;
|
||||
var qos;
|
||||
if(typeof options == "object") {
|
||||
qos = options.qos;
|
||||
} else {
|
||||
qos = options;
|
||||
options = {};
|
||||
|
||||
//typedef for subscription object:
|
||||
/**
|
||||
* @typedef {Object} Subscription
|
||||
* @property {String} topic - topic to subscribe to
|
||||
* @property {Object} [options] - options object
|
||||
* @property {Number} [options.qos] - quality of service
|
||||
* @property {Number} [options.nl] - no local
|
||||
* @property {Number} [options.rap] - retain as published
|
||||
* @property {Number} [options.rh] - retain handling
|
||||
* @property {Number} [options.properties] - MQTT 5.0 properties
|
||||
* @property {Number} [options.properties.subscriptionIdentifier] - MQTT 5.0 subscription identifier
|
||||
* @property {Number} [options.properties.userProperties] - MQTT 5.0 user properties
|
||||
* @property {Function} callback
|
||||
* @property {String} ref - reference to the node that created the subscription
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a subscription object
|
||||
* @param {String} _topic - topic to subscribe to
|
||||
* @param {Object} _options - options object
|
||||
* @param {String} _ref - reference to the node that created the subscription
|
||||
* @returns {Subscription}
|
||||
*/
|
||||
function createSubscriptionObject(_topic, _options, _ref, _brokerId) {
|
||||
/** @type {Subscription} */
|
||||
const subscription = {};
|
||||
const ref = _ref || 0;
|
||||
let options
|
||||
let qos = 1 // default to QoS 1 (AWS and several other brokers don't support QoS 2)
|
||||
|
||||
// if options is an object, then clone it
|
||||
if (typeof _options == "object") {
|
||||
options = RED.util.cloneMessage(_options || {})
|
||||
qos = _options.qos;
|
||||
} else if (typeof _options == "number") {
|
||||
qos = _options;
|
||||
}
|
||||
options.qos = qos;
|
||||
options = options || {};
|
||||
|
||||
// sanitise qos
|
||||
if (typeof qos === "number" && qos >= 0 && qos <= 2) {
|
||||
options.qos = qos;
|
||||
}
|
||||
|
||||
subscription.topic = _topic;
|
||||
subscription.qos = qos;
|
||||
subscription.options = RED.util.cloneMessage(options);
|
||||
subscription.ref = ref;
|
||||
subscription.brokerId = _brokerId;
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* If topic is a subscription object, then use that, otherwise look up the topic in
|
||||
* the subscriptions object. If the topic is not found, then create a new subscription
|
||||
* object and add it to the subscriptions object.
|
||||
* @param {Subscription|String} topic
|
||||
* @param {*} options
|
||||
* @param {*} callback
|
||||
* @param {*} ref
|
||||
*/
|
||||
node.subscribe = function (topic, options, callback, ref) {
|
||||
/** @type {Subscription} */
|
||||
let subscription
|
||||
let doCompare = false
|
||||
let changesFound = false
|
||||
|
||||
// function signature 1: subscribe(subscription: Subscription)
|
||||
if (typeof topic === "object" && topic !== null) {
|
||||
subscription = topic
|
||||
topic = subscription.topic
|
||||
options = subscription.options
|
||||
ref = subscription.ref
|
||||
callback = subscription.callback
|
||||
}
|
||||
|
||||
// function signature 2: subscribe(topic: String, options: Object, callback: Function, ref: String)
|
||||
else if (typeof topic === "string") {
|
||||
// since this is a call where all params are provided, it might be
|
||||
// a node change (modification) so we need to check for changes
|
||||
doCompare = true
|
||||
subscription = node.subscriptions[topic] && node.subscriptions[topic][ref]
|
||||
}
|
||||
|
||||
// bad function call
|
||||
else {
|
||||
console.warn('Invalid call to node.subscribe')
|
||||
return
|
||||
}
|
||||
const thisBrokerId = node.type === 'mqtt-broker' ? node.id : node.broker
|
||||
|
||||
// unsubscribe topics where the broker has changed
|
||||
const oldBrokerSubs = (unsubscribeCandidates[ref] || []).filter(sub => sub.brokerId !== thisBrokerId)
|
||||
oldBrokerSubs.forEach(sub => {
|
||||
/** @type {MQTTBrokerNode} */
|
||||
const _brokerConn = RED.nodes.getNode(sub.brokerId)
|
||||
if (_brokerConn) {
|
||||
_brokerConn.unsubscribe(sub.topic, sub.ref, true)
|
||||
}
|
||||
})
|
||||
|
||||
// if subscription is found (or sent in as a parameter), then check for changes.
|
||||
// if there are any changes requested, tidy up the old subscription
|
||||
if (subscription) {
|
||||
if (doCompare) {
|
||||
// compare the current sub to the passed in parameters. Use RED.util.compareObjects against
|
||||
// only the minimal set of properties to identify if the subscription has changed
|
||||
const currentSubscription = createSubscriptionObject(subscription.topic, subscription.options, subscription.ref)
|
||||
const newSubscription = createSubscriptionObject(topic, options, ref)
|
||||
changesFound = RED.util.compareObjects(currentSubscription, newSubscription) === false
|
||||
}
|
||||
}
|
||||
|
||||
if (changesFound) {
|
||||
if (subscription.handler) {
|
||||
node._clientRemoveListeners('message', subscription.handler)
|
||||
subscription.handler = null
|
||||
}
|
||||
const _brokerConn = RED.nodes.getNode(subscription.brokerId)
|
||||
if (_brokerConn) {
|
||||
_brokerConn.unsubscribe(subscription.topic, subscription.ref, true)
|
||||
}
|
||||
}
|
||||
|
||||
// clean up the unsubscribe candidate list
|
||||
delete unsubscribeCandidates[ref]
|
||||
|
||||
// determine if this is an existing subscription
|
||||
const existingSubscription = typeof subscription === "object" && subscription !== null
|
||||
|
||||
// if existing subscription is not found or has changed, create a new subscription object
|
||||
if (existingSubscription === false || changesFound) {
|
||||
subscription = createSubscriptionObject(topic, options, ref, node.id)
|
||||
}
|
||||
|
||||
// setup remainder of subscription properties and event handling
|
||||
node.subscriptions[topic] = node.subscriptions[topic] || {};
|
||||
node.subscriptions[topic][ref] = subscription
|
||||
if (!node.subscriptionIds[topic]) {
|
||||
node.subscriptionIds[topic] = node.subid++;
|
||||
}
|
||||
options.properties = options.properties || {};
|
||||
options.properties.subscriptionIdentifier = node.subscriptionIds[topic];
|
||||
subscription.options = subscription.options || {};
|
||||
subscription.options.properties = options.properties || {};
|
||||
subscription.options.properties.subscriptionIdentifier = node.subscriptionIds[topic];
|
||||
subscription.callback = callback;
|
||||
|
||||
node.subscriptions[topic] = node.subscriptions[topic]||{};
|
||||
var sub = {
|
||||
topic:topic,
|
||||
qos:qos,
|
||||
options:options,
|
||||
handler:function(mtopic,mpayload, mpacket) {
|
||||
if(mpacket.properties && options.properties && mpacket.properties.subscriptionIdentifier && options.properties.subscriptionIdentifier && (mpacket.properties.subscriptionIdentifier !== options.properties.subscriptionIdentifier) ) {
|
||||
//do nothing as subscriptionIdentifier does not match
|
||||
} else if (matchTopic(topic,mtopic)) {
|
||||
callback(mtopic,mpayload, mpacket);
|
||||
}
|
||||
},
|
||||
ref: ref
|
||||
};
|
||||
node.subscriptions[topic][ref] = sub;
|
||||
// if the client is connected, then setup the handler and subscribe
|
||||
if (node.connected) {
|
||||
node._clientOn('message',sub.handler);
|
||||
node.client.subscribe(topic, options);
|
||||
}
|
||||
};
|
||||
const subIdsAvailable = node.subscriptionIdentifiersAvailable()
|
||||
|
||||
node.unsubscribe = function (topic, ref, removed) {
|
||||
ref = ref||0;
|
||||
var sub = node.subscriptions[topic];
|
||||
if (sub) {
|
||||
if (sub[ref]) {
|
||||
if(node.client) {
|
||||
node._clientRemoveListeners('message',sub[ref].handler);
|
||||
}
|
||||
delete sub[ref];
|
||||
}
|
||||
//TODO: Review. The `if(removed)` was commented out to always delete and remove subscriptions.
|
||||
// if we dont then property changes dont get applied and old subs still trigger
|
||||
//if (removed) {
|
||||
if (Object.keys(sub).length === 0) {
|
||||
delete node.subscriptions[topic];
|
||||
delete node.subscriptionIds[topic];
|
||||
if (node.connected) {
|
||||
node.client.unsubscribe(topic);
|
||||
if (!subscription.handler) {
|
||||
subscription.handler = function (mtopic, mpayload, mpacket) {
|
||||
const sops = subscription.options ? subscription.options.properties : {}
|
||||
const pops = mpacket.properties || {}
|
||||
if (subIdsAvailable && pops.subscriptionIdentifier && sops.subscriptionIdentifier && (pops.subscriptionIdentifier !== sops.subscriptionIdentifier)) {
|
||||
//do nothing as subscriptionIdentifier does not match
|
||||
} else if (matchTopic(topic, mtopic)) {
|
||||
subscription.callback && subscription.callback(mtopic, mpayload, mpacket)
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
node._clientOn('message', subscription.handler)
|
||||
// if the broker doesn't support subscription identifiers, then don't send them (AWS support)
|
||||
if (subscription.options.properties && subscription.options.properties.subscriptionIdentifier && subIdsAvailable !== true) {
|
||||
delete subscription.options.properties.subscriptionIdentifier
|
||||
}
|
||||
node.client.subscribe(topic, subscription.options)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
node.unsubscribe = function (topic, ref, removeClientSubscription) {
|
||||
ref = ref||0;
|
||||
const unsub = removeClientSubscription || node.autoUnsubscribe !== false
|
||||
const sub = node.subscriptions[topic];
|
||||
let brokerId = node.id
|
||||
if (sub) {
|
||||
if (sub[ref]) {
|
||||
brokerId = sub[ref].brokerId || brokerId
|
||||
if(node.client && sub[ref].handler) {
|
||||
node._clientRemoveListeners('message', sub[ref].handler);
|
||||
sub[ref].handler = null
|
||||
}
|
||||
if (unsub) {
|
||||
delete sub[ref]
|
||||
}
|
||||
}
|
||||
// if instructed to remove the actual MQTT client subscription
|
||||
if (unsub) {
|
||||
// if there are no more subscriptions for the topic, then remove the topic
|
||||
if (Object.keys(sub).length === 0) {
|
||||
try {
|
||||
node.client.unsubscribe(topic)
|
||||
} catch (_err) {
|
||||
// do nothing
|
||||
} finally {
|
||||
// remove unsubscribe candidate as it is now REALLY unsubscribed
|
||||
delete node.subscriptions[topic];
|
||||
delete node.subscriptionIds[topic];
|
||||
if (unsubscribeCandidates[ref]) {
|
||||
unsubscribeCandidates[ref] = unsubscribeCandidates[ref].filter(sub => sub.topic !== topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if instructed to not remove the client subscription, then add it to the candidate list
|
||||
// of subscriptions to be removed when the the same ref is used in a subsequent subscribe
|
||||
// and the topic has changed
|
||||
unsubscribeCandidates[ref] = unsubscribeCandidates[ref] || [];
|
||||
unsubscribeCandidates[ref].push({
|
||||
topic: topic,
|
||||
ref: ref,
|
||||
brokerId: brokerId
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
node.topicAliases = {};
|
||||
@@ -988,7 +1157,7 @@ module.exports = function(RED) {
|
||||
setStrProp(msg, options.properties, "contentType");
|
||||
setIntProp(msg, options.properties, "messageExpiryInterval", 0);
|
||||
setUserProperties(msg.userProperties, options.properties);
|
||||
setIntProp(msg, options.properties, "topicAlias", 1, node.serverProperties.topicAliasMaximum || 0);
|
||||
setIntProp(msg, options.properties, "topicAlias", 1, bsp.topicAliasMaximum || 0);
|
||||
setBoolProp(msg, options.properties, "payloadFormatIndicator");
|
||||
//FUTURE setIntProp(msg, options.properties, "subscriptionIdentifier", 1, 268435455);
|
||||
|
||||
@@ -1124,7 +1293,7 @@ module.exports = function(RED) {
|
||||
if(node.rap === "true" || node.rap === true) options.rap = true;
|
||||
else if(node.rap === "false" || node.rap === false) options.rap = false;
|
||||
}
|
||||
|
||||
node._topic = node.topic; // store the original topic incase node is later changed
|
||||
node.brokerConn.subscribe(node.topic,options,function(topic, payload, packet) {
|
||||
subscriptionHandler(node, node.datatype, topic, payload, packet);
|
||||
},node.id);
|
||||
@@ -1177,7 +1346,7 @@ module.exports = function(RED) {
|
||||
}
|
||||
if (action === Actions.UNSUBSCRIBE) {
|
||||
subscriptions.forEach(function (sub) {
|
||||
node.brokerConn.unsubscribe(sub.topic, node.id);
|
||||
node.brokerConn.unsubscribe(sub.topic, node.id, true);
|
||||
delete node.dynamicSubs[sub.topic];
|
||||
})
|
||||
//user can access current subscriptions through the complete node is so desired
|
||||
@@ -1187,7 +1356,7 @@ module.exports = function(RED) {
|
||||
subscriptions.forEach(function (sub) {
|
||||
//always unsubscribe before subscribe to prevent multiple subs to same topic
|
||||
if (node.dynamicSubs[sub.topic]) {
|
||||
node.brokerConn.unsubscribe(sub.topic, node.id);
|
||||
node.brokerConn.unsubscribe(sub.topic, node.id, true);
|
||||
delete node.dynamicSubs[sub.topic];
|
||||
}
|
||||
|
||||
@@ -1233,16 +1402,12 @@ module.exports = function(RED) {
|
||||
node.on('close', function(removed, done) {
|
||||
if (node.brokerConn) {
|
||||
if(node.isDynamic) {
|
||||
if (node.brokerConn.options.autoUnsubscribe) {
|
||||
Object.keys(node.dynamicSubs).forEach(function (topic) {
|
||||
node.brokerConn.unsubscribe(topic, node.id, removed);
|
||||
});
|
||||
node.dynamicSubs = {};
|
||||
}
|
||||
Object.keys(node.dynamicSubs).forEach(function (topic) {
|
||||
node.brokerConn.unsubscribe(topic, node.id, removed);
|
||||
});
|
||||
node.dynamicSubs = {};
|
||||
} else {
|
||||
if (node.brokerConn.options.autoUnsubscribe) {
|
||||
node.brokerConn.unsubscribe(node.topic, node.id, removed);
|
||||
}
|
||||
node.brokerConn.unsubscribe(node.topic, node.id, removed);
|
||||
}
|
||||
node.brokerConn.deregister(node, done, removed);
|
||||
node.brokerConn = null;
|
||||
|
@@ -93,7 +93,7 @@
|
||||
|
||||
<div class="form-row">
|
||||
<input type="checkbox" id="node-input-insecureHTTPParser" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-insecureHTTPParser", style="width: auto;" data-i18n="httpin.insecureHTTPParser"></label>
|
||||
<label for="node-input-insecureHTTPParser" style="width: auto;" data-i18n="httpin.insecureHTTPParser"></label>
|
||||
</div>
|
||||
|
||||
|
||||
|
@@ -23,6 +23,8 @@ module.exports = async function(RED) {
|
||||
const { v4: uuid } = require('uuid');
|
||||
const crypto = require('crypto');
|
||||
const URL = require("url").URL
|
||||
const http = require("http")
|
||||
const https = require("https")
|
||||
var mustache = require("mustache");
|
||||
var querystring = require("querystring");
|
||||
var cookie = require("cookie");
|
||||
@@ -65,16 +67,27 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
function HTTPRequest(n) {
|
||||
RED.nodes.createNode(this,n);
|
||||
checkNodeAgentPatch();
|
||||
var node = this;
|
||||
var nodeUrl = n.url;
|
||||
var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
|
||||
var nodeMethod = n.method || "GET";
|
||||
var paytoqs = false;
|
||||
var paytobody = false;
|
||||
var redirectList = [];
|
||||
var sendErrorsToCatch = n.senderr;
|
||||
const node = this;
|
||||
const nodeUrl = n.url;
|
||||
const isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1;
|
||||
const nodeMethod = n.method || "GET";
|
||||
let paytoqs = false;
|
||||
let paytobody = false;
|
||||
let redirectList = [];
|
||||
const sendErrorsToCatch = n.senderr;
|
||||
node.headers = n.headers || [];
|
||||
var nodeHTTPPersistent = n["persist"];
|
||||
const useKeepAlive = n["persist"];
|
||||
let agents = null
|
||||
if (useKeepAlive) {
|
||||
agents = {
|
||||
http: new http.Agent({ keepAlive: true }),
|
||||
https: new https.Agent({ keepAlive: true })
|
||||
}
|
||||
node.on('close', function () {
|
||||
agents.http.destroy()
|
||||
agents.https.destroy()
|
||||
})
|
||||
}
|
||||
if (n.tls) {
|
||||
var tlsNode = RED.nodes.getNode(n.tls);
|
||||
}
|
||||
@@ -560,12 +573,14 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
opts.agent = {
|
||||
http: new HttpProxyAgent(proxyOptions),
|
||||
https: new HttpsProxyAgent(proxyOptions)
|
||||
};
|
||||
|
||||
}
|
||||
} else {
|
||||
node.warn("Bad proxy url: "+ prox);
|
||||
}
|
||||
}
|
||||
if (useKeepAlive && !opts.agent) {
|
||||
opts.agent = agents
|
||||
}
|
||||
if (tlsNode) {
|
||||
opts.https = {};
|
||||
tlsNode.addTLSOptions(opts.https);
|
||||
@@ -622,6 +637,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
|
||||
msg.payload = msg.payload.toString('utf8'); // txt
|
||||
|
||||
if (node.ret === "obj") {
|
||||
if (msg.statusCode == 204){msg.payload= "{}"};
|
||||
try { msg.payload = JSON.parse(msg.payload); } // obj
|
||||
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
|
||||
}
|
||||
|
@@ -629,6 +629,9 @@ module.exports = function(RED) {
|
||||
joinChar = node.joiner;
|
||||
if (n.count === "" && msg.hasOwnProperty('parts')) {
|
||||
targetCount = msg.parts.count || 0;
|
||||
if (msg.parts.hasOwnProperty('id')) {
|
||||
partId = msg.parts.id;
|
||||
}
|
||||
}
|
||||
if (node.build === 'object') {
|
||||
propertyKey = RED.util.getMessageProperty(msg,node.key);
|
||||
|
@@ -68,9 +68,15 @@ module.exports = function(RED) {
|
||||
node.error(err,msg);
|
||||
return done();
|
||||
} else {
|
||||
filename = value;
|
||||
if (typeof value !== 'string' && value !== null && value !== undefined) {
|
||||
value = value.toString();
|
||||
}
|
||||
processMsg2(msg,nodeSend,value,done);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processMsg2(msg,nodeSend,filename,done) {
|
||||
filename = filename || "";
|
||||
msg.filename = filename;
|
||||
var fullFilename = filename;
|
||||
@@ -275,7 +281,6 @@ module.exports = function(RED) {
|
||||
}
|
||||
RED.nodes.registerType("file",FileNode);
|
||||
|
||||
|
||||
function FileInNode(n) {
|
||||
// Read a file
|
||||
RED.nodes.createNode(this,n);
|
||||
@@ -311,9 +316,15 @@ module.exports = function(RED) {
|
||||
node.error(err,msg);
|
||||
return done();
|
||||
} else {
|
||||
filename = (value || "").replace(/\t|\r|\n/g,'');
|
||||
if (typeof value !== 'string' && value !== null && value !== undefined) {
|
||||
value = value.toString();
|
||||
}
|
||||
processMsg2(msg, nodeSend, (value || "").replace(/\t|\r|\n/g,''), nodeDone);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function processMsg2(msg, nodeSend, filename, nodeDone) {
|
||||
filename = filename || "";
|
||||
var fullFilename = filename;
|
||||
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
||||
@@ -434,7 +445,8 @@ module.exports = function(RED) {
|
||||
nodeDone();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.on('close', function() {
|
||||
node.status({});
|
||||
});
|
||||
|
@@ -216,7 +216,8 @@
|
||||
"initialize": "Start",
|
||||
"finalize": "Stopp",
|
||||
"outputs": "Ausgänge",
|
||||
"modules": "Module"
|
||||
"modules": "Module",
|
||||
"timeout": "Timeout"
|
||||
},
|
||||
"text": {
|
||||
"initialize": "// Der Code hier wird ausgeführt,\n// wenn der Node gestartet wird\n",
|
||||
|
@@ -36,5 +36,5 @@ greater than one day you should consider using a scheduler node that can cope wi
|
||||
<p><b>Note</b>: The <i>"Interval between times"</i> and <i>"at a specific time"</i> options use the standard cron system.
|
||||
This means that 20 minutes will be at the next hour, 20 minutes past and 40 minutes past - not in 20 minutes time.
|
||||
If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
|
||||
<p><b>Note</b>: To include a newline in a string you must use a Function node to create the payload.</p>
|
||||
<p><b>Note</b>: To include a newline in a string you must use the Function or Template node to create the payload.</p>
|
||||
</script>
|
||||
|
@@ -94,6 +94,7 @@
|
||||
},
|
||||
"catch": {
|
||||
"catch": "catch: all",
|
||||
"catchGroup": "catch: group",
|
||||
"catchNodes": "catch: __number__",
|
||||
"catchUncaught": "catch: uncaught",
|
||||
"label": {
|
||||
@@ -109,6 +110,7 @@
|
||||
},
|
||||
"status": {
|
||||
"status": "status: all",
|
||||
"statusGroup": "status: group",
|
||||
"statusNodes": "status: __number__",
|
||||
"label": {
|
||||
"source": "Report status from",
|
||||
@@ -250,7 +252,8 @@
|
||||
"initialize": "On Start",
|
||||
"finalize": "On Stop",
|
||||
"outputs": "Outputs",
|
||||
"modules": "Modules"
|
||||
"modules": "Modules",
|
||||
"timeout": "Timeout"
|
||||
},
|
||||
"text": {
|
||||
"initialize": "// Code added here will be run once\n// whenever the node is started.\n",
|
||||
|
@@ -103,6 +103,7 @@
|
||||
},
|
||||
"scope": {
|
||||
"all": "tous les noeuds",
|
||||
"group": "dans le même groupe",
|
||||
"selected": "noeuds sélectionnés"
|
||||
}
|
||||
},
|
||||
@@ -115,6 +116,7 @@
|
||||
},
|
||||
"scope": {
|
||||
"all": "tous les noeuds",
|
||||
"group": "dans le même groupe",
|
||||
"selected": "noeuds sélectionnés"
|
||||
}
|
||||
},
|
||||
@@ -414,6 +416,7 @@
|
||||
"port": "Port",
|
||||
"keepalive": "Rester en vie",
|
||||
"cleansession": "Utiliser une session propre",
|
||||
"autoUnsubscribe": "Se désabonner automatiquement lors de la déconnexion",
|
||||
"cleanstart": "Utiliser un démarrage propre",
|
||||
"use-tls": "Utiliser TLS",
|
||||
"tls-config": "Configuration TLS",
|
||||
|
@@ -30,5 +30,5 @@
|
||||
<p>また、フロー開始の際に一度だけメッセージを送出させることもできます。</p>
|
||||
<p>「<i>時間間隔</i>」に指定可能な値の最大値は、約596時間(もしくは24日)です。一日より長い間隔を扱いたい場合は、電源停止や再起動にも対応可能なスケジューラノードの利用を検討すると良いでしょう。</p>
|
||||
<p><b>注</b>:「<i>指定した時間間隔、日時</i>」と「<i>指定した日時</i>」オプションは標準的なcronシステムを内部で利用します。したがって「20分」という指定は、その時点から20分後ではなく、毎時きっかり、20分、40分を意味します。現時刻から20分毎を指定するには「<i>指定した時間間隔</i>」オプションを用います。</p>
|
||||
<p><b>注</b>: 文字列に改行を含めたい場合は、functionノードを使ってペイロードを設定してください。</p>
|
||||
<p><b>注</b>: 文字列に改行を含めたい場合は、functionノードまたはtemplateノードを使ってペイロードを設定してください。</p>
|
||||
</script>
|
||||
|
@@ -94,6 +94,7 @@
|
||||
},
|
||||
"catch": {
|
||||
"catch": "catch: 全て",
|
||||
"catchGroup": "catch: グループ",
|
||||
"catchNodes": "catch: __number__",
|
||||
"catchUncaught": "catch: 未補足",
|
||||
"label": {
|
||||
@@ -103,11 +104,13 @@
|
||||
},
|
||||
"scope": {
|
||||
"all": "全てのノード",
|
||||
"group": "同一グループ内",
|
||||
"selected": "選択したノード"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"status": "status: 全て",
|
||||
"statusGroup": "status: グループ",
|
||||
"statusNodes": "status: __number__",
|
||||
"label": {
|
||||
"source": "ステータス取得元",
|
||||
@@ -115,6 +118,7 @@
|
||||
},
|
||||
"scope": {
|
||||
"all": "全てのノード",
|
||||
"group": "同一グループ内",
|
||||
"selected": "選択したノード"
|
||||
}
|
||||
},
|
||||
@@ -248,7 +252,8 @@
|
||||
"initialize": "初期化処理",
|
||||
"finalize": "終了処理",
|
||||
"outputs": "出力数",
|
||||
"modules": "モジュール"
|
||||
"modules": "モジュール",
|
||||
"timeout": "タイムアウト"
|
||||
},
|
||||
"text": {
|
||||
"initialize": "// ここに記述したコードは、ノードをデプロイした時に\n// 一度だけ実行されます。\n",
|
||||
|
@@ -212,7 +212,8 @@
|
||||
"function": "Функция",
|
||||
"initialize": "Настройка",
|
||||
"finalize": "Закрытие",
|
||||
"outputs": "Выходы"
|
||||
"outputs": "Выходы",
|
||||
"timeout":"Время ожидания"
|
||||
},
|
||||
"text": {
|
||||
"initialize": "// Добавленный здесь код будет исполняться\n// однократно при развертывании узла.\n",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/nodes",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.0-beta.4",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/registry",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.0-beta.4",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "3.1.0-beta.2",
|
||||
"@node-red/util": "3.1.0-beta.4",
|
||||
"clone": "2.1.2",
|
||||
"fs-extra": "11.1.1",
|
||||
"semver": "7.5.0",
|
||||
|
@@ -14,19 +14,20 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var clone = require("clone");
|
||||
var redUtil = require("@node-red/util").util;
|
||||
const clone = require("clone");
|
||||
const redUtil = require("@node-red/util").util;
|
||||
const events = require("@node-red/util").events;
|
||||
var flowUtil = require("./util");
|
||||
const flowUtil = require("./util");
|
||||
const context = require('../nodes/context');
|
||||
const hooks = require("@node-red/util").hooks;
|
||||
const credentials = require("../nodes/credentials");
|
||||
|
||||
var Subflow;
|
||||
var Log;
|
||||
let Subflow;
|
||||
let Log;
|
||||
let Group;
|
||||
|
||||
var nodeCloseTimeout = 15000;
|
||||
var asyncMessageDelivery = true;
|
||||
let nodeCloseTimeout = 15000;
|
||||
let asyncMessageDelivery = true;
|
||||
|
||||
/**
|
||||
* This class represents a flow within the runtime. It is responsible for
|
||||
@@ -52,6 +53,8 @@ class Flow {
|
||||
this.isGlobalFlow = false;
|
||||
}
|
||||
this.id = this.flow.id || "global";
|
||||
this.groups = {}
|
||||
this.groupOrder = []
|
||||
this.activeNodes = {};
|
||||
this.subflowInstanceNodes = {};
|
||||
this.catchNodes = [];
|
||||
@@ -59,6 +62,11 @@ class Flow {
|
||||
this.path = this.id;
|
||||
// Ensure a context exists for this flow
|
||||
this.context = context.getFlowContext(this.id,this.parent.id);
|
||||
|
||||
// env is an array of env definitions
|
||||
// _env is an object for direct lookup of env name -> value
|
||||
this.env = this.flow.env
|
||||
this._env = {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,7 +144,7 @@ class Flow {
|
||||
* @param {[type]} msg [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
start(diff) {
|
||||
async start(diff) {
|
||||
this.trace("start "+this.TYPE+" ["+this.path+"]");
|
||||
var node;
|
||||
var newNode;
|
||||
@@ -145,6 +153,52 @@ class Flow {
|
||||
this.statusNodes = [];
|
||||
this.completeNodeMap = {};
|
||||
|
||||
|
||||
if (this.isGlobalFlow) {
|
||||
// This is the global flow. It needs to go find the `global-config`
|
||||
// node and extract any env properties from it
|
||||
const configNodes = Object.keys(this.flow.configs);
|
||||
for (let i = 0; i < configNodes.length; i++) {
|
||||
const node = this.flow.configs[configNodes[i]]
|
||||
if (node.type === 'global-config' && node.env) {
|
||||
const nodeEnv = await flowUtil.evaluateEnvProperties(this, node.env, credentials.get(node.id))
|
||||
this._env = { ...this._env, ...nodeEnv }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.env) {
|
||||
this._env = { ...this._env, ...await flowUtil.evaluateEnvProperties(this, this.env, credentials.get(this.id)) }
|
||||
}
|
||||
|
||||
// Initialise the group objects. These must be done in the right order
|
||||
// starting from outer-most to inner-most so that the parent hierarchy
|
||||
// is maintained.
|
||||
this.groups = {}
|
||||
this.groupOrder = []
|
||||
const groupIds = Object.keys(this.flow.groups || {})
|
||||
while (groupIds.length > 0) {
|
||||
const id = groupIds.shift()
|
||||
const groupDef = this.flow.groups[id]
|
||||
if (!groupDef.g || this.groups[groupDef.g]) {
|
||||
// The parent of this group is available - either another group
|
||||
// or the top-level flow (this)
|
||||
const parent = this.groups[groupDef.g] || this
|
||||
this.groups[groupDef.id] = new Group(parent, groupDef)
|
||||
this.groupOrder.push(groupDef.id)
|
||||
} else {
|
||||
// Try again once we've processed the other groups
|
||||
groupIds.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.groupOrder.length; i++) {
|
||||
// Start the groups in the right order so they
|
||||
// can setup their env vars knowning their parent
|
||||
// will have been started
|
||||
await this.groups[this.groupOrder[i]].start()
|
||||
}
|
||||
|
||||
var configNodes = Object.keys(this.flow.configs);
|
||||
var configNodeAttempts = {};
|
||||
while (configNodes.length > 0) {
|
||||
@@ -177,7 +231,7 @@ class Flow {
|
||||
}
|
||||
}
|
||||
if (readyToCreate) {
|
||||
newNode = flowUtil.createNode(this,node);
|
||||
newNode = await flowUtil.createNode(this,node);
|
||||
if (newNode) {
|
||||
this.activeNodes[id] = newNode;
|
||||
}
|
||||
@@ -203,7 +257,7 @@ class Flow {
|
||||
if (node.d !== true) {
|
||||
if (!node.subflow) {
|
||||
if (!this.activeNodes[id]) {
|
||||
newNode = flowUtil.createNode(this,node);
|
||||
newNode = await flowUtil.createNode(this,node);
|
||||
if (newNode) {
|
||||
this.activeNodes[id] = newNode;
|
||||
}
|
||||
@@ -221,7 +275,7 @@ class Flow {
|
||||
node
|
||||
);
|
||||
this.subflowInstanceNodes[id] = subflow;
|
||||
subflow.start();
|
||||
await subflow.start();
|
||||
this.activeNodes[id] = subflow.node;
|
||||
|
||||
// this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id});
|
||||
@@ -404,8 +458,7 @@ class Flow {
|
||||
* @return {Node} group node
|
||||
*/
|
||||
getGroupNode(id) {
|
||||
const groups = this.global.groups;
|
||||
return groups[id];
|
||||
return this.groups[id];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,95 +469,8 @@ class Flow {
|
||||
return this.activeNodes;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get value of environment variable defined in group node.
|
||||
* @param {String} group - group node
|
||||
* @param {String} name - name of variable
|
||||
* @return {Object} object containing the value in val property or null if not defined
|
||||
*/
|
||||
getGroupEnvSetting(node, group, name) {
|
||||
if (group) {
|
||||
if (name === "NR_GROUP_NAME") {
|
||||
return [{
|
||||
val: group.name
|
||||
}, null];
|
||||
}
|
||||
if (name === "NR_GROUP_ID") {
|
||||
return [{
|
||||
val: group.id
|
||||
}, null];
|
||||
}
|
||||
|
||||
if (group.credentials === undefined) {
|
||||
group.credentials = credentials.get(group.id) || {};
|
||||
}
|
||||
if (!name.startsWith("$parent.")) {
|
||||
if (group.env) {
|
||||
if (!group._env) {
|
||||
const envs = group.env;
|
||||
const entries = envs.map((env) => {
|
||||
if (env.type === "cred") {
|
||||
const cred = group.credentials;
|
||||
if (cred.hasOwnProperty(env.name)) {
|
||||
env.value = cred[env.name];
|
||||
}
|
||||
}
|
||||
return [env.name, env];
|
||||
});
|
||||
group._env = Object.fromEntries(entries);
|
||||
}
|
||||
const env = group._env[name];
|
||||
if (env) {
|
||||
let value = env.value;
|
||||
const type = env.type;
|
||||
if ((type !== "env") ||
|
||||
(value !== name)) {
|
||||
if (type === "env") {
|
||||
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}");
|
||||
}
|
||||
if (type === "bool") {
|
||||
const val
|
||||
= ((value === "true") ||
|
||||
(value === true));
|
||||
return [{
|
||||
val: val
|
||||
}, null];
|
||||
}
|
||||
if (type === "cred") {
|
||||
return [{
|
||||
val: value
|
||||
}, null];
|
||||
}
|
||||
try {
|
||||
var val = redUtil.evaluateNodeProperty(value, type, node, null, null);
|
||||
return [{
|
||||
val: val
|
||||
}, null];
|
||||
}
|
||||
catch (e) {
|
||||
this.error(e);
|
||||
return [null, null];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
name = name.substring(8);
|
||||
}
|
||||
if (group.g) {
|
||||
const parent = this.getGroupNode(group.g);
|
||||
return this.getGroupEnvSetting(node, parent, name);
|
||||
}
|
||||
}
|
||||
return [null, name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a flow setting value. This currently automatically defers to the parent
|
||||
* flow which, as defined in ./index.js returns `process.env[key]`.
|
||||
* This lays the groundwork for Subflow to have instance-specific settings
|
||||
* Get a flow setting value.
|
||||
* @param {[type]} key [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
@@ -516,54 +482,14 @@ class Flow {
|
||||
if (key === "NR_FLOW_ID") {
|
||||
return flow.id;
|
||||
}
|
||||
if (flow.credentials === undefined) {
|
||||
flow.credentials = credentials.get(flow.id) || {};
|
||||
}
|
||||
if (flow.env) {
|
||||
if (!key.startsWith("$parent.")) {
|
||||
if (!flow._env) {
|
||||
const envs = flow.env;
|
||||
const entries = envs.map((env) => {
|
||||
if (env.type === "cred") {
|
||||
const cred = flow.credentials;
|
||||
if (cred.hasOwnProperty(env.name)) {
|
||||
env.value = cred[env.name];
|
||||
}
|
||||
}
|
||||
return [env.name, env]
|
||||
});
|
||||
flow._env = Object.fromEntries(entries);
|
||||
}
|
||||
const env = flow._env[key];
|
||||
if (env) {
|
||||
let value = env.value;
|
||||
const type = env.type;
|
||||
if ((type !== "env") || (value !== key)) {
|
||||
if (type === "env") {
|
||||
value = value.replace(new RegExp("\\${"+key+"}","g"),"${$parent."+key+"}");
|
||||
}
|
||||
try {
|
||||
if (type === "bool") {
|
||||
const val = ((value === "true") ||
|
||||
(value === true));
|
||||
return val;
|
||||
}
|
||||
if (type === "cred") {
|
||||
return value;
|
||||
}
|
||||
var val = redUtil.evaluateNodeProperty(value, type, null, null, null);
|
||||
return val;
|
||||
}
|
||||
catch (e) {
|
||||
this.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!key.startsWith("$parent.")) {
|
||||
if (this._env.hasOwnProperty(key)) {
|
||||
return this._env[key]
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
key = key.substring(8);
|
||||
}
|
||||
}
|
||||
// Delegate to the parent flow.
|
||||
return this.parent.getSetting(key);
|
||||
}
|
||||
|
||||
@@ -601,10 +527,9 @@ class Flow {
|
||||
// Delegate status to any nodes using this config node
|
||||
for (let userNode in node.users) {
|
||||
if (node.users.hasOwnProperty(userNode)) {
|
||||
node.users[userNode]._flow.handleStatus(node,statusMessage,node.users[userNode],true);
|
||||
handled = node.users[userNode]._flow.handleStatus(node,statusMessage,node.users[userNode],true) || handled;
|
||||
}
|
||||
}
|
||||
handled = true;
|
||||
} else {
|
||||
const candidateNodes = [];
|
||||
this.statusNodes.forEach(targetStatusNode => {
|
||||
@@ -618,10 +543,10 @@ class Flow {
|
||||
let distance = 0
|
||||
if (reportingNode.g) {
|
||||
// Reporting node inside a group. Calculate the distance between it and the status node
|
||||
let containingGroup = this.global.groups[reportingNode.g]
|
||||
let containingGroup = this.groups[reportingNode.g]
|
||||
while (containingGroup && containingGroup.id !== targetStatusNode.g) {
|
||||
distance++
|
||||
containingGroup = this.global.groups[containingGroup.g]
|
||||
containingGroup = this.groups[containingGroup.g]
|
||||
}
|
||||
if (!containingGroup && targetStatusNode.g && targetStatusNode.scope === 'group') {
|
||||
// This status node is in a group, but not in the same hierachy
|
||||
@@ -688,10 +613,9 @@ class Flow {
|
||||
// Delegate status to any nodes using this config node
|
||||
for (let userNode in node.users) {
|
||||
if (node.users.hasOwnProperty(userNode)) {
|
||||
node.users[userNode]._flow.handleError(node,logMessage,msg,node.users[userNode]);
|
||||
handled = node.users[userNode]._flow.handleError(node,logMessage,msg,node.users[userNode]) || handled;
|
||||
}
|
||||
}
|
||||
handled = true;
|
||||
} else {
|
||||
const candidateNodes = [];
|
||||
this.catchNodes.forEach(targetCatchNode => {
|
||||
@@ -706,10 +630,10 @@ class Flow {
|
||||
let distance = 0
|
||||
if (reportingNode.g) {
|
||||
// Reporting node inside a group. Calculate the distance between it and the catch node
|
||||
let containingGroup = this.global.groups[reportingNode.g]
|
||||
let containingGroup = this.groups[reportingNode.g]
|
||||
while (containingGroup && containingGroup.id !== targetCatchNode.g) {
|
||||
distance++
|
||||
containingGroup = this.global.groups[containingGroup.g]
|
||||
containingGroup = this.groups[containingGroup.g]
|
||||
}
|
||||
if (!containingGroup && targetCatchNode.g && targetCatchNode.scope === 'group') {
|
||||
// This catch node is in a group, but not in the same hierachy
|
||||
@@ -859,7 +783,7 @@ function handlePreRoute(flow, sendEvent, reportError) {
|
||||
return;
|
||||
} else if (err !== false) {
|
||||
sendEvent.destination.node = flow.getNode(sendEvent.destination.id);
|
||||
if (sendEvent.destination.node) {
|
||||
if (sendEvent.destination.node && typeof sendEvent.destination.node === 'object') {
|
||||
if (sendEvent.cloneMessage) {
|
||||
sendEvent.msg = redUtil.cloneMessage(sendEvent.msg);
|
||||
}
|
||||
@@ -909,9 +833,10 @@ module.exports = {
|
||||
asyncMessageDelivery = !runtime.settings.runtimeSyncDelivery
|
||||
Log = runtime.log;
|
||||
Subflow = require("./Subflow");
|
||||
Group = require("./Group").Group
|
||||
},
|
||||
create: function(parent,global,conf) {
|
||||
return new Flow(parent,global,conf);
|
||||
return new Flow(parent,global,conf)
|
||||
},
|
||||
Flow: Flow
|
||||
}
|
||||
|
55
packages/node_modules/@node-red/runtime/lib/flows/Group.js
vendored
Normal file
55
packages/node_modules/@node-red/runtime/lib/flows/Group.js
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
const flowUtil = require("./util");
|
||||
const credentials = require("../nodes/credentials");
|
||||
|
||||
/**
|
||||
* This class represents a group within the runtime.
|
||||
*/
|
||||
class Group {
|
||||
|
||||
/**
|
||||
* Create a Group object.
|
||||
* @param {[type]} parent The parent flow/group
|
||||
* @param {[type]} groupDef This group's definition
|
||||
*/
|
||||
constructor(parent, groupDef) {
|
||||
this.TYPE = 'group'
|
||||
this.name = groupDef.name
|
||||
this.parent = parent
|
||||
this.group = groupDef
|
||||
this.id = this.group.id
|
||||
this.g = this.group.g
|
||||
this.env = this.group.env
|
||||
this._env = {}
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this.env) {
|
||||
this._env = await flowUtil.evaluateEnvProperties(this, this.env, credentials.get(this.id))
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get a group setting value.
|
||||
* @param {[type]} key [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
getSetting(key) {
|
||||
if (key === "NR_GROUP_NAME") {
|
||||
return this.name;
|
||||
}
|
||||
if (key === "NR_GROUP_ID") {
|
||||
return this.id;
|
||||
}
|
||||
if (!key.startsWith("$parent.")) {
|
||||
if (this._env.hasOwnProperty(key)) {
|
||||
return this._env[key]
|
||||
}
|
||||
} else {
|
||||
key = key.substring(8);
|
||||
}
|
||||
return this.parent.getSetting(key);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Group
|
||||
}
|
@@ -119,7 +119,7 @@ class Subflow extends Flow {
|
||||
this.templateCredentials = credentials.get(subflowDef.id) || {};
|
||||
this.instanceCredentials = credentials.get(id) || {};
|
||||
|
||||
var env = [];
|
||||
var env = {};
|
||||
if (this.subflowDef.env) {
|
||||
this.subflowDef.env.forEach(e => {
|
||||
env[e.name] = e;
|
||||
@@ -145,7 +145,7 @@ class Subflow extends Flow {
|
||||
}
|
||||
});
|
||||
}
|
||||
this.env = env;
|
||||
this.env = Object.values(env);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +156,7 @@ class Subflow extends Flow {
|
||||
* @param {[type]} diff [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
start(diff) {
|
||||
async start(diff) {
|
||||
var self = this;
|
||||
// Create a subflow node to accept inbound messages and route appropriately
|
||||
var Node = require("../nodes/Node");
|
||||
@@ -310,7 +310,7 @@ class Subflow extends Flow {
|
||||
}
|
||||
}
|
||||
}
|
||||
super.start(diff);
|
||||
return super.start(diff);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,68 +335,35 @@ class Subflow extends Flow {
|
||||
}
|
||||
/**
|
||||
* Get environment variable of subflow
|
||||
* @param {String} name name of env var
|
||||
* @param {String} key name of env var
|
||||
* @return {Object} val value of env var
|
||||
*/
|
||||
getSetting(name) {
|
||||
if (!/^\$parent\./.test(name)) {
|
||||
var env = this.env;
|
||||
if (env && env.hasOwnProperty(name)) {
|
||||
var val = env[name];
|
||||
// If this is an env type property we need to be careful not
|
||||
// to get into lookup loops.
|
||||
// 1. if the value to lookup is the same as this one, go straight to parent
|
||||
// 2. otherwise, check if it is a compound env var ("foo $(bar)")
|
||||
// and if so, substitute any instances of `name` with $parent.name
|
||||
// See https://github.com/node-red/node-red/issues/2099
|
||||
if (val.type !== 'env' || val.value !== name) {
|
||||
let value = val.value;
|
||||
var type = val.type;
|
||||
if (type === 'env') {
|
||||
value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}");
|
||||
}
|
||||
try {
|
||||
return evaluateInputValue(value, type, this.node);
|
||||
}
|
||||
catch (e) {
|
||||
this.error(e);
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
// This _is_ an env property pointing at itself - go to parent
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// name starts $parent. ... so delegate to parent automatically
|
||||
name = name.substring(8);
|
||||
}
|
||||
getSetting(key) {
|
||||
const node = this.subflowInstance;
|
||||
if (node) {
|
||||
if (name === "NR_NODE_NAME") {
|
||||
if (key === "NR_NODE_NAME" || key === "NR_SUBFLOW_NAME") {
|
||||
return node.name;
|
||||
}
|
||||
if (name === "NR_NODE_ID") {
|
||||
if (key === "NR_NODE_ID" || key === "NR_SUBFLOW_ID") {
|
||||
return node.id;
|
||||
}
|
||||
if (name === "NR_NODE_PATH") {
|
||||
if (key === "NR_NODE_PATH" || key === "NR_SUBFLOW_PATH") {
|
||||
return node._path;
|
||||
}
|
||||
}
|
||||
if (node.g) {
|
||||
const group = this.getGroupNode(node.g);
|
||||
const [result, newName] = this.getGroupEnvSetting(node, group, name);
|
||||
if (result) {
|
||||
return result.val;
|
||||
if (!key.startsWith("$parent.")) {
|
||||
if (this._env.hasOwnProperty(key)) {
|
||||
return this._env[key]
|
||||
}
|
||||
name = newName;
|
||||
} else {
|
||||
key = key.substring(8);
|
||||
}
|
||||
|
||||
var parent = this.parent;
|
||||
if (parent) {
|
||||
var val = parent.getSetting(name);
|
||||
return val;
|
||||
// Push the request up to the parent.
|
||||
// Unlike a Flow, the parent of a Subflow could be a Group
|
||||
if (node.g) {
|
||||
return this.parent.getGroupNode(node.g).getSetting(key)
|
||||
}
|
||||
return undefined;
|
||||
return this.parent.getSetting(key)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -271,6 +271,10 @@ function getFlows() {
|
||||
|
||||
async function start(type,diff,muteLog,isDeploy) {
|
||||
type = type || "full";
|
||||
if (diff && diff.globalConfigChanged) {
|
||||
type = 'full'
|
||||
}
|
||||
|
||||
started = true;
|
||||
state = 'start'
|
||||
var i;
|
||||
@@ -359,7 +363,7 @@ async function start(type,diff,muteLog,isDeploy) {
|
||||
if (activeFlowConfig.flows.hasOwnProperty(id)) {
|
||||
if (!activeFlowConfig.flows[id].disabled && !activeFlows[id]) {
|
||||
// This flow is not disabled, nor is it currently active, so create it
|
||||
activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]);
|
||||
activeFlows[id] = Flow.create(activeFlows['global'],activeFlowConfig,activeFlowConfig.flows[id]);
|
||||
log.debug("red/nodes/flows.start : starting flow : "+id);
|
||||
} else {
|
||||
log.debug("red/nodes/flows.start : not starting disabled flow : "+id);
|
||||
@@ -379,7 +383,7 @@ async function start(type,diff,muteLog,isDeploy) {
|
||||
activeFlows[id].update(activeFlowConfig,activeFlowConfig.flows[id]);
|
||||
} else {
|
||||
// This flow didn't previously exist, so create it
|
||||
activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]);
|
||||
activeFlows[id] = Flow.create(activeFlows['global'],activeFlowConfig,activeFlowConfig.flows[id]);
|
||||
log.debug("red/nodes/flows.start : starting flow : "+id);
|
||||
}
|
||||
} else {
|
||||
@@ -391,7 +395,7 @@ async function start(type,diff,muteLog,isDeploy) {
|
||||
for (id in activeFlows) {
|
||||
if (activeFlows.hasOwnProperty(id)) {
|
||||
try {
|
||||
activeFlows[id].start(diff);
|
||||
await activeFlows[id].start(diff);
|
||||
// Create a map of node id to flow id and also a subflowInstance lookup map
|
||||
var activeNodes = activeFlows[id].getActiveNodes();
|
||||
Object.keys(activeNodes).forEach(function(nid) {
|
||||
@@ -432,7 +436,8 @@ function stop(type,diff,muteLog,isDeploy) {
|
||||
changed:[],
|
||||
removed:[],
|
||||
rewired:[],
|
||||
linked:[]
|
||||
linked:[],
|
||||
flowChanged:[]
|
||||
};
|
||||
if (!muteLog) {
|
||||
if (type !== "full") {
|
||||
@@ -441,6 +446,9 @@ function stop(type,diff,muteLog,isDeploy) {
|
||||
log.info(log._("nodes.flows.stopping-flows"));
|
||||
}
|
||||
}
|
||||
if (diff.globalConfigChanged) {
|
||||
type = 'full'
|
||||
}
|
||||
started = false;
|
||||
state = 'stop'
|
||||
var promises = [];
|
||||
@@ -464,7 +472,7 @@ function stop(type,diff,muteLog,isDeploy) {
|
||||
|
||||
activeFlowIds.forEach(id => {
|
||||
if (activeFlows.hasOwnProperty(id)) {
|
||||
var flowStateChanged = diff && (diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1);
|
||||
var flowStateChanged = diff && (diff.flowChanged.indexOf(id) !== -1 || diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1);
|
||||
log.debug("red/nodes/flows.stop : stopping flow : "+id);
|
||||
promises.push(activeFlows[id].stop(flowStateChanged?null:stopList,removedList));
|
||||
if (type === "full" || flowStateChanged || diff.removed.indexOf(id)!==-1) {
|
||||
@@ -784,17 +792,6 @@ const flowAPI = {
|
||||
log: m => log.log(m)
|
||||
}
|
||||
|
||||
|
||||
function getGlobalConfig() {
|
||||
let gconf = null;
|
||||
eachNode((n) => {
|
||||
if (n.type === "global-config") {
|
||||
gconf = n;
|
||||
}
|
||||
});
|
||||
return gconf;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
|
||||
@@ -807,10 +804,7 @@ module.exports = {
|
||||
|
||||
get:getNode,
|
||||
eachNode: eachNode,
|
||||
|
||||
|
||||
getGlobalConfig: getGlobalConfig,
|
||||
|
||||
|
||||
/**
|
||||
* Gets the current flow configuration
|
||||
*/
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -205,7 +205,6 @@ module.exports = {
|
||||
getNode: flows.get,
|
||||
eachNode: flows.eachNode,
|
||||
getContext: context.get,
|
||||
getGlobalConfig: flows.getGlobalConfig,
|
||||
|
||||
clearContext: context.clear,
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/runtime",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.0-beta.4",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,8 +16,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/registry": "3.1.0-beta.2",
|
||||
"@node-red/util": "3.1.0-beta.2",
|
||||
"@node-red/registry": "3.1.0-beta.4",
|
||||
"@node-red/util": "3.1.0-beta.4",
|
||||
"async-mutex": "0.4.0",
|
||||
"clone": "2.1.2",
|
||||
"express": "4.18.2",
|
||||
|
@@ -526,7 +526,7 @@ function setObjectProperty(msg,prop,value,createMissing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
/**
|
||||
* Get value of environment variable.
|
||||
* @param {Node} node - accessing node
|
||||
* @param {String} name - name of variable
|
||||
@@ -548,11 +548,9 @@ function getSetting(node, name, flow_) {
|
||||
if (flow) {
|
||||
if (node && node.g) {
|
||||
const group = flow.getGroupNode(node.g);
|
||||
const [result, newName] = flow.getGroupEnvSetting(node, group, name);
|
||||
if (result) {
|
||||
return result.val;
|
||||
if (group) {
|
||||
return group.getSetting(name)
|
||||
}
|
||||
name = newName;
|
||||
}
|
||||
return flow.getSetting(name);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/util",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.0-beta.4",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
10
packages/node_modules/node-red/package.json
vendored
10
packages/node_modules/node-red/package.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.0-beta.4",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "http://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,10 +31,10 @@
|
||||
"flow"
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/editor-api": "3.1.0-beta.2",
|
||||
"@node-red/runtime": "3.1.0-beta.2",
|
||||
"@node-red/util": "3.1.0-beta.2",
|
||||
"@node-red/nodes": "3.1.0-beta.2",
|
||||
"@node-red/editor-api": "3.1.0-beta.4",
|
||||
"@node-red/runtime": "3.1.0-beta.4",
|
||||
"@node-red/util": "3.1.0-beta.4",
|
||||
"@node-red/nodes": "3.1.0-beta.4",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"express": "4.18.2",
|
||||
|
14
packages/node_modules/node-red/red.js
vendored
14
packages/node_modules/node-red/red.js
vendored
@@ -302,7 +302,7 @@ httpsPromise.then(function(startupHttps) {
|
||||
settings.httpNodeAuth = settings.httpNodeAuth || settings.httpAuth;
|
||||
}
|
||||
|
||||
if(settings.httpStatic) {
|
||||
if (settings.httpStatic) {
|
||||
settings.httpStaticRoot = formatRoot(settings.httpStaticRoot || "/");
|
||||
const statics = Array.isArray(settings.httpStatic) ? settings.httpStatic : [settings.httpStatic];
|
||||
const sanitised = [];
|
||||
@@ -414,13 +414,7 @@ httpsPromise.then(function(startupHttps) {
|
||||
if (settings.httpNodeRoot !== false) {
|
||||
app.use(settings.httpNodeRoot,RED.httpNode);
|
||||
}
|
||||
// if (settings.httpStatic) {
|
||||
// settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth;
|
||||
// if (settings.httpStaticAuth) {
|
||||
// app.use("/",basicAuthMiddleware(settings.httpStaticAuth.user,settings.httpStaticAuth.pass));
|
||||
// }
|
||||
// app.use("/",express.static(settings.httpStatic));
|
||||
// }
|
||||
|
||||
if (settings.httpStatic) {
|
||||
let appUseMem = {};
|
||||
for (let si = 0; si < settings.httpStatic.length; si++) {
|
||||
@@ -428,6 +422,7 @@ httpsPromise.then(function(startupHttps) {
|
||||
const filePath = sp.path;
|
||||
const thisRoot = sp.root || "/";
|
||||
const options = sp.options;
|
||||
const middleware = sp.middleware;
|
||||
if(appUseMem[filePath + "::" + thisRoot]) {
|
||||
continue;// this path and root already registered!
|
||||
}
|
||||
@@ -435,6 +430,9 @@ httpsPromise.then(function(startupHttps) {
|
||||
if (settings.httpStaticAuth) {
|
||||
app.use(thisRoot, basicAuthMiddleware(settings.httpStaticAuth.user, settings.httpStaticAuth.pass));
|
||||
}
|
||||
if (middleware) {
|
||||
app.use(thisRoot, middleware)
|
||||
}
|
||||
app.use(thisRoot, express.static(filePath, options));
|
||||
}
|
||||
}
|
||||
|
6
test/node_modules/nr-test-utils/index.js
generated
vendored
6
test/node_modules/nr-test-utils/index.js
generated
vendored
@@ -16,7 +16,6 @@
|
||||
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const PACKAGE_ROOT = "../../../packages/node_modules";
|
||||
|
||||
@@ -27,5 +26,10 @@ module.exports = {
|
||||
},
|
||||
resolve: function(file) {
|
||||
return path.resolve(path.join(__dirname,PACKAGE_ROOT,file));
|
||||
},
|
||||
sleep: async (time) => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, time)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -22,7 +22,9 @@ var helper = require("node-red-node-test-helper");
|
||||
describe('inject node', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
helper.startServer(done);
|
||||
helper.startServer(() => {
|
||||
done()
|
||||
});
|
||||
});
|
||||
|
||||
function initContext(done) {
|
||||
@@ -41,7 +43,7 @@ describe('inject node', function() {
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(function(done) {
|
||||
afterEach(async function() {
|
||||
helper.unload().then(function () {
|
||||
return Context.clean({allNodes: {}});
|
||||
}).then(function () {
|
||||
@@ -53,8 +55,11 @@ describe('inject node', function() {
|
||||
|
||||
function basicTest(type, val, rval) {
|
||||
it('inject value ('+type+')', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", topic: "t1", payload: val, payloadType: type, wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
var flow = [
|
||||
{id:'flow', type:'tab'},
|
||||
{id: "n1", type: "inject", topic: "t1", payload: val, payloadType: type, wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper", z:'flow'}
|
||||
];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
@@ -93,6 +98,7 @@ describe('inject node', function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function (msg) {
|
||||
delete process.env.NR_TEST
|
||||
try {
|
||||
msg.should.have.property("topic", "t1");
|
||||
msg.should.have.property("payload", "foo");
|
||||
@@ -101,13 +107,13 @@ describe('inject node', function() {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
process.env.NR_TEST = 'foo';
|
||||
process.env.NR_TEST = 'foo';
|
||||
n1.receive({});
|
||||
});
|
||||
});
|
||||
|
||||
it('inject name of node as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_NAME", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_NODE_NAME", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
@@ -125,7 +131,7 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject id of node as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_ID", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_NODE_ID", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
@@ -143,7 +149,7 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject path of node as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_PATH", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_NODE_PATH", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
@@ -162,7 +168,7 @@ describe('inject node', function() {
|
||||
|
||||
|
||||
it('inject name of flow as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_FLOW_NAME", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_FLOW_NAME", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "flow", type: "tab", label: "FLOW" },
|
||||
];
|
||||
@@ -182,7 +188,7 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject id of flow as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_FLOW_ID", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_FLOW_ID", payloadType: "env", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "flow", type: "tab", name: "FLOW" },
|
||||
];
|
||||
@@ -202,9 +208,10 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject name of group as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_GROUP_NAME", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "g0", type: "group", name: "GROUP" },
|
||||
var flow = [{id: "flow", type: "tab" },
|
||||
{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_GROUP_NAME", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper", z: "flow"},
|
||||
{id: "g0", type: "group", name: "GROUP", z: "flow" },
|
||||
];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
@@ -222,9 +229,10 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject id of group as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_GROUP_ID", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "g0", type: "group", name: "GROUP" },
|
||||
var flow = [{id: "flow", type: "tab" },
|
||||
{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_GROUP_ID", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper", z: "flow"},
|
||||
{id: "g0", type: "group", name: "GROUP", z: "flow" },
|
||||
];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
@@ -243,8 +251,9 @@ describe('inject node', function() {
|
||||
|
||||
|
||||
it('inject name of node as environment variable by substitution ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
var flow = [{id: "flow", type: "tab" },
|
||||
{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_NODE_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper", z: "flow"}];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
var n2 = helper.getNode("n2");
|
||||
@@ -261,7 +270,7 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject id of node as environment variable by substitution ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_ID}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_NODE_ID}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
@@ -279,7 +288,7 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject path of node as environment variable by substitution ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_PATH}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_NODE_PATH}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"}];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
@@ -298,7 +307,7 @@ describe('inject node', function() {
|
||||
|
||||
|
||||
it('inject name of flow as environment variable by substitution ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_FLOW_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "flow", type: "tab", label: "FLOW" },
|
||||
];
|
||||
@@ -318,7 +327,7 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject id of flow as environment variable ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_ID}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_FLOW_ID}", payloadType: "str", wires: [["n2"]], z: "flow"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "flow", type: "tab", name: "FLOW" },
|
||||
];
|
||||
@@ -338,9 +347,10 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject name of group as environment variable by substitution ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_NAME}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "g0", type: "group", name: "GROUP" },
|
||||
var flow = [{id: "flow", type: "tab" },
|
||||
{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_GROUP_NAME}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper", z: "flow"},
|
||||
{id: "g0", type: "group", name: "GROUP", z: "flow" },
|
||||
];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
@@ -358,9 +368,10 @@ describe('inject node', function() {
|
||||
});
|
||||
|
||||
it('inject id of group as environment variable by substitution ', function (done) {
|
||||
var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_ID}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper"},
|
||||
{id: "g0", type: "group", name: "GROUP" },
|
||||
var flow = [{id: "flow", type: "tab" },
|
||||
{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_GROUP_ID}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"},
|
||||
{id: "n2", type: "helper", z: "flow"},
|
||||
{id: "g0", type: "group", name: "GROUP", z: "flow" },
|
||||
];
|
||||
helper.load(injectNode, flow, function () {
|
||||
var n1 = helper.getNode("n1");
|
||||
|
@@ -3,7 +3,7 @@ var config = require("nr-test-utils").require("@node-red/nodes/core/common/91-gl
|
||||
var inject = require("nr-test-utils").require("@node-red/nodes/core/common/20-inject.js");
|
||||
var helper = require("node-red-node-test-helper");
|
||||
|
||||
describe('unknown Node', function() {
|
||||
describe('Global Config Node', function() {
|
||||
|
||||
afterEach(function() {
|
||||
helper.unload();
|
||||
@@ -44,4 +44,30 @@ describe('unknown Node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should evaluate a global environment variable that is a JSONata value', function (done) {
|
||||
const flow = [{
|
||||
id: "n1", type: "global-config", name: "XYZ",
|
||||
env: [
|
||||
{ name: "now-var", type: "jsonata", value: "$millis()" }
|
||||
]
|
||||
},
|
||||
{ id: "n2", type: "inject", topic: "t1", payload: "now-var", payloadType: "env", wires: [["n3"]], z: "flow" },
|
||||
{ id: "n3", type: "helper" }
|
||||
];
|
||||
helper.load([config, inject], flow, function () {
|
||||
var n2 = helper.getNode("n2");
|
||||
var n3 = helper.getNode("n3");
|
||||
n3.on("input", (msg) => {
|
||||
try {
|
||||
const now = Date.now();
|
||||
msg.should.have.property("payload").and.be.approximately(now, 1000);
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n2.receive({});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -1424,7 +1424,30 @@ describe('function node', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should timeout if timeout is set', function(done) {
|
||||
var flow = [{id:"n1",type:"function",wires:[["n2"]],timeout:"0.010",func:"while(1==1){};\nreturn msg;"}];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
n1.receive({payload:"foo",topic: "bar"});
|
||||
setTimeout(function() {
|
||||
try {
|
||||
helper.log().called.should.be.true();
|
||||
var logEvents = helper.log().args.filter(function(evt) {
|
||||
return evt[0].type == "function";
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
var msg = logEvents[0][0];
|
||||
msg.should.have.property('level', helper.log().ERROR);
|
||||
msg.should.have.property('id', 'n1');
|
||||
msg.should.have.property('type', 'function');
|
||||
should.equal(msg.msg.message, 'Script execution timed out after 10ms');
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
},50);
|
||||
});
|
||||
});
|
||||
|
||||
describe("finalize function", function() {
|
||||
|
||||
|
@@ -568,11 +568,12 @@ describe('change Node', function() {
|
||||
|
||||
it('sets the value using env property from group', function(done) {
|
||||
var flow = [
|
||||
{"id": "flow", type:"tab"},
|
||||
{"id":"group1","type":"group","env":[
|
||||
{"name":"NR_TEST_A", "value":"bar", "type": "str"}
|
||||
]},
|
||||
{"id":"changeNode1","type":"change","g":"group1",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper", wires:[]}
|
||||
], z: "flow"},
|
||||
{"id":"changeNode1","type":"change","g":"group1",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]], z: "flow"},
|
||||
{id:"helperNode1", type:"helper", wires:[], z: "flow"}
|
||||
];
|
||||
helper.load(changeNode, flow, function() {
|
||||
var changeNode1 = helper.getNode("changeNode1");
|
||||
@@ -591,12 +592,13 @@ describe('change Node', function() {
|
||||
|
||||
it('sets the value using env property from nested group', function(done) {
|
||||
var flow = [
|
||||
{"id": "flow", type:"tab"},
|
||||
{"id":"group1","type":"group","env":[
|
||||
{"name":"NR_TEST_A", "value":"bar", "type": "str"}
|
||||
]},
|
||||
{"id":"group2","type":"group","g":"group1","env":[]},
|
||||
{"id":"changeNode1","type":"change","g":"group2",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper", wires:[]}
|
||||
], z: "flow"},
|
||||
{"id":"group2","type":"group","g":"group1","env":[], z: "flow"},
|
||||
{"id":"changeNode1","type":"change","g":"group2",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]], z: "flow"},
|
||||
{id:"helperNode1", type:"helper", wires:[], z: "flow"}
|
||||
];
|
||||
helper.load(changeNode, flow, function() {
|
||||
var changeNode1 = helper.getNode("changeNode1");
|
||||
|
@@ -667,7 +667,7 @@ describe('HTTP Request Node', function() {
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property('payload','');
|
||||
msg.should.have.property('payload',{});
|
||||
msg.should.have.property('statusCode',204);
|
||||
done();
|
||||
} catch(err) {
|
||||
|
@@ -173,7 +173,7 @@ describe('JSON node', function() {
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.startWith("Unexpected token o");
|
||||
logEvents[0][0].msg.should.match(/^Unexpected token (o|'o')/);
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
@@ -199,7 +199,7 @@ describe('JSON node', function() {
|
||||
});
|
||||
logEvents.should.have.length(1);
|
||||
logEvents[0][0].should.have.a.property('msg');
|
||||
logEvents[0][0].msg.should.startWith("Unexpected token o");
|
||||
logEvents[0][0].msg.should.match(/^Unexpected token (o|'o')/);
|
||||
logEvents[0][0].should.have.a.property('level',helper.log().ERROR);
|
||||
done();
|
||||
} catch(err) { done(err) }
|
||||
|
@@ -98,6 +98,29 @@ describe('file Nodes', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should write to a file using JSONata', function(done) {
|
||||
var fileToTest4jsonata = "'" + resourcesDir + "/'&(20+30)&'-file-test-file.txt'";
|
||||
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename": fileToTest4jsonata, "filenameType": "jsonata", "appendNewline":false, "overwriteFile":true, wires: [["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper"}];
|
||||
helper.load(fileNode, flow, function() {
|
||||
var n1 = helper.getNode("fileNode1");
|
||||
var n2 = helper.getNode("helperNode1");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
var f = fs.readFileSync(fileToTest);
|
||||
f.should.have.length(4);
|
||||
fs.unlinkSync(fileToTest);
|
||||
msg.should.have.property("payload", "test");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:"test"});
|
||||
});
|
||||
});
|
||||
|
||||
it('should write to a file using RED.settings.fileWorkingDirectory', function(done) {
|
||||
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":relativePathToFile, "appendNewline":false, "overwriteFile":true, wires: [["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper"}];
|
||||
@@ -121,7 +144,6 @@ describe('file Nodes', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should write multi-byte string to a file', function(done) {
|
||||
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":true, wires: [["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper"}];
|
||||
@@ -423,6 +445,31 @@ describe('file Nodes', function() {
|
||||
n1.receive({payload:"typedInput", _user_specified_filename:fileToTest});
|
||||
});
|
||||
});
|
||||
|
||||
it('should support number in msg._user_specified_filename', function (done) {
|
||||
var flow = [{id:"fileNode1", type:"file", filename:"_user_specified_filename", filenameType:"msg", name:"fileNode", "appendNewline":false, "overwriteFile":true, wires:[["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper"}];
|
||||
helper.load(fileNode, flow, function () {
|
||||
RED.settings.fileWorkingDirectory = resourcesDir;
|
||||
var n1 = helper.getNode("fileNode1");
|
||||
var n2 = helper.getNode("helperNode1");
|
||||
n2.on("input", function (msg) {
|
||||
try {
|
||||
var fileToTest = path.join(resourcesDir, "123");
|
||||
var f = fs.readFileSync(fileToTest);
|
||||
f.should.have.length(4);
|
||||
fs.unlinkSync(fileToTest);
|
||||
msg.should.have.property("payload", "test");
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
n1.receive({payload: "test", _user_specified_filename: 123});
|
||||
});
|
||||
});
|
||||
|
||||
it('should use env.TEST_FILE set in nodes typedInput', function(done) {
|
||||
var flow = [{id:"fileNode1", type:"file", filename:"TEST_FILE", filenameType: "env", name: "fileNode", "appendNewline":true, "overwriteFile":true, wires: [["helperNode1"]]},
|
||||
{id:"helperNode1", type:"helper"}];
|
||||
@@ -1237,6 +1284,27 @@ describe('file Nodes', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should read in a file using JSONata and output a utf8 string', function(done) {
|
||||
var fileToTest4jsonata = "'" + resourcesDir + "/'&(20+30)&'-file-test-file.txt'";
|
||||
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest4jsonata, "filenameType": "jsonata", "format":"utf8", wires:[["n2"]]},
|
||||
{id:"n2", type:"helper"}];
|
||||
helper.load(fileNode, flow, function() {
|
||||
var n1 = helper.getNode("fileInNode1");
|
||||
var n2 = helper.getNode("n2");
|
||||
n2.on("input", function(msg) {
|
||||
try {
|
||||
msg.should.have.property('payload');
|
||||
msg.payload.should.be.a.String();
|
||||
msg.payload.should.have.length(40)
|
||||
msg.payload.should.equal("File message line 1\nFile message line 2\n");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
n1.receive({payload:""});
|
||||
});
|
||||
});
|
||||
|
||||
it('should read in a file using fileWorkingDirectory to set cwd', function(done) {
|
||||
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":relativePathToFile, "format":"utf8", wires:[["n2"]]},
|
||||
|
@@ -253,35 +253,32 @@ describe('subflow', function() {
|
||||
|
||||
it('should access typed value of env var', function(done) {
|
||||
var flow = [
|
||||
{id:"t0", type:"tab", label:"", disabled:false, info:""},
|
||||
{id:"n1", x:10, y:10, z:"t0", type:"subflow:s1",
|
||||
env: [
|
||||
{name: "KN", type: "num", value: "100"},
|
||||
{name: "KB", type: "bool", value: "true"},
|
||||
{name: "KJ", type: "json", value: "[1,2,3]"},
|
||||
{name: "Kb", type: "bin", value: "[65,65]"},
|
||||
{name: "Ke", type: "env", value: "KS"},
|
||||
{name: "Kj", type: "jsonata", value: "1+2"},
|
||||
],
|
||||
wires:[["n2"]]},
|
||||
{id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]},
|
||||
// Subflow
|
||||
{id:"s1", type:"subflow", name:"Subflow", info:"",
|
||||
in:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1"} ]
|
||||
}],
|
||||
out:[{
|
||||
x:10, y:10,
|
||||
wires:[ {id:"s1-n1", port:0} ]
|
||||
}],
|
||||
env: [
|
||||
{name: "KS", type: "str", value: "STR"}
|
||||
]
|
||||
{ id: "t0", type: "tab", label: "", disabled: false, info: "" },
|
||||
{
|
||||
id: "n1", x: 10, y: 10, z: "t0", type: "subflow:s1",
|
||||
env: [
|
||||
{ name: "KN", type: "num", value: "100" },
|
||||
{ name: "KB", type: "bool", value: "true" },
|
||||
{ name: "KJ", type: "json", value: "[1,2,3]" },
|
||||
{ name: "Kb", type: "bin", value: "[65,65]" },
|
||||
{ name: "Ke", type: "env", value: "KS" },
|
||||
{ name: "Kj", type: "jsonata", value: "1+2" },
|
||||
],
|
||||
wires: [["n2"]]
|
||||
},
|
||||
{id:"s1-n1", x:10, y:10, z:"s1", type:"function",
|
||||
func:"msg.VE = env.get('Ke'); msg.VS = env.get('KS'); msg.VN = env.get('KN'); msg.VB = env.get('KB'); msg.VJ = env.get('KJ'); msg.Vb = env.get('Kb'); msg.Vj = env.get('Kj'); return msg;",
|
||||
wires:[]}
|
||||
{ id: "n2", x: 10, y: 10, z: "t0", type: "helper", wires: [] },
|
||||
// Subflow
|
||||
{
|
||||
id: "s1", type: "subflow", name: "Subflow", info: "",
|
||||
in: [{ x: 10, y: 10, wires: [{ id: "s1-n1" }] }],
|
||||
out: [{ x: 10, y: 10, wires: [{ id: "s1-n1", port: 0 }] }],
|
||||
env: [{ name: "KS", type: "str", value: "STR" }]
|
||||
},
|
||||
{
|
||||
id: "s1-n1", x: 10, y: 10, z: "s1", type: "function",
|
||||
func: "msg.VE = env.get('Ke'); msg.VS = env.get('KS'); msg.VN = env.get('KN'); msg.VB = env.get('KB'); msg.VJ = env.get('KJ'); msg.Vb = env.get('Kb'); msg.Vj = env.get('Kj'); return msg;",
|
||||
wires: []
|
||||
}
|
||||
];
|
||||
helper.load(functionNode, flow, function() {
|
||||
var n1 = helper.getNode("n1");
|
||||
|
File diff suppressed because it is too large
Load Diff
48
test/unit/@node-red/runtime/lib/flows/Group_spec.js
Normal file
48
test/unit/@node-red/runtime/lib/flows/Group_spec.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const should = require("should");
|
||||
const NR_TEST_UTILS = require("nr-test-utils");
|
||||
const { Group } = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Group");
|
||||
|
||||
describe('Group', function () {
|
||||
describe('getSetting', function () {
|
||||
it("returns group name/id", async function () {
|
||||
const group = new Group({
|
||||
getSetting: v => v+v
|
||||
}, {
|
||||
name: "g1",
|
||||
id: "group1"
|
||||
})
|
||||
await group.start()
|
||||
|
||||
group.getSetting("NR_GROUP_NAME").should.equal("g1")
|
||||
group.getSetting("NR_GROUP_ID").should.equal("group1")
|
||||
})
|
||||
it("delegates to parent if not found", async function () {
|
||||
const group = new Group({
|
||||
getSetting: v => v+v
|
||||
}, {
|
||||
name: "g1",
|
||||
id: "group1"
|
||||
})
|
||||
await group.start()
|
||||
|
||||
group.getSetting("123").should.equal("123123")
|
||||
})
|
||||
it("delegates to parent if explicit requested", async function () {
|
||||
const parentGroup = new Group({
|
||||
getSetting: v => v+v
|
||||
}, {
|
||||
name: "g0",
|
||||
id: "group0"
|
||||
})
|
||||
const group = new Group(parentGroup, {
|
||||
name: "g1",
|
||||
id: "group1"
|
||||
})
|
||||
await parentGroup.start()
|
||||
await group.start()
|
||||
|
||||
group.getSetting("$parent.NR_GROUP_NAME").should.equal("g0")
|
||||
group.getSetting("$parent.NR_GROUP_ID").should.equal("group0")
|
||||
})
|
||||
})
|
||||
})
|
@@ -68,11 +68,13 @@ describe('Subflow', function() {
|
||||
this.handled = 0;
|
||||
this.stopped = false;
|
||||
this.received = null;
|
||||
this.receivedEnv = null;
|
||||
currentNodes[node.id] = node;
|
||||
this.on('input',function(msg) {
|
||||
// console.log(this.id,msg.payload);
|
||||
node.handled++;
|
||||
node.received = msg.payload;
|
||||
node.receivedEnv = msg.receivedEnv;
|
||||
node.send(msg);
|
||||
});
|
||||
this.on('close',function() {
|
||||
@@ -185,7 +187,15 @@ describe('Subflow', function() {
|
||||
var flow = node._flow;
|
||||
var val = flow.getSetting("__KEY__");
|
||||
node.received = val;
|
||||
node.send({payload: val});
|
||||
const receivedEnv = {}
|
||||
try {
|
||||
['__KEY__','__KEY1__','__KEY2__','__KEY3__','__KEY4__'].forEach(k => {
|
||||
receivedEnv[k] = flow.getSetting(k)
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
node.send({payload: val, receivedEnv});
|
||||
});
|
||||
this.on('close',function() {
|
||||
node.stopped = true;
|
||||
@@ -282,7 +292,7 @@ describe('Subflow', function() {
|
||||
getType.restore();
|
||||
});
|
||||
describe('#start',function() {
|
||||
it("instantiates a subflow and stops it",function(done) {
|
||||
it("instantiates a subflow and stops it", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
||||
@@ -297,7 +307,7 @@ describe('Subflow', function() {
|
||||
]);
|
||||
var flow = Flow.create({handleError: (a,b,c) => { console.log(a,b,c); }},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
Object.keys(activeNodes).should.have.length(4);
|
||||
@@ -332,37 +342,21 @@ describe('Subflow', function() {
|
||||
// currentNodes[sfInstanceId2].should.have.a.property("handled",0);
|
||||
|
||||
currentNodes["1"].receive({payload:"test"});
|
||||
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
|
||||
setTimeout(function() {
|
||||
currentNodes["1"].should.have.a.property("handled",1);
|
||||
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
|
||||
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
|
||||
currentNodes["3"].should.have.a.property("handled",1);
|
||||
currentNodes["4"].should.have.a.property("handled",1);
|
||||
currentNodes["1"].should.have.a.property("handled",1);
|
||||
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
|
||||
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
|
||||
currentNodes["3"].should.have.a.property("handled",1);
|
||||
currentNodes["4"].should.have.a.property("handled",1);
|
||||
|
||||
|
||||
|
||||
flow.stop().then(function() {
|
||||
Object.keys(currentNodes).should.have.length(0);
|
||||
Object.keys(stoppedNodes).should.have.length(6);
|
||||
|
||||
// currentNodes.should.not.have.a.property("1");
|
||||
// currentNodes.should.not.have.a.property("3");
|
||||
// currentNodes.should.not.have.a.property("4");
|
||||
// // currentNodes.should.not.have.a.property(sfInstanceId);
|
||||
// // currentNodes.should.not.have.a.property(sfInstanceId2);
|
||||
// // currentNodes.should.not.have.a.property(sfConfigId);
|
||||
// stoppedNodes.should.have.a.property("1");
|
||||
// stoppedNodes.should.have.a.property("3");
|
||||
// stoppedNodes.should.have.a.property("4");
|
||||
// // stoppedNodes.should.have.a.property(sfInstanceId);
|
||||
// // stoppedNodes.should.have.a.property(sfInstanceId2);
|
||||
// // stoppedNodes.should.have.a.property(sfConfigId);
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
await flow.stop()
|
||||
Object.keys(currentNodes).should.have.length(0);
|
||||
Object.keys(stoppedNodes).should.have.length(6);
|
||||
});
|
||||
it("instantiates a subflow inside a subflow and stops it",function(done) {
|
||||
|
||||
it("instantiates a subflow inside a subflow and stops it", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
||||
@@ -379,24 +373,20 @@ describe('Subflow', function() {
|
||||
]);
|
||||
var flow = Flow.create({},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
currentNodes["1"].should.have.a.property("handled",0);
|
||||
currentNodes["3"].should.have.a.property("handled",0);
|
||||
|
||||
currentNodes["1"].receive({payload:"test"});
|
||||
|
||||
setTimeout(function() {
|
||||
currentNodes["1"].should.have.a.property("handled",1);
|
||||
currentNodes["3"].should.have.a.property("handled",1);
|
||||
flow.stop().then(function() {
|
||||
Object.keys(currentNodes).should.have.length(0);
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
currentNodes["1"].should.have.a.property("handled",1);
|
||||
currentNodes["3"].should.have.a.property("handled",1);
|
||||
await flow.stop()
|
||||
Object.keys(currentNodes).should.have.length(0);
|
||||
});
|
||||
it("rewires a subflow node on update/start",function(done){
|
||||
|
||||
it("rewires a subflow node on update/start", async function(){
|
||||
var rawConfig = [
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
||||
@@ -417,7 +407,7 @@ describe('Subflow', function() {
|
||||
var diff = flowUtils.diffConfigs(config,newConfig);
|
||||
var flow = Flow.create({},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
Object.keys(activeNodes).should.have.length(4);
|
||||
@@ -429,36 +419,28 @@ describe('Subflow', function() {
|
||||
currentNodes["4"].should.have.a.property("handled",0);
|
||||
|
||||
currentNodes["1"].receive({payload:"test"});
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
currentNodes["1"].should.have.a.property("handled",1);
|
||||
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
|
||||
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
|
||||
currentNodes["3"].should.have.a.property("handled",1);
|
||||
currentNodes["4"].should.have.a.property("handled",0);
|
||||
|
||||
setTimeout(function() {
|
||||
currentNodes["1"].should.have.a.property("handled",1);
|
||||
// currentNodes[sfInstanceId].should.have.a.property("handled",1);
|
||||
// currentNodes[sfInstanceId2].should.have.a.property("handled",1);
|
||||
currentNodes["3"].should.have.a.property("handled",1);
|
||||
currentNodes["4"].should.have.a.property("handled",0);
|
||||
flow.update(newConfig,newConfig.flows["t1"]);
|
||||
await flow.start(diff)
|
||||
currentNodes["1"].receive({payload:"test2"});
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
currentNodes["1"].should.have.a.property("handled",2);
|
||||
// currentNodes[sfInstanceId].should.have.a.property("handled",2);
|
||||
// currentNodes[sfInstanceId2].should.have.a.property("handled",2);
|
||||
currentNodes["3"].should.have.a.property("handled",1);
|
||||
currentNodes["4"].should.have.a.property("handled",1);
|
||||
|
||||
flow.update(newConfig,newConfig.flows["t1"]);
|
||||
flow.start(diff)
|
||||
|
||||
currentNodes["1"].receive({payload:"test2"});
|
||||
setTimeout(function() {
|
||||
|
||||
currentNodes["1"].should.have.a.property("handled",2);
|
||||
// currentNodes[sfInstanceId].should.have.a.property("handled",2);
|
||||
// currentNodes[sfInstanceId2].should.have.a.property("handled",2);
|
||||
currentNodes["3"].should.have.a.property("handled",1);
|
||||
currentNodes["4"].should.have.a.property("handled",1);
|
||||
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
},150);
|
||||
await flow.stop()
|
||||
});
|
||||
});
|
||||
describe('#stop', function() {
|
||||
it("stops subflow instance nodes",function(done) {
|
||||
it("stops subflow instance nodes", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]},
|
||||
@@ -470,20 +452,18 @@ describe('Subflow', function() {
|
||||
]);
|
||||
var flow = Flow.create({},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
Object.keys(activeNodes).should.have.length(3);
|
||||
Object.keys(stoppedNodes).should.have.length(0);
|
||||
flow.stop(["2"]).then(function() {
|
||||
Object.keys(currentNodes).should.have.length(2);
|
||||
Object.keys(stoppedNodes).should.have.length(1);
|
||||
done();
|
||||
}).catch(done);
|
||||
await flow.stop(["2"])
|
||||
Object.keys(currentNodes).should.have.length(2);
|
||||
Object.keys(stoppedNodes).should.have.length(1);
|
||||
});
|
||||
});
|
||||
describe("#handleStatus",function() {
|
||||
it("passes a status event to the subflow's parent tab status node - all scope",function(done) {
|
||||
it("passes a status event to the subflow's parent tab status node - all scope", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||
@@ -496,27 +476,24 @@ describe('Subflow', function() {
|
||||
]);
|
||||
var flow = Flow.create({},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
|
||||
activeNodes["1"].receive({payload:"test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","test status");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("type","testStatus");
|
||||
statusMessage.status.source.should.have.a.property("name","test-status-node");
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","test status");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("type","testStatus");
|
||||
statusMessage.status.source.should.have.a.property("name","test-status-node");
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
await flow.stop()
|
||||
});
|
||||
it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) {
|
||||
it("passes a status event to the subflow's parent tab status node - targetted scope", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||
@@ -531,34 +508,30 @@ describe('Subflow', function() {
|
||||
|
||||
var flow = Flow.create({handleStatus:() => { parentFlowStatusCalled = true} },config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
|
||||
activeNodes["1"].receive({payload:"test"});
|
||||
|
||||
setTimeout(function() {
|
||||
parentFlowStatusCalled.should.be.false();
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
parentFlowStatusCalled.should.be.false();
|
||||
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","test status");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("type","testStatus");
|
||||
statusMessage.status.source.should.have.a.property("name","test-status-node");
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","test status");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("type","testStatus");
|
||||
statusMessage.status.source.should.have.a.property("name","test-status-node");
|
||||
|
||||
flow.stop().then(function() {
|
||||
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
await flow.stop()
|
||||
});
|
||||
});
|
||||
|
||||
describe("status node", function() {
|
||||
it("emits a status event when a message is passed to a subflow-status node - msg.payload as string", function(done) {
|
||||
it("emits a status event when a message is passed to a subflow-status node - msg.payload as string", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||
@@ -578,29 +551,24 @@ describe('Subflow', function() {
|
||||
]);
|
||||
var flow = Flow.create({},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
|
||||
activeNodes["1"].receive({payload:"test-payload"});
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
|
||||
setTimeout(function() {
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","test-payload");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("id","2");
|
||||
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
|
||||
|
||||
flow.stop().then(function() {
|
||||
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","test-payload");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("id","2");
|
||||
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
|
||||
await flow.stop()
|
||||
});
|
||||
it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) {
|
||||
it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||
@@ -620,29 +588,26 @@ describe('Subflow', function() {
|
||||
]);
|
||||
var flow = Flow.create({},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
|
||||
activeNodes["1"].receive({payload:{text:"payload-obj"}});
|
||||
|
||||
setTimeout(function() {
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","payload-obj");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("id","2");
|
||||
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
|
||||
flow.stop().then(function() {
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","payload-obj");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("id","2");
|
||||
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
|
||||
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
await flow.stop()
|
||||
});
|
||||
it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) {
|
||||
it("emits a status event when a message is passed to a subflow-status node - msg.status", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||
@@ -662,29 +627,26 @@ describe('Subflow', function() {
|
||||
]);
|
||||
var flow = Flow.create({},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
|
||||
activeNodes["1"].receive({status:{text:"status-obj"}});
|
||||
|
||||
setTimeout(function() {
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","status-obj");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("id","2");
|
||||
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
|
||||
flow.stop().then(function() {
|
||||
statusMessage.should.have.a.property("status");
|
||||
statusMessage.status.should.have.a.property("text","status-obj");
|
||||
statusMessage.status.should.have.a.property("source");
|
||||
statusMessage.status.source.should.have.a.property("id","2");
|
||||
statusMessage.status.source.should.have.a.property("type","subflow:sf1");
|
||||
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
flow.stop()
|
||||
});
|
||||
it("does not emit a regular status event if it contains a subflow-status node", function(done) {
|
||||
it("does not emit a regular status event if it contains a subflow-status node", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||
@@ -704,7 +666,7 @@ describe('Subflow', function() {
|
||||
]);
|
||||
var flow = Flow.create({},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
|
||||
@@ -712,15 +674,12 @@ describe('Subflow', function() {
|
||||
|
||||
currentNodes["sn"].should.have.a.property("handled",0);
|
||||
|
||||
flow.stop().then(function() {
|
||||
|
||||
done();
|
||||
});
|
||||
await flow.stop()
|
||||
});
|
||||
})
|
||||
|
||||
describe("#handleError",function() {
|
||||
it("passes an error event to the subflow's parent tab catch node - all scope",function(done) {
|
||||
it("passes an error event to the subflow's parent tab catch node - all scope",async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||
@@ -733,28 +692,26 @@ describe('Subflow', function() {
|
||||
]);
|
||||
var flow = Flow.create({},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
|
||||
activeNodes["1"].receive({payload:"test"});
|
||||
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
|
||||
setTimeout(function() {
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
|
||||
statusMessage.should.have.a.property("error");
|
||||
statusMessage.error.should.have.a.property("message","test error");
|
||||
statusMessage.error.should.have.a.property("source");
|
||||
statusMessage.error.source.should.have.a.property("type","testError");
|
||||
statusMessage.error.source.should.have.a.property("name","test-error-node");
|
||||
statusMessage.should.have.a.property("error");
|
||||
statusMessage.error.should.have.a.property("message","test error");
|
||||
statusMessage.error.should.have.a.property("source");
|
||||
statusMessage.error.source.should.have.a.property("type","testError");
|
||||
statusMessage.error.source.should.have.a.property("name","test-error-node");
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
await flow.stop()
|
||||
});
|
||||
it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) {
|
||||
it("passes an error event to the subflow's parent tab catch node - targetted scope", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]},
|
||||
@@ -768,50 +725,31 @@ describe('Subflow', function() {
|
||||
var parentFlowErrorCalled = false;
|
||||
var flow = Flow.create({handleError:() => { parentFlowErrorCalled = true} },config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var activeNodes = flow.getActiveNodes();
|
||||
|
||||
activeNodes["1"].receive({payload:"test"});
|
||||
|
||||
setTimeout(function() {
|
||||
parentFlowErrorCalled.should.be.false();
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
|
||||
parentFlowErrorCalled.should.be.false();
|
||||
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
currentNodes["sn"].should.have.a.property("handled",1);
|
||||
var statusMessage = currentNodes["sn"].messages[0];
|
||||
|
||||
statusMessage.should.have.a.property("error");
|
||||
statusMessage.error.should.have.a.property("message","test error");
|
||||
statusMessage.error.should.have.a.property("source");
|
||||
statusMessage.error.source.should.have.a.property("type","testError");
|
||||
statusMessage.error.source.should.have.a.property("name","test-error-node");
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
statusMessage.should.have.a.property("error");
|
||||
statusMessage.error.should.have.a.property("message","test error");
|
||||
statusMessage.error.should.have.a.property("source");
|
||||
statusMessage.error.source.should.have.a.property("type","testError");
|
||||
statusMessage.error.source.should.have.a.property("name","test-error-node");
|
||||
|
||||
await flow.stop()
|
||||
});
|
||||
});
|
||||
|
||||
describe("#env var", function() {
|
||||
// should be changed according to internal env var representation
|
||||
function setEnv(node, key, val) {
|
||||
var flow = node._flow;
|
||||
if (flow) {
|
||||
var env = flow.env;
|
||||
if (!env) {
|
||||
env = flow.env = {};
|
||||
}
|
||||
env[key] = {
|
||||
name: key,
|
||||
type: "str",
|
||||
value: val
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
it("can access process env var", function(done) {
|
||||
it("can access process env var", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
|
||||
@@ -828,29 +766,25 @@ describe('Subflow', function() {
|
||||
handleError: (a,b,c) => { console.log(a,b,c); }
|
||||
},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
process.env["__KEY__"] = "__VAL__";
|
||||
|
||||
currentNodes["1"].receive({payload: "test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["3"].should.have.a.property("received", "__VAL__");
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
currentNodes["3"].should.have.a.property("received", "__VAL__");
|
||||
await flow.stop()
|
||||
});
|
||||
|
||||
it("can access subflow env var", function(done) {
|
||||
it("can access subflow env var", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
|
||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]},
|
||||
{id:"sf1",type:"subflow",name:"Subflow 2",info:"",
|
||||
"in":[ {wires:[{id:"sf1-1"}]} ],
|
||||
"out":[ {wires:[{id:"sf1-2",port:0}]} ]},
|
||||
{id:"sf1",type:"subflow",name:"Subflow 2",info:"",env: [{name: '__KEY__', value: '__VAL1__', type: 'str'}],
|
||||
"in":[ {wires:[{id:"sf1-1"}]} ],
|
||||
"out":[ {wires:[{id:"sf1-2",port:0}]} ]},
|
||||
{id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]},
|
||||
{id:"sf1-2",type:"testEnv",z:"sf1",foo:"sf1.2",x:166,y:99,wires:[[]]}
|
||||
]);
|
||||
@@ -859,7 +793,7 @@ describe('Subflow', function() {
|
||||
handleError: (a,b,c) => { console.log(a,b,c); }
|
||||
},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
var testenv_node = null;
|
||||
for (var n in currentNodes) {
|
||||
@@ -870,32 +804,30 @@ describe('Subflow', function() {
|
||||
}
|
||||
}
|
||||
process.env["__KEY__"] = "__VAL0__";
|
||||
setEnv(testenv_node, "__KEY__", "__VAL1__");
|
||||
|
||||
currentNodes["1"].receive({payload: "test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["3"].should.have.a.property("received", "__VAL1__");
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
currentNodes["3"].should.have.a.property("received", "__VAL1__");
|
||||
await flow.stop()
|
||||
});
|
||||
|
||||
it("can access nested subflow env var", function(done) {
|
||||
it("can access nested subflow env var", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"t1",type:"tab", env: [{name: '__KEY1__', value: 't1', type: 'str'}]},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
|
||||
{id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]},
|
||||
{id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]},
|
||||
{id:"sf1",type:"subflow",name:"Subflow 1",info:"",
|
||||
in:[{wires:[{id:"sf1-1"}]}],
|
||||
out:[{wires:[{id:"sf1-2",port:0}]}]},
|
||||
env: [{name: '__KEY2__', value: 'sf1', type: 'str'}],
|
||||
in:[{wires:[{id:"sf1-1"}]}],
|
||||
out:[{wires:[{id:"sf1-2",port:0}]}]},
|
||||
{id:"sf2",type:"subflow",name:"Subflow 2",info:"",
|
||||
in:[{wires:[{id:"sf2-1"}]}],
|
||||
out:[{wires:[{id:"sf2-2",port:0}]}]},
|
||||
env: [{name: '__KEY3__', value: 'sf2', type: 'str'}],
|
||||
in:[{wires:[{id:"sf2-1"}]}],
|
||||
out:[{wires:[{id:"sf2-2",port:0}]}]},
|
||||
{id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]},
|
||||
{id:"sf1-2",type:"subflow:sf2",z:"sf1",x:166,y:99,wires:[[]]},
|
||||
{id:"sf1-2",type:"subflow:sf2",z:"sf1",x:166,y:99,wires:[[]], env: [{name: '__KEY4__', value: 'sf1-2', type: 'str'}] },
|
||||
{id:"sf2-1",type:"test",z:"sf2",foo:"sf2.1",x:166,y:99,wires:[["sf2-2"]]},
|
||||
{id:"sf2-2",type:"testEnv",z:"sf2",foo:"sf2.2",x:166,y:99,wires:[[]]},
|
||||
]);
|
||||
@@ -904,45 +836,22 @@ describe('Subflow', function() {
|
||||
handleError: (a,b,c) => { console.log(a,b,c); }
|
||||
},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
|
||||
var node_sf1_1 = null;
|
||||
var node_sf2_1 = null;
|
||||
var testenv_node = null;
|
||||
for (var n in currentNodes) {
|
||||
var node = currentNodes[n];
|
||||
if (node.foo === "sf1.1") {
|
||||
node_sf1_1 = node;
|
||||
}
|
||||
if (node.foo === "sf2.1") {
|
||||
node_sf2_1 = node;
|
||||
}
|
||||
}
|
||||
await flow.start();
|
||||
|
||||
process.env["__KEY__"] = "__VAL0__";
|
||||
currentNodes["1"].receive({payload: "test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["3"].should.have.a.property("received", "__VAL0__");
|
||||
|
||||
setEnv(node_sf1_1, "__KEY__", "__VAL1__");
|
||||
currentNodes["1"].receive({payload: "test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["3"].should.have.a.property("received", "__VAL1__");
|
||||
|
||||
setEnv(node_sf2_1, "__KEY__", "__VAL2__");
|
||||
currentNodes["1"].receive({payload: "test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["3"].should.have.a.property("received", "__VAL2__");
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
},150);
|
||||
},150);
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
currentNodes["3"].should.have.a.property("receivedEnv");
|
||||
currentNodes["3"].receivedEnv.should.have.a.property('__KEY__', '__VAL0__')
|
||||
currentNodes["3"].receivedEnv.should.have.a.property('__KEY1__', 't1')
|
||||
currentNodes["3"].receivedEnv.should.have.a.property('__KEY2__', 'sf1')
|
||||
currentNodes["3"].receivedEnv.should.have.a.property('__KEY3__', 'sf2')
|
||||
currentNodes["3"].receivedEnv.should.have.a.property('__KEY4__', 'sf1-2')
|
||||
|
||||
await flow.stop()
|
||||
});
|
||||
|
||||
it("can access name of subflow as env var", function(done) {
|
||||
it("can access name of subflow as env var", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
|
||||
@@ -959,19 +868,15 @@ describe('Subflow', function() {
|
||||
handleError: (a,b,c) => { console.log(a,b,c); }
|
||||
},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
currentNodes["1"].receive({payload: "test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["3"].should.have.a.property("received", "SFN");
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
currentNodes["3"].should.have.a.property("received", "SFN");
|
||||
await flow.stop()
|
||||
});
|
||||
|
||||
it("can access id of subflow as env var", function(done) {
|
||||
it("can access id of subflow as env var", async function() {
|
||||
var config = flowUtils.parseConfig([
|
||||
{id:"t1",type:"tab"},
|
||||
{id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]},
|
||||
@@ -988,19 +893,13 @@ describe('Subflow', function() {
|
||||
handleError: (a,b,c) => { console.log(a,b,c); }
|
||||
},config,config.flows["t1"]);
|
||||
|
||||
flow.start();
|
||||
await flow.start();
|
||||
|
||||
currentNodes["1"].receive({payload: "test"});
|
||||
setTimeout(function() {
|
||||
currentNodes["3"].should.have.a.property("received", "2");
|
||||
|
||||
flow.stop().then(function() {
|
||||
done();
|
||||
});
|
||||
},150);
|
||||
await NR_TEST_UTILS.sleep(150)
|
||||
currentNodes["3"].should.have.a.property("received", "2");
|
||||
await flow.stop()
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -93,7 +93,7 @@ describe('flows/index', function() {
|
||||
flowCreate.flows[id] = {
|
||||
flow: flow,
|
||||
global: global,
|
||||
start: sinon.spy(),
|
||||
start: sinon.spy(async() => {}),
|
||||
update: sinon.spy(),
|
||||
stop: sinon.spy(),
|
||||
getActiveNodes: function() {
|
||||
@@ -221,13 +221,18 @@ describe('flows/index', function() {
|
||||
return Promise.resolve({flows:originalConfig});
|
||||
}
|
||||
events.once('flows:started',function() {
|
||||
flows.setFlows(newConfig,"nodes").then(function() {
|
||||
flows.getFlows().flows.should.eql(newConfig);
|
||||
flowCreate.flows['t1'].update.called.should.be.true();
|
||||
flowCreate.flows['t2'].start.called.should.be.true();
|
||||
flowCreate.flows['_GLOBAL_'].update.called.should.be.true();
|
||||
done();
|
||||
events.once('flows:started', function() {
|
||||
try {
|
||||
flows.getFlows().flows.should.eql(newConfig);
|
||||
flowCreate.flows['t1'].update.called.should.be.true();
|
||||
flowCreate.flows['t2'].start.called.should.be.true();
|
||||
flowCreate.flows['_GLOBAL_'].update.called.should.be.true();
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
flows.setFlows(newConfig,"nodes")
|
||||
});
|
||||
|
||||
flows.init({log:mockLog, settings:{},storage:storage});
|
||||
@@ -250,13 +255,14 @@ describe('flows/index', function() {
|
||||
}
|
||||
|
||||
events.once('flows:started',function() {
|
||||
flows.setFlows(newConfig,"nodes").then(function() {
|
||||
events.once('flows:started',function() {
|
||||
flows.getFlows().flows.should.eql(newConfig);
|
||||
flowCreate.flows['t1'].update.called.should.be.true();
|
||||
flowCreate.flows['t2'].start.called.should.be.true();
|
||||
flowCreate.flows['_GLOBAL_'].update.called.should.be.true();
|
||||
flows.stopFlows().then(done);
|
||||
})
|
||||
flows.setFlows(newConfig,"nodes")
|
||||
});
|
||||
|
||||
flows.init({log:mockLog, settings:{},storage:storage});
|
||||
|
@@ -149,7 +149,7 @@ describe('flows/util', function() {
|
||||
{id:"t1",type:"tab"}
|
||||
];
|
||||
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]};
|
||||
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"missingTypes":[]};
|
||||
parsedConfig.should.eql(expectedConfig);
|
||||
});
|
||||
|
||||
@@ -160,7 +160,7 @@ describe('flows/util', function() {
|
||||
{id:"t1",type:"tab"}
|
||||
];
|
||||
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]};
|
||||
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]};
|
||||
parsedConfig.should.eql(expectedConfig);
|
||||
});
|
||||
|
||||
@@ -172,7 +172,7 @@ describe('flows/util', function() {
|
||||
{id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]}
|
||||
];
|
||||
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]};
|
||||
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"missingTypes":[]};
|
||||
parsedConfig.should.eql(expectedConfig);
|
||||
});
|
||||
|
||||
@@ -184,7 +184,7 @@ describe('flows/util', function() {
|
||||
{id:"sf1-1",x:10,y:10,z:"sf1",type:"test",wires:[]}
|
||||
];
|
||||
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"groups":{},"missingTypes":[]};
|
||||
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"groups":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"missingTypes":[]};
|
||||
parsedConfig.should.eql(expectedConfig);
|
||||
});
|
||||
|
||||
@@ -196,7 +196,7 @@ describe('flows/util', function() {
|
||||
];
|
||||
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||
parsedConfig.missingTypes.should.eql(['missing']);
|
||||
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"groups":{},"missingTypes":["missing"]};
|
||||
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"missingTypes":["missing"]};
|
||||
redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true();
|
||||
});
|
||||
|
||||
@@ -206,7 +206,7 @@ describe('flows/util', function() {
|
||||
{id:"cn",type:"test"},
|
||||
];
|
||||
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]};
|
||||
var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]};
|
||||
parsedConfig.should.eql(expectedConfig);
|
||||
});
|
||||
|
||||
@@ -217,7 +217,7 @@ describe('flows/util', function() {
|
||||
{id:"g1",type:"group",z:"t1"}
|
||||
];
|
||||
var parsedConfig = flowUtil.parseConfig(originalConfig);
|
||||
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"g1":{"id":"g1","type":"group","z":"t1"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{"g1":{"id":"g1","type":"group","z":"t1"}},"missingTypes":[]}
|
||||
var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"g1":{"id":"g1","type":"group","z":"t1"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{"g1":{"id":"g1","type":"group","z":"t1"}},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"missingTypes":[]}
|
||||
parsedConfig.should.eql(expectedConfig);
|
||||
});
|
||||
|
||||
|
@@ -489,7 +489,7 @@ describe('storage/localfilesystem', function() {
|
||||
var rootdir = path.win32.resolve(userDir+'/some');
|
||||
// make it into a local UNC path
|
||||
flowFile = flowFile.replace('C:\\', '\\\\localhost\\c$\\');
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFile}, mockRuntime).then(function() {
|
||||
localfilesystem.init({userDir:userDir, flowFile:flowFile, getUserSettings: () => {{}}}, mockRuntime).then(function() {
|
||||
fs.existsSync(flowFile).should.be.false();
|
||||
localfilesystem.saveFlows(testFlow).then(function() {
|
||||
fs.existsSync(flowFile).should.be.true();
|
||||
|
Reference in New Issue
Block a user