import crontab db

pull/1/head
Suresh Alse 2015-06-14 02:08:20 +05:30
parent 41ca8a72c7
commit 12679779cf
10 changed files with 134 additions and 19 deletions

View File

@ -14,9 +14,8 @@ Editing the plain text crontab is error prone for managing jobs, e.g., adding jo
TODO TODO
==== ====
1. Run jobs as different user 1. Run jobs as different user
2. Online backup 2. Profiling jobs
3. Profiling jobs 3. Logs
4. Logs 4. Importing from existing crontab file

39
app.js
View File

@ -3,6 +3,12 @@ var app = express();
var crontab = require("./crontab"); var crontab = require("./crontab");
var restore = require("./restore"); var restore = require("./restore");
var path = require('path');
var mime = require('mime');
var fs = require('fs');
var busboy = require('connect-busboy'); // for file upload
// include the routes // include the routes
var routes = require("./routes").routes; var routes = require("./routes").routes;
@ -14,6 +20,7 @@ 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
// include all folders // include all folders
app.use(express.static(__dirname + '/public')); app.use(express.static(__dirname + '/public'));
@ -92,6 +99,38 @@ app.get(routes.restore_backup, function(req, res) {
res.end(); res.end();
}) })
app.get(routes.export, function(req, res) {
var file = __dirname + '/crontabs/crontab.db';
var filename = path.basename(file);
var mimetype = mime.lookup(file);
res.setHeader('Content-disposition', 'attachment; filename=' + filename);
res.setHeader('Content-type', mimetype);
var filestream = fs.createReadStream(file);
filestream.pipe(res);
})
app.post(routes.import, function(req, res) {
var fstream;
req.pipe(req.busboy);
req.busboy.on('file', function (fieldname, file, filename) {
fstream = fs.createWriteStream(__dirname + '/crontabs/crontab.db');
file.pipe(fstream);
fstream.on('close', function () {
crontab.reload_db();
res.redirect(routes.root);
});
});
})
app.get(routes.import_crontab, function(req, res) {
crontab.import_crontab()
res.end();
})
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'))
}) })

View File

@ -5,19 +5,23 @@ 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")
crontab = function(name, command, schedule, stopped){ crontab = function(name, command, schedule, stopped){
var data = {}; var data = {};
data.name = name; data.name = name;
data.command = command; data.command = command;
data.schedule = schedule; data.schedule = schedule;
if(stopped != null) data.stopped = stopped; if(stopped != null) {
data.stopped = stopped;
}
data.timestamp = (new Date()).toString(); data.timestamp = (new Date()).toString();
return data; return data;
} }
exports.create_new = function(name, command, schedule){ exports.create_new = function(name, command, schedule){
var tab = crontab(name, command, schedule, false); var tab = crontab(name, command, schedule, false);
tab.created = new Date().valueOf();
db.insert(tab); db.insert(tab);
} }
@ -33,7 +37,13 @@ exports.remove = function(_id){
db.remove({_id: _id}, {}); db.remove({_id: _id}, {});
} }
exports.crontabs = function(callback){ exports.crontabs = function(callback){
db.find({}, function(err, docs){ db.find({}).sort({ created: -1 }).exec(function(err, docs){
for(var i=0; i<docs.length; i++){
if(docs[i].schedule == "@reboot")
docs[i].next = "Next Reboot"
else
docs[i].next = cron_parser.parseExpression(docs[i].schedule).next().toString();
}
callback(docs); callback(docs);
}); });
} }
@ -61,6 +71,21 @@ exports.get_backup_names = function(){
} }
}); });
// Sort by date. Newest on top
for(var i=0; i<backups.length; i++){
var Ti = backups[i].split("backup")[1]
Ti = new Date(Ti.substring(0, Ti.length-3)).valueOf();
for(var j=0; j<i; j++){
var Tj = backups[j].split("backup")[1]
Tj = new Date(Tj.substring(0, Tj.length-3)).valueOf();
if(Ti > Tj){
var temp = backups[i];
backups[i] = backups[j];
backups[j] = temp;
}
}
}
return backups; return backups;
} }
@ -74,6 +99,24 @@ exports.restore = function(db_name){
db.loadDatabase(); // reload the database db.loadDatabase(); // reload the database
} }
exports.import = function(){ exports.reload_db= function(){
//TODO db.loadDatabase();
}
// TODO
exports.import_crontab = function(){
exec("crontab -l", function(error, stdout, stderr){
var lines = stdout.split("\n");
lines.forEach(function(line){
/*
trim the spaces at edges
split the line based of space and tab
remove empty splits
If the first character is @
*/
//if(line.indexOf("@")
})
console.log(stdout);
});
} }

View File

@ -10,7 +10,10 @@
"express": "latest", "express": "latest",
"ejs": "latest", "ejs": "latest",
"nedb": "latest", "nedb": "latest",
"body-parser": "latest" "body-parser": "latest",
"mime": "latest",
"cron-parser": "latest",
"connect-busboy": "latest"
}, },
"engines": { "engines": {
"node": "latest" "node": "latest"

View File

@ -10,8 +10,8 @@ function messageBox(body, title, ok_text, close_text, callback){
$("#modal-body").html(body); $("#modal-body").html(body);
$("#modal-title").html(title); $("#modal-title").html(title);
if (ok_text) $("#modal-button").html(ok_text); if (ok_text) $("#modal-button").html(ok_text);
$("#modal-button").show();
if(close_text) $("#modal-close-button").html(close_text); if(close_text) $("#modal-close-button").html(close_text);
$("#modal-button").unbind("click"); // remove existing events attached to this
$("#modal-button").click(callback); $("#modal-button").click(callback);
$("#popup").modal("show"); $("#popup").modal("show");
} }
@ -131,6 +131,12 @@ function restore_backup(db_name){
}); });
} }
function import_db(){
messageBox("<p> Do you want to import crontab?<br /> <b style='color:red'>NOTE: It is recommended to take a backup before this.</b> </p>", "Confirm import from crontab", null, null, function(){
$('#import_file').click();
});
}
// script corresponding to job popup management // script corresponding to job popup management
var schedule = ""; var schedule = "";

