From 81937ddc4576bffd159c54ed61dba8af2130f8d4 Mon Sep 17 00:00:00 2001 From: Ralph Wetzel Date: Tue, 17 Oct 2023 22:44:33 +0200 Subject: [PATCH] Add plugin support to palette manager --- .../@node-red/editor-api/lib/admin/index.js | 1 + .../@node-red/editor-api/lib/admin/plugins.js | 26 ++ .../editor-client/locales/de/editor.json | 2 + .../editor-client/locales/en-US/editor.json | 2 + .../@node-red/editor-client/src/js/nodes.js | 2 + .../@node-red/editor-client/src/js/plugins.js | 37 ++- .../@node-red/editor-client/src/js/red.js | 36 +++ .../editor-client/src/js/ui/palette-editor.js | 278 ++++++++++++------ .../@node-red/registry/lib/index.js | 1 + .../@node-red/registry/lib/installer.js | 18 +- .../@node-red/registry/lib/plugins.js | 83 +++++- .../@node-red/registry/lib/registry.js | 11 +- .../@node-red/runtime/lib/api/plugins.js | 19 ++ .../@node-red/runtime/lib/nodes/index.js | 6 +- .../@node-red/runtime/lib/plugins.js | 1 + .../runtime/locales/en-US/runtime.json | 1 + 16 files changed, 420 insertions(+), 104 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js index 26eabe65b..fa910a399 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js @@ -91,6 +91,7 @@ module.exports = { // Plugins adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler); adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler); + adminApp.get(/^\/plugins\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,needsPermission("plugins.read"),plugins.getConfig,apiUtil.errorHandler); adminApp.get("/diagnostics", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler); diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js index ac6c6f701..304aed90e 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js @@ -40,5 +40,31 @@ module.exports = { console.log(err.stack); apiUtils.rejectHandler(req,res,err); }) + }, + getConfig: function(req, res) { + + let opts = { + user: req.user, + module: req.params[0], + req: apiUtils.getRequestLogObject(req) + } + + if (req.get("accept") === "application/json") { + runtimeAPI.nodes.getNodeInfo(opts.module).then(function(result) { + res.send(result); + }).catch(function(err) { + apiUtils.rejectHandler(req,res,err); + }) + } else { + opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + opts.lang = "en-US"; + } + runtimeAPI.plugins.getPluginConfig(opts).then(function(result) { + return res.send(result); + }).catch(function(err) { + apiUtils.rejectHandler(req,res,err); + }) + } } }; diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index f2955c266..b3e891d03 100644 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -591,6 +591,8 @@ }, "nodeCount": "__label__ Node", "nodeCount_plural": "__label__ Nodes", + "pluginCount": "__count__ Plugin", + "pluginCount_plural": "__count__ Plugins", "moduleCount": "__count__ Modul verfügbar", "moduleCount_plural": "__count__ Module verfügbar", "inuse": "In Gebrauch", 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 8742a6c6f..a0d61c6d7 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 @@ -609,6 +609,8 @@ }, "nodeCount": "__label__ node", "nodeCount_plural": "__label__ nodes", + "pluginCount": "__count__ plugin", + "pluginCount_plural": "__count__ plugins", "moduleCount": "__count__ module available", "moduleCount_plural": "__count__ modules available", "inuse": "in use", 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 2a14a0244..8f2849241 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 @@ -124,6 +124,8 @@ RED.nodes = (function() { }, removeNodeSet: function(id) { var ns = nodeSets[id]; + if (!ns) return {}; + for (var j=0;j { + addedPlugins.push(p.id); + }) + + RED.i18n.loadNodeCatalog(id, function() { + var lang = localStorage.getItem("editor-language")||RED.i18n.detectLanguage(); + $.ajax({ + headers: { + "Accept":"text/html", + "Accept-Language": lang + }, + cache: false, + url: 'plugins/'+id, + success: function(data) { + appendPluginConfig(data); + } + }); + }); + }); + if (addedPlugins.length) { + let pluginList = ""; + // ToDo: Adapt notification (node -> plugin) + RED.notify(RED._("palette.event.nodeAdded", {count:addedPlugins.length})+pluginList,"success"); + } + }) + } + }); RED.comms.subscribe("notification/node/#",function(topic,msg) { var i,m; var typeList; 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 8d3815749..554ef3a5f 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 @@ -248,86 +248,105 @@ RED.palette.editor = (function() { var moduleInfo = nodeEntries[module].info; var nodeEntry = nodeEntries[module].elements; if (nodeEntry) { - var activeTypeCount = 0; - var typeCount = 0; - var errorCount = 0; - nodeEntry.errorList.empty(); - nodeEntries[module].totalUseCount = 0; - nodeEntries[module].setUseCount = {}; + if (moduleInfo.plugin) { + nodeEntry.enableButton.hide(); + nodeEntry.removeButton.show(); - for (var setName in moduleInfo.sets) { - if (moduleInfo.sets.hasOwnProperty(setName)) { - var inUseCount = 0; - var set = moduleInfo.sets[setName]; - var setElements = nodeEntry.sets[setName]; - if (set.err) { - errorCount++; - var errMessage = set.err; - if (set.err.message) { - errMessage = set.err.message; - } else if (set.err.code) { - errMessage = set.err.code; + let pluginCount = 0; + + for (let setName in moduleInfo.sets) { + if (moduleInfo.sets.hasOwnProperty(setName)) { + let set = moduleInfo.sets[setName]; + if (set.plugins) { + pluginCount += set.plugins.length; } - $("
  • ").text(errMessage).appendTo(nodeEntry.errorList); } - if (set.enabled) { - activeTypeCount += set.types.length; - } - typeCount += set.types.length; - for (var i=0;i").text(errMessage).appendTo(nodeEntry.errorList); + } if (set.enabled) { - var def = RED.nodes.getType(t); - if (def && def.color) { - swatch.css({background:RED.utils.getNodeColor(t,def)}); - swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))}) + activeTypeCount += set.types.length; + } + typeCount += set.types.length; + for (var i=0;i 0) { - setElements.enableButton.text(RED._('palette.editor.inuse')); - setElements.enableButton.addClass('disabled'); - } else { - setElements.enableButton.removeClass('disabled'); - if (set.enabled) { - setElements.enableButton.text(RED._('palette.editor.disable')); + if (inUseCount > 0) { + setElements.enableButton.text(RED._('palette.editor.inuse')); + setElements.enableButton.addClass('disabled'); } else { - setElements.enableButton.text(RED._('palette.editor.enable')); + setElements.enableButton.removeClass('disabled'); + if (set.enabled) { + setElements.enableButton.text(RED._('palette.editor.disable')); + } else { + setElements.enableButton.text(RED._('palette.editor.enable')); + } } + setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled); } - setElements.setRow.toggleClass("red-ui-palette-module-set-disabled",!set.enabled); } - } - if (errorCount === 0) { - nodeEntry.errorRow.hide() - } else { - nodeEntry.errorRow.show(); - } - - var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount; - nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount})); - - if (nodeEntries[module].totalUseCount > 0) { - nodeEntry.enableButton.text(RED._('palette.editor.inuse')); - nodeEntry.enableButton.addClass('disabled'); - nodeEntry.removeButton.hide(); - } else { - nodeEntry.enableButton.removeClass('disabled'); - if (moduleInfo.local) { - nodeEntry.removeButton.css('display', 'inline-block'); - } - if (activeTypeCount === 0) { - nodeEntry.enableButton.text(RED._('palette.editor.enableall')); + if (errorCount === 0) { + nodeEntry.errorRow.hide() } else { - nodeEntry.enableButton.text(RED._('palette.editor.disableall')); + nodeEntry.errorRow.show(); + } + + var nodeCount = (activeTypeCount === typeCount)?typeCount:activeTypeCount+" / "+typeCount; + nodeEntry.setCount.text(RED._('palette.editor.nodeCount',{count:typeCount,label:nodeCount})); + + if (nodeEntries[module].totalUseCount > 0) { + nodeEntry.enableButton.text(RED._('palette.editor.inuse')); + nodeEntry.enableButton.addClass('disabled'); + nodeEntry.removeButton.hide(); + } else { + nodeEntry.enableButton.removeClass('disabled'); + if (moduleInfo.local) { + nodeEntry.removeButton.css('display', 'inline-block'); + } + if (activeTypeCount === 0) { + nodeEntry.enableButton.text(RED._('palette.editor.enableall')); + } else { + nodeEntry.enableButton.text(RED._('palette.editor.disableall')); + } + nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0)); } - nodeEntry.container.toggleClass("disabled",(activeTypeCount === 0)); } } if (moduleInfo.pending_version) { @@ -678,6 +697,33 @@ RED.palette.editor = (function() { } } }) + + RED.events.on("registry:plugin-module-added", function(module) { + + if (!nodeEntries.hasOwnProperty(module)) { + nodeEntries[module] = {info:RED.plugins.getModule(module)}; + var index = [module]; + for (var s in nodeEntries[module].info.sets) { + if (nodeEntries[module].info.sets.hasOwnProperty(s)) { + index.push(s); + index = index.concat(nodeEntries[module].info.sets[s].types) + } + } + nodeEntries[module].index = index.join(",").toLowerCase(); + nodeList.editableList('addItem', nodeEntries[module]); + } else { + _refreshNodeModule(module); + } + + for (var i=0;i',{class:"red-ui-palette-module-set"}).appendTo(contentRow); var buttonGroup = $('
    ',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow); var typeSwatches = {}; - set.types.forEach(function(t) { - var typeDiv = $('
    ',{class:"red-ui-palette-module-type"}).appendTo(setRow); - typeSwatches[t] = $('',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv); - $('',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv); - }) - var enableButton = $('').appendTo(buttonGroup); - enableButton.on("click", function(evt) { - evt.preventDefault(); - if (object.setUseCount[setName] === 0) { - var currentSet = RED.nodes.registry.getNodeSet(set.id); - shade.show(); - var newState = !currentSet.enabled - changeNodeState(set.id,newState,shade,function(xhr){ - if (xhr) { - if (xhr.responseJSON) { - RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message})); + if (set.types) { + set.types.forEach(function(t) { + var typeDiv = $('
    ',{class:"red-ui-palette-module-type"}).appendTo(setRow); + typeSwatches[t] = $('',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv); + $('',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv); + }) + var enableButton = $('').appendTo(buttonGroup); + enableButton.on("click", function(evt) { + evt.preventDefault(); + if (object.setUseCount[setName] === 0) { + var currentSet = RED.nodes.registry.getNodeSet(set.id); + shade.show(); + var newState = !currentSet.enabled + changeNodeState(set.id,newState,shade,function(xhr){ + if (xhr) { + if (xhr.responseJSON) { + RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message})); + } } - } - }); - } - }) + }); + } + }) + } else if (set.plugins) { + set.plugins.forEach(function(p) { + var typeDiv = $('
    ',{class:"red-ui-palette-module-type"}).appendTo(setRow); + // typeSwatches[p.id] = $('',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv); + $(' ',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv); + $('',{class:"red-ui-palette-module-type-node"}).text(p.id).appendTo(typeDiv); + }) + } object.elements.sets[set.name] = { setRow: setRow, @@ -1226,7 +1282,55 @@ RED.palette.editor = (function() { } } ] - }); } + }); + } + } else { + // dedicated list management for plugins + if (entry.plugin) { + + let e = nodeEntries[entry.name]; + if (e) { + nodeList.editableList('removeItem', e); + delete nodeEntries[entry.name]; + } + + // We assume that a plugin that implements onremove + // cleans the editor accordingly of its left-overs. + let found_onremove = true; + + let keys = Object.keys(entry.sets); + keys.forEach((key) => { + let set = entry.sets[key]; + for (let i=0; i\n"; - result += config.config; - } - } - } + result += get_config_of_plugins(moduleConfigs[module].plugins); } } pluginConfigCache[lang] = result; } return pluginConfigCache[lang]; } + +function getPluginConfig(id, lang) { + let result = ''; + let moduleConfigs = registry.getModuleList(); + if (moduleConfigs.hasOwnProperty(id)) { + result = get_config_of_plugins(moduleConfigs[id].plugins); + } + return result; +} + +// helper function to avoid code duplication +function get_config_of_plugins(plugins) { + let result = ''; + for (let plugin in plugins) { + if (plugins.hasOwnProperty(plugin)) { + let config = plugins[plugin]; + if (config.enabled && !config.err && config.config) { + result += "\n\n"; + result += config.config; + } + } + } + return result; +} + function getPluginList() { var list = []; var moduleConfigs = registry.getModuleList(); @@ -142,12 +162,51 @@ function exportPluginSettings(safeSettings) { return safeSettings; } +function removeModule(moduleId) { + + // clean the (plugin) registry when a module is removed / uninstalled + + let pluginList = []; + let module = registry.getModule(moduleId); + let keys = Object.keys(module.plugins ?? {}); + keys.forEach( key => { + let _plugins = module.plugins[key].plugins ?? []; + _plugins.forEach( plugin => { + let id = plugin.id; + + if (plugin.onremove && typeof plugin.onremove === 'function') { + plugin.onremove(); + } + + delete pluginToId[id]; + delete plugins[id]; + delete pluginSettings[id]; + pluginConfigCache = {}; + + let psbtype = pluginsByType[plugin.type] ?? []; + for (let i=psbtype.length; i>0; i--) { + let pbt = psbtype[i-1]; + if (pbt.id == id) { + psbtype.splice(i-1, 1); + } + } + }) + + pluginList.push(registry.filterNodeInfo(module.plugins[key])); + + }) + + return pluginList; +} + module.exports = { init, registerPlugin, getPlugin, getPluginsByType, getPluginConfigs, + getPluginConfig, getPluginList, - exportPluginSettings + exportPluginSettings, + removeModule } diff --git a/packages/node_modules/@node-red/registry/lib/registry.js b/packages/node_modules/@node-red/registry/lib/registry.js index 389285dbc..d7ac7e672 100644 --- a/packages/node_modules/@node-red/registry/lib/registry.js +++ b/packages/node_modules/@node-red/registry/lib/registry.js @@ -386,7 +386,8 @@ function getModuleInfo(module) { local: moduleConfigs[module].local, user: moduleConfigs[module].user, path: moduleConfigs[module].path, - nodes: [] + nodes: [], + plugins: [] }; if (moduleConfigs[module].dependencies) { m.dependencies = moduleConfigs[module].dependencies; @@ -399,6 +400,14 @@ function getModuleInfo(module) { nodeInfo.version = m.version; m.nodes.push(nodeInfo); } + + let plugins = Object.values(moduleConfigs[module].plugins); + plugins.forEach((plugin) => { + let nodeInfo = filterNodeInfo(plugin); + nodeInfo.version = m.version; + m.plugins.push(nodeInfo); + }); + return m; } else { return null; diff --git a/packages/node_modules/@node-red/runtime/lib/api/plugins.js b/packages/node_modules/@node-red/runtime/lib/api/plugins.js index 21703508c..e15f33cb4 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/plugins.js +++ b/packages/node_modules/@node-red/runtime/lib/api/plugins.js @@ -65,6 +65,25 @@ var api = module.exports = { runtime.log.audit({event: "plugins.configs.get"}, opts.req); return runtime.plugins.getPluginConfigs(opts.lang); }, + + /** + * Gets the editor content for one registered plugin + * @param {Object} opts + * @param {User} opts.user - the user calling the api + * @param {User} opts.user - the user calling the api + * @param {Object} opts.req - the request to log (optional) + * @return {Promise} - the plugin information + * @memberof @node-red/runtime_plugins + */ + getPluginConfig: async function(opts) { + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + throw new Error("Invalid language: "+opts.lang) + return; + } + runtime.log.audit({event: "plugins.configs.get"}, opts.req); + return runtime.plugins.getPluginConfig(opts.module, opts.lang); + }, + /** * Gets all registered module message catalogs * @param {Object} opts diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js index 5b859a5f8..a852a86ea 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js @@ -173,7 +173,11 @@ function installModule(module,version,url) { if (info.pending_version) { events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:info.name,version:info.pending_version}}); } else { - events.emit("runtime-event",{id:"node/added",retain:false,payload:info.nodes}); + if (!info.nodes.length && info.plugins.length) { + events.emit("runtime-event",{id:"plugin/added",retain:false,payload:info.plugins}); + } else { + events.emit("runtime-event",{id:"node/added",retain:false,payload:info.nodes}); + } } return info; }); diff --git a/packages/node_modules/@node-red/runtime/lib/plugins.js b/packages/node_modules/@node-red/runtime/lib/plugins.js index bedeabb4c..3e85430ac 100644 --- a/packages/node_modules/@node-red/runtime/lib/plugins.js +++ b/packages/node_modules/@node-red/runtime/lib/plugins.js @@ -7,5 +7,6 @@ module.exports = { getPluginsByType: registry.getPluginsByType, getPluginList: registry.getPluginList, getPluginConfigs: registry.getPluginConfigs, + getPluginConfig: registry.getPluginConfig, exportPluginSettings: registry.exportPluginSettings } \ No newline at end of file diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json index 46890c26c..c8033a3de 100644 --- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json @@ -25,6 +25,7 @@ "removing-modules": "Removing modules from config", "added-types": "Added node types:", "removed-types": "Removed node types:", + "removed-plugins": "Removed plugins:", "install": { "invalid": "Invalid module name", "installing": "Installing module: __name__, version: __version__",