Changed how stript-task will be integrated into MeshCentral, added run button to device general tab.

This commit is contained in:
Ylian Saint-Hilaire 2022-08-18 21:31:09 -07:00
parent 1b67b84369
commit 44af3a2408
10 changed files with 44 additions and 2550 deletions

View File

@ -708,7 +708,6 @@ db = require('SimpleDataStore').Shared();
sha = require('SHA256Stream');
mesh = require('MeshAgent');
childProcess = require('child_process');
try { scriptTask = require('script-task').CreateScriptTask(mesh); } catch (ex) { }
if (mesh.hasKVM == 1) { // if the agent is compiled with KVM support
// Check if this computer supports a desktop
@ -1556,10 +1555,6 @@ function handleServerCommand(data) {
try { require(data.plugin).consoleaction(data, data.rights, data.sessionid, this); } catch (ex) { throw ex; }
break;
}
case 'task': {
if (scriptTask) { scriptTask.consoleAction(data, data.rights, data.sessionid, false); }
break;
}
case 'coredump':
// Set the current agent coredump situation.s
if (data.value === true) {
@ -4565,11 +4560,6 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
}
break;
}
case 'task': {
if (!scriptTask) { response = "Tasks are not supported on this agent"; }
else { response = scriptTask.consoleAction(args, rights, sessionid, true); }
break;
}
case 'plugin': {
if (typeof args['_'][0] == 'string') {
try {

View File

@ -1,416 +0,0 @@
/**
* @description MeshCentral Script-Task
* @author Ryan Blenis
* @copyright
* @license Apache-2.0
*/
'use strict';
function CreateScriptTask(parent) {
var obj = {};
var db = require('SimpleDataStore').Shared();
var pendingDownload = [];
var debugFlag = false;
var runningJobs = [];
var runningJobPIDs = {};
function dbg(str) {
if (debugFlag !== true) return;
var fs = require('fs');
var logStream = fs.createWriteStream('scripttask.txt', { 'flags': 'a' });
// use {'flags': 'a'} to append and {'flags': 'w'} to erase and write a new file
logStream.write('\n' + new Date().toLocaleString() + ': ' + str);
logStream.end('\n');
}
function removeFromArray(arr, from, to) {
var rest = arr.slice((to || from) + 1 || arr.length);
arr.length = from < 0 ? arr.length + from : from;
return arr.push.apply(arr, rest);
};
obj.consoleAction = function(args, rights, sessionid, interactive) {
//sendConsoleText('task: ' + JSON.stringify(args), sessionid); // Debug
/*
if (typeof args['_'] == 'undefined') {
args['_'] = [];
args['_'][1] = args.pluginaction; // TODO
args['_'][2] = null;
args['_'][3] = null;
args['_'][4] = null;
}
*/
var fnname = args['_'][0];
if (fnname == null) { return "Valid task commands are: trigger, cache, clear, clearCache, debug, list"; }
switch (fnname.toLowerCase()) {
case 'trigger': {
var jObj = {
jobId: args.jobId,
scriptId: args.scriptId,
replaceVars: args.replaceVars,
scriptHash: args.scriptHash,
dispatchTime: args.dispatchTime
};
//dbg('jObj args is ' + JSON.stringify(jObj));
var sObj = getScriptFromCache(jObj.scriptId);
//dbg('sobj = ' + JSON.stringify(sObj) + ', shash = ' + jObj.scriptHash);
if ((sObj == null) || (sObj.contentHash != jObj.scriptHash)) {
// get from the server, then run
//dbg('Getting and caching script '+ jObj.scriptId);
parent.SendCommand({ action: 'script-task', subaction: 'getScript', scriptId: jObj.scriptId, sessionid: sessionid, tag: 'console' });
pendingDownload.push(jObj);
} else {
// ready to run
runScript(sObj, jObj, sessionid);
}
break;
}
case 'cache': {
var sObj = args.script;
cacheScript(sObj);
var setRun = [];
if (pendingDownload.length) {
pendingDownload.forEach(function (pd, k) {
if ((pd.scriptId == sObj._id) && (pd.scriptHash == sObj.contentHash)) {
if (setRun.indexOf(pd) === -1) { runScript(sObj, pd, sessionid); setRun.push(pd); }
removeFromArray(pendingDownload, k);
}
});
}
break;
}
case 'clear': {
clearCache();
parent.SendCommand({ action: 'script-task', subaction: 'clearAllPendingTasks', sessionid: sessionid, tag: 'console' });
return "Cache cleared. All pending tasks cleared.";
}
case 'clearcache': {
clearCache();
return "The script cache has been cleared";
}
case 'debug': {
debugFlag = (debugFlag) ? false : true;
var str = (debugFlag) ? 'on' : 'off';
return 'Debugging is now ' + str;
}
case 'list': {
var ret = '';
if (pendingDownload.length == 0) return "No tasks pending script download";
pendingDownload.forEach(function (pd, k) { ret += 'Task ' + k + ': ' + 'TaskID: ' + pd.jobId + ' ScriptID: ' + pd.scriptId + '\r\n'; });
return ret;
}
default: {
dbg('Unknown action: ' + fnname + ' with data ' + JSON.stringify(args));
break;
}
}
}
function finalizeJob(job, retVal, errVal, sessionid) {
if (errVal != null && errVal.stack != null) errVal = errVal.stack;
removeFromArray(runningJobs, runningJobs.indexOf(job.jobId));
if (typeof runningJobPIDs[job.jobId] != 'undefined') delete runningJobPIDs[job.jobId];
parent.SendCommand({
action: 'script-task',
subaction: 'taskComplete',
jobId: job.jobId,
scriptId: job.scriptId,
retVal: retVal,
errVal: errVal,
dispatchTime: job.dispatchTime, // include original run time (long running tasks could have tried a re-send)
sessionid: sessionid,
tag: 'console'
});
}
//@TODO Test powershell on *nix devices with and without powershell installed
function runPowerShell(sObj, jObj, sessionid) {
if (process.platform != 'win32') return runPowerShellNonWin(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 pwshout = '', pwsherr = '', cancontinue = false;
try {
fs.writeFileSync(pName, sObj.content);
var outstr = '', errstr = '';
var child = require('child_process').execFile(process.env['windir'] + '\\system32\\WindowsPowerShell\\v1.0\\powershell.exe', ['-NoLogo']);
child.stderr.on('data', function (chunk) { errstr += chunk; });
child.stdout.on('data', function (chunk) { });
runningJobPIDs[jObj.jobId] = child.pid;
child.stdin.write('.\\' + pName + ' | Out-File ' + oName + ' -Encoding UTF8\r\n');
child.on('exit', function (procRetVal, procRetSignal) {
dbg('Exiting with ' + procRetVal + ', Signal: ' + procRetSignal);
if (errstr != '') {
finalizeJob(jObj, null, errstr, sessionid);
try { fs.unlinkSync(oName); fs.unlinkSync(pName); } catch (ex) { dbg('Could not unlink files, error was: ' + ex); }
return;
}
if (procRetVal == 1) {
finalizeJob(jObj, null, 'Process terminated unexpectedly.', sessionid);
try { fs.unlinkSync(oName); fs.unlinkSync(pName); } catch (ex) { dbg('Could not unlink files, error was: ' + ex); }
return;
}
try { outstr = fs.readFileSync(oName, 'utf8').toString(); } catch (ex) { outstr = (procRetVal) ? 'Failure' : 'Success'; }
if (outstr) {
//outstr = outstr.replace(/[^\x20-\x7E]/g, '');
try { outstr = outstr.trim(); } catch (ex) { }
} else {
outstr = (procRetVal) ? 'Failure' : 'Success';
}
dbg('Output is: ' + outstr);
finalizeJob(jObj, outstr, null, sessionid);
try { fs.unlinkSync(oName); fs.unlinkSync(pName); } catch (ex) { }
});
child.stdin.write('exit\r\n');
//child.waitExit(); // this was causing the event loop to stall on long-running scripts, switched to '.on exit'
} catch (ex) {
dbg('Error block was (PowerShell): ' + ex);
finalizeJob(jObj, null, ex, sessionid);
}
}
function runPowerShellNonWin(sObj, jObj, sessionid) {
const fs = require('fs');
var rand = Math.random().toString(32).replace('0.', '');
var path = '';
var pathTests = ['/usr/local/mesh', '/tmp', '/usr/local/mesh_services/meshagent', '/var/tmp'];
pathTests.forEach(function (p) { if (path == '' && fs.existsSync(p)) { path = p; } });
dbg('Path chosen is: ' + path);
path = path + '/';
var oName = 'st' + rand + '.txt';
var pName = 'st' + rand + '.ps1';
var pwshout = '', pwsherr = '', cancontinue = false;
try {
var childp = require('child_process').execFile('/bin/sh', ['sh']);
childp.stderr.on('data', function (chunk) { pwsherr += chunk; });
childp.stdout.on('data', function (chunk) { pwshout += chunk; });
childp.stdin.write('which pwsh' + '\n');
childp.stdin.write('exit\n');
childp.waitExit();
} catch (ex) { finalizeJob(jObj, null, "Couldn't determine pwsh in env: " + ex, sessionid); }
if (pwsherr != '') { finalizeJob(jObj, null, "PowerShell env determination error: " + pwsherr, sessionid); return; }
if (pwshout.trim() != '') { cancontinue = true; }
if (cancontinue === false) { finalizeJob(jObj, null, "PowerShell is not installed", sessionid); return; }
try {
fs.writeFileSync(path + pName, '#!' + pwshout + '\n' + sObj.content.split('\r\n').join('\n').split('\r').join('\n'));
var outstr = '', errstr = '';
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stderr.on('data', function (chunk) { errstr += chunk; });
child.stdout.on('data', function (chunk) { });
runningJobPIDs[jObj.jobId] = child.pid;
child.stdin.write('cd ' + path + '\n');
child.stdin.write('chmod a+x ' + pName + '\n');
child.stdin.write('./' + pName + ' > ' + oName + '\n');
child.on('exit', function (procRetVal, procRetSignal) {
if (errstr != '') {
finalizeJob(jObj, null, errstr, sessionid);
try {
fs.unlinkSync(path + oName);
fs.unlinkSync(path + pName);
} catch (ex) { dbg('Could not unlink files, error was: ' + ex + ' for path ' + path); }
return;
}
if (procRetVal == 1) {
finalizeJob(jObj, null, 'Process terminated unexpectedly.', sessionid);
try {
fs.unlinkSync(path + oName);
fs.unlinkSync(path + pName);
} catch (ex) { dbg('Could not unlink files1, error was: ' + ex + ' for path ' + path); }
return;
}
try { outstr = fs.readFileSync(path + oName, 'utf8').toString(); } catch (es) { outstr = (procRetVal) ? 'Failure' : 'Success'; }
if (outstr) {
//outstr = outstr.replace(/[^\x20-\x7E]/g, '');
try { outstr = outstr.trim(); } catch (ex) { }
} else {
outstr = (procRetVal) ? 'Failure' : 'Success';
}
dbg('Output is: ' + outstr);
finalizeJob(jObj, outstr, null, sessionid);
try { fs.unlinkSync(path + oName); fs.unlinkSync(path + pName); } catch (ex) { dbg('Could not unlink files2, error was: ' + ex + ' for path ' + path); }
});
child.stdin.write('exit\n');
} catch (ex) {
dbg('Error block was (PowerShellNonWin): ' + ex);
finalizeJob(jObj, null, ex, sessionid);
}
}
function runBat(sObj, jObj, sessionid) {
if (process.platform != 'win32') { finalizeJob(jObj, null, "Platform not supported.", sessionid); return; }
const fs = require('fs');
var rand = Math.random().toString(32).replace('0.', '');
var oName = 'st' + rand + '.txt';
var pName = 'st' + rand + '.bat';
try {
fs.writeFileSync(pName, sObj.content);
var outstr = '', errstr = '';
var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe');
child.stderr.on('data', function (chunk) { errstr += chunk; });
child.stdout.on('data', function (chunk) { });
runningJobPIDs[jObj.jobId] = child.pid;
child.stdin.write(pName + ' > ' + oName + '\r\n');
child.stdin.write('exit\r\n');
child.on('exit', function (procRetVal, procRetSignal) {
if (errstr != '') {
try { fs.unlinkSync(oName); fs.unlinkSync(pName); } catch (ex) { dbg('Could not unlink files, error was: ' + ex); }
finalizeJob(jObj, null, errstr, sessionid);
return;
}
if (procRetVal == 1) {
try { fs.unlinkSync(oName); fs.unlinkSync(pName); } catch (ex) { dbg('Could not unlink files, error was: ' + ex); }
finalizeJob(jObj, null, 'Process terminated unexpectedly.', sessionid);
return;
}
try { outstr = fs.readFileSync(oName, 'utf8').toString(); } catch (ex) { outstr = (procRetVal) ? 'Failure' : 'Success'; }
if (outstr) {
//outstr = outstr.replace(/[^\x20-\x7E]/g, '');
try { outstr = outstr.trim(); } catch (ex) { }
} else {
outstr = (procRetVal) ? 'Failure' : 'Success';
}
dbg('Output is: ' + outstr);
try { fs.unlinkSync(oName); fs.unlinkSync(pName); } catch (ex) { dbg('Could not unlink files, error was: ' + ex); }
finalizeJob(jObj, outstr, null, sessionid);
});
} catch (ex) {
dbg('Error block was (BAT): ' + ex);
finalizeJob(jObj, null, ex, sessionid);
}
}
function runBash(sObj, jObj, sessionid) {
if (process.platform == 'win32') { finalizeJob(jObj, null, "Platform not supported.", sessionid); return; }
//dbg('proc is ' + JSON.stringify(process));
const fs = require('fs');
var path = '';
var pathTests = ['/usr/local/mesh', '/tmp', '/usr/local/mesh_services/meshagent', '/var/tmp'];
pathTests.forEach(function (p) {
if (path == '' && fs.existsSync(p)) { path = p; }
});
dbg('Path chosen is: ' + path);
path = path + '/';
//var child = require('child_process');
//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';
try {
fs.writeFileSync(path + pName, sObj.content);
var outstr = '', errstr = '';
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stderr.on('data', function (chunk) { errstr += chunk; });
child.stdout.on('data', function (chunk) { });
runningJobPIDs[jObj.jobId] = child.pid;
child.stdin.write('cd ' + path + '\n');
child.stdin.write('chmod a+x ' + pName + '\n');
child.stdin.write('./' + pName + ' > ' + oName + '\n');
child.stdin.write('exit\n');
child.on('exit', function (procRetVal, procRetSignal) {
if (errstr != '') {
try { fs.unlinkSync(path + oName); fs.unlinkSync(path + pName); } catch (ex) { dbg('Could not unlink files, error was: ' + ex + ' for path ' + path); }
finalizeJob(jObj, null, errstr, sessionid);
return;
}
if (procRetVal == 1) {
try { fs.unlinkSync(path + oName); fs.unlinkSync(path + pName); } catch (ex) { dbg('Could not unlink files1, error was: ' + ex + ' for path ' + path); }
finalizeJob(jObj, null, "Process terminated unexpectedly.", sessionid);
return;
}
try { outstr = fs.readFileSync(path + oName, 'utf8').toString(); } catch (ex) { outstr = (procRetVal) ? 'Failure' : 'Success'; }
if (outstr) {
//outstr = outstr.replace(/[^\x20-\x7E]/g, '');
try { outstr = outstr.trim(); } catch (ex) { }
} else {
outstr = (procRetVal) ? 'Failure' : 'Success';
}
dbg('Output is: ' + outstr);
try { fs.unlinkSync(path + oName); fs.unlinkSync(path + pName); } catch (ex) { dbg('Could not unlink files2, error was: ' + ex + ' for path ' + path); }
finalizeJob(jObj, outstr, null, sessionid);
});
} catch (ex) {
dbg('Error block was (bash): ' + ex);
finalizeJob(jObj, null, ex, sessionid);
}
}
function jobIsRunning(jObj) {
if (runningJobs.indexOf(jObj.jobId) === -1) return false;
return true;
}
function runScript(sObj, jObj, sessionid) {
// get current processes and clean running jobs if they are no longer running (computer fell asleep, user caused process to stop, etc.)
if (process.platform != 'linux' && runningJobs.length) { // linux throws errors here in the meshagent for some reason
require('process-manager').getProcesses(function (plist) {
dbg('Got process list');
dbg('There are currently ' + runningJobs.length + ' running jobs.');
if (runningJobs.length) {
runningJobs.forEach(function (jobId, idx) {
dbg('Checking for running job: ' + jobId + ' with PID ' + runningJobPIDs[jobId]);
if (typeof plist[runningJobPIDs[jobId]] == 'undefined' || typeof plist[runningJobPIDs[jobId]].cmd != 'string') {
dbg('Found job with no process. Removing running status.');
delete runningJobPIDs[jobId];
removeFromArray(runningJobs, runningJobs.indexOf(idx));
//dbg('RunningJobs: ' + JSON.stringify(runningJobs));
//dbg('RunningJobsPIDs: ' + JSON.stringify(runningJobPIDs));
}
});
}
});
}
if (jobIsRunning(jObj)) { dbg('Job already running job id [' + jObj.jobId + ']. Skipping.'); return; }
if (jObj.replaceVars != null) {
Object.getOwnPropertyNames(jObj.replaceVars).forEach(function (key) {
var val = jObj.replaceVars[key];
sObj.content = sObj.content.replace(new RegExp('#' + key + '#', 'g'), val);
dbg('replacing var ' + key + ' with ' + val);
});
sObj.content = sObj.content.replace(new RegExp('#(.*?)#', 'g'), 'VAR_NOT_FOUND');
}
runningJobs.push(jObj.jobId);
dbg('Running Script ' + sObj._id);
switch (sObj.filetype) {
case 'ps1': runPowerShell(sObj, jObj, sessionid); break;
case 'bat': runBat(sObj, jObj, sessionid); break;
case 'bash': runBash(sObj, jObj, sessionid); break;
default: dbg('Unknown filetype: ' + sObj.filetype); break;
}
}
function getScriptFromCache(id) {
var script = db.Get('scriptTask_script_' + id);
if (script == '' || script == null) return null;
try { script = JSON.parse(script); } catch (ex) { return null; }
return script;
}
function cacheScript(sObj) {
db.Put('scriptTask_script_' + sObj._id, sObj);
}
function clearCache() {
db.Keys.forEach(function (k) { if (k.indexOf('scriptTask_script_') === 0) { db.Put(k, null); db.Delete(k); } });
}
function sendConsoleText(text, sessionid) {
if (typeof text == 'object') { text = JSON.stringify(text); }
parent.SendCommand({ action: 'msg', type: 'console', value: 'XXX: ' + text, sessionid: sessionid });
}
return obj;
}
module.exports = { CreateScriptTask: CreateScriptTask };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,714 +1,20 @@
/**
* @description MeshCentral ScriptTask
* @author Ryan Blenis
* @copyright
/**
* @description MeshCentral task manager
* @author Ylian Saint-Hilaire
* @copyright Intel Corporation 2018-2022
* @license Apache-2.0
* @version v0.0.1
*/
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
'use strict';
module.exports.createTaskManager = function (parent) {
var obj = {};
obj.parent = parent.webserver;
obj.meshServer = parent;
obj.db = null;
obj.intervalTimer = null;
obj.debug = obj.meshServer.debug;
obj.VIEWS = __dirname + '/views/';
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
};
// Start the task manager
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', tabId: 'pluginScriptTask' });
QA('pluginScriptTask', '<iframe id="pluginIframeScriptTask" style="width: 100%; height: 800px;" scrolling="no" frameBorder=0 src="/pluginadmin.ashx?pin=scripttask&user=1" />');
};
/*
// may not be needed, saving for later. Can be called to resize iFrame
obj.resizeContent = function() {
var iFrame = document.getElementById('pluginIframeScriptTask');
var newHeight = 800;
var sHeight = iFrame.contentWindow.document.body.scrollHeight;
if (sHeight > newHeight) newHeight = sHeight;
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);
obj.db.getPendingJobs(onlineAgents)
.then(function(jobs) {
if (jobs.length == 0) return;
//@TODO check for a large number and use taskLimiter to queue the jobs
jobs.forEach(function(job) {
obj.db.get(job.scriptId)
.then(async function(script) {
script = script[0];
var foundVars = script.content.match(/#(.*?)#/g);
var replaceVars = {};
if (foundVars != null && foundVars.length > 0) {
var foundVarNames = [];
foundVars.forEach(function(fv) { foundVarNames.push(fv.replace(/^#+|#+$/g, '')); });
var limiters = {
scriptId: job.scriptId,
nodeId: job.node,
meshId: obj.meshServer.webserver.wsagents[job.node]['dbMeshKey'],
names: foundVarNames
};
var finvals = await obj.db.getVariables(limiters);
var ordering = { 'global': 0, 'script': 1, 'mesh': 2, 'node': 3 }
finvals.sort(function(a, b) { return (ordering[a.scope] - ordering[b.scope]) || a.name.localeCompare(b.name); });
finvals.forEach(function(fv) { replaceVars[fv.name] = fv.value; });
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);
}
var dispatchTime = Math.floor(new Date() / 1000);
var jObj = {
action: 'task',
subaction: 'triggerJob',
jobId: job._id,
scriptId: job.scriptId,
replaceVars: replaceVars,
scriptHash: script.contentHash,
dispatchTime: dispatchTime
};
//obj.debug('ScriptTask', 'Sending job to agent');
try {
obj.meshServer.webserver.wsagents[job.node].send(JSON.stringify(jObj));
obj.db.update(job._id, { dispatchTime: dispatchTime });
} catch (ex) { }
})
.catch(function (ex) { console.log('task: Could not dispatch job.', ex) });
});
})
.then(function() {
obj.makeJobsFromSchedules();
obj.cleanHistory();
})
.catch(function(ex) { console.log('task: Queue Run Error: ', ex); });
};
obj.cleanHistory = function() {
if (Math.round(Math.random() * 100) == 99) {
//obj.debug('Task', 'Running history cleanup');
obj.db.deleteOldHistory();
}
};
obj.downloadFile = function(req, res, user) {
var id = req.query.dl;
obj.db.get(id)
.then(function(found) {
if (found.length != 1) { res.sendStatus(401); return; }
var file = found[0];
res.setHeader('Content-disposition', 'attachment; filename=' + file.name);
res.setHeader('Content-type', 'text/plain');
//var fs = require('fs');
res.send(file.content);
});
};
obj.updateFrontEnd = async function(ids){
if (ids.scriptId != null) {
var scriptHistory = null;
obj.db.getJobScriptHistory(ids.scriptId)
.then(function(sh) {
scriptHistory = sh;
return obj.db.getJobSchedulesForScript(ids.scriptId);
})
.then(function(scriptSchedule) {
var targets = ['*', 'server-users'];
obj.meshServer.DispatchEvent(targets, obj, { nolog: true, action: 'task', subaction: 'historyData', scriptId: ids.scriptId, nodeId: null, scriptHistory: scriptHistory, nodeHistory: null, scriptSchedule: scriptSchedule });
});
}
if (ids.nodeId != null) {
var nodeHistory = null;
obj.db.getJobNodeHistory(ids.nodeId)
.then(function(nh) {
nodeHistory = nh;
return obj.db.getJobSchedulesForNode(ids.nodeId);
})
.then(function(nodeSchedule) {
var targets = ['*', 'server-users'];
obj.meshServer.DispatchEvent(targets, obj, { nolog: true, action: 'task', subaction: 'historyData', scriptId: null, nodeId: ids.nodeId, scriptHistory: null, nodeHistory: nodeHistory, nodeSchedule: nodeSchedule });
});
}
if (ids.tree === true) {
obj.db.getScriptTree()
.then(function(tree) {
var targets = ['*', 'server-users'];
obj.meshServer.DispatchEvent(targets, obj, { nolog: true, action: 'task', subaction: 'newScriptTree', tree: tree });
});
}
if (ids.variables === true) {
obj.db.getVariables()
.then(function(vars) {
var targets = ['*', 'server-users'];
obj.meshServer.DispatchEvent(targets, obj, { nolog: true, action: 'task', subaction: 'variableData', vars: vars });
});
}
};
obj.handleAdminReq = function(req, res, user) {
if ((user.siteadmin & 0xFFFFFFFF) == 1 && req.query.admin == 1)
{
// admin wants admin, grant
var vars = {};
res.render(obj.VIEWS + 'admin', vars);
return;
} else if (req.query.admin == 1 && (user.siteadmin & 0xFFFFFFFF) == 0) {
// regular user wants admin
res.sendStatus(401);
return;
} 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);
obj.db.get(req.query.id)
.then(function(scripts) {
if (scripts[0].filetype == 'proc') {
vars.procData = JSON.stringify(scripts[0]);
res.render(obj.VIEWS + 'procedit', vars);
} else {
vars.scriptData = JSON.stringify(scripts[0]);
res.render(obj.VIEWS + 'scriptedit', vars);
}
});
return;
} else if (req.query.schedule == 1) {
var vars = {};
res.render(obj.VIEWS + 'schedule', vars);
return;
}
// default user view (tree)
vars.scriptTree = 'null';
obj.db.getScriptTree()
.then(function(tree) {
vars.scriptTree = JSON.stringify(tree);
res.render(obj.VIEWS + 'user', vars);
});
return;
} else if (req.query.include == 1) {
switch (req.query.path.split('/').pop().split('.').pop()) {
case 'css': res.contentType('text/css'); break;
case 'js': res.contentType('text/javascript'); break;
}
res.sendFile(__dirname + '/includes/' + req.query.path); // don't freak out. Express covers any path issues.
return;
}
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;
}
switch (s.recur) {
case 'once':
if (s.nextRun == null) nextTime = s.startAt;
else nextTime = null;
break;
case 'minutes':
/*var lRun = s.nextRun || nowTime;
if (lRun == null) lRun = nowTime;
nextTime = lRun + (s.interval * 60);
if (s.startAt > nextTime) nextTime = s.startAt;*/
if (s.nextRun == null) { // hasn't run yet, set to start time
nextTime = s.startAt;
break;
}
nextTime = s.nextRun + (s.interval * 60);
// this prevents "catch-up" tasks being scheduled if an endpoint is offline for a long period of time
// e.g. always make sure the next scheduled time is relevant to the scheduled interval, but in the future
if (nextTime < nowTime) {
// initially I was worried about this causing event loop lockups
// if there was a long enough time gap. Testing over 50 years of backlog for a 3 min interval
// still ran under a fraction of a second. Safe to say this approach is safe! (~8.5 million times)
while (nextTime < nowTime) {
nextTime = nextTime + (s.interval * 60);
}
}
if (s.startAt > nextTime) nextTime = s.startAt;
break;
case 'hourly':
if (s.nextRun == null) { // hasn't run yet, set to start time
nextTime = s.startAt;
break;
}
nextTime = s.nextRun + (s.interval * 60 * 60);
if (nextTime < nowTime) {
while (nextTime < nowTime) {
nextTime = nextTime + (s.interval * 60 * 60);
}
}
if (s.startAt > nextTime) nextTime = s.startAt;
break;
case 'daily':
if (s.nextRun == null) { // hasn't run yet, set to start time
nextTime = s.startAt;
break;
}
nextTime = s.nextRun + (s.interval * 60 * 60 * 24);
if (nextTime < nowTime) {
while (nextTime < nowTime) {
nextTime = nextTime + (s.interval * 60 * 60 * 24);
}
}
if (s.startAt > nextTime) nextTime = s.startAt;
break;
case 'weekly':
var tempDate = new Date();
var nowDate = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate());
if (s.daysOfWeek.length == 0) {
nextTime = null;
break;
}
s.daysOfWeek = s.daysOfWeek.map(function (el) { Number(el) });
var baseTime = s.startAt;
//console.log('dow is ', s.daysOfWeek);
var lastDayOfWeek = Math.max(...s.daysOfWeek);
var startX = 0;
//console.log('ldow is ', lastDayOfWeek);
if (s.nextRun != null) {
baseTime = s.nextRun;
//console.log('basetime 2: ', baseTime);
if (nowDate.getDay() == lastDayOfWeek) {
baseTime = baseTime + ( s.interval * 604800 ) - (lastDayOfWeek * 86400);
//console.log('basetime 3: ', baseTime);
}
startX = 0;
} else if (s.startAt < nowTime) {
baseTime = Math.floor(nowDate.getTime() / 1000);
//console.log('basetime 4: ', baseTime);
}
//console.log('startX is: ', startX);
//var secondsFromMidnight = nowTimeDate.getSeconds() + (nowTimeDate.getMinutes() * 60) + (nowTimeDate.getHours() * 60 * 60);
//console.log('seconds from midnight: ', secondsFromMidnight);
//var dBaseTime = new Date(0); dBaseTime.setUTCSeconds(baseTime);
//var dMidnight = new Date(dBaseTime.getFullYear(), dBaseTime.getMonth(), dBaseTime.getDate());
//baseTime = Math.floor(dMidnight.getTime() / 1000);
for (var x = startX; x <= 7; x++){
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');
if (s.nextRun == null && s.daysOfWeek.indexOf(dm.getDay()) !== -1 && dm.getTime() >= nowDate.getTime()) break;
if (s.daysOfWeek.indexOf(dm.getDay()) !== -1 && dm.getTime() > nowDate.getTime()) break;
//if (s.daysOfWeek.indexOf(d.getDay()) !== -1 && Math.floor(d.getTime() / 1000) >= nowTime) break;
}
var sa = new Date(0); sa.setUTCSeconds(s.startAt);
var sad = new Date(sa.getFullYear(), sa.getMonth(), sa.getDate());
var diff = (sa.getTime() - sad.getTime()) / 1000;
nextTime = Math.floor(dm.getTime() / 1000) + diff;
//console.log('next schedule is ' + d.toLocaleString());
break;
default:
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(function(schedules) {
//obj.debug('ScriptTask', 'Found ' + schedules.length + ' schedules to process. Current time is: ' + Math.floor(new Date() / 1000));
if (schedules.length) {
schedules.forEach(function(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(function(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(function(jobs) {
if (jobs.length > 0) { /* obj.debug('Task', 'Skipping job creation'); */ return Promise.resolve(); }
else { /* obj.debug('Task', '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(function() {
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(function() {
obj.updateFrontEnd( { scriptId: s.scriptId, nodeId: s.node } );
})
.catch(function(ex) { console.log('Task: Error managing job schedules: ', ex); });
}
});
}
});
};
obj.deleteElement = function (command) {
var delObj = null;
obj.db.get(command.id)
.then(function(found) {
var file = found[0];
delObj = {...{}, ...found[0]};
return file;
})
.then(function(file) {
if (file.type == 'folder') return obj.db.deleteByPath(file.path); //@TODO delete schedules for scripts within folders
if (file.type == 'script') return obj.db.deleteSchedulesForScript(file._id);
if (file.type == 'jobSchedule') return obj.db.deletePendingJobsForSchedule(file._id);
})
.then(function() {
return obj.db.delete(command.id)
})
.then(function() {
var updateObj = { tree: true };
if (delObj.type == 'jobSchedule') {
updateObj.scriptId = delObj.scriptId;
updateObj.nodeId = delObj.node;
}
return obj.updateFrontEnd( updateObj );
})
.catch(function(ex) { console.log('Task: Error deleting ', ex.stack); });
};
// Process 'task' commands received by an agent
obj.agentAction = function (command, agent) {
console.log('task-agentAction', command);
switch (command.subaction) {
case 'getScript':
// TODO
break;
case 'clearAllPendingTasks':
// TODO
break;
case 'taskComplete':
// TODO
break;
}
}
obj.serveraction = function(command, myparent, grandparent) {
switch (command.subaction) {
case 'addScript':
obj.db.addScript(command.name, command.content, command.path, command.filetype)
.then(function() { obj.updateFrontEnd( { tree: true } ); });
break;
case 'new':
var parent_path = '', new_path = '';
obj.db.get(command.parent_id)
.then(function(found) { if (found.length > 0) { var file = found[0]; parent_path = file.path; } else { parent_path = 'Shared'; } })
.then(function () { obj.db.addScript(command.name, '', parent_path, command.filetype); })
.then(function() { obj.updateFrontEnd( { tree: true } ); });
break;
case 'rename':
obj.db.get(command.id)
.then(function(docs) {
var doc = docs[0];
if (doc.type == 'folder') {
console.log('old', doc.path, 'new', doc.path.replace(doc.path, command.name));
return obj.db.update(command.id, { path: doc.path.replace(doc.name, command.name) })
.then(function() { // update sub-items
return obj.db.getByPath(doc.path)
})
.then(function(found) {
if (found.length > 0) {
var proms = [];
found.forEach(function(f) { proms.push(obj.db.update(f._id, { path: doc.path.replace(doc.name, command.name) } )); })
return Promise.all(proms);
}
})
} else {
return Promise.resolve();
}
})
.then(function() {
obj.db.update(command.id, { name: command.name })
})
.then(function() {
return obj.db.updateScriptJobName(command.id, command.name);
})
.then(function() {
obj.updateFrontEnd( { scriptId: command.id, nodeId: command.currentNodeId, tree: true } );
});
break;
case 'move':
var toPath = null, fromPath = null, parentType = null;
obj.db.get(command.to)
.then(function(found) { // get target data
if (found.length > 0) {
var file = found[0];
toPath = file.path;
} else throw Error('Target destination not found');
})
.then(function() { // get item to be moved
return obj.db.get(command.id);
})
.then(function(found) { // set item to new location
var file = found[0];
if (file.type == 'folder') {
fromPath = file.path;
toPath += '/' + file.name;
parentType = 'folder';
if (file.name == 'Shared' && file.path == 'Shared') throw Error('Cannot move top level directory: Shared');
}
return obj.db.update(command.id, { path: toPath } );
})
.then(function() { // update sub-items
return obj.db.getByPath(fromPath)
})
.then(function(found) {
if (found.length > 0) {
var proms = [];
found.forEach(function(f) {
proms.push(obj.db.update(f._id, { path: toPath } ));
})
return Promise.all(proms);
}
})
.then(function() {
return obj.updateFrontEnd( { tree: true } );
})
.catch(function(ex) { console.log('Task: Error moving ', ex.stack); });
break;
case 'newFolder':
var parent_path = '';
var new_path = '';
obj.db.get(command.parent_id)
.then(function(found) {
if (found.length > 0) {
var file = found[0];
parent_path = file.path;
} else {
parent_path = 'Shared';
}
})
.then(function() {
new_path = parent_path + '/' + command.name;
})
.then(function() {
return obj.db.addFolder(command.name, new_path);
})
.then(function () {
return obj.updateFrontEnd( { tree: true } );
})
.catch(function(ex) { console.log('Task: Error creating new folder ', ex.stack); });
break;
case 'delete':
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,
scheduledBy: myparent.user.name,
recur: sj.recur,
interval: sj.interval,
daysOfWeek: sj.dayVals,
startAt: sj.startAt,
endAt: sj.endAt,
lastRun: null,
nextRun: null,
type: "jobSchedule"
};
var sel = command.nodes;
var proms = [];
if (Array.isArray(sel)) {
sel.forEach(function(s) {
var sObj = {...sched, ...{ node: s }};
proms.push(obj.db.addJobSchedule( sObj ));
});
} else { test.push(sObj);
proms.push(obj.db.addJobSchedule( sObj ));
}
Promise.all(proms)
.then(function() {
obj.makeJobsFromSchedules();
return Promise.resolve();
})
.catch(function(ex) { console.log('Task: Error adding schedules. The error was: ', ex); });
break;
case 'runScript':
var scriptId = command.scriptId;
var sel = command.nodes;
var proms = [];
if (Array.isArray(sel)) {
sel.forEach(function(s) {
proms.push(obj.db.addJob( { scriptId: scriptId, node: s, runBy: myparent.user.name } ));
});
} else {
proms.push(obj.db.addJob( { scriptId: scriptId, node: sel, runBy: myparent.user.name } ));
}
Promise.all(proms)
.then(function() {
return obj.db.get(scriptId);
})
.then(function(scripts) {
return obj.db.updateScriptJobName(scriptId, scripts[0].name);
})
.then(function() {
obj.resetQueueTimer();
obj.queueRun();
obj.updateFrontEnd( { scriptId: scriptId, nodeId: command.currentNodeId } );
});
break;
case 'getScript':
//obj.debug('ScriptTask', 'getScript Triggered', JSON.stringify(command));
obj.db.get(command.scriptId)
.then(function(script) {
myparent.send(JSON.stringify({
action: 'task',
subaction: 'cacheScript',
nodeid: myparent.dbNodeKey,
rights: true,
sessionid: true,
script: script[0]
}));
});
break;
case 'jobComplete':
//obj.debug('ScriptTask', 'jobComplete Triggered', JSON.stringify(command));
var jobNodeHistory = null, scriptHistory = null;
var jobId = command.jobId, retVal = command.retVal, errVal = command.errVal, dispatchTime = command.dispatchTime;
var completeTime = Math.floor(new Date() / 1000);
obj.db.update(jobId, {
completeTime: completeTime,
returnVal: retVal,
errorVal: errVal,
dispatchTime: dispatchTime
})
.then(function() {
return obj.db.get(jobId)
.then(function(jobs) {
return Promise.resolve(jobs[0].jobSchedule);
})
.then(function(sId) {
if (sId == null) return Promise.resolve();
return obj.db.update(sId, { lastRun: completeTime } )
.then(function() {
obj.makeJobsFromSchedules(sId);
});
});
})
.then(function() {
obj.updateFrontEnd( { scriptId: command.scriptId, nodeId: myparent.dbNodeKey } );
})
.catch(function(ex) { console.log('Task: Failed to complete job. ', ex); });
// update front end by eventing
break;
case 'loadNodeHistory':
obj.updateFrontEnd( { nodeId: command.nodeId } );
break;
case 'loadScriptHistory':
obj.updateFrontEnd( { scriptId: command.scriptId } );
break;
case 'editScript':
obj.db.update(command.scriptId, { type: command.scriptType, name: command.scriptName, content: command.scriptContent })
.then(function() { obj.updateFrontEnd( { scriptId: command.scriptId, tree: true } ); });
break;
case 'clearAllPendingJobs':
obj.db.deletePendingJobsForNode(myparent.dbNodeKey);
break;
case 'loadVariables':
obj.updateFrontEnd( { variables: true } );
break;
case 'newVar':
obj.db.addVariable(command.name, command.scope, command.scopeTarget, command.value)
.then(function() { obj.updateFrontEnd( { variables: true } ); })
break;
case 'editVar':
obj.db.update(command.id, {
name: command.name,
scope: command.scope,
scopeTarget: command.scopeTarget,
value: command.value
})
.then(function() { obj.updateFrontEnd( { variables: true } ); })
break;
case 'deleteVar':
obj.db.delete(command.id)
.then(function() { obj.updateFrontEnd( { variables: true } ); })
break;
default:
console.log('Task: unknown action');
break;
}
};
return obj;
}

View File

@ -5537,25 +5537,7 @@
p2downloadDeviceInfo();
} else if (op == 106) {
// Run commands
var wintype = false, linuxtype = false, agenttype = false, chkNodeIds = getCheckedDevices();
for (var i in chkNodeIds) {
var n = getNodeFromId(chkNodeIds[i]);
if (n.agent) { if ((GetNodeRights(n) & 24) == 24) { agenttype = true; } if ((n.agent.id > 0) && (n.agent.id < 5)) { wintype = true; } else { linuxtype = true; } }
}
if ((wintype == true) || (linuxtype == true) || (agenttype == true)) {
var x = "Run commands on selected devices." + '<br />';
x += '<select id=d2cmdtype onclick=d2runCommandValidate() style=width:100%;margin-bottom:4px;margin-top:4px>';
if (wintype == true) { x += '<option value=1>' + "Windows Command Prompt" + '</option><option value=2>' + "Windows PowerShell" + '</option>'; }
if (linuxtype == true) { x += '<option value=3>' + "Linux/BSD/macOS Command Shell" + '</option>'; }
if (agenttype == true) { x += '<option value=4>' + "Agent Console" + '</option>'; } // MESHRIGHT_REMOTECONTROL & MESHRIGHT_AGENTCONSOLE are needed
x += '</select>';
x += '<select id=d2cmduser style=width:100%;margin-bottom:4px><option value=0>' + "Run as agent" + '</option><option value=1>' + "Run as user, agent if no user" + '</option><option value=2>' + "Must run as user" + '</option></select>';
x += '<textarea id=d2runcmd style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
setDialogMode(2, "Run Commands", 3, d2groupActionFunctionRunCommands, x);
Q('d2runcmd').focus();
//QE('idx_dlgOkButton', true);
d2runCommandValidate();
}
d2runCommandDialog({ nodeids: getCheckedDevices(), title: "Run commands on selected devices.", func: uncheckAllDevices });
} else if (op == 107) {
// Edit tags
var x = "Perform batch device tag operation" + '<br /><br />';
@ -5611,7 +5593,6 @@
}
}
function d2runCommandValidate() { QV('d2cmduser', Q('d2cmdtype').value < 4); }
function d2batchUploadValidate() { QE('idx_dlgOkButton', (Q('d2uploadinput').files.length != 0) && ((Q('d2winuploadpath') == null) || (Q('d2winuploadpath').value != '')) && ((Q('d2linuxuploadpath') == null) || (Q('d2linuxuploadpath').value != ''))); }
function d2batchUploadValidateOk() { Q('d2batchUploadSubmit').click(); }
function d2groupActionFunctionAgentUpdateExec() { meshserver.send({ action: 'updateAgents', nodeids: getCheckedDevices() }); }
@ -5728,10 +5709,34 @@
function d2groupActionFunctionDelCheck() { QE('idx_dlgOkButton', Q('d2check').checked); }
function d2groupActionFunctionDelExec() { meshserver.send({ action: 'removedevices', nodeids: getCheckedDevices() }); uncheckAllDevices(); }
function d2groupActionFunctionRunCommands() {
function d2runCommandDialog(options) {
var wintype = false, linuxtype = false, agenttype = false;
for (var i in options.nodeids) {
var n = getNodeFromId(options.nodeids[i]);
if (n.agent) { if ((GetNodeRights(n) & 24) == 24) { agenttype = true; } if ((n.agent.id > 0) && (n.agent.id < 5)) { wintype = true; } else { linuxtype = true; } }
}
if ((wintype == true) || (linuxtype == true) || (agenttype == true)) {
var x = options.title + '<br />';
x += '<select id=d2cmdtype onclick=d2runCommandValidate() style=width:100%;margin-bottom:4px;margin-top:4px>';
if (wintype == true) { x += '<option value=1>' + "Windows Command Prompt" + '</option><option value=2>' + "Windows PowerShell" + '</option>'; }
if (linuxtype == true) { x += '<option value=3>' + "Linux/BSD/macOS Command Shell" + '</option>'; }
if (agenttype == true) { x += '<option value=4>' + "Agent Console" + '</option>'; } // MESHRIGHT_REMOTECONTROL & MESHRIGHT_AGENTCONSOLE are needed
x += '</select>';
x += '<select id=d2cmduser style=width:100%;margin-bottom:4px><option value=0>' + "Run as agent" + '</option><option value=1>' + "Run as user, agent if no user" + '</option><option value=2>' + "Must run as user" + '</option></select>';
x += '<textarea id=d2runcmd style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
setDialogMode(2, "Run Commands", 3, d2groupActionFunctionRunCommands, x, options);
Q('d2runcmd').focus();
//QE('idx_dlgOkButton', true);
d2runCommandValidate();
}
}
function d2runCommandValidate() { QV('d2cmduser', Q('d2cmdtype').value < 4); }
function d2groupActionFunctionRunCommands(b, options) {
var type = 3;
try { type = parseInt(Q('d2cmdtype').value); } catch (ex) { }
meshserver.send({ action: 'runcommands', nodeids: getCheckedDevices(), type: type, cmds: Q('d2runcmd').value, runAsUser: parseInt(Q('d2cmduser').value) }); uncheckAllDevices();
meshserver.send({ action: 'runcommands', nodeids: options.nodeids, type: type, cmds: Q('d2runcmd').value, runAsUser: parseInt(Q('d2cmduser').value) });
if (options.func) { options.func(); }
}
function onSortSelectChange(skipsave) {
@ -7174,6 +7179,7 @@
if (((meshrights & (4 + 8 + 64 + 262144)) != 0) && (node.mtype < 3) && ((node.agent == null) || (node.agent.id != 34))) { x += '<input type=button value="' + "Actions" + '" title="' + "Perform power actions on the device" + '" onclick=deviceActionFunction() />'; }
x += '<input type=button value="' + "Notes" + '" title="' + "View notes about this device" + '" onclick=showNotes(' + ((meshrights & 128) == 0) + ',"' + encodeURIComponentEx(node._id) + '") />';
x += '<input type=button value="' + "Log Event" + '" title="' + "Write an event for this device" + '" onclick=writeDeviceEvent("' + encodeURIComponentEx(node._id) + '") />';
if ((node.mtype == 2) && (connectivity & 1) && (meshrights == 0xFFFFFFFF)) { x += '<input type=button value="' + "Run" + '" title="' + "Run commands on this device" + '" onclick=runDeviceCmd("' + encodeURIComponentEx(node._id) + '") />'; }
if (node.mtype != 4) {
if ((meshrights & 8) && ((connectivity & 1) || ((node.pmt == 1) && ((features2 & 2) != 0)))) { x += '<input type=button value="' + "Message" + '" title="' + "Display a text message on the remote device" + '" onclick=deviceMessageFunction() />'; }
//if ((connectivity & 1) && (meshrights & 8) && (node.agent.id < 5)) { x += '<input type=button value=Toast title="' + "Display a text message of the remote device" + '" onclick=deviceToastFunction() />'; }
@ -7632,6 +7638,12 @@
return str.join(', ');
}
// Run commands on current device
function runDeviceCmd(nodeid) {
if (xxdialogMode) return;
d2runCommandDialog({ nodeids: [ decodeURIComponent(nodeid) ], title: "Run commands on this device." });
}
function writeDeviceEvent(nodeid) {
if (xxdialogMode) return;
setDialogMode(2, "Add Device Event", 3, writeDeviceEventEx, '<textarea id=d2devEvent style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea><span style=font-size:10px>' + "This will add an entry to this device's event log." + '<span>', nodeid);

View File

@ -1,249 +0,0 @@
<html>
<head>
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
<link rel="stylesheet" type="text/css" href="/public/tail.DateTime/tail.datetime-default-blue.min.css" />
<style>
body {
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
color: white;
}
#scriptContent {
width: 80%;
height: 80%;
}
#schedContentC {
padding: 20px;
}
#controlBar button {
cursor: pointer;
}
#scriptNameC {
padding: 20px;
}
#scriptName {
width: 300px;
}
#controlBar {
padding: 5px;
padding-left: 20px;
}
#left {
height: 100%;
width: 25%;
float: left;
}
#right {
height: 100%;
width: 75%;
float: right;
}
body {
background-color: #036;
}
#intervalListC {
list-style-type: none;
}
#daysListC {
list-style-type: none;
}
.rOpt {
height: 40px;
}
#daysListC {
display: inline-grid;
}
li {
padding: 2px;
}
</style>
</head>
<body onload="doOnLoad();">
<script type="text/javascript" src="/public/tail.DateTime/tail.datetime.min.js"></script>
<div id="scriptTaskSchedule">
<div id="controlBar">
<button onclick="goSave();">Schedule</button>
<button onclick="goCancel();">Cancel</button>
</div>
<div id="schedContentC">
<div id="left">
<span class="oTitle">Recurrence</span>
<ul id="intervalListC">
<li><label><input onclick="intervalSelected(this);" type="radio" checked name="recur" value="once">Once</label></li>
<li><label><input onclick="intervalSelected(this);" type="radio" name="recur" value="minutes">Minutes</label></li>
<li><label><input onclick="intervalSelected(this);" type="radio" name="recur" value="hourly">Hourly</label></li>
<li><label><input onclick="intervalSelected(this);" type="radio" name="recur" value="daily">Daily</label></li>
<li><label><input onclick="intervalSelected(this);" type="radio" name="recur" value="weekly">Weekly</label></li>
<!-- li><label><input type="radio" name="recur" value="monthly">Monthly</label></li -->
</ul>
</div>
<div id="right">
<div class="rOpt">
<span class="oTitle">Start: </span>
<input type="text" class="datePick" id="startDate" value="" />
<input type="text" class="timePick" id="startTime" value="" />
</div>
<div class="rOpt" id="intervalC" style="display: none;">
<span class="oTitle">Every: </span>
<input type="text" id="interval" value="1" />&nbsp;<span id="hintText"></span>
</div>
<div class="rOpt" id="endC" style="display: none;">
<span class="oTitle">End: </span>
<input type="text" class="datePick" id="endDate" value="" />
<input type="text" class="timePick" id="endTime" value="" />
<label><input type="checkbox" id="endNever" checked onclick="checkEndNever(this);" /> Never</label>
</div>
<div class="rOpt" id="daysC" style="display: none;">
<span class="oTitle">Days: </span>
<ul id="daysListC">
<li><label><input type="checkbox" name="days[]" value="0"> Sunday</label></li>
<li><label><input type="checkbox" name="days[]" value="1"> Monday</label></li>
<li><label><input type="checkbox" name="days[]" value="2"> Tuesday</label></li>
<li><label><input type="checkbox" name="days[]" value="3"> Wednesday</label></li>
<li><label><input type="checkbox" name="days[]" value="4"> Thursday</label></li>
<li><label><input type="checkbox" name="days[]" value="5"> Friday</label></li>
<li><label><input type="checkbox" name="days[]" value="6"> Saturday</label></li>
</ul>
</div>
</div>
</div>
</div>
<script type="text/javascript">
function checkEndNever(el) {
if (el.checked) {
Q('endDate').value = '';
Q('endTime').value = '';
}
}
function setTimePick() {
var d = new Date();
document.getElementById("startDate").value = d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate();
document.getElementById("startTime").value = d.getHours() + ':' + d.getMinutes();
tail.DateTime(".datePick", { position: "bottom", dateStart: Date(), timeFormat: false });
tail.DateTime(".timePick", { position: "bottom", dateFormat: false, timeFormat: "HH:ii", timeStepMinutes: 15 });
tail.datetime.inst[document.getElementById('endDate').getAttribute('data-tail-datetime')].on('change', function () {
document.getElementById('endNever').checked = false;
});
tail.datetime.inst[document.getElementById('endTime').getAttribute('data-tail-datetime')].on('change', function () {
document.getElementById('endNever').checked = false;
});
}
function doOnLoad() {
try {
if (scriptId == null) {
alert('Page reloaded and data lost. Please re-run scheduler from the main window.');
goCancel();
return;
}
} catch (e) {
alert('Page reloaded and data lost. Please re-run scheduler from the main window.');
goCancel();
return;
}
setTimePick();
}
function intervalSelected(el) {
var v = el.value;
switch (v) {
case 'once':
QV('intervalC', false);
QV('endC', false);
QV('daysC', false);
break;
case 'minutes':
QV('intervalC', true);
QV('endC', true);
QV('daysC', false);
QH('hintText', 'minute(s)');
break;
case 'hourly':
QV('intervalC', true);
QV('endC', true);
QV('daysC', false);
QH('hintText', 'hour(s)');
break;
case 'daily':
QV('intervalC', true);
QV('endC', true);
QV('daysC', false);
QH('hintText', 'day(s)');
break;
case 'weekly':
QV('intervalC', true);
QV('endC', true);
QV('daysC', true);
QH('hintText', 'week(s)');
break;
}
}
function goSave() {
var o = {};
var recurEls = document.getElementsByName("recur");
recurEls.forEach(function (el) {
if (el.checked) o.recur = el.value;
});
switch (o.recur) {
case 'once':
o.startAt = Date.parse(Q('startDate').value + ' ' + Q('startTime').value);
o.startAt = Math.floor(o.startAt / 1000);
break;
case 'minutes':
case 'hourly':
case 'daily':
o.startAt = Date.parse(Q('startDate').value + ' ' + Q('startTime').value);
o.startAt = Math.floor(o.startAt / 1000);
o.interval = Number(Q('interval').value);
if (Q('endNever').checked) o.endAt = null;
else {
o.endAt = Date.parse(Q('endDate').value + ' ' + Q('endTime').value);
o.endAt = Math.floor(o.endAt / 1000);
}
break;
case 'weekly':
o.startAt = Date.parse(Q('startDate').value + ' ' + Q('startTime').value);
o.startAt = Math.floor(o.startAt / 1000);
o.interval = Number(Q('interval').value);
if (Q('endNever').checked) o.endAt = null;
else {
o.endAt = Date.parse(Q('endDate').value + ' ' + Q('endTime').value);
o.endAt = Math.floor(o.endAt / 1000);
}
var dayEls = document.getElementsByName("days[]");
o.dayVals = [];
if (dayEls.length) {
dayEls.forEach(function (de) {
if (de.checked) o.dayVals.push(de.value);
});
}
break;
}
o.scriptId = scriptId;
o.nodes = nodes;
window.opener.schedCallback(o);
window.close();
}
function goCancel() {
window.close();
}
</script>
</body>
</html>

View File

@ -1,73 +0,0 @@
<html>
<head>
<script type="text/javascript" src="scripts/common-0.0.1.js"></script>
<style>
body {
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
color: white;
}
#scriptContent {
width: 80%;
height: 80%;
}
#scriptContentC {
padding: 20px;
}
#controlBar button {
cursor: pointer;
}
#scriptNameC {
padding: 20px;
}
#scriptName {
width: 300px;
}
#controlBar {
padding: 5px;
padding-left: 20px;
}
body {
background-color: #036;
}
</style>
</head>
<body onload="doOnLoad();">
<div id="scriptTaskScriptEdit">
<div id="scriptNameC">Script Name: <input type="text" value="" id="scriptName" /></div>
<div id="controlBar">
<button onclick="goSave();">Save</button>
<button onclick="goClose();">Close</button>
</div>
<div id="scriptContentC">
<textarea id="scriptContent"></textarea>
</div>
</div>
<script type="text/javascript">
var scriptData = {{{scriptData}}};
function doOnLoad() {
//QH('scriptContent', scriptData.content);
Q('scriptContent').value = scriptData.content;
Q('scriptName').value = scriptData.name;
}
function goSave() {
scriptData.content = Q('scriptContent').value;
scriptData.name = Q('scriptName').value;
window.opener.callback(scriptData);
//goClose();
}
function goClose() {
window.close();
}
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff