import crontab db
parent
41ca8a72c7
commit
00746b4367
27
README.md
27
README.md
|
@ -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)
|
![flow](http://alseambusher.github.io/files/flow.gif)
|
||||||
|
|
||||||
1. Easy setup
|
1. Easy setup
|
||||||
2. Easy and 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. Backups
|
3. Backup your crontabs.
|
||||||
4. Download and schedule scripts which are online
|
4. Export crontab and deploy on other machines without much hassle.
|
||||||
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
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
39
app.js
39
app.js
|
@ -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'))
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var crontab = require('../app.js');
|
51
crontab.js
51
crontab.js
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
12
package.json
12
package.json
|
@ -1,19 +1,25 @@
|
||||||
{
|
{
|
||||||
"name": "crontab-ui",
|
"name": "crontab-ui",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"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": {
|
||||||
"start": "node index.js"
|
"start": "node app.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"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"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"crontab-ui": "bin/crontab-ui.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -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 = "";
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue