diff --git a/index.js b/index.js index f198704..4388d6e 100644 --- a/index.js +++ b/index.js @@ -39,7 +39,7 @@ let upload = multer({ }) }); -app.post('/newTask', addRequestId, upload.array('images'), (req, res) => { +app.post('/task/new', addRequestId, upload.array('images'), (req, res) => { if (req.files.length === 0) res.json({error: "Need at least 1 file."}); else{ // Move to data @@ -81,11 +81,11 @@ let getTaskFromUuid = (req, res, next) => { }else res.json({error: `${req.params.uuid} not found`}); } -app.get('/taskInfo/:uuid', getTaskFromUuid, (req, res) => { +app.get('/task/:uuid/info', getTaskFromUuid, (req, res) => { res.json(req.task.getInfo()); }); -app.get('/taskOutput/:uuid', getTaskFromUuid, (req, res) => { - res.json(req.task.getOutput()); +app.get('/task/:uuid/output', getTaskFromUuid, (req, res) => { + res.json(req.task.getOutput(req.query.line)); }); let uuidCheck = (req, res, next) => { @@ -100,15 +100,15 @@ let successHandler = res => { }; }; -app.post('/cancelTask', uuidCheck, (req, res) => { +app.post('/task/cancel', uuidCheck, (req, res) => { taskManager.cancel(req.body.uuid, successHandler(res)); }); -app.post('/removeTask', uuidCheck, (req, res) => { +app.post('/task/remove', uuidCheck, (req, res) => { taskManager.remove(req.body.uuid, successHandler(res)); }); -app.post('/restartTask', uuidCheck, (req, res) => { +app.post('/task/restart', uuidCheck, (req, res) => { taskManager.restart(req.body.uuid, successHandler(res)); }); diff --git a/libs/Task.js b/libs/Task.js index 54fc76a..94fac91 100644 --- a/libs/Task.js +++ b/libs/Task.js @@ -33,7 +33,13 @@ module.exports = class Task{ // Get path where images are stored for this task // (relative to nodejs process CWD) getImagesFolderPath(){ - return `data/${this.uuid}/images`; + return `${this.getProjectFolderPath()}/images`; + } + + // Get path of project (where all images and assets folder are contained) + // (relative to nodejs process CWD) + getProjectFolderPath(){ + return `data/${this.uuid}`; } setStatus(code, extra){ @@ -68,7 +74,7 @@ module.exports = class Task{ if (this.status.code === statusCodes.QUEUED){ this.setStatus(statusCodes.RUNNING); this.runnerProcess = odmRunner.run({ - projectPath: `${__dirname}/../${this.getImagesFolderPath()}` + projectPath: `${__dirname}/../${this.getProjectFolderPath()}` }, (err, code, signal) => { if (err){ this.setStatus(statusCodes.FAILED, {errorMessage: `Could not start process (${err.message})`}); @@ -81,6 +87,8 @@ module.exports = class Task{ } done(); }, output => { + // Replace console colors + output = output.replace(/\x1b\[[0-9;]*m/g, ""); this.output.push(output); }); @@ -120,7 +128,6 @@ module.exports = class Task{ // Returns the output of the OpenDroneMap process // Optionally starting from a certain line number getOutput(startFromLine = 0){ - let lineNum = Math.min(this.output.length, startFromLine); - return this.output.slice(lineNum, this.output.length); + return this.output.slice(startFromLine, this.output.length); } }; \ No newline at end of file diff --git a/libs/TaskManager.js b/libs/TaskManager.js index 442c031..28e771f 100644 --- a/libs/TaskManager.js +++ b/libs/TaskManager.js @@ -3,7 +3,7 @@ let assert = require('assert'); let Task = require('./Task'); let statusCodes = require('./statusCodes'); -let PARALLEL_QUEUE_PROCESS_LIMIT = 1; +let PARALLEL_QUEUE_PROCESS_LIMIT = 2; module.exports = class TaskManager{ constructor(){ @@ -50,8 +50,6 @@ module.exports = class TaskManager{ this.runningQueue = this.runningQueue.filter(t => { return t !== task; }); - - console.log("New queue length: " + this.runningQueue.length); } addNew(task){ @@ -90,7 +88,10 @@ module.exports = class TaskManager{ restart(uuid, cb){ let task; if (task = this.find(uuid, cb)){ - task.restart(cb); + task.restart(err => { + this.processNextTask(); + cb(err); + }); } } diff --git a/public/css/main.css b/public/css/main.css index 26d651e..72dcc94 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -48,4 +48,11 @@ .task .actionButtons{ text-align: right; +} + +.task .consoleOutput{ + width: 100%; + height: 200px; + font-family: monospace; + font-size: 90%; } \ No newline at end of file diff --git a/public/index.html b/public/index.html index 3b83d46..42d10a7 100644 --- a/public/index.html +++ b/public/index.html @@ -64,6 +64,8 @@
Name:
Images:
Status:
+
Output: ViewHide
+ diff --git a/public/js/main.js b/public/js/main.js index 373d721..f7e8596 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -7,7 +7,7 @@ $(function(){ return new Task(uuid); })); } - TaskList.prototype.addNew = function(task) { + TaskList.prototype.add = function(task) { this.tasks.push(task); this.saveTaskListToLocalStorage(); }; @@ -30,6 +30,8 @@ $(function(){ this.uuid = uuid; this.loading = ko.observable(true); this.info = ko.observable({}); + this.viewingOutput = ko.observable(false); + this.resetOutput(); var statusCodes = { 10: { @@ -64,7 +66,6 @@ $(function(){ this.icon = ko.pureComputed(function(){ if (this.info().status && this.info().status.code){ if(statusCodes[this.info().status.code]){ - console.log(statusCodes[this.info().status.code].icon); return statusCodes[this.info().status.code].icon; }else return "glyphicon-question-sign"; }else return ""; @@ -73,14 +74,11 @@ $(function(){ return this.info().status && this.info().status.code === 50; }, this); - this.refreshInfo(); - this.refreshInterval = setInterval(function(){ - self.refreshInfo(); - }, 2000); + this.startRefreshingInfo(); } Task.prototype.refreshInfo = function(){ var self = this; - var url = "/taskInfo/" + this.uuid; + var url = "/task/" + this.uuid + "/info"; $.get(url) .done(self.info) .fail(function(){ @@ -88,9 +86,61 @@ $(function(){ }) .always(function(){ self.loading(false); }); }; + Task.prototype.consoleMouseOver = function(){ this.autoScrollOutput = false; } + Task.prototype.consoleMouseOut = function(){ this.autoScrollOutput = true; } + Task.prototype.resetOutput = function(){ + this.viewOutputLine = 0; + this.autoScrollOutput = true; + this.output = ko.observableArray(); + }; + Task.prototype.viewOutput = function(){ + var self = this; + + function fetchOutput(){ + var url = "/task/" + self.uuid + "/output"; + $.get(url, {line: self.viewOutputLine}) + .done(function(output){ + for (var i in output){ + self.output.push(output[i]); + } + if (output.length){ + self.viewOutputLine += output.length; + if (self.autoScrollOutput){ + var $console = $("#console_" + self.uuid); + $console.scrollTop($console[0].scrollHeight - $console.height()) + } + } + }) + .fail(function(){ + self.info({error: url + " is unreachable."}); + }); + } + this.fetchOutputInterval = setInterval(fetchOutput, 2000); + fetchOutput(); + + this.viewingOutput(true); + }; + Task.prototype.hideOutput = function(){ + if (this.fetchOutputInterval) clearInterval(this.fetchOutputInterval); + this.viewingOutput(false); + }; + Task.prototype.startRefreshingInfo = function() { + var self = this; + this.stopRefreshingInfo(); + this.refreshInfo(); + this.refreshInterval = setInterval(function(){ + self.refreshInfo(); + }, 2000); + }; + Task.prototype.stopRefreshingInfo = function() { + if (this.refreshInterval){ + clearInterval(this.refreshInterval); + this.refreshInterval = null; + } + }; Task.prototype.remove = function() { var self = this; - var url = "/removeTask"; + var url = "/task/remove"; $.post(url, { uuid: this.uuid @@ -102,13 +152,11 @@ $(function(){ self.info({error: json.error}); } - if (self.refreshInterval){ - clearInterval(self.refreshInterval); - self.refreshInterval = null; - } + self.stopRefreshingInfo(); }) .fail(function(){ self.info({error: url + " is unreachable."}); + self.stopRefreshingInfo(); }); }; @@ -116,34 +164,32 @@ $(function(){ return function(){ var self = this; - - // TODO: maybe there's a better way - // to handle refreshInfo here... - $.post(url, { uuid: this.uuid }) .done(function(json){ if (json.success){ - self.refreshInfo(); + self.startRefreshingInfo(); }else{ + self.stopRefreshingInfo(); self.info({error: json.error}); } }) .fail(function(){ self.info({error: url + " is unreachable."}); + self.stopRefreshingInfo(); }); } }; - Task.prototype.cancel = genApiCall("/cancelTask"); - Task.prototype.restart = genApiCall("/restartTask"); + Task.prototype.cancel = genApiCall("/task/cancel"); + Task.prototype.restart = genApiCall("/task/restart"); var taskList = new TaskList(); ko.applyBindings(taskList); // Handle uploads $("#images").fileinput({ - uploadUrl: '/newTask', + uploadUrl: '/task/new', showPreview: false, allowedFileExtensions: ['jpg', 'jpeg'], elErrorContainer: '#errorBlock', @@ -170,7 +216,7 @@ $(function(){ $("#images").fileinput('reset'); if (params.response.success && params.response.uuid){ - taskList.addNew(new Task(params.response.uuid)); + taskList.add(new Task(params.response.uuid)); } }) .on('filebatchuploadcomplete', function(){