mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Compare commits
75 Commits
fix-subflo
...
4648-reado
Author | SHA1 | Date | |
---|---|---|---|
|
5494c167fc | ||
|
c5ae0be7b1 | ||
|
b653914ee0 | ||
|
c107c5fc92 | ||
|
0980c03129 | ||
|
f6c3fdc806 | ||
|
2c2628d816 | ||
|
56fe2801eb | ||
|
87b1ee9642 | ||
|
e1c36d232b | ||
|
13ee8cec24 | ||
|
a977b87cb3 | ||
|
14dfb9aef8 | ||
|
d520cde57a | ||
|
70167d7d1d | ||
|
3389c8160b | ||
|
c214710f8e | ||
|
ac6a4945cb | ||
|
fd1a001a23 | ||
|
f3c561cd86 | ||
|
4e33e785fb | ||
|
f55ee6e665 | ||
|
edc5e88d5a | ||
|
47bf166a6e | ||
|
cf26209790 | ||
|
e55ebde170 | ||
|
a745ddc164 | ||
|
18d0fa2259 | ||
|
d706c9cb37 | ||
|
20d2450cac | ||
|
34345461f1 | ||
|
aa372a1707 | ||
|
03648dc7e8 | ||
|
66a667fe58 | ||
|
1bb3a0eca5 | ||
|
0e0bba25c1 | ||
|
af701d65ac | ||
|
08927dfb55 | ||
|
b27483de9c | ||
|
b02f69b77a | ||
|
598b0c84ab | ||
|
22cc8da088 | ||
|
a70618cdef | ||
|
faf142cf66 | ||
|
1a3cc06935 | ||
|
a712a9363b | ||
|
67e716466f | ||
|
3fae03da98 | ||
|
361391ceb8 | ||
|
bf0ca38350 | ||
|
437c28e2b8 | ||
|
c05d18ada1 | ||
|
cfb300ec06 | ||
|
236e668201 | ||
|
211d420fb2 | ||
|
c9b902c2b4 | ||
|
b8ca4665c1 | ||
|
ac8b1e19b7 | ||
|
960af87fb0 | ||
|
de7339ae97 | ||
|
595933d046 | ||
|
789426f80e | ||
|
0995af62b6 | ||
|
c2e03a40b4 | ||
|
148e64c3da | ||
|
c6289ebb2c | ||
|
5f4ece6813 | ||
|
c990ec39d6 | ||
|
1fdc600ecd | ||
|
c855050bcf | ||
|
d938e5fb6b | ||
|
28907082f1 | ||
|
f83174c40a | ||
|
ec062d008f | ||
|
a587655a5a |
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -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 }}
|
||||
|
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,3 +1,36 @@
|
||||
#### 4.0.0-beta.3: Beta Release
|
||||
|
||||
Editor
|
||||
|
||||
- Improve background-deploy notification handling (#4692) @knolleary
|
||||
- Hide workspace tab on middle mouse click (#4657) @Steve-Mcl
|
||||
- multiplayer: Add user presence indicators (#4666) @knolleary
|
||||
- Enable updating dependency node of package.json in project feature (#4676) @kazuhitoyokoi
|
||||
- Add French translations for 4.0.0-beta.2 (#4681) @GogoVega
|
||||
- Add Japanese translations for 4.0.0-beta.2 (#4674) @kazuhitoyokoi
|
||||
- Fix saving of conf-type properties in module packaged subflows (#4658) @knolleary
|
||||
- Add npm install timeout notification (#4662) @hardillb
|
||||
- Fix undo of subflow env property edits (#4667) @knolleary
|
||||
- Fix three error typos in monaco.js (#4660) @JoshuaCWebDeveloper
|
||||
- docs: Add closing paragraph tag (#4664) @ZJvandeWeg
|
||||
- Avoid login loops when autoLogin enabled but login fails (#4684) @knolleary
|
||||
|
||||
Runtime
|
||||
|
||||
- Allow blank strings to be used for env var property substitutions (#4672) @knolleary
|
||||
- Use rfdc for cloning pure JSON values (#4679) @knolleary
|
||||
- fix: remove outdated Node 11+ check (#4314) @Rotzbua
|
||||
- feat(ci): add new nodejs v22 (#4694) @Rotzbua
|
||||
- fix(node): increase required node >=18.5 (#4690) @Rotzbua
|
||||
- fix(dns): remove outdated node check (#4689) @Rotzbua
|
||||
- fix(polyfill): remove import module polyfill (#4688) @Rotzbua
|
||||
- Fix typo (#4686) @Rotzbua
|
||||
|
||||
Nodes
|
||||
|
||||
- Pass full error object in Function node and copy over cause property (#4685) @knolleary
|
||||
- Replacing vm.createScript in favour of vm.Script (#4534) @patlux
|
||||
|
||||
#### 4.0.0-beta.2: Beta Release
|
||||
|
||||
Editor
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3-1",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "https://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-api",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3-1",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,8 +16,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "4.0.0-beta.2",
|
||||
"@node-red/editor-client": "4.0.0-beta.2",
|
||||
"@node-red/util": "4.0.0-beta.3-1",
|
||||
"@node-red/editor-client": "4.0.0-beta.3-1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.20.2",
|
||||
"clone": "2.1.2",
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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": "了解"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/editor-client",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3-1",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -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) {
|
||||
|
@@ -1,114 +1,92 @@
|
||||
RED.multiplayer = (function () {
|
||||
|
||||
// sessionId - used to identify sessions across websocket reconnects
|
||||
let sessionId
|
||||
// activeSessionId - used to identify sessions across websocket reconnects
|
||||
let activeSessionId
|
||||
|
||||
let headerWidget
|
||||
// Map of session id to { session:'', user:{}, location:{}}
|
||||
let connections = {}
|
||||
// Map of username to { user:{}, connections:[] }
|
||||
let sessions = {}
|
||||
// Map of username to { user:{}, sessions:[] }
|
||||
let users = {}
|
||||
|
||||
function addUserConnection (connection) {
|
||||
if (connections[connection.session]) {
|
||||
function addUserSession (session) {
|
||||
if (sessions[session.session]) {
|
||||
// This is an existing connection that has been authenticated
|
||||
const existingConnection = connections[connection.session]
|
||||
if (existingConnection.user.username !== connection.user.username) {
|
||||
removeUserButton(users[existingConnection.user.username])
|
||||
const existingSession = sessions[session.session]
|
||||
if (existingSession.user.username !== session.user.username) {
|
||||
removeUserHeaderButton(users[existingSession.user.username])
|
||||
}
|
||||
}
|
||||
connections[connection.session] = connection
|
||||
const user = users[connection.user.username] = users[connection.user.username] || {
|
||||
user: connection.user,
|
||||
connections: []
|
||||
sessions[session.session] = session
|
||||
const user = users[session.user.username] = users[session.user.username] || {
|
||||
user: session.user,
|
||||
sessions: []
|
||||
}
|
||||
connection.location = connection.location || {}
|
||||
user.connections.push(connection)
|
||||
if (session.user.profileColor === undefined) {
|
||||
session.user.profileColor = (1 + Math.floor(Math.random() * 5))
|
||||
}
|
||||
session.location = session.location || {}
|
||||
user.sessions.push(session)
|
||||
|
||||
if (connection.user.username === RED.settings.user?.username ||
|
||||
connection.session === sessionId
|
||||
) {
|
||||
// This is the current user - do not add a extra button for them
|
||||
if (session.session === activeSessionId) {
|
||||
// This is the current user session - do not add a extra button for them
|
||||
} else {
|
||||
if (user.connections.length === 1) {
|
||||
if (user.sessions.length === 1) {
|
||||
if (user.button) {
|
||||
clearTimeout(user.inactiveTimeout)
|
||||
clearTimeout(user.removeTimeout)
|
||||
user.button.removeClass('inactive')
|
||||
} else {
|
||||
addUserButton(user)
|
||||
addUserHeaderButton(user)
|
||||
}
|
||||
}
|
||||
sessions[session.session].location = session.location
|
||||
updateUserLocation(session.session)
|
||||
}
|
||||
}
|
||||
|
||||
function removeUserConnection (session, isDisconnected) {
|
||||
const connection = connections[session]
|
||||
delete connections[session]
|
||||
const user = users[connection.user.username]
|
||||
const i = user.connections.indexOf(connection)
|
||||
user.connections.splice(i, 1)
|
||||
function removeUserSession (sessionId, isDisconnected) {
|
||||
removeUserLocation(sessionId)
|
||||
const session = sessions[sessionId]
|
||||
delete sessions[sessionId]
|
||||
const user = users[session.user.username]
|
||||
const i = user.sessions.indexOf(session)
|
||||
user.sessions.splice(i, 1)
|
||||
if (isDisconnected) {
|
||||
removeUserButton(user)
|
||||
removeUserHeaderButton(user)
|
||||
} else {
|
||||
if (user.connections.length === 0) {
|
||||
if (user.sessions.length === 0) {
|
||||
// Give the user 5s to reconnect before marking inactive
|
||||
user.inactiveTimeout = setTimeout(() => {
|
||||
user.button.addClass('inactive')
|
||||
// Give the user further 20 seconds to reconnect before removing them
|
||||
// from the user toolbar entirely
|
||||
user.removeTimeout = setTimeout(() => {
|
||||
removeUserButton(user)
|
||||
removeUserHeaderButton(user)
|
||||
}, 20000)
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addUserButton (user) {
|
||||
user.button = $('<li class="red-ui-multiplayer-user"><button type="button" class="red-ui-multiplayer-user-icon" href="#"></button></li>')
|
||||
function addUserHeaderButton (user) {
|
||||
user.button = $('<li class="red-ui-multiplayer-user"><button type="button" class="red-ui-multiplayer-user-icon"></button></li>')
|
||||
.attr('data-username', user.user.username)
|
||||
.prependTo("#red-ui-multiplayer-user-list");
|
||||
var button = user.button.find("button")
|
||||
RED.popover.tooltip(button, user.user.username)
|
||||
button.on('click', function () {
|
||||
RED.popover.create({
|
||||
target:button,
|
||||
trigger: 'modal',
|
||||
interactive: true,
|
||||
width: "250px",
|
||||
direction: 'bottom',
|
||||
content: () => {
|
||||
const content = $('<div>')
|
||||
$('<div style="text-align: center">').text(user.user.username).appendTo(content)
|
||||
|
||||
const location = user.connections[0].location
|
||||
if (location.workspace) {
|
||||
const ws = RED.nodes.workspace(location.workspace) || RED.nodes.subflow(location.workspace)
|
||||
if (ws) {
|
||||
$('<div>').text(`${ws.type}: ${ws.label||ws.name||ws.id}`).appendTo(content)
|
||||
} else {
|
||||
$('<div>').text(`tab: unknown`).appendTo(content)
|
||||
}
|
||||
}
|
||||
if (location.node) {
|
||||
const node = RED.nodes.node(location.node)
|
||||
if (node) {
|
||||
$('<div>').text(`node: ${node.id}`).appendTo(content)
|
||||
} else {
|
||||
$('<div>').text(`node: unknown`).appendTo(content)
|
||||
}
|
||||
}
|
||||
return content
|
||||
},
|
||||
}).open()
|
||||
const location = user.sessions[0].location
|
||||
revealUser(location)
|
||||
})
|
||||
if (!user.user.image) {
|
||||
$('<i class="fa fa-user"></i>').appendTo(button);
|
||||
} else {
|
||||
$('<span class="user-profile"></span>').css({
|
||||
backgroundImage: "url("+user.user.image+")",
|
||||
}).appendTo(button);
|
||||
}
|
||||
|
||||
const userProfile = RED.user.generateUserIcon(user.user)
|
||||
userProfile.appendTo(button)
|
||||
}
|
||||
|
||||
function removeUserHeaderButton (user) {
|
||||
user.button.remove()
|
||||
delete user.button
|
||||
}
|
||||
|
||||
function getLocation () {
|
||||
@@ -124,7 +102,7 @@ RED.multiplayer = (function () {
|
||||
}
|
||||
return location
|
||||
}
|
||||
function updateLocation () {
|
||||
function publishLocation () {
|
||||
const location = getLocation()
|
||||
if (location.workspace !== 0) {
|
||||
log('send', 'multiplayer/location', location)
|
||||
@@ -132,31 +110,314 @@ RED.multiplayer = (function () {
|
||||
}
|
||||
}
|
||||
|
||||
function removeUserButton (user) {
|
||||
user.button.remove()
|
||||
delete user.button
|
||||
function revealUser(location, skipWorkspace) {
|
||||
if (location.node) {
|
||||
// Need to check if this is a known node, so we can fall back to revealing
|
||||
// the workspace instead
|
||||
const node = RED.nodes.node(location.node)
|
||||
if (node) {
|
||||
RED.view.reveal(location.node)
|
||||
} else if (!skipWorkspace && location.workspace) {
|
||||
RED.view.reveal(location.workspace)
|
||||
}
|
||||
} else if (!skipWorkspace && location.workspace) {
|
||||
RED.view.reveal(location.workspace)
|
||||
}
|
||||
}
|
||||
|
||||
function updateUserLocation (data) {
|
||||
connections[data.session].location = data
|
||||
delete data.session
|
||||
const workspaceTrays = {}
|
||||
function getWorkspaceTray(workspaceId) {
|
||||
// console.log('get tray for',workspaceId)
|
||||
if (!workspaceTrays[workspaceId]) {
|
||||
const tray = $('<div class="red-ui-multiplayer-users-tray"></div>')
|
||||
const users = []
|
||||
const userIcons = {}
|
||||
|
||||
const userCountIcon = $(`<div class="red-ui-multiplayer-user-location"><span class="red-ui-user-profile red-ui-multiplayer-user-count"><span></span></span></div>`)
|
||||
const userCountSpan = userCountIcon.find('span span')
|
||||
userCountIcon.hide()
|
||||
userCountSpan.text('')
|
||||
userCountIcon.appendTo(tray)
|
||||
const userCountTooltip = RED.popover.tooltip(userCountIcon, function () {
|
||||
const content = $('<div>')
|
||||
users.forEach(sessionId => {
|
||||
$('<div>').append($('<a href="#">').text(sessions[sessionId].user.username).on('click', function (evt) {
|
||||
evt.preventDefault()
|
||||
revealUser(sessions[sessionId].location, true)
|
||||
userCountTooltip.close()
|
||||
})).appendTo(content)
|
||||
})
|
||||
return content
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
|
||||
const updateUserCount = function () {
|
||||
const maxShown = 2
|
||||
const children = tray.children()
|
||||
children.each(function (index, element) {
|
||||
const i = users.length - index
|
||||
if (i > maxShown) {
|
||||
$(this).hide()
|
||||
} else if (i >= 0) {
|
||||
$(this).show()
|
||||
}
|
||||
})
|
||||
if (users.length < maxShown + 1) {
|
||||
userCountIcon.hide()
|
||||
} else {
|
||||
userCountSpan.text('+'+(users.length - maxShown))
|
||||
userCountIcon.show()
|
||||
}
|
||||
}
|
||||
workspaceTrays[workspaceId] = {
|
||||
attached: false,
|
||||
tray,
|
||||
users,
|
||||
userIcons,
|
||||
addUser: function (sessionId) {
|
||||
if (users.indexOf(sessionId) === -1) {
|
||||
// console.log(`addUser ws:${workspaceId} session:${sessionId}`)
|
||||
users.push(sessionId)
|
||||
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
|
||||
const userLocationIcon = $(`<div class="red-ui-multiplayer-user-location" id="${userLocationId}"></div>`)
|
||||
RED.user.generateUserIcon(sessions[sessionId].user).appendTo(userLocationIcon)
|
||||
userLocationIcon.prependTo(tray)
|
||||
RED.popover.tooltip(userLocationIcon, sessions[sessionId].user.username)
|
||||
userIcons[sessionId] = userLocationIcon
|
||||
updateUserCount()
|
||||
}
|
||||
},
|
||||
removeUser: function (sessionId) {
|
||||
// console.log(`removeUser ws:${workspaceId} session:${sessionId}`)
|
||||
const userLocationId = `red-ui-multiplayer-user-location-${sessionId}`
|
||||
const index = users.indexOf(sessionId)
|
||||
if (index > -1) {
|
||||
users.splice(index, 1)
|
||||
userIcons[sessionId].remove()
|
||||
delete userIcons[sessionId]
|
||||
}
|
||||
updateUserCount()
|
||||
},
|
||||
updateUserCount
|
||||
}
|
||||
}
|
||||
const trayDef = workspaceTrays[workspaceId]
|
||||
if (!trayDef.attached) {
|
||||
const workspaceTab = $(`#red-ui-tab-${workspaceId}`)
|
||||
if (workspaceTab.length > 0) {
|
||||
trayDef.attached = true
|
||||
trayDef.tray.appendTo(workspaceTab)
|
||||
trayDef.users.forEach(sessionId => {
|
||||
trayDef.userIcons[sessionId].on('click', function (evt) {
|
||||
revealUser(sessions[sessionId].location, true)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
return workspaceTrays[workspaceId]
|
||||
}
|
||||
function attachWorkspaceTrays () {
|
||||
let viewTouched = false
|
||||
for (let sessionId of Object.keys(sessions)) {
|
||||
const location = sessions[sessionId].location
|
||||
if (location) {
|
||||
if (location.workspace) {
|
||||
getWorkspaceTray(location.workspace).updateUserCount()
|
||||
}
|
||||
if (location.node) {
|
||||
addUserToNode(sessionId, location.node)
|
||||
viewTouched = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (viewTouched) {
|
||||
RED.view.redraw()
|
||||
}
|
||||
}
|
||||
|
||||
function addUserToNode(sessionId, nodeId) {
|
||||
const node = RED.nodes.node(nodeId)
|
||||
if (node) {
|
||||
if (!node._multiplayer) {
|
||||
node._multiplayer = {
|
||||
users: [sessionId]
|
||||
}
|
||||
node._multiplayer_refresh = true
|
||||
} else {
|
||||
if (node._multiplayer.users.indexOf(sessionId) === -1) {
|
||||
node._multiplayer.users.push(sessionId)
|
||||
node._multiplayer_refresh = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function removeUserFromNode(sessionId, nodeId) {
|
||||
const node = RED.nodes.node(nodeId)
|
||||
if (node && node._multiplayer) {
|
||||
const i = node._multiplayer.users.indexOf(sessionId)
|
||||
if (i > -1) {
|
||||
node._multiplayer.users.splice(i, 1)
|
||||
}
|
||||
if (node._multiplayer.users.length === 0) {
|
||||
delete node._multiplayer
|
||||
} else {
|
||||
node._multiplayer_refresh = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function removeUserLocation (sessionId) {
|
||||
updateUserLocation(sessionId, {})
|
||||
}
|
||||
function updateUserLocation (sessionId, location) {
|
||||
let viewTouched = false
|
||||
const oldLocation = sessions[sessionId].location
|
||||
if (location) {
|
||||
if (oldLocation.workspace !== location.workspace) {
|
||||
// console.log('removing', sessionId, oldLocation.workspace)
|
||||
workspaceTrays[oldLocation.workspace]?.removeUser(sessionId)
|
||||
}
|
||||
if (oldLocation.node !== location.node) {
|
||||
removeUserFromNode(sessionId, oldLocation.node)
|
||||
viewTouched = true
|
||||
}
|
||||
sessions[sessionId].location = location
|
||||
} else {
|
||||
location = sessions[sessionId].location
|
||||
}
|
||||
// console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
|
||||
if (location.workspace) {
|
||||
getWorkspaceTray(location.workspace).addUser(sessionId)
|
||||
}
|
||||
if (location.node) {
|
||||
addUserToNode(sessionId, location.node)
|
||||
viewTouched = true
|
||||
}
|
||||
if (viewTouched) {
|
||||
RED.view.redraw()
|
||||
}
|
||||
}
|
||||
|
||||
// function refreshUserLocations () {
|
||||
// for (const session of Object.keys(sessions)) {
|
||||
// if (session !== activeSessionId) {
|
||||
// updateUserLocation(session)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return {
|
||||
init: function () {
|
||||
|
||||
|
||||
sessionId = RED.settings.getLocal('multiplayer:sessionId')
|
||||
if (!sessionId) {
|
||||
sessionId = RED.nodes.id()
|
||||
RED.settings.setLocal('multiplayer:sessionId', sessionId)
|
||||
function createAnnotationUser(user) {
|
||||
|
||||
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||
const badge = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
||||
const radius = 20
|
||||
badge.setAttribute("cx",radius/2);
|
||||
badge.setAttribute("cy",radius/2);
|
||||
badge.setAttribute("r",radius/2);
|
||||
badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
|
||||
group.appendChild(badge)
|
||||
if (user && user.profileColor !== undefined) {
|
||||
badge.setAttribute("class", "red-ui-multiplayer-annotation-background red-ui-user-profile-color-" + user.profileColor)
|
||||
}
|
||||
if (user && user.image) {
|
||||
const image = document.createElementNS("http://www.w3.org/2000/svg","image");
|
||||
image.setAttribute("width", radius)
|
||||
image.setAttribute("height", radius)
|
||||
image.setAttribute("href", user.image)
|
||||
image.setAttribute("clip-path", "circle("+Math.floor(radius/2)+")")
|
||||
group.appendChild(image)
|
||||
} else if (user && user.anonymous) {
|
||||
const anonIconHead = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
||||
anonIconHead.setAttribute("cx", radius/2)
|
||||
anonIconHead.setAttribute("cy", radius/2 - 2)
|
||||
anonIconHead.setAttribute("r", 2.4)
|
||||
anonIconHead.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||
group.appendChild(anonIconHead)
|
||||
const anonIconBody = document.createElementNS("http://www.w3.org/2000/svg","path");
|
||||
anonIconBody.setAttribute("class","red-ui-multiplayer-annotation-anon-label");
|
||||
// anonIconBody.setAttribute("d",`M ${radius/2 - 4} ${radius/2 + 1} h 8 v4 h -8 z`);
|
||||
anonIconBody.setAttribute("d",`M ${radius/2} ${radius/2 + 5} h -2.5 c -2 1 -2 -5 0.5 -4.5 c 2 1 2 1 4 0 c 2.5 -0.5 2.5 5.5 0 4.5 z`);
|
||||
group.appendChild(anonIconBody)
|
||||
} else {
|
||||
const labelText = user.username ? user.username.substring(0,2) : user
|
||||
const label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
||||
if (user.username) {
|
||||
label.setAttribute("class","red-ui-multiplayer-annotation-label");
|
||||
label.textContent = user.username.substring(0,2)
|
||||
} else {
|
||||
label.setAttribute("class","red-ui-multiplayer-annotation-label red-ui-multiplayer-user-count")
|
||||
label.textContent = user
|
||||
}
|
||||
label.setAttribute("text-anchor", "middle")
|
||||
label.setAttribute("x",radius/2);
|
||||
label.setAttribute("y",radius/2 + 3);
|
||||
group.appendChild(label)
|
||||
}
|
||||
const border = document.createElementNS("http://www.w3.org/2000/svg","circle");
|
||||
border.setAttribute("cx",radius/2);
|
||||
border.setAttribute("cy",radius/2);
|
||||
border.setAttribute("r",radius/2);
|
||||
border.setAttribute("class", "red-ui-multiplayer-annotation-border")
|
||||
group.appendChild(border)
|
||||
|
||||
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
RED.view.annotations.register("red-ui-multiplayer",{
|
||||
type: 'badge',
|
||||
align: 'left',
|
||||
class: "red-ui-multiplayer-annotation",
|
||||
show: "_multiplayer",
|
||||
refresh: "_multiplayer_refresh",
|
||||
element: function(node) {
|
||||
const containerGroup = document.createElementNS("http://www.w3.org/2000/svg","g");
|
||||
containerGroup.setAttribute("transform","translate(0,-4)")
|
||||
if (node._multiplayer) {
|
||||
let y = 0
|
||||
for (let i = Math.min(1, node._multiplayer.users.length - 1); i >= 0; i--) {
|
||||
const user = sessions[node._multiplayer.users[i]].user
|
||||
const group = createAnnotationUser(user)
|
||||
group.setAttribute("transform","translate("+y+",0)")
|
||||
y += 15
|
||||
containerGroup.appendChild(group)
|
||||
}
|
||||
if (node._multiplayer.users.length > 2) {
|
||||
const group = createAnnotationUser('+'+(node._multiplayer.users.length - 2))
|
||||
group.setAttribute("transform","translate("+y+",0)")
|
||||
y += 12
|
||||
containerGroup.appendChild(group)
|
||||
}
|
||||
|
||||
}
|
||||
return containerGroup;
|
||||
},
|
||||
tooltip: node => { return node._multiplayer.users.map(u => sessions[u].user.username).join('\n') }
|
||||
});
|
||||
|
||||
|
||||
// activeSessionId = RED.settings.getLocal('multiplayer:sessionId')
|
||||
// if (!activeSessionId) {
|
||||
activeSessionId = RED.nodes.id()
|
||||
// RED.settings.setLocal('multiplayer:sessionId', activeSessionId)
|
||||
// log('Session ID (new)', activeSessionId)
|
||||
// } else {
|
||||
log('Session ID', activeSessionId)
|
||||
// }
|
||||
|
||||
headerWidget = $('<li><ul id="red-ui-multiplayer-user-list"></ul></li>').prependTo('.red-ui-header-toolbar')
|
||||
|
||||
RED.comms.on('connect', () => {
|
||||
const location = getLocation()
|
||||
const connectInfo = {
|
||||
session: sessionId
|
||||
session: activeSessionId
|
||||
}
|
||||
if (location.workspace !== 0) {
|
||||
connectInfo.location = location
|
||||
@@ -168,40 +429,52 @@ RED.multiplayer = (function () {
|
||||
if (topic === 'multiplayer/init') {
|
||||
// We have just reconnected, runtime has sent state to
|
||||
// initialise the world
|
||||
connections = {}
|
||||
sessions = {}
|
||||
users = {}
|
||||
$('#red-ui-multiplayer-user-list').empty()
|
||||
|
||||
msg.forEach(connection => {
|
||||
addUserConnection(connection)
|
||||
msg.sessions.forEach(session => {
|
||||
addUserSession(session)
|
||||
})
|
||||
} else if (topic === 'multiplayer/connection-added') {
|
||||
addUserConnection(msg)
|
||||
addUserSession(msg)
|
||||
} else if (topic === 'multiplayer/connection-removed') {
|
||||
removeUserConnection(msg.session, msg.disconnected)
|
||||
removeUserSession(msg.session, msg.disconnected)
|
||||
} else if (topic === 'multiplayer/location') {
|
||||
updateUserLocation(msg)
|
||||
const session = msg.session
|
||||
delete msg.session
|
||||
updateUserLocation(session, msg)
|
||||
}
|
||||
})
|
||||
|
||||
RED.events.on('workspace:change', (event) => {
|
||||
updateLocation()
|
||||
getWorkspaceTray(event.workspace)
|
||||
publishLocation()
|
||||
})
|
||||
RED.events.on('editor:open', () => {
|
||||
updateLocation()
|
||||
publishLocation()
|
||||
})
|
||||
RED.events.on('editor:close', () => {
|
||||
updateLocation()
|
||||
publishLocation()
|
||||
})
|
||||
RED.events.on('editor:change', () => {
|
||||
updateLocation()
|
||||
publishLocation()
|
||||
})
|
||||
RED.events.on('login', () => {
|
||||
updateLocation()
|
||||
publishLocation()
|
||||
})
|
||||
RED.events.on('flows:loaded', () => {
|
||||
attachWorkspaceTrays()
|
||||
})
|
||||
RED.events.on('workspace:close', (event) => {
|
||||
// A subflow tab has been closed. Need to mark its tray as detached
|
||||
if (workspaceTrays[event.workspace]) {
|
||||
workspaceTrays[event.workspace].attached = false
|
||||
}
|
||||
})
|
||||
RED.events.on('logout', () => {
|
||||
const disconnectInfo = {
|
||||
session: sessionId
|
||||
session: activeSessionId
|
||||
}
|
||||
RED.comms.send('multiplayer/disconnect', disconnectInfo)
|
||||
RED.settings.removeLocal('multiplayer:sessionId')
|
||||
|
@@ -298,6 +298,7 @@ var RED = (function() {
|
||||
RED.workspaces.show(workspaces[0]);
|
||||
}
|
||||
}
|
||||
RED.events.emit('flows:loaded')
|
||||
} catch(err) {
|
||||
console.warn(err);
|
||||
RED.notify(
|
||||
|
@@ -445,9 +445,12 @@ RED.popover = (function() {
|
||||
|
||||
return {
|
||||
create: createPopover,
|
||||
tooltip: function(target,content, action) {
|
||||
tooltip: function(target,content, action, interactive) {
|
||||
var label = function() {
|
||||
var label = content;
|
||||
if (typeof content === 'function') {
|
||||
label = content()
|
||||
}
|
||||
if (action) {
|
||||
var shortcut = RED.keyboard.getShortcut(action);
|
||||
if (shortcut && shortcut.key) {
|
||||
@@ -463,6 +466,7 @@ RED.popover = (function() {
|
||||
size: "small",
|
||||
direction: "bottom",
|
||||
content: label,
|
||||
interactive,
|
||||
delay: { show: 750, hide: 50 }
|
||||
});
|
||||
popover.setContent = function(newContent) {
|
||||
|
@@ -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(); })
|
||||
|
||||
|
@@ -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);
|
||||
@@ -61,7 +63,7 @@ RED.deploy = (function() {
|
||||
'<img src="red/images/spin.svg"/>'+
|
||||
'</span>'+
|
||||
'</a>'+
|
||||
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i></a>'+
|
||||
'<a id="red-ui-header-button-deploy-options" class="red-ui-deploy-button" href="#"><i class="fa fa-caret-down"></i><i class="fa fa-lock"></i></a>'+
|
||||
'</span></li>').prependTo(".red-ui-header-toolbar");
|
||||
const mainMenuItems = [
|
||||
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
|
||||
@@ -112,53 +114,80 @@ RED.deploy = (function() {
|
||||
RED.actions.add("core:set-deploy-type-to-modified-nodes",function() { RED.menu.setSelected("deploymenu-item-node",true); });
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener('beforeunload', function (event) {
|
||||
if (RED.nodes.dirty()) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation()
|
||||
event.returnValue = RED._("deploy.confirm.undeployedChanges");
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
RED.events.on('workspace:dirty',function(state) {
|
||||
if (RED.settings.user?.permissions === 'read') {
|
||||
return
|
||||
}
|
||||
if (state.dirty) {
|
||||
window.onbeforeunload = function() {
|
||||
return RED._("deploy.confirm.undeployedChanges");
|
||||
}
|
||||
// window.onbeforeunload = function() {
|
||||
// return
|
||||
// }
|
||||
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||
} else {
|
||||
window.onbeforeunload = null;
|
||||
// window.onbeforeunload = null;
|
||||
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
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)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
updateLockedState()
|
||||
RED.events.on('login', updateLockedState)
|
||||
}
|
||||
|
||||
function updateLockedState() {
|
||||
if (RED.settings.user?.permissions === 'read') {
|
||||
$(".red-ui-deploy-button-group").addClass("readOnly");
|
||||
$("#red-ui-header-button-deploy").addClass("disabled");
|
||||
} else {
|
||||
$(".red-ui-deploy-button-group").removeClass("readOnly");
|
||||
if (RED.nodes.dirty()) {
|
||||
$("#red-ui-header-button-deploy").removeClass("disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeInfo(node) {
|
||||
@@ -213,7 +242,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();
|
||||
}
|
||||
}
|
||||
@@ -226,6 +259,7 @@ RED.deploy = (function() {
|
||||
if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
|
||||
RED.diff.mergeDiff(currentDiff);
|
||||
conflictNotification.close();
|
||||
activeBackgroundDeployNotification.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,6 +272,7 @@ RED.deploy = (function() {
|
||||
click: function() {
|
||||
save(true,activeDeploy);
|
||||
conflictNotification.close();
|
||||
activeBackgroundDeployNotification.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -248,21 +283,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) {
|
||||
@@ -612,7 +643,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) {
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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',
|
||||
|
@@ -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,
|
||||
|
@@ -9,14 +9,27 @@ RED.view.annotations = (function() {
|
||||
addAnnotation(evt.node.__pendingAnnotation__,evt);
|
||||
delete evt.node.__pendingAnnotation__;
|
||||
}
|
||||
var badgeDX = 0;
|
||||
var controlDX = 0;
|
||||
for (var i=0,l=evt.el.__annotations__.length;i<l;i++) {
|
||||
var annotation = evt.el.__annotations__[i];
|
||||
let badgeRDX = 0;
|
||||
let badgeLDX = 0;
|
||||
|
||||
for (let i=0,l=evt.el.__annotations__.length;i<l;i++) {
|
||||
const annotation = evt.el.__annotations__[i];
|
||||
if (annotations.hasOwnProperty(annotation.id)) {
|
||||
var opts = annotations[annotation.id];
|
||||
var showAnnotation = true;
|
||||
var isBadge = opts.type === 'badge';
|
||||
const opts = annotations[annotation.id];
|
||||
let showAnnotation = true;
|
||||
const isBadge = opts.type === 'badge';
|
||||
if (opts.refresh !== undefined) {
|
||||
let refreshAnnotation = false
|
||||
if (typeof opts.refresh === "string") {
|
||||
refreshAnnotation = !!evt.node[opts.refresh]
|
||||
delete evt.node[opts.refresh]
|
||||
} else if (typeof opts.refresh === "function") {
|
||||
refreshAnnotation = opts.refresh(evnt.node)
|
||||
}
|
||||
if (refreshAnnotation) {
|
||||
refreshAnnotationElement(annotation.id, annotation.node, annotation.element)
|
||||
}
|
||||
}
|
||||
if (opts.show !== undefined) {
|
||||
if (typeof opts.show === "string") {
|
||||
showAnnotation = !!evt.node[opts.show]
|
||||
@@ -29,17 +42,24 @@ RED.view.annotations = (function() {
|
||||
}
|
||||
if (isBadge) {
|
||||
if (showAnnotation) {
|
||||
var rect = annotation.element.getBoundingClientRect();
|
||||
badgeDX += rect.width;
|
||||
annotation.element.setAttribute("transform", "translate("+(evt.node.w-3-badgeDX)+", -8)");
|
||||
badgeDX += 4;
|
||||
}
|
||||
} else {
|
||||
if (showAnnotation) {
|
||||
var rect = annotation.element.getBoundingClientRect();
|
||||
annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
|
||||
controlDX += rect.width + 4;
|
||||
const rect = annotation.element.getBoundingClientRect();
|
||||
let annotationX
|
||||
if (!opts.align || opts.align === 'right') {
|
||||
annotationX = evt.node.w - 3 - badgeRDX - rect.width
|
||||
badgeRDX += rect.width + 4;
|
||||
|
||||
} else if (opts.align === 'left') {
|
||||
annotationX = 3 + badgeLDX
|
||||
badgeLDX += rect.width + 4;
|
||||
}
|
||||
annotation.element.setAttribute("transform", "translate("+annotationX+", -8)");
|
||||
}
|
||||
// } else {
|
||||
// if (showAnnotation) {
|
||||
// var rect = annotation.element.getBoundingClientRect();
|
||||
// annotation.element.setAttribute("transform", "translate("+(3+controlDX)+", -12)");
|
||||
// controlDX += rect.width + 4;
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
annotation.element.parentNode.removeChild(annotation.element);
|
||||
@@ -95,15 +115,25 @@ RED.view.annotations = (function() {
|
||||
annotationGroup.setAttribute("class",opts.class || "");
|
||||
evt.el.__annotations__.push({
|
||||
id:id,
|
||||
node: evt.node,
|
||||
element: annotationGroup
|
||||
});
|
||||
var annotation = opts.element(evt.node);
|
||||
refreshAnnotationElement(id, evt.node, annotationGroup)
|
||||
evt.el.appendChild(annotationGroup);
|
||||
}
|
||||
|
||||
function refreshAnnotationElement(id, node, annotationGroup) {
|
||||
const opts = annotations[id];
|
||||
const annotation = opts.element(node);
|
||||
if (opts.tooltip) {
|
||||
annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation,evt.node,opts.tooltip));
|
||||
annotation.addEventListener("mouseenter", getAnnotationMouseEnter(annotation, node, opts.tooltip));
|
||||
annotation.addEventListener("mouseleave", annotationMouseLeave);
|
||||
}
|
||||
if (annotationGroup.hasChildNodes()) {
|
||||
annotationGroup.removeChild(annotationGroup.firstChild)
|
||||
}
|
||||
annotationGroup.appendChild(annotation);
|
||||
evt.el.appendChild(annotationGroup);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -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") {
|
||||
@@ -401,6 +407,7 @@ RED.workspaces = (function() {
|
||||
if (tab.type === "tab") {
|
||||
workspaceTabCount--;
|
||||
} else {
|
||||
RED.events.emit("workspace:close",{workspace: tab.id})
|
||||
hideStack.push(tab.id);
|
||||
}
|
||||
RED.menu.setDisabled("menu-item-workspace-delete",activeWorkspace === 0 || workspaceTabCount <= 1);
|
||||
|
@@ -212,6 +212,8 @@ RED.user = (function() {
|
||||
|
||||
function updateUserMenu() {
|
||||
$("#red-ui-header-button-user-submenu li").remove();
|
||||
const userMenu = $("#red-ui-header-button-user")
|
||||
userMenu.empty()
|
||||
if (RED.settings.user.anonymous) {
|
||||
RED.menu.addItem("red-ui-header-button-user",{
|
||||
id:"usermenu-item-login",
|
||||
@@ -226,7 +228,6 @@ RED.user = (function() {
|
||||
});
|
||||
}
|
||||
});
|
||||
$('<i class="fa fa-user"></i>').appendTo("#red-ui-header-button-user");
|
||||
} else {
|
||||
RED.menu.addItem("red-ui-header-button-user",{
|
||||
id:"usermenu-item-username",
|
||||
@@ -239,17 +240,9 @@ RED.user = (function() {
|
||||
RED.user.logout();
|
||||
}
|
||||
});
|
||||
const userMenu = $("#red-ui-header-button-user")
|
||||
userMenu.empty()
|
||||
if (RED.settings.user.image) {
|
||||
$('<span class="user-profile"></span>').css({
|
||||
backgroundImage: "url("+RED.settings.user.image+")",
|
||||
}).appendTo(userMenu);
|
||||
} else {
|
||||
$('<i class="fa fa-user"></i>').appendTo(userMenu);
|
||||
}
|
||||
}
|
||||
|
||||
const userIcon = generateUserIcon(RED.settings.user)
|
||||
userIcon.appendTo(userMenu);
|
||||
}
|
||||
|
||||
function init() {
|
||||
@@ -320,12 +313,30 @@ RED.user = (function() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function generateUserIcon(user) {
|
||||
const userIcon = $('<span class="red-ui-user-profile"></span>')
|
||||
if (user.image) {
|
||||
userIcon.addClass('has_profile_image')
|
||||
userIcon.css({
|
||||
backgroundImage: "url("+user.image+")",
|
||||
})
|
||||
} else if (user.anonymous) {
|
||||
$('<i class="fa fa-user"></i>').appendTo(userIcon);
|
||||
} else {
|
||||
$('<span>').text(user.username.substring(0,2)).appendTo(userIcon);
|
||||
}
|
||||
if (user.profileColor !== undefined) {
|
||||
userIcon.addClass('red-ui-user-profile-color-' + user.profileColor)
|
||||
}
|
||||
return userIcon
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
login: login,
|
||||
logout: logout,
|
||||
hasPermission: hasPermission
|
||||
hasPermission: hasPermission,
|
||||
generateUserIcon
|
||||
}
|
||||
|
||||
})();
|
||||
|
@@ -314,6 +314,16 @@ $spinner-color: #999;
|
||||
|
||||
$tab-icon-color: #dedede;
|
||||
|
||||
// Anonymous User Colors
|
||||
|
||||
$user-profile-colors: (
|
||||
1: #822e81,
|
||||
2: #955e42,
|
||||
3: #9c914f,
|
||||
4: #748e54,
|
||||
5: #06bcc1
|
||||
);
|
||||
|
||||
// Deprecated
|
||||
$text-color-green: $text-color-success;
|
||||
$info-text-code-color: $text-color-code;
|
||||
|
@@ -186,6 +186,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
.red-ui-deploy-button-group.readOnly {
|
||||
.fa-caret-down { display: none; }
|
||||
.fa-lock { display: inline-block; }
|
||||
}
|
||||
.red-ui-deploy-button-group:not(.readOnly) {
|
||||
.fa-caret-down { display: inline-block; }
|
||||
.fa-lock { display: none; }
|
||||
}
|
||||
.red-ui-deploy-button-group.readOnly {
|
||||
a {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
li.open .button {
|
||||
background: var(--red-ui-header-button-background-active);
|
||||
border-color: var(--red-ui-header-button-background-active);
|
||||
@@ -274,18 +288,44 @@
|
||||
#usermenu-item-username > .red-ui-menu-label {
|
||||
color: var(--red-ui-header-menu-heading-color);
|
||||
}
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
vertical-align: middle;
|
||||
|
||||
.red-ui-user-profile {
|
||||
background-color: var(--red-ui-header-background);
|
||||
border: 2px solid var(--red-ui-header-menu-color);
|
||||
border-radius: 30px;
|
||||
overflow: hidden;
|
||||
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 20px;
|
||||
|
||||
&.red-ui-user-profile-color-1 {
|
||||
background-color: var(--red-ui-user-profile-colors-1);
|
||||
}
|
||||
&.red-ui-user-profile-color-2 {
|
||||
background-color: var(--red-ui-user-profile-colors-2);
|
||||
}
|
||||
&.red-ui-user-profile-color-3 {
|
||||
background-color: var(--red-ui-user-profile-colors-3);
|
||||
}
|
||||
&.red-ui-user-profile-color-4 {
|
||||
background-color: var(--red-ui-user-profile-colors-4);
|
||||
}
|
||||
&.red-ui-user-profile-color-5 {
|
||||
background-color: var(--red-ui-user-profile-colors-5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 450px) {
|
||||
span.red-ui-header-logo > span {
|
||||
display: none;
|
||||
|
@@ -5,23 +5,18 @@
|
||||
li {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 30px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.red-ui-multiplayer-user-icon {
|
||||
background: var(--red-ui-header-background);
|
||||
border: 2px solid var(--red-ui-header-menu-color);
|
||||
border-radius: 30px;
|
||||
background: none;
|
||||
border: none;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
color: var(--red-ui-header-menu-color);
|
||||
@@ -36,13 +31,86 @@
|
||||
.red-ui-multiplayer-user.inactive & {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.user-profile {
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
.red-ui-user-profile {
|
||||
width: 20px;
|
||||
border-radius: 20px;
|
||||
height: 20px;
|
||||
font-size: 12px
|
||||
}
|
||||
}
|
||||
.red-ui-multiplayer-users-tray {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 20px;
|
||||
line-height: normal;
|
||||
cursor: pointer;
|
||||
// &:hover {
|
||||
// .red-ui-multiplayer-user-location {
|
||||
// margin-left: 1px;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
$multiplayer-user-icon-background: var(--red-ui-primary-background);
|
||||
$multiplayer-user-icon-border: var(--red-ui-view-background);
|
||||
$multiplayer-user-icon-text-color: var(--red-ui-header-menu-color);
|
||||
$multiplayer-user-icon-count-text-color: var(--red-ui-primary-color);
|
||||
$multiplayer-user-icon-shadow: 0px 0px 4px var(--red-ui-shadow);
|
||||
.red-ui-multiplayer-user-location {
|
||||
display: inline-block;
|
||||
margin-left: -6px;
|
||||
transition: margin-left 0.2s;
|
||||
.red-ui-user-profile {
|
||||
border: 1px solid $multiplayer-user-icon-border;
|
||||
color: $multiplayer-user-icon-text-color;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 18px;
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
box-shadow: $multiplayer-user-icon-shadow;
|
||||
&.red-ui-multiplayer-user-count {
|
||||
color: $multiplayer-user-icon-count-text-color;
|
||||
background-color: $multiplayer-user-icon-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.red-ui-multiplayer-annotation {
|
||||
.red-ui-multiplayer-annotation-background {
|
||||
filter: drop-shadow($multiplayer-user-icon-shadow);
|
||||
fill: $multiplayer-user-icon-background;
|
||||
&.red-ui-user-profile-color-1 {
|
||||
fill: var(--red-ui-user-profile-colors-1);
|
||||
}
|
||||
&.red-ui-user-profile-color-2 {
|
||||
fill: var(--red-ui-user-profile-colors-2);
|
||||
}
|
||||
&.red-ui-user-profile-color-3 {
|
||||
fill: var(--red-ui-user-profile-colors-3);
|
||||
}
|
||||
&.red-ui-user-profile-color-4 {
|
||||
fill: var(--red-ui-user-profile-colors-4);
|
||||
}
|
||||
&.red-ui-user-profile-color-5 {
|
||||
fill: var(--red-ui-user-profile-colors-5);
|
||||
}
|
||||
}
|
||||
.red-ui-multiplayer-annotation-border {
|
||||
stroke: $multiplayer-user-icon-border;
|
||||
stroke-width: 1px;
|
||||
fill: none;
|
||||
}
|
||||
.red-ui-multiplayer-annotation-anon-label {
|
||||
fill: $multiplayer-user-icon-text-color;
|
||||
stroke: none;
|
||||
}
|
||||
text {
|
||||
user-select: none;
|
||||
fill: $multiplayer-user-icon-text-color;
|
||||
stroke: none;
|
||||
font-size: 10px;
|
||||
&.red-ui-multiplayer-user-count {
|
||||
fill: $multiplayer-user-icon-count-text-color;
|
||||
}
|
||||
}
|
||||
}
|
@@ -299,4 +299,7 @@
|
||||
|
||||
--red-ui-tab-icon-color: #{$tab-icon-color};
|
||||
|
||||
@each $current-color in 1 2 3 4 5 {
|
||||
--red-ui-user-profile-colors-#{"" + $current-color}: #{map-get($user-profile-colors, $current-color)};
|
||||
}
|
||||
}
|
||||
|
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-background-deploy.png
vendored
Normal file
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-background-deploy.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-diff-update.png
vendored
Normal file
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-diff-update.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer-location.png
vendored
Normal file
BIN
packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer-location.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@@ -1,12 +1,12 @@
|
||||
export default {
|
||||
version: "4.0.0-beta.2",
|
||||
version: "4.0.0-beta.3",
|
||||
steps: [
|
||||
{
|
||||
titleIcon: "fa fa-map-o",
|
||||
title: {
|
||||
"en-US": "Welcome to Node-RED 4.0 Beta 2!",
|
||||
"ja": "Node-RED 4.0 Beta 2へようこそ!",
|
||||
"fr": "Bienvenue dans Node-RED 4.0 Beta 2!"
|
||||
"en-US": "Welcome to Node-RED 4.0 Beta 3!",
|
||||
"ja": "Node-RED 4.0 Beta 3へようこそ!",
|
||||
"fr": "Bienvenue dans Node-RED 4.0 Beta 3!"
|
||||
},
|
||||
description: {
|
||||
"en-US": "<p>Let's take a moment to discover the new features in this release.</p>",
|
||||
@@ -16,54 +16,115 @@ export default {
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Multiplayer Mode"
|
||||
"en-US": "Multiplayer Mode",
|
||||
"ja": "複数ユーザ同時利用モード",
|
||||
"fr": "Mode Multi-utilisateur"
|
||||
},
|
||||
image: 'images/nr4-multiplayer.png',
|
||||
image: 'images/nr4-multiplayer-location.png',
|
||||
description: {
|
||||
"en-US": `<p>This release includes the first small steps towards making Node-RED easier
|
||||
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>`
|
||||
"en-US": `<p>Multiplayer mode was introduced in the previous beta. With this release it
|
||||
now shows where in the editor other users are.</p>
|
||||
<p>As with the last beta, check the release post for details on how to enable this feature in your settings file.</p>`,
|
||||
// "ja": ``,
|
||||
"fr": `<p>Le mode multi-utilisateur a été introduit dans la version bêta précédente. Avec cette nouvelle version, vous
|
||||
pourrez désormais savoir où ces utilisateurs se trouvent dans l'éditeur.</p>
|
||||
<p>Comme pour la dernière version bêta, 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 background deploy handling",
|
||||
// "ja": "",
|
||||
"fr": "Meilleure gestion du déploiement en arrière-plan"
|
||||
},
|
||||
image: 'images/nr4-background-deploy.png',
|
||||
description: {
|
||||
"en-US": `<p>If another user deploys changes whilst you are editing, we now use a more discrete notification
|
||||
that doesn't stop you continuing your work - especially if they are being very productive and deploying lots
|
||||
of changes.</p>`,
|
||||
// "ja": ``,
|
||||
"fr": `<p>Si un autre utilisateur déploie des modifications pendant que vous êtes en train de modifier, vous recevrez
|
||||
une notification plus discrète qu'auparavant qui ne vous empêche pas de continuer votre travail.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "Improved flow diffs",
|
||||
// "ja": "",
|
||||
"fr": "Amélioration des différences de flux"
|
||||
},
|
||||
image: 'images/nr4-diff-update.png',
|
||||
description: {
|
||||
"en-US": `<p>When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration
|
||||
changes and those that have only been moved.<p>
|
||||
<p>When faced with a long list of changes to look at, this makes it much easier to focus on more sigificant items.</p>`,
|
||||
// "ja": ``,
|
||||
"fr": `<p>Lors de l'affichage des modifications apportées à un flux, Node-RED fait désormais la distinction entre les
|
||||
noeuds qui ont changé de configuration et ceux qui ont seulement été déplacés.<p>
|
||||
<p>Face à une longue liste de changements à examiner, il est beaucoup plus facile de se concentrer sur les éléments les
|
||||
plus importants.</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "That's it for Beta 3!",
|
||||
"ja": "ベータ2については以上です!",
|
||||
"fr": "C'est tout pour la bêta 3 !"
|
||||
},
|
||||
description: {
|
||||
"en-US": `<p>Keep clicking through to see what was added in previous beta releases</p>`,
|
||||
"ja": `<p>クリックを続けてベータ1で追加された内容を確認してください。</p>`,
|
||||
"fr": `<p>Continuez à cliquer pour voir ce qui a été ajouté dans la version bêta 1</p>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"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>`
|
||||
}
|
||||
},
|
||||
{
|
||||
title: {
|
||||
"en-US": "That's if for Beta 2!"
|
||||
},
|
||||
description: {
|
||||
"en-US": `<p>Keep clicking through to see what was added in Beta 1</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>`
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -374,7 +374,7 @@ module.exports = function(RED) {
|
||||
iniOpt.breakOnSigint = true;
|
||||
}
|
||||
}
|
||||
node.script = vm.createScript(functionText, createVMOpt(node, ""));
|
||||
node.script = new vm.Script(functionText, createVMOpt(node, ""));
|
||||
if (node.fin && (node.fin !== "")) {
|
||||
var finText = `(function () {
|
||||
var node = {
|
||||
@@ -438,10 +438,9 @@ module.exports = function(RED) {
|
||||
|
||||
//store the error in msg to be used in flows
|
||||
msg.error = err;
|
||||
|
||||
var line = 0;
|
||||
var errorMessage;
|
||||
if (stack.length > 0) {
|
||||
let line = 0;
|
||||
let errorMessage;
|
||||
while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) {
|
||||
line++;
|
||||
}
|
||||
@@ -455,11 +454,13 @@ module.exports = function(RED) {
|
||||
errorMessage += " (line "+lineno+", col "+cha+")";
|
||||
}
|
||||
}
|
||||
if (errorMessage) {
|
||||
err.message = errorMessage
|
||||
}
|
||||
}
|
||||
if (!errorMessage) {
|
||||
errorMessage = err.toString();
|
||||
}
|
||||
done(errorMessage);
|
||||
// Pass the whole error object so any additional properties
|
||||
// (such as cause) are preserved
|
||||
done(err);
|
||||
}
|
||||
else if (typeof err === "string") {
|
||||
done(err);
|
||||
|
@@ -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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/nodes",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3-1",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -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";
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/registry",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3-1",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/util": "4.0.0-beta.2",
|
||||
"@node-red/util": "4.0.0-beta.3-1",
|
||||
"clone": "2.1.2",
|
||||
"fs-extra": "11.1.1",
|
||||
"semver": "7.5.4",
|
||||
|
@@ -678,6 +678,9 @@ class Flow {
|
||||
if (logMessage.hasOwnProperty('stack')) {
|
||||
errorMessage.error.stack = logMessage.stack;
|
||||
}
|
||||
if (logMessage.hasOwnProperty('cause')) {
|
||||
errorMessage.error.cause = logMessage.cause;
|
||||
}
|
||||
targetCatchNode.receive(errorMessage);
|
||||
handled = true;
|
||||
});
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
});
|
||||
|
@@ -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 = {};
|
||||
|
@@ -69,7 +69,7 @@ module.exports = {
|
||||
// Send init info to new connection
|
||||
const initPacket = {
|
||||
topic: "multiplayer/init",
|
||||
data: getSessionsList(),
|
||||
data: { sessions: getSessionsList() },
|
||||
session: opts.session
|
||||
}
|
||||
// console.log('<<', initPacket)
|
||||
|
@@ -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) {
|
||||
|
@@ -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) {
|
||||
|
@@ -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"
|
||||
}
|
||||
},
|
||||
|
@@ -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"
|
||||
}
|
||||
},
|
||||
|
@@ -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"
|
||||
}
|
||||
},
|
||||
|
@@ -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__",
|
||||
|
@@ -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である必要があります"
|
||||
}
|
||||
},
|
||||
|
@@ -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"
|
||||
}
|
||||
},
|
||||
|
@@ -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 было функцией"
|
||||
}
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/runtime",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3-1",
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
@@ -16,12 +16,13 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/registry": "4.0.0-beta.2",
|
||||
"@node-red/util": "4.0.0-beta.2",
|
||||
"@node-red/registry": "4.0.0-beta.3-1",
|
||||
"@node-red/util": "4.0.0-beta.3-1",
|
||||
"async-mutex": "0.4.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@node-red/util",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3-1",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
8
packages/node_modules/node-red/lib/red.js
vendored
8
packages/node_modules/node-red/lib/red.js
vendored
@@ -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');
|
||||
|
12
packages/node_modules/node-red/package.json
vendored
12
packages/node_modules/node-red/package.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "node-red",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3-1",
|
||||
"description": "Low-code programming for event-driven applications",
|
||||
"homepage": "https://nodered.org",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,10 +31,10 @@
|
||||
"flow"
|
||||
],
|
||||
"dependencies": {
|
||||
"@node-red/editor-api": "4.0.0-beta.2",
|
||||
"@node-red/runtime": "4.0.0-beta.2",
|
||||
"@node-red/util": "4.0.0-beta.2",
|
||||
"@node-red/nodes": "4.0.0-beta.2",
|
||||
"@node-red/editor-api": "4.0.0-beta.3-1",
|
||||
"@node-red/runtime": "4.0.0-beta.3-1",
|
||||
"@node-red/util": "4.0.0-beta.3-1",
|
||||
"@node-red/nodes": "4.0.0-beta.3-1",
|
||||
"basic-auth": "2.0.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"express": "4.19.2",
|
||||
@@ -47,6 +47,6 @@
|
||||
"bcrypt": "5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=18.5"
|
||||
}
|
||||
}
|
||||
|
53
packages/node_modules/node-red/red.js
vendored
53
packages/node_modules/node-red/red.js
vendored
@@ -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 {
|
||||
|
@@ -390,7 +390,8 @@ describe('function node', function() {
|
||||
msg.should.have.property('level', helper.log().ERROR);
|
||||
msg.should.have.property('id', 'n1');
|
||||
msg.should.have.property('type', 'function');
|
||||
msg.should.have.property('msg', 'ReferenceError: retunr is not defined (line 2, col 1)');
|
||||
msg.should.have.property('msg')
|
||||
msg.msg.message.should.equal('ReferenceError: retunr is not defined (line 2, col 1)');
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
@@ -659,7 +660,8 @@ describe('function node', function() {
|
||||
msg.should.have.property('level', helper.log().ERROR);
|
||||
msg.should.have.property('id', name);
|
||||
msg.should.have.property('type', 'function');
|
||||
msg.should.have.property('msg', 'Error: Callback must be a function');
|
||||
msg.should.have.property('msg')
|
||||
msg.msg.message.should.equal('Callback must be a function');
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
|
@@ -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'})
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user