diff --git a/app.js b/app.js index c496138..e6c6a7d 100755 --- a/app.js +++ b/app.js @@ -3,6 +3,7 @@ var app = express(); var crontab = require("./crontab"); var restore = require("./restore"); +var moment = require('moment'); var path = require('path'); var mime = require('mime'); var fs = require('fs'); @@ -30,15 +31,24 @@ app.set('views', __dirname + '/views'); //set port app.set('port', (process.env.PORT || 8000)); +app.set('listen', (process.env.LISTEN || '0.0.0.0')); app.get(routes.root, function(req, res) { // get all the crontabs crontab.crontabs( function(docs){ - res.render('index', { - routes : JSON.stringify(routes), - crontabs : JSON.stringify(docs), - backups : crontab.get_backup_names(), - env : crontab.get_env() + crontab.templates(function(templates) { + res.render('index', { + routes : JSON.stringify(routes), + crontabs : JSON.stringify(docs), + templates: templates, + templatesById: templates.reduce(function(memo, t) { + memo[t._id] = t; + return memo; + }, {}), + backups : crontab.get_backup_names(), + env : crontab.get_env(), + moment: moment, + }); }); }); }) @@ -46,15 +56,33 @@ app.get(routes.root, function(req, res) { app.post(routes.save, function(req, res) { // new job if(req.body._id == -1){ - crontab.create_new(req.body.name, req.body.command, req.body.schedule, req.body.logging); + crontab.create_new(req.body.name, req.body.command, req.body.command_template, req.body.vars, req.body.schedule, req.body.logging); } // edit job else{ + crontab.update(req.body); } res.end(); }) +app.post(routes.save_template, function(req, res) { + // new job + if(req.body._id == -1){ + crontab.create_new_template(req.body.name, req.body.command, req.body.schedule); + } + // edit job + else{ + crontab.update_template(req.body); + } + res.end(); +}); + +app.post(routes.remove_template, function(req, res) { + crontab.remove_template(req.body._id); + res.end(); +}) + app.post(routes.stop, function(req, res) { crontab.status(req.body._id, true); res.end(); @@ -81,11 +109,18 @@ app.get(routes.backup, function(req, res) { app.get(routes.restore, function(req, res) { // get all the crontabs - restore.crontabs(req.query.db, function(docs){ + restore.loadBackupFile(req.query.db, function(docs, templates) { res.render('restore', { routes : JSON.stringify(routes), crontabs : JSON.stringify(docs), + templates: templates, + templatesById: templates.reduce(function(memo, t) { + memo[t._id] = t; + return memo; + }, {}), backups : crontab.get_backup_names(), + env : crontab.get_env(), + moment: moment, db: req.query.db }); }); @@ -102,31 +137,30 @@ app.get(routes.restore_backup, function(req, res) { }) app.get(routes.export, function(req, res) { - var file = __dirname + '/crontabs/crontab.db'; + var backupData = crontab.backup_data(); - var filename = path.basename(file); - var mimetype = mime.lookup(file); + res.setHeader('Content-disposition', 'attachment; filename=crontab_ui_backup.json'); + res.setHeader('Content-type', 'application/json'); - res.setHeader('Content-disposition', 'attachment; filename=' + filename); - res.setHeader('Content-type', mimetype); - - var filestream = fs.createReadStream(file); - filestream.pipe(res); -}) + res.end(JSON.stringify(backupData)); +}); 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 () { + + file.on('data', function(data) { + crontab.restore_data(JSON.parse(data.toString('utf8'))); crontab.reload_db(); - res.redirect(routes.root); - }); - }); -}) + }); + }); + + req.busboy.on('finish', function() { + res.redirect(routes.root); + }) +}); app.get(routes.import_crontab, function(req, res) { crontab.import_crontab() @@ -142,6 +176,8 @@ app.get(routes.logger, function(req, res) { res.end("No errors logged yet"); }) -app.listen(app.get('port'), function() { - console.log("Crontab UI is running at localhost:" + app.get('port')) +// app.use('/scripts/moment.js', express.static(__dirname + '/node_modules/moment/min/moment.min.js')); + +app.listen(app.get('port'), app.get('listen'), function() { + console.log("Crontab UI is running at " + app.get('listen') + ":" + app.get('port')) }) diff --git a/crontab.js b/crontab.js index 5401850..6edda46 100644 --- a/crontab.js +++ b/crontab.js @@ -1,21 +1,29 @@ //load database + +exports.crontab_db_file = __dirname + '/crontabs/crontab.db'; +exports.templates_db_file = __dirname + '/crontabs/templates.db'; + var Datastore = require('nedb'); -var db = new Datastore({ filename: __dirname + '/crontabs/crontab.db' }); -db.loadDatabase(function (err) { -}); +var db = { + crontabs: new Datastore({ filename: exports.crontab_db_file, autoload: true }), + templates: new Datastore({ filename: exports.templates_db_file, autoload: true }), +}; var exec = require('child_process').exec; var fs = require('fs'); var cron_parser = require("cron-parser") var os = require("os") +var _ = require('underscore'); exports.log_folder = __dirname + '/crontabs/logs'; exports.env_file = __dirname + '/crontabs/env.db'; -crontab = function(name, command, schedule, stopped, logging){ +crontab = function(name, command, command_template, vars, schedule, stopped, logging){ var data = {}; data.name = name; data.command = command; + data.command_template = command_template; data.schedule = schedule; + data.vars = vars || {}; if(stopped != null) { data.stopped = stopped; } @@ -24,34 +32,88 @@ crontab = function(name, command, schedule, stopped, logging){ return data; } -exports.create_new = function(name, command, schedule, logging){ - var tab = crontab(name, command, schedule, false, logging); +crontab_template = function(name, command, schedule){ + var data = {}; + data.name = name; + data.command = command; + data.schedule = schedule; + data.timestamp = (new Date()).toString(); + return data; +} + +exports.create_new = function(name, command, command_template, vars, schedule, logging){ + var tab = crontab(name, command, command_template, vars, schedule, false, logging); tab.created = new Date().valueOf(); - db.insert(tab); + db.crontabs.insert(tab); } exports.update = function(data){ - db.update({_id: data._id}, crontab(data.name, data.command, data.schedule, null, data.logging)); + db.crontabs.update({_id: data._id}, crontab(data.name, data.command, data.command_template, data.vars, data.schedule, null, data.logging)); } exports.status = function(_id, stopped){ - db.update({_id: _id},{$set: {stopped: stopped}}); + db.crontabs.update({_id: _id},{$set: {stopped: stopped}}); } exports.remove = function(_id){ - db.remove({_id: _id}, {}); -} -exports.crontabs = function(callback){ - db.find({}).sort({ created: -1 }).exec(function(err, docs){ - for(var i=0; i').val(vars[varName]); + + $jobVariables.append(''); + $jobVariables.append($input); + $jobVariables.append('
'); + + $input.change(function() { + current_job.vars = current_job.vars || {}; + current_job.vars[varName] = $(this).val(); + }) + }) + + } else { + job_command = $('#job-command').val(); + } + + job_string(); +} + +function findCommandVariables (command) { + return command.match(/{[a-zA-Z]+}/g).map(function(cmd) { + return cmd.substring(1, cmd.length - 1); + }); +}; + +function renderTemplateCommand(templateId) { + var template = findTemplate(templateId); + + if(template) { + return template.command; + } + + return ''; +} + function newJob(){ + current_job = {}; schedule = "" job_command = "" $("#job-minute").val("*"); @@ -101,14 +168,17 @@ function newJob(){ $("#job").modal("show"); $("#job-name").val(""); $("#job-command").val(""); + $("#job-command-template").val(""); job_string(); $("#job-save").unbind("click"); // remove existing events attached to this $("#job-save").click(function(){ // TODO good old boring validations - $.post(routes.save, {name: $("#job-name").val(), command: job_command , schedule: schedule, _id: -1, logging: $("#job-logging").prop("checked")}, function(){ + $.post(routes.save, {name: $("#job-name").val(), command: job_command , schedule: schedule, _id: -1, logging: $("#job-logging").prop("checked"), command_template: $('#job-command-template').val(), vars: current_job.vars }, function(){ location.reload(); }) }); + + setTemplateVariables(null); } function doBackup(){ @@ -145,6 +215,7 @@ function import_db(){ // script corresponding to job popup management var schedule = ""; var job_command = ""; +var current_job = null; function job_string(){ $("#job-string").val(schedule + " " + job_command); return schedule + " " + job_command; @@ -155,3 +226,83 @@ function set_schedule(){ job_string(); } // popup management ends + + +/**************** Template Action s***************/ + +function newTemplate(){ + schedule = "" + template_command = "" + $("#template-minute").val("*"); + $("#template-hour").val("*"); + $("#template-day").val("*"); + $("#template-month").val("*"); + $("#template-week").val("*"); + + $("#template-popup").modal("show"); + $("#template-name").val(""); + $("#template-command").val(""); + template_string(); + $("#template-save").unbind("click"); // remove existing events attached to this + $("#template-save").click(function(){ + // TODO good old boring validations + $.post(routes.save_template, {name: $("#template-name").val(), command: template_command , schedule: schedule, _id: -1}, function(){ + location.reload(); + }) + }); + + $('#template-remove').unbind('click').hide(); +} + +function editTemplate(_id){ + var template = findTemplate(_id); + if(template){ + $("#template-popup").modal("show"); + $("#template-name").val(template.name); + $("#template-command").val(template.command); + + // if macro not used + if(template.schedule && template.schedule.indexOf("@") != 0){ + var components = template.schedule.split(" "); + $("#template-minute").val(components[0]); + $("#template-hour").val(components[1]); + $("#template-day").val(components[2]); + $("#template-month").val(components[3]); + $("#template-week").val(components[4]); + } + schedule = template.schedule; + template_command = template.command; + + template_string(); + } + + $("#template-save").unbind("click"); // remove existing events attached to this + $("#template-save").click(function(){ + // TODO good old boring validations + $.post(routes.save_template, {name: $("#template-name").val(), command: template_command , schedule: schedule, _id: _id}, function(){ + location.reload(); + }) + }); + + $('#template-remove') + .show() + .unbind('click') + .click(function() { + if(window.confirm('Are you sure you want to delete this template?')) { + $.post(routes.remove_template, {_id:_id}, function() { + location.reload(); + }); + } + }); + +} + +function set_template_schedule(){ + schedule = $("#template-minute").val() + " " +$("#template-hour").val() + " " +$("#template-day").val() + " " +$("#template-month").val() + " " +$("#template-week").val(); + template_string(); +} + +function template_string(){ + $("#template-string").val(schedule + " " + template_command); + return schedule + " " + template_command; +} diff --git a/restore.js b/restore.js index 637fe41..91348cd 100644 --- a/restore.js +++ b/restore.js @@ -3,15 +3,34 @@ var Datastore = require('nedb'); var exec = require('child_process').exec; var fs = require('fs'); +var tmp = require('tmp'); -exports.crontabs = function(db_name, callback){ - var db = new Datastore({ filename: __dirname + '/crontabs/' + db_name }); - db.loadDatabase(function (err) { - }); - db.find({}).sort({ created: -1 }).exec(function(err, docs){ - callback(docs); - }); -} +exports.loadBackupFile = function(db_name, callback) { + + console.log(__dirname + '/crontabs/' + db_name); + + var backupFileData = fs.readFileSync(__dirname + '/crontabs/' + db_name).toString('utf8'); + console.log(backupFileData); + var data = JSON.parse(backupFileData); + + + var crontabFile = tmp.fileSync(); + var templateFile = tmp.fileSync(); + + fs.writeFileSync(crontabFile.name, data.crontabs); + fs.writeFileSync(templateFile.name, data.templates); + + var crontabDB = new Datastore({ filename: crontabFile.name , autoload: true }); + var templateDB = new Datastore({ filename: templateFile.name , autoload: true }); + + crontabDB.find({}).sort({ created: -1 }).exec(function(err, docs){ + templateDB.find({}).sort({ name: 1 }).exec(function(err, templates) { + + callback(docs, templates); + }) + }); + +}; exports.delete = function(db_name){ fs.unlink(__dirname + '/crontabs/' + db_name); diff --git a/routes.js b/routes.js index 15a629c..3dd9a56 100644 --- a/routes.js +++ b/routes.js @@ -13,4 +13,6 @@ exports.routes = { "import": "/import", // this is import from database "import_crontab": "/import_crontab", // this is from existing crontab "logger": "/logger", + "save_template" : "/save_template", + "remove_template" : "/remove_template", } diff --git a/views/index.ejs b/views/index.ejs index 6073ac7..cdac035 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -3,8 +3,11 @@ Crontab UI - + + + + diff --git a/views/navbar.ejs b/views/navbar.ejs index e653059..7818824 100644 --- a/views/navbar.ejs +++ b/views/navbar.ejs @@ -14,16 +14,30 @@ + + +