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"> </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) {