gui plugin updates partial

Ryan Blenis committed Oct 30, 2019 at 04:17 UTC c57ac19cbae88c420eeed01c56eabf55f6c7dd1b
6 files changed +242 -3
db.js
+23
@@ -442,6 +442,9 @@ module.exports.CreateDB = function (parent, func) {
442 });
443 }
444 });
445 +
446 + // Setup plugin info collection
447 + obj.pluginsfile = db.collection('plugins');
448
449 setupFunctions(func); // Completed setup of MongoDB
450 });
@@ -543,6 +546,9 @@ module.exports.CreateDB = function (parent, func) {
546 });
547 }
548 });
549 +
550 + // Setup plugin info collection
551 + obj.pluginsfile = db.collection('plugins');
552
553 setupFunctions(func); // Completed setup of MongoJS
554 } else {
@@ -604,6 +610,10 @@ module.exports.CreateDB = function (parent, func) {
610 obj.serverstatsfile.ensureIndex({ fieldName: 'time', expireAfterSeconds: 60 * 60 * 24 * 30 }); // Limit the server stats log to 30 days (Seconds * Minutes * Hours * Days)
611 obj.serverstatsfile.ensureIndex({ fieldName: 'expire', expireAfterSeconds: 0 }); // Auto-expire events
612
613 + // Setup plugin info collection
614 + obj.pluginsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-plugins.db'), autoload: true });
615 + obj.pluginsfile.persistence.setAutocompactionInterval(36000);
616 +
617 setupFunctions(func); // Completed setup of NeDB
618 }
619
@@ -741,6 +751,19 @@ module.exports.CreateDB = function (parent, func) {
751 func(r);
752 });
753 }
754 +
755 + // Add a plugin
756 + obj.addPlugin = function (plugin) { obj.pluginsfile.insertOne(plugin); };
757 +
758 + // Get all plugins
759 + obj.getPlugins = function (func) { obj.pluginsfile.find().sort({ name: 1 }).toArray(func); };
760 +
761 + // Get plugin
762 + obj.getPlugin = function (id, func) { obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).toArray(func); };
763 +
764 + // Delete plugin
765 + obj.deletePlugin = function (id) { obj.pluginsfile.deleteOne({ _id: id }); };
766 +
767 } else {
768 // Database actions on the main collection (NeDB and MongoJS)
769 obj.Set = function (data, func) {
meshuser.js
+19
@@ -3102,6 +3102,25 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3102 }
3103 break;
3104 }
3105 + case 'plugins': {
3106 + if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
3107 + parent.db.getPlugins(function(err, docs) {
3108 + try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
3109 + });
3110 + break;
3111 + }
3112 + case 'addplugin': {
3113 + // @Ylianst - Do we need a new permission here?
3114 + if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
3115 + parent.parent.pluginHandler.addPlugin(command.url);
3116 + break;
3117 + }
3118 + case 'removeplugin': {
3119 + // @Ylianst - Do we need a new permission here?
3120 + if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
3121 + parent.parent.pluginHandler.removePlugin(command.id);
3122 + break;
3123 + }
3124 case 'plugin': {
3125 if (parent.parent.pluginHandler == null) break; // If the plugin's are not supported, reject this command.
3126 command.userid = user._id;
pluginHandler.js
+89 -1
@@ -71,6 +71,13 @@ module.exports.pluginHandler = function (parent) {
71 for (const i of pages) { i.style.display = 'none'; }
72 QV(id, true);
73 };
74 + obj.addPluginEx = function() {
75 + meshserver.send({ action: 'addplugin', url: Q('pluginurlinput').value});
76 + };
77 + obj.addPluginDlg = function() {
78 + setDialogMode(2, "Plugin URL", 3, obj.addPluginEx, '<input type=text id=pluginurlinput style=width:100% />');
79 + focusTextBox('pluginurlinput');
80 + };
81 return obj; };`;
82 return str;
83 }
@@ -152,7 +159,88 @@ module.exports.pluginHandler = function (parent) {
159 }
160 }
161 return panel;
155 - }
162 + };
163 +
164 + obj.isValidConfig = function(conf, url) { // check for the required attributes
165 + var isValid = true;
166 + if (!(
167 + typeof conf.name == 'string'
168 + && typeof conf.version == 'string'
169 + && typeof conf.author == 'string'
170 + && typeof conf.description == 'string'
171 + && typeof conf.hasAdminPanel == 'boolean'
172 + && typeof conf.homepage == 'string'
173 + && typeof conf.changelogUrl == 'string'
174 + && typeof conf.configUrl == 'string'
175 + && typeof conf.repository == 'object'
176 + && typeof conf.repository.type == 'string'
177 + && typeof conf.repository.url == 'string'
178 + && typeof conf.meshCentralCompat == 'string'
179 + // && conf.configUrl == url // make sure we're loading a plugin from its desired config
180 + )) isValid = false;
181 + // more checks here?
182 + return isValid;
183 + };
184 +
185 + obj.addPlugin = function(url) {
186 + var https = require('https');
187 + //var pit = obj.path.join(obj.pluginPath, )
188 +
189 + https.get(url, function(res) {
190 + var configStr = '';
191 + res.on('data', function(chunk){
192 + configStr += chunk;
193 + });
194 + res.on('end', function(){
195 + if (configStr[0] == '{') {
196 + try {
197 + var pluginConfig = JSON.parse(configStr);
198 + if (obj.isValidConfig(pluginConfig, url)) {
199 + // add to database
200 + // we met the requirements of a valid config, but in case there's extra, let's rebuild for what we need
201 + parent.db.addPlugin({
202 + "name": pluginConfig.name,
203 + "version": pluginConfig.version,
204 + "description": pluginConfig.description,
205 + "hasAdminPanel": pluginConfig.hasAdminPanel,
206 + "homepage": pluginConfig.homepage,
207 + "changelogUrl": pluginConfig.changelogUrl,
208 + "configUrl": pluginConfig.configUrl,
209 + "repository": {
210 + "type": pluginConfig.repository.type,
211 + "url": pluginConfig.repository.url
212 + },
213 + "meshCentralCompat": pluginConfig.meshCentralCompat,
214 + "status": 0 // 0: disabled, 1: enabled
215 + });
216 + parent.db.getPlugins(function(err, docs){
217 + var targets = ['*', 'server-users'];
218 + parent.DispatchEvent(targets, obj, { action: 'updatePluginList', list: docs });
219 +
220 + })
221 + } else {
222 + // @TODO return error to user
223 + }
224 +
225 + } catch (e) { console.log('Error processing addPlugin request. Check that you have valid JSON.'); }
226 + }
227 + });
228
229 + }).on('error', function(e) {
230 + console.log("Got error: " + e.message);
231 + });
232 + /* const file = fs.createWriteStream("file.jpg");
233 + const request = http.get("http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg", function(response) {
234 + response.pipe(file);
235 + }); */
236 + };
237 +
238 + obj.getPlugins = function() {
239 + var p = parent.db.getPlugins();
240 + if (typeof p == 'undefined' || p.length == 0) {
241 + return null;
242 + }
243 + return p;
244 + }
245 return obj;
246 };
\ No newline at end of file
public/images/plus32.png
Binary files /dev/null and b/public/images/plus32.png differ
public/styles/style.css
+47
@@ -2563,4 +2563,51 @@ a {
2563 padding: 3px;
2564 border-radius: 3px;
2565 background-color: #DDD;
2566 +}
2567 +
2568 +#p7tbl {
2569 + width: 100%;
2570 + border-collapse: collapse;
2571 +}
2572 +
2573 +#p7tbl th, #p7tbl td {
2574 + text-align: left;
2575 + padding: 12px;
2576 +}
2577 +
2578 +#p7tbl tr:nth-child(n+2):nth-child(odd) {
2579 + background-color: #cfeeff;
2580 +}
2581 +
2582 +#p7tbl .chName {
2583 + width: 20%;
2584 +}
2585 +
2586 +#p7tbl .chDescription {
2587 + width: 40%;
2588 +}
2589 +
2590 +#p7tbl .chSite {
2591 + width: 10%;
2592 +}
2593 +
2594 +#p7tbl .chVersion {
2595 + width: 10%;
2596 +}
2597 +
2598 +#p7tbl .chStatus {
2599 + width: 10%;
2600 +}
2601 +
2602 +#p7tbl .chAction {
2603 + width: 10%;
2604 +}
2605 +
2606 +#addPlugin {
2607 + background-image: url(../images/plus32.png);
2608 + width: 32px;
2609 + height: 32px;
2610 + float: right;
2611 + cursor: pointer;
2612 + margin-right: 12px;
2613 }
\ No newline at end of file
views/default.handlebars
+64 -2
@@ -88,6 +88,9 @@
88 <div id=LeftMenuMyServer tabindex=0 class="lbbutton" style="display:none" title="My Server" onclick=go(6,event) onkeypress="if (event.key=='Enter') { go(6); }">
89 <div class="lb6"></div>
90 </div>
91 + <div id=LeftMenuMyPlugins tabindex=0 class="lbbutton" style="display:none" title="My Plugins" onclick=go(7,event) onkeypress="if (event.key=='Enter') { go(7); }">
92 + <div class="lb7"></div>
93 + </div>
94 </div>
95 <div id=topbar class=noselect>
96 <div>
@@ -109,6 +112,7 @@
112 <td tabindex=0 id=MainMenuMyFiles class="topbar_td style3x" onclick=go(5,event) onkeypress="if (event.key == 'Enter') go(5)">My Files</td>
113 <td tabindex=0 id=MainMenuMyUsers class="topbar_td style3x" onclick=go(4,event) onkeypress="if (event.key == 'Enter') go(4)">My Users</td>
114 <td tabindex=0 id=MainMenuMyServer class="topbar_td style3x" onclick=go(6,event) onkeypress="if (event.key == 'Enter') go(6)">My Server</td>
115 + <td tabindex=0 id=MainMenuMyPlugins class="topbar_td style3x" onclick=go(7,event) onkeypress="if (event.key == 'Enter') go(7)">My Plugins</td>
116 <td class="topbar_td_end style3">&nbsp;</td>
117 </tr>
118 </table>
@@ -405,6 +409,13 @@
409 <div id="serverStatsTable"></div>
410 </div>
411 </div>
412 + <div id=p7 style="display:none">
413 + <h1>My Plugins</h1>
414 + <div id="addPlugin" onclick="return pluginHandler.addPluginDlg();"></div>
415 + <table id="p7tbl">
416 + <tr><th class="chName">Name</th><th class="chDescription">Description</th><th class="chSite">Link</th><th class="chVersion">Version</th><th class="chStatus">Status</th><th class="chAction">Action</th></tr>
417 + </table>
418 + </div>
419 <div id=p10 style="display:none">
420 <table style="width:100%" cellpadding="0" cellspacing="0">
421 <tr>
@@ -1042,6 +1053,7 @@
1053 var pluginHandlerBuilder = {{{pluginHandler}}};
1054 var pluginHandler = null;
1055 if (pluginHandlerBuilder != null) { pluginHandler = new pluginHandlerBuilder(); }
1056 + var installedPluginList = null;
1057
1058 // Console Message Display Timers
1059 var p11DeskConsoleMsgTimer = null;
@@ -1295,6 +1307,7 @@
1307 // Fetch list of meshes, nodes, files
1308 meshserver.send({ action: 'meshes' });
1309 meshserver.send({ action: 'nodes', id: '{{currentNode}}' });
1310 + meshserver.send({ action: 'plugins' });
1311 if ('{{currentNode}}' == '') { meshserver.send({ action: 'files' }); }
1312 if ('{{viewmode}}' == '') { go(1); }
1313 authCookieRenewTimer = setInterval(function () { meshserver.send({ action: 'authcookie' }); }, 1800000); // Request a cookie refresh every 30 minutes.
@@ -1337,6 +1350,7 @@
1350 QV('p2ServerActionsVersion', siteRights & 16);
1351 QV('MainMenuMyFiles', siteRights & 8);
1352 QV('LeftMenuMyFiles', siteRights & 8);
1353 + QV('MainMenuMyPlugins', (pluginHandler != null));
1354 if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { setDialogMode(0); go(1); }
1355 if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); }
1356
@@ -2284,6 +2298,12 @@
2298 //console.log(message.msg);
2299 break;
2300 }
2301 + case 'updatePluginList': {
2302 + // @Ylianst - Do we need a rights check here?
2303 + installedPluginList = message.event.list;
2304 + updatePluginList();
2305 + break;
2306 + }
2307 default:
2308 //console.log('Unknown message.event.action', message.event.action);
2309 break;
@@ -2337,6 +2357,11 @@
2357 QH('p0span', message.msg);
2358 break;
2359 }
2360 + case 'updatePluginList': {
2361 + installedPluginList = message.list;
2362 + updatePluginList();
2363 + break;
2364 + }
2365 case 'plugin': {
2366 if ((pluginHandler == null) || (typeof message.plugin != 'string')) break;
2367 try { pluginHandler[message.plugin][message.method](server, message); } catch (e) { console.log('Error loading plugin handler ('+ e + ')'); }
@@ -9250,6 +9275,9 @@
9275
9276 // Remove top bar selection
9277 var mainBarItems = ['MainMenuMyDevices', 'MainMenuMyAccount', 'MainMenuMyEvents', 'MainMenuMyFiles', 'MainMenuMyUsers', 'MainMenuMyServer'];
9278 + if (pluginHandler != null) {
9279 + mainBarItems.push('MainMenuMyPlugins');
9280 + }
9281 for (var i in mainBarItems) {
9282 QC(mainBarItems[i]).remove('fullselect');
9283 QC(mainBarItems[i]).remove('semiselect');
@@ -9257,6 +9285,9 @@
9285
9286 // Remove left bar selection
9287 var leftBarItems = ['LeftMenuMyDevices', 'LeftMenuMyAccount', 'LeftMenuMyEvents', 'LeftMenuMyFiles', 'LeftMenuMyUsers', 'LeftMenuMyServer'];
9288 + if (pluginHandler != null) {
9289 + leftBarItems.push('LeftMenuMyPlugins');
9290 + }
9291 for (var i in leftBarItems) {
9292 QC(leftBarItems[i]).remove('lbbuttonsel');
9293 QC(leftBarItems[i]).remove('lbbuttonsel2');
@@ -9289,7 +9320,11 @@
9320 // My Server
9321 if ((x == 6) || (x == 115)) QC('MainMenuMyServer').add(mainMenuActiveClass);
9322 if ((x == 6) || (x == 115) || (x == 40)) QC('LeftMenuMyServer').add(leftMenuActiveClass);
9292 -
9323 +
9324 + // My Plugins
9325 + if (x == 7) QC('MainMenuMyPlugins').add(mainMenuActiveClass);
9326 + if (x == 7) QC('LeftMenuMyPlugins').add(leftMenuActiveClass);
9327 +
9328 // column_l max-height
9329 if (webPageStackMenu && (x >= 10)) { QC('column_l').add('room4submenu'); } else { QC('column_l').remove('room4submenu'); }
9330
@@ -9333,7 +9368,34 @@
9368 document.title = decodeURIComponent('{{{extitle}}}');
9369 }
9370 }
9336 -
9371 +
9372 + function updatePluginList() {
9373 + if (installedPluginList.length) {
9374 + var tr = Q('p7tbl').querySelectorAll(".p7tblRow");
9375 + if (tr.length) {
9376 + for (const i in Object.values(tr)) {
9377 + tr[i].parentNode.removeChild(tr[i]);
9378 + }
9379 + }
9380 + var statusMap = {
9381 + 0: 'Disabled',
9382 + 1: 'Installed'
9383 + }
9384 + var tbl = Q('p7tbl');
9385 + installedPluginList.forEach(function(p){
9386 + if (p.hasAdminPanel == true) {
9387 + p.name = `<a onclick="return goPlugin('${p._id}');">${p.name}</a>`;
9388 + }
9389 + p.status = statusMap[p.status];
9390 + p.actions = 'TODO'; // Install / Upgrade / Disable / Delete
9391 + let tpl = `<td>${p.name}</td><td>${p.description}</td><td><a href="${p.homepage}" target="_blank">Homepage</a></td><td>${p.version}</td><td>${p.status}</td><td>${p.actions}</td>`;
9392 + let tr = tbl.insertRow(-1);
9393 + tr.innerHTML = tpl;
9394 + tr.classList.add('p7tblRow');
9395 + });
9396 + }
9397 +
9398 + }
9399 // Generic methods
9400 function joinPaths() { var x = []; for (var i in arguments) { var w = arguments[i]; if ((w != null) && (w != '')) { while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); } while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } x.push(w); } } return x.join('/'); }
9401 function putstore(name, val) {