gui plugin admin updates part 2

This commit is contained in:
Ryan Blenis 2019-11-01 16:49:18 -04:00
parent f1ea4ae1b8
commit d9344fcb1a
6 changed files with 357 additions and 100 deletions

8
db.js
View File

@ -753,16 +753,18 @@ module.exports.CreateDB = function (parent, func) {
} }
// Add a plugin // Add a plugin
obj.addPlugin = function (plugin) { obj.pluginsfile.insertOne(plugin); }; obj.addPlugin = function (plugin, func) { obj.pluginsfile.insertOne(plugin, func); };
// Get all plugins // Get all plugins
obj.getPlugins = function (func) { obj.pluginsfile.find().sort({ name: 1 }).toArray(func); }; obj.getPlugins = function (func) { obj.pluginsfile.find().sort({ name: 1 }).toArray(func); };
// Get plugin // Get plugin
obj.getPlugin = function (id, func) { obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).toArray(func); }; obj.getPlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).toArray(func); };
// Delete plugin // Delete plugin
obj.deletePlugin = function (id) { obj.pluginsfile.deleteOne({ _id: id }); }; obj.deletePlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.deleteOne({ _id: id }, func); };
obj.setPluginStatus = function(id, status, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.updateOne({ _id: id }, { $set: {status: status } }, func); };
} else { } else {
// Database actions on the main collection (NeDB and MongoJS) // Database actions on the main collection (NeDB and MongoJS)

View File

@ -1287,8 +1287,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
case 'plugin': { case 'plugin': {
if ((parent.parent.pluginHandler == null) || (typeof command.plugin != 'string')) break; if ((parent.parent.pluginHandler == null) || (typeof command.plugin != 'string')) break;
try { try {
var pluginHandler = require('./pluginHandler.js').pluginHandler(parent.parent); parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
} catch (e) { } catch (e) {
console.log('Error loading plugin handler (' + e + ')'); console.log('Error loading plugin handler (' + e + ')');
} }

View File

@ -3103,22 +3103,51 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
break; break;
} }
case 'plugins': { case 'plugins': {
if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled // @Ylianst - Do we need a new permission set here?
if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin with plugins enabled
parent.db.getPlugins(function(err, docs) { parent.db.getPlugins(function(err, docs) {
try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { } try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
}); });
break; break;
} }
case 'pluginLatestCheck': {
if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin with plugins enabled
parent.parent.pluginHandler.getPluginLatest(function(latest) {
try { ws.send(JSON.stringify({ action: 'pluginVersionsAvailable', list: latest })); } catch (ex) { }
});
break;
}
case 'addplugin': { 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 if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
parent.parent.pluginHandler.addPlugin(command.url); parent.parent.pluginHandler.addPlugin(command.url);
break; break;
} }
case 'removeplugin': { case 'installplugin': {
// @Ylianst - Do we need a new permission here?
if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
parent.parent.pluginHandler.removePlugin(command.id); parent.parent.pluginHandler.installPlugin(command.id, function(){
parent.parent.updateMeshCore();
parent.db.getPlugins(function(err, docs) {
try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
});
});
break;
}
case 'disableplugin': {
if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
parent.parent.pluginHandler.disablePlugin(command.id, function(){
parent.db.getPlugins(function(err, docs) {
try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
});
});
break;
}
case 'removeplugin': {
if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin, plugins enabled
parent.parent.pluginHandler.removePlugin(command.id, function(){
parent.db.getPlugins(function(err, docs) {
try { ws.send(JSON.stringify({ action: 'updatePluginList', list: docs, result: err })); } catch (ex) { }
});
});
break; break;
} }
case 'plugin': { case 'plugin': {
@ -3128,8 +3157,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
routeCommandToNode(command); routeCommandToNode(command);
} else { } else {
try { try {
var pluginHandler = require('./pluginHandler.js').pluginHandler(parent.parent); parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
} catch (e) { console.log('Error loading plugin handler (' + e + ')'); } } catch (e) { console.log('Error loading plugin handler (' + e + ')'); }
} }
break; break;

View File

