merging v0.2.3
commit
a6ab493b89
|
@ -22,6 +22,12 @@ If you need to set/use an alternate port, you may do so by setting an environmen
|
||||||
|
|
||||||
PORT=9000 crontab-ui
|
PORT=9000 crontab-ui
|
||||||
|
|
||||||
|
Also, you may have to **set permissions** for your `node_modules` folder. Refer [this](https://docs.npmjs.com/getting-started/fixing-npm-permissions).
|
||||||
|
|
||||||
|
If you need to autosave your changes to crontab directly:
|
||||||
|
|
||||||
|
crontab-ui --autosave
|
||||||
|
|
||||||
###Adding, deleting, pausing and resuming jobs.
|
###Adding, deleting, pausing and resuming jobs.
|
||||||
|
|
||||||
Once setup Crontab UI provides you with a web interface using which you can manage all the jobs without much hassle.
|
Once setup Crontab UI provides you with a web interface using which you can manage all the jobs without much hassle.
|
||||||
|
|
|
@ -8,3 +8,7 @@ __crontab-ui is running but is not accessible on browser__ -
|
||||||
This is usually because the place where your crontab-ui is installed does not give access to others. It can be resolved by either giving permission to the user (Recommended) or running crontab-ui as root. Refer [this](https://github.com/alseambusher/crontab-ui/issues/8)
|
This is usually because the place where your crontab-ui is installed does not give access to others. It can be resolved by either giving permission to the user (Recommended) or running crontab-ui as root. Refer [this](https://github.com/alseambusher/crontab-ui/issues/8)
|
||||||
|
|
||||||
__Hosting crontab-ui : it works on localhost but not outside the server__ - You have to host it using nginx, apache2, etc. Refer [this](nginx.md).
|
__Hosting crontab-ui : it works on localhost but not outside the server__ - You have to host it using nginx, apache2, etc. Refer [this](nginx.md).
|
||||||
|
|
||||||
|
__crontab-ui stopped working__ - It can happen that your crontab-ui can stop working for some reason like adding incorrect jobs or timings. In order to fix it, you can just go ahead a remove the job from `crontab.db` or `env.db` in "crontabs" folder and restart crontab-ui.
|
||||||
|
|
||||||
|
__Where is my root node_modules folder__ - You can find it by `npm root -g`
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Mailing and Hooks
|
||||||
|
=================
|
67
app.js
67
app.js
|
@ -1,3 +1,4 @@
|
||||||
|
/*jshint esversion: 6*/
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var app = express();
|
var app = express();
|
||||||
var crontab = require("./crontab");
|
var crontab = require("./crontab");
|
||||||
|
@ -9,7 +10,6 @@ var mime = require('mime');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var busboy = require('connect-busboy'); // for file upload
|
var busboy = require('connect-busboy'); // for file upload
|
||||||
|
|
||||||
|
|
||||||
// include the routes
|
// include the routes
|
||||||
var routes = require("./routes").routes;
|
var routes = require("./routes").routes;
|
||||||
|
|
||||||
|
@ -27,14 +27,17 @@ app.use(busboy()); // to support file uploads
|
||||||
app.use(express.static(__dirname + '/public'));
|
app.use(express.static(__dirname + '/public'));
|
||||||
app.use(express.static(__dirname + '/public/css'));
|
app.use(express.static(__dirname + '/public/css'));
|
||||||
app.use(express.static(__dirname + '/public/js'));
|
app.use(express.static(__dirname + '/public/js'));
|
||||||
|
app.use(express.static(__dirname + '/config'));
|
||||||
app.set('views', __dirname + '/views');
|
app.set('views', __dirname + '/views');
|
||||||
|
|
||||||
//set port
|
// set port to 8000 or the value set by environment var PORT
|
||||||
app.set('port', (process.env.PORT || 8000));
|
app.set('port', (process.env.PORT || 8000));
|
||||||
|
|
||||||
|
// root page handler
|
||||||
app.get(routes.root, function(req, res) {
|
app.get(routes.root, function(req, res) {
|
||||||
// get all the crontabs
|
// reload the database before rendering
|
||||||
crontab.reload_db();
|
crontab.reload_db();
|
||||||
|
// send all the required parameters
|
||||||
crontab.crontabs( function(docs){
|
crontab.crontabs( function(docs){
|
||||||
res.render('index', {
|
res.render('index', {
|
||||||
routes : JSON.stringify(routes),
|
routes : JSON.stringify(routes),
|
||||||
|
@ -46,10 +49,15 @@ app.get(routes.root, function(req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handle to save crontab to database
|
||||||
|
If it is a new job @param _id is set to -1
|
||||||
|
@param name, command, schedule, logging has to be sent with _id (if exists)
|
||||||
|
*/
|
||||||
app.post(routes.save, function(req, res) {
|
app.post(routes.save, function(req, res) {
|
||||||
// new job
|
// new job
|
||||||
if(req.body._id == -1){
|
if(req.body._id == -1){
|
||||||
crontab.create_new(req.body.name, req.body.command, req.body.schedule, req.body.logging);
|
crontab.create_new(req.body.name, req.body.command, req.body.schedule, req.body.logging, req.body.mailing);
|
||||||
}
|
}
|
||||||
// edit job
|
// edit job
|
||||||
else{
|
else{
|
||||||
|
@ -58,30 +66,39 @@ app.post(routes.save, function(req, res) {
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// set stop to job
|
||||||
app.post(routes.stop, function(req, res) {
|
app.post(routes.stop, function(req, res) {
|
||||||
crontab.status(req.body._id, true);
|
crontab.status(req.body._id, true);
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// set start to job
|
||||||
app.post(routes.start, function(req, res) {
|
app.post(routes.start, function(req, res) {
|
||||||
crontab.status(req.body._id, false);
|
crontab.status(req.body._id, false);
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// remove a job
|
||||||
app.post(routes.remove, function(req, res) {
|
app.post(routes.remove, function(req, res) {
|
||||||
crontab.remove(req.body._id);
|
crontab.remove(req.body._id);
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
app.get(routes.crontab, function(req, res) {
|
|
||||||
crontab.set_crontab(req.query.env_vars);
|
// set crontab. Needs env_vars to be passed
|
||||||
res.end();
|
app.get(routes.crontab, function(req, res, next) {
|
||||||
|
crontab.set_crontab(req.query.env_vars, function(err) {
|
||||||
|
if (err) next(err);
|
||||||
|
else res.end();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// backup crontab db
|
||||||
app.get(routes.backup, function(req, res) {
|
app.get(routes.backup, function(req, res) {
|
||||||
crontab.backup();
|
crontab.backup();
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This renders the restore page similar to backup page
|
||||||
app.get(routes.restore, function(req, res) {
|
app.get(routes.restore, function(req, res) {
|
||||||
// get all the crontabs
|
// get all the crontabs
|
||||||
restore.crontabs(req.query.db, function(docs){
|
restore.crontabs(req.query.db, function(docs){
|
||||||
|
@ -94,16 +111,19 @@ app.get(routes.restore, function(req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// delete backup db
|
||||||
app.get(routes.delete_backup, function(req, res) {
|
app.get(routes.delete_backup, function(req, res) {
|
||||||
restore.delete(req.query.db);
|
restore.delete(req.query.db);
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// restore from backup db
|
||||||
app.get(routes.restore_backup, function(req, res) {
|
app.get(routes.restore_backup, function(req, res) {
|
||||||
crontab.restore(req.query.db);
|
crontab.restore(req.query.db);
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// export current crontab db so that user can download it
|
||||||
app.get(routes.export, function(req, res) {
|
app.get(routes.export, function(req, res) {
|
||||||
var file = __dirname + '/crontabs/crontab.db';
|
var file = __dirname + '/crontabs/crontab.db';
|
||||||
|
|
||||||
|
@ -117,7 +137,7 @@ app.get(routes.export, function(req, res) {
|
||||||
filestream.pipe(res);
|
filestream.pipe(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// import from exported crontab db
|
||||||
app.post(routes.import, function(req, res) {
|
app.post(routes.import, function(req, res) {
|
||||||
var fstream;
|
var fstream;
|
||||||
req.pipe(req.busboy);
|
req.pipe(req.busboy);
|
||||||
|
@ -131,13 +151,14 @@ app.post(routes.import, function(req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// import from current ACTUALL crontab
|
||||||
app.get(routes.import_crontab, function(req, res) {
|
app.get(routes.import_crontab, function(req, res) {
|
||||||
crontab.import_crontab();
|
crontab.import_crontab();
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// get the log file a given job. id passed as query param
|
||||||
app.get(routes.logger, function(req, res) {
|
app.get(routes.logger, function(req, res) {
|
||||||
var fs = require("fs");
|
|
||||||
_file = crontab.log_folder +"/"+req.query.id+".log";
|
_file = crontab.log_folder +"/"+req.query.id+".log";
|
||||||
if (fs.existsSync(_file))
|
if (fs.existsSync(_file))
|
||||||
res.sendFile(_file);
|
res.sendFile(_file);
|
||||||
|
@ -145,6 +166,34 @@ app.get(routes.logger, function(req, res) {
|
||||||
res.end("No errors logged yet");
|
res.end("No errors logged yet");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// error handler
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
var data = {};
|
||||||
|
var statusCode = err.statusCode || 500;
|
||||||
|
|
||||||
|
data.message = err.message || 'Internal Server Error';
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development' && err.stack) {
|
||||||
|
data.stack = err.stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt(data.statusCode) >= 500) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(statusCode).json(data);
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(app.get('port'), function() {
|
app.listen(app.get('port'), function() {
|
||||||
|
// If --autosave is used then we will also save whatever is in the db automatically without having to mention it explictly
|
||||||
|
// we do this by watching log file and setting a on change hook to it
|
||||||
|
if (process.argv.includes("--autosave")){
|
||||||
|
crontab.autosave_crontab(()=>{});
|
||||||
|
fs.watchFile(__dirname + '/crontabs/crontab.db', () => {
|
||||||
|
crontab.autosave_crontab(()=>{
|
||||||
|
console.log("Attempted to autosave crontab");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
console.log("Crontab UI is running at http://localhost:" + app.get('port'));
|
console.log("Crontab UI is running at http://localhost:" + app.get('port'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var defaults = require("../config/mailconfig.js");
|
||||||
|
|
||||||
|
var nodemailer = require('nodemailer');
|
||||||
|
|
||||||
|
// create reusable transporter object using the default SMTP transport
|
||||||
|
var transporter = nodemailer.createTransport(defaults.transporterStr);
|
||||||
|
var mailOptions = defaults.mailOptions;
|
||||||
|
|
||||||
|
var stdin = process.stdin,
|
||||||
|
stdout = process.stdout,
|
||||||
|
inputChunks = [];
|
||||||
|
|
||||||
|
stdin.resume();
|
||||||
|
stdin.setEncoding('utf8');
|
||||||
|
|
||||||
|
stdin.on('data', function (chunk) {
|
||||||
|
inputChunks.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
stdin.on('end', function () {
|
||||||
|
var inputJSON = inputChunks.join(),
|
||||||
|
mailOptions = JSON.parse(inputJSON);
|
||||||
|
|
||||||
|
// outputJSON = JSON.stringify(parsedData, null, ' ');
|
||||||
|
// stdout.write(outputJSON);
|
||||||
|
// stdout.write('\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
transporter.sendMail(mailOptions, function(error, info){
|
||||||
|
if(error){
|
||||||
|
return console.log(error);
|
||||||
|
}
|
||||||
|
console.log('Message sent: ' + info.response);
|
||||||
|
});
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*jshint esversion: 6*/
|
||||||
|
// refer nodemailer for more info
|
||||||
|
|
||||||
|
var transporterStr = 'smtps://user%40gmail.com:pass@smtp.gmail.com';
|
||||||
|
|
||||||
|
var mailOptions = {
|
||||||
|
from: '"Fred Foo 👥" <foo@blurdybloop.com>', // sender address
|
||||||
|
to: 'bar@blurdybloop.com, baz@blurdybloop.com', // list of receivers
|
||||||
|
subject: 'Hello ✔', // Subject line
|
||||||
|
text: 'Hello world 🐴', // plaintext body
|
||||||
|
html: '<b>Hello world 🐴</b>' // html body
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
exports.transporterStr = transporterStr;
|
||||||
|
exports.mailOptions = mailOptions;
|
||||||
|
} else {
|
||||||
|
if (!window.config)
|
||||||
|
window.config = {};
|
||||||
|
window.config.transporterStr = transporterStr;
|
||||||
|
window.config.mailOptions = mailOptions;
|
||||||
|
}
|
75
crontab.js
75
crontab.js
|
@ -1,8 +1,12 @@
|
||||||
|
/*jshint esversion: 6*/
|
||||||
//load database
|
//load database
|
||||||
var Datastore = require('nedb');
|
var Datastore = require('nedb');
|
||||||
var db = new Datastore({ filename: __dirname + '/crontabs/crontab.db' });
|
var db = new Datastore({ filename: __dirname + '/crontabs/crontab.db' });
|
||||||
|
|
||||||
db.loadDatabase(function (err) {
|
db.loadDatabase(function (err) {
|
||||||
|
if (err) throw err; // no hope, just terminate
|
||||||
});
|
});
|
||||||
|
|
||||||
var exec = require('child_process').exec;
|
var exec = require('child_process').exec;
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var cron_parser = require("cron-parser");
|
var cron_parser = require("cron-parser");
|
||||||
|
@ -11,7 +15,7 @@ var os = require("os");
|
||||||
exports.log_folder = __dirname + '/crontabs/logs';
|
exports.log_folder = __dirname + '/crontabs/logs';
|
||||||
exports.env_file = __dirname + '/crontabs/env.db';
|
exports.env_file = __dirname + '/crontabs/env.db';
|
||||||
|
|
||||||
crontab = function(name, command, schedule, stopped, logging){
|
crontab = function(name, command, schedule, stopped, logging, mailing){
|
||||||
var data = {};
|
var data = {};
|
||||||
data.name = name;
|
data.name = name;
|
||||||
data.command = command;
|
data.command = command;
|
||||||
|
@ -21,17 +25,20 @@ crontab = function(name, command, schedule, stopped, logging){
|
||||||
}
|
}
|
||||||
data.timestamp = (new Date()).toString();
|
data.timestamp = (new Date()).toString();
|
||||||
data.logging = logging;
|
data.logging = logging;
|
||||||
|
if (!mailing)
|
||||||
|
mailing = {};
|
||||||
|
data.mailing = mailing;
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.create_new = function(name, command, schedule, logging){
|
exports.create_new = function(name, command, schedule, logging, mailing){
|
||||||
var tab = crontab(name, command, schedule, false, logging);
|
var tab = crontab(name, command, schedule, false, logging, mailing);
|
||||||
tab.created = new Date().valueOf();
|
tab.created = new Date().valueOf();
|
||||||
db.insert(tab);
|
db.insert(tab);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.update = function(data){
|
exports.update = function(data){
|
||||||
db.update({_id: data._id}, crontab(data.name, data.command, data.schedule, null, data.logging));
|
db.update({_id: data._id}, crontab(data.name, data.command, data.schedule, null, data.logging, data.mailing));
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.status = function(_id, stopped){
|
exports.status = function(_id, stopped){
|
||||||
|
@ -41,6 +48,8 @@ exports.status = function(_id, stopped){
|
||||||
exports.remove = function(_id){
|
exports.remove = function(_id){
|
||||||
db.remove({_id: _id}, {});
|
db.remove({_id: _id}, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Iterates through all the crontab entries in the db and calls the callback with the entries
|
||||||
exports.crontabs = function(callback){
|
exports.crontabs = function(callback){
|
||||||
db.find({}).sort({ created: -1 }).exec(function(err, docs){
|
db.find({}).sort({ created: -1 }).exec(function(err, docs){
|
||||||
for(var i=0; i<docs.length; i++){
|
for(var i=0; i<docs.length; i++){
|
||||||
|
@ -52,7 +61,9 @@ exports.crontabs = function(callback){
|
||||||
callback(docs);
|
callback(docs);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
exports.set_crontab = function(env_vars){
|
|
||||||
|
// Set actual crontab file from the db
|
||||||
|
exports.set_crontab = function(env_vars, callback){
|
||||||
exports.crontabs( function(tabs){
|
exports.crontabs( function(tabs){
|
||||||
var crontab_string = "";
|
var crontab_string = "";
|
||||||
if (env_vars) {
|
if (env_vars) {
|
||||||
|
@ -61,23 +72,49 @@ exports.set_crontab = function(env_vars){
|
||||||
tabs.forEach(function(tab){
|
tabs.forEach(function(tab){
|
||||||
if(!tab.stopped) {
|
if(!tab.stopped) {
|
||||||
if (tab.logging && tab.logging == "true") {
|
if (tab.logging && tab.logging == "true") {
|
||||||
tmp_log = "/tmp/" + tab._id + ".log";
|
let tmp_log = "/tmp/" + tab._id + ".log";
|
||||||
log_file = exports.log_folder + "/" + tab._id + ".log";
|
let log_file = exports.log_folder + "/" + tab._id + ".log";
|
||||||
if(tab.command[tab.command.length-1] != ";") // add semicolon
|
if(tab.command[tab.command.length-1] != ";") // add semicolon
|
||||||
tab.command +=";";
|
tab.command +=";";
|
||||||
//{ command; } 2>/tmp/<id>.log|| {if test -f /tmp/<id>; then date >> <log file>; cat /tmp/<id>.log >> <log file>; rm /tmp<id>.log }
|
// hook is in beta
|
||||||
crontab_string += tab.schedule + " { " + tab.command + " } 2> " + tmp_log +"; if test -f " + tmp_log +"; then date >> " + log_file + "; cat " + tmp_log + " >> " + log_file + "; rm " + tmp_log + "; fi \n";
|
if (tab.hook){
|
||||||
|
let tmp_hook = "/tmp/" + tab._id + ".hook";
|
||||||
|
crontab_string += tab.schedule + " ({ " + tab.command + " } | tee " + tmp_hook + ") 3>&1 1>&2 2>&3 | tee " + tmp_log +
|
||||||
|
"; if test -f " + tmp_log +
|
||||||
|
"; then date >> " + log_file +
|
||||||
|
"; cat " + tmp_log + " >> " + log_file +
|
||||||
|
"; rm " + tmp_log +
|
||||||
|
"; fi; if test -f " + tmp_hook +
|
||||||
|
"; then " + tab.hook + " < " + tmp_hook +
|
||||||
|
"; rm " + tmp_hook +
|
||||||
|
"; fi \n";
|
||||||
|
} else {
|
||||||
|
crontab_string += tab.schedule + " { " + tab.command + " } 2> " + tmp_log +
|
||||||
|
"; if test -f " + tmp_log +
|
||||||
|
"; then date >> " + log_file +
|
||||||
|
"; cat " + tmp_log + " >> " + log_file +
|
||||||
|
"; rm " + tmp_log +
|
||||||
|
"; fi \n";
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
else {
|
||||||
crontab_string += tab.schedule + " " + tab.command + "\n";
|
crontab_string += tab.schedule + " " + tab.command + "\n";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFile(exports.env_file, env_vars);
|
fs.writeFile(exports.env_file, env_vars, function(err) {
|
||||||
|
if (err) callback(err);
|
||||||
|
|
||||||
fs.writeFile("/tmp/crontab", crontab_string, function(err) {
|
fs.writeFile("/tmp/crontab", crontab_string, function(err) {
|
||||||
exec("crontab /tmp/crontab");
|
if (err) return callback(err);
|
||||||
});
|
|
||||||
|
|
||||||
|
exec("crontab /tmp/crontab", function(err) {
|
||||||
|
if (err) return callback(err);
|
||||||
|
else callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -85,7 +122,7 @@ exports.get_backup_names = function(){
|
||||||
var backups = [];
|
var backups = [];
|
||||||
fs.readdirSync(__dirname + '/crontabs').forEach(function(file){
|
fs.readdirSync(__dirname + '/crontabs').forEach(function(file){
|
||||||
// file name begins with backup
|
// file name begins with backup
|
||||||
if(file.indexOf("backup") == 0){
|
if(file.indexOf("backup") === 0){
|
||||||
backups.push(file);
|
backups.push(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -139,7 +176,10 @@ exports.import_crontab = function(){
|
||||||
var command = line.replace(regex, '').trim();
|
var command = line.replace(regex, '').trim();
|
||||||
var schedule = line.replace(command, '').trim();
|
var schedule = line.replace(command, '').trim();
|
||||||
|
|
||||||
if(command && schedule){
|
var is_valid = false;
|
||||||
|
try { is_valid = cron_parser.parseString(line).expressions.length > 0; } catch (e){}
|
||||||
|
|
||||||
|
if(command && schedule && is_valid){
|
||||||
var name = namePrefix + '_' + index;
|
var name = namePrefix + '_' + index;
|
||||||
|
|
||||||
db.findOne({ command: command, schedule: schedule }, function(err, doc) {
|
db.findOne({ command: command, schedule: schedule }, function(err, doc) {
|
||||||
|
@ -159,3 +199,8 @@ exports.import_crontab = function(){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.autosave_crontab = function(callback) {
|
||||||
|
let env_vars = exports.get_env();
|
||||||
|
exports.set_crontab(env_vars, callback);
|
||||||
|
};
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
"node": "latest"
|
"node": "latest"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"crontab-ui": "bin/crontab-ui.js"
|
"crontab-ui": "bin/crontab-ui.js",
|
||||||
|
"crontab-ui-mailer": "bin/crontab-ui-mailer.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -6,6 +6,13 @@ function infoMessageBox(message, title){
|
||||||
$("#info-title").html(title);
|
$("#info-title").html(title);
|
||||||
$("#info-popup").modal('show');
|
$("#info-popup").modal('show');
|
||||||
}
|
}
|
||||||
|
// like info, but for errors.
|
||||||
|
function errorMessageBox(message) {
|
||||||
|
var msg =
|
||||||
|
"Operation failed: " + message + ". " +
|
||||||
|
"Please see error log for details.";
|
||||||
|
infoMessageBox(msg, "Error");
|
||||||
|
}
|
||||||
// modal with full control
|
// modal with full control
|
||||||
function messageBox(body, title, ok_text, close_text, callback){
|
function messageBox(body, title, ok_text, close_text, callback){
|
||||||
$("#modal-body").html(body);
|
$("#modal-body").html(body);
|
||||||
|
@ -19,6 +26,9 @@ function messageBox(body, title, ok_text, close_text, callback){
|
||||||
|
|
||||||
|
|
||||||
/*********** crontab actions ****************/
|
/*********** crontab actions ****************/
|
||||||
|
// TODO get rid of global variables
|
||||||
|
var schedule = "";
|
||||||
|
var job_command = "";
|
||||||
|
|
||||||
function deleteJob(_id){
|
function deleteJob(_id){
|
||||||
// TODO fix this. pass callback properly
|
// TODO fix this. pass callback properly
|
||||||
|
@ -50,6 +60,8 @@ function setCrontab(){
|
||||||
$.get(routes.crontab, { "env_vars": $("#env_vars").val() }, function(){
|
$.get(routes.crontab, { "env_vars": $("#env_vars").val() }, function(){
|
||||||
// TODO show only if success
|
// TODO show only if success
|
||||||
infoMessageBox("Successfuly set crontab file!","Information");
|
infoMessageBox("Successfuly set crontab file!","Information");
|
||||||
|
}).fail(function(response) {
|
||||||
|
errorMessageBox(response.statusText,"Error");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -75,7 +87,7 @@ function editJob(_id){
|
||||||
$("#job-name").val(job.name);
|
$("#job-name").val(job.name);
|
||||||
$("#job-command").val(job.command);
|
$("#job-command").val(job.command);
|
||||||
// if macro not used
|
// if macro not used
|
||||||
if(job.schedule.indexOf("@") != 0){
|
if(job.schedule.indexOf("@") !== 0){
|
||||||
var components = job.schedule.split(" ");
|
var components = job.schedule.split(" ");
|
||||||
$("#job-minute").val(components[0]);
|
$("#job-minute").val(components[0]);
|
||||||
$("#job-hour").val(components[1]);
|
$("#job-hour").val(components[1]);
|
||||||
|
@ -83,6 +95,9 @@ function editJob(_id){
|
||||||
$("#job-month").val(components[3]);
|
$("#job-month").val(components[3]);
|
||||||
$("#job-week").val(components[4]);
|
$("#job-week").val(components[4]);
|
||||||
}
|
}
|
||||||
|
if (job.mailing) {
|
||||||
|
$("#job-mailing").attr("data-json", JSON.stringify(job.mailing));
|
||||||
|
}
|
||||||
schedule = job.schedule;
|
schedule = job.schedule;
|
||||||
job_command = job.command;
|
job_command = job.command;
|
||||||
if (job.logging && job.logging != "false")
|
if (job.logging && job.logging != "false")
|
||||||
|
@ -93,7 +108,13 @@ function editJob(_id){
|
||||||
$("#job-save").unbind("click"); // remove existing events attached to this
|
$("#job-save").unbind("click"); // remove existing events attached to this
|
||||||
$("#job-save").click(function(){
|
$("#job-save").click(function(){
|
||||||
// TODO good old boring validations
|
// TODO good old boring validations
|
||||||
$.post(routes.save, {name: $("#job-name").val(), command: job_command , schedule: schedule, _id: _id, logging: $("#job-logging").prop("checked")}, function(){
|
if (!schedule) {
|
||||||
|
schedule = "* * * * *";
|
||||||
|
}
|
||||||
|
let name = $("#job-name").val();
|
||||||
|
let mailing = JSON.parse($("#job-mailing").attr("data-json"));
|
||||||
|
let logging = $("#job-logging").prop("checked");
|
||||||
|
$.post(routes.save, {name: name, command: job_command , schedule: schedule, _id: _id, logging: logging, mailing: mailing}, function(){
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -111,11 +132,18 @@ function newJob(){
|
||||||
$("#job").modal("show");
|
$("#job").modal("show");
|
||||||
$("#job-name").val("");
|
$("#job-name").val("");
|
||||||
$("#job-command").val("");
|
$("#job-command").val("");
|
||||||
|
$("#job-mailing").attr("data-json", "{}");
|
||||||
job_string();
|
job_string();
|
||||||
$("#job-save").unbind("click"); // remove existing events attached to this
|
$("#job-save").unbind("click"); // remove existing events attached to this
|
||||||
$("#job-save").click(function(){
|
$("#job-save").click(function(){
|
||||||
// TODO good old boring validations
|
// TODO good old boring validations
|
||||||
$.post(routes.save, {name: $("#job-name").val(), command: job_command , schedule: schedule, _id: -1, logging: $("#job-logging").prop("checked")}, function(){
|
if (!schedule) {
|
||||||
|
schedule = "* * * * *";
|
||||||
|
}
|
||||||
|
let name = $("#job-name").val();
|
||||||
|
let mailing = JSON.parse($("#job-mailing").attr("data-json"));
|
||||||
|
let logging = $("#job-logging").prop("checked");
|
||||||
|
$.post(routes.save, {name: name, command: job_command , schedule: schedule, _id: -1, logging: logging, mailing: mailing}, function(){
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -151,10 +179,65 @@ function import_db(){
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setMailConfig(a){
|
||||||
|
let data = JSON.parse(a.getAttribute("data-json"));
|
||||||
|
let container = document.createElement("div");
|
||||||
|
|
||||||
|
let message = "<p>This is based on nodemailer. Refer <a href='https://github.com/alseambusher/crontab-ui/tree/master/README/mail.md'>this</a> for more details.</p>";
|
||||||
|
container.innerHTML += message;
|
||||||
|
|
||||||
|
let transporterLabel = document.createElement("label");
|
||||||
|
transporterLabel.innerHTML = "Transporter";
|
||||||
|
let transporterInput = document.createElement("input");
|
||||||
|
transporterInput.type = "text";
|
||||||
|
transporterInput.id = "transporterInput";
|
||||||
|
transporterInput.setAttribute("placeholder", config.transporterStr);
|
||||||
|
transporterInput.className = "form-control";
|
||||||
|
if (data.transporterStr){
|
||||||
|
transporterInput.setAttribute("value", data.transporterStr);
|
||||||
|
}
|
||||||
|
container.appendChild(transporterLabel);
|
||||||
|
container.appendChild(transporterInput);
|
||||||
|
|
||||||
|
container.innerHTML += "<br/>";
|
||||||
|
|
||||||
|
let mailOptionsLabel = document.createElement("label");
|
||||||
|
mailOptionsLabel.innerHTML = "Mail Config";
|
||||||
|
let mailOptionsInput = document.createElement("textarea");
|
||||||
|
mailOptionsInput.setAttribute("placeholder", JSON.stringify(config.mailOptions, null, 2));
|
||||||
|
mailOptionsInput.className = "form-control";
|
||||||
|
mailOptionsInput.id = "mailOptionsInput";
|
||||||
|
mailOptionsInput.setAttribute("rows", "10");
|
||||||
|
if (data.mailOptions)
|
||||||
|
mailOptionsInput.innerHTML = JSON.stringify(data.mailOptions, null, 2);
|
||||||
|
container.appendChild(mailOptionsLabel);
|
||||||
|
container.appendChild(mailOptionsInput);
|
||||||
|
|
||||||
|
container.innerHTML += "<br/>";
|
||||||
|
|
||||||
|
let button = document.createElement("a");
|
||||||
|
button.className = "btn btn-primary btn-small";
|
||||||
|
button.innerHTML = "Use Defaults";
|
||||||
|
button.onclick = function(){
|
||||||
|
document.getElementById("transporterInput").value = config.transporterStr;
|
||||||
|
document.getElementById("mailOptionsInput").innerHTML = JSON.stringify(config.mailOptions, null, 2);
|
||||||
|
};
|
||||||
|
container.appendChild(button);
|
||||||
|
|
||||||
|
messageBox(container, "Mailing", null, null, function(){
|
||||||
|
let transporterStr = document.getElementById("transporterInput").value;
|
||||||
|
let mailOptions = JSON.parse(document.getElementById("mailOptionsInput").innerHTML);
|
||||||
|
if (transporterStr && mailOptions){
|
||||||
|
a.setAttribute("data-json", JSON.stringify({transporterStr: transporterStr, mailOptions: mailOptions}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHookConfig(a){
|
||||||
|
messageBox("<p>Coming Soon</p>", "Hooks", null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
// script corresponding to job popup management
|
// script corresponding to job popup management
|
||||||
var schedule = "";
|
|
||||||
var job_command = "";
|
|
||||||
function job_string(){
|
function job_string(){
|
||||||
$("#job-string").val(schedule + " " + job_command);
|
$("#job-string").val(schedule + " " + job_command);
|
||||||
return schedule + " " + job_command;
|
return schedule + " " + job_command;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<script src="jquery.js"></script>
|
<script src="jquery.js"></script>
|
||||||
<script src="script.js"></script>
|
<script src="script.js"></script>
|
||||||
<script src="bootstrap.min.js"></script>
|
<script src="bootstrap.min.js"></script>
|
||||||
|
<script src="mailconfig.js"></script>
|
||||||
<script type="text/javascript" src="https://cdn.datatables.net/v/bs/dt-1.10.12/datatables.min.js"></script>
|
<script type="text/javascript" src="https://cdn.datatables.net/v/bs/dt-1.10.12/datatables.min.js"></script>
|
||||||
<link rel="stylesheet" href="bootstrap.min.css" />
|
<link rel="stylesheet" href="bootstrap.min.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.12/css/dataTables.bootstrap.min.css"/>
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.12/css/dataTables.bootstrap.min.css"/>
|
||||||
|
|
|
@ -1,36 +1,3 @@
|
||||||
<div class="modal fade" id="popup">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
||||||
<h4 class="modal-title" id="modal-title">Message</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" id="modal-body">
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal" id="modal-close-button">Close</button>
|
|
||||||
<button type="button" class="btn btn-primary" data-dismiss="modal" id="modal-button">Ok</button>
|
|
||||||
</div>
|
|
||||||
</div><!-- /.modal-content -->
|
|
||||||
</div><!-- /.modal-dialog -->
|
|
||||||
</div><!-- /.modal -->
|
|
||||||
|
|
||||||
<div class="modal fade" id="info-popup">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
||||||
<h4 class="modal-title" id="info-title">Message</h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" id="info-body">
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-primary" data-dismiss="modal" id="info-button">Ok</button>
|
|
||||||
</div>
|
|
||||||
</div><!-- /.modal-content -->
|
|
||||||
</div><!-- /.modal-dialog -->
|
|
||||||
</div><!-- /.modal -->
|
|
||||||
|
|
||||||
<!-- Job -->
|
<!-- Job -->
|
||||||
<div class="modal fade" id="job">
|
<div class="modal fade" id="job">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
|
@ -72,7 +39,9 @@
|
||||||
<br />
|
<br />
|
||||||
<label>Job</label>
|
<label>Job</label>
|
||||||
<input type='text' class='form-control' id='job-string' disabled='disabled'/><br />
|
<input type='text' class='form-control' id='job-string' disabled='disabled'/><br />
|
||||||
<label><input type="checkbox" id="job-logging" style="position:relative;top:2px"/> Enable error logging.</label>
|
<label><input type="checkbox" id="job-logging" style="position:relative;top:2px"/> Enable error logging.</label><br />
|
||||||
|
<a class="btn btn-primary btn-small" data-json="{}" onclick="setMailConfig(this);" id="job-mailing">Mailing</a>
|
||||||
|
<a class="btn btn-primary btn-small" data-json="{}" onclick="setHookConfig(this);">Hooks</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
@ -81,3 +50,36 @@
|
||||||
</div><!-- /.modal-content -->
|
</div><!-- /.modal-content -->
|
||||||
</div><!-- /.modal-dialog -->
|
</div><!-- /.modal-dialog -->
|
||||||
</div><!-- /.modal -->
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
<div class="modal fade" id="popup">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="modal-title">Message</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="modal-body">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal" id="modal-close-button">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" data-dismiss="modal" id="modal-button">Ok</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
<div class="modal fade" id="info-popup">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="info-title">Message</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="info-body">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" data-dismiss="modal" id="info-button">Ok</button>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
Loading…
Reference in New Issue