diff --git a/MeshCentralServer.njsproj b/MeshCentralServer.njsproj index d2d0cf3f..bc357bcb 100644 --- a/MeshCentralServer.njsproj +++ b/MeshCentralServer.njsproj @@ -170,6 +170,7 @@ + diff --git a/agents/meshcore.js b/agents/meshcore.js index 87578b1f..ba7b9874 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -1102,8 +1102,7 @@ function createMeshCore(agent) { if (fs.existsSync("/usr/bin/python") && fs.existsSync("/bin/bash")) { this.httprequest.process = childProcess.execFile("/usr/bin/python", [ "python", "-c", "import pty; pty.spawn([\"/bin/bash\"])" ]); if (process.platform == 'linux') { this.httprequest.process.stdin.write("export TERM='xterm'\nalias ls='ls --color=auto'\nclear\n"); } - } - else if (fs.existsSync("/bin/bash")) { + } else if (fs.existsSync("/bin/bash")) { this.httprequest.process = childProcess.execFile("/bin/bash", ["bash", "-i"], { type: childProcess.SpawnTypes.TERM }); if (process.platform == 'linux') { this.httprequest.process.stdin.write("alias ls='ls --color=auto'\nclear\n"); } } else { diff --git a/agents/meshcore.min.js b/agents/meshcore.min.js index 87578b1f..ba7b9874 100644 --- a/agents/meshcore.min.js +++ b/agents/meshcore.min.js @@ -1102,8 +1102,7 @@ function createMeshCore(agent) { if (fs.existsSync("/usr/bin/python") && fs.existsSync("/bin/bash")) { this.httprequest.process = childProcess.execFile("/usr/bin/python", [ "python", "-c", "import pty; pty.spawn([\"/bin/bash\"])" ]); if (process.platform == 'linux') { this.httprequest.process.stdin.write("export TERM='xterm'\nalias ls='ls --color=auto'\nclear\n"); } - } - else if (fs.existsSync("/bin/bash")) { + } else if (fs.existsSync("/bin/bash")) { this.httprequest.process = childProcess.execFile("/bin/bash", ["bash", "-i"], { type: childProcess.SpawnTypes.TERM }); if (process.platform == 'linux') { this.httprequest.process.stdin.write("alias ls='ls --color=auto'\nclear\n"); } } else { diff --git a/db.js b/db.js index 07a0e3f0..fec1c3d9 100644 --- a/db.js +++ b/db.js @@ -442,9 +442,9 @@ module.exports.CreateDB = function (parent, func) { }); } }); - + // Setup plugin info collection - obj.pluginsfile = db.collection('plugins'); + if (parent.config.settings != null) { obj.pluginsfile = db.collection('plugins'); } setupFunctions(func); // Completed setup of MongoDB }); @@ -548,7 +548,7 @@ module.exports.CreateDB = function (parent, func) { }); // Setup plugin info collection - obj.pluginsfile = db.collection('plugins'); + if (parent.config.settings != null) { obj.pluginsfile = db.collection('plugins'); } setupFunctions(func); // Completed setup of MongoJS } else { @@ -611,8 +611,10 @@ module.exports.CreateDB = function (parent, func) { 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); + if (parent.config.settings != null) { + obj.pluginsfile = new Datastore({ filename: parent.getConfigFilePath('meshcentral-plugins.db'), autoload: true }); + obj.pluginsfile.persistence.setAutocompactionInterval(36000); + } setupFunctions(func); // Completed setup of NeDB } @@ -764,22 +766,16 @@ module.exports.CreateDB = function (parent, func) { func(r); }); } - - // Add a plugin - obj.addPlugin = function (plugin, func) { plugin.type = "plugin"; obj.pluginsfile.insertOne(plugin, func); }; - - // Get all plugins - obj.getPlugins = function (func) { obj.pluginsfile.find({"type": "plugin"}).project({"type": 0}).sort({ name: 1 }).toArray(func); }; - - // Get plugin - obj.getPlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).toArray(func); }; - - // Delete plugin - 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); }; - - obj.updatePlugin = function(id, args, func) { delete args._id; id = require('mongodb').ObjectID(id); obj.pluginsfile.updateOne({ _id: id }, { $set: args }, func); }; + + // Plugin operations + if (parent.config.settings.plugins != null) { + obj.addPlugin = function (plugin, func) { plugin.type = "plugin"; obj.pluginsfile.insertOne(plugin, func); }; // Add a plugin + obj.getPlugins = function (func) { obj.pluginsfile.find({ "type": "plugin" }).project({ "type": 0 }).sort({ name: 1 }).toArray(func); }; // Get all plugins + obj.getPlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).toArray(func); }; // Get plugin + obj.deletePlugin = function (id, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.deleteOne({ _id: id }, func); }; // Delete plugin + obj.setPluginStatus = function (id, status, func) { id = require('mongodb').ObjectID(id); obj.pluginsfile.updateOne({ _id: id }, { $set: { status: status } }, func); }; + obj.updatePlugin = function (id, args, func) { delete args._id; id = require('mongodb').ObjectID(id); obj.pluginsfile.updateOne({ _id: id }, { $set: args }, func); }; + } } else { // Database actions on the main collection (NeDB and MongoJS) @@ -911,22 +907,16 @@ module.exports.CreateDB = function (parent, func) { func(r); }); } - - // Add a plugin - obj.addPlugin = function (plugin, func) { plugin.type = "plugin"; obj.pluginsfile.insert(plugin, func); }; - - // Get all plugins - obj.getPlugins = function (func) { obj.pluginsfile.find({"type": "plugin"}, {"type": 0}).sort({ name: 1 }).exec(func); }; - - // Get plugin - obj.getPlugin = function (id, func) { obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).exec(func); }; - - // Delete plugin - obj.deletePlugin = function (id, func) { obj.pluginsfile.remove({ _id: id }, func); }; - - obj.setPluginStatus = function(id, status, func) { obj.pluginsfile.update({ _id: id }, { $set: {status: status } }, func); }; - - obj.updatePlugin = function(id, args, func) { delete args._id; obj.pluginsfile.update({ _id: id }, { $set: args }, func); }; + + // Plugin operations + if (parent.config.settings.plugins != null) { + obj.addPlugin = function (plugin, func) { plugin.type = "plugin"; obj.pluginsfile.insert(plugin, func); }; // Add a plugin + obj.getPlugins = function (func) { obj.pluginsfile.find({ "type": "plugin" }, { "type": 0 }).sort({ name: 1 }).exec(func); }; // Get all plugins + obj.getPlugin = function (id, func) { obj.pluginsfile.find({ _id: id }).sort({ name: 1 }).exec(func); }; // Get plugin + obj.deletePlugin = function (id, func) { obj.pluginsfile.remove({ _id: id }, func); }; // Delete plugin + obj.setPluginStatus = function (id, status, func) { obj.pluginsfile.update({ _id: id }, { $set: { status: status } }, func); }; + obj.updatePlugin = function (id, args, func) { delete args._id; obj.pluginsfile.update({ _id: id }, { $set: args }, func); }; + } } diff --git a/meshcentral.js b/meshcentral.js index 3fe365bf..df75489c 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -840,7 +840,13 @@ function CreateMeshCentralServer(config, args) { // Start plugin manager if configuration allows this. if ((obj.config) && (obj.config.settings) && (obj.config.settings.plugins != null)) { - obj.pluginHandler = require('./pluginHandler.js').pluginHandler(obj); + const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); + if (nodeVersion < 7) { + console.log("WARNING: Plugin support requires Node 7 or higher."); + delete obj.config.settings.plugins; + } else { + obj.pluginHandler = require('./pluginHandler.js').pluginHandler(obj); + } } // Load the default meshcore and meshcmd @@ -2073,6 +2079,7 @@ function mainStart() { if (config.settings.mqtt != null) { modules.push('aedes'); } // Add MQTT Modules if (config.settings.mongodb != null) { modules.push('mongodb'); } // Add MongoDB, official driver. if (config.settings.vault != null) { modules.push('node-vault'); } // Add official HashiCorp's Vault module. + if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent'); } // Required for HTTP/HTTPS proxy support else if (config.settings.xmongodb != null) { modules.push('mongojs'); } // Add MongoJS, old driver. if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support diff --git a/meshuser.js b/meshuser.js index a4780c80..952f8862 100644 --- a/meshuser.js +++ b/meshuser.js @@ -3138,6 +3138,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use break; } case 'distributeCore': { + // This is only available when plugins are enabled since it could cause stress on the server + if ((user.siteadmin & 0xFFFFFFFF) == 0 || parent.parent.pluginHandler == null) break; // must be full admin with plugins enabled for (var i in command.nodes) { parent.sendMeshAgentCore(user, domain, command.nodes[i]._id, 'default'); } @@ -3170,7 +3172,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use }) .catch(function(err) { if (typeof err == 'object') err = err.message; - try { ws.send(JSON.stringify({ action: 'pluginError', msg: err })); } catch (er) { } + try { ws.send(JSON.stringify({ action: 'pluginError', msg: err })); } catch (er) { } }); } catch(e) { console.log('Cannot add plugin: ' + e); } diff --git a/pluginHandler.js b/pluginHandler.js index 0c70e55d..d88cd980 100644 --- a/pluginHandler.js +++ b/pluginHandler.js @@ -25,27 +25,27 @@ module.exports.pluginHandler = function (parent) { obj.plugins = {}; obj.exports = {}; obj.loadList = obj.parent.config.settings.plugins.list; // For local development / manual install, not from DB - + if (typeof obj.loadList != 'object') { obj.loadList = {}; - parent.db.getPlugins(function(err, plugins){ - 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); - } - try { // try loading local info about plugin to database (if it changed locally) - var plugin_config = obj.fs.readFileSync(obj.pluginPath + '/' + plugin.shortName + '/config.json'); - plugin_config = JSON.parse(plugin_config); - parent.db.updatePlugin(plugin._id, plugin_config); - } catch (e) { console.log('Plugin config file for '+ plugin.name +' could not be parsed.'); } - } - obj.parent.updateMeshCore(); // db calls are delayed, lets inject here once we're ready - }); + parent.db.getPlugins(function (err, plugins) { + 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); + } + try { // try loading local info about plugin to database (if it changed locally) + var plugin_config = obj.fs.readFileSync(obj.pluginPath + '/' + plugin.shortName + '/config.json'); + plugin_config = JSON.parse(plugin_config); + parent.db.updatePlugin(plugin._id, plugin_config); + } catch (e) { console.log("Plugin config file for " + plugin.name + " could not be parsed."); } + } + obj.parent.updateMeshCore(); // db calls are delayed, lets inject here once we're ready + }); }); } else { obj.loadList.forEach(function (plugin, index) { @@ -59,15 +59,15 @@ module.exports.pluginHandler = function (parent) { } }); } - + obj.prepExports = function () { var str = 'function() {\r\n'; str += ' var obj = {};\r\n'; - for (const p of Object.keys(obj.plugins)) { + for (var p of Object.keys(obj.plugins)) { str += ' obj.' + p + ' = {};\r\n'; if (Array.isArray(obj.exports[p])) { - for (const l of Object.values(obj.exports[p])) { + for (var l of Object.values(obj.exports[p])) { str += ' obj.' + p + '.' + l + ' = ' + obj.plugins[p][l].toString() + '\r\n'; } } @@ -97,7 +97,7 @@ module.exports.pluginHandler = function (parent) { meshserver.send({ action: 'addplugin', url: Q('pluginurlinput').value}); }; obj.addPluginDlg = function() { - setDialogMode(2, "Plugin Config URL", 3, obj.addPluginEx, ''); + setDialogMode(2, "Plugin Download URL", 3, obj.addPluginEx, '

