diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 6f83457a4..a479b36e5 100644 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -265,7 +265,7 @@ "download": "Download", "importUnrecognised": "Imported unrecognised type:", "importUnrecognised_plural": "Imported unrecognised types:", - "importWithModuleInfo": "Required dependencies missing", + "importWithModuleInfo": "Required modules missing", "importWithModuleInfoDesc": "These nodes are not currently installed in your palette and are required for the imported flow:", "importDuplicate": "Imported duplicate node:", "importDuplicate_plural": "Imported duplicate nodes:", @@ -626,6 +626,7 @@ "yearsMonthsV": "__y__ years, __count__ month ago", "yearsMonthsV_plural": "__y__ years, __count__ months ago" }, + "manageModules": "Manage modules", "nodeCount": "__label__ node", "nodeCount_plural": "__label__ nodes", "pluginCount": "__count__ plugin", @@ -643,7 +644,9 @@ "update": "update to __version__", "updated": "updated", "install": "install", + "installAll": "Install all", "installed": "installed", + "installing": "Module installation in progress: __module__", "conflict": "conflict", "conflictTip": "

This module cannot be installed as it includes a
node type that has already been installed

Conflicts with __module__

", "loading": "Loading catalogues...", @@ -653,6 +656,7 @@ "sortRelevance": "relevance", "sortAZ": "a-z", "sortRecent": "recent", + "successfulInstall": "Successfully installed modules", "more": "+ __count__ more", "upload": "Upload module tgz file", "refresh": "Refresh module list", diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 2f0bc810d..a4cb79698 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -1494,7 +1494,14 @@ RED.nodes = (function() { } /** * Converts the current node selection to an exportable JSON Object - **/ + * @param {Array} set the node selection to export + * @param {Object} options + * @param {Record} [options.exportedIds] + * @param {Record} [options.exportedSubflows] + * @param {Record} [options.exportedConfigNodes] + * @param {boolean} [options.includeModuleConfig] + * @returns {Array} + */ function createExportableNodeSet(set, { exportedIds, exportedSubflows, @@ -1582,10 +1589,14 @@ RED.nodes = (function() { return nns; } - // Create the Flow JSON for the current configuration - // opts.credentials (whether to include (known) credentials) - default: true - // opts.dimensions (whether to include node dimensions) - default: false - // opts.includeModuleConfig (whether to include modules) - default: false + /** + * Converts the current configuration to an exportable JSON Object + * @param {object} opts + * @param {boolean} [opts.credentials] whether to include (known) credentials. Default `true`. + * @param {boolean} [opts.dimensions] whether to include node dimensions. Default `false`. + * @param {boolean} [opts.includeModuleConfig] whether to include modules. Default `false`. + * @returns {Array} + */ function createCompleteNodeSet(opts) { var nns = []; var i; @@ -2009,15 +2020,30 @@ RED.nodes = (function() { // Provide option to install missing modules notificationOptions.buttons = [ { - text: "Manage dependencies", - class:"primary", + text: RED._("palette.editor.manageModules"), + class: "primary", click: function(e) { unknownNotification.close(); RED.actions.invoke('core:manage-palette', { view: 'install', filter: '"' + missingModules.join('", "') + '"' - }) + }); + } + }, + { + text: RED._("palette.editor.installAll"), + class: "pull-left", + click: function(e) { + unknownNotification.close(); + + RED.actions.invoke('core:manage-palette', { + autoInstall: true, + modules: missingModules.reduce((modules, moduleName) => { + modules[moduleName] = options.modules[moduleName]; + return modules; + }, {}), + }); } } ] @@ -3188,21 +3214,33 @@ RED.nodes = (function() { } } } + + /** + * Gets the module list for the given nodes + * @param {Array} nodes the nodes to search in + * @returns {Record} an object with {[moduleName]: moduleVersion} + */ function getModuleListForNodes(nodes) { const modules = {} - nodes.forEach(n => { - const nodeSet = RED.nodes.registry.getNodeSetForType(n.type) - if (nodeSet) { - modules[nodeSet.module] = nodeSet.version + const typeSet = new Set() + nodes.forEach((n) => { + if (!typeSet.has(n.type)) { + typeSet.add(n.type) + const nodeSet = RED.nodes.registry.getNodeSetForType(n.type) + if (nodeSet) { + modules[nodeSet.module] = nodeSet.version + nodeSet.types.forEach((t) => typeSet.add(t)) + } } }) return modules } + function updateGlobalConfigModuleList(nodes) { const modules = getModuleListForNodes(nodes) delete modules['node-red'] const hasModules = (Object.keys(modules).length > 0) - let globalConfigNode = nodes.find(n => n.type === 'global-config') + let globalConfigNode = nodes.find((n) => n.type === 'global-config') if (!globalConfigNode && hasModules) { globalConfigNode = { id: RED.nodes.id(), @@ -3211,7 +3249,7 @@ RED.nodes = (function() { modules } nodes.push(globalConfigNode) - } else if (globalConfigNode) { + } else if (hasModules) { globalConfigNode.modules = modules } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index 921c3a1eb..64d526dc9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -744,6 +744,9 @@ RED.palette.editor = (function() { refreshCatalogues() RED.actions.add("core:manage-palette", function(opts) { + if (opts && opts.autoInstall && opts.modules) { + autoInstallModules(opts.modules); + } else { RED.userSettings.show('palette'); if (opts) { if (opts.view) { @@ -757,7 +760,8 @@ RED.palette.editor = (function() { } } } - }); + } + }); RED.events.on('registry:module-updated', function(ns) { refreshNodeModule(ns.module); @@ -1688,6 +1692,75 @@ RED.palette.editor = (function() { }) } + function autoInstallModules(modules) { + if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { + console.error('Palette not editable'); + return; + } + + let notification; + const notificationOptions = { + 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"); + } + } + ] + }; + + const moduleArray = Object.entries(modules); + const installModule = function (module) { + const [moduleName, moduleVersion] = module; + + const spinner = '
'; + const msg = "

" + RED._("palette.editor.installing", { module: moduleName }) + "

" + spinner; + + if (!notification) { + notification = RED.notify(msg, notificationOptions); + } else { + notification.update(msg, notificationOptions); + } + + installNodeModule(moduleName, moduleVersion, undefined, function(xhr, textStatus, err) { + if (err && xhr.status === 504) { + notification.update(RED._("palette.editor.errors.installTimeout"), { + modal: true, + fixed: true, + buttons: notificationOptions.buttons + }); + } else if (xhr) { + if (xhr.responseJSON) { + notification.update(RED._("palette.editor.errors.installFailed", { module: moduleName, message:xhr.responseJSON.message }), { + type: "error", + modal: true, + fixed: true, + buttons: notificationOptions.buttons + }); + } + } else { + if (moduleArray.length) { + installModule(moduleArray.shift()); + } else { + notification.update(RED._("palette.editor.successfulInstall"), { ...notificationOptions, type: "success", timeout: 10000 }); + } + } + }); + }; + + if (moduleArray.length) { + installModule(moduleArray.shift()); + } + } + const updateStatusWidget = $(''); let updateStatusWidgetPopover; const updateStatusState = { moduleCount: 0 } diff --git a/packages/node_modules/@node-red/nodes/core/common/98-unknown.html b/packages/node_modules/@node-red/nodes/core/common/98-unknown.html index 282ad3415..64c9e8e4e 100644 --- a/packages/node_modules/@node-red/nodes/core/common/98-unknown.html +++ b/packages/node_modules/@node-red/nodes/core/common/98-unknown.html @@ -3,7 +3,7 @@

- +

diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index fdb85f021..7cde427f4 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -406,6 +406,7 @@ "label": { "unknown": "unknown" }, + "manageModules": "Manage modules", "tip": "

This node is a type unknown to your installation of Node-RED.

If you deploy with the node in this state, it's configuration will be preserved, but the flow will not start until the missing type is installed.

See the Info side bar for more help

" }, "mqtt": {