Add plugin support to palette manager

This commit is contained in:
Ralph Wetzel 2023-10-17 22:44:33 +02:00
parent eb940d6d57
commit 81937ddc45
16 changed files with 420 additions and 104 deletions

View File

@ -91,6 +91,7 @@ module.exports = {
// Plugins // Plugins
adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler); adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, 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); adminApp.get("/diagnostics", needsPermission("diagnostics.read"), diagnostics.getReport, apiUtil.errorHandler);

View File

@ -40,5 +40,31 @@ module.exports = {
console.log(err.stack); console.log(err.stack);
apiUtils.rejectHandler(req,res,err); 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);
})
}
} }
}; };

View File

@ -591,6 +591,8 @@
}, },
"nodeCount": "__label__ Node", "nodeCount": "__label__ Node",
"nodeCount_plural": "__label__ Nodes", "nodeCount_plural": "__label__ Nodes",
"pluginCount": "__count__ Plugin",
"pluginCount_plural": "__count__ Plugins",
"moduleCount": "__count__ Modul verfügbar", "moduleCount": "__count__ Modul verfügbar",
"moduleCount_plural": "__count__ Module verfügbar", "moduleCount_plural": "__count__ Module verfügbar",
"inuse": "In Gebrauch", "inuse": "In Gebrauch",

View File

@ -609,6 +609,8 @@
}, },
"nodeCount": "__label__ node", "nodeCount": "__label__ node",
"nodeCount_plural": "__label__ nodes", "nodeCount_plural": "__label__ nodes",
"pluginCount": "__count__ plugin",
"pluginCount_plural": "__count__ plugins",
"moduleCount": "__count__ module available", "moduleCount": "__count__ module available",
"moduleCount_plural": "__count__ modules available", "moduleCount_plural": "__count__ modules available",
"inuse": "in use", "inuse": "in use",

View File

