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 c2e89133b..3cd8581a2 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:", @@ -616,6 +616,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", @@ -631,7 +632,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...", @@ -641,6 +644,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 1118f063b..8d92d0417 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 @@ -1473,7 +1473,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, @@ -1561,10 +1568,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; @@ -1963,15 +1974,27 @@ 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: '"' + Object.keys(options.modules).join('", "') + '"' - }) + }); + } + }, + { + text: RED._("palette.editor.installAll"), + class: "pull-left", + click: function(e) { + unknownNotification.close(); + + RED.actions.invoke('core:manage-palette', { + autoInstall: true, + modules: options.modules + }); } } ] @@ -2993,21 +3016,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(), @@ -3016,7 +3051,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 44e6146c8..76a64cb82 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 @@ -626,6 +626,9 @@ RED.palette.editor = (function() { }) 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) { @@ -639,7 +642,8 @@ RED.palette.editor = (function() { } } } - }); + } + }); RED.events.on('registry:module-updated', function(ns) { refreshNodeModule(ns.module); @@ -1501,6 +1505,77 @@ RED.palette.editor = (function() { }) } + function autoInstallModules(modules) { + if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { + console.error(new 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); + } + + RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install") + " : " + moduleName + " " + moduleVersion); + + 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()); + } + } + return { init: init, install: install 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 bc89992e2..4cfe835e1 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,7 +406,8 @@ "label": { "unknown": "unknown" }, - "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

" + "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

You can also click the button below to open the palette and install the missing modules.

" }, "mqtt": { "label": {