gui plugin admin updates part 2
Ryan Blenis committed
Nov 1, 2019 at 16:49 UTC
0516b0afd336fefe5c74180e75b2e42d7510b135
6 files changed
+353
-96
db.js
+5
-3
@@ -753,16 +753,18 @@ module.exports.CreateDB = function (parent, func) {
753
}
754
755
// Add a plugin
756
- obj.addPlugin = function (plugin) { obj.pluginsfile.insertOne(plugin); };
756
+ obj.addPlugin = function (plugin, func) { obj.pluginsfile.insertOne(plugin, func); };
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); };
762
+ obj.getPlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).toArray(func); };
763
764
// Delete plugin
765
- obj.deletePlugin = function (id) { obj.pluginsfile.deleteOne({ _id: id }); };
765
+ obj.deletePlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.deleteOne({ _id: id }, func); };
766
+
767
+ obj.setPluginStatus = function(id, status, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.updateOne({ _id: id }, { $set: {status: status } }, func); };
768
769
} else {
770
// Database actions on the main collection (NeDB and MongoJS)
meshagent.js
+1
-2
@@ -1287,8 +1287,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
1287
case 'plugin': {
1288
if ((parent.parent.pluginHandler == null) || (typeof command.plugin != 'string')) break;
1289
try {
1290
- var pluginHandler = require('./pluginHandler.js').pluginHandler(parent.parent);
1291
- pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
1290
+ parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
1291
} catch (e) {
1292
console.log('Error loading plugin handler (' + e + ')');
1293
}
meshuser.js
+34
-6
@@ -3103,22 +3103,51 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3103
break;
3104
}
3105
case 'plugins': {
3106
- if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
3106
+ // @Ylianst - Do we need a new permission set here?
3107
+ if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin with plugins enabled
3108
parent.db.getPlugins(function(err, docs) {
3109
try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
3110
});
3111
break;
3112
}
3113
+ case 'pluginLatestCheck': {
3114
+ if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin with plugins enabled
3115
+ parent.parent.pluginHandler.getPluginLatest(function(latest) {
3116
+ try { ws.send(JSON.stringify({ action: 'pluginVersionsAvailable', list: latest })); } catch (ex) { }
3117
+ });
3118
+ break;
3119
+ }
3120
case 'addplugin': {
3113
- // @Ylianst - Do we need a new permission here?
3121
if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
3122
parent.parent.pluginHandler.addPlugin(command.url);
3123
break;
3124
}
3125
+ case 'installplugin': {
3126
+ if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
3127
+ parent.parent.pluginHandler.installPlugin(command.id, function(){
3128
+ parent.parent.updateMeshCore();
3129
+ parent.db.getPlugins(function(err, docs) {
3130
+ try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
3131
+ });
3132
+ });
3133
+ break;
3134
+ }
3135
+ case 'disableplugin': {
3136
+ if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
3137
+ parent.parent.pluginHandler.disablePlugin(command.id, function(){
3138
+ parent.db.getPlugins(function(err, docs) {
3139
+ try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
3140
+ });
3141
+ });
3142
+ break;
3143
+ }
3144
case 'removeplugin': {
3119
- // @Ylianst - Do we need a new permission here?
3145
if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
3121
- parent.parent.pluginHandler.removePlugin(command.id);
3146
+ parent.parent.pluginHandler.removePlugin(command.id, function(){
3147
+ parent.db.getPlugins(function(err, docs) {
3148
+ try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
3149
+ });
3150
+ });
3151
break;
3152
}
3153
case 'plugin': {
@@ -3128,8 +3157,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
3157
routeCommandToNode(command);
3158
} else {
3159
try {
3131
- var pluginHandler = require('./pluginHandler.js').pluginHandler(parent.parent);
3132
- pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
3160
+ parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
3161
} catch (e) { console.log('Error loading plugin handler (' + e + ')'); }
3162
}
3163
break;
pluginHandler.js
+203
-74
@@ -23,34 +23,52 @@ module.exports.pluginHandler = function (parent) {
23
obj.pluginPath = obj.parent.path.join(obj.parent.datapath, 'plugins');
24
obj.plugins = {};
25
obj.exports = {};
26
- obj.loadList = obj.parent.config.settings.plugins.list;
27
-
26
+ obj.loadList = obj.parent.config.settings.plugins.list; // For local development / manual install, not from DB
27
+
28
if (typeof obj.loadList != 'object') {
29
obj.loadList = {};
30
- console.log('Plugin list not specified, please fix configuration file.');
31
- return null;
32
- }
33
-
34
- obj.loadList.forEach(function (plugin, index) {
35
- if (obj.fs.existsSync(obj.pluginPath + '/' + plugin)) {
36
- try {
37
- obj.plugins[plugin] = require(obj.pluginPath + '/' + plugin + '/' + plugin + '.js')[plugin](obj);
38
- obj.exports[plugin] = obj.plugins[plugin].exports;
39
- } catch (e) {
40
- console.log("Error loading plugin: " + plugin + " (" + e + "). It has been disabled.", e.stack);
30
+ parent.db.getPlugins(function(err, plugins){
31
+ plugins.forEach(function(plugin){
32
+ if (plugin.status != 1) return;
33
+ if (obj.fs.existsSync(obj.pluginPath + '/' + plugin.shortName)) {
34
+ try {
35
+ obj.plugins[plugin.shortName] = require(obj.pluginPath + '/' + plugin.shortName + '/' + plugin.shortName + '.js')[plugin.shortName](obj);
36
+ obj.exports[plugin.shortName] = obj.plugins[plugin.shortName].exports;
37
+ } catch (e) {
38
+ console.log("Error loading plugin: " + plugin.shortName + " (" + e + "). It has been disabled.", e.stack);
39
+ }
40
+ }
41
+ obj.parent.updateMeshCore(); // db calls are delayed, lets inject here once we're ready
42
+ });
43
+ });
44
+ } else {
45
+ obj.loadList.forEach(function (plugin, index) {
46
+ if (obj.fs.existsSync(obj.pluginPath + '/' + plugin)) {
47
+ try {
48
+ obj.plugins[plugin] = require(obj.pluginPath + '/' + plugin + '/' + plugin + '.js')[plugin](obj);
49
+ obj.exports[plugin] = obj.plugins[plugin].exports;
50
+ } catch (e) {
51
+ console.log("Error loading plugin: " + plugin + " (" + e + "). It has been disabled.", e.stack);
52
+ }
53
}
54
+ });
55
+ }
56
+
57
+ obj.prepExportsForPlugin = function(plugin) {
58
+ var str = '';
59
+ str += ' obj.' + plugin + ' = {};\r\n';
60
+ for (const l of Object.values(obj.exports[plugin])) {
61
+ str += ' obj.' + plugin + '.' + l + ' = ' + obj.plugins[plugin][l].toString() + '\r\n';
62
}
43
- });
44
-
63
+ return str;
64
+ };
65
+
66
obj.prepExports = function () {
67
var str = 'function() {\r\n';
68
str += ' var obj = {};\r\n';
69
70
for (const p of Object.keys(obj.plugins)) {
50
- str += ' obj.' + p + ' = {};\r\n';
51
- for (const l of Object.values(obj.exports[p])) {
52
- str += ' obj.' + p + '.' + l + ' = ' + obj.plugins[p][l].toString() + '\r\n';
53
- }
71
+ str += obj.prepExportsForPlugin(p);
72
}
73
74
str += `obj.onDeviceRefeshEnd = function(nodeid, panel, refresh, event) {
@@ -75,7 +93,7 @@ module.exports.pluginHandler = function (parent) {
93
meshserver.send({ action: 'addplugin', url: Q('pluginurlinput').value});
94
};
95
obj.addPluginDlg = function() {
78
- setDialogMode(2, "Plugin URL", 3, obj.addPluginEx, '<input type=text id=pluginurlinput style=width:100% />');
96
+ setDialogMode(2, "Plugin Config URL", 3, obj.addPluginEx, '<input type=text id=pluginurlinput style=width:100% />');
97
focusTextBox('pluginurlinput');
98
};
99
return obj; };`;
@@ -165,6 +183,7 @@ module.exports.pluginHandler = function (parent) {
183
var isValid = true;
184
if (!(
185
typeof conf.name == 'string'
186
+ && typeof conf.shortName == 'string'
187
&& typeof conf.version == 'string'
188
&& typeof conf.author == 'string'
189
&& typeof conf.description == 'string'
@@ -179,68 +198,178 @@ module.exports.pluginHandler = function (parent) {
198
// && conf.configUrl == url // make sure we're loading a plugin from its desired config
199
)) isValid = false;
200
// more checks here?
201
+ if (conf.repository.type == 'git') {
202
+ if (typeof conf.downloadUrl != 'string') isValid = false;
203
+ }
204
return isValid;
205
};
206
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;
207
+ obj.getPlugins = function(func) {
208
+ var plugins = parent.db.getPlugins();
209
+ if (typeof plugins == 'undefined' || plugins.length == 0) {
210
+ return null;
211
+ }
212
+
213
+ plugins.forEach(function(p, x){
214
+ // check semantic version
215
+ console.log('FOREACH PLUGIN', p, x);
216
+ // callbacks to new versions
217
+
218
});
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
219
+
220
+ return plugins;
221
+ }
222
+
223
+ obj.getPluginConfig = function(configUrl, func) {
224
+ var https = require('https');
225
+ if (configUrl.indexOf('://') === -1) return; // @TODO error here
226
+ https.get(configUrl, function(res) {
227
+ var configStr = '';
228
+ res.on('data', function(chunk){
229
+ configStr += chunk;
230
+ });
231
+ res.on('end', function(){
232
+ if (configStr[0] == '{') { // let's be sure we're JSON
233
+ try {
234
+ var pluginConfig = JSON.parse(configStr);
235
+ if (Array.isArray(pluginConfig) && pluginConfig.length == 1) pluginConfig = pluginConfig[0];
236
+ if (obj.isValidConfig(pluginConfig, configUrl)) {
237
+ func(pluginConfig);
238
+ }
239
+
240
+ } catch (e) { console.log('Error getting plugin config. Check that you have valid JSON.', e.stack); }
241
+ }
242
+ });
243
+
244
+ }).on('error', function(e) {
245
+ console.log("Error getting plugin config. Check that the URL is correct.: " + e.message);
246
+ });
247
+ };
248
+
249
+ obj.getPluginLatest = function(func) {
250
+ parent.db.getPlugins(function(err, plugins){
251
+ plugins.forEach(function(curconf){
252
+ obj.getPluginConfig(curconf.configUrl, function(newconf){
253
+ var s = require('semver');
254
+ func({
255
+ "id": curconf._id,
256
+ "installedVersion": curconf.version,
257
+ "version": newconf.version,
258
+ "hasUpdate": s.gt(newconf.version, curconf.version),
259
+ "meshCentralCompat": s.satisfies(s.coerce(parent.currentVer), newconf.meshCentralCompat),
260
+ "changelogUrl": curconf.changelogUrl,
261
+ "status": curconf.status
262
+ });
263
+ });
264
+ });
265
+ });
266
+
267
+ };
268
+
269
+ obj.addPlugin = function(url) {
270
+ obj.getPluginConfig(url, function(pluginConfig){
271
+ parent.db.addPlugin({
272
+ "name": pluginConfig.name,
273
+ "shortName": pluginConfig.shortName,
274
+ "version": pluginConfig.version,
275
+ "description": pluginConfig.description,
276
+ "hasAdminPanel": pluginConfig.hasAdminPanel,
277
+ "homepage": pluginConfig.homepage,
278
+ "changelogUrl": pluginConfig.changelogUrl,
279
+ "configUrl": pluginConfig.configUrl,
280
+ "downloadUrl": pluginConfig.downloadUrl,
281
+ "repository": {
282
+ "type": pluginConfig.repository.type,
283
+ "url": pluginConfig.repository.url
284
+ },
285
+ "meshCentralCompat": pluginConfig.meshCentralCompat,
286
+ "status": 0 // 0: disabled, 1: enabled
287
+ }, function() {
288
+ parent.db.getPlugins(function(err, docs){
289
+ var targets = ['*', 'server-users'];
290
+ parent.DispatchEvent(targets, obj, { action: 'updatePluginList', list: docs });
291
+
292
+ });
293
+ });
294
+ });
295
+ };
296
+
297
+ obj.installPlugin = function(id, func) {
298
+ parent.db.getPlugin(id, function(err, docs){
299
+ var http = require('https');
300
+ // the "id" would probably suffice, but is probably an sanitary issue, generate a random instead
301
+ var randId = Math.random().toString(32).replace('0.', '');
302
+ var fileName = obj.parent.path.join(require('os').tmpdir(), 'Plugin_'+randId+'.zip');
303
+ var plugin = docs[0];
304
+ if (plugin.repository.type == 'git') {
305
+ const file = obj.fs.createWriteStream(fileName);
306
+ var request = http.get(plugin.downloadUrl, function(response) {
307
+ response.pipe(file);
308
+ file.on('finish', function() {
309
+ file.close(function(){
310
+ var yauzl = require("yauzl");
311
+ if (!obj.fs.existsSync(obj.pluginPath)) {
312
+ obj.fs.mkdirSync(obj.pluginPath);
313
+ }
314
+ if (!obj.fs.existsSync(obj.parent.path.join(obj.pluginPath, plugin.shortName))) {
315
+ obj.fs.mkdirSync(obj.parent.path.join(obj.pluginPath, plugin.shortName));
316
+ }
317
+ yauzl.open(fileName, { lazyEntries: true }, function (err, zipfile) {
318
+ if (err) throw err;
319
+ zipfile.readEntry();
320
+ zipfile.on("entry", function (entry) {
321
+ let pluginPath = obj.parent.path.join(obj.pluginPath, plugin.shortName);
322
+ let pathReg = new RegExp(/(.*?\/)/);
323
+ if (process.platform == 'win32') pathReg = new RegExp(/(.*?\\/);
324
+ let filePath = obj.parent.path.join(pluginPath, entry.fileName.replace(pathReg, '')); // remove top level dir
325
+
326
+ if (/\/$/.test(entry.fileName)) { // dir
327
+ if (!obj.fs.existsSync(filePath))
328
+ obj.fs.mkdirSync(filePath);
329
+ zipfile.readEntry();
330
+ } else { // file
331
+ zipfile.openReadStream(entry, function (err, readStream) {
332
+ if (err) throw err;
333
+ readStream.on("end", function () { zipfile.readEntry(); });
334
+ readStream.pipe(obj.fs.createWriteStream(filePath));
335
+ });
336
+ }
337
+ });
338
+ zipfile.on("end", function () { setTimeout(function () {
339
+ obj.fs.unlinkSync(fileName);
340
+ parent.db.setPluginStatus(id, 1, func);
341
+ obj.plugins[plugin.shortName] = require(obj.pluginPath + '/' + plugin.shortName + '/' + plugin.shortName + '.js')[plugin.shortName](obj);
342
+ obj.exports[plugin.shortName] = obj.plugins[plugin.shortName].exports;
343
+ }); });
344
+ });
345
});
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.'); }
346
+ });
347
+ });
348
+ } else if (plugin.repository.type == 'npm') {
349
+ // @TODO npm install and symlink dirs (need a test plugin)
350
}
351
+
352
+
353
+ });
354
+
355
+
356
+ };
357
+
358
+ obj.disablePlugin = function(id, func) {
359
+ parent.db.setPluginStatus(id, 0, func);
360
+ };
361
+
362
+ obj.removePlugin = function(id, func) {
363
+ parent.db.getPlugin(id, function(err, docs){
364
+ var plugin = docs[0];
365
+ var rimraf = require("rimraf");
366
+ let pluginPath = obj.parent.path.join(obj.pluginPath, plugin.shortName);
367
+ rimraf.sync(pluginPath);
368
+ parent.db.deletePlugin(id, func);
369
+ delete obj.plugins[plugin.shortName];
370
+ obj.parent.updateMeshCore();
371
});
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
- }); */
372
};
373
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
- }
374
return obj;
375
};
\ No newline at end of file
public/styles/style.css
+19
-2
@@ -2584,14 +2584,18 @@ a {
2584
}
2585
2586
#p7tbl .chDescription {
2587
- width: 40%;
2587
+ width: 38%;
2588
}
2589
2590
#p7tbl .chSite {
2591
- width: 10%;
2591
+ width: 7%;
2592
}
2593
2594
#p7tbl .chVersion {
2595
+ width: 5%;
2596
+}
2597
+
2598
+#p7tbl .chUpgradeAvail {
2599
width: 10%;
2600
}
2601
@@ -2603,6 +2607,10 @@ a {
2607
width: 10%;
2608
}
2609
2610
+.pActDisable, .pActDelete, .pActInstall, .pActUpgrade {
2611
+ cursor: pointer;
2612
+}
2613
+
2614
#addPlugin {
2615
background-image: url(../images/plus32.png);
2616
width: 32px;
@@ -2610,4 +2618,13 @@ a {
2618
float: right;
2619
cursor: pointer;
2620
margin-right: 12px;
2621
+}
2622
+
2623
+#pluginRestartNotice {
2624
+ width: 40em;
2625
+ font-weight: bold;
2626
+ border: 1px solid red;
2627
+ text-align: center;
2628
+ padding: 14px;
2629
+ margin: 50px auto;
2630
}
\ No newline at end of file
views/default.handlebars
+91
-9
@@ -411,10 +411,11 @@
411
</div>
412
<div id=p7 style="display:none">
413
<h1>My Plugins</h1>
414
- <div id="addPlugin" onclick="return pluginHandler.addPluginDlg();"></div>
414
+ <div id="addPlugin" title="Add New Plugin" 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>
416
+ <tr><th class="chName">Name</th><th class="chDescription">Description</th><th class="chSite">Link</th><th class="chVersion">Version</th><th class="chUpgradeAvail">Latest Available</th><th class="chStatus">Status</th><th class="chAction">Action</th></tr>
417
</table>
418
+ <div id="pluginRestartNotice" style="display:none;"><div>Notice:</div> MeshCentral restart required to complete plugin changes.</div>
419
</div>
420
<div id=p10 style="display:none">
421
<table style="width:100%" cellpadding="0" cellspacing="0">
@@ -2362,6 +2363,27 @@
2363
updatePluginList();
2364
break;
2365
}
2366
+ case 'pluginVersionsAvailable': {
2367
+ if (pluginHandler == null) break;
2368
+ try {
2369
+ var td = Q('pluginRow-'+message.list.id).querySelectorAll(".pluginUpgradeAvailable");
2370
+ var sel = Q('pluginRow-'+message.list.id).querySelectorAll(".pluginAction > select");
2371
+ td = td[0];
2372
+ sel = sel[0];
2373
+ if (message.list.hasUpdate && message.list.status) {
2374
+ td.innerHTML = '<a title="View Changelog" target="_blank" href="' + message.list.changelogUrl + '">' + message.list.version + '</a>';
2375
+ if (sel.innerHTML.indexOf('Upgrade') === -1) {
2376
+ var option = document.createElement("option");
2377
+ option.value = "install"
2378
+ option.text = "Upgrade";
2379
+ sel.add(option);
2380
+ }
2381
+ } else {
2382
+ td.innerHTML = "Up to date";
2383
+ }
2384
+ } catch (e) { }
2385
+ break;
2386
+ }
2387
case 'plugin': {
2388
if ((pluginHandler == null) || (typeof message.plugin != 'string')) break;
2389
try { pluginHandler[message.plugin][message.method](server, message); } catch (e) { console.log('Error loading plugin handler ('+ e + ')'); }
@@ -9361,6 +9383,8 @@
9383
// Fetch the server timeline stats if needed
9384
if ((x == 40) && (serverTimelineStats == null)) { refreshServerTimelineStats(); }
9385
9386
+ if (x == 7) refreshPluginLatest();
9387
+
9388
// Update the web page title
9389
if ((currentNode) && (x >= 10) && (x < 20)) {
9390
document.title = decodeURIComponent('{{{extitle}}}') + ' - ' + currentNode.name + ' - ' + meshes[currentNode.meshid].name;
@@ -9378,24 +9402,82 @@
9402
}
9403
}
9404
var statusMap = {
9381
- 0: 'Disabled',
9382
- 1: 'Installed'
9383
- }
9405
+ 0: {
9406
+ "text": 'Disabled',
9407
+ "color": '858483'
9408
+ },
9409
+ 1: {
9410
+ "text": 'Installed',
9411
+ "color": '00ff00'
9412
+ }
9413
+ };
9414
+ var statusAvailability = {
9415
+ 0: {
9416
+ 'install': 'Install',
9417
+ 'delete': 'Delete'
9418
+ },
9419
+ 1: {
9420
+ 'disable': 'Disable'
9421
+ }
9422
+ };
9423
var tbl = Q('p7tbl');
9424
installedPluginList.forEach(function(p){
9425
if (p.hasAdminPanel == true) {
9426
p.name = `<a onclick="return goPlugin('${p._id}');">${p.name}</a>`;
9427
}
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>`;
9428
+ p.statusText = statusMap[p.status].text;
9429
+ p.statusColor = statusMap[p.status].color;
9430
+
9431
+ p.actions = '<select onchange="return pluginAction(this, \'' + p._id + '\');"><option value=""> --</option>';
9432
+ for (const [k, v] of Object.entries(statusAvailability[p.status])) {
9433
+ p.actions += '<option value="' + k + '">' + v + '</option>';
9434
+ }
9435
+ p.action += '</select>'
9436
+
9437
+ 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 class="pluginUpgradeAvailable">Checking...</td><td style="color: #${p.statusColor}">${p.statusText}</td><td class="pluginAction">${p.actions}</td>`;
9438
let tr = tbl.insertRow(-1);
9439
tr.innerHTML = tpl;
9440
tr.classList.add('p7tblRow');
9441
+ tr.setAttribute('data-id', p._id);
9442
+ tr.setAttribute('id', 'pluginRow-'+p._id);
9443
});
9444
+ } else {
9445
+ var tr = Q('p7tbl').querySelectorAll(".p7tblRow");
9446
+ for (const i in Object.values(tr)) {
9447
+ tr[i].parentNode.removeChild(tr[i]);
9448
+ }
9449
}
9397
-
9450
+ refreshPluginLatest();
9451
}
9452
+
9453
+ function refreshPluginLatest() {
9454
+ meshserver.send({ action: 'pluginLatestCheck' });
9455
+ }
9456
+
9457
+ function pluginActionEx() {
9458
+ var act = Q('lastPluginAct').value, id = Q('lastPluginId').value;
9459
+ switch(act) {
9460
+ case 'install': {
9461
+ meshserver.send({ "action": "installplugin", "id": id });
9462
+ break;
9463
+ }
9464
+ case 'delete': {
9465
+ meshserver.send({ "action": "removeplugin", "id": id });
9466
+ break;
9467
+ }
9468
+ case 'disable': {
9469
+ meshserver.send({ "action": "disableplugin", "id": id });
9470
+ break;
9471
+ }
9472
+ }
9473
+ QS('pluginRestartNotice').display = '';
9474
+ }
9475
+
9476
+ function pluginAction(elem, id) {
9477
+ setDialogMode(2, 'Plugin Action', 3, pluginActionEx, 'Are you sure you want to ' + elem.value + ' the plugin: ' + elem.parentNode.parentNode.firstChild.innerText+'<input id="lastPluginAct" type="hidden" value="' + elem.value + '" /><input id="lastPluginId" type="hidden" value="' + elem.parentNode.parentNode.getAttribute('data-id') + '" />');
9478
+ elem.value = '';
9479
+ }
9480
+
9481
// Generic methods
9482
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('/'); }
9483
function putstore(name, val) {