More work on integrating the script-task.

This commit is contained in:
Ylian Saint-Hilaire 2022-08-18 18:03:03 -07:00
parent 3aca17ea6d
commit 1b67b84369
10 changed files with 2120 additions and 14 deletions

View File

@ -238,6 +238,7 @@
<Compile Include="public\scripts\common-0.0.1.js" />
<Compile Include="public\scripts\meshcentral.js" />
<Compile Include="redirserver.js" />
<Compile Include="taskmanager.js" />
<Compile Include="translate\translate.js" />
<Compile Include="ua-parser.js" />
<Compile Include="webauthn.js" />

View File

@ -1740,20 +1740,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
}
case 'script-task': {
// These command are for running regular batch jobs on the remote device
switch (command.subaction) {
case 'getScript': {
console.log('getScript');
break;
}
case 'clearAllPendingTasks': {
console.log('clearAllPendingTasks');
break;
}
case 'taskComplete': {
console.log('taskComplete');
break;
}
}
if (parent.parent.taskManager != null) { parent.parent.taskManager.agentAction(command, obj); }
break;
}
default: {

View File

@ -33,6 +33,7 @@ function CreateMeshCentralServer(config, args) {
obj.amtScanner = null;
obj.amtManager = null;
obj.meshScanner = null;
obj.taskManager = null;
obj.letsencrypt = null;
obj.eventsDispatch = {};
obj.fs = require('fs');
@ -1497,6 +1498,11 @@ function CreateMeshCentralServer(config, args) {
return;
}
// Setup the task manager
if ((obj.config) && (obj.config.settings) && (obj.config.settings.taskmanager == true)) {
obj.taskManager = require('./taskmanager').createTaskManager(obj);
}
// Start plugin manager if configuration allows this.
if ((obj.config) && (obj.config.settings) && (obj.config.settings.plugins != null) && (obj.config.settings.plugins != false) && ((typeof obj.config.settings.plugins != 'object') || (obj.config.settings.plugins.enabled != false))) {
obj.pluginHandler = require('./pluginHandler.js').pluginHandler(obj);

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

714
taskmanager.js Normal file
View File

@ -0,0 +1,714 @@
/**
* @description MeshCentral ScriptTask
* @author Ryan Blenis
* @copyright
* @license Apache-2.0
*/
'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

@ -0,0 +1,249 @@
<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

@ -0,0 +1,73 @@
<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>

1070
views/task-user.handlebars Normal file

File diff suppressed because it is too large Load Diff