WARNING: Downloading plugins may compromise server security. Only download from trusted sources.

'); focusTextBox('pluginurlinput'); }; obj.refreshPluginHandler = function() { @@ -108,20 +108,20 @@ module.exports.pluginHandler = function (parent) { return obj; };`; return str; } - - obj.refreshJS = function(req, res) { + + obj.refreshJS = function (req, res) { // to minimize server reboots when installing new plugins, we call the new data and overwrite the old pluginHandler on the front end res.set('Content-Type', 'text/javascript'); - res.send('pluginHandlerBuilder = '+obj.prepExports() + ' pluginHandler = new pluginHandlerBuilder();'); + res.send('pluginHandlerBuilder = ' + obj.prepExports() + ' pluginHandler = new pluginHandlerBuilder();'); } - + obj.callHook = function (hookName, ...args) { for (var p in obj.plugins) { if (typeof obj.plugins[p][hookName] == 'function') { try { obj.plugins[p][hookName](args); } catch (e) { - console.log('Error ocurred while running plugin hook' + p + ':' + hookName + ' (' + e + ')'); + console.log("Error ocurred while running plugin hook" + p + ':' + hookName + ' (' + e + ')'); } } } @@ -187,20 +187,20 @@ module.exports.pluginHandler = function (parent) { panel[p].header = obj.plugins[p].on_device_header(); panel[p].content = obj.plugins[p].on_device_page(); } catch (e) { - console.log('Error ocurred while getting plugin views ' + p + ':' + ' (' + e + ')'); + console.log("Error ocurred while getting plugin views " + p + ':' + ' (' + e + ')'); } } } return panel; }; - - obj.isValidConfig = function(conf, url) { // check for the required attributes + + obj.isValidConfig = function (conf, url) { // check for the required attributes var isValid = true; if (!( typeof conf.name == 'string' && typeof conf.shortName == 'string' && typeof conf.version == 'string' - // && typeof conf.author == 'string' + // && typeof conf.author == 'string' && typeof conf.description == 'string' && typeof conf.hasAdminPanel == 'boolean' && typeof conf.homepage == 'string' @@ -210,7 +210,7 @@ module.exports.pluginHandler = function (parent) { && 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 + // && conf.configUrl == url // make sure we're loading a plugin from its desired config )) isValid = false; // more checks here? if (conf.repository.type == 'git') { @@ -218,119 +218,120 @@ module.exports.pluginHandler = function (parent) { } return isValid; }; - - obj.getPluginConfig = function(configUrl) { - return new Promise(function(resolve, reject) { - if (configUrl.indexOf('https://') >= 0) { - var http = require('https'); - } else { - var http = require('http'); + + // https://raw.githubusercontent.com/ryanblenis/MeshCentral-Sample/master/config.json + obj.getPluginConfig = function (configUrl) { + return new Promise(function (resolve, reject) { + var http = (configUrl.indexOf('https://') >= 0) ? require('https') : require('http'); + if (configUrl.indexOf('://') === -1) reject("Unable to fetch the config: Bad URL (" + configUrl + ")"); + var options = require('url').parse(configUrl); + if (typeof parent.config.settings.plugins.proxy == 'string') { // Proxy support + const HttpsProxyAgent = require('https-proxy-agent'); + options.agent = new HttpsProxyAgent(require('url').parse(parent.config.settings.plugins.proxy)); } - if (configUrl.indexOf('://') === -1) reject('Unable to fetch the config: Bad URL (' + configUrl + ')'); - http.get(configUrl, function(res) { - var configStr = ''; - res.on('data', function(chunk){ - configStr += chunk; - }); - res.on('end', function(){ - if (configStr[0] == '{') { // let's be sure we're JSON - try { - var pluginConfig = JSON.parse(configStr); - if (Array.isArray(pluginConfig) && pluginConfig.length == 1) pluginConfig = pluginConfig[0]; - if (obj.isValidConfig(pluginConfig, configUrl)) { - resolve(pluginConfig); - } else { - reject("This does not appear to be a valid plugin configuration."); - } - - } catch (e) { reject('Error getting plugin config. Check that you have valid JSON.'); } - } else { - reject('Error getting plugin config. Check that you have valid JSON.'); - } - }); - - }).on('error', function(e) { + http.get(options, function (res) { + var configStr = ''; + res.on('data', function (chunk) { + configStr += chunk; + }); + res.on('end', function () { + if (configStr[0] == '{') { // Let's be sure we're JSON + try { + var pluginConfig = JSON.parse(configStr); + if (Array.isArray(pluginConfig) && pluginConfig.length == 1) pluginConfig = pluginConfig[0]; + if (obj.isValidConfig(pluginConfig, configUrl)) { + resolve(pluginConfig); + } else { + reject("This does not appear to be a valid plugin configuration."); + } + } catch (e) { reject("Error getting plugin config. Check that you have valid JSON."); } + } else { + reject("Error getting plugin config. Check that you have valid JSON."); + } + }); + + }).on('error', function (e) { reject("Error getting plugin config: " + e.message); - }); + }); }) }; - - obj.getPluginLatest = function() { - return new Promise(function(resolve, reject) { - parent.db.getPlugins(function(err, plugins) { + + obj.getPluginLatest = function () { + return new Promise(function (resolve, reject) { + parent.db.getPlugins(function (err, plugins) { var proms = []; - plugins.forEach(function(curconf) { - proms.push(obj.getPluginConfig(curconf.configUrl).catch(e => { return null; } )); + plugins.forEach(function (curconf) { + proms.push(obj.getPluginConfig(curconf.configUrl).catch(e => { return null; })); }); var latestRet = []; - Promise.all(proms).then(function(newconfs) { + Promise.all(proms).then(function (newconfs) { var nconfs = []; - // filter out config download issues - newconfs.forEach(function(nc) { - if (nc !== null) nconfs.push(nc); - }); - nconfs.forEach(function(newconf) { - var curconf = null; - plugins.forEach(function(conf) { - if (conf.configUrl == newconf.configUrl) curconf = conf; + // Filter out config download issues + newconfs.forEach(function (nc) { if (nc !== null) nconfs.push(nc); }); + if (nconfs.length == 0) { resolve([]); } else { + nconfs.forEach(function (newconf) { + var curconf = null; + plugins.forEach(function (conf) { + if (conf.configUrl == newconf.configUrl) curconf = conf; + }); + if (curconf == null) reject("Some plugin configs could not be parsed"); + var s = require('semver'); + // MeshCentral doesn't adhere to semantic versioning (due to the - at the end of the version) + // Convert the letter to ASCII for a "true" version number comparison + var mcCurVer = parent.currentVer.replace(/-(.)$/, (m, p1) => { return p1.charCodeAt(0); }); + var piCompatVer = newconf.meshCentralCompat.replace(/-(.)\b/g, (m, p1) => { return p1.charCodeAt(0); }); + latestRet.push({ + 'id': curconf._id, + 'installedVersion': curconf.version, + 'version': newconf.version, + 'hasUpdate': s.gt(newconf.version, curconf.version), + 'meshCentralCompat': s.satisfies(mcCurVer, piCompatVer), + 'changelogUrl': curconf.changelogUrl, + 'status': curconf.status + }); + resolve(latestRet); }); - if (curconf == null) reject('Some plugin configs could not be parsed'); - var s = require('semver'); - // MeshCentral doesn't adhere to semantic versioning (due to the - at the end of the version) - // Convert the letter to ASCII for a "true" version number comparison - var mcCurVer = parent.currentVer.replace(/-(.)$/, (m, p1) => { return p1.charCodeAt(0); }); - var piCompatVer = newconf.meshCentralCompat.replace(/-(.)\b/g, (m, p1) => { return p1.charCodeAt(0); }); - latestRet.push({ - "id": curconf._id, - "installedVersion": curconf.version, - "version": newconf.version, - "hasUpdate": s.gt(newconf.version, curconf.version), - "meshCentralCompat": s.satisfies(mcCurVer, piCompatVer), - "changelogUrl": curconf.changelogUrl, - "status": curconf.status - }); - resolve(latestRet); - }); - }).catch((e) => { console.log('Error reaching plugins, update call aborted. ', e)}); + } + }).catch((e) => { console.log("Error reaching plugins, update call aborted.", e) }); }); }); }; - - obj.addPlugin = function(pluginConfig) { - return new Promise(function(resolve, reject) { - parent.db.addPlugin({ - "name": pluginConfig.name, - "shortName": pluginConfig.shortName, - "version": pluginConfig.version, - "description": pluginConfig.description, - "hasAdminPanel": pluginConfig.hasAdminPanel, - "homepage": pluginConfig.homepage, - "changelogUrl": pluginConfig.changelogUrl, - "configUrl": pluginConfig.configUrl, - "downloadUrl": pluginConfig.downloadUrl, - "repository": { - "type": pluginConfig.repository.type, - "url": pluginConfig.repository.url - }, - "meshCentralCompat": pluginConfig.meshCentralCompat, - "versionHistoryUrl": pluginConfig.versionHistoryUrl, - "status": 0 // 0: disabled, 1: enabled - }, function() { - parent.db.getPlugins(function(err, docs){ - if (err) reject(err); - else resolve(docs); + + obj.addPlugin = function (pluginConfig) { + return new Promise(function (resolve, reject) { + parent.db.addPlugin({ + 'name': pluginConfig.name, + 'shortName': pluginConfig.shortName, + 'version': pluginConfig.version, + 'description': pluginConfig.description, + 'hasAdminPanel': pluginConfig.hasAdminPanel, + 'homepage': pluginConfig.homepage, + 'changelogUrl': pluginConfig.changelogUrl, + 'configUrl': pluginConfig.configUrl, + 'downloadUrl': pluginConfig.downloadUrl, + 'repository': { + 'type': pluginConfig.repository.type, + 'url': pluginConfig.repository.url + }, + 'meshCentralCompat': pluginConfig.meshCentralCompat, + 'versionHistoryUrl': pluginConfig.versionHistoryUrl, + 'status': 0 // 0: disabled, 1: enabled + }, function () { + parent.db.getPlugins(function (err, docs) { + if (err) reject(err); + else resolve(docs); }); }); }); }; - - obj.installPlugin = function(id, version_only, force_url, func) { - parent.db.getPlugin(id, function(err, docs){ + + obj.installPlugin = function (id, version_only, force_url, func) { + parent.db.getPlugin(id, function (err, docs) { // 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 fileName = obj.parent.path.join(require('os').tmpdir(), 'Plugin_' + randId + '.zip'); var plugin = docs[0]; - if (plugin.repository.type == 'git') { + if (plugin.repository.type == 'git') { const file = obj.fs.createWriteStream(fileName); var dl_url = plugin.downloadUrl; if (version_only != null && version_only != false) dl_url = version_only.url; @@ -339,7 +340,7 @@ module.exports.pluginHandler = function (parent) { var q = url.parse(dl_url, true); var http = (q.protocol == "http") ? require('http') : require('https'); var opts = { - path: q.pathname, + path: q.pathname, host: q.hostname, port: q.port, headers: { @@ -348,12 +349,16 @@ module.exports.pluginHandler = function (parent) { followRedirects: true, method: 'GET' }; - var request = http.get(opts, function(response) { + if (typeof parent.config.settings.plugins.proxy == 'string') { // Proxy support + const HttpsProxyAgent = require('https-proxy-agent'); + opts.agent = new HttpsProxyAgent(require('url').parse(parent.config.settings.plugins.proxy)); + } + var request = http.get(opts, function (response) { // handle redirections with grace if (response.headers.location) return obj.installPlugin(id, version_only, response.headers.location, func); response.pipe(file); - file.on('finish', function() { - file.close(function(){ + file.on('finish', function () { + file.close(function () { var yauzl = require("yauzl"); if (!obj.fs.existsSync(obj.pluginPath)) { obj.fs.mkdirSync(obj.pluginPath); @@ -364,12 +369,12 @@ module.exports.pluginHandler = function (parent) { yauzl.open(fileName, { lazyEntries: true }, function (err, zipfile) { if (err) throw err; zipfile.readEntry(); - zipfile.on("entry", function (entry) { + 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(/(.*?\\/); + //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); @@ -377,50 +382,48 @@ module.exports.pluginHandler = function (parent) { } else { // file zipfile.openReadStream(entry, function (err, readStream) { if (err) throw err; - readStream.on("end", function () { zipfile.readEntry(); }); + readStream.on('end', function () { zipfile.readEntry(); }); readStream.pipe(obj.fs.createWriteStream(filePath)); }); } }); - zipfile.on("end", function () { setTimeout(function () { - obj.fs.unlinkSync(fileName); - if (version_only == null || version_only === false) { - parent.db.setPluginStatus(id, 1, func); - } else { - parent.db.updatePlugin(id, { status: 1, version: version_only.name }, 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; - if (typeof obj.plugins[plugin.shortName].server_startup == 'function') obj.plugins[plugin.shortName].server_startup(); - var plugin_config = obj.fs.readFileSync(obj.pluginPath + '/' + plugin.shortName + '/config.json'); - plugin_config = JSON.parse(plugin_config); - parent.db.updatePlugin(plugin._id, plugin_config); - parent.updateMeshCore(); - }); }); + zipfile.on('end', function () { + setTimeout(function () { + obj.fs.unlinkSync(fileName); + if (version_only == null || version_only === false) { + parent.db.setPluginStatus(id, 1, func); + } else { + parent.db.updatePlugin(id, { status: 1, version: version_only.name }, 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; + if (typeof obj.plugins[plugin.shortName].server_startup == 'function') obj.plugins[plugin.shortName].server_startup(); + var plugin_config = obj.fs.readFileSync(obj.pluginPath + '/' + plugin.shortName + '/config.json'); + plugin_config = JSON.parse(plugin_config); + parent.db.updatePlugin(plugin._id, plugin_config); + parent.updateMeshCore(); + }); + }); }); }); }); }); - } else if (plugin.repository.type == 'npm') { + } else if (plugin.repository.type == 'npm') { // @TODO npm support? (need a test plugin) } - - }); - - }; - - obj.getPluginVersions = function(id) { - return new Promise(function(resolve, reject) { - parent.db.getPlugin(id, function(err, docs) { + + obj.getPluginVersions = function (id) { + return new Promise(function (resolve, reject) { + parent.db.getPlugin(id, function (err, docs) { var plugin = docs[0]; - if (plugin.versionHistoryUrl == null) reject('No version history available for this plugin.'); + if (plugin.versionHistoryUrl == null) reject("No version history available for this plugin."); var url = require('url'); var q = url.parse(plugin.versionHistoryUrl, true); - var http = (q.protocol == "http") ? require('http') : require('https'); + var http = (q.protocol == 'http') ? require('http') : require('https'); var opts = { - path: q.pathname, + path: q.pathname, host: q.hostname, port: q.port, headers: { @@ -428,47 +431,51 @@ module.exports.pluginHandler = function (parent) { 'Accept': 'application/vnd.github.v3+json' } }; - http.get(opts, function(res) { + if (typeof parent.config.settings.plugins.proxy == 'string') { // Proxy support + const HttpsProxyAgent = require('https-proxy-agent'); + options.agent = new HttpsProxyAgent(require('url').parse(parent.config.settings.plugins.proxy)); + } + http.get(opts, function (res) { var versStr = ''; - res.on('data', function(chunk){ + res.on('data', function (chunk) { versStr += chunk; }); - res.on('end', function(){ + res.on('end', function () { if (versStr[0] == '{' || versStr[0] == '[') { // let's be sure we're JSON try { var vers = JSON.parse(versStr); var vList = []; var s = require('semver'); vers.forEach((v) => { - if (s.lt(v.name, plugin.version)) vList.push(v); + if (s.lt(v.name, plugin.version)) vList.push(v); }); - if (vers.length == 0) reject('No previous versions available.'); + if (vers.length == 0) reject("No previous versions available."); resolve({ 'id': plugin._id, 'name': plugin.name, versionList: vList }); - } catch (e) { reject('Version history problem.'); } + } catch (e) { reject("Version history problem."); } } else { - reject('Version history appears to be malformed.'+versStr); + reject("Version history appears to be malformed." + versStr); } }); - }).on('error', function(e) { + }).on('error', function (e) { reject("Error getting plugin versions: " + e.message); - }); + }); }); }); }; - - obj.disablePlugin = function(id, func) { - parent.db.getPlugin(id, function(err, docs){ + + obj.disablePlugin = function (id, func) { + parent.db.getPlugin(id, function (err, docs) { var plugin = docs[0]; parent.db.setPluginStatus(id, 0, func); delete obj.plugins[plugin.shortName]; delete obj.exports[plugin.shortName]; }); }; - - obj.removePlugin = function(id, func) { - parent.db.getPlugin(id, function(err, docs){ + + obj.removePlugin = function (id, func) { + parent.db.getPlugin(id, function (err, docs) { var plugin = docs[0]; - var rimraf = require("rimraf"); + var rimraf = require('rimraf'); let pluginPath = obj.parent.path.join(obj.pluginPath, plugin.shortName); rimraf.sync(pluginPath); parent.db.deletePlugin(id, func); @@ -476,25 +483,23 @@ module.exports.pluginHandler = function (parent) { obj.parent.updateMeshCore(); }); }; - + obj.handleAdminReq = function (req, res, user, serv) { var path = obj.path.join(obj.pluginPath, req.query.pin, 'views'); serv.app.set('views', path); if (obj.plugins[req.query.pin] != null && typeof obj.plugins[req.query.pin].handleAdminReq == 'function') { obj.plugins[req.query.pin].handleAdminReq(req, res, user); - } - else { + } else { res.sendStatus(401); } } - - obj.handleAdminPostReq = function(req, res, user, serv) { + + obj.handleAdminPostReq = function (req, res, user, serv) { var path = obj.path.join(obj.pluginPath, req.query.pin, 'views'); serv.app.set('views', path); if (obj.plugins[req.query.pin] != null && typeof obj.plugins[req.query.pin].handleAdminPostReq == 'function') { obj.plugins[req.query.pin].handleAdminPostReq(req, res, user); - } - else { + } else { res.sendStatus(401); } } diff --git a/public/images/leftbar-64.png b/public/images/leftbar-64.png index ef07b545..1299fed8 100644 Binary files a/public/images/leftbar-64.png and b/public/images/leftbar-64.png differ diff --git a/public/images/plugin.png b/public/images/plugin.png new file mode 100644 index 00000000..7f396383 Binary files /dev/null and b/public/images/plugin.png differ diff --git a/public/images/plugin24.png b/public/images/plugin24.png new file mode 100644 index 00000000..fc42c00e Binary files /dev/null and b/public/images/plugin24.png differ diff --git a/public/images/plus32.png b/public/images/plus32.png deleted file mode 100644 index f2e17cce..00000000 Binary files a/public/images/plus32.png and /dev/null differ diff --git a/public/styles/style.css b/public/styles/style.css index e326a87e..d7b44da6 100644 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -1295,17 +1295,6 @@ a { left: 6px; } -.lb7 { - background: url(../images/leftbar-64.png) -382px -2px; - height: 62px; - width: 62px; - cursor: pointer; - border: none; - position: absolute; - top: 6px; - left: 6px; -} - .m0 { background: url(../images/images16.png) -32px 0px; height: 16px; @@ -1694,6 +1683,10 @@ a { background-color: #FFFFFF; } +.gradTable1 { background-image: linear-gradient(to right, #fff 0%, #d3d9d6 100%); } +.gradTable2 { background-color: #d3d9d6; } +.gradTable3 { background-image: linear-gradient(to right, #d3d9d6 0%, #fff 100%); } + .h1 { background-position: 0% 0%; width: 14px; @@ -2580,45 +2573,47 @@ a { background-color: #DDD; } -#p7tbl { +.p42tblRow { + height: 36px; + max-height: 40px; +} + +#p42tbl { width: 100%; - border-collapse: collapse; + border-collapse: separate; + border-spacing:0 5px; } -#p7tbl th, #p7tbl td { +#p42tbl th, #p7tbl td { text-align: left; - padding: 12px; + padding: 4px; } -#p7tbl tr:nth-child(n+2):nth-child(odd) { - background-color: #cfeeff; -} - -#p7tbl .chName { +#p42tbl .chName { width: 20%; } -#p7tbl .chDescription { +#p42tbl .chDescription { width: 38%; } -#p7tbl .chSite { +#p42tbl .chSite { width: 7%; } -#p7tbl .chVersion { +#p42tbl .chVersion { width: 5%; } -#p7tbl .chUpgradeAvail { +#p42tbl .chUpgradeAvail { width: 10%; } -#p7tbl .chStatus { +#p42tbl .chStatus { width: 10%; } -#p7tbl .chAction { +#p42tbl .chAction { width: 10%; } @@ -2636,12 +2631,14 @@ a { } #pluginRestartNotice { + /* width: 40em; font-weight: bold; border: 1px solid red; text-align: center; padding: 14px; margin: 50px auto; + */ } .pluginContent { diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 330c9d7f..1004a478 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1,4 +1,4 @@ -{{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

