From 0999c32e4481ae3e2f82adf9defceec84754ebd0 Mon Sep 17 00:00:00 2001 From: taravat Date: Sun, 30 Nov 2025 13:32:32 +0330 Subject: [PATCH 1/5] Added: Check for new devices added before scheduled task for all devices and groups --- scripttask.js | 279 ++++++++++++++++++++++++----------------- views/user.handlebars | 285 +++++++++++++++++++++++------------------- 2 files changed, 324 insertions(+), 240 deletions(-) diff --git a/scripttask.js b/scripttask.js index 609e720..d32931c 100644 --- a/scripttask.js +++ b/scripttask.js @@ -1,7 +1,7 @@ -/** +/** * @description MeshCentral ScriptTask * @author Ryan Blenis -* @copyright +* @copyright * @license Apache-2.0 */ @@ -15,31 +15,31 @@ module.exports.scripttask = function (parent) { obj.intervalTimer = null; obj.debug = obj.meshServer.debug; obj.VIEWS = __dirname + '/views/'; - obj.exports = [ + obj.exports = [ 'onDeviceRefreshEnd', 'resizeContent', 'historyData', 'variableData', 'malix_triggerOption' ]; - + obj.malix_triggerOption = function(selectElem) { selectElem.options.add(new Option("ScriptTask - Run Script", "scripttask_runscript")); } obj.malix_triggerFields_scripttask_runscript = function() { - + } obj.resetQueueTimer = function() { clearTimeout(obj.intervalTimer); obj.intervalTimer = setInterval(obj.queueRun, 1 * 60 * 1000); // every minute }; - + obj.server_startup = function() { obj.meshServer.pluginHandler.scripttask_db = require (__dirname + '/db.js').CreateDB(obj.meshServer); obj.db = obj.meshServer.pluginHandler.scripttask_db; obj.resetQueueTimer(); }; - + obj.onDeviceRefreshEnd = function() { pluginHandler.registerPluginTab({ tabTitle: 'ScriptTask', @@ -56,7 +56,7 @@ module.exports.scripttask = function (parent) { //if (newHeight > 1600) newHeight = 1600; iFrame.style.height = newHeight + 'px'; }; - + obj.queueRun = async function() { var onlineAgents = Object.keys(obj.meshServer.webserver.wsagents); //obj.debug('ScriptTask', 'Queue Running', Date().toLocaleString(), 'Online agents: ', onlineAgents); @@ -66,6 +66,7 @@ module.exports.scripttask = function (parent) { if (jobs.length == 0) return; //@TODO check for a large number and use taskLimiter to queue the jobs jobs.forEach(job => { + obj.db.get(job.scriptId) .then(async (script) => { script = script[0]; @@ -76,8 +77,8 @@ module.exports.scripttask = function (parent) { foundVars.forEach(fv => { foundVarNames.push(fv.replace(/^#+|#+$/g, '')); }); - - var limiters = { + + var limiters = { scriptId: job.scriptId, nodeId: job.node, meshId: obj.meshServer.webserver.wsagents[job.node]['dbMeshKey'], @@ -94,13 +95,13 @@ module.exports.scripttask = function (parent) { }); replaceVars['GBL:meshId'] = obj.meshServer.webserver.wsagents[job.node]['dbMeshKey']; replaceVars['GBL:nodeId'] = job.node; - //console.log('FV IS', finvals); - //console.log('RV IS', replaceVars); + console.log('FV IS', finvals); + console.log('RV IS', replaceVars); } var dispatchTime = Math.floor(new Date() / 1000); - var jObj = { - action: 'plugin', - plugin: 'scripttask', + var jObj = { + action: 'plugin', + plugin: 'scripttask', pluginaction: 'triggerJob', jobId: job._id, scriptId: job.scriptId, @@ -109,7 +110,7 @@ module.exports.scripttask = function (parent) { dispatchTime: dispatchTime }; //obj.debug('ScriptTask', 'Sending job to agent'); - try { + try { obj.meshServer.webserver.wsagents[job.node].send(JSON.stringify(jObj)); obj.db.update(job._id, { dispatchTime: dispatchTime }); } catch (e) { } @@ -123,14 +124,14 @@ module.exports.scripttask = function (parent) { }) .catch(e => { console.log('PLUGIN: ScriptTask: Queue Run Error: ', e); }); }; - + obj.cleanHistory = function() { if (Math.round(Math.random() * 100) == 99) { //obj.debug('Plugin', 'ScriptTask', 'Running history cleanup'); obj.db.deleteOldHistory(); } }; - + obj.downloadFile = function(req, res, user) { var id = req.query.dl; obj.db.get(id) @@ -143,7 +144,7 @@ module.exports.scripttask = function (parent) { res.send(file.content); }); }; - + obj.updateFrontEnd = async function(ids){ if (ids.scriptId != null) { var scriptHistory = null; @@ -184,9 +185,9 @@ module.exports.scripttask = function (parent) { }); } }; - + obj.handleAdminReq = function(req, res, user) { - if ((user.siteadmin & 0xFFFFFFFF) == 1 && req.query.admin == 1) + if ((user.siteadmin & 0xFFFFFFFF) == 1 && req.query.admin == 1) { // admin wants admin, grant var vars = {}; @@ -194,15 +195,15 @@ module.exports.scripttask = function (parent) { return; } else if (req.query.admin == 1 && (user.siteadmin & 0xFFFFFFFF) == 0) { // regular user wants admin - res.sendStatus(401); + res.sendStatus(401); return; - } else if (req.query.user == 1) { + } else if (req.query.user == 1) { // regular user wants regular access, grant if (req.query.dl != null) return obj.downloadFile(req, res, user); var vars = {}; - + if (req.query.edit == 1) { // edit script - if (req.query.id == null) return res.sendStatus(401); + if (req.query.id == null) return res.sendStatus(401); obj.db.get(req.query.id) .then((scripts) => { if (scripts[0].filetype == 'proc') { @@ -235,23 +236,23 @@ module.exports.scripttask = function (parent) { res.sendFile(__dirname + '/includes/' + req.query.path); // don't freak out. Express covers any path issues. return; } - res.sendStatus(401); + res.sendStatus(401); return; }; - + obj.historyData = function (message) { if (typeof pluginHandler.scripttask.loadHistory == 'function') pluginHandler.scripttask.loadHistory(message); if (typeof pluginHandler.scripttask.loadSchedule == 'function') pluginHandler.scripttask.loadSchedule(message); }; - + obj.variableData = function (message) { if (typeof pluginHandler.scripttask.loadVariables == 'function') pluginHandler.scripttask.loadVariables(message); }; - + obj.determineNextJobTime = function(s) { var nextTime = null; var nowTime = Math.floor(new Date() / 1000); - + // special case: we've reached the end of our run if (s.endAt !== null && s.endAt <= nowTime) { return nextTime; @@ -313,7 +314,7 @@ module.exports.scripttask = function (parent) { case 'weekly': var tempDate = new Date(); var nowDate = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate()); - + if (s.daysOfWeek.length == 0) { nextTime = null; break; @@ -346,7 +347,7 @@ module.exports.scripttask = function (parent) { var checkDate = baseTime + (86400 * x); var d = new Date(0); d.setUTCSeconds(checkDate); var dm = new Date(d.getFullYear(), d.getMonth(), d.getDate()); - + console.log('testing date: ', dm.toLocaleString()); // dMidnight.toLocaleString()); //console.log('if break check :', (s.daysOfWeek.indexOf(d.getDay()) !== -1 && checkDate >= nowTime)); //console.log('checkDate vs nowTime: ', (checkDate - nowTime), ' if positive, nowTime is less than checkDate'); @@ -364,52 +365,106 @@ module.exports.scripttask = function (parent) { nextTime = null; break; } - + if (s.endAt != null && nextTime > s.endAt) nextTime = null; // if the next time reaches the bound of the endAt time, nullify - + return nextTime; }; obj.makeJobsFromSchedules = function(scheduleId) { - //obj.debug('ScriptTask', 'makeJobsFromSchedules starting'); return obj.db.getSchedulesDueForJob(scheduleId) - .then(schedules => { - //obj.debug('ScriptTask', 'Found ' + schedules.length + ' schedules to process. Current time is: ' + Math.floor(new Date() / 1000)); - if (schedules.length) { - schedules.forEach(s => { - var nextJobTime = obj.determineNextJobTime(s); - var nextJobScheduled = false; - if (nextJobTime === null) { - //obj.debug('ScriptTask', 'Removing Job Schedule for', JSON.stringify(s)); - obj.db.removeJobSchedule(s._id); - } else { - //obj.debug('ScriptTask', 'Scheduling Job for', JSON.stringify(s)); - obj.db.get(s.scriptId) - .then(scripts => { - // if a script is scheduled to run, but a previous run hasn't completed, - // don't schedule another job for the same (device probably offline). - // results in the minimum jobs running once an agent comes back online. - return obj.db.getIncompleteJobsForSchedule(s._id) - .then((jobs) => { - if (jobs.length > 0) { /* obj.debug('Plugin', 'ScriptTask', 'Skipping job creation'); */ return Promise.resolve(); } - else { /* obj.debug('Plugin', 'ScriptTask', 'Creating new job'); */ nextJobScheduled = true; return obj.db.addJob( { scriptId: s.scriptId, scriptName: scripts[0].name, node: s.node, runBy: s.scheduledBy, dontQueueUntil: nextJobTime, jobSchedule: s._id } ); } - }); - }) - .then(() => { - - if (nextJobScheduled) { /* obj.debug('Plugin', 'ScriptTask', 'Updating nextRun time'); */ return obj.db.update(s._id, { nextRun: nextJobTime }); } - else { /* obj.debug('Plugin', 'ScriptTask', 'NOT updating nextRun time'); */ return Promise.resolve(); } - }) - .then(() => { - obj.updateFrontEnd( { scriptId: s.scriptId, nodeId: s.node } ); - }) - .catch((e) => { console.log('PLUGIN: ScriptTask: Error managing job schedules: ', e); }); - } - }); - } - }); + .then(schedules => { + if (schedules.length > 0) { + console.log('PLUGIN DEBUG: Found ' + schedules.length + ' schedules due.'); + + schedules.forEach(s => { + var nextJobTime = obj.determineNextJobTime(s); + var executeImmediatelyTime = nextJobTime - 60; + if (nextJobTime === null) { + obj.db.removeJobSchedule(s._id); + } else { + obj.db.get(s.scriptId) + .then(scripts => { + if (!scripts || scripts.length === 0) return; + var scriptName = scripts[0].name; + if (s.node === 'all') { + obj.meshServer.db.GetAllType('node', function(err, docs) { + if (docs && docs.length > 0) { + createJobsForList(docs, s, scriptName, executeImmediatelyTime, nextJobTime); + } + }); + } + + else if (typeof s.node === 'string' && s.node.startsWith('mesh/')) { + console.log('PLUGIN DEBUG: Schedule is for a MESH (Group):', s.node); + obj.meshServer.db.GetAllType('node', function(err, docs) { + if (docs && docs.length > 0) { + console.log(docs) + var meshNodes = docs.filter(function(d) { return d.meshid === s.node; }); + + console.log('PLUGIN DEBUG: Nodes found in this group:', meshNodes.length); + + if (meshNodes.length > 0) { + createJobsForList(meshNodes, s, scriptName, executeImmediatelyTime, nextJobTime); + } else { + + obj.db.update(s._id, { nextRun: nextJobTime }); + } + } + }); + } + + else { + return obj.db.getIncompleteJobsForSchedule(s._id) + .then((jobs) => { + if (jobs.length > 0) { return Promise.resolve(); } + else { + return obj.db.addJob({ + scriptId: s.scriptId, + scriptName: scriptName, + node: s.node, + runBy: s.scheduledBy, + dontQueueUntil: executeImmediatelyTime, + jobSchedule: s._id + }); + } + }) + .then(() => { + return obj.db.update(s._id, { nextRun: nextJobTime }); + }) + .then(() => { + obj.updateFrontEnd( { scriptId: s.scriptId, nodeId: s.node } ); + }); + } + }) + .catch((e) => { console.log('PLUGIN Error:', e); }); + } + }); + } + }); }; - + + + function createJobsForList(nodeList, schedule, scriptName, execTime, nextTime) { + var jobProms = []; + nodeList.forEach(function(device) { + jobProms.push(obj.db.addJob({ + scriptId: schedule.scriptId, + scriptName: scriptName, + node: device._id, + runBy: schedule.scheduledBy, + dontQueueUntil: execTime, + jobSchedule: schedule._id + })); + }); + + Promise.all(jobProms).then(() => { + console.log('PLUGIN DEBUG: Jobs created for list. Updating next run.'); + obj.db.update(schedule._id, { nextRun: nextTime }); + obj.updateFrontEnd({ scriptId: schedule.scriptId }); + }); + } + obj.deleteElement = function (command) { var delObj = null; obj.db.get(command.id) @@ -436,14 +491,14 @@ module.exports.scripttask = function (parent) { }) .catch(e => { console.log('PLUGIN: ScriptTask: Error deleting ', e.stack); }); }; - + obj.serveraction = function(command, myparent, grandparent) { switch (command.pluginaction) { case 'addScript': obj.db.addScript(command.name, command.content, command.path, command.filetype) .then(() => { obj.updateFrontEnd( { tree: true } ); - }); + }); break; case 'new': var parent_path = ''; @@ -539,7 +594,7 @@ module.exports.scripttask = function (parent) { case 'newFolder': var parent_path = ''; var new_path = ''; - + obj.db.get(command.parent_id) .then(found => { if (found.length > 0) { @@ -564,26 +619,12 @@ module.exports.scripttask = function (parent) { obj.deleteElement(command); break; case 'addScheduledJob': - /* { - scriptId: scriptId, - node: s, - scheduledBy: myparent.user.name, - recur: command.recur, // [once, minutes, hourly, daily, weekly, monthly] - interval: x, - daysOfWeek: x, // only used for weekly recur val - // onTheXDay: x, // only used for monthly - startAt: x, - endAt: x, - runCountLimit: x, - lastRun: x, - nextRun: x, - type: "scheduledJob" - } */ var sj = command.schedule; - - var sched = { - scriptId: command.scriptId, - node: null, + var sel = command.nodes; + + var sObj = { + scriptId: command.scriptId, + node: null, scheduledBy: myparent.user.name, recur: sj.recur, interval: sj.interval, @@ -594,25 +635,35 @@ module.exports.scripttask = function (parent) { nextRun: null, type: "jobSchedule" }; - var sel = command.nodes; + var proms = []; - if (Array.isArray(sel)) { - sel.forEach((s) => { - var sObj = {...sched, ...{ - node: s - }}; - proms.push(obj.db.addJobSchedule( sObj )); - }); - } else { test.push(sObj); - proms.push(obj.db.addJobSchedule( sObj )); + + + if (sel === 'all') { + + sObj.node = 'all'; + proms.push(obj.db.addJobSchedule(sObj)); } + else if (Array.isArray(sel)) { + + sel.forEach((s) => { + var nodeSched = {...sObj, node: s}; + proms.push(obj.db.addJobSchedule(nodeSched)); + }); + } else { + + sObj.node = sel; + proms.push(obj.db.addJobSchedule(sObj)); + } + + Promise.all(proms) - .then(() => { - obj.makeJobsFromSchedules(); - return Promise.resolve(); - }) - .catch(e => { console.log('PLUGIN: ScriptTask: Error adding schedules. The error was: ', e); }); - break; + .then(() => { + obj.makeJobsFromSchedules(); + return Promise.resolve(); + }) + .catch(e => { console.log('PLUGIN: ScriptTask: Error adding schedules. The error was: ', e); }); + break; case 'runScript': var scriptId = command.scriptId; var sel = command.nodes; @@ -641,7 +692,7 @@ module.exports.scripttask = function (parent) { //obj.debug('ScriptTask', 'getScript Triggered', JSON.stringify(command)); obj.db.get(command.scriptId) .then(script => { - myparent.send(JSON.stringify({ + myparent.send(JSON.stringify({ action: 'plugin', plugin: 'scripttask', pluginaction: 'cacheScript', @@ -707,9 +758,9 @@ module.exports.scripttask = function (parent) { }) break; case 'editVar': - obj.db.update(command.id, { - name: command.name, - scope: command.scope, + obj.db.update(command.id, { + name: command.name, + scope: command.scope, scopeTarget: command.scopeTarget, value: command.value }) @@ -728,6 +779,6 @@ module.exports.scripttask = function (parent) { break; } }; - + return obj; } diff --git a/views/user.handlebars b/views/user.handlebars index f69a731..336d89c 100644 --- a/views/user.handlebars +++ b/views/user.handlebars @@ -13,7 +13,7 @@ width: 100%; position: absolute; float:left;*/ - + width: 100%; overflow: hidden; position: absolute; @@ -202,7 +202,10 @@
+ + +
Meshes
@@ -210,6 +213,7 @@ Tags
+
Node Schedules
@@ -244,7 +248,7 @@ [+]
- + @@ -258,7 +262,7 @@ var nodesObj = {}; var variables = []; var varScopes = { global: 'Global', script: 'Script', mesh: 'Mesh', node: 'Node' }; -function onlyUnique(value, index, self) { +function onlyUnique(value, index, self) { return self.indexOf(value) === index; } function resizeIframe() { @@ -287,11 +291,12 @@ function updateNodesTable() { }); tagList = tagList.filter(onlyUnique); tagList = tagList.sort(); var nodeRowIns = document.querySelector('#mRunTblMesh'); + for (const i in parent.meshes) { // parent.meshes.forEach(function(i) { var item = {...parent.meshes[i], ...{}}; if (item.mtype == 2) { let tpl = ` - + `; nodeRowIns.insertAdjacentHTML('beforeend', tpl); } @@ -311,26 +316,17 @@ function selNodesByTag(el) { var checked = false; if (el.checked) checked = true; allNodes.forEach(function(n) { - if (nodesObj[n.value].tags && nodesObj[n.value].tags.indexOf(t) > -1) n.checked = checked; - }); - return true; -} -function selNodesByMesh(el) { - var mid = el.value; - var allNodes = Q('mRunTbl').querySelectorAll('input[type="checkbox"][name="runOn[]"]'); - var checked = false; - if (el.checked) checked = true; - allNodes.forEach(function(n) { - if (nodesObj[n.value].meshid == mid) n.checked = checked; + if (nodesObj[n.value].tags && nodesObj[n.value].tags.indexOf(t) > -1) n.checked = checked; }); return true; } + function selAllNodes(el) { var allNodes = Q('mRunTbl').querySelectorAll('input[type="checkbox"][name="runOn[]"]'); var checked = false; if (el.checked) checked = true; allNodes.forEach(function(n) { - n.checked = checked; + n.checked = checked; }); return true; } @@ -340,7 +336,7 @@ function doOnLoad() { selectPreviouslySelectedScript(); updateNodesTable(); parent.meshserver.send({ 'action': 'plugin', 'plugin': 'scripttask', 'pluginaction': 'loadNodeHistory', 'nodeId': parent.currentNode._id }); - parent.meshserver.send({ 'action': 'plugin', 'plugin': 'scripttask', 'pluginaction': 'loadVariables', 'nodeId': parent.currentNode._id }); + parent.meshserver.send({ 'action': 'plugin', 'plugin': 'scripttask', 'pluginaction': 'loadVariables', 'nodeId': parent.currentNode._id }); } function selectPreviouslySelectedScript() { @@ -361,13 +357,13 @@ function goRun() { var scriptId = selScript[0].getAttribute('x-data-id'); if (scriptId == selScript[0].getAttribute('x-folder-id')) { - parent.setDialogMode(2, "Oops!", 1, null, 'Please select a script. A folder is currently selected.'); + parent.setDialogMode(2, "Oops!", 1, null, 'Please select a script. A folder is currently selected.'); } else { parent.meshserver.send({ 'action': 'plugin', 'plugin': 'scripttask', 'pluginaction': 'runScript', 'scriptId': scriptId, 'nodes': [ parent.currentNode._id ], 'currentNodeId': parent.currentNode._id }); } } else { - parent.setDialogMode(2, "Oops!", 1, null, 'No script has been selected to run on the machines.'); + parent.setDialogMode(2, "Oops!", 1, null, 'No script has been selected to run on the machines.'); } } @@ -380,43 +376,66 @@ function goEdit() { parent.meshserver.send({ 'action': 'plugin', 'plugin': 'scripttask', 'pluginaction': 'editScript', 'scriptId': sd._id, 'scriptType': sd.type, 'scriptName': sd.name, 'scriptContent': sd.content, 'currentNodeId': parent.currentNode._id }); }; } else { - parent.setDialogMode(2, "Oops!", 1, null, 'No script has been selected to edit.'); + parent.setDialogMode(2, "Oops!", 1, null, 'No script has been selected to edit.'); } } -function goAdvancedRun() { - var cboxes = document.getElementsByName("runOn[]"); - var sel = []; - - cboxes.forEach((n) => { - if (n.checked) sel.push(n.value); - }); - if (sel.length == 0) { - parent.setDialogMode(2, "Oops!", 1, null, 'No machines have been selected.'); - return; - } - var selScript = document.querySelectorAll('.liselected'); - if (selScript.length) { + function goAdvancedRun() { + + var alls = document.getElementById("all") ? document.getElementById("all").checked : false; + var sel = []; + + if (alls) { + sel = "all"; +} else { + var meshCheckboxes = document.querySelectorAll('#mRunTblMesh input[type="checkbox"]:not(#all)'); + var selectedMeshIds = []; + meshCheckboxes.forEach((box) => { + if (box.checked) { + sel.push(box.value); + selectedMeshIds.push(box.value); +} +}); + + var nodeCheckboxes = document.getElementsByName("runOn[]"); + nodeCheckboxes.forEach((n) => { + if (n.checked) { + var nodeData = nodesObj[n.value]; + if (nodeData && selectedMeshIds.includes(nodeData.meshid)) { +} else { + sel.push(n.value); +} +} +}); +} + + if (sel.length == 0 && sel !== 'all') { + parent.setDialogMode(2, "Oops!", 1, null, 'No machines or groups have been selected.'); + return; +} + + var selScript = document.querySelectorAll('.liselected'); + if (selScript.length) { var scriptId = selScript[0].getAttribute('x-data-id'); var sWin = window.open('/pluginadmin.ashx?pin=scripttask&user=1&schedule=1', 'schedule', "width=800,height=600"); sWin.scriptId = scriptId; sWin.nodes = sel; + window.schedCallback = function(opts) { - parent.meshserver.send({ - 'action': 'plugin', - 'plugin': 'scripttask', - 'pluginaction': 'addScheduledJob', - 'scriptId': opts.scriptId, - 'nodes': opts.nodes, - 'currentNodeId': parent.currentNode._id, - 'schedule': opts - }); - }; - } else { - parent.setDialogMode(2, "Oops!", 1, null, 'No script has been selected to run on the machines.'); - } + parent.meshserver.send({ + 'action': 'plugin', + 'plugin': 'scripttask', + 'pluginaction': 'addScheduledJob', + 'scriptId': opts.scriptId, + 'nodes': opts.nodes, + 'currentNodeId': parent.currentNode._id, + 'schedule': opts +}); +}; +} else { + parent.setDialogMode(2, "Oops!", 1, null, 'No script has been selected.'); +} } - var coll = document.getElementsByClassName("infoBar"); for (var i = 0; i < coll.length; i++) { coll[i].addEventListener("click", function() { @@ -449,7 +468,7 @@ function addScript(name, content, path) { parent.meshserver.send({ action: 'plugin', plugin: 'scripttask', pluginaction: 'addScript', name: name, content: content, path: path, filetype: n }); } else { - parent.setDialogMode(2, "Oops!", 1, null, 'Currently accepted filetypes are .ps1, .bat, and bash scripts.'); + parent.setDialogMode(2, "Oops!", 1, null, 'Currently accepted filetypes are .ps1, .bat, and bash scripts.'); } } function redrawScriptTree() { @@ -498,20 +517,20 @@ function redrawScriptTree() { parent.pluginHandler.scripttask.loadHistory = function(message) { // cache script names var nNames = {}; - parent.nodes.forEach(function(n){ - nNames[n._id] = n.name; + parent.nodes.forEach(function(n){ + nNames[n._id] = n.name; }); if (message.event.nodeHistory != null && message.event.nodeId == parent.currentNode._id) { var nHistTbl = document.getElementById('nHistTbl'); var rows = nHistTbl.querySelectorAll('.stNHRow'); if (rows.length) { rows.forEach(function(r) { - r.parentNode.removeChild(r); + r.parentNode.removeChild(r); }); } if (message.event.nodeHistory.length) { message.event.nodeHistory.forEach(function(nh) { - nh.latestTime = Math.max(nh.completeTime, nh.queueTime, nh.dispatchTime, nh.dontQueueUntil); + 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.forEach(function(nh) { @@ -534,12 +553,12 @@ function redrawScriptTree() { var rows = sHistTbl.querySelectorAll('.stSHRow'); if (rows.length) { rows.forEach(function(r) { - r.parentNode.removeChild(r); + r.parentNode.removeChild(r); }); } if (message.event.scriptHistory.length) { message.event.scriptHistory.forEach(function(nh) { - nh.latestTime = Math.max(nh.completeTime, nh.queueTime, nh.dispatchTime, nh.dontQueueUntil); + nh.latestTime = Math.max(nh.completeTime, nh.queueTime, nh.dispatchTime, nh.dontQueueUntil); }); message.event.scriptHistory.sort((a, b) => (a.latestTime < b.latestTime) ? 1 : -1); message.event.scriptHistory.forEach(function(nh) { @@ -567,7 +586,7 @@ function redrawScriptTree() { if (nh.dispatchTime != null) nh.statusTxt = 'Running'; if (nh.errorVal != null) nh.statusTxt = 'Error'; if (nh.returnVal != null) nh.statusTxt = 'Completed'; - if (nh.dontQueueUntil > nowTime) nh.statusTxt = 'Scheduled'; + if (nh.dontQueueUntil > nowTime) nh.statusTxt = 'Scheduled'; if (nh.returnTxt == null) nh.returnTxt = ' '; if (nh.statusTxt == 'Completed') { nh.statusTxt = '' + nh.statusTxt + ''; @@ -617,7 +636,7 @@ function redrawScriptTree() { }) var ordering = { 'global': 0, 'script': 1, 'mesh': 2, 'node': 3 } vars.sort((a, b) => { - return (ordering[a.scope] - ordering[b.scope]) + return (ordering[a.scope] - ordering[b.scope]) || a.name.localeCompare(b.name) || a.scopeTargetTxt.localeCompare(b.scopeTargetTxt); }); @@ -628,10 +647,10 @@ function redrawScriptTree() { function parseVariables() { var vTbl = document.getElementById('varTbl'); var rows = vTbl.querySelectorAll('.stVRow'); - + if (rows.length) { rows.forEach(function(r) { - r.parentNode.removeChild(r); + r.parentNode.removeChild(r); }); } var scriptEl = document.querySelectorAll('.liselected'); @@ -654,27 +673,25 @@ function redrawScriptTree() { tr.setAttribute('x-data-id', vd._id); }) } - parent.pluginHandler.scripttask.loadSchedule = function(message) { - // cache script names - var nNames = {}, sNames = {}; - parent.nodes.forEach(function(n){ - nNames[n._id] = n.name; - }); - scriptTree.forEach(function(s) { - if (s.type == 'script') sNames[s._id] = s.name; - }); - if (message.event.nodeSchedule != null && message.event.nodeId == parent.currentNode._id) { - var nTbl = document.getElementById('nSchTbl'); - var rows = nTbl.querySelectorAll('.stNSRow'); - if (rows.length) { - rows.forEach(function(r) { - r.parentNode.removeChild(r); - }); - } - if (message.event.nodeSchedule.length) { - message.event.nodeSchedule.forEach(function(nh) { - nh = prepSchedule(nh); - let tpl = '' + sNames[nh.scriptId] + ' \ + parent.pluginHandler.scripttask.loadSchedule = function(message) { + var nNames = {}, sNames = {}; + parent.nodes.forEach(function(n){ + nNames[n._id] = n.name; +}); + scriptTree.forEach(function(s) { + if (s.type == 'script') sNames[s._id] = s.name; +}); + + if (message.event.nodeSchedule != null && message.event.nodeId == parent.currentNode._id) { + var nTbl = document.getElementById('nSchTbl'); + var rows = nTbl.querySelectorAll('.stNSRow'); + if (rows.length) { + rows.forEach(function(r) { r.parentNode.removeChild(r); }); +} + if (message.event.nodeSchedule.length) { + message.event.nodeSchedule.forEach(function(nh) { + nh = prepSchedule(nh); + let tpl = '' + sNames[nh.scriptId] + ' \ ' + nh.scheduledBy + ' \ ' + nh.everyTxt + ' \ ' + nh.startedTxt + ' \ @@ -682,27 +699,43 @@ function redrawScriptTree() { ' + nh.lastRunTxt + ' \ ' + nh.nextRunTxt + ' \ ' + nh.actionTxt + ''; - let tr = nTbl.insertRow(-1); - tr.innerHTML = tpl; - tr.classList.add('stNSRow'); - tr.setAttribute('x-data-id', nh._id); - }); - } - } - var currentScript = document.getElementById('scriptHistory'); - var currentScriptId = currentScript.getAttribute('x-data-id'); - if (message.event.scriptSchedule != null && message.event.scriptId == currentScriptId) { - var sTbl = document.getElementById('sSchTbl'); - var rows = sTbl.querySelectorAll('.stSSRow'); - if (rows.length) { - rows.forEach(function(r) { - r.parentNode.removeChild(r); - }); - } - if (message.event.scriptSchedule.length) { - message.event.scriptSchedule.forEach(function(nh) { - nh = prepSchedule(nh); - let tpl = '' + nNames[nh.node] + ' \ + let tr = nTbl.insertRow(-1); + tr.innerHTML = tpl; + tr.classList.add('stNSRow'); + tr.setAttribute('x-data-id', nh._id); +}); +} +} + + var currentScript = document.getElementById('scriptHistory'); + var currentScriptId = currentScript.getAttribute('x-data-id'); + if (message.event.scriptSchedule != null && message.event.scriptId == currentScriptId) { + var sTbl = document.getElementById('sSchTbl'); + var rows = sTbl.querySelectorAll('.stSSRow'); + if (rows.length) { + rows.forEach(function(r) { r.parentNode.removeChild(r); }); +} + if (message.event.scriptSchedule.length) { + message.event.scriptSchedule.forEach(function(nh) { + nh = prepSchedule(nh); + + var displayName = ''; + + if (nh.node === 'all') { + displayName = 'All Devices'; +} + else if (typeof nh.node === 'string' && nh.node.startsWith('mesh/')) { + if (parent.meshes[nh.node]) { + displayName = 'Group: ' + parent.meshes[nh.node].name + ''; +} else { + displayName = 'Unknown Group'; +} +} + else { + displayName = nNames[nh.node] || 'Unknown Node'; +} + + let tpl = '' + displayName + ' \ ' + nh.scheduledBy + ' \ ' + nh.everyTxt + ' \ ' + nh.startedTxt + ' \ @@ -710,15 +743,15 @@ function redrawScriptTree() { ' + nh.lastRunTxt + ' \ ' + nh.nextRunTxt + ' \ ' + nh.actionTxt + ''; - let tr = sTbl.insertRow(-1); - tr.innerHTML = tpl; - tr.classList.add('stSSRow'); - tr.setAttribute('x-data-id', nh._id); - }); - } - } - resizeIframe(); - } + let tr = sTbl.insertRow(-1); + tr.innerHTML = tpl; + tr.classList.add('stSSRow'); + tr.setAttribute('x-data-id', nh._id); +}); +} +} + resizeIframe(); +} function prepSchedule(nh) { nh.everyTxt = nh.interval + ' '; switch (nh.recur) { @@ -742,7 +775,7 @@ function redrawScriptTree() { break; } if (nh.interval > 1) nh.everyTxt += 's'; - + if (nh.recur == 'weekly') { nh.daysOfWeek = nh.daysOfWeek.map(el => Number(el)); nh.everyTxt += ' ('; @@ -759,7 +792,7 @@ function redrawScriptTree() { }); nh.everyTxt += ')'; } - + var d = new Date(0); d.setUTCSeconds(nh.startAt); nh.startedTxt = d.toLocaleString(); d = new Date(0); d.setUTCSeconds(nh.endAt); @@ -773,7 +806,7 @@ function redrawScriptTree() { nh.nextRunTxt = d.toLocaleString(); if (nh.nextRun == null) nh.nextRunTxt = 'Never'; if (nh.nextRun < nh.lastRun) nh.nextRunTxt = 'Running now'; - + nh.actionTxt = 'Delete'; return nh; } @@ -787,7 +820,7 @@ function redrawScriptTree() { var hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : ""; var mDisplay = m > 0 ? m + (m == 1 ? " minute, " : " minutes, ") : ""; var sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : ""; - return "in " + hDisplay + mDisplay + sDisplay; + return "in " + hDisplay + mDisplay + sDisplay; } function isJsonString(str) { try { @@ -816,7 +849,7 @@ function redrawScriptTree() { } function newVar() { - parent.setDialogMode(2, "New Variable", 3, newVarEx, 'Variable Name:
Scope:
Value: '); + parent.setDialogMode(2, "New Variable", 3, newVarEx, 'Variable Name:
Scope:
Value: '); parent.focusTextBox('stvarname'); } function editVarEx() { @@ -846,7 +879,7 @@ function redrawScriptTree() { if (v.scope == k) soptHtml += ' selected'; soptHtml += '>' + t + ''; } - parent.setDialogMode(2, "Edit Variable", 3, editVarEx, 'Variable Name:
Scope:
Value: '); + parent.setDialogMode(2, "Edit Variable", 3, editVarEx, 'Variable Name:
Scope:
Value: '); parent.focusTextBox('stvarname'); } function delVarEx() { @@ -857,7 +890,7 @@ function redrawScriptTree() { function delVar(el) { var vid = el.parentNode.parentNode.getAttribute('x-data-id'); var v = variables.filter(obj => { return obj._id === vid })[0]; - parent.setDialogMode(2, "Delete Variable", 3, delVarEx, 'Are you sure you want to delete this?
Name: '+ v.name +'
Scope: '+ varScopes[v.scope] +'
Value: '+ v.value); + parent.setDialogMode(2, "Delete Variable", 3, delVarEx, 'Are you sure you want to delete this?
Name: '+ v.name +'
Scope: '+ varScopes[v.scope] +'
Value: '+ v.value); } function renameEx() { var name = parent.document.getElementById('stfilename').value; @@ -870,7 +903,7 @@ function redrawScriptTree() { var el = scriptEl[0]; var name = el.querySelector('.fname').innerHTML; var id = el.getAttribute('x-data-id'); - parent.setDialogMode(2, "Rename " + name, 3, renameEx, ''); + parent.setDialogMode(2, "Rename " + name, 3, renameEx, ''); parent.focusTextBox('stfilename'); } function newEx() { @@ -886,7 +919,7 @@ function redrawScriptTree() { var el = scriptEl[0]; folder_id = el.getAttribute('x-data-folder'); } - parent.setDialogMode(2, "New Script", 3, newEx, 'Name:
Type:'); + parent.setDialogMode(2, "New Script", 3, newEx, 'Name:
Type:'); parent.focusTextBox('stfilename'); } function newFolderEx() { @@ -901,7 +934,7 @@ function redrawScriptTree() { var el = scriptEl[0]; folder_id = el.getAttribute('x-data-folder'); } - parent.setDialogMode(2, "New Folder", 3, newFolderEx, ''); + parent.setDialogMode(2, "New Folder", 3, newFolderEx, ''); parent.focusTextBox('stfoldername'); } function goScript(el) { @@ -928,7 +961,7 @@ function redrawScriptTree() { var el = els[0]; var name = el.innerHTML; var id = el.getAttribute('x-data-id'); - parent.setDialogMode(2, "Delete " + name, 3, deleteEx, 'Are you sure? '); + parent.setDialogMode(2, "Delete " + name, 3, deleteEx, 'Are you sure? '); } function deleteScheduleEx() { var id = parent.document.getElementById('stdelid').value; @@ -936,7 +969,7 @@ function redrawScriptTree() { } function deleteSchedule(el) { var id = el.parentNode.parentNode.getAttribute('x-data-id'); - parent.setDialogMode(2, "Delete Schedule", 3, deleteScheduleEx, 'Are you sure you want to delete this schedule? '); + parent.setDialogMode(2, "Delete Schedule", 3, deleteScheduleEx, 'Are you sure you want to delete this schedule? '); } function toggleCollapse(el) { var xdf = el.getAttribute('x-data-path'); @@ -945,12 +978,12 @@ function redrawScriptTree() { folderEls.forEach(function(e){ if (e === el) return; if (e.getAttribute('x-data-path').indexOf(xdf) !== -1) { - if (e.style.display == 'none') { + if (e.style.display == 'none') { if (showHide === null) showHide = ''; } else { if (showHide === null) showHide = 'none'; } - e.style.display = showHide; + e.style.display = showHide; } }); goScript(el); @@ -966,7 +999,7 @@ function redrawScriptTree() { if (dragTimer != null) dragTimer = null; //document.getElementById('list').innerHTML = ''; } - + function fileUpload(files) { if (files == null) files = document.getElementById('files').files; var path = null; @@ -997,7 +1030,7 @@ function redrawScriptTree() { dragTimer = setTimeout(function(){ dragCounter = 0; QV('dropBlock', false); }, 100); } } - + function dropMove(evt) { const move_id = evt.dataTransfer.getData('text'); const container_id = evt.target.parentNode.getAttribute('x-data-id'); @@ -1017,6 +1050,6 @@ function redrawScriptTree() { } }); dropZone.addEventListener('drop', handleFileSelect); - + From 494d2e20ac7c2ff905529baeb0bae305336e8283 Mon Sep 17 00:00:00 2001 From: mohammad taravat <56771263+MrTaravat@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:37:31 +0330 Subject: [PATCH 2/5] Update URLs in config.json to new repository --- config.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config.json b/config.json index 00cd76a..d103066 100644 --- a/config.json +++ b/config.json @@ -7,12 +7,12 @@ "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", + "configUrl": "https://raw.githubusercontent.com/MrTaravat/MeshCentral-ScriptTask/master/config.json", + "downloadUrl": "https://github.com/MrTaravat/MeshCentral-ScriptTask/archive/master.zip", "repository": { "type": "git", - "url": "https://github.com/ryanblenis/MeshCentral-ScriptTask.git" + "url": "https://github.com/MrTaravat/MeshCentral-ScriptTask.git" }, - "versionHistoryUrl": "https://api.github.com/repos/ryanblenis/MeshCentral-ScriptTask/tags", + "versionHistoryUrl": "https://api.github.com/repos/MrTaravat/MeshCentral-ScriptTask/tags", "meshCentralCompat": ">=1.1.35" -} \ No newline at end of file +} From 6e4512bbe090b835a4cf9746e8afb1d6409beaea Mon Sep 17 00:00:00 2001 From: mohammad taravat <56771263+MrTaravat@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:46:28 +0330 Subject: [PATCH 3/5] Update plugin configuration URL in README --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a289b34..da92af8 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,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/MrTaravat/MeshCentral-ScriptTask/master/config.json` ## Features - Add scripts to a central store From ab8480bb60417bf5648c10a846eb6734493f1e63 Mon Sep 17 00:00:00 2001 From: mohammad taravat <56771263+MrTaravat@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:47:51 +0330 Subject: [PATCH 4/5] Bump version from 0.0.20 to 0.0.21 --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index d103066..5355d3a 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,7 @@ { "name": "ScriptTask", "shortName": "scripttask", - "version": "0.0.20", + "version": "0.0.21", "author": "Ryan Blenis", "description": "Script (PowerShell, BAT, Bash) runner for endpoints", "hasAdminPanel": false, From 949ec34b125684a1227228fbcc6c2f2e2eedb319 Mon Sep 17 00:00:00 2001 From: mohammad taravat <56771263+MrTaravat@users.noreply.github.com> Date: Mon, 1 Dec 2025 07:43:02 +0330 Subject: [PATCH 5/5] Update executeImmediatelyTime calculation logic --- scripttask.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripttask.js b/scripttask.js index d32931c..19c4e2d 100644 --- a/scripttask.js +++ b/scripttask.js @@ -379,7 +379,7 @@ module.exports.scripttask = function (parent) { schedules.forEach(s => { var nextJobTime = obj.determineNextJobTime(s); - var executeImmediatelyTime = nextJobTime - 60; + var executeImmediatelyTime = nextJobTime ; if (nextJobTime === null) { obj.db.removeJobSchedule(s._id); } else {