mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
89 Commits
4223-fix-h
...
3.1.0-beta
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
4d9fcaeebf | ||
|
aa0225f59f | ||
|
20d2c11154 | ||
|
3f604e9d93 | ||
|
234e92db12 | ||
|
aafb86ef09 | ||
|
1ad67d5c73 | ||
|
3b38669c04 | ||
|
74ab03288b | ||
|
502dacd865 | ||
|
31bc99cd61 | ||
|
5435c9ebd2 | ||
|
ceb9a320ba | ||
|
ee8b2a0b58 | ||
|
8202f1b7c6 | ||
|
4808cac89d | ||
|
c1ea3380eb | ||
|
694fdebc71 | ||
|
1cbd910e5d | ||
|
b102ef512e | ||
|
220a621dc6 | ||
|
876053f858 | ||
|
4047612b96 | ||
|
2f9523a586 | ||
|
0697c26dd1 | ||
|
c2812b05a4 | ||
|
2253417459 | ||
|
940512fb2c |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,3 +1,33 @@
|
||||
#### 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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "3.1.0-beta.3",
|
||||
"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.3",
|
||||
"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.3",
|
||||
"@node-red/editor-client": "3.1.0-beta.3",
|
||||
"@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",
|
||||
|
@@ -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.3",
|
||||
"version": "3.1.0-beta.4",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1722,7 +1773,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 +3097,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 +3140,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 +4176,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 +4602,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 +5031,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 +5159,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;
|
||||
|
@@ -1,17 +1,17 @@
|
||||
export default {
|
||||
version: "3.1.0-beta.3",
|
||||
version: "3.1.0-beta.4",
|
||||
steps: [
|
||||
{
|
||||
titleIcon: "fa fa-map-o",
|
||||
title: {
|
||||
"en-US": "Welcome to Node-RED 3.1 Beta 3!",
|
||||
"en-US": "Welcome to Node-RED 3.1 Beta 4!",
|
||||
"ja": "Node-RED 3.1 ベータ3へようこそ!",
|
||||
"fr": "Bienvenue dans Node-RED 3.1 Bêta 3 !"
|
||||
"fr": "Bienvenue dans Node-RED 3.1 Bêta 4 !"
|
||||
},
|
||||
description: {
|
||||
"en-US": "<p>This is the third beta release for 3.1.0. This is mostly a bug fix release, so you can skip this tour if you've tried the other betas.</p><p>If not, stick around to see what's new in Node-RED 3.1.</p>",
|
||||
"ja": "<p>これは3.1.0の3回目のベータリリースです。不具合修正のリリースのため、もし他のベータ版を試したことがある場合は、このツアーを読み飛ばしてもかまいません。</p><p>そうでない場合は、Node-RED 3.1の新機能を確認してください。</p>",
|
||||
"fr": "<p>Il s'agit de la troisième bêta de la version 3.1.0. Cette version apporte principalement la correction de bugs, vous pouvez donc ignorer cette visite guidée si vous avez essayé les autres versions bêta.</p><p>Si ce n'est pas le cas, restez dans les parages pour voir les nouveautés de Node-RED 3.1.</p>"
|
||||
"en-US": "<p>This is the fourth beta release for 3.1.0. This is mostly a bug fix release, so you can skip this tour if you've tried the other betas.</p><p>If not, stick around to see what's new in Node-RED 3.1.</p>",
|
||||
"ja": "<p>これは3.1.0の4回目のベータリリースです。不具合修正のリリースのため、もし他のベータ版を試したことがある場合は、このツアーを読み飛ばしてもかまいません。</p><p>そうでない場合は、Node-RED 3.1の新機能を確認してください。</p>",
|
||||
"fr": "<p>Il s'agit de la quatrième bêta de la version 3.1.0. Cette version apporte principalement la correction de bugs, vous pouvez donc ignorer cette visite guidée si vous avez essayé les autres versions bêta.</p><p>Si ce n'est pas le cas, restez dans les parages pour voir les nouveautés de Node-RED 3.1.</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@@ -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');
|
||||
|
@@ -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>
|
||||
|
||||
|
||||
|
@@ -68,9 +68,12 @@ module.exports = function(RED) {
|
||||
node.error(err,msg);
|
||||
return done();
|
||||
} else {
|
||||
filename = value;
|
||||
processMsg2(msg,nodeSend,value,done);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processMsg2(msg,nodeSend,filename,done) {
|
||||
filename = filename || "";
|
||||
msg.filename = filename;
|
||||
var fullFilename = filename;
|
||||
@@ -311,9 +314,12 @@ module.exports = function(RED) {
|
||||
node.error(err,msg);
|
||||
return done();
|
||||
} else {
|
||||
filename = (value || "").replace(/\t|\r|\n/g,'');
|
||||
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 +440,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>
|
||||
|
@@ -252,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",
|
||||
|
@@ -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": {
|
||||
@@ -109,6 +110,7 @@
|
||||
},
|
||||
"status": {
|
||||
"status": "status: 全て",
|
||||
"statusGroup": "status: グループ",
|
||||
"statusNodes": "status: __number__",
|
||||
"label": {
|
||||
"source": "ステータス取得元",
|
||||
@@ -250,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.3",
|
||||
"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.3",
|
||||
"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.3",
|
||||
"@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.3",
|
||||
"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.3",
|
||||
"@node-red/util": "3.1.0-beta.3",
|
||||
"@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.3",
|
||||
"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.3",
|
||||
"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.3",
|
||||
"@node-red/runtime": "3.1.0-beta.3",
|
||||
"@node-red/util": "3.1.0-beta.3",
|
||||
"@node-red/nodes": "3.1.0-beta.3",
|
||||
"@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");
|
||||
|
@@ -98,6 +98,30 @@ 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"}];
|
||||
@@ -1237,6 +1261,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