kopia lustrzana https://github.com/OpenDroneMap/NodeODM
Cancel task, restart task, minor refactoring
rodzic
514bc654c0
commit
c0e239d709
42
index.js
42
index.js
|
@ -6,10 +6,13 @@ let app = express();
|
||||||
|
|
||||||
let addRequestId = require('./libs/express-request-id')();
|
let addRequestId = require('./libs/express-request-id')();
|
||||||
let multer = require('multer');
|
let multer = require('multer');
|
||||||
|
let bodyParser = require('body-parser');
|
||||||
|
|
||||||
let taskManager = new (require('./libs/taskManager'))();
|
let taskManager = new (require('./libs/taskManager'))();
|
||||||
let Task = require('./libs/Task');
|
let Task = require('./libs/Task');
|
||||||
|
|
||||||
|
app.use(bodyParser.urlencoded({extended: true}));
|
||||||
|
app.use(bodyParser.json());
|
||||||
app.use(express.static('public'));
|
app.use(express.static('public'));
|
||||||
|
|
||||||
let upload = multer({
|
let upload = multer({
|
||||||
|
@ -32,16 +35,21 @@ let upload = multer({
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/newTask', addRequestId, upload.array('images'), (req, res, next) => {
|
app.post('/newTask', addRequestId, upload.array('images'), (req, res) => {
|
||||||
if (req.files.length === 0) res.json({error: "Need at least 1 file."});
|
if (req.files.length === 0) res.json({error: "Need at least 1 file."});
|
||||||
else{
|
else{
|
||||||
console.log(`Received ${req.files.length} files`);
|
console.log(`Received ${req.files.length} files`);
|
||||||
|
|
||||||
// Move to data
|
// Move to data
|
||||||
fs.rename(`tmp/${req.id}`, `data/${req.id}`, (err) => {
|
fs.rename(`tmp/${req.id}`, `data/${req.id}`, err => {
|
||||||
if (!err){
|
if (!err){
|
||||||
taskManager.addNew(new Task(req.id, req.body.name));
|
new Task(req.id, req.body.name, (err, task) => {
|
||||||
res.json({uuid: req.id, success: true});
|
if (err) res.json({error: err.message});
|
||||||
|
else{
|
||||||
|
taskManager.addNew(task);
|
||||||
|
res.json({uuid: req.id, success: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
}else{
|
}else{
|
||||||
res.json({error: "Could not move images folder."});
|
res.json({error: "Could not move images folder."});
|
||||||
}
|
}
|
||||||
|
@ -49,13 +57,37 @@ app.post('/newTask', addRequestId, upload.array('images'), (req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/taskInfo/:uuid', (req, res, next) => {
|
app.get('/taskInfo/:uuid', (req, res) => {
|
||||||
let task = taskManager.find(req.params.uuid);
|
let task = taskManager.find(req.params.uuid);
|
||||||
if (task){
|
if (task){
|
||||||
res.json(task.getInfo());
|
res.json(task.getInfo());
|
||||||
}else res.json({error: `${req.params.uuid} not found`});
|
}else res.json({error: `${req.params.uuid} not found`});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let uuidCheck = (req, res, next) => {
|
||||||
|
if (!req.body.uuid) res.json({error: "uuid param missing."});
|
||||||
|
else next();
|
||||||
|
};
|
||||||
|
|
||||||
|
let successHandler = res => {
|
||||||
|
return err => {
|
||||||
|
if (!err) res.json({success: true});
|
||||||
|
else res.json({error: err.message});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
app.post('/cancelTask', uuidCheck, (req, res) => {
|
||||||
|
taskManager.cancel(req.body.uuid, successHandler(res));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/removeTask', uuidCheck, (req, res) => {
|
||||||
|
taskManager.remove(req.body.uuid, successHandler(res));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/restartTask', uuidCheck, (req, res) => {
|
||||||
|
taskManager.restart(req.body.uuid, successHandler(res));
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(3000, () => {
|
app.listen(3000, () => {
|
||||||
console.log('Example app listening on port 3000!');
|
console.log('Example app listening on port 3000!');
|
||||||
});
|
});
|
44
libs/Task.js
44
libs/Task.js
|
@ -1,16 +1,19 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
let assert = require('assert');
|
let assert = require('assert');
|
||||||
|
let fs = require('fs');
|
||||||
|
|
||||||
let statusCodes = {
|
let statusCodes = {
|
||||||
QUEUED: 10,
|
QUEUED: 10,
|
||||||
RUNNING: 20,
|
RUNNING: 20,
|
||||||
FAILED: 30,
|
FAILED: 30,
|
||||||
COMPLETED: 40
|
COMPLETED: 40,
|
||||||
|
CANCELED: 50
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = class Task{
|
module.exports = class Task{
|
||||||
constructor(uuid, name){
|
constructor(uuid, name, readyCb){
|
||||||
assert(uuid !== undefined, "uuid must be set");
|
assert(uuid !== undefined, "uuid must be set");
|
||||||
|
assert(readyCb !== undefined, "ready must be set");
|
||||||
|
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.name = name != "" ? name : "Task of " + (new Date()).toISOString();
|
this.name = name != "" ? name : "Task of " + (new Date()).toISOString();
|
||||||
|
@ -19,6 +22,40 @@ module.exports = class Task{
|
||||||
code: statusCodes.QUEUED
|
code: statusCodes.QUEUED
|
||||||
};
|
};
|
||||||
this.options = {};
|
this.options = {};
|
||||||
|
|
||||||
|
// Read images info
|
||||||
|
fs.readdir(`data/${this.uuid}`, (err, files) => {
|
||||||
|
if (err) readyCb(err);
|
||||||
|
else{
|
||||||
|
this.images = files;
|
||||||
|
readyCb(null, this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(cb){
|
||||||
|
if (this.status.code !== statusCodes.CANCELED){
|
||||||
|
this.status.code = statusCodes.CANCELED;
|
||||||
|
|
||||||
|
console.log("Requested to cancel " + this.name);
|
||||||
|
// TODO
|
||||||
|
cb(null);
|
||||||
|
}else{
|
||||||
|
cb(new Error("Task already cancelled"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restart(cb){
|
||||||
|
if (this.status.code === statusCodes.CANCELED){
|
||||||
|
this.status.code = statusCodes.QUEUED;
|
||||||
|
|
||||||
|
console.log("Requested to restart " + this.name);
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
cb(null);
|
||||||
|
}else{
|
||||||
|
cb(new Error("Task cannot be restarted"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getInfo(){
|
getInfo(){
|
||||||
|
@ -27,7 +64,8 @@ module.exports = class Task{
|
||||||
name: this.name,
|
name: this.name,
|
||||||
dateCreated: this.dateCreated,
|
dateCreated: this.dateCreated,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
options: this.options
|
options: this.options,
|
||||||
|
imagesCount: this.images.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -12,7 +12,33 @@ module.exports = class TaskManager{
|
||||||
this.tasks[task.uuid] = task;
|
this.tasks[task.uuid] = task;
|
||||||
}
|
}
|
||||||
|
|
||||||
find(uuid){
|
cancel(uuid, cb){
|
||||||
return this.tasks[uuid];
|
let task;
|
||||||
|
if (task = this.find(uuid, cb)){
|
||||||
|
task.cancel(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(uuid, cb){
|
||||||
|
this.cancel(uuid, err => {
|
||||||
|
if (!err){
|
||||||
|
delete(this.tasks[uuid]);
|
||||||
|
// TODO: other cleanup
|
||||||
|
cb(null);
|
||||||
|
}else cb(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
restart(uuid, cb){
|
||||||
|
let task;
|
||||||
|
if (task = this.find(uuid, cb)){
|
||||||
|
task.restart(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
find(uuid, errCb){
|
||||||
|
let task = this.tasks[uuid];
|
||||||
|
if (!task && errCb) errCb(new Error(`${uuid} not found`));
|
||||||
|
return task;
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -59,15 +59,21 @@
|
||||||
<p data-bind="visible: tasks().length === 0">No running tasks.</p>
|
<p data-bind="visible: tasks().length === 0">No running tasks.</p>
|
||||||
<div data-bind="foreach: tasks">
|
<div data-bind="foreach: tasks">
|
||||||
<div class="task">
|
<div class="task">
|
||||||
<p data-bind="visible: loading()">Retrieving <span data-bind="text: uuid"></span> ... <span class="spinning glyphicon glyphicon glyphicon glyphicon-refresh"></span></p>
|
<p data-bind="visible: loading()">Retrieving <span data-bind="text: uuid"></span> ... <span class="glyphicon glyphicon-refresh spinning"></span></p>
|
||||||
<div data-bind="visible: !loading() && !info().error">
|
<div data-bind="visible: !loading() && !info().error">
|
||||||
<div class="taskItem"><strong>Name:</strong> <span data-bind="text: info().name"></span></div>
|
<div class="taskItem"><strong>Name:</strong> <span data-bind="text: info().name"></span></div>
|
||||||
|
<div class="taskItem"><strong>Images:</strong> <span data-bind="text: info().imagesCount"></span></div>
|
||||||
<div class="taskItem"><strong>Status:</strong> <span data-bind="text: statusDescr()"></span></div>
|
<div class="taskItem"><strong>Status:</strong> <span data-bind="text: statusDescr()"></span></div>
|
||||||
<span class="statusIcon spinning glyphicon glyphicon glyphicon-cog"></span>
|
|
||||||
|
<span data-bind="css: 'statusIcon glyphicon ' + icon()"></span>
|
||||||
|
|
||||||
<div class="actionButtons">
|
<div class="actionButtons">
|
||||||
<button type="button" class="btn btn-primary btn-sm">
|
<button data-bind="click: cancel, visible: !canceled()" type="button" class="btn btn-primary btn-sm" >
|
||||||
<span class="glyphicon glyphicon glyphicon-remove-circle"></span> Cancel
|
<span class="glyphicon glyphicon-remove-circle"></span> Cancel
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button data-bind="click: restart, visible: canceled()" type="button" class="btn btn-primary btn-sm" >
|
||||||
|
<span class="glyphicon glyphicon-play"></span> Restart
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -77,7 +83,7 @@
|
||||||
<div class="alert alert-warning" role="alert" data-bind="text: info().error"></div>
|
<div class="alert alert-warning" role="alert" data-bind="text: info().error"></div>
|
||||||
<div class="actionButtons">
|
<div class="actionButtons">
|
||||||
<button data-bind="click: remove" type="button" class="btn btn-primary btn-sm">
|
<button data-bind="click: remove" type="button" class="btn btn-primary btn-sm">
|
||||||
<span class="glyphicon glyphicon glyphicon-remove-circle"></span> Remove Task
|
<span class="glyphicon glyphicon-remove-circle"></span> Remove
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,71 +28,141 @@ $(function(){
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.loading = ko.observable(true);
|
this.loading = ko.observable(true);
|
||||||
this.info = ko.observable({});
|
this.info = ko.observable({});
|
||||||
|
|
||||||
|
var statusCodes = {
|
||||||
|
10: {
|
||||||
|
descr: "Queued",
|
||||||
|
icon: "glyphicon-hourglass"
|
||||||
|
},
|
||||||
|
20: {
|
||||||
|
descr: "Running",
|
||||||
|
icon: "glyphicon-refresh spinning"
|
||||||
|
},
|
||||||
|
30: {
|
||||||
|
descr: "Failed",
|
||||||
|
icon: "glyphicon-remove-circle"
|
||||||
|
},
|
||||||
|
40: {
|
||||||
|
descr: "Completed",
|
||||||
|
icon: "glyphicon-ok-circle"
|
||||||
|
},
|
||||||
|
50: {
|
||||||
|
descr: "Canceled",
|
||||||
|
icon: "glyphicon-ban-circle"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.statusDescr = ko.pureComputed(function(){
|
this.statusDescr = ko.pureComputed(function(){
|
||||||
if (this.info().status && this.info().status.code){
|
if (this.info().status && this.info().status.code){
|
||||||
switch(this.info().status.code){
|
if(statusCodes[this.info().status.code]){
|
||||||
case 10: return "Queued";
|
return statusCodes[this.info().status.code].descr;
|
||||||
case 20: return "Running";
|
}else return "Unknown (Status Code: " + this.info().status.code + ")";
|
||||||
case 30: return "Failed";
|
|
||||||
case 40: return "Completed";
|
|
||||||
default: return "Unknown (Status Code: " + this.info().status.code + ")";
|
|
||||||
}
|
|
||||||
}else return "-";
|
}else return "-";
|
||||||
}, this);
|
}, this);
|
||||||
|
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 "";
|
||||||
|
}, this);
|
||||||
|
this.canceled = ko.pureComputed(function(){
|
||||||
|
return this.info().status && this.info().status.code === 50;
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.refreshInfo();
|
||||||
|
}
|
||||||
|
Task.prototype.refreshInfo = function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
var url = "/taskInfo/" + uuid;
|
var url = "/taskInfo/" + this.uuid;
|
||||||
$.get(url)
|
$.get(url)
|
||||||
.done(self.info)
|
.done(self.info)
|
||||||
.fail(function(){
|
.fail(function(){
|
||||||
self.info({error: url + " is unreachable."});
|
self.info({error: url + " is unreachable."});
|
||||||
})
|
})
|
||||||
.always(function(){ self.loading(false); });
|
.always(function(){ self.loading(false); });
|
||||||
}
|
|
||||||
Task.prototype.remove = function() {
|
|
||||||
taskList.remove(this);
|
|
||||||
};
|
};
|
||||||
|
Task.prototype.remove = function() {
|
||||||
|
var self = this;
|
||||||
|
var url = "/removeTask";
|
||||||
|
|
||||||
|
$.post(url, {
|
||||||
|
uuid: this.uuid
|
||||||
|
})
|
||||||
|
.done(function(json){
|
||||||
|
if (json.success || self.info().error){
|
||||||
|
taskList.remove(self);
|
||||||
|
}else{
|
||||||
|
self.info({error: json.error});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail(function(){
|
||||||
|
self.info({error: url + " is unreachable."});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function genApiCall(url){
|
||||||
|
return function(){
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
$.post(url, {
|
||||||
|
uuid: this.uuid
|
||||||
|
})
|
||||||
|
.done(function(json){
|
||||||
|
if (json.success){
|
||||||
|
self.refreshInfo();
|
||||||
|
}else{
|
||||||
|
self.info({error: json.error});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail(function(){
|
||||||
|
self.info({error: url + " is unreachable."});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Task.prototype.cancel = genApiCall("/cancelTask");
|
||||||
|
Task.prototype.restart = genApiCall("/restartTask");
|
||||||
|
|
||||||
var taskList = new TaskList();
|
var taskList = new TaskList();
|
||||||
ko.applyBindings(taskList);
|
ko.applyBindings(taskList);
|
||||||
|
|
||||||
|
|
||||||
// Handle uploads
|
// Handle uploads
|
||||||
$("#images").fileinput({
|
$("#images").fileinput({
|
||||||
uploadUrl: '/newTask',
|
uploadUrl: '/newTask',
|
||||||
showPreview: false,
|
showPreview: false,
|
||||||
allowedFileExtensions: ['jpg', 'jpeg'],
|
allowedFileExtensions: ['jpg', 'jpeg'],
|
||||||
elErrorContainer: '#errorBlock',
|
elErrorContainer: '#errorBlock',
|
||||||
showUpload: false,
|
showUpload: false,
|
||||||
uploadAsync: false,
|
uploadAsync: false,
|
||||||
uploadExtraData: function(){
|
uploadExtraData: function(){
|
||||||
return {
|
return {
|
||||||
name: $("#taskName").val()
|
name: $("#taskName").val()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#btnUpload").click(function(){
|
$("#btnUpload").click(function(){
|
||||||
var btnUploadLabel = $("#btnUpload").val();
|
$("#btnUpload").attr('disabled', true)
|
||||||
$("#btnUpload").attr('disabled', true)
|
.val("Uploading...");
|
||||||
.val("Uploading...");
|
|
||||||
|
|
||||||
$("#images")
|
// Start upload
|
||||||
.fileinput('upload')
|
$("#images").fileinput('upload');
|
||||||
.on('filebatchuploadsuccess', function(e, params){
|
});
|
||||||
$("#images").fileinput('reset');
|
|
||||||
|
|
||||||
// TODO: this is called multiple times
|
var btnUploadLabel = $("#btnUpload").val();
|
||||||
// consider switching file upload plugin.
|
$("#images")
|
||||||
if (params.response.success && params.response.uuid){
|
.on('filebatchuploadsuccess', function(e, params){
|
||||||
taskList.addNew(new Task(params.response.uuid));
|
$("#images").fileinput('reset');
|
||||||
}
|
|
||||||
})
|
if (params.response.success && params.response.uuid){
|
||||||
.on('filebatchuploadcomplete', function(){
|
taskList.addNew(new Task(params.response.uuid));
|
||||||
$("#btnUpload").removeAttr('disabled')
|
}
|
||||||
.val(btnUploadLabel);
|
})
|
||||||
})
|
.on('filebatchuploadcomplete', function(){
|
||||||
.on('filebatchuploaderror', function(e, data, msg){
|
$("#btnUpload").removeAttr('disabled')
|
||||||
});
|
.val(btnUploadLabel);
|
||||||
});
|
})
|
||||||
|
.on('filebatchuploaderror', function(e, data, msg){
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Ładowanie…
Reference in New Issue