@ -124,6 +124,8 @@ RED.nodes = (function() {
}, },
removeNodeSet: function(id) { removeNodeSet: function(id) {
var ns = nodeSets[id]; var ns = nodeSets[id];
if (!ns) return {};
for (var j=0;j<ns.types.length;j++) { for (var j=0;j<ns.types.length;j++) {
delete typeToId[ns.types[j]]; delete typeToId[ns.types[j]];
} }

View File

@ -1,6 +1,7 @@
RED.plugins = (function() { RED.plugins = (function() {
var plugins = {}; var plugins = {};
var pluginsByType = {}; var pluginsByType = {};
var moduleList = {};
function registerPlugin(id,definition) { function registerPlugin(id,definition) {
plugins[id] = definition; plugins[id] = definition;
@ -38,9 +39,43 @@ RED.plugins = (function() {
function getPluginsByType(type) { function getPluginsByType(type) {
return pluginsByType[type] || []; return pluginsByType[type] || [];
} }
function setPluginList(list) {
for(let i=0;i<list.length;i++) {
let p = list[i];
addPlugin(p);
}
}
function addPlugin(p) {
moduleList[p.module] = moduleList[p.module] || {
name:p.module,
version:p.version,
local:p.local,
sets:{},
plugin: true,
id: p.id
};
if (p.pending_version) {
moduleList[p.module].pending_version = p.pending_version;
}
moduleList[p.module].sets[p.name] = p;
RED.events.emit("registry:plugin-module-added",p.module);
}
function getModule(module) {
return moduleList[module];
}
return { return {
registerPlugin: registerPlugin, registerPlugin: registerPlugin,
getPlugin: getPlugin, getPlugin: getPlugin,
getPluginsByType: getPluginsByType getPluginsByType: getPluginsByType,
setPluginList: setPluginList,
addPlugin: addPlugin,
getModule: getModule
} }
})(); })();

View File

@ -25,6 +25,7 @@ var RED = (function() {
cache: false, cache: false,
url: 'plugins', url: 'plugins',
success: function(data) { success: function(data) {
RED.plugins.setPluginList(data);
loader.reportProgress(RED._("event.loadPlugins"), 13) loader.reportProgress(RED._("event.loadPlugins"), 13)
RED.i18n.loadPluginCatalogs(function() { RED.i18n.loadPluginCatalogs(function() {
loadPlugins(function() { loadPlugins(function() {
@ -525,6 +526,41 @@ var RED = (function() {
RED.view.redrawStatus(node); RED.view.redrawStatus(node);
} }
}); });
RED.comms.subscribe("notification/plugin/#",function(topic,msg) {
if (topic == "notification/plugin/added") {
RED.settings.refreshSettings(function(err, data) {
let addedPlugins = [];
msg.forEach(function(m) {
let id = m.id;
RED.plugins.addPlugin(m);
m.plugins.forEach((p) => {
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 = "<ul><li>"+addedPlugins.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
// ToDo: Adapt notification (node -> plugin)
RED.notify(RED._("palette.event.nodeAdded", {count:addedPlugins.length})+pluginList,"success");
}
})
}
});
RED.comms.subscribe("notification/node/#",function(topic,msg) { RED.comms.subscribe("notification/node/#",function(topic,msg) {
var i,m; var i,m;
var typeList; var typeList;

View File

@ -248,86 +248,105 @@ RED.palette.editor = (function() {
var moduleInfo = nodeEntries[module].info; var moduleInfo = nodeEntries[module].info;
var nodeEntry = nodeEntries[module].elements; var nodeEntry = nodeEntries[module].elements;
if (nodeEntry) { if (nodeEntry) {
var activeTypeCount = 0; if (moduleInfo.plugin) {
var typeCount = 0; nodeEntry.enableButton.hide();
var errorCount = 0; nodeEntry.removeButton.show();
nodeEntry.errorList.empty();
nodeEntries[module].totalUseCount = 0;
nodeEntries[module].setUseCount = {};
for (var setName in moduleInfo.sets) { let pluginCount = 0;
if (moduleInfo.sets.hasOwnProperty(setName)) {
var inUseCount = 0; for (let setName in moduleInfo.sets) {
var set = moduleInfo.sets[setName]; if (moduleInfo.sets.hasOwnProperty(setName)) {
var setElements = nodeEntry.sets[setName]; let set = moduleInfo.sets[setName];
if (set.err) { if (set.plugins) {
errorCount++; pluginCount += set.plugins.length;
var errMessage = set.err;
if (set.err.message) {
errMessage = set.err.message;
} else if (set.err.code) {
errMessage = set.err.code;
} }
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
} }
if (set.enabled) { }
activeTypeCount += set.types.length;
} nodeEntry.setCount.text(RED._('palette.editor.pluginCount',{count:pluginCount,label:pluginCount}));
typeCount += set.types.length;
for (var i=0;i<moduleInfo.sets[setName].types.length;i++) { } else {
var t = moduleInfo.sets[setName].types[i]; var activeTypeCount = 0;
inUseCount += (typesInUse[t]||0); var typeCount = 0;
var swatch = setElements.swatches[t]; var errorCount = 0;
nodeEntry.errorList.empty();
nodeEntries[module].totalUseCount = 0;
nodeEntries[module].setUseCount = {};
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;
}
$("<li>").text(errMessage).appendTo(nodeEntry.errorList);
}
if (set.enabled) { if (set.enabled) {
var def = RED.nodes.getType(t); activeTypeCount += set.types.length;
if (def && def.color) { }
swatch.css({background:RED.utils.getNodeColor(t,def)}); typeCount += set.types.length;
swatch.css({border: "1px solid "+getContrastingBorder(swatch.css('backgroundColor'))}) for (var i=0;i<moduleInfo.sets[setName].types.length;i++) {
var t = moduleInfo.sets[setName].types[i];
inUseCount += (typesInUse[t]||0);
var swatch = setElements.swatches[t];
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'))})
}
} }
} }
} nodeEntries[module].setUseCount[setName] = inUseCount;
nodeEntries[module].setUseCount[setName] = inUseCount; nodeEntries[module].totalUseCount += inUseCount;
nodeEntries[module].totalUseCount += inUseCount;
if (inUseCount > 0) { if (inUseCount > 0) {
setElements.enableButton.text(RED._('palette.editor.inuse')); setElements.enableButton.text(RED._('palette.editor.inuse'));
setElements.enableButton.addClass('disabled'); setElements.enableButton.addClass('disabled');
} else {
setElements.enableButton.removeClass('disabled');
if (set.enabled) {
setElements.enableButton.text(RED._('palette.editor.disable'));
} else { } 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) { if (errorCount === 0) {
nodeEntry.errorRow.hide() 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'));
} else { } 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) { 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<filteredList.length;i++) {
if (filteredList[i].info.id === module) {
var installButton = filteredList[i].elements.installButton;
installButton.addClass('disabled');
installButton.text(RED._('palette.editor.installed'));
break;
}
}
});
} }
var settingsPane; var settingsPane;
@ -804,6 +850,7 @@ RED.palette.editor = (function() {
errorRow: errorRow, errorRow: errorRow,
errorList: errorList, errorList: errorList,
setCount: setCount, setCount: setCount,
setButton: setButton,
container: container, container: container,
shade: shade, shade: shade,
versionSpan: versionSpan, versionSpan: versionSpan,
@ -829,27 +876,36 @@ RED.palette.editor = (function() {
var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow); var setRow = $('<div>',{class:"red-ui-palette-module-set"}).appendTo(contentRow);
var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow); var buttonGroup = $('<div>',{class:"red-ui-palette-module-set-button-group"}).appendTo(setRow);
var typeSwatches = {}; var typeSwatches = {};
set.types.forEach(function(t) { if (set.types) {
var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow); set.types.forEach(function(t) {
typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv); var typeDiv = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
$('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv); typeSwatches[t] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
}) $('<span>',{class:"red-ui-palette-module-type-node"}).text(t).appendTo(typeDiv);
var enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup); })
enableButton.on("click", function(evt) { var enableButton = $('<a href="#" class="red-ui-button red-ui-button-small"></a>').appendTo(buttonGroup);
evt.preventDefault(); enableButton.on("click", function(evt) {
if (object.setUseCount[setName] === 0) { evt.preventDefault();
var currentSet = RED.nodes.registry.getNodeSet(set.id); if (object.setUseCount[setName] === 0) {
shade.show(); var currentSet = RED.nodes.registry.getNodeSet(set.id);
var newState = !currentSet.enabled shade.show();
changeNodeState(set.id,newState,shade,function(xhr){ var newState = !currentSet.enabled
if (xhr) { changeNodeState(set.id,newState,shade,function(xhr){
if (xhr.responseJSON) { if (xhr) {
RED.notify(RED._('palette.editor.errors.'+(newState?'enable':'disable')+'Failed',{module: id,message:xhr.responseJSON.message})); 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 = $('<div>',{class:"red-ui-palette-module-type"}).appendTo(setRow);
// typeSwatches[p.id] = $('<span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
$('<span><i class="fa fa-puzzle-piece" aria-hidden="true"></i> </span>',{class:"red-ui-palette-module-type-swatch"}).appendTo(typeDiv);
$('<span>',{class:"red-ui-palette-module-type-node"}).text(p.id).appendTo(typeDiv);
})
}
object.elements.sets[set.name] = { object.elements.sets[set.name] = {
setRow: setRow, 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<set.plugins?.length; i++) {
let plgn = RED.plugins.getPlugin(set.plugins[i].id);
if (plgn && plgn.onremove && typeof plgn.onremove === 'function') {
plgn.onremove();
} else {
if (plgn && plgn.onadd && typeof plgn.onadd === 'function') {
// if there's no 'onadd', there shouldn't be any left-overs
found_onremove = false;
}
}
}
});
if (!found_onremove) {
let removeNotify = RED.notify("Removed plugin " + entry.name + ". Please reload the editor to clear left-overs.",{
modal: true,
fixed: true,
type: 'warning',
buttons: [
{
text: "Understood",
class:"primary",
click: function(e) {
removeNotify.close();
}
}
]
});
}
}
} }
}) })
notification.close(); notification.close();

View File

@ -319,6 +319,7 @@ module.exports = {
getPluginsByType: plugins.getPluginsByType, getPluginsByType: plugins.getPluginsByType,
getPluginList: plugins.getPluginList, getPluginList: plugins.getPluginList,
getPluginConfigs: plugins.getPluginConfigs, getPluginConfigs: plugins.getPluginConfigs,
getPluginConfig: plugins.getPluginConfig,
exportPluginSettings: plugins.exportPluginSettings, exportPluginSettings: plugins.exportPluginSettings,

View File

@ -28,6 +28,8 @@ const child_process = require('child_process');
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
let installerEnabled = false; let installerEnabled = false;
const plugins = require("./plugins");
let settings; let settings;
const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/; const moduleRe = /^(@[^/@]+?[/])?[^/@]+?$/;
const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/; const slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
@ -330,10 +332,18 @@ function reportRemovedModules(removedNodes) {
//comms.publish("node/removed",removedNodes,false); //comms.publish("node/removed",removedNodes,false);
log.info(log._("server.removed-types")); log.info(log._("server.removed-types"));
for (var j=0;j<removedNodes.length;j++) { for (var j=0;j<removedNodes.length;j++) {
for (var i=0;i<removedNodes[j].types.length;i++) { for (var i=0;i<removedNodes[j].types?.length;i++) {
log.info(" - "+(removedNodes[j].module?removedNodes[j].module+":":"")+removedNodes[j].types[i]); log.info(" - "+(removedNodes[j].module?removedNodes[j].module+":":"")+removedNodes[j].types[i]);
} }
} }
log.info(log._("server.removed-plugins"));
for (let j=0;j<removedNodes.length;j++) {
for (var i=0;i<removedNodes[j].plugins?.length;i++) {
log.info(" - "+(removedNodes[j].module?removedNodes[j].module+":":"")+removedNodes[j].plugins[i].id);
}
}
return removedNodes; return removedNodes;
} }
@ -495,8 +505,12 @@ function uninstallModule(module) {
} catch(err) { } catch(err) {
return reject(new Error(log._("server.install.uninstall-failed",{name:module}))); return reject(new Error(log._("server.install.uninstall-failed",{name:module})));
} }
// need to remove the plugins first,
// as registry data necessary to perform this operation
var list = plugins.removeModule(module);
list = list.concat(registry.removeModule(module));
var list = registry.removeModule(module);
log.info(log._("server.install.uninstalling",{name:module})); log.info(log._("server.install.uninstalling",{name:module}));
let triggerPayload = { let triggerPayload = {

View File

@ -39,6 +39,8 @@ function registerPlugin(nodeSetId,id,definition) {
pluginSettings[id] = definition.settings; pluginSettings[id] = definition.settings;
} }
// reset the cache when a new plugin is incoming!
pluginConfigCache = {};
if (definition.onadd && typeof definition.onadd === 'function') { if (definition.onadd && typeof definition.onadd === 'function') {
definition.onadd(); definition.onadd();
@ -55,29 +57,47 @@ function getPluginsByType(type) {
} }
function getPluginConfigs(lang) { function getPluginConfigs(lang) {
// we're not re-using getPluginConfig() here,
// to avoid calling registry.getModuleList() multiple times!
if (!pluginConfigCache[lang]) { if (!pluginConfigCache[lang]) {
var result = ""; var result = "";
var script = "";
var moduleConfigs = registry.getModuleList(); var moduleConfigs = registry.getModuleList();
for (var module in moduleConfigs) { for (var module in moduleConfigs) {
/* istanbul ignore else */ /* istanbul ignore else */
if (moduleConfigs.hasOwnProperty(module)) { if (moduleConfigs.hasOwnProperty(module)) {
var plugins = moduleConfigs[module].plugins; result += get_config_of_plugins(moduleConfigs[module].plugins);
for (var plugin in plugins) {
if (plugins.hasOwnProperty(plugin)) {
var config = plugins[plugin];
if (config.enabled && !config.err && config.config) {
result += "\n<!-- --- [red-plugin:"+config.id+"] --- -->\n";
result += config.config;
}
}
}
} }
} }
pluginConfigCache[lang] = result; pluginConfigCache[lang] = result;
} }
return pluginConfigCache[lang]; 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<!-- --- [red-plugin:"+config.id+"] --- -->\n";
result += config.config;
}
}
}
return result;
}
function getPluginList() { function getPluginList() {
var list = []; var list = [];
var moduleConfigs = registry.getModuleList(); var moduleConfigs = registry.getModuleList();
@ -142,12 +162,51 @@ function exportPluginSettings(safeSettings) {
return 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 = { module.exports = {
init, init,
registerPlugin, registerPlugin,
getPlugin, getPlugin,
getPluginsByType, getPluginsByType,
getPluginConfigs, getPluginConfigs,
getPluginConfig,
getPluginList, getPluginList,
exportPluginSettings exportPluginSettings,
removeModule
} }

View File

@ -386,7 +386,8 @@ function getModuleInfo(module) {
local: moduleConfigs[module].local, local: moduleConfigs[module].local,
user: moduleConfigs[module].user, user: moduleConfigs[module].user,
path: moduleConfigs[module].path, path: moduleConfigs[module].path,
nodes: [] nodes: [],
plugins: []
}; };
if (moduleConfigs[module].dependencies) { if (moduleConfigs[module].dependencies) {
m.dependencies = moduleConfigs[module].dependencies; m.dependencies = moduleConfigs[module].dependencies;
@ -399,6 +400,14 @@ function getModuleInfo(module) {
nodeInfo.version = m.version; nodeInfo.version = m.version;
m.nodes.push(nodeInfo); 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; return m;
} else { } else {
return null; return null;

View File

@ -65,6 +65,25 @@ var api = module.exports = {
runtime.log.audit({event: "plugins.configs.get"}, opts.req); runtime.log.audit({event: "plugins.configs.get"}, opts.req);
return runtime.plugins.getPluginConfigs(opts.lang); 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<NodeInfo>} - 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 * Gets all registered module message catalogs
* @param {Object} opts * @param {Object} opts

View File

@ -173,7 +173,11 @@ function installModule(module,version,url) {
if (info.pending_version) { if (info.pending_version) {
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:info.name,version:info.pending_version}}); events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:info.name,version:info.pending_version}});
} else { } 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; return info;
}); });

View File

@ -7,5 +7,6 @@ module.exports = {
getPluginsByType: registry.getPluginsByType, getPluginsByType: registry.getPluginsByType,
getPluginList: registry.getPluginList, getPluginList: registry.getPluginList,
getPluginConfigs: registry.getPluginConfigs, getPluginConfigs: registry.getPluginConfigs,
getPluginConfig: registry.getPluginConfig,
exportPluginSettings: registry.exportPluginSettings exportPluginSettings: registry.exportPluginSettings
} }

View File

@ -25,6 +25,7 @@
"removing-modules": "Removing modules from config", "removing-modules": "Removing modules from config",
"added-types": "Added node types:", "added-types": "Added node types:",
"removed-types": "Removed node types:", "removed-types": "Removed node types:",
"removed-plugins": "Removed plugins:",
"install": { "install": {
"invalid": "Invalid module name", "invalid": "Invalid module name",
"installing": "Installing module: __name__, version: __version__", "installing": "Installing module: __name__, version: __version__",