import crontab db

v0.1.3
Suresh Alse 2015-06-14 02:08:20 +05:30
parent 41ca8a72c7
commit 00746b4367
11 changed files with 157 additions and 30 deletions

View File

@ -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.

43
app.js
View 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.listen(app.get('port'), function() {
console.log("Crontab UI is running at localhost:" + app.get('port'))
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'))
})

3
bin/crontab-ui.js Executable file
View File

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

View File

@ -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);
});
}

View File

@ -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",

View File

@ -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 = "";

View File

@ -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);
});
}

View File

@ -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
}

View File

@ -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>

View File

@ -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">

View File

@ -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>