Compare commits

...

56 Commits

Author SHA1 Message Date
Nick O'Leary
13ee8cec24 Merge branch 'master' into sync-dev-2 2024-05-14 17:22:42 +01:00
Nick O'Leary
a977b87cb3 Merge pull request #4692 from node-red/improve-conflict-handling
Improve background-deploy notification handling
2024-05-14 16:04:58 +01:00
Nick O'Leary
14dfb9aef8 Merge pull request #4695 from node-red/improve-merge-diff-view
Improve diff view display of nodes that have only moved
2024-05-14 16:04:27 +01:00
Nick O'Leary
d520cde57a Merge pull request #4314 from Rotzbua/remove_outdated_node_check
fix: remove outdated Node 11+ check
2024-05-14 13:54:15 +01:00
Nick O'Leary
70167d7d1d Merge pull request #4694 from Rotzbua/test_add_node_22
feat(ci): add new nodejs v22
2024-05-14 13:52:34 +01:00
Nick O'Leary
3389c8160b Handle group w/h properties as move rather than change 2024-05-14 13:51:52 +01:00
Nick O'Leary
c214710f8e Improve diff view display of nodes that have only moved 2024-05-14 13:35:05 +01:00
Nick O'Leary
ac6a4945cb Merge pull request #4690 from Rotzbua/fix_node_requirement
fix(node): increase required node >=18.5
2024-05-14 13:29:52 +01:00
Rotzbua
fd1a001a23 feat(ci): add new nodejs v22 2024-05-14 13:39:32 +02:00
Rotzbua
f3c561cd86 fix(node): increase required node >=18.5
Statement can be simplified by increasing the required minor version.
2024-05-14 12:27:38 +02:00
Nick O'Leary
4e33e785fb Handle multiple background deploys 2024-05-14 10:27:55 +01:00
Nick O'Leary
edc5e88d5a Merge pull request #4689 from Rotzbua/fix_workaroud_dns
fix(dns): remove outdated node check
2024-05-13 14:41:35 +01:00
Nick O'Leary
47bf166a6e Update packages/node_modules/node-red/lib/red.js 2024-05-13 14:41:24 +01:00
Nick O'Leary
cf26209790 Merge pull request #4688 from Rotzbua/remove_import_polyfill
fix(polyfill): remove import module polyfill
2024-05-13 14:39:33 +01:00
Nick O'Leary
e55ebde170 Merge pull request #4686 from Rotzbua/fix_typo
Fix typo
2024-05-13 14:38:35 +01:00
Nick O'Leary
a745ddc164 Fix linting 2024-05-13 14:33:35 +01:00
Nick O'Leary
18d0fa2259 Improve background conflict handling 2024-05-13 14:19:24 +01:00
Rotzbua
d706c9cb37 fix: remove outdated Node 11+ check 2024-05-12 22:51:05 +02:00
Rotzbua
20d2450cac fix(polyfill): remove import module polyfill
Was required for node <12.17.
2024-05-12 22:38:03 +02:00
Rotzbua
34345461f1 fix(dns): remove outdated node check
Add reference to issue for this workaround.
2024-05-12 22:25:39 +02:00
Rotzbua
aa372a1707 Fix typo in source code comment 2024-05-12 18:25:08 +02:00
Nick O'Leary
0e0bba25c1 Merge pull request #4679 from node-red/use-rfdc
Use rfdc for cloning pure JSON values
2024-05-08 16:18:13 +01:00
Nick O'Leary
af701d65ac Merge pull request #4681 from GogoVega/addfrench-v4beta.2
Add French translations for 4.0.0-beta.2
2024-05-08 16:17:57 +01:00
Nick O'Leary
08927dfb55 Merge pull request #4684 from node-red/4363-autoLogin-redirect-loop
Avoid login loops when autoLogin enabled but login fails
2024-05-08 15:48:21 +01:00
Nick O'Leary
b27483de9c Avoid login loops when autoLogin enabled but login fails
Fixes #4363
2024-05-08 15:09:51 +01:00
GogoVega
b02f69b77a Add translations for 4.0.0-beta.2 2024-05-05 15:00:42 +02:00
Nick O'Leary
598b0c84ab Merge pull request #4657 from node-red/tab-close-middle-click
Hide workspace tab on middle mouse click
2024-05-03 16:59:28 +01:00
Nick O'Leary
22cc8da088 Apply suggestions from code review 2024-05-03 16:59:17 +01:00
Nick O'Leary
a70618cdef Merge pull request #4666 from node-red/multiplayer-2
[multiplayer] Add user presence indicators
2024-05-03 16:52:50 +01:00
Nick O'Leary
faf142cf66 Merge pull request #4676 from kazuhitoyokoi/dev-fixpulldependencies
Enable updating dependency node of package.json in project feature
2024-05-03 16:52:33 +01:00
Nick O'Leary
1a3cc06935 Use rfdc module for cloning when we know its pure JSON 2024-05-03 16:45:50 +01:00
Nick O'Leary
a712a9363b Merge pull request #4674 from kazuhitoyokoi/dev-addjpn
Add Japanese translations for 4.0.0-beta.2
2024-05-03 16:18:50 +01:00
Steve-Mcl
67e716466f handle middle click hide-tab in onclick handler 2024-04-29 20:14:45 +01:00
Steve-Mcl
3fae03da98 Merge branch 'dev' into tab-close-middle-click 2024-04-29 19:04:47 +01:00
Kazuhito Yokoi
361391ceb8 Load the latest project files when retrieving project information 2024-04-29 16:02:56 +09:00
Kazuhito Yokoi
bf0ca38350 Enable updating dependencies of package.json in project feature 2024-04-29 02:12:08 +09:00
Kazuhito Yokoi
437c28e2b8 Fix typos in welcome tour for 4.0.0-beta.2 2024-04-28 21:27:25 +09:00
Kazuhito Yokoi
c05d18ada1 Add Japanese translations for 4.0.0-beta.2 2024-04-28 21:22:15 +09:00
Nick O'Leary
cfb300ec06 Merge pull request #4672 from node-red/allow-blank-env-var-substitutions
Allow blank strings to be used for env var property substitutions
2024-04-24 23:37:23 +02:00
Nick O'Leary
236e668201 Allow blank strings to be used for env var property substitutions
Fixes #4663
2024-04-24 22:58:11 +02:00
Nick O'Leary
211d420fb2 Merge pull request #4667 from node-red/fix-subflow-property-undo
Fix undo of subflow env property edits
2024-04-23 23:45:46 +02:00
Nick O'Leary
c9b902c2b4 Merge pull request #4658 from node-red/fix-subflow-conf-type
Fix saving of conf-type properties in module packaged subflows
2024-04-23 23:45:29 +02:00
Nick O'Leary
b8ca4665c1 Merge pull request #4660 from JoshuaCWebDeveloper/patch-1
Fix three error typos in monaco.js
2024-04-23 23:45:16 +02:00
Nick O'Leary
ac8b1e19b7 Merge pull request #4662 from hardillb/timeout-npm-install
Add npm install timeout notification
2024-04-23 23:44:51 +02:00
Nick O'Leary
960af87fb0 Ensure subflow change state is cleared after deploy 2024-04-23 21:17:35 +02:00
Nick O'Leary
de7339ae97 Fix undo of subflow env property edits 2024-04-23 20:39:14 +02:00
Stephen McLaughlin
0995af62b6 Merge pull request #4664 from ZJvandeWeg/patch-3
docs: Add closing paragraph tag
2024-04-20 13:54:37 +01:00
Zeger-Jan van de Weg
c2e03a40b4 docs: Add closing paragraph tag
Minor change that only improves xpath parsing.
2024-04-20 14:20:59 +02:00
Ben Hardill
148e64c3da Update packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
2024-04-18 14:22:50 +01:00
Ben Hardill
c6289ebb2c Merge branch 'dev' into timeout-npm-install 2024-04-18 12:12:53 +01:00
Ben Hardill
5f4ece6813 Move translation 2024-04-18 11:47:49 +01:00
Ben Hardill
c990ec39d6 revert DELETE change 2024-04-18 11:35:51 +01:00
Ben Hardill
1fdc600ecd Add npm install timeout notification
part of https://github.com/node-red/node-red/issues/4622
2024-04-18 11:27:32 +01:00
Joshua Carter
c855050bcf Fix three error typos in monaco.js 2024-04-15 08:09:26 -07:00
Nick O'Leary
e354d2ce29 Fix saving of conf-type properties in module packaged subflows 2024-04-12 14:08:07 +01:00
Steve-Mcl
d938e5fb6b close tab on middle mouse click 2024-04-12 11:42:55 +01:00
40 changed files with 509 additions and 304 deletions

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}