@ -23,14 +23,25 @@ module.exports.pluginHandler = function (parent) {
obj.pluginPath = obj.parent.path.join(obj.parent.datapath, 'plugins'); obj.pluginPath = obj.parent.path.join(obj.parent.datapath, 'plugins');
obj.plugins = {}; obj.plugins = {};
obj.exports = {}; obj.exports = {};
obj.loadList = obj.parent.config.settings.plugins.list; obj.loadList = obj.parent.config.settings.plugins.list; // For local development / manual install, not from DB
if (typeof obj.loadList != 'object') { if (typeof obj.loadList != 'object') {
obj.loadList = {}; obj.loadList = {};
console.log('Plugin list not specified, please fix configuration file.'); parent.db.getPlugins(function(err, plugins){
return null; plugins.forEach(function(plugin){
if (plugin.status != 1) return;
if (obj.fs.existsSync(obj.pluginPath + '/' + plugin.shortName)) {
try {
obj.plugins[plugin.shortName] = require(obj.pluginPath + '/' + plugin.shortName + '/' + plugin.shortName + '.js')[plugin.shortName](obj);
obj.exports[plugin.shortName] = obj.plugins[plugin.shortName].exports;
} catch (e) {
console.log("Error loading plugin: " + plugin.shortName + " (" + e + "). It has been disabled.", e.stack);
} }
}
obj.parent.updateMeshCore(); // db calls are delayed, lets inject here once we're ready
});
});
} else {
obj.loadList.forEach(function (plugin, index) { obj.loadList.forEach(function (plugin, index) {
if (obj.fs.existsSync(obj.pluginPath + '/' + plugin)) { if (obj.fs.existsSync(obj.pluginPath + '/' + plugin)) {
try { try {
@ -41,16 +52,23 @@ module.exports.pluginHandler = function (parent) {
} }
} }
}); });
}
obj.prepExportsForPlugin = function(plugin) {
var str = '';
str += ' obj.' + plugin + ' = {};\r\n';
for (const l of Object.values(obj.exports[plugin])) {
str += ' obj.' + plugin + '.' + l + ' = ' + obj.plugins[plugin][l].toString() + '\r\n';
}
return str;
};
obj.prepExports = function () { obj.prepExports = function () {
var str = 'function() {\r\n'; var str = 'function() {\r\n';
str += ' var obj = {};\r\n'; str += ' var obj = {};\r\n';
for (const p of Object.keys(obj.plugins)) { for (const p of Object.keys(obj.plugins)) {
str += ' obj.' + p + ' = {};\r\n'; str += obj.prepExportsForPlugin(p);
for (const l of Object.values(obj.exports[p])) {
str += ' obj.' + p + '.' + l + ' = ' + obj.plugins[p][l].toString() + '\r\n';
}
} }
str += `obj.onDeviceRefeshEnd = function(nodeid, panel, refresh, event) { str += `obj.onDeviceRefeshEnd = function(nodeid, panel, refresh, event) {
@ -75,7 +93,7 @@ module.exports.pluginHandler = function (parent) {
meshserver.send({ action: 'addplugin', url: Q('pluginurlinput').value}); meshserver.send({ action: 'addplugin', url: Q('pluginurlinput').value});
}; };
obj.addPluginDlg = function() { obj.addPluginDlg = function() {
setDialogMode(2, "Plugin URL", 3, obj.addPluginEx, '<input type=text id=pluginurlinput style=width:100% />'); setDialogMode(2, "Plugin Config URL", 3, obj.addPluginEx, '<input type=text id=pluginurlinput style=width:100% />');
focusTextBox('pluginurlinput'); focusTextBox('pluginurlinput');
}; };
return obj; };`; return obj; };`;
@ -165,6 +183,7 @@ module.exports.pluginHandler = function (parent) {
var isValid = true; var isValid = true;
if (!( if (!(
typeof conf.name == 'string' typeof conf.name == 'string'
&& typeof conf.shortName == 'string'
&& typeof conf.version == 'string' && typeof conf.version == 'string'
&& typeof conf.author == 'string' && typeof conf.author == 'string'
&& typeof conf.description == 'string' && typeof conf.description == 'string'
@ -179,68 +198,178 @@ module.exports.pluginHandler = function (parent) {
// && conf.configUrl == url // make sure we're loading a plugin from its desired config // && conf.configUrl == url // make sure we're loading a plugin from its desired config
)) isValid = false; )) isValid = false;
// more checks here? // more checks here?
if (conf.repository.type == 'git') {
if (typeof conf.downloadUrl != 'string') isValid = false;
}
return isValid; return isValid;
}; };
obj.addPlugin = function(url) { obj.getPlugins = function(func) {
var https = require('https'); var plugins = parent.db.getPlugins();
//var pit = obj.path.join(obj.pluginPath, ) if (typeof plugins == 'undefined' || plugins.length == 0) {
return null;
}
https.get(url, function(res) { plugins.forEach(function(p, x){
// check semantic version
console.log('FOREACH PLUGIN', p, x);
// callbacks to new versions
});
return plugins;
}
obj.getPluginConfig = function(configUrl, func) {
var https = require('https');
if (configUrl.indexOf('://') === -1) return; // @TODO error here
https.get(configUrl, function(res) {
var configStr = ''; var configStr = '';
res.on('data', function(chunk){ res.on('data', function(chunk){
configStr += chunk; configStr += chunk;
}); });
res.on('end', function(){ res.on('end', function(){
if (configStr[0] == '{') { if (configStr[0] == '{') { // let's be sure we're JSON
try { try {
var pluginConfig = JSON.parse(configStr); var pluginConfig = JSON.parse(configStr);
if (obj.isValidConfig(pluginConfig, url)) { if (Array.isArray(pluginConfig) && pluginConfig.length == 1) pluginConfig = pluginConfig[0];
// add to database if (obj.isValidConfig(pluginConfig, configUrl)) {
// we met the requirements of a valid config, but in case there's extra, let's rebuild for what we need func(pluginConfig);
}
} catch (e) { console.log('Error getting plugin config. Check that you have valid JSON.', e.stack); }
}
});
}).on('error', function(e) {
console.log("Error getting plugin config. Check that the URL is correct.: " + e.message);
});
};
obj.getPluginLatest = function(func) {
parent.db.getPlugins(function(err, plugins){
plugins.forEach(function(curconf){
obj.getPluginConfig(curconf.configUrl, function(newconf){
var s = require('semver');
func({
"id": curconf._id,
"installedVersion": curconf.version,
"version": newconf.version,
"hasUpdate": s.gt(newconf.version, curconf.version),
"meshCentralCompat": s.satisfies(s.coerce(parent.currentVer), newconf.meshCentralCompat),
"changelogUrl": curconf.changelogUrl,
"status": curconf.status
});
});
});
});
};
obj.addPlugin = function(url) {
obj.getPluginConfig(url, function(pluginConfig){
parent.db.addPlugin({ parent.db.addPlugin({
"name": pluginConfig.name, "name": pluginConfig.name,
"shortName": pluginConfig.shortName,
"version": pluginConfig.version, "version": pluginConfig.version,
"description": pluginConfig.description, "description": pluginConfig.description,
"hasAdminPanel": pluginConfig.hasAdminPanel, "hasAdminPanel": pluginConfig.hasAdminPanel,
"homepage": pluginConfig.homepage, "homepage": pluginConfig.homepage,
"changelogUrl": pluginConfig.changelogUrl, "changelogUrl": pluginConfig.changelogUrl,
"configUrl": pluginConfig.configUrl, "configUrl": pluginConfig.configUrl,
"downloadUrl": pluginConfig.downloadUrl,
"repository": { "repository": {
"type": pluginConfig.repository.type, "type": pluginConfig.repository.type,
"url": pluginConfig.repository.url "url": pluginConfig.repository.url
}, },
"meshCentralCompat": pluginConfig.meshCentralCompat, "meshCentralCompat": pluginConfig.meshCentralCompat,
"status": 0 // 0: disabled, 1: enabled "status": 0 // 0: disabled, 1: enabled
}); }, function() {
parent.db.getPlugins(function(err, docs){ parent.db.getPlugins(function(err, docs){
var targets = ['*', 'server-users']; var targets = ['*', 'server-users'];
parent.DispatchEvent(targets, obj, { action: 'updatePluginList', list: docs }); 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() { obj.installPlugin = function(id, func) {
var p = parent.db.getPlugins(); parent.db.getPlugin(id, function(err, docs){
if (typeof p == 'undefined' || p.length == 0) { var http = require('https');
return null; // the "id" would probably suffice, but is probably an sanitary issue, generate a random instead
var randId = Math.random().toString(32).replace('0.', '');
var fileName = obj.parent.path.join(require('os').tmpdir(), 'Plugin_'+randId+'.zip');
var plugin = docs[0];
if (plugin.repository.type == 'git') {
const file = obj.fs.createWriteStream(fileName);
var request = http.get(plugin.downloadUrl, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(function(){
var yauzl = require("yauzl");
if (!obj.fs.existsSync(obj.pluginPath)) {
obj.fs.mkdirSync(obj.pluginPath);
} }
return p; if (!obj.fs.existsSync(obj.parent.path.join(obj.pluginPath, plugin.shortName))) {
obj.fs.mkdirSync(obj.parent.path.join(obj.pluginPath, plugin.shortName));
} }
yauzl.open(fileName, { lazyEntries: true }, function (err, zipfile) {
if (err) throw err;
zipfile.readEntry();
zipfile.on("entry", function (entry) {
let pluginPath = obj.parent.path.join(obj.pluginPath, plugin.shortName);
let pathReg = new RegExp(/(.*?\/)/);
if (process.platform == 'win32') pathReg = new RegExp(/(.*?\\/);
let filePath = obj.parent.path.join(pluginPath, entry.fileName.replace(pathReg, '')); // remove top level dir
if (/\/$/.test(entry.fileName)) { // dir
if (!obj.fs.existsSync(filePath))
obj.fs.mkdirSync(filePath);
zipfile.readEntry();
} else { // file
zipfile.openReadStream(entry, function (err, readStream) {
if (err) throw err;
readStream.on("end", function () { zipfile.readEntry(); });
readStream.pipe(obj.fs.createWriteStream(filePath));
});
}
});
zipfile.on("end", function () { setTimeout(function () {
obj.fs.unlinkSync(fileName);
parent.db.setPluginStatus(id, 1, func);
obj.plugins[plugin.shortName] = require(obj.pluginPath + '/' + plugin.shortName + '/' + plugin.shortName + '.js')[plugin.shortName](obj);
obj.exports[plugin.shortName] = obj.plugins[plugin.shortName].exports;
}); });
});
});
});
});
} else if (plugin.repository.type == 'npm') {
// @TODO npm install and symlink dirs (need a test plugin)
}
});
};
obj.disablePlugin = function(id, func) {
parent.db.setPluginStatus(id, 0, func);
};
obj.removePlugin = function(id, func) {
parent.db.getPlugin(id, function(err, docs){
var plugin = docs[0];
var rimraf = require("rimraf");
let pluginPath = obj.parent.path.join(obj.pluginPath, plugin.shortName);
rimraf.sync(pluginPath);
parent.db.deletePlugin(id, func);
delete obj.plugins[plugin.shortName];
obj.parent.updateMeshCore();
});
};
return obj; return obj;
}; };

View File

@ -2584,14 +2584,18 @@ a {
} }
#p7tbl .chDescription { #p7tbl .chDescription {
width: 40%; width: 38%;
} }
#p7tbl .chSite { #p7tbl .chSite {
width: 10%; width: 7%;
} }
#p7tbl .chVersion { #p7tbl .chVersion {
width: 5%;
}
#p7tbl .chUpgradeAvail {
width: 10%; width: 10%;
} }
@ -2603,6 +2607,10 @@ a {
width: 10%; width: 10%;
} }
.pActDisable, .pActDelete, .pActInstall, .pActUpgrade {
cursor: pointer;
}
#addPlugin { #addPlugin {
background-image: url(../images/plus32.png); background-image: url(../images/plus32.png);
width: 32px; width: 32px;
@ -2611,3 +2619,12 @@ a {
cursor: pointer; cursor: pointer;
margin-right: 12px; margin-right: 12px;
} }
#pluginRestartNotice {
width: 40em;
font-weight: bold;
border: 1px solid red;
text-align: center;
padding: 14px;
margin: 50px auto;
}

View File

@ -411,10 +411,11 @@
</div> </div>
<div id=p7 style="display:none"> <div id=p7 style="display:none">
<h1>My Plugins</h1> <h1>My Plugins</h1>
<div id="addPlugin" onclick="return pluginHandler.addPluginDlg();"></div> <div id="addPlugin" title="Add New Plugin" onclick="return pluginHandler.addPluginDlg();"></div>
<table id="p7tbl"> <table id="p7tbl">
<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> <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>
</table> </table>
<div id="pluginRestartNotice" style="display:none;"><div>Notice:</div> MeshCentral restart required to complete plugin changes.</div>
</div> </div>
<div id=p10 style="display:none"> <div id=p10 style="display:none">
<table style="width:100%" cellpadding="0" cellspacing="0"> <table style="width:100%" cellpadding="0" cellspacing="0">
@ -2362,6 +2363,27 @@
updatePluginList(); updatePluginList();
break; break;
} }
case 'pluginVersionsAvailable': {
if (pluginHandler == null) break;
try {
var td = Q('pluginRow-'+message.list.id).querySelectorAll(".pluginUpgradeAvailable");
var sel = Q('pluginRow-'+message.list.id).querySelectorAll(".pluginAction > select");
td = td[0];
sel = sel[0];
if (message.list.hasUpdate && message.list.status) {
td.innerHTML = '<a title="View Changelog" target="_blank" href="' + message.list.changelogUrl + '">' + message.list.version + '</a>';
if (sel.innerHTML.indexOf('Upgrade') === -1) {
var option = document.createElement("option");
option.value = "install"
option.text = "Upgrade";
sel.add(option);
}
} else {
td.innerHTML = "Up to date";
}
} catch (e) { }
break;
}
case 'plugin': { case 'plugin': {
if ((pluginHandler == null) || (typeof message.plugin != 'string')) break; if ((pluginHandler == null) || (typeof message.plugin != 'string')) break;
try { pluginHandler[message.plugin][message.method](server, message); } catch (e) { console.log('Error loading plugin handler ('+ e + ')'); } try { pluginHandler[message.plugin][message.method](server, message); } catch (e) { console.log('Error loading plugin handler ('+ e + ')'); }
@ -9361,6 +9383,8 @@
// Fetch the server timeline stats if needed // Fetch the server timeline stats if needed
if ((x == 40) && (serverTimelineStats == null)) { refreshServerTimelineStats(); } if ((x == 40) && (serverTimelineStats == null)) { refreshServerTimelineStats(); }
if (x == 7) refreshPluginLatest();
// Update the web page title // Update the web page title
if ((currentNode) && (x >= 10) && (x < 20)) { if ((currentNode) && (x >= 10) && (x < 20)) {
document.title = decodeURIComponent('{{{extitle}}}') + ' - ' + currentNode.name + ' - ' + meshes[currentNode.meshid].name; document.title = decodeURIComponent('{{{extitle}}}') + ' - ' + currentNode.name + ' - ' + meshes[currentNode.meshid].name;
@ -9378,24 +9402,82 @@
} }
} }
var statusMap = { var statusMap = {
0: 'Disabled', 0: {
1: 'Installed' "text": 'Disabled',
"color": '858483'
},
1: {
"text": 'Installed',
"color": '00ff00'
} }
};
var statusAvailability = {
0: {
'install': 'Install',
'delete': 'Delete'
},
1: {
'disable': 'Disable'
}
};
var tbl = Q('p7tbl'); var tbl = Q('p7tbl');
installedPluginList.forEach(function(p){ installedPluginList.forEach(function(p){
if (p.hasAdminPanel == true) { if (p.hasAdminPanel == true) {
p.name = `<a onclick="return goPlugin('${p._id}');">${p.name}</a>`; p.name = `<a onclick="return goPlugin('${p._id}');">${p.name}</a>`;
} }
p.status = statusMap[p.status]; p.statusText = statusMap[p.status].text;
p.actions = 'TODO'; // Install / Upgrade / Disable / Delete p.statusColor = statusMap[p.status].color;
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>`;
p.actions = '<select onchange="return pluginAction(this, \'' + p._id + '\');"><option value=""> --</option>';
for (const [k, v] of Object.entries(statusAvailability[p.status])) {
p.actions += '<option value="' + k + '">' + v + '</option>';
}
p.action += '</select>'
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>`;
let tr = tbl.insertRow(-1); let tr = tbl.insertRow(-1);
tr.innerHTML = tpl; tr.innerHTML = tpl;
tr.classList.add('p7tblRow'); tr.classList.add('p7tblRow');
tr.setAttribute('data-id', p._id);
tr.setAttribute('id', 'pluginRow-'+p._id);
}); });
} else {
var tr = Q('p7tbl').querySelectorAll(".p7tblRow");
for (const i in Object.values(tr)) {
tr[i].parentNode.removeChild(tr[i]);
}
}
refreshPluginLatest();
} }
function refreshPluginLatest() {
meshserver.send({ action: 'pluginLatestCheck' });
} }
function pluginActionEx() {
var act = Q('lastPluginAct').value, id = Q('lastPluginId').value;
switch(act) {
case 'install': {
meshserver.send({ "action": "installplugin", "id": id });
break;
}
case 'delete': {
meshserver.send({ "action": "removeplugin", "id": id });
break;
}
case 'disable': {
meshserver.send({ "action": "disableplugin", "id": id });
break;
}
}
QS('pluginRestartNotice').display = '';
}
function pluginAction(elem, id) {
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') + '" />');
elem.value = '';
}
// Generic methods // Generic methods
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('/'); } 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('/'); }
function putstore(name, val) { function putstore(name, val) {