View File

@ -8,7 +8,7 @@ exports.crontabs = function(db_name, callback){
var db = new Datastore({ filename: __dirname + '/crontabs/' + db_name }); var db = new Datastore({ filename: __dirname + '/crontabs/' + db_name });
db.loadDatabase(function (err) { db.loadDatabase(function (err) {
}); });
db.find({}, function(err, docs){ db.find({}).sort({ created: -1 }).exec(function(err, docs){
callback(docs); callback(docs);
}); });
} }

View File

@ -8,5 +8,8 @@ exports.routes = {
"backup": "/backup", "backup": "/backup",
"restore": "/restore", "restore": "/restore",
"delete_backup": "/delete", "delete_backup": "/delete",
"restore_backup": "/restore_backup" "restore_backup": "/restore_backup",
"export": "/export",
"import": "/import", // this is import from database
"import_crontab": "/import_crontab", // this is from existing crontab
} }

View File

@ -20,14 +20,29 @@
<% include navbar %> <% include navbar %>
<div class="container"> <div class="container">
<h2>Cronjobs</h2> <h2>Cronjobs</h2>
<a class="btn btn-primary" onclick="newJob();"><span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span> New</a>
<a class="btn btn-info" onclick="doBackup();"><span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span> Backup</a>
<form id="import_form" enctype="multipart/form-data" action="<%= JSON.parse(routes).import %>" method="post" style="display:none">
<input type="file" id="import_file" name="import_file" onchange="$('#import_form').submit()"/>
</form>
<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-info" onclick="import_crontab()"><span class="glyphicon glyphicon-import" aria-hidden="true"></span> Import from crontab</a>-->
<a class="btn btn-success" onclick="setCrontab();"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save to crontab</a>
<br/>
<br/>
<table class="table"> <table class="table">
<tr> <tr>
<th>Id</th>
<th></th> <th></th>
<th>Job</th> <th>Job</th>
<th>Time</th> <th>Time</th>
<th>Last Modified</th> <th>Last Modified</th>
<th></th> <th></th>
</tr> </tr>
<% var index = 1 %>
<% JSON.parse(crontabs).forEach(function(crontab){ %> <% JSON.parse(crontabs).forEach(function(crontab){ %>
<!-- color based on crontab state --> <!-- color based on crontab state -->
<% if (!crontab.stopped) { %> <% if (!crontab.stopped) { %>
@ -35,6 +50,11 @@
<% } else { %> <% } else { %>
<tr style="background:#3A6DA6;color:#fff"> <tr style="background:#3A6DA6;color:#fff">
<% } %> <% } %>
<td>
<%= index %>.
<% index += 1 %>
<%= crontab._id %>
</td>
<td> <td>
<% if (crontab.name) { %> <% if (crontab.name) { %>
@ -42,7 +62,7 @@
<% } %> <% } %>
</td> </td>
<td><%= crontab.command %></td> <td><%= crontab.command %></td>
<td><%= crontab.schedule %></td> <td><span style="cursor:pointer" data-toggle="tooltip" data-placement="bottom" title="<%= crontab.next %>"><%= crontab.schedule %></span></td>
<td><%= crontab.timestamp %></td> <td><%= crontab.timestamp %></td>
<td> <td>
@ -62,11 +82,6 @@
</table> </table>
<a class="btn btn-primary" onclick="newJob();"><span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span> New</a>
<a class="btn btn-info" onclick="doBackup();"><span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span> Backup</a>
<a class="btn btn-info"><span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span> Export</a>
<a class="btn btn-info"><span class="glyphicon glyphicon-import" aria-hidden="true"></span> Import from crontab</a>
<a class="btn btn-success" onclick="setCrontab();"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save to crontab</a>
</div> </div>
<% include popup.ejs %> <% include popup.ejs %>
</body> </body>

View File

@ -14,7 +14,6 @@
<!-- Collect the nav links, forms, and other content for toggling --> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li><a href="#">Import</a></li>
<li class="dropdown"> <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Backups <span class="caret"></span></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Backups <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">

View File

@ -22,11 +22,13 @@
<h2><%= db %></h2> <h2><%= db %></h2>
<table class="table"> <table class="table">
<tr> <tr>
<th>Id</th>
<th></th> <th></th>
<th>Job</th> <th>Job</th>
<th>Time</th> <th>Time</th>
<th>Last Modified</th> <th>Last Modified</th>
</tr> </tr>
<% var index = 1 %>
<% JSON.parse(crontabs).forEach(function(crontab){ %> <% JSON.parse(crontabs).forEach(function(crontab){ %>
<!-- color based on crontab state --> <!-- color based on crontab state -->
<% if (!crontab.stopped) { %> <% if (!crontab.stopped) { %>
@ -35,6 +37,12 @@
<tr style="background:#3A6DA6;color:#fff"> <tr style="background:#3A6DA6;color:#fff">
<% } %> <% } %>
<td>
<%= index %>.
<% index += 1 %>
<%= crontab._id %>
</td>
<td> <td>
<% if (crontab.name) { %> <% if (crontab.name) { %>
<a class="btn" data-toggle="tooltip" data-placement="right" title="<%= crontab.name %>"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> </a> <a class="btn" data-toggle="tooltip" data-placement="right" title="<%= crontab.name %>"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span> </a>