View File

@@ -73,6 +73,7 @@
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.5.2",
"rfdc": "^1.3.1",
"semver": "7.5.4",
"tar": "6.2.1",
"tough-cookie": "4.1.3",
@@ -122,6 +123,6 @@
"supertest": "6.3.3"
},
"engines": {
"node": ">=18"
"node": ">=18.5"
}
}

View File

@@ -205,9 +205,10 @@ function genericStrategy(adminApp,strategy) {
passport.use(new strategy.strategy(options, verify));
adminApp.get('/auth/strategy',
passport.authenticate(strategy.name, {session:false,
passport.authenticate(strategy.name, {
session:false,
failureMessage: true,
failureRedirect: settings.httpAdminRoot
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
}),
completeGenerateStrategyAuth,
handleStrategyError
@@ -221,7 +222,7 @@ function genericStrategy(adminApp,strategy) {
passport.authenticate(strategy.name, {
session:false,
failureMessage: true,
failureRedirect: settings.httpAdminRoot
failureRedirect: settings.httpAdminRoot + '?session_message=Login Failed'
}),
completeGenerateStrategyAuth,
handleStrategyError

View File

@@ -372,6 +372,7 @@
"deleted": "deleted",
"flowDeleted": "flow deleted",
"flowAdded": "flow added",
"moved": "moved",
"movedTo": "moved to __id__",
"movedFrom": "moved from __id__"
},
@@ -643,6 +644,7 @@
"errors": {
"catalogLoadFailed": "<p>Failed to load node catalogue.</p><p>Check the browser console for more information</p>",
"installFailed": "<p>Failed to install: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"installTimeout": "<p>Install continuing the background.</p><p>Nodes will appear in palette when complete. Check the log for more information.</p>",
"removeFailed": "<p>Failed to remove: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"updateFailed": "<p>Failed to update: __module__</p><p>__message__</p><p>Check the log for more information</p>",
"enableFailed": "<p>Failed to enable: __module__</p><p>__message__</p><p>Check the log for more information</p>",
@@ -657,6 +659,9 @@
"body": "<p>Removing '__module__'</p><p>Removing the node will uninstall it from Node-RED. The node may continue to use resources until Node-RED is restarted.</p>",
"title": "Remove nodes"
},
"removePlugin": {
"body": "<p>Removed plugin __module__. Please reload the editor to clear left-overs.</p>"
},
"update": {
"body": "<p>Updating '__module__'</p><p>Updating the node will require a restart of Node-RED to complete the update. This must be done manually.</p>",
"title": "Update nodes"
@@ -668,7 +673,8 @@
"review": "Open node information",
"install": "Install",
"remove": "Remove",
"update": "Update"
"update": "Update",
"understood": "Understood"
}
}
}

View File

@@ -614,6 +614,8 @@
},
"nodeCount": "__label__ noeud",
"nodeCount_plural": "__label__ noeuds",
"pluginCount": "__count__ plugin",
"pluginCount_plural": "__count__ plugins",
"moduleCount": "__count__ module disponible",
"moduleCount_plural": "__count__ modules disponibles",
"inuse": "En cours d'utilisation",
@@ -641,6 +643,7 @@
"errors": {
"catalogLoadFailed": "<p>Échec du chargement du catalogue de noeuds.</p><p>Vérifier la console du navigateur pour plus d'informations</p>",
"installFailed": "<p>Échec lors de l'installation : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
"installTimeout": "<p>L'installation continue en arrière-plan.</p><p>Les noeuds apparaîtront dans la palette une fois l'installation terminée. Consulter le journal pour plus d'informations.</p>",
"removeFailed": "<p>Échec lors de la suppression : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
"updateFailed": "<p>Échec lors de la mise à jour : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
"enableFailed": "<p>Échec lors de l'activation : __module__</p><p>__message__</p><p>Consulter le journal pour plus d'informations</p>",
@@ -652,9 +655,12 @@
"title": "Installer les noeuds"
},
"remove": {
"body": "<p>Suppression de '__module__'</p><p>La suppression du noeud le désinstallera de Node-RED. Le noeud peut continuer à utiliser des ressources jusqu'au redémarrage de Node-RED.</p>",
"body": "<p>Suppression de '__module__'</p><p>La suppression du noeud le désinstallera de Node-RED. Le noeud peut continuer à utiliser ses ressources jusqu'au redémarrage de Node-RED.</p>",
"title": "Supprimer les noeuds"
},
"removePlugin": {
"body": "<p>Suppression du plugin '__module__'. Veuillez recharger l'éditeur afin d'appliquer les changements.</p>"
},
"update": {
"body": "<p>Mise à jour de '__module__'</p><p>La mise à jour du noeud nécessitera un redémarrage de Node-RED pour terminer la mise à jour. Cela doit être fait manuellement.</p>",
"title": "Mettre à jour les noeuds"
@@ -666,7 +672,8 @@
"review": "Ouvrir la documentation",
"install": "Installer",
"remove": "Supprimer",
"update": "Mettre à jour"
"update": "Mettre à jour",
"understood": "Compris"
}
}
}

View File

@@ -614,6 +614,8 @@
},
"nodeCount": "__label__ 個のノード",
"nodeCount_plural": "__label__ 個のノード",
"pluginCount": "__count__ 個のプラグイン",
"pluginCount_plural": "__count__ 個のプラグイン",
"moduleCount": "__count__ 個のモジュール",
"moduleCount_plural": "__count__ 個のモジュール",
"inuse": "使用中",
@@ -641,6 +643,7 @@
"errors": {
"catalogLoadFailed": "<p>ノードのカタログの読み込みに失敗しました。</p><p>詳細はブラウザのコンソールを確認してください。</p>",
"installFailed": "<p>追加処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"installTimeout": "<p>バックグラウンドでインストールが継続されます。</p><p>完了した時にノードが表示されます。詳細はログを確認してください。</p>",
"removeFailed": "<p>削除処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"updateFailed": "<p>更新処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
"enableFailed": "<p>有効化処理が失敗しました: __module__</p><p>__message__</p><p>詳細はログを確認してください。</p>",
@@ -655,6 +658,9 @@
"body": "<p>__module__ を削除します。</p><p>Node-REDからードを削除します。ードはNode-REDが再起動されるまで、リソースを使い続ける可能性があります。</p>",
"title": "ノードを削除"
},
"removePlugin": {
"body": "<p>プラグイン __module__ を削除しました。ブラウザを再読み込みして残った表示を消してください。</p>"
},
"update": {
"body": "<p>__module__ を更新します。</p><p>更新を完了するには手動でNode-REDを再起動する必要があります。</p>",
"title": "ノードの更新"
@@ -666,7 +672,8 @@
"review": "ノードの情報を参照",
"install": "追加",
"remove": "削除",
"update": "更新"
"update": "更新",
"understood": "了解"
}
}
}

View File

