diff --git a/config.json b/config.json index 00cd76a..f534b28 100644 --- a/config.json +++ b/config.json @@ -1,18 +1,18 @@ { "name": "ScriptTask", "shortName": "scripttask", - "version": "0.0.20", - "author": "Ryan Blenis", + "version": "0.0.21", + "author": "Ryan Blenis / Pablo Navarro", "description": "Script (PowerShell, BAT, Bash) runner for endpoints", "hasAdminPanel": false, - "homepage": "https://github.com/ryanblenis/MeshCentral-ScriptTask", - "changelogUrl": "https://raw.githubusercontent.com/ryanblenis/MeshCentral-ScriptTask/master/changelog.md", - "configUrl": "https://raw.githubusercontent.com/ryanblenis/MeshCentral-ScriptTask/master/config.json", - "downloadUrl": "https://github.com/ryanblenis/MeshCentral-ScriptTask/archive/master.zip", + "homepage": "https://github.com/panreyes/MeshCentral-ScriptTask", + "changelogUrl": "https://raw.githubusercontent.com/panreyes/MeshCentral-ScriptTask/master/changelog.md", + "configUrl": "https://raw.githubusercontent.com/panreyes/MeshCentral-ScriptTask/master/config.json", + "downloadUrl": "https://github.com/panreyes/MeshCentral-ScriptTask/archive/master.zip", "repository": { "type": "git", - "url": "https://github.com/ryanblenis/MeshCentral-ScriptTask.git" + "url": "https://github.com/panreyes/MeshCentral-ScriptTask.git" }, - "versionHistoryUrl": "https://api.github.com/repos/ryanblenis/MeshCentral-ScriptTask/tags", + "versionHistoryUrl": "https://api.github.com/repos/panreyes/MeshCentral-ScriptTask/tags", "meshCentralCompat": ">=1.1.35" -} \ No newline at end of file +} diff --git a/db.js b/db.js index 1d2cc60..34eb5d1 100644 --- a/db.js +++ b/db.js @@ -285,6 +285,26 @@ module.exports.CreateDB = function(meshserver) { } obj.initFunctions(); }); + } else if (meshserver.args.mariadb) { // use MariaDB + var mariadb = null; + try { mariadb = require('mariadb'); } catch (e) { console.log('PLUGIN: ScriptTask: mariadb module is required but not found.'); } + if (mariadb != null) { + var NEMariaDB = require(__dirname + '/nemariadb.js'); + var m_options = meshserver.args.mariadb; + if (typeof m_options === 'string') { + try { + const urlToConfig = require('mariadb/lib/misc/url-to-config.js'); + m_options = urlToConfig(m_options); + } catch (e) { + // Fallback to letting createPool parse it directly if supported + } + } + if (meshserver.args.mariadbname && typeof m_options === 'object') m_options.database = meshserver.args.mariadbname; + var pool = mariadb.createPool(m_options); + obj.scriptFile = new NEMariaDB(pool); + formatId = function(id) { return id; }; + obj.initFunctions(); + } } else { // use NeDb try { Datastore = require('@seald-io/nedb'); } catch (ex) { } // This is the NeDB with Node 23 support. if (Datastore == null) { diff --git a/modules_meshcore/scripttask.js b/modules_meshcore/scripttask.js index 3143a4a..feab779 100644 --- a/modules_meshcore/scripttask.js +++ b/modules_meshcore/scripttask.js @@ -150,8 +150,8 @@ function runPowerShell(sObj, jObj) { const fs = require('fs'); var rand = Math.random().toString(32).replace('0.', ''); - var oName = 'st' + rand + '.txt'; - var pName = 'st' + rand + '.ps1'; + var oName = 'tmp_st_' + rand + '.txt'; + var pName = 'tmp_st_' + rand + '.ps1'; var pwshout = '', pwsherr = '', cancontinue = false; try { fs.writeFileSync(pName, sObj.content); @@ -221,8 +221,8 @@ function runPowerShellNonWin(sObj, jObj) { dbg('Path chosen is: ' + path); path = path + '/'; - var oName = 'st' + rand + '.txt'; - var pName = 'st' + rand + '.ps1'; + var oName = 'tmp_st_' + rand + '.txt'; + var pName = 'tmp_st_' + rand + '.ps1'; var pwshout = '', pwsherr = '', cancontinue = false; try { var childp = require('child_process').execFile('/bin/sh', ['sh']); @@ -298,8 +298,8 @@ function runBat(sObj, jObj) { } const fs = require('fs'); var rand = Math.random().toString(32).replace('0.', ''); - var oName = 'st' + rand + '.txt'; - var pName = 'st' + rand + '.bat'; + var oName = 'tmp_st_' + rand + '.txt'; + var pName = 'tmp_st_' + rand + '.bat'; try { fs.writeFileSync(pName, sObj.content); var outstr = '', errstr = ''; @@ -372,8 +372,8 @@ function runBash(sObj, jObj) { //child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 }); var rand = Math.random().toString(32).replace('0.', ''); - var oName = 'st' + rand + '.txt'; - var pName = 'st' + rand + '.sh'; + var oName = 'tmp_st_' + rand + '.txt'; + var pName = 'tmp_st_' + rand + '.sh'; try { fs.writeFileSync(path + pName, sObj.content); var outstr = '', errstr = ''; diff --git a/nemariadb.js b/nemariadb.js new file mode 100644 index 0000000..298e915 --- /dev/null +++ b/nemariadb.js @@ -0,0 +1,451 @@ +/** +* @description MeshCentral database abstraction layer for MariaDB +* @author Ryan Blenis +* @copyright +* @license Apache-2.0 +* This is a simple abstraction layer for many commonly used DB calls. +* It routes requests between the legacy JSON table and a dedicated Jobs table. +*/ + +class NEMariaDB { + constructor(pool) { + this.pool = pool; + this._find = null; + this._proj = null; + this._limit = null; + this._sort = null; + + // initialize tables + this._initDB(); + + return this; + } + + _initDB() { + this.pool.query("CREATE TABLE IF NOT EXISTS plugin_scripttask (id VARCHAR(128) PRIMARY KEY, doc JSON)") + .catch(err => { console.log("PLUGIN: ScriptTask: Error creating database table", err); }); + + this.pool.query("CREATE TABLE IF NOT EXISTS plugin_scripttask_jobs (id VARCHAR(128) PRIMARY KEY, type VARCHAR(64) DEFAULT 'job', queueTime BIGINT, dontQueueUntil BIGINT, dispatchTime BIGINT, completeTime BIGINT, node VARCHAR(256), scriptId VARCHAR(128), scriptName VARCHAR(512), replaceVars JSON, returnVal TEXT, errorVal TEXT, returnAct VARCHAR(256), runBy VARCHAR(256), jobSchedule VARCHAR(128))") + .catch(err => { console.log("PLUGIN: ScriptTask: Error creating jobs table", err); }); + } + + _escape(val) { + if (typeof val === 'string') { + return "'" + val.replace(/'/g, "''").replace(/\\/g, "\\\\") + "'"; + } + if (typeof val === 'number') return val; + if (val === null) return "NULL"; + if (typeof val === 'boolean') return val ? 'TRUE' : 'FALSE'; + return "'" + JSON.stringify(val).replace(/'/g, "''").replace(/\\/g, "\\\\") + "'"; + } + + _buildWhereDoc(filter) { + if (!filter || Object.keys(filter).length === 0) return "1=1"; + var conditions = []; + for (var key in filter) { + if (key === '$or') { + var orConds = []; + for (var i in filter.$or) orConds.push("(" + this._buildWhereDoc(filter.$or[i]) + ")"); + conditions.push("(" + orConds.join(" OR ") + ")"); + } else if (key === '$and') { + var andConds = []; + for (var i in filter.$and) andConds.push("(" + this._buildWhereDoc(filter.$and[i]) + ")"); + conditions.push("(" + andConds.join(" AND ") + ")"); + } else { + var val = filter[key]; + var dbKey = key === '_id' ? 'id' : `JSON_UNQUOTE(JSON_EXTRACT(doc, '$.${key}'))`; + + if (val !== null && typeof val === 'object' && !Array.isArray(val)) { + for (var op in val) { + if (op === '$in') { + if (val.$in.length === 0) { + conditions.push('1=0'); + } else { + var inList = val.$in.map(v => this._escape(v)).join(","); + conditions.push(`${dbKey} IN (${inList})`); + } + } else if (op === '$gte') { + conditions.push(`${dbKey} >= ${val.$gte}`); + } else if (op === '$lte') { + conditions.push(`${dbKey} <= ${val.$lte}`); + } else if (op === '$gt') { + conditions.push(`${dbKey} > ${val.$gt}`); + } else if (op === '$lt') { + conditions.push(`${dbKey} < ${val.$lt}`); + } + } + } else if (val === null) { + conditions.push(`(${dbKey} IS NULL OR ${dbKey} = 'null')`); + } else { + conditions.push(`${dbKey} = ${this._escape(val)}`); + } + } + } + return conditions.join(" AND "); + } + + _buildWhereJob(filter) { + if (!filter || Object.keys(filter).length === 0) return "1=1"; + var conditions = []; + for (var key in filter) { + if (key === '$or') { + var orConds = []; + for (var i in filter.$or) orConds.push("(" + this._buildWhereJob(filter.$or[i]) + ")"); + conditions.push("(" + orConds.join(" OR ") + ")"); + } else if (key === '$and') { + var andConds = []; + for (var i in filter.$and) andConds.push("(" + this._buildWhereJob(filter.$and[i]) + ")"); + conditions.push("(" + andConds.join(" AND ") + ")"); + } else { + var val = filter[key]; + var dbKey = key === '_id' ? 'id' : key; + + if (val !== null && typeof val === 'object' && !Array.isArray(val)) { + for (var op in val) { + if (op === '$in') { + if (val.$in.length === 0) { + conditions.push('1=0'); + } else { + var inList = val.$in.map(v => this._escape(v)).join(","); + conditions.push(`${dbKey} IN (${inList})`); + } + } else if (op === '$gte') { + conditions.push(`${dbKey} >= ${val.$gte}`); + } else if (op === '$lte') { + conditions.push(`${dbKey} <= ${val.$lte}`); + } else if (op === '$gt') { + conditions.push(`${dbKey} > ${val.$gt}`); + } else if (op === '$lt') { + conditions.push(`${dbKey} < ${val.$lt}`); + } + } + } else if (val === null) { + conditions.push(`${dbKey} IS NULL`); // In native column, NULL is exactly NULL + } else { + conditions.push(`${dbKey} = ${this._escape(val)}`); + } + } + } + return conditions.join(" AND "); + } + + find(args, proj) { + this._find = args; + this._proj = proj; + this._sort = null; + this._limit = null; + return this; + } + + project(args) { this._proj = args; return this; } + sort(args) { this._sort = args; return this; } + limit(limit) { this._limit = limit; return this; } + + _applyProjection(docs) { + if (!this._proj) return docs; + var keepFields = []; + var excludeFields = []; + for (var p in this._proj) { + if (this._proj[p] === 1) keepFields.push(p); + else if (this._proj[p] === 0) excludeFields.push(p); + } + var ret = []; + for (var doc of docs) { + var pDoc = {}; + if (keepFields.length > 0) { + for (var k of keepFields) { if (doc[k] !== undefined) pDoc[k] = doc[k]; } + if (excludeFields.indexOf('_id') === -1) pDoc._id = doc._id || doc.id; + ret.push(pDoc); + } else { + var nDoc = {...doc}; + for (var k of excludeFields) delete nDoc[k]; + ret.push(nDoc); + } + } + return ret; + } + + toArray(callback) { + var self = this; + return new Promise(function(resolve, reject) { + var isJob = self._find && self._find.type === 'job'; + + var queryJob = () => { + var wJ = self._buildWhereJob(self._find); + var q = `SELECT * FROM plugin_scripttask_jobs WHERE ${wJ}`; + if (self._sort) { + var order = []; + for (var key in self._sort) order.push(`${key === '_id' ? 'id' : key} ${self._sort[key] === -1 ? 'DESC' : 'ASC'}`); + if (order.length > 0) q += " ORDER BY " + order.join(", "); + } + if (self._limit) q += ` LIMIT ${self._limit}`; + return self.pool.query(q).then(rows => { + var docs = []; + for (var r of rows) { + var it = {...r}; + it._id = it.id; delete it.id; + for (var k in it) { + if (typeof it[k] === 'bigint') it[k] = Number(it[k]); + } + if (it.replaceVars && typeof it.replaceVars === 'string') { + try { it.replaceVars = JSON.parse(it.replaceVars); } catch(e) {} + } + docs.push(it); + } + return docs; + }); + }; + + var queryDoc = () => { + var wD = self._buildWhereDoc(self._find); + var q = `SELECT doc FROM plugin_scripttask WHERE ${wD}`; + if (self._sort) { + var order = []; + for (var key in self._sort) { + if (key === '_id') order.push(`id ${self._sort[key] === -1 ? 'DESC' : 'ASC'}`); + else order.push(`JSON_UNQUOTE(JSON_EXTRACT(doc, '$.${key}')) ${self._sort[key] === -1 ? 'DESC' : 'ASC'}`); + } + if (order.length > 0) q += " ORDER BY " + order.join(", "); + } + if (self._limit) q += ` LIMIT ${self._limit}`; + return self.pool.query(q).then(rows => { + var docs = []; + for (var i = 0; i < rows.length; i++) { + var doc = typeof rows[i].doc === 'string' ? JSON.parse(rows[i].doc) : rows[i].doc; + docs.push(doc); + } + return docs; + }); + }; + + var handleResults = (docs) => { + docs = self._applyProjection(docs); + if (callback != null && typeof callback == 'function') callback(null, docs); + resolve(docs); + } + + if (isJob) return queryJob().then(handleResults).catch(reject); + if (self._find && self._find.type && self._find.type !== 'job') return queryDoc().then(handleResults).catch(reject); + + // Generic ID query (or empty query) fallback to both + queryDoc().then(docs => { + if (docs.length > 0) return handleResults(docs); + return queryJob().then(handleResults); + }).catch(reject); + }); + } + + insertOne(args, options) { + var self = this; + return new Promise(function(resolve, reject) { + var id = args._id; + if (!id) { + id = require('crypto').randomBytes(12).toString('hex'); + args._id = id; + } + if (args.type === 'job') { + var cols = ['id']; + var qmarks = ['?']; + var vals = [id]; + for (var k in args) { + if (k === '_id' || k === 'id') continue; + cols.push(k); + qmarks.push('?'); + var v = args[k]; + if (typeof v === 'object' && v !== null) v = JSON.stringify(v); + vals.push(v); + } + self.pool.query(`INSERT INTO plugin_scripttask_jobs (${cols.join(',')}) VALUES (${qmarks.join(',')})`, vals) + .then(res => resolve({ insertedId: id })) + .catch(reject); + } else { + var docStr = JSON.stringify(args); + self.pool.query("INSERT INTO plugin_scripttask (id, doc) VALUES (?, ?)", [id, docStr]) + .then(res => resolve({ insertedId: id })) + .catch(reject); + } + }); + } + + deleteOne(filter, options) { + var self = this; + return new Promise(function(resolve, reject) { + var count = 0; + self.pool.query(`DELETE FROM plugin_scripttask WHERE ${self._buildWhereDoc(filter)} LIMIT 1`) + .then(res => { + count += res.affectedRows; + return self.pool.query(`DELETE FROM plugin_scripttask_jobs WHERE ${self._buildWhereJob(filter)} LIMIT 1`); + }) + .then(res => { + count += res.affectedRows; + resolve({ deletedCount: count }); + }) + .catch(reject); + }); + } + + deleteMany(filter, options) { + var self = this; + return new Promise(function(resolve, reject) { + var count = 0; + self.pool.query(`DELETE FROM plugin_scripttask WHERE ${self._buildWhereDoc(filter)}`) + .then(res => { + count += res.affectedRows; + return self.pool.query(`DELETE FROM plugin_scripttask_jobs WHERE ${self._buildWhereJob(filter)}`); + }) + .then(res => { + count += res.affectedRows; + resolve({ deletedCount: count }); + }) + .catch(reject); + }); + } + + updateOne(filter, update, options) { + var self = this; + if (options == null) options = {}; + if (options.upsert == null) options.upsert = false; + + return new Promise(function(resolve, reject) { + var tryUpdateJob = () => { + var wJ = self._buildWhereJob(filter); + return self.pool.query(`SELECT id FROM plugin_scripttask_jobs WHERE ${wJ} LIMIT 1`) + .then(rows => { + if (rows.length === 0) return { matchedCount: 0, modifiedCount: 0 }; + var updates = [], vals = []; + var src = update.$set ? update.$set : update; + for (var k in src) { + if (k === '_id' || k === 'id') continue; + updates.push(`${k} = ?`); + var v = src[k]; + if (typeof v === 'object' && v !== null) v = JSON.stringify(v); + vals.push(v); + } + if (updates.length > 0) { + vals.push(rows[0].id); + return self.pool.query(`UPDATE plugin_scripttask_jobs SET ${updates.join(', ')} WHERE id = ?`, vals) + .then(() => ({ matchedCount: 1, modifiedCount: 1, upsertedId: rows[0].id })); + } else { + return { matchedCount: 1, modifiedCount: 0 }; + } + }); + }; + + var tryUpdateDoc = () => { + var wD = self._buildWhereDoc(filter); + return self.pool.query(`SELECT id, doc FROM plugin_scripttask WHERE ${wD} LIMIT 1`) + .then(rows => { + if (rows.length === 0) return { matchedCount: 0, modifiedCount: 0 }; + var id = rows[0].id; + var doc = typeof rows[0].doc === 'string' ? JSON.parse(rows[0].doc) : rows[0].doc; + var modified = false; + if (update.$set) { + for (var k in update.$set) doc[k] = update.$set[k]; + modified = true; + } else { + doc = { ...doc, ...update }; + if (!doc._id) doc._id = id; + modified = true; + } + if (modified) { + return self.pool.query("UPDATE plugin_scripttask SET doc = ? WHERE id = ?", [JSON.stringify(doc), id]) + .then(() => ({ matchedCount: 1, modifiedCount: 1, upsertedId: id })); + } else { + return { matchedCount: 1, modifiedCount: 0 }; + } + }); + }; + + var isJob = filter.type === 'job'; + var isDoc = filter.type && filter.type !== 'job'; + + if (isJob) return tryUpdateJob().then(res => { + if (res.matchedCount === 0 && options.upsert) { + var newDoc = { ...filter, ...(update.$set || {}) }; + return self.insertOne(newDoc).then(r => ({matchedCount:0, modifiedCount:1, upsertedId: r.insertedId})); + } + resolve(res); + }).catch(reject); + + if (isDoc) return tryUpdateDoc().then(res => { + if (res.matchedCount === 0 && options.upsert) { + var newDoc = { ...filter, ...(update.$set || {}) }; + return self.insertOne(newDoc).then(r => ({matchedCount:0, modifiedCount:1, upsertedId: r.insertedId})); + } + resolve(res); + }).catch(reject); + + // generic branch + tryUpdateDoc().then(res => { + if (res.matchedCount > 0) return resolve(res); + return tryUpdateJob().then(res2 => { + if (res2.matchedCount === 0 && options.upsert) { + var newDoc = { ...filter, ...(update.$set || {}) }; // fallback to insert doc + return self.insertOne(newDoc).then(r => resolve({matchedCount:0, modifiedCount:1, upsertedId: r.insertedId})); + } + resolve(res2); + }); + }).catch(reject); + }); + } + + updateMany(filter, update, options) { + var self = this; + if (options == null) options = {}; + if (options.upsert == null) options.upsert = false; + + return new Promise(function(resolve, reject) { + var tryUpdateJob = () => { + var wJ = self._buildWhereJob(filter); + return self.pool.query(`SELECT id FROM plugin_scripttask_jobs WHERE ${wJ}`) + .then(rows => { + if (rows.length === 0) return { matchedCount: 0, modifiedCount: 0 }; + var updatesQ = [], vals = []; + var src = update.$set ? update.$set : update; + for (var k in src) { + if (k === '_id' || k === 'id') continue; + updatesQ.push(`${k} = ?`); + var v = src[k]; + if (typeof v === 'object' && v !== null) v = JSON.stringify(v); + vals.push(v); + } + if (updatesQ.length > 0) { + var proms = rows.map(r => self.pool.query(`UPDATE plugin_scripttask_jobs SET ${updatesQ.join(', ')} WHERE id = ?`, [...vals, r.id])); + return Promise.all(proms).then(() => ({ matchedCount: rows.length, modifiedCount: rows.length })); + } else { + return { matchedCount: rows.length, modifiedCount: 0 }; + } + }); + }; + + var tryUpdateDoc = () => { + var wD = self._buildWhereDoc(filter); + return self.pool.query(`SELECT id, doc FROM plugin_scripttask WHERE ${wD}`) + .then(rows => { + if (rows.length === 0) return { matchedCount: 0, modifiedCount: 0 }; + var proms = rows.map(r => { + var doc = typeof r.doc === 'string' ? JSON.parse(r.doc) : r.doc; + if (update.$set) { + for (var k in update.$set) doc[k] = update.$set[k]; + } else { + doc = { ...doc, ...update }; + if (!doc._id) doc._id = r.id; + } + return self.pool.query("UPDATE plugin_scripttask SET doc = ? WHERE id = ?", [JSON.stringify(doc), r.id]); + }); + return Promise.all(proms).then(() => ({ matchedCount: rows.length, modifiedCount: rows.length })); + }); + }; + + var isJob = filter.type === 'job'; + if (isJob) return tryUpdateJob().then(res => resolve(res)).catch(reject); + return tryUpdateDoc().then(res => resolve(res)).catch(reject); + }); + } + + indexes(callback) { if (callback != null && typeof callback == 'function') callback(null, []); } + dropIndexes(callback) { if (callback != null && typeof callback == 'function') callback(null); } + createIndex(args, options) { } +} + +module.exports = NEMariaDB; diff --git a/readme.md b/readme.md index a289b34..a21276e 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,5 @@ # MeshCentral-ScriptTask +# Forked to add support to MariaDB! A script running plugin for the [MeshCentral2](https://github.com/Ylianst/MeshCentral) Project. The plugin supports PowerShell, BAT, and Bash scripts. Windows, MacOS, and Linux endpoints are all supported. PowerShell can be run on any OS that has PowerShell installed, not just Windows. @@ -14,7 +15,7 @@ A script running plugin for the [MeshCentral2](https://github.com/Ylianst/MeshCe Restart your MeshCentral server after making this change. To install, simply add the plugin configuration URL when prompted: - `https://raw.githubusercontent.com/ryanblenis/MeshCentral-ScriptTask/master/config.json` + `https://raw.githubusercontent.com/panreyes/MeshCentral-ScriptTask/master/config.json` ## Features - Add scripts to a central store diff --git a/scripttask.js b/scripttask.js index 609e720..b3c7f88 100644 --- a/scripttask.js +++ b/scripttask.js @@ -137,7 +137,7 @@ module.exports.scripttask = function (parent) { .then(found => { if (found.length != 1) { res.sendStatus(401); return; } var file = found[0]; - res.setHeader('Content-disposition', 'attachment; filename=' + file.name); + res.setHeader('Content-disposition', 'attachment; filename=' + file.name + "." + file.filetype + ".txt"); res.setHeader('Content-type', 'text/plain'); //var fs = require('fs'); res.send(file.content); diff --git a/views/user.handlebars b/views/user.handlebars index f69a731..8f5b92d 100644 --- a/views/user.handlebars +++ b/views/user.handlebars @@ -1,6 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
- New - Rename - Edit - Delete - New Folder - Download - Run + New + New Folder + Edit + Rename + Download + Delete + Run now
-
Advanced Run
-
-
+ Advanced Run + Node Schedules + Node History + Variables + Script Schedules + Script History + +
+
@@ -211,37 +279,32 @@
-
Node Schedules
ScriptAuthorEveryStartingEndingLast RunNext RunAction
-
Script Schedules
NodeAuthorEveryStartingEndingLast RunNext RunAction
-
Node History
TimeRun ByScriptStatusReturn Value
-
Script History
TimeRun ByNodeStatusReturn Value
-
Variables
Variable NameValueScopeScope TargetAction

- [+] + Add variable
@@ -272,6 +335,7 @@ function updateNodesTable() { }); var tagList = []; var nodeRowIns = document.querySelector('#mRunTbl'); + parent.nodes.sort((a,b) => (a.name > b.name) ? 1 : -1); parent.nodes.forEach(function(i) { var item = {...i, ...{}}; if (item.mtype == 2) { @@ -286,6 +350,17 @@ function updateNodesTable() { } }); tagList = tagList.filter(onlyUnique); tagList = tagList.sort(); + // parent.meshes.sort((a,b) => a.name.localeCompare(b.name)); + + // sort meshes by name + const sortedEntries = Object.entries(parent.meshes).sort(([, v1], [, v2]) => { + const a = v1 && v1.name ? v1.name : ""; + const b = v2 && v2.name ? v2.name : ""; + return a > b ? 1 : a < b ? -1 : 0; + }); + + parent.meshes = Object.fromEntries(sortedEntries); + var nodeRowIns = document.querySelector('#mRunTblMesh'); for (const i in parent.meshes) { // parent.meshes.forEach(function(i) { var item = {...parent.meshes[i], ...{}}; @@ -359,7 +434,7 @@ function goRun() { var selScript = document.querySelectorAll('.liselected'); if (selScript.length) { var scriptId = selScript[0].getAttribute('x-data-id'); - if (scriptId == selScript[0].getAttribute('x-folder-id')) + if (scriptId == selScript[0].getAttribute('x-data-folder')) { parent.setDialogMode(2, "Oops!", 1, null, 'Please select a script. A folder is currently selected.'); } @@ -420,14 +495,27 @@ function goAdvancedRun() { var coll = document.getElementsByClassName("infoBar"); for (var i = 0; i < coll.length; i++) { coll[i].addEventListener("click", function() { + for (var j = 0; j < coll.length; j++) { + coll[j].classList.remove("active"); + //coll[j].nextElementSibling.style.display = "none"; + } + $('#multiRun').hide(200); + $('#nSch').hide(200); + $('#nodeHistory').hide(200); + $('#variables').hide(200); + $('#sSch').hide(200); + $('#scriptHistory').hide(200); + this.classList.toggle("active"); - var content = this.nextElementSibling; + //var content = this.nextElementSibling; + // var content = this; + var content = document.getElementById(this.id.slice(this.id.lastIndexOf('_') + 1)); if (content.style.display === "block") { content.style.display = "none"; } else { content.style.display = "block"; } - content.style.maxHeight = '300px'; + content.style.maxHeight = '400px'; content.style.overflowY = 'scroll'; resizeIframe(); }); @@ -441,6 +529,7 @@ function goDownload() { if (id == sel.getAttribute('x-data-folder')) return; window.location = '/pluginadmin.ashx?pin=scripttask&user=1&dl='+id; } + function addScript(name, content, path) { // file type testing var n = name.split('.').pop().toLowerCase(); @@ -452,6 +541,7 @@ function addScript(name, content, path) { parent.setDialogMode(2, "Oops!", 1, null, 'Currently accepted filetypes are .ps1, .bat, and bash scripts.'); } } + function redrawScriptTree() { var lastpath = null; var str = ''; @@ -513,14 +603,14 @@ function redrawScriptTree() { message.event.nodeHistory.forEach(function(nh) { nh.latestTime = Math.max(nh.completeTime, nh.queueTime, nh.dispatchTime, nh.dontQueueUntil); }); - message.event.nodeHistory.sort((a, b) => (a.latestTime < b.latestTime) ? 1 : -1); + message.event.nodeHistory.sort((a, b) => a.name.localeCompare(b.name)); message.event.nodeHistory.forEach(function(nh) { nh = prepHistory(nh); let tpl = '' + nh.timeStr + ' \ ' + nh.runBy + ' \ ' + nh.scriptName + ' \ ' + nh.statusTxt + ' \ - ' + nh.returnTxt + ''; +
' + nh.returnTxt + '
'; let tr = nHistTbl.insertRow(-1); tr.innerHTML = tpl; tr.classList.add('stNHRow'); @@ -548,7 +638,7 @@ function redrawScriptTree() { ' + nh.runBy + ' \ ' + nNames[nh.node] + ' \ ' + nh.statusTxt + ' \ - ' + nh.returnTxt + ''; +
' + nh.returnTxt + '
'; let tr = sHistTbl.insertRow(-1); tr.innerHTML = tpl; tr.classList.add('stSHRow'); @@ -598,6 +688,7 @@ function redrawScriptTree() { break; case 'script': var s = scriptTree.filter(obj => { return obj._id === vd.scopeTarget })[0] + if (s === undefined) { return; } vd.scopeTargetHtml = '' + s.name + ''; vd.scopeTargetTxt = s.name; break; @@ -606,8 +697,14 @@ function redrawScriptTree() { break; case 'node': var n = parent.nodes.filter(obj => { return obj._id === vd.scopeTarget })[0] - vd.scopeTargetHtml = '' + n.name + ''; - vd.scopeTargetTxt = n.name; + if (n === undefined) { + console.log("No existe el nodo " + vd.scopeTarget); + parent.meshserver.send({ action: 'plugin', plugin: 'scripttask', pluginaction: 'deleteVar', id: vd._id, currentNodeId: parent.currentNode._id }); + vd = null; + return; + } + vd.scopeTargetHtml = '' + n.name + ''; + vd.scopeTargetTxt = n.name; break; default: vd.scopeTargetTxt = vd.scopeTargetHtml = 'N/A'; @@ -639,10 +736,11 @@ function redrawScriptTree() { var el = scriptEl[0]; scopeTargetScriptId = el.getAttribute('x-data-id'); variables.forEach(function(vd) { + if (vd === null) return; if (vd.scope == 'script' && vd.scopeTarget != scopeTargetScriptId) return; if (vd.scope == 'mesh' && vd.scopeTarget != parent.currentNode.meshid) return; if (vd.scope == 'node' && vd.scopeTarget != parent.currentNode._id) return; - let actionHtml = 'Edit Delete'; + let actionHtml = 'Edit Delete'; let tpl = '' + vd.name + ' \ ' + vd.value + ' \ ' + vd.scopeTxt + ' \ @@ -689,6 +787,7 @@ function redrawScriptTree() { }); } } + var currentScript = document.getElementById('scriptHistory'); var currentScriptId = currentScript.getAttribute('x-data-id'); if (message.event.scriptSchedule != null && message.event.scriptId == currentScriptId) {