{{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

diff --git a/views/translations/default-min_fr.handlebars b/views/translations/default-min_fr.handlebars index dfe91c1a..0fee842c 100644 --- a/views/translations/default-min_fr.handlebars +++ b/views/translations/default-min_fr.handlebars @@ -1,4 +1,4 @@ -{{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

{{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

diff --git a/webserver.js b/webserver.js index fb2b311f..0bf4c874 100644 --- a/webserver.js +++ b/webserver.js @@ -3214,35 +3214,38 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } }); } - - obj.handlePluginAdminReq = function(req, res) { - const domain = checkUserIpAddress(req, res); - if (domain == null) { res.sendStatus(404); return; } - if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; } - var user = obj.users[req.session.userid]; - if (user == null) { res.sendStatus(401); return; } - - parent.pluginHandler.handleAdminReq(req, res, user, obj); - } - - obj.handlePluginAdminPostReq = function(req, res) { - const domain = checkUserIpAddress(req, res); - if (domain == null) { res.sendStatus(404); return; } - if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; } - var user = obj.users[req.session.userid]; - if (user == null) { res.sendStatus(401); return; } - - parent.pluginHandler.handleAdminPostReq(req, res, user, obj); - } - - obj.handlePluginJS = function(req, res) { - const domain = checkUserIpAddress(req, res); - if (domain == null) { res.sendStatus(404); return; } - if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; } - var user = obj.users[req.session.userid]; - if (user == null) { res.sendStatus(401); return; } - - parent.pluginHandler.refreshJS(req, res); + + if (parent.pluginHandler != null) { + // Handle a plugin admin request + obj.handlePluginAdminReq = function (req, res) { + const domain = checkUserIpAddress(req, res); + if (domain == null) { res.sendStatus(404); return; } + if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; } + var user = obj.users[req.session.userid]; + if (user == null) { res.sendStatus(401); return; } + + parent.pluginHandler.handleAdminReq(req, res, user, obj); + } + + obj.handlePluginAdminPostReq = function (req, res) { + const domain = checkUserIpAddress(req, res); + if (domain == null) { res.sendStatus(404); return; } + if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; } + var user = obj.users[req.session.userid]; + if (user == null) { res.sendStatus(401); return; } + + parent.pluginHandler.handleAdminPostReq(req, res, user, obj); + } + + obj.handlePluginJS = function (req, res) { + const domain = checkUserIpAddress(req, res); + if (domain == null) { res.sendStatus(404); return; } + if ((!req.session) || (req.session == null) || (!req.session.userid)) { res.sendStatus(401); return; } + var user = obj.users[req.session.userid]; + if (user == null) { res.sendStatus(401); return; } + + parent.pluginHandler.refreshJS(req, res); + } } // Starts the HTTPS server, this should be called after the user/mesh tables are loaded diff --git a/x.txt b/x.txt new file mode 100644 index 00000000..535b0541 --- /dev/null +++ b/x.txt @@ -0,0 +1,9735 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{{title}}} + + + + + + + + +
+ +
+
{{{title}}}
+
{{{title2}}}
+
+ +
+

{{{logoutControl}}}

+
+
+
+
+
+
+
+
+
+
+
+
+ + + + +
+
+
+
+
+ ♦ + +
+ + + + + + + + + + + + + + + + + +
+ + +
 
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+ +
+ + +