@@ -29,7 +29,14 @@ RED.history = (function() {
}
return RED.nodes.junction(id);
}
function ensureUnlocked(id, flowsToLock) {
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
const isLocked = flow ? flow.locked : false;
if (flow && isLocked) {
flow.locked = false;
flowsToLock.add(flow)
}
}
function undoEvent(ev) {
var i;
var len;
@@ -59,18 +66,46 @@ RED.history = (function() {
t: 'replace',
config: RED.nodes.createCompleteNodeSet(),
changed: {},
rev: RED.nodes.version()
moved: {},
complete: true,
rev: RED.nodes.version(),
dirty: RED.nodes.dirty()
};
var selectedTab = RED.workspaces.active();
inverseEv.config.forEach(n => {
const node = RED.nodes.node(n.id)
if (node) {
inverseEv.changed[n.id] = node.changed
inverseEv.moved[n.id] = node.moved
}
})
RED.nodes.clear();
var imported = RED.nodes.import(ev.config);
// Clear all change flags from the import
RED.nodes.dirty(false);
const flowsToLock = new Set()
imported.nodes.forEach(function(n) {
if (ev.changed[n.id]) {
ensureUnlocked(n.z, flowsToLock)
n.changed = true;
inverseEv.changed[n.id] = true;
}
if (ev.moved[n.id]) {
ensureUnlocked(n.z, flowsToLock)
n.moved = true;
}
})
flowsToLock.forEach(flow => {
flow.locked = true
})
RED.nodes.version(ev.rev);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.workspaces.show(selectedTab, true);
RED.sidebar.config.refresh();
} else {
var importMap = {};
ev.config.forEach(function(n) {

View File

@@ -365,7 +365,10 @@ RED.tabs = (function() {
var thisTabA = thisTab.find("a");
if (options.onclick) {
options.onclick(tabs[thisTabA.attr('href').slice(1)]);
options.onclick(tabs[thisTabA.attr('href').slice(1)], evt);
if (evt.isDefaultPrevented() && evt.isPropagationStopped()) {
return false
}
}
activateTab(thisTabA);
if (fireSelectionChanged) {
@@ -548,6 +551,8 @@ RED.tabs = (function() {
ul.find("li.red-ui-tab a")
.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
.on("mouseup",onTabClick)
// prevent browser-default middle-click behaviour
.on("auxclick", function(evt) { evt.preventDefault() })
.on("click", function(evt) {evt.preventDefault(); })
.on("dblclick", function(evt) {evt.stopPropagation(); evt.preventDefault(); })
@@ -816,6 +821,8 @@ RED.tabs = (function() {
}
link.on("mousedown", function(evt) { mousedownTab = evt.currentTarget })
link.on("mouseup",onTabClick);
// prevent browser-default middle-click behaviour
link.on("auxclick", function(evt) { evt.preventDefault() })
link.on("click", function(evt) { evt.preventDefault(); })
link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })

View File

@@ -34,6 +34,8 @@ RED.deploy = (function() {
var currentDiff = null;
var activeBackgroundDeployNotification;
function changeDeploymentType(type) {
deploymentType = type;
$("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img);
@@ -133,37 +135,38 @@ RED.deploy = (function() {
}
});
var activeNotifyMessage;
RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
if (!activeNotifyMessage) {
var currentRev = RED.nodes.version();
if (currentRev === null || deployInflight || currentRev === msg.revision) {
return;
}
var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
activeNotifyMessage = RED.notify(message,{
modal: true,
fixed: true,
buttons: [
{
text: RED._('deploy.confirm.button.ignore'),
click: function() {
activeNotifyMessage.close();
activeNotifyMessage = null;
}
},
{
text: RED._('deploy.confirm.button.review'),
class: "primary",
click: function() {
activeNotifyMessage.close();
var nns = RED.nodes.createCompleteNodeSet();
resolveConflict(nns,false);
activeNotifyMessage = null;
}
var currentRev = RED.nodes.version();
if (currentRev === null || deployInflight || currentRev === msg.revision) {
return;
}
if (activeBackgroundDeployNotification?.hidden && !activeBackgroundDeployNotification?.closed) {
activeBackgroundDeployNotification.showNotification()
return
}
const message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate'));
const options = {
id: 'background-update',
type: 'compact',
modal: false,
fixed: true,
timeout: 10000,
buttons: [
{
text: RED._('deploy.confirm.button.review'),
class: "primary",
click: function() {
activeBackgroundDeployNotification.hideNotification();
var nns = RED.nodes.createCompleteNodeSet();
resolveConflict(nns,false);
}
]
});
}
]
}
if (!activeBackgroundDeployNotification || activeBackgroundDeployNotification.closed) {
activeBackgroundDeployNotification = RED.notify(message, options)
} else {
activeBackgroundDeployNotification.update(message, options)
}
});
}
@@ -220,7 +223,11 @@ RED.deploy = (function() {
class: "primary disabled",
click: function() {
if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) {
RED.diff.showRemoteDiff();
RED.diff.showRemoteDiff(null, {
onmerge: function () {
activeBackgroundDeployNotification.close()
}
});
conflictNotification.close();
}
}
@@ -233,6 +240,7 @@ RED.deploy = (function() {
if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
RED.diff.mergeDiff(currentDiff);
conflictNotification.close();
activeBackgroundDeployNotification.close()
}
}
}
@@ -245,6 +253,7 @@ RED.deploy = (function() {
click: function() {
save(true,activeDeploy);
conflictNotification.close();
activeBackgroundDeployNotification.close()
}
})
}
@@ -255,21 +264,17 @@ RED.deploy = (function() {
buttons: buttons
});
var now = Date.now();
RED.diff.getRemoteDiff(function(diff) {
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
currentDiff = diff;
setTimeout(function() {
conflictCheck.hide();
var d = Object.keys(diff.conflicts);
if (d.length === 0) {
conflictAutoMerge.show();
$("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
} else {
conflictManualMerge.show();
}
$("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
},ellapsed);
conflictCheck.hide();
var d = Object.keys(diff.conflicts);
if (d.length === 0) {
conflictAutoMerge.show();
$("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled')
} else {
conflictManualMerge.show();
}
$("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
})
}
function cropList(list) {
@@ -619,7 +624,10 @@ RED.deploy = (function() {
}
});
RED.nodes.eachSubflow(function (subflow) {
subflow.changed = false;
if (subflow.changed) {
subflow.changed = false;
RED.events.emit("subflows:change", subflow);
}
});
RED.nodes.eachWorkspace(function (ws) {
if (ws.changed || ws.added) {

View File

@@ -1,5 +1,4 @@
RED.diff = (function() {
var currentDiff = {};
var diffVisible = false;
var diffList;
@@ -62,12 +61,14 @@ RED.diff = (function() {
addedCount:0,
deletedCount:0,
changedCount:0,
movedCount:0,
unchangedCount: 0
},
remote: {
addedCount:0,
deletedCount:0,
changedCount:0,
movedCount:0,
unchangedCount: 0
},
conflicts: 0
@@ -138,7 +139,7 @@ RED.diff = (function() {
$(this).parent().toggleClass('collapsed');
});
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div);
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode).appendTo(div);
selectState = "";
if (conflicts[tab.id]) {
flowStats.conflicts++;
@@ -208,19 +209,26 @@ RED.diff = (function() {
var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell);
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats);
if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) {
if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.movedCount + flowStats.local.deletedCount > 0) {
$('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats);
if (flowStats.conflicts > 0) {
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats);
}
if (flowStats.local.addedCount > 0) {
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.added'))
}
if (flowStats.local.changedCount > 0) {
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.changed'))
}
if (flowStats.local.movedCount > 0) {
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.movedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.moved'))
}
if (flowStats.local.deletedCount > 0) {
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
}
$('<span class="red-ui-diff-status"> ] </span>').appendTo(localStats);
}
@@ -246,19 +254,26 @@ RED.diff = (function() {
}
var remoteStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell);
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats);
if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) {
if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.movedCount + flowStats.remote.deletedCount > 0) {
$('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats);
if (flowStats.conflicts > 0) {
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats);
}
if (flowStats.remote.addedCount > 0) {
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.added'))
}
if (flowStats.remote.changedCount > 0) {
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.changed'))
}
if (flowStats.remote.movedCount > 0) {
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.movedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.moved'))
}
if (flowStats.remote.deletedCount > 0) {
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
}
$('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats);
}
@@ -293,7 +308,7 @@ RED.diff = (function() {
if (options.mode === "merge") {
diffPanel.addClass("red-ui-diff-panel-merge");
}
var diffList = createDiffTable(diffPanel, diff);
var diffList = createDiffTable(diffPanel, diff, options);
var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff;
@@ -516,7 +531,6 @@ RED.diff = (function() {
var hasChanges = false; // exists in original and local/remote but with changes
var unChanged = true; // existing in original,local,remote unchanged
var localChanged = false;
if (localDiff.added[node.id]) {
stats.local.addedCount++;
@@ -535,12 +549,20 @@ RED.diff = (function() {
unChanged = false;
}
if (localDiff.changed[node.id]) {
stats.local.changedCount++;
if (localDiff.positionChanged[node.id]) {
stats.local.movedCount++
} else {
stats.local.changedCount++;
}
hasChanges = true;
unChanged = false;
}
if (remoteDiff && remoteDiff.changed[node.id]) {
stats.remote.changedCount++;
if (remoteDiff.positionChanged[node.id]) {
stats.remote.movedCount++
} else {
stats.remote.changedCount++;
}
hasChanges = true;
unChanged = false;
}
@@ -605,27 +627,32 @@ RED.diff = (function() {
localNodeDiv.addClass("red-ui-diff-status-moved");
var localMovedMessage = "";
if (node.z === localN.z) {
localMovedMessage = RED._("diff.type.movedFrom",{id:(localDiff.currentConfig.all[node.id].z||'global')});
const movedFromNodeTab = localDiff.currentConfig.all[localDiff.currentConfig.all[node.id].z]
const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
localMovedMessage = RED._("diff.type.movedFrom",{id: movedFromLabel});
} else {
localMovedMessage = RED._("diff.type.movedTo",{id:(localN.z||'global')});
const movedToNodeTab = localDiff.newConfig.all[localN.z]
const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
localMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
}
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv);
}
localChanged = true;
} else if (localDiff.deleted[node.z]) {
localNodeDiv.addClass("red-ui-diff-empty");
localChanged = true;
} else if (localDiff.deleted[node.id]) {
localNodeDiv.addClass("red-ui-diff-status-deleted");
$('<span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(localNodeDiv);
localChanged = true;
} else if (localDiff.changed[node.id]) {
if (localDiff.newConfig.all[node.id].z !== node.z) {
localNodeDiv.addClass("red-ui-diff-empty");
} else {
localNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
localChanged = true;
if (localDiff.positionChanged[node.id]) {
localNodeDiv.addClass("red-ui-diff-status-moved");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(localNodeDiv);
} else {
localNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
}
}
} else {
if (localDiff.newConfig.all[node.id].z !== node.z) {
@@ -646,9 +673,13 @@ RED.diff = (function() {
remoteNodeDiv.addClass("red-ui-diff-status-moved");
var remoteMovedMessage = "";
if (node.z === remoteN.z) {
remoteMovedMessage = RED._("diff.type.movedFrom",{id:(remoteDiff.currentConfig.all[node.id].z||'global')});
const movedFromNodeTab = remoteDiff.currentConfig.all[remoteDiff.currentConfig.all[node.id].z]
const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'`
remoteMovedMessage = RED._("diff.type.movedFrom",{id:movedFromLabel});
} else {
remoteMovedMessage = RED._("diff.type.movedTo",{id:(remoteN.z||'global')});
const movedToNodeTab = remoteDiff.newConfig.all[remoteN.z]
const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'`
remoteMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel});
}
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv);
}
@@ -661,8 +692,13 @@ RED.diff = (function() {
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
remoteNodeDiv.addClass("red-ui-diff-empty");
} else {
remoteNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
if (remoteDiff.positionChanged[node.id]) {
remoteNodeDiv.addClass("red-ui-diff-status-moved");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(remoteNodeDiv);
} else {
remoteNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
}
}
} else {
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
@@ -788,7 +824,7 @@ RED.diff = (function() {
$("<td>",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row);
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
if (localNode) {
localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged"));
localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged"));
$('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
var localPosition = {x:localNode.x,y:localNode.y};
@@ -813,7 +849,7 @@ RED.diff = (function() {
if (remoteNode !== undefined) {
remoteCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row);
remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"changed":"unchanged"));
remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"moved":"unchanged"));
if (remoteNode) {
$('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
@@ -1099,11 +1135,11 @@ RED.diff = (function() {
// var diff = generateDiff(originalFlow,nns);
// showDiff(diff);
// }
function showRemoteDiff(diff) {
if (diff === undefined) {
getRemoteDiff(showRemoteDiff);
function showRemoteDiff(diff, options = {}) {
if (!diff) {
getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options));
} else {
showDiff(diff,{mode:'merge'});
showDiff(diff,{...options, mode:'merge'});
}
}
function parseNodes(nodeList) {
@@ -1144,23 +1180,53 @@ RED.diff = (function() {
}
}
function generateDiff(currentNodes,newNodes) {
var currentConfig = parseNodes(currentNodes);
var newConfig = parseNodes(newNodes);
var added = {};
var deleted = {};
var changed = {};
var moved = {};
const currentConfig = parseNodes(currentNodes);
const newConfig = parseNodes(newNodes);
const added = {};
const deleted = {};
const changed = {};
const positionChanged = {};
const moved = {};
Object.keys(currentConfig.all).forEach(function(id) {
var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
const node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id);
if (!newConfig.all.hasOwnProperty(id)) {
deleted[id] = true;
} else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) {
return
}
const currentConfigJSON = JSON.stringify(currentConfig.all[id])
const newConfigJSON = JSON.stringify(newConfig.all[id])
if (currentConfigJSON !== newConfigJSON) {
changed[id] = true;
if (currentConfig.all[id].z !== newConfig.all[id].z) {
moved[id] = true;
} else if (
currentConfig.all[id].x !== newConfig.all[id].x ||
currentConfig.all[id].y !== newConfig.all[id].y ||
currentConfig.all[id].w !== newConfig.all[id].w ||
currentConfig.all[id].h !== newConfig.all[id].h
) {
// This node's position on its parent has changed. We want to
// check if this is the *only* change for this given node
const currentNodeClone = JSON.parse(currentConfigJSON)
const newNodeClone = JSON.parse(newConfigJSON)
delete currentNodeClone.x
delete currentNodeClone.y
delete currentNodeClone.w
delete currentNodeClone.h
delete newNodeClone.x
delete newNodeClone.y
delete newNodeClone.w
delete newNodeClone.h
if (JSON.stringify(currentNodeClone) === JSON.stringify(newNodeClone)) {
// Only the position has changed - everything else is the same
positionChanged[id] = true
}
}
}
});
Object.keys(newConfig.all).forEach(function(id) {
@@ -1169,13 +1235,14 @@ RED.diff = (function() {
}
});
var diff = {
currentConfig: currentConfig,
newConfig: newConfig,
added: added,
deleted: deleted,
changed: changed,
moved: moved
const diff = {
currentConfig,
newConfig,
added,
deleted,
changed,
positionChanged,
moved
};
return diff;
}
@@ -1240,12 +1307,14 @@ RED.diff = (function() {
return diff;
}
function showDiff(diff,options) {
function showDiff(diff, options) {
if (diffVisible) {
return;
}
options = options || {};
var mode = options.mode || 'merge';
options.hidePositionChanges = true
var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff;
@@ -1315,6 +1384,9 @@ RED.diff = (function() {
if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) {
refreshConflictHeader(diff);
mergeDiff(diff);
if (options.onmerge) {
options.onmerge()
}
RED.tray.close();
}
}
@@ -1345,6 +1417,7 @@ RED.diff = (function() {
var newConfig = [];
var node;
var nodeChangedStates = {};
var nodeMovedStates = {};
var localChangedStates = {};
for (id in localDiff.newConfig.all) {
if (localDiff.newConfig.all.hasOwnProperty(id)) {
@@ -1352,12 +1425,14 @@ RED.diff = (function() {
if (resolutions[id] === 'local') {
if (node) {
nodeChangedStates[id] = node.changed;
nodeMovedStates[id] = node.moved;
}
newConfig.push(localDiff.newConfig.all[id]);
} else if (resolutions[id] === 'remote') {
if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) {
if (node) {
nodeChangedStates[id] = node.changed;
nodeMovedStates[id] = node.moved;
}
localChangedStates[id] = 1;
newConfig.push(remoteDiff.newConfig.all[id]);
@@ -1381,8 +1456,9 @@ RED.diff = (function() {
}
return {
config: newConfig,
nodeChangedStates: nodeChangedStates,
localChangedStates: localChangedStates
nodeChangedStates,
nodeMovedStates,
localChangedStates
}
}
@@ -1393,6 +1469,7 @@ RED.diff = (function() {
var newConfig = appliedDiff.config;
var nodeChangedStates = appliedDiff.nodeChangedStates;
var nodeMovedStates = appliedDiff.nodeMovedStates;
var localChangedStates = appliedDiff.localChangedStates;
var isDirty = RED.nodes.dirty();
@@ -1401,33 +1478,56 @@ RED.diff = (function() {
t:"replace",
config: RED.nodes.createCompleteNodeSet(),
changed: nodeChangedStates,
moved: nodeMovedStates,
complete: true,
dirty: isDirty,
rev: RED.nodes.version()
}
RED.history.push(historyEvent);
var originalFlow = RED.nodes.originalFlow();
// originalFlow is what the editor things it loaded
// - add any newly added nodes from remote diff as they are now part of the record
for (var id in diff.remoteDiff.added) {
if (diff.remoteDiff.added.hasOwnProperty(id)) {
if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
}
}
}
// var originalFlow = RED.nodes.originalFlow();
// // originalFlow is what the editor thinks it loaded
// // - add any newly added nodes from remote diff as they are now part of the record
// for (var id in diff.remoteDiff.added) {
// if (diff.remoteDiff.added.hasOwnProperty(id)) {
// if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
// originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
// }
// }
// }
RED.nodes.clear();
var imported = RED.nodes.import(newConfig);
// Restore the original flow so subsequent merge resolutions can properly
// identify new-vs-old
RED.nodes.originalFlow(originalFlow);
// // Restore the original flow so subsequent merge resolutions can properly
// // identify new-vs-old
// RED.nodes.originalFlow(originalFlow);
// Clear all change flags from the import
RED.nodes.dirty(false);
const flowsToLock = new Set()
function ensureUnlocked(id) {
const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null);
const isLocked = flow ? flow.locked : false;
if (flow && isLocked) {
flow.locked = false;
flowsToLock.add(flow)
}
}
imported.nodes.forEach(function(n) {
if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
if (nodeChangedStates[n.id]) {
ensureUnlocked(n.z)
n.changed = true;
}
if (nodeMovedStates[n.id]) {
ensureUnlocked(n.z)
n.moved = true;
}
})
flowsToLock.forEach(flow => {
flow.locked = true
})
RED.nodes.version(diff.remoteDiff.rev);

View File

@@ -1721,8 +1721,8 @@ RED.editor = (function() {
}
if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env;
editState.changes.env = editing_node.env;
editing_node.env = new_env;
editState.changed = true;
}

View File

@@ -514,7 +514,7 @@ RED.editor.codeEditor.monaco = (function() {
_monaco.languages.json.jsonDefaults.setDiagnosticsOptions(diagnosticOptions);
if(modeConfiguration) { _monaco.languages.json.jsonDefaults.setModeConfiguration(modeConfiguration); }
} catch (error) {
console.warn("monaco - Error setting up json options", err)
console.warn("monaco - Error setting up json options", error)
}
}
@@ -526,7 +526,7 @@ RED.editor.codeEditor.monaco = (function() {
if(htmlDefaults) { _monaco.languages.html.htmlDefaults.setOptions(htmlDefaults); }
if(handlebarDefaults) { _monaco.languages.html.handlebarDefaults.setOptions(handlebarDefaults); }
} catch (error) {
console.warn("monaco - Error setting up html options", err)
console.warn("monaco - Error setting up html options", error)
}
}
@@ -546,7 +546,7 @@ RED.editor.codeEditor.monaco = (function() {
if(lessDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(lessDefaults_modeConfiguration); }
if(scssDefaults_modeConfiguration) { _monaco.languages.css.cssDefaults.setDiagnosticsOptions(scssDefaults_modeConfiguration); }
} catch (error) {
console.warn("monaco - Error setting up CSS/SCSS/LESS options", err)
console.warn("monaco - Error setting up CSS/SCSS/LESS options", error)
}
}

View File

@@ -221,12 +221,12 @@ RED.notifications = (function() {
if (newType) {
n.className = "red-ui-notification red-ui-notification-"+newType;
}
newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout
if (!fixed || newOptions.fixed === false) {
newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000;
newTimeout = newTimeout || 5000
}
if (newOptions.buttons) {
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn)
var buttonSet = $('<div class="ui-dialog-buttonset"></div>').appendTo(nn)
newOptions.buttons.forEach(function(buttonDef) {
var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet);
if (buttonDef.id) {
@@ -272,6 +272,15 @@ RED.notifications = (function() {
};
})());
n.timeoutid = window.setTimeout(n.close,timeout||5000);
} else if (timeout) {
$(n).on("click.red-ui-notification-close", (function() {
var nn = n;
return function() {
nn.hideNotification();
window.clearTimeout(nn.timeoutid);
};
})());
n.timeoutid = window.setTimeout(n.hideNotification,timeout||5000);
}
currentNotifications.push(n);
if (options.id) {

View File

@@ -133,7 +133,7 @@ RED.palette.editor = (function() {
}).done(function(data,textStatus,xhr) {
callback();
}).fail(function(xhr,textStatus,err) {
callback(xhr);
callback(xhr,textStatus,err);
});
}
function removeNodeModule(id,callback) {
@@ -1346,13 +1346,13 @@ RED.palette.editor = (function() {
});
if (!found_onremove) {
let removeNotify = RED.notify("Removed plugin " + entry.name + ". Please reload the editor to clear left-overs.",{
let removeNotify = RED.notify(RED._("palette.editor.confirm.removePlugin.body",{module:entry.name}),{
modal: true,
fixed: true,
type: 'warning',
buttons: [
{
text: "Understood",
text: RED._("palette.editor.confirm.button.understood"),
class:"primary",
click: function(e) {
removeNotify.close();
@@ -1405,9 +1405,28 @@ RED.palette.editor = (function() {
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) {
installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr, textStatus,err) {
spinner.remove();
if (xhr) {
if (err && xhr.status === 504) {
var notification = RED.notify(RED._("palette.editor.errors.installTimeout"), {
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
},{
text: RED._("eventLog.view"),
click: function() {
notification.close();
RED.actions.invoke("core:show-event-log");
}
}
]
})
} else if (xhr) {
if (xhr.responseJSON) {
var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{
type: 'error',

View File

@@ -287,7 +287,7 @@ RED.projects.settings = (function() {
var notInstalledCount = 0;
for (var m in modulesInUse) {
if (modulesInUse.hasOwnProperty(m)) {
if (modulesInUse.hasOwnProperty(m) && !activeProject.dependencies.hasOwnProperty(m)) {
depsList.editableList('addItem',{
id: modulesInUse[m].module,
version: modulesInUse[m].version,
@@ -307,8 +307,8 @@ RED.projects.settings = (function() {
if (activeProject.dependencies) {
for (var m in activeProject.dependencies) {
if (activeProject.dependencies.hasOwnProperty(m) && !modulesInUse.hasOwnProperty(m)) {
var installed = !!RED.nodes.registry.getModule(m);
if (activeProject.dependencies.hasOwnProperty(m)) {
var installed = !!RED.nodes.registry.getModule(m) && activeProject.dependencies[m] === modulesInUse[m].version;
depsList.editableList('addItem',{
id: m,
version: activeProject.dependencies[m], //RED.nodes.registry.getModule(module).version,

View File

@@ -1363,7 +1363,7 @@ RED.subflow = (function() {
break;
case "conf-types":
item.value = input.val()
item.type = data.parent.value;
item.type = "conf-type"
}
if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
env.push(item);

View File

@@ -359,11 +359,17 @@ RED.workspaces = (function() {
RED.sidebar.config.refresh();
RED.view.focus();
},
onclick: function(tab) {
if (tab.id !== activeWorkspace) {
addToViewStack(activeWorkspace);
onclick: function(tab, evt) {
if(evt.which === 2) {
evt.preventDefault();
evt.stopPropagation();
RED.actions.invoke("core:hide-flow", tab)
} else {
if (tab.id !== activeWorkspace) {
addToViewStack(activeWorkspace);
}
RED.view.focus();
}
RED.view.focus();
},
ondblclick: function(tab) {
if (tab.type != "subflow") {

View File

@@ -16,7 +16,9 @@ export default {
},
{
title: {
"en-US": "Multiplayer Mode"
"en-US": "Multiplayer Mode",
"ja": "複数ユーザ同時利用モード",
"fr": "Mode Multi-utilisateur"
},
image: 'images/nr4-multiplayer.png',
description: {
@@ -24,46 +26,77 @@ export default {
to work with when you have multiple people editing flows at the same time.</p>
<p>When this feature is enabled, you will now see who else has the editor open and some
basic information on where they are in the editor.</p>
<p>Check the release post for details on how to enable this feature in your settings file.</p>`
<p>Check the release post for details on how to enable this feature in your settings file.</p>`,
"ja": `<p>本リリースには、複数ユーザが同時にフローを編集する時に、Node-REDをより使いやすくするのための最初の微修正が入っています。</p>
<p>本機能を有効にすると、誰がエディタを開いているか、その人がエディタ上のどこにいるかの基本的な情報が表示されます。</p>
<p>設定ファイルで本機能を有効化する方法の詳細は、リリースの投稿を確認してください。</p>`,
"fr": `<p>Cette version inclut les premières étapes visant à rendre Node-RED plus facile à utiliser
lorsque plusieurs personnes modifient des flux en même temps.</p>
<p>Lorsque cette fonctionnalité est activée, vous pourrez désormais voir si dautres utilisateurs ont
ouvert l'éditeur. Vous pourrez également savoir où ces utilisateurs se trouvent dans l'éditeur.</p>
<p>Consultez la note de publication pour plus de détails sur la façon d'activer cette fonctionnalité
dans votre fichier de paramètres.</p>`
}
},
{
title: {
"en-US": "Better Configuration Node UX"
"en-US": "Better Configuration Node UX",
"ja": "設定ードのUXが向上",
"fr": "Meilleure expérience utilisateur du noeud de configuration"
},
image: 'images/nr4-config-select.png',
description: {
"en-US": `<p>The Configuration node selection UI has had a small update to have a dedicated 'add' button
next to the select box.</p>
<p>It's a small change, but should make it easier to work with your config nodes.</p>`
<p>It's a small change, but should make it easier to work with your config nodes.</p>`,
"ja": `<p>設定ードを選択するUIが修正され、選択ボックスの隣に専用の「追加」ボタンが追加されました。</p>
<p>微修正ですが設定ノードの操作が容易になります。</p>`,
"fr": `<p>L'interface utilisateur de la sélection du noeud de configuration a fait l'objet d'une petite
mise à jour afin de disposer d'un bouton « Ajouter » à côté de la zone de sélection.</p>
<p>C'est un petit changement, mais cela devrait faciliter le travail avec vos noeuds de configuration.</p>`
}
},
{
title: {
"en-US": "Remembering palette state"
"en-US": "Remembering palette state",
"ja": "パレットの状態を維持",
"fr": "Mémorisation de l'état de la palette"
},
description: {
"en-US": `<p>The palette now remembers what categories you have hidden between reloads - as well as any
filter you have applied.</p>`
filter you have applied.</p>`,
"ja": `<p>パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。</p>`,
"fr": `<p>La palette se souvient désormais des catégories que vous avez masquées entre les rechargements,
ainsi que le filtre que vous avez appliqué.</p>`
}
},
{
title: {
"en-US": "Plugins shown in the Palette Manager"
"en-US": "Plugins shown in the Palette Manager",
"ja": "パレット管理にプラグインを表示",
"fr": "Affichage des Plugins dans le gestionnaire de palettes"
},
image: 'images/nr4-plugins.png',
description: {
"en-US": `<p>The palette manager now shows any plugin modules you have installed, such as
<code>node-red-debugger</code>. Previously they would only be shown if they plugin include
nodes for the palette.</p>`
<code>node-red-debugger</code>. Previously they would only be shown if the plugins include
nodes for the palette.</p>`,
"ja": `<p>パレットの管理に <code>node-red-debugger</code> の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。</p>`,
"fr": `<p>Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés,
tels que <code>node-red-debugger</code>. Auparavant, ils n'étaient affichés que s'ils contenaient
des noeuds pour la palette.</p>`
}
},
{
title: {
"en-US": "That's if for Beta 2!"
"en-US": "That's it for Beta 2!",
"ja": "ベータ2については以上です!",
"fr": "C'est tout pour la bêta 2 !"
},
description: {
"en-US": `<p>Keep clicking through to see what was added in Beta 1</p>`
"en-US": `<p>Keep clicking through to see what was added in Beta 1</p>`,
"ja": `<p>クリックを続けてベータ1で追加された内容を確認してください。</p>`,
"fr": `<p>Continuez à cliquer pour voir ce qui a été ajouté dans la version bêta 1</p>`
}
},
{

View File

@@ -76,7 +76,7 @@ declare namespace RED {
*/
function compareObjects(obj1: any, obj2: any): boolean;
/**
* Generates a psuedo-unique-random id.
* Generates a pseudo-unique-random id.
* @return {string} a random-ish id
* @memberof @node-red/util_util
*/

View File

@@ -103,7 +103,7 @@
<h4>Automatic mode</h4>
<p>Automatic mode uses the <code>parts</code> property of incoming messages to
determine how the sequence should be joined. This allows it to automatically
reverse the action of a <b>split</b> node.
reverse the action of a <b>split</b> node.</p>
<h4>Manual mode</h4>
<p>When configured to join in manual mode, the node is able to join sequences

View File

@@ -28,11 +28,6 @@ let installEnabled = true;
let installAllowList = ['*'];
let installDenyList = [];
let IMPORT_SUPPORTED = true;
const nodeVersionParts = process.versions.node.split(".").map(v => parseInt(v));
if (nodeVersionParts[0] < 12 || (nodeVersionParts[0] === 12 && nodeVersionParts[1] < 17)) {
IMPORT_SUPPORTED = false;
}
function getInstallDir() {
return path.resolve(settings.userDir || process.env.NODE_RED_HOME || ".");
@@ -110,18 +105,6 @@ function requireModule(module) {
return require(moduleDir);
}
function importModule(module) {
if (!IMPORT_SUPPORTED) {
// On Node < 12.17 - fall back to try a require
return new Promise((resolve, reject) => {
try {
const mod = requireModule(module);
resolve(mod);
} catch(err) {
reject(err);
}
});
}
if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
const e = new Error("Module not allowed");
e.code = "module_not_allowed";

View File

@@ -88,7 +88,7 @@ function generateSubflowConfig(subflow) {
this.credentials['has_' + prop.name] = (this.credentials[prop.name] !== "");
} else {
switch(prop.type) {
case "str": this[prop.name] = prop.value||""; break;
case "str": case "conf-type": this[prop.name] = prop.value||""; break;
case "bool": this[prop.name] = (typeof prop.value === 'boolean')?prop.value:prop.value === "true" ; break;
case "num": this[prop.name] = (typeof prop.value === 'number')?prop.value:Number(prop.value); break;
default:

View File

@@ -15,6 +15,7 @@
**/
const clone = require("clone");
const jsonClone = require("rfdc")();
const Flow = require('./Flow').Flow;
const context = require('../nodes/context');
const util = require("util");
@@ -108,7 +109,7 @@ class Subflow extends Flow {
}
}
subflowInternalFlowConfig.subflows = clone(subflowDef.subflows || {});
subflowInternalFlowConfig.subflows = jsonClone(subflowDef.subflows || {});
remapSubflowNodes(subflowInternalFlowConfig.configs,node_map);
remapSubflowNodes(subflowInternalFlowConfig.nodes,node_map);
@@ -220,7 +221,7 @@ class Subflow extends Flow {
}
if (this.subflowDef.in) {
subflowInstanceConfig.wires = this.subflowDef.in.map(function(n) { return n.wires.map(function(w) { return self.node_map[w.id].id;})})
subflowInstanceConfig._originalWires = clone(subflowInstanceConfig.wires);
subflowInstanceConfig._originalWires = jsonClone(subflowInstanceConfig.wires);
}
this.node = new Node(subflowInstanceConfig);
@@ -244,14 +245,14 @@ class Subflow extends Flow {
if (self.subflowDef.out) {
var node,wires,i,j;
// Restore the original wiring to the internal nodes
subflowInstanceConfig.wires = clone(subflowInstanceConfig._originalWires);
subflowInstanceConfig.wires = jsonClone(subflowInstanceConfig._originalWires);
for (i=0;i<self.subflowDef.out.length;i++) {
wires = self.subflowDef.out[i].wires;
for (j=0;j<wires.length;j++) {
if (wires[j].id != self.subflowDef.id) {
node = self.node_map[wires[j].id];
if (node && node._originalWires) {
node.wires = clone(node._originalWires);
node.wires = jsonClone(node._originalWires);
}
}
}
@@ -300,7 +301,7 @@ class Subflow extends Flow {
var node = self.node_map[wires[j].id];
if (node) {
if (!node._originalWires) {
node._originalWires = clone(node.wires);
node._originalWires = jsonClone(node.wires);
}
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(this.subflowInstance.wires[i]);
} else {
@@ -323,7 +324,7 @@ class Subflow extends Flow {
var node = self.node_map[wires[j].id];
if (node) {
if (!node._originalWires) {
node._originalWires = clone(node.wires);
node._originalWires = jsonClone(node.wires);
}
node.wires[wires[j].port] = (node.wires[wires[j].port]||[]);
node.wires[wires[j].port].push(subflowStatusId);
@@ -463,7 +464,7 @@ class Subflow extends Flow {
* @return {[type]} [description]
*/
function createNodeInSubflow(subflowInstanceId, def) {
let node = clone(def);
let node = jsonClone(def);
let nid = `${subflowInstanceId}-${node.id}` //redUtil.generateId();
// console.log("Create Node In subflow",node._alias, "--->",nid, "(",node.type,")")
// node_map[node.id] = node;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
**/
var clone = require("clone");
const jsonClone = require("rfdc")();
var Flow = require('./Flow');
@@ -140,16 +140,16 @@ function setFlows(_config,_credentials,type,muteLog,forceStart,user) {
if (type === "load") {
isLoad = true;
configSavePromise = loadFlows().then(function(_config) {
config = clone(_config.flows);
newFlowConfig = flowUtil.parseConfig(clone(config));
config = jsonClone(_config.flows);
newFlowConfig = flowUtil.parseConfig(jsonClone(config));
type = "full";
return _config.rev;
});
} else {
// Clone the provided config so it can be manipulated
config = clone(_config);
config = jsonClone(_config);
// Parse the configuration
newFlowConfig = flowUtil.parseConfig(clone(config));
newFlowConfig = flowUtil.parseConfig(jsonClone(config));
// Generate a diff to identify what has changed
diff = flowUtil.diffConfigs(activeFlowConfig,newFlowConfig);
@@ -609,7 +609,7 @@ async function addFlow(flow, user) {
nodes.push(node);
}
}
var newConfig = clone(activeConfig.flows);
var newConfig = jsonClone(activeConfig.flows);
newConfig = newConfig.concat(nodes);
return setFlows(newConfig, null, 'flows', true, null, user).then(function() {
@@ -650,7 +650,7 @@ function getFlow(id) {
var nodeIds = Object.keys(flow.nodes);
if (nodeIds.length > 0) {
result.nodes = nodeIds.map(function(nodeId) {
var node = clone(flow.nodes[nodeId]);
var node = jsonClone(flow.nodes[nodeId]);
if (node.type === 'link out') {
delete node.wires;
}
@@ -662,7 +662,7 @@ function getFlow(id) {
if (flow.configs) {
var configIds = Object.keys(flow.configs);
result.configs = configIds.map(function(configId) {
const node = clone(flow.configs[configId]);
const node = jsonClone(flow.configs[configId]);
delete node.credentials;
return node
@@ -674,17 +674,17 @@ function getFlow(id) {
if (flow.subflows) {
var subflowIds = Object.keys(flow.subflows);
result.subflows = subflowIds.map(function(subflowId) {
var subflow = clone(flow.subflows[subflowId]);
var subflow = jsonClone(flow.subflows[subflowId]);
var nodeIds = Object.keys(subflow.nodes);
subflow.nodes = nodeIds.map(function(id) {
const node = clone(subflow.nodes[id])
const node = jsonClone(subflow.nodes[id])
delete node.credentials
return node
});
if (subflow.configs) {
var configIds = Object.keys(subflow.configs);
subflow.configs = configIds.map(function(id) {
const node = clone(subflow.configs[id])
const node = jsonClone(subflow.configs[id])
delete node.credentials
return node
})
@@ -709,7 +709,7 @@ async function updateFlow(id,newFlow, user) {
}
label = activeFlowConfig.flows[id].label;
}
var newConfig = clone(activeConfig.flows);
var newConfig = jsonClone(activeConfig.flows);
var nodes;
if (id === 'global') {
@@ -779,7 +779,7 @@ async function removeFlow(id, user) {
throw e;
}
var newConfig = clone(activeConfig.flows);
var newConfig = jsonClone(activeConfig.flows);
newConfig = newConfig.filter(function(node) {
return node.z !== id && node.id !== id;
});

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
const clone = require("clone");
const jsonClone = require("rfdc")();
const redUtil = require("@node-red/util").util;
const Log = require("@node-red/util").log;
const typeRegistry = require("@node-red/registry");
@@ -68,7 +68,7 @@ function mapEnvVarProperties(obj,prop,flow,config) {
if (obj[prop][0] === "$" && (EnvVarPropertyRE_old.test(v) || EnvVarPropertyRE.test(v)) ) {
const envVar = v.substring(2,v.length-1);
const r = redUtil.getSetting(config, envVar, flow);
if (r !== undefined && r !== '') {
if (r !== undefined) {
obj[prop] = r
}
}
@@ -175,7 +175,7 @@ async function createNode(flow,config) {
try {
var nodeTypeConstructor = typeRegistry.get(type);
if (typeof nodeTypeConstructor === "function") {
var conf = clone(config);
var conf = jsonClone(config);
delete conf.credentials;
try {
Object.defineProperty(conf,'_module', {value: typeRegistry.getNodeInfo(type), enumerable: false, writable: true })
@@ -202,8 +202,8 @@ async function createNode(flow,config) {
var subflowInstanceConfig = subflowConfig.subflows[nodeTypeConstructor.subflow.id];
delete subflowConfig.subflows[nodeTypeConstructor.subflow.id];
subflowInstanceConfig.subflows = subflowConfig.subflows;
var instanceConfig = clone(config);
instanceConfig.env = clone(nodeTypeConstructor.subflow.env);
var instanceConfig = jsonClone(config);
instanceConfig.env = jsonClone(nodeTypeConstructor.subflow.env);
instanceConfig.env = nodeTypeConstructor.subflow.env.map(nodeProp => {
var nodePropType;
@@ -256,7 +256,7 @@ function parseConfig(config) {
flow.missingTypes = [];
config.forEach(function (n) {
flow.allNodes[n.id] = clone(n);
flow.allNodes[n.id] = jsonClone(n);
if (n.type === 'tab') {
flow.flows[n.id] = n;
flow.flows[n.id].subflows = {};

View File

@@ -14,9 +14,8 @@
* limitations under the License.
**/
var path = require("path");
var fs = require("fs");
var clone = require("clone");
const jsonClone = require("rfdc")();
var util = require("util");
var registry = require("@node-red/registry");
@@ -98,7 +97,7 @@ function createNode(node,def) {
}
var creds = credentials.get(id);
if (creds) {
creds = clone(creds);
creds = jsonClone(creds);
//console.log("Attaching credentials to ",node.id);
// allow $(foo) syntax to substitute env variables for credentials also...
for (var p in creds) {

View File

@@ -242,7 +242,9 @@ function loadProject(name) {
function getProject(user, name) {
checkActiveProject(name);
return Promise.resolve(activeProject.export());
return loadProject(name).then(function () {
return Promise.resolve(activeProject.export());
});
}
function deleteProject(user, name) {

View File

@@ -56,7 +56,6 @@
"refresh-interval": "Erneuerung der https-Einstellungen erfolgt alle __interval__ Stunden",
"settings-refreshed": "https-Einstellungen wurden erneuert",
"refresh-failed": "Erneuerung der https-Einstellungen fehlgeschlagen: __message__",
"nodejs-version": "httpsRefreshInterval erfordert Node.js 11 oder höher",
"function-required": "httpsRefreshInterval erfordert die https-Eigenschaft in Form einer Funktion"
}
},

View File

@@ -58,7 +58,6 @@
"refresh-interval": "Refreshing https settings every __interval__ hours",
"settings-refreshed": "Server https settings have been refreshed",
"refresh-failed": "Failed to refresh https settings: __message__",
"nodejs-version": "httpsRefreshInterval requires Node.js 11 or later",
"function-required": "httpsRefreshInterval requires https property to be a function"
}
},

View File

@@ -57,7 +57,6 @@
"refresh-interval": "Actualizando la configuración HTTPS cada __interval__ horas",
"settings-refreshed": "La configuración HTTPS del servidor se ha actualizado",
"refresh-failed": "No se pudo actualizar la configuración HTTPS: __message__",
"nodejs-version": "httpsRefreshInterval requiere Node.js 11 o superior",
"function-required": "httpsRefreshInterval requiere que la propiedad HTTPS sea una función"
}
},

View File

@@ -20,10 +20,12 @@
"errors-help": "Exécuter avec -v pour plus de détails",
"missing-modules": "Modules de noeud manquants :",
"node-version-mismatch": "Le module de noeud ne peut pas être chargé sur cette version. Nécessite : __version__ ",
"set-has-no-types": "L'ensemble n'a aucun type. Nom : '__name__', module : '__module__', fichier : '__file__'",
"type-already-registered": "'__type__' déjà enregistré par le module __module__",
"removing-modules": "Suppression de modules de la configuration",
"added-types": "Types de noeuds ajoutés :",
"removed-types": "Types de noeuds supprimés :",
"removed-plugins": "Plugins supprimés :",
"install": {
"invalid": "Nom de module invalide",
"installing": "Installation du module : __name__, version : __version__",
@@ -56,7 +58,6 @@
"refresh-interval": "Actualisation des paramètres https toutes les __interval__ heures",
"settings-refreshed": "Les paramètres https du serveur ont été actualisés",
"refresh-failed": "Échec de l'actualisation des paramètres https : __message__",
"nodejs-version": "httpsRefreshInterval nécessite Node.js 11 ou version ultérieure",
"function-required": "httpsRefreshInterval nécessite que la propriété https soit une fonction"
}
},
@@ -134,7 +135,8 @@
"flow": {
"unknown-type": "Type inconnu : __type__",
"missing-types": "Types manquants",
"error-loop": "Le message a dépassé le nombre maximum de captures (catches)"
"error-loop": "Le message a dépassé le nombre maximum de captures (catches)",
"non-message-returned": "Le noeud a tenté d'envoyer un message du type __type__"
},
"index": {
"unrecognised-id": "Identifiant non reconnu : __id__",

View File

@@ -25,6 +25,7 @@
"removing-modules": "設定からモジュールを削除します",
"added-types": "追加したノード:",
"removed-types": "削除したノード:",
"removed-plugins": "削除したプラグイン:",
"install": {
"invalid": "不正なモジュール名",
"installing": "モジュール __name__, バージョン: __version__ をインストールします",
@@ -57,7 +58,6 @@
"refresh-interval": "__interval__ 時間毎にhttps設定を更新します",
"settings-refreshed": "サーバのhttps設定が更新されました",
"refresh-failed": "https設定の更新で失敗しました: __message__",
"nodejs-version": "httpsRefreshIntervalにはNode.js 11以降が必要です",
"function-required": "httpsRefreshIntervalでは、httpsプロパティはfunctionである必要があります"
}
},

View File

@@ -57,7 +57,6 @@
"refresh-interval": "Atualizando as configurações de https a cada __interval__ hora(s)",
"settings-refreshed": "As configurações https do servidor foram atualizadas",
"refresh-failed": "Falha ao atualizar as configurações https: __message__",
"nodejs-version": "httpsRefreshInterval requer Node.js 11 ou posterior",
"function-required": "httpsRefreshInterval requer que a propriedade https seja uma função"
}
},

View File

@@ -55,7 +55,6 @@
"refresh-interval": "Обновление настроек https каждые __interval__ часов",
"settings-refreshed": "Настройки сервера https обновлены",
"refresh-failed": "Не удалось обновить настройки https: __message__",
"nodejs-version": "httpsRefreshInterval требует Node.js 11 или выше",
"function-required": "httpsRefreshInterval требует, чтобы свойство https было функцией"
}
},

View File

@@ -22,6 +22,7 @@
"clone": "2.1.2",
"express": "4.19.2",
"fs-extra": "11.1.1",
"json-stringify-safe": "5.0.1"
"json-stringify-safe": "5.0.1",
"rfdc": "^1.3.1"
}
}

View File

@@ -27,7 +27,7 @@ const util = require("util");
const { hasOwnProperty } = Object.prototype;
const log = require("./log")
/**
* Safely returns the object construtor name.
* Safely returns the object constructor name.
* @return {String} the name of the object constructor if it exists, empty string otherwise.
*/
function constructorName(obj) {
@@ -37,7 +37,7 @@ function constructorName(obj) {
}
/**
* Generates a psuedo-unique-random id.
* Generates a pseudo-unique-random id.
* @return {String} a random-ish id
* @memberof @node-red/util_util
*/

View File

@@ -25,11 +25,9 @@ var api = require("@node-red/editor-api");
var server = null;
var apiEnabled = false;
const NODE_MAJOR_VERSION = process.versions.node.split('.')[0];
if (NODE_MAJOR_VERSION >= 16) {
const dns = require('dns');
dns.setDefaultResultOrder('ipv4first');
}
// Ensure ipv4 results are returned first: https://github.com/node-red/node-red/issues/4010
const dns = require('dns');
dns.setDefaultResultOrder('ipv4first');
function checkVersion(userSettings) {
var semver = require('semver');

View File

@@ -47,6 +47,6 @@
"bcrypt": "5.1.0"
},
"engines": {
"node": ">=18"
"node": ">=18.5"
}
}

View File

@@ -240,39 +240,34 @@ httpsPromise.then(function(startupHttps) {
// Max value based on (2^31-1)ms - the max that setInterval can accept
httpsRefreshInterval = 596;
}
// Check whether setSecureContext is available (Node.js 11+)
if (server.setSecureContext) {
// Check whether `http` is a callable function
if (typeof settings.https === "function") {
delayedLogItems.push({type:"info", id:"server.https.refresh-interval", params:{interval:httpsRefreshInterval}});
setInterval(function () {
try {
// Get the result of the function, because createServer doesn't accept functions as input
Promise.resolve(settings.https()).then(function(refreshedHttps) {
if (refreshedHttps) {
// The key/cert needs to be updated in the NodeJs http(s) server, when no key/cert is yet available or when the key/cert has changed.
// Note that the refreshed key/cert can be supplied as a string or a buffer.
var updateKey = (server.key == undefined || (Buffer.isBuffer(server.key) && !server.key.equals(refreshedHttps.key)) || (typeof server.key == "string" && server.key != refreshedHttps.key));
var updateCert = (server.cert == undefined || (Buffer.isBuffer(server.cert) && !server.cert.equals(refreshedHttps.cert)) || (typeof server.cert == "string" && server.cert != refreshedHttps.cert));
// Check whether `http` is a callable function
if (typeof settings.https === "function") {
delayedLogItems.push({type:"info", id:"server.https.refresh-interval", params:{interval:httpsRefreshInterval}});
setInterval(function () {
try {
// Get the result of the function, because createServer doesn't accept functions as input
Promise.resolve(settings.https()).then(function(refreshedHttps) {
if (refreshedHttps) {
// The key/cert needs to be updated in the NodeJs http(s) server, when no key/cert is yet available or when the key/cert has changed.
// Note that the refreshed key/cert can be supplied as a string or a buffer.
var updateKey = (server.key == undefined || (Buffer.isBuffer(server.key) && !server.key.equals(refreshedHttps.key)) || (typeof server.key == "string" && server.key != refreshedHttps.key));
var updateCert = (server.cert == undefined || (Buffer.isBuffer(server.cert) && !server.cert.equals(refreshedHttps.cert)) || (typeof server.cert == "string" && server.cert != refreshedHttps.cert));
// Only update the credentials in the server when key or cert has changed
if(updateKey || updateCert) {
server.setSecureContext(refreshedHttps);
RED.log.info(RED.log._("server.https.settings-refreshed"));
}
// Only update the credentials in the server when key or cert has changed
if(updateKey || updateCert) {
server.setSecureContext(refreshedHttps);
RED.log.info(RED.log._("server.https.settings-refreshed"));
}
}).catch(function(err) {
RED.log.error(RED.log._("server.https.refresh-failed",{message:err}));
});
} catch(err) {
}
}).catch(function(err) {
RED.log.error(RED.log._("server.https.refresh-failed",{message:err}));
}
}, httpsRefreshInterval*60*60*1000);
} else {
delayedLogItems.push({type:"warn", id:"server.https.function-required"});
}
});
} catch(err) {
RED.log.error(RED.log._("server.https.refresh-failed",{message:err}));
}
}, httpsRefreshInterval*60*60*1000);
} else {
delayedLogItems.push({type:"warn", id:"server.https.nodejs-version"});
delayedLogItems.push({type:"warn", id:"server.https.function-required"});
}
}
} else {

View File

@@ -2509,69 +2509,59 @@ describe('HTTP Request Node', function() {
});
describe('should parse broken headers', function() {
let port = testPort++
const versions = process.versions.node.split('.')
let server;
if (( versions[0] == 14 && versions[1] >= 20 ) ||
( versions[0] == 16 && versions[1] >= 16 ) ||
( versions[0] == 18 && versions[1] >= 5 ) ||
( versions[0] > 18)) {
// only test if on new enough NodeJS version
before(function() {
server = net.createServer(function (socket) {
socket.write("HTTP/1.0 200\nContent-Type: text/plain\n\nHelloWorld")
socket.end()
})
let port = testPort++
server.listen(port,'127.0.0.1', function(err) {
})
});
let server;
after(function() {
server.close()
});
before(function() {
server = net.createServer(function (socket) {
socket.write("HTTP/1.0 200\nContent-Type: text/plain\n\nHelloWorld")
socket.end()
it('should accept broken headers', function (done) {
var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`, insecureHTTPParser: true},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on('input', function(msg) {
try {
msg.payload.should.equal('HelloWorld')
done()
} catch (err) {
done(err)
}
})
n1.receive({payload: 'foo'})
});
});
server.listen(port,'127.0.0.1', function(err) {
it('should reject broken headers', function (done) {
var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on('input', function(msg) {
try{
msg.payload.should.match(/RequestError: Parse Error/)
done()
} catch (err) {
done(err)
}
})
});
n1.receive({payload: 'foo'})
after(function() {
server.close()
});
it('should accept broken headers', function (done) {
var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`, insecureHTTPParser: true},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on('input', function(msg) {
try {
msg.payload.should.equal('HelloWorld')
done()
} catch (err) {
done(err)
}
})
n1.receive({payload: 'foo'})
});
});
it('should reject broken headers', function (done) {
var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:`http://localhost:${port}/`},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on('input', function(msg) {
try{
msg.payload.should.match(/RequestError: Parse Error/)
done()
} catch (err) {
done(err)
}
})
n1.receive({payload: 'foo'})
});
});
}
});
});
});