diff --git a/db.js b/db.js index 2e882e74..e4861996 100644 --- a/db.js +++ b/db.js @@ -442,6 +442,9 @@ module.exports.CreateDB = function (parent, func) { }); } }); + + // Setup plugin info collection + obj.pluginsfile = db.collection('plugins'); setupFunctions(func); // Completed setup of MongoDB }); @@ -543,6 +546,9 @@ module.exports.CreateDB = function (parent, func) { }); } }); + + // Setup plugin info collection + obj.pluginsfile = db.collection('plugins'); setupFunctions(func); // Completed setup of MongoJS } else { @@ -604,6 +610,10 @@ module.exports.CreateDB = function (parent, func) { obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 30 }); // Limit the server stats log to 30 days (Seconds * Minutes * Hours * Days) obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events + // Setup plugin info collection + obj.pluginsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-plugins.db'), autoload: true }); + obj.pluginsfile.persistence.setAutocompactionInterval(36000); + setupFunctions(func); // Completed setup of NeDB } @@ -741,6 +751,19 @@ module.exports.CreateDB = function (parent, func) { func(r); }); } + + // Add a plugin + obj.addPlugin = function (plugin) { obj.pluginsfile.insertOne(plugin); }; + + // Get all plugins + obj.getPlugins = function (func) { obj.pluginsfile.find().sort({ name: 1 }).toArray(func); }; + + // Get plugin + obj.getPlugin = function (id, func) { obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).toArray(func); }; + + // Delete plugin + obj.deletePlugin = function (id) { obj.pluginsfile.deleteOne({ _id: id }); }; + } else { // Database actions on the main collection (NeDB and MongoJS) obj.Set = function (data, func) { diff --git a/meshuser.js b/meshuser.js index 8d817960..9f59d764 100644 --- a/meshuser.js +++ b/meshuser.js @@ -3102,6 +3102,25 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } break; } + case 'plugins': { + if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled + parent.db.getPlugins(function(err, docs) { + try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { } + }); + break; + } + case 'addplugin': { + // @Ylianst - Do we need a new permission here? + if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled + parent.parent.pluginHandler.addPlugin(command.url); + break; + } + case 'removeplugin': { + // @Ylianst - Do we need a new permission here? + if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled + parent.parent.pluginHandler.removePlugin(command.id); + break; + } case 'plugin': { if (parent.parent.pluginHandler == null) break; // If the plugin's are not supported, reject this command. command.userid = user._id; diff --git a/pluginHandler.js b/pluginHandler.js index cb96ec42..5b328ee6 100644 --- a/pluginHandler.js +++ b/pluginHandler.js @@ -71,6 +71,13 @@ module.exports.pluginHandler = function (parent) { for (const i of pages) { i.style.display = 'none'; } QV(id, true); }; + obj.addPluginEx = function() { + meshserver.send({ action: 'addplugin', url: Q('pluginurlinput').value}); + }; + obj.addPluginDlg = function() { + setDialogMode(2, "Plugin URL", 3, obj.addPluginEx, ''); + focusTextBox('pluginurlinput'); + }; return obj; };`; return str; } @@ -152,7 +159,88 @@ module.exports.pluginHandler = function (parent) { } } return panel; - } + }; + + obj.isValidConfig = function(conf, url) { // check for the required attributes + var isValid = true; + if (!( + typeof conf.name == 'string' + && typeof conf.version == 'string' + && typeof conf.author == 'string' + && typeof conf.description == 'string' + && typeof conf.hasAdminPanel == 'boolean' + && typeof conf.homepage == 'string' + && typeof conf.changelogUrl == 'string' + && typeof conf.configUrl == 'string' + && typeof conf.repository == 'object' + && typeof conf.repository.type == 'string' + && typeof conf.repository.url == 'string' + && typeof conf.meshCentralCompat == 'string' + // && conf.configUrl == url // make sure we're loading a plugin from its desired config + )) isValid = false; + // more checks here? + return isValid; + }; + + obj.addPlugin = function(url) { + var https = require('https'); + //var pit = obj.path.join(obj.pluginPath, ) + https.get(url, function(res) { + var configStr = ''; + res.on('data', function(chunk){ + configStr += chunk; + }); + res.on('end', function(){ + if (configStr[0] == '{') { + try { + var pluginConfig = JSON.parse(configStr); + if (obj.isValidConfig(pluginConfig, url)) { + // add to database + // we met the requirements of a valid config, but in case there's extra, let's rebuild for what we need + parent.db.addPlugin({ + "name": pluginConfig.name, + "version": pluginConfig.version, + "description": pluginConfig.description, + "hasAdminPanel": pluginConfig.hasAdminPanel, + "homepage": pluginConfig.homepage, + "changelogUrl": pluginConfig.changelogUrl, + "configUrl": pluginConfig.configUrl, + "repository": { + "type": pluginConfig.repository.type, + "url": pluginConfig.repository.url + }, + "meshCentralCompat": pluginConfig.meshCentralCompat, + "status": 0 // 0: disabled, 1: enabled + }); + parent.db.getPlugins(function(err, docs){ + var targets = ['*', 'server-users']; + parent.DispatchEvent(targets, obj, { action: 'updatePluginList', list: docs }); + + }) + } else { + // @TODO return error to user + } + + } catch (e) { console.log('Error processing addPlugin request. Check that you have valid JSON.'); } + } + }); + + }).on('error', function(e) { + console.log("Got error: " + e.message); + }); + /* const file = fs.createWriteStream("file.jpg"); + const request = http.get("http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg", function(response) { + response.pipe(file); + }); */ + }; + + obj.getPlugins = function() { + var p = parent.db.getPlugins(); + if (typeof p == 'undefined' || p.length == 0) { + return null; + } + return p; + } return obj; }; \ No newline at end of file diff --git a/public/images/plus32.png b/public/images/plus32.png new file mode 100644 index 00000000..f2e17cce Binary files /dev/null and b/public/images/plus32.png differ diff --git a/public/styles/style.css b/public/styles/style.css index 8eb985b6..ebd1751c 100644 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -2563,4 +2563,51 @@ a { padding: 3px; border-radius: 3px; background-color: #DDD; +} + +#p7tbl { + width: 100%; + border-collapse: collapse; +} + +#p7tbl th, #p7tbl td { + text-align: left; + padding: 12px; +} + +#p7tbl tr:nth-child(n+2):nth-child(odd) { + background-color: #cfeeff; +} + +#p7tbl .chName { + width: 20%; +} + +#p7tbl .chDescription { + width: 40%; +} + +#p7tbl .chSite { + width: 10%; +} + +#p7tbl .chVersion { + width: 10%; +} + +#p7tbl .chStatus { + width: 10%; +} + +#p7tbl .chAction { + width: 10%; +} + +#addPlugin { + background-image: url(../images/plus32.png); + width: 32px; + height: 32px; + float: right; + cursor: pointer; + margin-right: 12px; } \ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 5793477b..9eb3687b 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -88,6 +88,9 @@ +
@@ -109,6 +112,7 @@ My Files My Users My Server + My Plugins   @@ -405,6 +409,13 @@
+