parent
285bcd1f25
commit
1c44ddb8ea
|
@ -3,49 +3,50 @@ Crontab UI
|
||||||
|
|
||||||
Editing the plain text crontab is error prone for managing jobs, e.g., adding jobs, deleting jobs, or pausing jobs. A small mistake can easily bring down all the jobs and might cost you a lot of time. With Crontab UI, it is very easy to manage crontab. Here are the key features of Crontab UI.
|
Editing the plain text crontab is error prone for managing jobs, e.g., adding jobs, deleting jobs, or pausing jobs. A small mistake can easily bring down all the jobs and might cost you a lot of time. With Crontab UI, it is very easy to manage crontab. Here are the key features of Crontab UI.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
1. Easy setup
|
1. Easy setup. You can even import from existing crontab.
|
||||||
2. Safe adding, deleting or pausing jobs. Easy to maintain hundreds of jobs.
|
2. Safe adding, deleting or pausing jobs. Easy to maintain hundreds of jobs.
|
||||||
3. Backup your crontabs.
|
3. Backup your crontabs.
|
||||||
4. Export crontab and deploy on other machines without much hassle.
|
4. Export crontab and deploy on other machines without much hassle.
|
||||||
5. Error log support (NEW)
|
5. Error log support.
|
||||||
|
|
||||||
Read [this](http://lifepluslinux.blogspot.in/2015/06/crontab-ui-easy-and-safe-way-to-manage.html) to see more details.
|
Read [this](http://lifepluslinux.blogspot.in/2015/06/crontab-ui-easy-and-safe-way-to-manage.html) to see more details.
|
||||||
|
|
||||||
##Setup
|
##Setup
|
||||||
|
|
||||||
npm install crontab-ui
|
npm install -g crontab-ui
|
||||||
crontab-ui
|
crontab-ui
|
||||||
|
|
||||||
###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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
###Import from existing crontab
|
||||||
|
|
||||||
|
Import from existing crontab file automatically.
|
||||||
|

|
||||||
|
|
||||||
###Backup and restore crontab
|
###Backup and restore crontab
|
||||||
|
|
||||||
Keep backups of your crontab in case you mess up.
|
Keep backups of your crontab in case you mess up.
|
||||||

|

|
||||||
|
|
||||||
###Export and import crontab on multiple instances of Crontab UI.
|
###Export and import crontab on multiple instances of Crontab UI.
|
||||||
|
|
||||||
If you want to run the same jobs on multiple machines simply export from one instance and import the same on the other. No SSH, No copy paste!
|
If you want to run the same jobs on multiple machines simply export from one instance and import the same on the other. No SSH, No copy paste!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
But make sure to take a backup before importing.
|
But make sure to take a backup before importing.
|
||||||
|
|
||||||
###See when the job is going to run next.
|
|
||||||

|
|
||||||
|
|
||||||
###Separate error log support for every job
|
###Separate error log support for every job
|
||||||

|

|
||||||
|
|
||||||
###Contribute
|
###Contribute
|
||||||
Fork Crontab UI and contribute to it. Pull requests are encouraged.
|
Fork Crontab UI and contribute to it. Pull requests are encouraged.
|
||||||
|
|
||||||
###License
|
###License
|
||||||
[MIT](LICENSE.md)
|
[MIT](LICENSE.md)
|
||||||
|
|
||||||
|
|
38
app.js
38
app.js
|
@ -16,12 +16,12 @@ var routes = require("./routes").routes;
|
||||||
// set the view engine to ejs
|
// set the view engine to ejs
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
|
|
||||||
var bodyParser = require('body-parser')
|
var bodyParser = require('body-parser');
|
||||||
app.use( bodyParser.json() ); // to support JSON-encoded bodies
|
app.use( bodyParser.json() ); // to support JSON-encoded bodies
|
||||||
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
|
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
|
||||||
extended: true
|
extended: true
|
||||||
}));
|
}));
|
||||||
app.use(busboy()) // to support file uploads
|
app.use(busboy()); // to support file uploads
|
||||||
|
|
||||||
// include all folders
|
// include all folders
|
||||||
app.use(express.static(__dirname + '/public'));
|
app.use(express.static(__dirname + '/public'));
|
||||||
|
@ -44,7 +44,7 @@ app.get(routes.root, function(req, res) {
|
||||||
moment: moment
|
moment: moment
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
app.post(routes.save, function(req, res) {
|
app.post(routes.save, function(req, res) {
|
||||||
// new job
|
// new job
|
||||||
|
@ -56,31 +56,31 @@ app.post(routes.save, function(req, res) {
|
||||||
crontab.update(req.body);
|
crontab.update(req.body);
|
||||||
}
|
}
|
||||||
res.end();
|
res.end();
|
||||||
})
|
});
|
||||||
|
|
||||||
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();
|
||||||
})
|
});
|
||||||
|
|
||||||
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();
|
||||||
})
|
});
|
||||||
|
|
||||||
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) {
|
app.get(routes.crontab, function(req, res) {
|
||||||
crontab.set_crontab(req.query.env_vars);
|
crontab.set_crontab(req.query.env_vars);
|
||||||
res.end();
|
res.end();
|
||||||
})
|
});
|
||||||
|
|
||||||
app.get(routes.backup, function(req, res) {
|
app.get(routes.backup, function(req, res) {
|
||||||
crontab.backup();
|
crontab.backup();
|
||||||
res.end();
|
res.end();
|
||||||
})
|
});
|
||||||
|
|
||||||
app.get(routes.restore, function(req, res) {
|
app.get(routes.restore, function(req, res) {
|
||||||
// get all the crontabs
|
// get all the crontabs
|
||||||
|
@ -92,17 +92,17 @@ app.get(routes.restore, function(req, res) {
|
||||||
db: req.query.db
|
db: req.query.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();
|
||||||
})
|
});
|
||||||
|
|
||||||
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();
|
||||||
})
|
});
|
||||||
|
|
||||||
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';
|
||||||
|
@ -115,7 +115,7 @@ app.get(routes.export, function(req, res) {
|
||||||
|
|
||||||
var filestream = fs.createReadStream(file);
|
var filestream = fs.createReadStream(file);
|
||||||
filestream.pipe(res);
|
filestream.pipe(res);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
app.post(routes.import, function(req, res) {
|
app.post(routes.import, function(req, res) {
|
||||||
|
@ -129,12 +129,12 @@ app.post(routes.import, function(req, res) {
|
||||||
res.redirect(routes.root);
|
res.redirect(routes.root);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
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();
|
||||||
})
|
});
|
||||||
|
|
||||||
app.get(routes.logger, function(req, res) {
|
app.get(routes.logger, function(req, res) {
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
@ -143,8 +143,8 @@ app.get(routes.logger, function(req, res) {
|
||||||
res.sendFile(_file);
|
res.sendFile(_file);
|
||||||
else
|
else
|
||||||
res.end("No errors logged yet");
|
res.end("No errors logged yet");
|
||||||
})
|
});
|
||||||
|
|
||||||
app.listen(app.get('port'), function() {
|
app.listen(app.get('port'), function() {
|
||||||
console.log("Crontab UI is running at localhost:" + app.get('port'))
|
console.log("Crontab UI is running at localhost:" + app.get('port'));
|
||||||
})
|
});
|
||||||
|
|
54
crontab.js
54
crontab.js
|
@ -5,8 +5,8 @@ db.loadDatabase(function (err) {
|
||||||
});
|
});
|
||||||
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");
|
||||||
var os = require("os")
|
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';
|
||||||
|
@ -22,36 +22,36 @@ crontab = function(name, command, schedule, stopped, logging){
|
||||||
data.timestamp = (new Date()).toString();
|
data.timestamp = (new Date()).toString();
|
||||||
data.logging = logging;
|
data.logging = logging;
|
||||||
return data;
|
return data;
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.create_new = function(name, command, schedule, logging){
|
exports.create_new = function(name, command, schedule, logging){
|
||||||
var tab = crontab(name, command, schedule, false, logging);
|
var tab = crontab(name, command, schedule, false, logging);
|
||||||
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));
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.status = function(_id, stopped){
|
exports.status = function(_id, stopped){
|
||||||
db.update({_id: _id},{$set: {stopped: stopped}});
|
db.update({_id: _id},{$set: {stopped: stopped}});
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.remove = function(_id){
|
exports.remove = function(_id){
|
||||||
db.remove({_id: _id}, {});
|
db.remove({_id: _id}, {});
|
||||||
}
|
};
|
||||||
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++){
|
||||||
if(docs[i].schedule == "@reboot")
|
if(docs[i].schedule == "@reboot")
|
||||||
docs[i].next = "Next Reboot"
|
docs[i].next = "Next Reboot";
|
||||||
else
|
else
|
||||||
docs[i].next = cron_parser.parseExpression(docs[i].schedule).next().toString();
|
docs[i].next = cron_parser.parseExpression(docs[i].schedule).next().toString();
|
||||||
}
|
}
|
||||||
callback(docs);
|
callback(docs);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
exports.set_crontab = function(env_vars){
|
exports.set_crontab = function(env_vars){
|
||||||
exports.crontabs( function(tabs){
|
exports.crontabs( function(tabs){
|
||||||
var crontab_string = "";
|
var crontab_string = "";
|
||||||
|
@ -64,7 +64,7 @@ exports.set_crontab = function(env_vars){
|
||||||
tmp_log = "/tmp/" + tab._id + ".log";
|
tmp_log = "/tmp/" + tab._id + ".log";
|
||||||
log_file = exports.log_folder + "/" + tab._id + ".log";
|
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 }
|
//{ command; } 2>/tmp/<id>.log|| {if test -f /tmp/<id>; then date >> <log file>; cat /tmp/<id>.log >> <log file>; rm /tmp<id>.log }
|
||||||
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";
|
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";
|
||||||
}
|
}
|
||||||
|
@ -79,10 +79,10 @@ exports.set_crontab = function(env_vars){
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.get_backup_names = function(){
|
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){
|
||||||
|
@ -92,10 +92,10 @@ exports.get_backup_names = function(){
|
||||||
|
|
||||||
// Sort by date. Newest on top
|
// Sort by date. Newest on top
|
||||||
for(var i=0; i<backups.length; i++){
|
for(var i=0; i<backups.length; i++){
|
||||||
var Ti = backups[i].split("backup")[1]
|
var Ti = backups[i].split("backup")[1];
|
||||||
Ti = new Date(Ti.substring(0, Ti.length-3)).valueOf();
|
Ti = new Date(Ti.substring(0, Ti.length-3)).valueOf();
|
||||||
for(var j=0; j<i; j++){
|
for(var j=0; j<i; j++){
|
||||||
var Tj = backups[j].split("backup")[1]
|
var Tj = backups[j].split("backup")[1];
|
||||||
Tj = new Date(Tj.substring(0, Tj.length-3)).valueOf();
|
Tj = new Date(Tj.substring(0, Tj.length-3)).valueOf();
|
||||||
if(Ti > Tj){
|
if(Ti > Tj){
|
||||||
var temp = backups[i];
|
var temp = backups[i];
|
||||||
|
@ -106,34 +106,32 @@ exports.get_backup_names = function(){
|
||||||
}
|
}
|
||||||
|
|
||||||
return backups;
|
return backups;
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.backup = function(){
|
exports.backup = function(){
|
||||||
//TODO check if it failed
|
//TODO check if it failed
|
||||||
fs.createReadStream( __dirname + '/crontabs/crontab.db').pipe(fs.createWriteStream( __dirname + '/crontabs/backup ' + (new Date()).toString().replace("+", " ") + '.db'));
|
fs.createReadStream( __dirname + '/crontabs/crontab.db').pipe(fs.createWriteStream( __dirname + '/crontabs/backup ' + (new Date()).toString().replace("+", " ") + '.db'));
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.restore = function(db_name){
|
exports.restore = function(db_name){
|
||||||
fs.createReadStream( __dirname + '/crontabs/' + db_name).pipe(fs.createWriteStream( __dirname + '/crontabs/crontab.db'));
|
fs.createReadStream( __dirname + '/crontabs/' + db_name).pipe(fs.createWriteStream( __dirname + '/crontabs/crontab.db'));
|
||||||
db.loadDatabase(); // reload the database
|
db.loadDatabase(); // reload the database
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.reload_db= function(){
|
exports.reload_db= function(){
|
||||||
db.loadDatabase();
|
db.loadDatabase();
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.get_env = function(){
|
exports.get_env = function(){
|
||||||
if (fs.existsSync(exports.env_file)) {
|
if (fs.existsSync(exports.env_file)) {
|
||||||
return fs.readFileSync(exports.env_file , 'utf8').replace("\n", "\n");
|
return fs.readFileSync(exports.env_file , 'utf8').replace("\n", "\n");
|
||||||
}
|
}
|
||||||
return ""
|
return "";
|
||||||
}
|
};
|
||||||
|
|
||||||
// TODO
|
|
||||||
exports.import_crontab = function(){
|
exports.import_crontab = function(){
|
||||||
exec("crontab -l", function(error, stdout, stderr){
|
exec("crontab -l", function(error, stdout, stderr){
|
||||||
var lines = stdout.split("\n");
|
var lines = stdout.split("\n");
|
||||||
|
|
||||||
var namePrefix = new Date().getTime();
|
var namePrefix = new Date().getTime();
|
||||||
|
|
||||||
lines.forEach(function(line, index){
|
lines.forEach(function(line, index){
|
||||||
|
@ -144,12 +142,12 @@ exports.import_crontab = function(){
|
||||||
if(command && schedule){
|
if(command && schedule){
|
||||||
var name = namePrefix + '_' + index;
|
var name = namePrefix + '_' + index;
|
||||||
|
|
||||||
db.crontabs.findOne({ command: command, schedule: schedule }, function(err, doc) {
|
db.findOne({ command: command, schedule: schedule }, function(err, doc) {
|
||||||
if(err) {
|
if(err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
if(!doc){
|
if(!doc){
|
||||||
exports.create_new(name, command, null, null, schedule, null);
|
exports.create_new(name, command, schedule, null);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
doc.command = command;
|
doc.command = command;
|
||||||
|
@ -157,11 +155,7 @@ exports.import_crontab = function(){
|
||||||
exports.update(doc);
|
exports.update(doc);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
})
|
|
||||||
//console.log(stdout);
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "crontab-ui",
|
"name": "crontab-ui",
|
||||||
"version": "0.1.7",
|
"version": "0.1.8",
|
||||||
"description": "Easy and safe way to manage your crontab file",
|
"description": "Easy and safe way to manage your crontab file",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<a class="btn btn-warning" onclick="import_db()"><span class="glyphicon glyphicon-import" aria-hidden="true"></span> Import</a>
|
<a class="btn btn-warning" onclick="import_db()"><span class="glyphicon glyphicon-import" aria-hidden="true"></span> Import</a>
|
||||||
<a class="btn btn-warning" href="<%= JSON.parse(routes).export %>"><span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span> Export</a>
|
<a class="btn btn-warning" href="<%= JSON.parse(routes).export %>"><span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span> Export</a>
|
||||||
<!--<a class="btn btn-info" onclick="import_crontab()"><span class="glyphicon glyphicon-import" aria-hidden="true"></span> Import from crontab</a>-->
|
<!--<a class="btn btn-info" onclick="import_crontab()"><span class="glyphicon glyphicon-import" aria-hidden="true"></span> Import from crontab</a>-->
|
||||||
<a class="btn btn-success" onclick="getCrontab();"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Get from crontab</a>
|
<a class="btn btn-success" onclick="getCrontab();"><span class="glyphicon glyphicon-open" aria-hidden="true"></span> Get from crontab</a>
|
||||||
<a class="btn btn-success" onclick="setCrontab();"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save to crontab</a>
|
<a class="btn btn-success" onclick="setCrontab();"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save to crontab</a>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
Loading…
Reference in New Issue