kopia lustrzana https://github.com/OpenDroneMap/NodeODM
rodzic
9725cba1a2
commit
b9ecdde14a
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
tests
|
||||
tmp
|
|
@ -39,3 +39,9 @@ jspm_packages
|
|||
|
||||
# Elastic Beanstalk
|
||||
.elasticbeanstalk
|
||||
|
||||
|
||||
.vscode
|
||||
.vscode/formatter.json
|
||||
.vscode/settings.json
|
||||
.vscode/launch.json
|
|
@ -30,8 +30,12 @@ RUN cd /staging/PotreeConverter && \
|
|||
RUN mkdir /var/www
|
||||
|
||||
WORKDIR "/var/www"
|
||||
RUN git clone https://github.com/OpenDroneMap/node-OpenDroneMap .
|
||||
#RUN git clone https://github.com/OpenDroneMap/node-OpenDroneMap .
|
||||
|
||||
COPY . /var/www
|
||||
|
||||
RUN npm install
|
||||
RUN mkdir tmp
|
||||
|
||||
# Fix old version of gdal2tiles.py
|
||||
# RUN (cd / && patch -p0) <patches/gdal2tiles.patch
|
||||
|
|
884
index.js
884
index.js
|
@ -38,455 +38,508 @@ let TaskManager = require('./libs/TaskManager');
|
|||
let Task = require('./libs/Task');
|
||||
let odmOptions = require('./libs/odmOptions');
|
||||
let Directories = require('./libs/Directories');
|
||||
let unzip = require('unzip');
|
||||
|
||||
// zip files
|
||||
let request = require('request');
|
||||
|
||||
let download = function(uri, filename, callback) {
|
||||
request.head(uri, function(err, res, body) {
|
||||
request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
let winstonStream = {
|
||||
write: function(message, encoding){
|
||||
logger.debug(message.slice(0, -1));
|
||||
write: function(message, encoding) {
|
||||
logger.debug(message.slice(0, -1));
|
||||
}
|
||||
};
|
||||
app.use(morgan('combined', { stream : winstonStream }));
|
||||
app.use(bodyParser.urlencoded({extended: true}));
|
||||
app.use(morgan('combined', { stream: winstonStream }));
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(express.static('public'));
|
||||
app.use('/swagger.json', express.static('docs/swagger.json'));
|
||||
|
||||
|
||||
|
||||
let upload = multer({
|
||||
storage: multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
let dstPath = path.join("tmp", req.id);
|
||||
fs.exists(dstPath, exists => {
|
||||
if (!exists){
|
||||
fs.mkdir(dstPath, undefined, () => {
|
||||
cb(null, dstPath);
|
||||
});
|
||||
}else{
|
||||
cb(null, dstPath);
|
||||
}
|
||||
});
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
cb(null, file.originalname);
|
||||
}
|
||||
})
|
||||
storage: multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
let dstPath = path.join("tmp", req.id);
|
||||
fs.exists(dstPath, exists => {
|
||||
if (!exists) {
|
||||
fs.mkdir(dstPath, undefined, () => {
|
||||
cb(null, dstPath);
|
||||
});
|
||||
} else {
|
||||
cb(null, dstPath);
|
||||
}
|
||||
});
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
cb(null, file.originalname);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let taskManager;
|
||||
let server;
|
||||
|
||||
/** @swagger
|
||||
* /task/new:
|
||||
* post:
|
||||
* description: Creates a new task and places it at the end of the processing queue
|
||||
* tags: [task]
|
||||
* consumes:
|
||||
* - multipart/form-data
|
||||
* parameters:
|
||||
* -
|
||||
* name: images
|
||||
* in: formData
|
||||
* description: Images to process, plus an optional GPC file. If included, the GPC file should have .txt extension
|
||||
* required: true
|
||||
* type: file
|
||||
* -
|
||||
* name: name
|
||||
* in: formData
|
||||
* description: An optional name to be associated with the task
|
||||
* required: false
|
||||
* type: string
|
||||
* -
|
||||
* name: options
|
||||
* in: formData
|
||||
* description: 'Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{"name":"cmvs-maxImages","value":"500"},{"name":"time","value":true}]. For a list of all options, call /options'
|
||||
* required: false
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [uuid]
|
||||
* properties:
|
||||
* uuid:
|
||||
* type: string
|
||||
* description: UUID of the newly created task
|
||||
* default:
|
||||
* description: Error
|
||||
* schema:
|
||||
* $ref: '#/definitions/Error'
|
||||
*/
|
||||
* /task/new:
|
||||
* post:
|
||||
* description: Creates a new task and places it at the end of the processing queue
|
||||
* tags: [task]
|
||||
* consumes:
|
||||
* - multipart/form-data
|
||||
* parameters:
|
||||
* -
|
||||
* name: images
|
||||
* in: formData
|
||||
* description: Images to process, plus an optional GPC file. If included, the GPC file should have .txt extension
|
||||
* required: true
|
||||
* type: file
|
||||
* -
|
||||
* name: zipurl
|
||||
* in: formData
|
||||
* description: Images to process from a url zip file, plus an optional GPC file. If included, the GPC file should have .txt extension
|
||||
* required: optional
|
||||
* type: file
|
||||
* -
|
||||
* name: name
|
||||
* in: formData
|
||||
* description: An optional name to be associated with the task
|
||||
* required: false
|
||||
* type: string
|
||||
* -
|
||||
* name: options
|
||||
* in: formData
|
||||
* description: 'Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{"name":"cmvs-maxImages","value":"500"},{"name":"time","value":true}]. For a list of all options, call /options'
|
||||
* required: false
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Success
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [uuid]
|
||||
* properties:
|
||||
* uuid:
|
||||
* type: string
|
||||
* description: UUID of the newly created task
|
||||
* default:
|
||||
* description: Error
|
||||
* schema:
|
||||
* $ref: '#/definitions/Error'
|
||||
*/
|
||||
app.post('/task/new', addRequestId, upload.array('images'), (req, res) => {
|
||||
if (!req.files || req.files.length === 0) res.json({error: "Need at least 1 file."});
|
||||
else{
|
||||
let srcPath = path.join("tmp", req.id);
|
||||
let destPath = path.join(Directories.data, req.id);
|
||||
let destImagesPath = path.join(destPath, "images");
|
||||
let destGpcPath = path.join(destPath, "gpc");
|
||||
if ((!req.files || req.files.length === 0) && !req.body.zipurl) res.json({ error: "Need at least 1 file or a zip file url." });
|
||||
else {
|
||||
let srcPath = path.join("tmp", req.id);
|
||||
let destPath = path.join(Directories.data, req.id);
|
||||
let destImagesPath = path.join(destPath, "images");
|
||||
let destGpcPath = path.join(destPath, "gpc");
|
||||
|
||||
async.series([
|
||||
cb => {
|
||||
odmOptions.filterOptions(req.body.options, (err, options) => {
|
||||
if (err) cb(err);
|
||||
else{
|
||||
req.body.options = options;
|
||||
cb(null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Move all uploads to data/<uuid>/images dir
|
||||
cb => {
|
||||
fs.stat(destPath, (err, stat) => {
|
||||
if (err && err.code === 'ENOENT') cb();
|
||||
else cb(new Error(`Directory exists (should not have happened: ${err.code})`));
|
||||
});
|
||||
},
|
||||
cb => fs.mkdir(destPath, undefined, cb),
|
||||
cb => fs.mkdir(destGpcPath, undefined, cb),
|
||||
cb => fs.rename(srcPath, destImagesPath, cb),
|
||||
cb => {
|
||||
// Find any *.txt (GPC) file and move it to the data/<uuid>/gpc directory
|
||||
fs.readdir(destImagesPath, (err, entries) => {
|
||||
if (err) cb(err);
|
||||
else{
|
||||
async.eachSeries(entries, (entry, cb) => {
|
||||
if (/\.txt$/gi.test(entry)){
|
||||
fs.rename(path.join(destImagesPath, entry), path.join(destGpcPath, entry), cb);
|
||||
}else cb();
|
||||
}, cb);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Create task
|
||||
cb => {
|
||||
new Task(req.id, req.body.name, (err, task) => {
|
||||
if (err) cb(err);
|
||||
else{
|
||||
taskManager.addNew(task);
|
||||
res.json({uuid: req.id});
|
||||
cb();
|
||||
}
|
||||
}, req.body.options);
|
||||
}
|
||||
], err => {
|
||||
if (err) res.json({error: err.message});
|
||||
});
|
||||
}
|
||||
|
||||
async.series([
|
||||
// moved these up becaus ei need to populat ethem if zip file first
|
||||
|
||||
cb => {
|
||||
odmOptions.filterOptions(req.body.options, (err, options) => {
|
||||
if (err) cb(err);
|
||||
else {
|
||||
req.body.options = options;
|
||||
cb(null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Move all uploads to data/<uuid>/images dir
|
||||
cb => {
|
||||
if (req.files && req.files.length > 0) {
|
||||
setTimeout(function() {
|
||||
fs.stat(destPath, (err, stat) => {
|
||||
if (err && err.code === 'ENOENT') cb();
|
||||
else cb(new Error(`Directory exists (should not have happened: ${err.code})`));
|
||||
});
|
||||
}, 500);
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
},
|
||||
cb => { fs.mkdir(srcPath, undefined, cb) },
|
||||
cb => { fs.mkdir(destPath, undefined, cb) },
|
||||
cb => { fs.mkdir(destGpcPath, undefined, cb) },
|
||||
cb => fs.rename(srcPath, destImagesPath, cb),
|
||||
cb => {
|
||||
if (req.body.zipurl) {
|
||||
var filename = path.basename(req.body.zipurl);
|
||||
download(req.body.zipurl, destPath + '/' + filename, function() {
|
||||
// unzip and flatten the zip file (incase there are folders in the zip)
|
||||
fs.createReadStream(destPath + '/' + filename).pipe(unzip.Parse())
|
||||
.on('entry', function(entry) {
|
||||
if (entry.type === 'File') {
|
||||
entry.pipe(fs.createWriteStream(destImagesPath + '/' + path.basename(entry.path)));
|
||||
} else {
|
||||
entry.autodrain();
|
||||
}
|
||||
}).on('close', function() {
|
||||
cb();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
},
|
||||
|
||||
cb => {
|
||||
// Find any *.txt (GPC) file and move it to the data/<uuid>/gpc directory
|
||||
fs.readdir(destImagesPath, (err, entries) => {
|
||||
if (err) cb(err);
|
||||
else {
|
||||
async.eachSeries(entries, (entry, cb) => {
|
||||
if (/\.txt$/gi.test(entry)) {
|
||||
fs.rename(path.join(destImagesPath, entry), path.join(destGpcPath, entry), cb);
|
||||
} else cb();
|
||||
}, cb);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Create task
|
||||
cb => {
|
||||
new Task(req.id, req.body.name, (err, task) => {
|
||||
if (err) cb(err);
|
||||
else {
|
||||
taskManager.addNew(task);
|
||||
res.json({ uuid: req.id });
|
||||
cb();
|
||||
}
|
||||
}, req.body.options);
|
||||
}
|
||||
], err => {
|
||||
if (err) res.json({ error: err.message });
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
let getTaskFromUuid = (req, res, next) => {
|
||||
let task = taskManager.find(req.params.uuid);
|
||||
if (task){
|
||||
req.task = task;
|
||||
next();
|
||||
}else res.json({error: `${req.params.uuid} not found`});
|
||||
let task = taskManager.find(req.params.uuid);
|
||||
if (task) {
|
||||
req.task = task;
|
||||
next();
|
||||
} else res.json({ error: `${req.params.uuid} not found` });
|
||||
};
|
||||
|
||||
/** @swagger
|
||||
* /task/{uuid}/info:
|
||||
* get:
|
||||
* description: Gets information about this task, such as name, creation date, processing time, status, command line options and number of images being processed. See schema definition for a full list.
|
||||
* tags: [task]
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: path
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Task Information
|
||||
* schema:
|
||||
* title: TaskInfo
|
||||
* type: object
|
||||
* required: [uuid, name, dateCreated, processingTime, status, options, imagesCount]
|
||||
* properties:
|
||||
* uuid:
|
||||
* type: string
|
||||
* description: UUID
|
||||
* name:
|
||||
* type: string
|
||||
* description: Name
|
||||
* dateCreated:
|
||||
* type: integer
|
||||
* description: Timestamp
|
||||
* processingTime:
|
||||
* type: integer
|
||||
* description: Milliseconds that have elapsed since the task started being processed.
|
||||
* status:
|
||||
* type: integer
|
||||
* description: Status code (10 = QUEUED, 20 = RUNNING, 30 = FAILED, 40 = COMPLETED, 50 = CANCELED)
|
||||
* enum: [10, 20, 30, 40, 50]
|
||||
* options:
|
||||
* type: array
|
||||
* description: List of options used to process this task
|
||||
* items:
|
||||
* type: object
|
||||
* required: [name, value]
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 'Option name (example: "odm_meshing-octreeDepth")'
|
||||
* value:
|
||||
* type: string
|
||||
* description: 'Value (example: 9)'
|
||||
* imagesCount:
|
||||
* type: integer
|
||||
* description: Number of images
|
||||
* default:
|
||||
* description: Error
|
||||
* schema:
|
||||
* $ref: '#/definitions/Error'
|
||||
*/
|
||||
* /task/{uuid}/info:
|
||||
* get:
|
||||
* description: Gets information about this task, such as name, creation date, processing time, status, command line options and number of images being processed. See schema definition for a full list.
|
||||
* tags: [task]
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: path
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Task Information
|
||||
* schema:
|
||||
* title: TaskInfo
|
||||
* type: object
|
||||
* required: [uuid, name, dateCreated, processingTime, status, options, imagesCount]
|
||||
* properties:
|
||||
* uuid:
|
||||
* type: string
|
||||
* description: UUID
|
||||
* name:
|
||||
* type: string
|
||||
* description: Name
|
||||
* dateCreated:
|
||||
* type: integer
|
||||
* description: Timestamp
|
||||
* processingTime:
|
||||
* type: integer
|
||||
* description: Milliseconds that have elapsed since the task started being processed.
|
||||
* status:
|
||||
* type: integer
|
||||
* description: Status code (10 = QUEUED, 20 = RUNNING, 30 = FAILED, 40 = COMPLETED, 50 = CANCELED)
|
||||
* enum: [10, 20, 30, 40, 50]
|
||||
* options:
|
||||
* type: array
|
||||
* description: List of options used to process this task
|
||||
* items:
|
||||
* type: object
|
||||
* required: [name, value]
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: 'Option name (example: "odm_meshing-octreeDepth")'
|
||||
* value:
|
||||
* type: string
|
||||
* description: 'Value (example: 9)'
|
||||
* imagesCount:
|
||||
* type: integer
|
||||
* description: Number of images
|
||||
* default:
|
||||
* description: Error
|
||||
* schema:
|
||||
* $ref: '#/definitions/Error'
|
||||
*/
|
||||
app.get('/task/:uuid/info', getTaskFromUuid, (req, res) => {
|
||||
res.json(req.task.getInfo());
|
||||
res.json(req.task.getInfo());
|
||||
});
|
||||
|
||||
/** @swagger
|
||||
* /task/{uuid}/output:
|
||||
* get:
|
||||
* description: Retrieves the console output of the OpenDroneMap's process. Useful for monitoring execution and to provide updates to the user.
|
||||
* tags: [task]
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: path
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* type: string
|
||||
* -
|
||||
* name: line
|
||||
* in: query
|
||||
* description: Optional line number that the console output should be truncated from. For example, passing a value of 100 will retrieve the console output starting from line 100. Defaults to 0 (retrieve all console output).
|
||||
* default: 0
|
||||
* required: false
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Console Output
|
||||
* schema:
|
||||
* type: string
|
||||
* default:
|
||||
* description: Error
|
||||
* schema:
|
||||
* $ref: '#/definitions/Error'
|
||||
*/
|
||||
* /task/{uuid}/output:
|
||||
* get:
|
||||
* description: Retrieves the console output of the OpenDroneMap's process. Useful for monitoring execution and to provide updates to the user.
|
||||
* tags: [task]
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: path
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* type: string
|
||||
* -
|
||||
* name: line
|
||||
* in: query
|
||||
* description: Optional line number that the console output should be truncated from. For example, passing a value of 100 will retrieve the console output starting from line 100. Defaults to 0 (retrieve all console output).
|
||||
* default: 0
|
||||
* required: false
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Console Output
|
||||
* schema:
|
||||
* type: string
|
||||
* default:
|
||||
* description: Error
|
||||
* schema:
|
||||
* $ref: '#/definitions/Error'
|
||||
*/
|
||||
app.get('/task/:uuid/output', getTaskFromUuid, (req, res) => {
|
||||
res.json(req.task.getOutput(req.query.line));
|
||||
res.json(req.task.getOutput(req.query.line));
|
||||
});
|
||||
|
||||
/** @swagger
|
||||
* /task/{uuid}/download/{asset}:
|
||||
* get:
|
||||
* description: Retrieves an asset (the output of OpenDroneMap's processing) associated with a task
|
||||
* tags: [task]
|
||||
* produces: [application/zip]
|
||||
* parameters:
|
||||
* - name: uuid
|
||||
* in: path
|
||||
* type: string
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* - name: asset
|
||||
* in: path
|
||||
* type: string
|
||||
* description: Type of asset to download. Use "all.zip" for zip file containing all assets.
|
||||
* required: true
|
||||
* enum:
|
||||
* - all.zip
|
||||
* - orthophoto.tif
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Asset File
|
||||
* schema:
|
||||
* type: file
|
||||
* default:
|
||||
* description: Error message
|
||||
* schema:
|
||||
* $ref: '#/definitions/Error'
|
||||
*/
|
||||
* /task/{uuid}/download/{asset}:
|
||||
* get:
|
||||
* description: Retrieves an asset (the output of OpenDroneMap's processing) associated with a task
|
||||
* tags: [task]
|
||||
* produces: [application/zip]
|
||||
* parameters:
|
||||
* - name: uuid
|
||||
* in: path
|
||||
* type: string
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* - name: asset
|
||||
* in: path
|
||||
* type: string
|
||||
* description: Type of asset to download. Use "all.zip" for zip file containing all assets.
|
||||
* required: true
|
||||
* enum:
|
||||
* - all.zip
|
||||
* - orthophoto.tif
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Asset File
|
||||
* schema:
|
||||
* type: file
|
||||
* default:
|
||||
* description: Error message
|
||||
* schema:
|
||||
* $ref: '#/definitions/Error'
|
||||
*/
|
||||
app.get('/task/:uuid/download/:asset', getTaskFromUuid, (req, res) => {
|
||||
let asset = req.params.asset !== undefined ? req.params.asset : "all.zip";
|
||||
let filePath = req.task.getAssetsArchivePath(asset);
|
||||
if (filePath){
|
||||
if (fs.existsSync(filePath)){
|
||||
res.setHeader('Content-Disposition', `attachment; filename=${asset}`);
|
||||
res.setHeader('Content-Type', mime.lookup(asset));
|
||||
res.setHeader('Content-Length', fs.statSync(filePath)["size"]);
|
||||
let asset = req.params.asset !== undefined ? req.params.asset : "all.zip";
|
||||
let filePath = req.task.getAssetsArchivePath(asset);
|
||||
if (filePath) {
|
||||
if (fs.existsSync(filePath)) {
|
||||
res.setHeader('Content-Disposition', `attachment; filename=${asset}`);
|
||||
res.setHeader('Content-Type', mime.lookup(asset));
|
||||
res.setHeader('Content-Length', fs.statSync(filePath)["size"]);
|
||||
|
||||
const filestream = fs.createReadStream(filePath);
|
||||
filestream.pipe(res);
|
||||
}else{
|
||||
res.json({error: "Asset not ready"});
|
||||
}
|
||||
}else{
|
||||
res.json({error: "Invalid asset"});
|
||||
}
|
||||
const filestream = fs.createReadStream(filePath);
|
||||
filestream.pipe(res);
|
||||
} else {
|
||||
res.json({ error: "Asset not ready" });
|
||||
}
|
||||
} else {
|
||||
res.json({ error: "Invalid asset" });
|
||||
}
|
||||
});
|
||||
|
||||
/** @swagger
|
||||
* definition:
|
||||
* Error:
|
||||
* type: object
|
||||
* required:
|
||||
* - error
|
||||
* properties:
|
||||
* error:
|
||||
* type: string
|
||||
* description: Description of the error
|
||||
* Response:
|
||||
* type: object
|
||||
* required:
|
||||
* - success
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* description: true if the command succeeded, false otherwise
|
||||
* error:
|
||||
* type: string
|
||||
* description: Error message if an error occured
|
||||
*/
|
||||
* definition:
|
||||
* Error:
|
||||
* type: object
|
||||
* required:
|
||||
* - error
|
||||
* properties:
|
||||
* error:
|
||||
* type: string
|
||||
* description: Description of the error
|
||||
* Response:
|
||||
* type: object
|
||||
* required:
|
||||
* - success
|
||||
* properties:
|
||||
* success:
|
||||
* type: boolean
|
||||
* description: true if the command succeeded, false otherwise
|
||||
* error:
|
||||
* type: string
|
||||
* description: Error message if an error occured
|
||||
*/
|
||||
let uuidCheck = (req, res, next) => {
|
||||
if (!req.body.uuid) res.json({error: "uuid param missing."});
|
||||
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({success: false, error: err.message});
|
||||
};
|
||||
return err => {
|
||||
if (!err) res.json({ success: true });
|
||||
else res.json({ success: false, error: err.message });
|
||||
};
|
||||
};
|
||||
|
||||
/** @swagger
|
||||
* /task/cancel:
|
||||
* post:
|
||||
* description: Cancels a task (stops its execution, or prevents it from being executed)
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: body
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Command Received
|
||||
* schema:
|
||||
* $ref: "#/definitions/Response"
|
||||
*/
|
||||
* /task/cancel:
|
||||
* post:
|
||||
* description: Cancels a task (stops its execution, or prevents it from being executed)
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: body
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Command Received
|
||||
* schema:
|
||||
* $ref: "#/definitions/Response"
|
||||
*/
|
||||
app.post('/task/cancel', uuidCheck, (req, res) => {
|
||||
taskManager.cancel(req.body.uuid, successHandler(res));
|
||||
taskManager.cancel(req.body.uuid, successHandler(res));
|
||||
});
|
||||
|
||||
/** @swagger
|
||||
* /task/remove:
|
||||
* post:
|
||||
* description: Removes a task and deletes all of its assets
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: body
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Command Received
|
||||
* schema:
|
||||
* $ref: "#/definitions/Response"
|
||||
*/
|
||||
* /task/remove:
|
||||
* post:
|
||||
* description: Removes a task and deletes all of its assets
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: body
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Command Received
|
||||
* schema:
|
||||
* $ref: "#/definitions/Response"
|
||||
*/
|
||||
app.post('/task/remove', uuidCheck, (req, res) => {
|
||||
taskManager.remove(req.body.uuid, successHandler(res));
|
||||
taskManager.remove(req.body.uuid, successHandler(res));
|
||||
});
|
||||
|
||||
/** @swagger
|
||||
* /task/restart:
|
||||
* post:
|
||||
* description: Restarts a task that was previously canceled or that had failed to process
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: body
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Command Received
|
||||
* schema:
|
||||
* $ref: "#/definitions/Response"
|
||||
*/
|
||||
* /task/restart:
|
||||
* post:
|
||||
* description: Restarts a task that was previously canceled or that had failed to process
|
||||
* parameters:
|
||||
* -
|
||||
* name: uuid
|
||||
* in: body
|
||||
* description: UUID of the task
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Command Received
|
||||
* schema:
|
||||
* $ref: "#/definitions/Response"
|
||||
*/
|
||||
app.post('/task/restart', uuidCheck, (req, res) => {
|
||||
taskManager.restart(req.body.uuid, successHandler(res));
|
||||
taskManager.restart(req.body.uuid, successHandler(res));
|
||||
});
|
||||
|
||||
/** @swagger
|
||||
* /options:
|
||||
* get:
|
||||
* description: Retrieves the command line options that can be passed to process a task
|
||||
* tags: [server]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Options
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* title: Option
|
||||
* type: object
|
||||
* required: [name, type, value, domain, help]
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: Command line option (exactly as it is passed to the OpenDroneMap process, minus the leading '--')
|
||||
* type:
|
||||
* type: string
|
||||
* description: Datatype of the value of this option
|
||||
* enum:
|
||||
* - int
|
||||
* - float
|
||||
* - string
|
||||
* - bool
|
||||
* value:
|
||||
* type: string
|
||||
* description: Default value of this option
|
||||
* domain:
|
||||
* type: string
|
||||
* description: Valid range of values (for example, "positive integer" or "float > 0.0")
|
||||
* help:
|
||||
* type: string
|
||||
* description: Description of what this option does
|
||||
*/
|
||||
* /options:
|
||||
* get:
|
||||
* description: Retrieves the command line options that can be passed to process a task
|
||||
* tags: [server]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Options
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* title: Option
|
||||
* type: object
|
||||
* required: [name, type, value, domain, help]
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description: Command line option (exactly as it is passed to the OpenDroneMap process, minus the leading '--')
|
||||
* type:
|
||||
* type: string
|
||||
* description: Datatype of the value of this option
|
||||
* enum:
|
||||
* - int
|
||||
* - float
|
||||
* - string
|
||||
* - bool
|
||||
* value:
|
||||
* type: string
|
||||
* description: Default value of this option
|
||||
* domain:
|
||||
* type: string
|
||||
* description: Valid range of values (for example, "positive integer" or "float > 0.0")
|
||||
* help:
|
||||
* type: string
|
||||
* description: Description of what this option does
|
||||
*/
|
||||
app.get('/options', (req, res) => {
|
||||
odmOptions.getOptions((err, options) => {
|
||||
if (err) res.json({error: err.message});
|
||||
else res.json(options);
|
||||
});
|
||||
odmOptions.getOptions((err, options) => {
|
||||
if (err) res.json({ error: err.message });
|
||||
else res.json(options);
|
||||
});
|
||||
});
|
||||
|
||||
/** @swagger
|
||||
* /info:
|
||||
* get:
|
||||
* description: Retrieves information about this node
|
||||
* tags: [server]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Info
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [version, taskQueueCount]
|
||||
* properties:
|
||||
* version:
|
||||
* type: string
|
||||
* description: Current version
|
||||
* taskQueueCount:
|
||||
* type: integer
|
||||
* description: Number of tasks currently being processed or waiting to be processed
|
||||
*/
|
||||
* /info:
|
||||
* get:
|
||||
* description: Retrieves information about this node
|
||||
* tags: [server]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Info
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [version, taskQueueCount]
|
||||
* properties:
|
||||
* version:
|
||||
* type: string
|
||||
* description: Current version
|
||||
* taskQueueCount:
|
||||
* type: integer
|
||||
* description: Number of tasks currently being processed or waiting to be processed
|
||||
*/
|
||||
app.get('/info', (req, res) => {
|
||||
res.json({
|
||||
version: packageJson.version,
|
||||
|
@ -495,46 +548,47 @@ app.get('/info', (req, res) => {
|
|||
});
|
||||
|
||||
let gracefulShutdown = done => {
|
||||
async.series([
|
||||
cb => taskManager.dumpTaskList(cb),
|
||||
cb => {
|
||||
logger.info("Closing server");
|
||||
server.close();
|
||||
logger.info("Exiting...");
|
||||
process.exit(0);
|
||||
}
|
||||
], done);
|
||||
async.series([
|
||||
cb => taskManager.dumpTaskList(cb),
|
||||
cb => {
|
||||
logger.info("Closing server");
|
||||
server.close();
|
||||
logger.info("Exiting...");
|
||||
process.exit(0);
|
||||
}
|
||||
], done);
|
||||
};
|
||||
|
||||
// listen for TERM signal .e.g. kill
|
||||
process.on ('SIGTERM', gracefulShutdown);
|
||||
process.on('SIGTERM', gracefulShutdown);
|
||||
|
||||
// listen for INT signal e.g. Ctrl-C
|
||||
process.on ('SIGINT', gracefulShutdown);
|
||||
process.on('SIGINT', gracefulShutdown);
|
||||
|
||||
// Startup
|
||||
if (config.test) logger.info("Running in test mode");
|
||||
|
||||
let commands = [
|
||||
cb => odmOptions.initialize(cb),
|
||||
cb => { taskManager = new TaskManager(cb); },
|
||||
cb => { server = app.listen(config.port, err => {
|
||||
if (!err) logger.info('Server has started on port ' + String(config.port));
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
cb => odmOptions.initialize(cb),
|
||||
cb => { taskManager = new TaskManager(cb); },
|
||||
cb => {
|
||||
server = app.listen(config.port, err => {
|
||||
if (!err) logger.info('Server has started on port ' + String(config.port));
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
if (config.powercycle){
|
||||
commands.push(cb => {
|
||||
logger.info("Power cycling is set, application will shut down...");
|
||||
process.exit(0);
|
||||
});
|
||||
if (config.powercycle) {
|
||||
commands.push(cb => {
|
||||
logger.info("Power cycling is set, application will shut down...");
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
async.series(commands, err => {
|
||||
if (err){
|
||||
logger.error("Error during startup: " + err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
if (err) {
|
||||
logger.error("Error during startup: " + err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
|
@ -31,8 +31,10 @@
|
|||
"multer": "^1.1.0",
|
||||
"node-schedule": "^1.1.1",
|
||||
"node-uuid": "^1.4.7",
|
||||
"request": "^2.81.0",
|
||||
"rimraf": "^2.5.3",
|
||||
"swagger-jsdoc": "^1.3.1",
|
||||
"unzip": "^0.1.11",
|
||||
"winston": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,146 +1,164 @@
|
|||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Node-OpenDroneMap</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
<style>
|
||||
body {
|
||||
padding-top: 50px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
<link href="css/fileinput.css" media="all" rel="stylesheet" type="text/css" />
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Node-OpenDroneMap</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<script src="js/vendor/modernizr-2.8.3.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!--[if lt IE 8]>
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
<style>
|
||||
body {
|
||||
padding-top: 50px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
<link href="css/fileinput.css" media="all" rel="stylesheet" type="text/css" />
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
|
||||
<script src="js/vendor/modernizr-2.8.3.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--[if lt IE 8]>
|
||||
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
|
||||
<![endif]-->
|
||||
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="/">Node-OpenDroneMap</a>
|
||||
<p class="navbar-text navbar-right">Open Source Drone Aerial Imagery Processing</a></p>
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="/">Node-OpenDroneMap</a>
|
||||
<p class="navbar-text navbar-right">Open Source Drone Aerial Imagery Processing</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
<!-- Example row of columns -->
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<h2>New Task</h2>
|
||||
<br/>
|
||||
<form enctype="multipart/form-data" onsubmit="return false;">
|
||||
<div class="form-group form-inline">
|
||||
<label for="taskName">Project Name:</lable> <input type="text" class="form-control" value="" id="taskName" />
|
||||
<!-- Example row of columns -->
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<h2>New Task</h2>
|
||||
<br/>
|
||||
<form enctype="multipart/form-data" onsubmit="return false;">
|
||||
<div class="form-group form-inline">
|
||||
<label for="taskName">Project Name:</lable> <input type="text" class="form-control" value="" id="taskName" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div id="imagesInput" class="form-group">
|
||||
<label for="images">Aerial Images and GCP List (optional):</label> <input id="images" name="images" multiple type="file">
|
||||
<div id="errorBlock" class="help-block"></div>
|
||||
</div>
|
||||
<div class="text-right"><input type="submit" class="btn btn-success" value="Start Task" id="btnUpload" /></div>
|
||||
<div id="options">
|
||||
<div class="form-inline form-group form-horizontal">
|
||||
<div data-bind="visible: error(), text: error()" class="alert alert-warning" role="alert"></div>
|
||||
<button style="position: relative; top: -45px;" type="submit" class="btn btn-default" data-bind="visible: !error(), click: function(){ showOptions(!showOptions()); }, text: (showOptions() ? 'Hide' : 'Show') + ' Options'"></button>
|
||||
|
||||
<div data-bind="visible: showOptions()">
|
||||
<div data-bind="foreach: options">
|
||||
<label data-bind="text: properties.name + (properties.domain ? ' (' + properties.domain + ')' : '')"></label><br/>
|
||||
<!-- ko if: properties.type !== 'bool' -->
|
||||
<input type="text" class="form-control" data-bind="attr: {placeholder: properties.value}, value: value">
|
||||
<!-- /ko -->
|
||||
<!-- ko if: properties.type === 'bool' -->
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
|
||||
</div>
|
||||
<div id="zipFileInput" class="form-group hidden">
|
||||
<label for="zipurl">Zip file url with Aerial Images and GCP List (optional):</label> <input id="zipurl" name="zipurl" class="form-control" multiple type="text">
|
||||
<div id="errorBlock" class="help-block"></div>
|
||||
</div>
|
||||
|
||||
<div class="text-right"><input type="button" class="btn btn-info" value="From URL" id="btnShowImport" />
|
||||
<input type="button" class="btn btn-info hidden" value="File Upload" id="btnShowUpload" />
|
||||
<input type="submit" class="btn btn-success" value="Start Task" id="btnUpload" />
|
||||
<input type="submit" class="btn btn-success hidden" value="Get ZIP" id="btnImport" /></div>
|
||||
<div id="options">
|
||||
<div class="form-inline form-group form-horizontal">
|
||||
<div data-bind="visible: error(), text: error()" class="alert alert-warning" role="alert"></div>
|
||||
<button style="position: relative; top: -45px;" type="submit" class="btn btn-default" data-bind="visible: !error(), click: function(){ showOptions(!showOptions()); }, text: (showOptions() ? 'Hide' : 'Show') + ' Options'"></button>
|
||||
|
||||
<div data-bind="visible: showOptions()">
|
||||
<div data-bind="foreach: options">
|
||||
<label data-bind="text: properties.name + (properties.domain ? ' (' + properties.domain + ')' : '')"></label><br/>
|
||||
<!-- ko if: properties.type !== 'bool' -->
|
||||
<input type="text" class="form-control" data-bind="attr: {placeholder: properties.value}, value: value">
|
||||
<!-- /ko -->
|
||||
<!-- ko if: properties.type === 'bool' -->
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" data-bind="checked: value"> Enable
|
||||
</label>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<button type="submit" class="btn glyphicon glyphicon-info-sign btn-info" data-toggle="tooltip" data-placement="top" data-bind="attr: {title: properties.help}"></button>
|
||||
<button type="submit" class="btn glyphicon glyphicon glyphicon-repeat btn-default" data-toggle="tooltip" data-placement="top" title="Reset to default" data-bind="click: resetToDefault"></button>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
<button type="submit" class="btn glyphicon glyphicon-info-sign btn-info" data-toggle="tooltip" data-placement="top" data-bind="attr: {title: properties.help}"></button>
|
||||
<button type="submit" class="btn glyphicon glyphicon glyphicon-repeat btn-default" data-toggle="tooltip" data-placement="top" title="Reset to default" data-bind="click: resetToDefault"></button>
|
||||
|
||||
<br/><br/>
|
||||
</div>
|
||||
<br/><br/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-7" id="taskList">
|
||||
<h2>Current Tasks (<span data-bind="text: tasks().length"></span>)</h2>
|
||||
<p data-bind="visible: tasks().length === 0">No running tasks.</p>
|
||||
<div data-bind="foreach: tasks">
|
||||
<div class="task" data-bind="css: {pulsePositive: info().status && info().status.code === 40, pulseNegative: info().status && info().status.code === 30}">
|
||||
<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 class="taskItem"><strong>UUID:</strong> <a data-bind="text: info().uuid, attr: {href: '/task/' + info().uuid + '/info'}"></a></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>Time Elapsed:</strong> <span data-bind="text: timeElapsed()"></span></div>
|
||||
<div class="taskItem"><strong>Output:</strong> <a href="javascript:void(0);" data-bind="click: viewOutput, visible: !viewingOutput()">View</a><a href="javascript:void(0);" data-bind="click: hideOutput, visible: viewingOutput()">Hide</a></div>
|
||||
<textarea class="consoleOutput" data-bind="value: output().join(''), visible: viewingOutput(), event: {mouseover: consoleMouseOver, mouseout: consoleMouseOut}, attr: {id: 'console_' + uuid}"></textarea>
|
||||
<div class="col-md-7" id="taskList">
|
||||
<h2>Current Tasks (<span data-bind="text: tasks().length"></span>)</h2>
|
||||
<p data-bind="visible: tasks().length === 0">No running tasks.</p>
|
||||
<div data-bind="foreach: tasks">
|
||||
<div class="task" data-bind="css: {pulsePositive: info().status && info().status.code === 40, pulseNegative: info().status && info().status.code === 30}">
|
||||
<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 class="taskItem"><strong>UUID:</strong>
|
||||
<a data-bind="text: info().uuid, attr: {href: '/task/' + info().uuid + '/info'}"></a>
|
||||
</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>Time Elapsed:</strong> <span data-bind="text: timeElapsed()"></span></div>
|
||||
<div class="taskItem"><strong>Output:</strong> <a href="javascript:void(0);" data-bind="click: viewOutput, visible: !viewingOutput()">View</a><a href="javascript:void(0);" data-bind="click: hideOutput, visible: viewingOutput()">Hide</a></div>
|
||||
<textarea class="consoleOutput" data-bind="value: output().join(''), visible: viewingOutput(), event: {mouseover: consoleMouseOver, mouseout: consoleMouseOut}, attr: {id: 'console_' + uuid}"></textarea>
|
||||
|
||||
<span data-bind="css: 'statusIcon glyphicon ' + icon()"></span>
|
||||
|
||||
<div class="actionButtons">
|
||||
<button data-bind="click: download, visible: showDownload()" type="button" class="btn btn-primary btn-sm" >
|
||||
<span data-bind="css: 'statusIcon glyphicon ' + icon()"></span>
|
||||
|
||||
<div class="actionButtons">
|
||||
<button data-bind="click: download, visible: showDownload()" type="button" class="btn btn-primary btn-sm">
|
||||
<span class="glyphicon glyphicon-download-alt"></span> Download All Assets
|
||||
</button>
|
||||
|
||||
<button data-bind="click: downloadOrthophoto, visible: showDownload()" type="button" class="btn btn-primary btn-sm" >
|
||||
<button data-bind="click: downloadOrthophoto, visible: showDownload()" type="button" class="btn btn-primary btn-sm">
|
||||
<span class="glyphicon glyphicon-picture"></span> Download Orthophoto
|
||||
</button>
|
||||
|
||||
<button data-bind="click: cancel, visible: showCancel()" type="button" class="btn btn-primary btn-sm" >
|
||||
<button data-bind="click: cancel, visible: showCancel()" type="button" class="btn btn-primary btn-sm">
|
||||
<span class="glyphicon glyphicon-remove-circle"></span> Cancel
|
||||
</button>
|
||||
|
||||
<button data-bind="click: restart, visible: showRestart()" type="button" class="btn btn-primary btn-sm" >
|
||||
<button data-bind="click: restart, visible: showRestart()" type="button" class="btn btn-primary btn-sm">
|
||||
<span class="glyphicon glyphicon-play"></span> Restart
|
||||
</button>
|
||||
|
||||
<button data-bind="click: remove, visible: showRemove()" type="button" class="btn btn-primary btn-sm">
|
||||
<button data-bind="click: remove, visible: showRemove()" type="button" class="btn btn-primary btn-sm">
|
||||
<span class="glyphicon glyphicon-remove-circle"></span> Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error messages -->
|
||||
<div data-bind="visible: info().error !== undefined">
|
||||
<div class="alert alert-warning" role="alert" data-bind="text: info().error"></div>
|
||||
<div class="actionButtons">
|
||||
<button data-bind="click: remove" type="button" class="btn btn-primary btn-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error messages -->
|
||||
<div data-bind="visible: info().error !== undefined">
|
||||
<div class="alert alert-warning" role="alert" data-bind="text: info().error"></div>
|
||||
<div class="actionButtons">
|
||||
<button data-bind="click: remove" type="button" class="btn btn-primary btn-sm">
|
||||
<span class="glyphicon glyphicon-remove-circle"></span> Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<hr>
|
||||
|
||||
<footer>
|
||||
<p>Links: <a href="https://github.com/pierotofy/node-OpenDroneMap/blob/master/docs/index.adoc" target="_blank">API Docs</a> | <a href="https://github.com/OpenDroneMap/WebODM" target="_blank">WebODM</a>
|
||||
<p>This software is released under the terms of the <a href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank">GPLv3 License</a>. See <a href="https://github.com/pierotofy/node-OpenDroneMap" target="_blank">node-opendronemap</a> on Github for more information.</p>
|
||||
</footer>
|
||||
</div> <!-- /container --> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
|
||||
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.11.2.min.js"><\/script>')</script>
|
||||
<script src="js/vendor/bootstrap.min.js"></script>
|
||||
<script src="js/vendor/knockout-3.4.0.js"></script>
|
||||
<script src="js/fileinput.js" type="text/javascript"></script>
|
||||
<footer>
|
||||
<p>Links: <a href="https://github.com/pierotofy/node-OpenDroneMap/blob/master/docs/index.adoc" target="_blank">API Docs</a> | <a href="https://github.com/OpenDroneMap/WebODM" target="_blank">WebODM</a>
|
||||
<p>This software is released under the terms of the <a href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank">GPLv3 License</a>. See <a href="https://github.com/pierotofy/node-OpenDroneMap" target="_blank">node-opendronemap</a> on Github for more information.</p>
|
||||
</footer>
|
||||
</div>
|
||||
<!-- /container -->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
|
||||
<script>
|
||||
window.jQuery || document.write('<script src="js/vendor/jquery-1.11.2.min.js"><\/script>')
|
||||
</script>
|
||||
<script src="js/vendor/bootstrap.min.js"></script>
|
||||
<script src="js/vendor/knockout-3.4.0.js"></script>
|
||||
<script src="js/fileinput.js" type="text/javascript"></script>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -15,30 +15,30 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
$(function(){
|
||||
function hoursMinutesSecs(t){
|
||||
$(function() {
|
||||
function hoursMinutesSecs(t) {
|
||||
var ch = 60 * 60 * 1000,
|
||||
cm = 60 * 1000,
|
||||
h = Math.floor(t / ch),
|
||||
m = Math.floor( (t - h * ch) / cm),
|
||||
s = Math.round( (t - h * ch - m * cm) / 1000),
|
||||
pad = function(n){ return n < 10 ? '0' + n : n; };
|
||||
if( s === 60 ){
|
||||
m++;
|
||||
s = 0;
|
||||
}
|
||||
if( m === 60 ){
|
||||
h++;
|
||||
m = 0;
|
||||
}
|
||||
return [pad(h), pad(m), pad(s)].join(':');
|
||||
m = Math.floor((t - h * ch) / cm),
|
||||
s = Math.round((t - h * ch - m * cm) / 1000),
|
||||
pad = function(n) { return n < 10 ? '0' + n : n; };
|
||||
if (s === 60) {
|
||||
m++;
|
||||
s = 0;
|
||||
}
|
||||
if (m === 60) {
|
||||
h++;
|
||||
m = 0;
|
||||
}
|
||||
return [pad(h), pad(m), pad(s)].join(':');
|
||||
}
|
||||
|
||||
function TaskList(){
|
||||
function TaskList() {
|
||||
var uuids = JSON.parse(localStorage.getItem("odmTaskList") || "[]");
|
||||
if (Object.prototype.toString.call(uuids) !== "[object Array]") uuids = [];
|
||||
|
||||
this.tasks = ko.observableArray($.map(uuids, function(uuid){
|
||||
this.tasks = ko.observableArray($.map(uuids, function(uuid) {
|
||||
return new Task(uuid);
|
||||
}));
|
||||
}
|
||||
|
@ -46,14 +46,13 @@ $(function(){
|
|||
this.tasks.push(task);
|
||||
this.saveTaskListToLocalStorage();
|
||||
};
|
||||
TaskList.prototype.saveTaskListToLocalStorage = function(){
|
||||
localStorage.setItem("odmTaskList", JSON.stringify($.map(this.tasks(), function(task){
|
||||
return task.uuid;
|
||||
})
|
||||
));
|
||||
TaskList.prototype.saveTaskListToLocalStorage = function() {
|
||||
localStorage.setItem("odmTaskList", JSON.stringify($.map(this.tasks(), function(task) {
|
||||
return task.uuid;
|
||||
})));
|
||||
};
|
||||
TaskList.prototype.remove = function(task){
|
||||
this.tasks.remove(function(t){
|
||||
TaskList.prototype.remove = function(task) {
|
||||
this.tasks.remove(function(t) {
|
||||
return t === task;
|
||||
});
|
||||
this.saveTaskListToLocalStorage();
|
||||
|
@ -67,7 +66,7 @@ $(function(){
|
|||
CANCELED: 50
|
||||
};
|
||||
|
||||
function Task(uuid){
|
||||
function Task(uuid) {
|
||||
var self = this;
|
||||
|
||||
this.uuid = uuid;
|
||||
|
@ -101,90 +100,90 @@ $(function(){
|
|||
}
|
||||
};
|
||||
|
||||
this.statusDescr = ko.pureComputed(function(){
|
||||
if (this.info().status && this.info().status.code){
|
||||
if(statusCodes[this.info().status.code]){
|
||||
this.statusDescr = ko.pureComputed(function() {
|
||||
if (this.info().status && this.info().status.code) {
|
||||
if (statusCodes[this.info().status.code]) {
|
||||
return statusCodes[this.info().status.code].descr;
|
||||
}else return "Unknown (Status Code: " + this.info().status.code + ")";
|
||||
}else return "-";
|
||||
} else return "Unknown (Status Code: " + this.info().status.code + ")";
|
||||
} else return "-";
|
||||
}, this);
|
||||
this.icon = ko.pureComputed(function(){
|
||||
if (this.info().status && this.info().status.code){
|
||||
if(statusCodes[this.info().status.code]){
|
||||
this.icon = ko.pureComputed(function() {
|
||||
if (this.info().status && this.info().status.code) {
|
||||
if (statusCodes[this.info().status.code]) {
|
||||
return statusCodes[this.info().status.code].icon;
|
||||
}else return "glyphicon-question-sign";
|
||||
}else return "";
|
||||
} else return "glyphicon-question-sign";
|
||||
} else return "";
|
||||
}, this);
|
||||
this.showCancel = ko.pureComputed(function(){
|
||||
return this.info().status &&
|
||||
(this.info().status.code === codes.QUEUED || this.info().status.code === codes.RUNNING);
|
||||
this.showCancel = ko.pureComputed(function() {
|
||||
return this.info().status &&
|
||||
(this.info().status.code === codes.QUEUED || this.info().status.code === codes.RUNNING);
|
||||
}, this);
|
||||
this.showRestart = ko.pureComputed(function(){
|
||||
return this.info().status &&
|
||||
(this.info().status.code === codes.CANCELED);
|
||||
this.showRestart = ko.pureComputed(function() {
|
||||
return this.info().status &&
|
||||
(this.info().status.code === codes.CANCELED);
|
||||
}, this);
|
||||
this.showRemove = ko.pureComputed(function(){
|
||||
return this.info().status &&
|
||||
(this.info().status.code === codes.FAILED || this.info().status.code === codes.COMPLETED || this.info().status.code === codes.CANCELED);
|
||||
this.showRemove = ko.pureComputed(function() {
|
||||
return this.info().status &&
|
||||
(this.info().status.code === codes.FAILED || this.info().status.code === codes.COMPLETED || this.info().status.code === codes.CANCELED);
|
||||
}, this);
|
||||
this.showDownload = ko.pureComputed(function(){
|
||||
return this.info().status &&
|
||||
(this.info().status.code === codes.COMPLETED);
|
||||
this.showDownload = ko.pureComputed(function() {
|
||||
return this.info().status &&
|
||||
(this.info().status.code === codes.COMPLETED);
|
||||
}, this);
|
||||
this.startRefreshingInfo();
|
||||
}
|
||||
Task.prototype.refreshInfo = function(){
|
||||
Task.prototype.refreshInfo = function() {
|
||||
var self = this;
|
||||
var url = "/task/" + this.uuid + "/info";
|
||||
$.get(url)
|
||||
.done(function(json){
|
||||
// Track time
|
||||
.done(function(json) {
|
||||
// Track time
|
||||
|
||||
if (json.processingTime && json.processingTime !== -1){
|
||||
self.timeElapsed(hoursMinutesSecs(json.processingTime));
|
||||
}
|
||||
self.info(json);
|
||||
})
|
||||
.fail(function(){
|
||||
self.info({error: url + " is unreachable."});
|
||||
})
|
||||
.always(function(){ self.loading(false); });
|
||||
if (json.processingTime && json.processingTime !== -1) {
|
||||
self.timeElapsed(hoursMinutesSecs(json.processingTime));
|
||||
}
|
||||
self.info(json);
|
||||
})
|
||||
.fail(function() {
|
||||
self.info({ error: url + " is unreachable." });
|
||||
})
|
||||
.always(function() { self.loading(false); });
|
||||
};
|
||||
Task.prototype.consoleMouseOver = function(){ this.autoScrollOutput = false; };
|
||||
Task.prototype.consoleMouseOut = function(){ this.autoScrollOutput = true; };
|
||||
Task.prototype.resetOutput = function(){
|
||||
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.removeAll();
|
||||
};
|
||||
Task.prototype.viewOutput = function(){
|
||||
Task.prototype.viewOutput = function() {
|
||||
var self = this;
|
||||
|
||||
function fetchOutput(){
|
||||
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());
|
||||
$.get(url, { line: self.viewOutputLine })
|
||||
.done(function(output) {
|
||||
for (var i in output) {
|
||||
self.output.push(output[i]);
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(function(){
|
||||
self.info({error: url + " is unreachable."});
|
||||
});
|
||||
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(){
|
||||
Task.prototype.hideOutput = function() {
|
||||
if (this.fetchOutputInterval) clearInterval(this.fetchOutputInterval);
|
||||
this.viewingOutput(false);
|
||||
};
|
||||
|
@ -192,12 +191,12 @@ $(function(){
|
|||
var self = this;
|
||||
this.stopRefreshingInfo();
|
||||
this.refreshInfo();
|
||||
this.refreshInterval = setInterval(function(){
|
||||
this.refreshInterval = setInterval(function() {
|
||||
self.refreshInfo();
|
||||
}, 500); // TODO: change to larger value
|
||||
};
|
||||
Task.prototype.stopRefreshingInfo = function() {
|
||||
if (this.refreshInterval){
|
||||
if (this.refreshInterval) {
|
||||
clearInterval(this.refreshInterval);
|
||||
this.refreshInterval = null;
|
||||
}
|
||||
|
@ -206,62 +205,62 @@ $(function(){
|
|||
var self = this;
|
||||
var url = "/task/remove";
|
||||
|
||||
function doRemove(){
|
||||
function doRemove() {
|
||||
$.post(url, {
|
||||
uuid: self.uuid
|
||||
})
|
||||
.done(function(json){
|
||||
if (json.success || self.info().error){
|
||||
taskList.remove(self);
|
||||
}else{
|
||||
self.info({error: json.error});
|
||||
}
|
||||
uuid: self.uuid
|
||||
})
|
||||
.done(function(json) {
|
||||
if (json.success || self.info().error) {
|
||||
taskList.remove(self);
|
||||
} else {
|
||||
self.info({ error: json.error });
|
||||
}
|
||||
|
||||
self.stopRefreshingInfo();
|
||||
})
|
||||
.fail(function(){
|
||||
self.info({error: url + " is unreachable."});
|
||||
self.stopRefreshingInfo();
|
||||
});
|
||||
self.stopRefreshingInfo();
|
||||
})
|
||||
.fail(function() {
|
||||
self.info({ error: url + " is unreachable." });
|
||||
self.stopRefreshingInfo();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.info().status && this.info().status.code === codes.COMPLETED){
|
||||
if (this.info().status && this.info().status.code === codes.COMPLETED) {
|
||||
if (confirm("Are you sure?")) doRemove();
|
||||
}else{
|
||||
} else {
|
||||
doRemove();
|
||||
}
|
||||
};
|
||||
|
||||
function genApiCall(url, onSuccess){
|
||||
return function(){
|
||||
function genApiCall(url, onSuccess) {
|
||||
return function() {
|
||||
var self = this;
|
||||
|
||||
$.post(url, {
|
||||
uuid: this.uuid
|
||||
})
|
||||
.done(function(json){
|
||||
if (json.success){
|
||||
if (onSuccess !== undefined) onSuccess(self, json);
|
||||
self.startRefreshingInfo();
|
||||
}else{
|
||||
uuid: this.uuid
|
||||
})
|
||||
.done(function(json) {
|
||||
if (json.success) {
|
||||
if (onSuccess !== undefined) onSuccess(self, json);
|
||||
self.startRefreshingInfo();
|
||||
} else {
|
||||
self.stopRefreshingInfo();
|
||||
self.info({ error: json.error });
|
||||
}
|
||||
})
|
||||
.fail(function() {
|
||||
self.info({ error: url + " is unreachable." });
|
||||
self.stopRefreshingInfo();
|
||||
self.info({error: json.error});
|
||||
}
|
||||
})
|
||||
.fail(function(){
|
||||
self.info({error: url + " is unreachable."});
|
||||
self.stopRefreshingInfo();
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
Task.prototype.cancel = genApiCall("/task/cancel");
|
||||
Task.prototype.restart = genApiCall("/task/restart", function(task){
|
||||
Task.prototype.restart = genApiCall("/task/restart", function(task) {
|
||||
task.resetOutput();
|
||||
});
|
||||
Task.prototype.download = function(){
|
||||
Task.prototype.download = function() {
|
||||
location.href = "/task/" + this.uuid + "/download/all.zip";
|
||||
};
|
||||
Task.prototype.downloadOrthophoto = function(){
|
||||
Task.prototype.downloadOrthophoto = function() {
|
||||
location.href = "/task/" + this.uuid + "/download/orthophoto.tif";
|
||||
};
|
||||
|
||||
|
@ -276,52 +275,75 @@ $(function(){
|
|||
elErrorContainer: '#errorBlock',
|
||||
showUpload: false,
|
||||
uploadAsync: false,
|
||||
uploadExtraData: function(){
|
||||
uploadExtraData: function() {
|
||||
return {
|
||||
name: $("#taskName").val(),
|
||||
zipurl: $("#zipurl").val(),
|
||||
options: JSON.stringify(optionsModel.getUserOptions())
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$("#btnUpload").click(function(){
|
||||
$("#btnUpload").click(function() {
|
||||
$("#btnUpload").attr('disabled', true)
|
||||
.val("Uploading...");
|
||||
.val("Uploading...");
|
||||
|
||||
// Start upload
|
||||
$("#images").fileinput('upload');
|
||||
});
|
||||
});
|
||||
|
||||
// zip file control
|
||||
$('#btnShowImport').on('click', function(e){
|
||||
e.preventDefault();
|
||||
$('#zipFileInput').removeClass('hidden');
|
||||
$('#btnShowUpload').removeClass('hidden');
|
||||
|
||||
$('#imagesInput').addClass('hidden');
|
||||
$('#btnShowImport').addClass('hidden');
|
||||
|
||||
});
|
||||
|
||||
$('#btnShowUpload').on('click', function(e){
|
||||
e.preventDefault();
|
||||
$('#imagesInput').removeClass('hidden');
|
||||
$('#btnShowImport').removeClass('hidden');
|
||||
|
||||
$('#zipFileInput').addClass('hidden');
|
||||
$('#btnShowUpload').addClass('hidden');
|
||||
|
||||
})
|
||||
|
||||
|
||||
var btnUploadLabel = $("#btnUpload").val();
|
||||
$("#images")
|
||||
.on('filebatchuploadsuccess', function(e, params){
|
||||
.on('filebatchuploadsuccess', function(e, params) {
|
||||
$("#images").fileinput('reset');
|
||||
|
||||
if (params.response && params.response.uuid){
|
||||
if (params.response && params.response.uuid) {
|
||||
taskList.add(new Task(params.response.uuid));
|
||||
}
|
||||
})
|
||||
.on('filebatchuploadcomplete', function(){
|
||||
.on('filebatchuploadcomplete', function() {
|
||||
$("#btnUpload").removeAttr('disabled')
|
||||
.val(btnUploadLabel);
|
||||
.val(btnUploadLabel);
|
||||
})
|
||||
.on('filebatchuploaderror', console.warn);
|
||||
|
||||
// Load options
|
||||
function Option(properties){
|
||||
function Option(properties) {
|
||||
this.properties = properties;
|
||||
this.value = ko.observable();
|
||||
}
|
||||
Option.prototype.resetToDefault = function(){
|
||||
Option.prototype.resetToDefault = function() {
|
||||
this.value(undefined);
|
||||
};
|
||||
|
||||
function OptionsModel(){
|
||||
function OptionsModel() {
|
||||
var self = this;
|
||||
|
||||
this.options = ko.observableArray();
|
||||
this.options.subscribe(function(){
|
||||
setTimeout(function(){
|
||||
this.options.subscribe(function() {
|
||||
setTimeout(function() {
|
||||
$('#options [data-toggle="tooltip"]').tooltip();
|
||||
}, 100);
|
||||
});
|
||||
|
@ -329,23 +351,23 @@ $(function(){
|
|||
this.error = ko.observable();
|
||||
|
||||
$.get("/options")
|
||||
.done(function(json){
|
||||
if (json.error) self.error(json.error);
|
||||
else{
|
||||
for (var i in json){
|
||||
self.options.push(new Option(json[i]));
|
||||
.done(function(json) {
|
||||
if (json.error) self.error(json.error);
|
||||
else {
|
||||
for (var i in json) {
|
||||
self.options.push(new Option(json[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(function(){
|
||||
self.error("options are not available.");
|
||||
});
|
||||
})
|
||||
.fail(function() {
|
||||
self.error("options are not available.");
|
||||
});
|
||||
}
|
||||
OptionsModel.prototype.getUserOptions = function(){
|
||||
OptionsModel.prototype.getUserOptions = function() {
|
||||
var result = [];
|
||||
for (var i = 0; i < this.options().length; i++){
|
||||
for (var i = 0; i < this.options().length; i++) {
|
||||
var opt = this.options()[i];
|
||||
if (opt.value() !== undefined){
|
||||
if (opt.value() !== undefined) {
|
||||
result.push({
|
||||
name: opt.properties.name,
|
||||
value: opt.value()
|
||||
|
@ -357,4 +379,4 @@ $(function(){
|
|||
|
||||
var optionsModel = new OptionsModel();
|
||||
ko.applyBindings(optionsModel, document.getElementById("options"));
|
||||
});
|
||||
});
|
Ładowanie…
Reference in New Issue