import crontab db

v0.1.3
Suresh Alse 10 years ago
parent 41ca8a72c7
commit 00746b4367

@ -6,17 +6,22 @@ Editing the plain text crontab is error prone for managing jobs, e.g., adding jo
![flow](http://alseambusher.github.io/files/flow.gif)
1. Easy setup
2. Easy and safe adding, deleting or pausing jobs. Easy to maintain hundreds of jobs.
3. Backups
4. Download and schedule scripts which are online
5. Manage crontabs on multiple machines easily. No SSH, No copy-pasting.
TODO
====
1. Run jobs as different user
2. Online backup
3. Profiling jobs
4. Logs
2. Safe adding, deleting or pausing jobs. Easy to maintain hundreds of jobs.
3. Backup your crontabs.
4. Export crontab and deploy on other machines without much hassle.
Read [this](http://lifepluslinux.blogspot.in/2015/06/crontab-ui-easy-and-safe-way-to-manage.html) to see more details.
##Setup
npm install crontab-ui
crontab-ui
###TODO
1. Run jobs as different user in one place.
2. Profiling jobs.
3. Logs.
4. Importing from existing crontab file.

@ -3,6 +3,12 @@ var app = express();
var crontab = require("./crontab");
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
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
extended: true
}));
app.use(busboy()) // to support file uploads
// include all folders
app.use(express.static(__dirname + '/public'));
@ -92,6 +99,38 @@ app.get(routes.restore_backup, function(req, res) {
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() {
console.log("Crontab UI is running at localhost:" + app.get('port'))
console.log("Crontab UI is running at localhost:" + app.get('port'))
})

@ -0,0 +1,3 @@
#!/usr/bin/env node
var crontab = require('../app.js');

@ -5,19 +5,23 @@ db.loadDatabase(function (err) {
});
var exec = require('child_process').exec;
var fs = require('fs');
var cron_parser = require("cron-parser")
crontab = function(name, command, schedule, stopped){
var data = {};
data.name = name;
data.command = command;
data.schedule = schedule;
if(stopped != null) data.stopped = stopped;
if(stopped != null) {
data.stopped = stopped;
}
data.timestamp = (new Date()).toString();
return data;
}
exports.create_new = function(name, command, schedule){
var tab = crontab(name, command, schedule, false);
tab.created = new Date().valueOf();
db.insert(tab);
}
@ -33,7 +37,13 @@ exports.remove = function(_id){
db.remove({_id: _id}, {});
}
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);
});
}
@ -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;
}
@ -74,6 +99,24 @@ exports.restore = function(db_name){
db.loadDatabase(); // reload the database
}
exports.import = function(){
//TODO
exports.reload_db= function(){
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);
});
}

@ -1,19 +1,25 @@
{
"name": "crontab-ui",
"version": "0.1.0",
"version": "0.1.1",
"description": "Easy and safe way to manage your crontab file",
"main": "index.js",
"scripts": {
"start": "node index.js"
"start": "node app.js"
},
"dependencies": {
"express": "latest",
"ejs": "latest",
"nedb": "latest",
"body-parser": "latest"
"body-parser": "latest",
"mime": "latest",
"cron-parser": "latest",
"connect-busboy": "latest"
},
"engines": {
"node": "latest"
},
"bin": {
"crontab-ui": "bin/crontab-ui.js"
},
"repository": {
"type": "git",

@ -10,8 +10,8 @@ function messageBox(body, title, ok_text, close_text, callback){
$("#modal-body").html(body);
$("#modal-title").html(title);
if (ok_text) $("#modal-button").html(ok_text);
$("#modal-button").show();
if(close_text) $("#modal-close-button").html(close_text);
$("#modal-button").unbind("click"); // remove existing events attached to this
$("#modal-button").click(callback);
$("#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
var schedule = "";

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

@ -8,5 +8,8 @@ exports.routes = {
"backup": "/backup",
"restore": "/restore",
"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
}

@ -20,14 +20,29 @@
<% include navbar %>
<div class="container">
<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">
<tr>
<th>Id</th>
<th></th>
<th>Job</th>
<th>Time</th>
<th>Last Modified</th>
<th></th>
</tr>
<% var index = 1 %>
<% JSON.parse(crontabs).forEach(function(crontab){ %>
<!-- color based on crontab state -->
<% if (!crontab.stopped) { %>
@ -35,6 +50,11 @@
<% } else { %>
<tr style="background:#3A6DA6;color:#fff">
<% } %>
<td>
<%= index %>.
<% index += 1 %>
<%= crontab._id %>
</td>
<td>
<% if (crontab.name) { %>
@ -42,7 +62,7 @@
<% } %>
</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>
@ -62,11 +82,6 @@
</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>
<% include popup.ejs %>
</body>

@ -14,7 +14,6 @@
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#">Import</a></li>
<li class="dropdown">
<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">

@ -22,11 +22,13 @@
<h2><%= db %></h2>
<table class="table">
<tr>
<th>Id</th>
<th></th>
<th>Job</th>
<th>Time</th>
<th>Last Modified</th>
</tr>
<% var index = 1 %>
<% JSON.parse(crontabs).forEach(function(crontab){ %>
<!-- color based on crontab state -->
<% if (!crontab.stopped) { %>
@ -35,6 +37,12 @@
<tr style="background:#3A6DA6;color:#fff">
<% } %>
<td>
<%= index %>.
<% index += 1 %>
<%= crontab._id %>
</td>
<td>
<% 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>

Loading…
Cancel
Save