Add: Zip file uploads with optional GCP file
pull/6/head
Lee Pepper 2017-04-07 22:05:20 -06:00
rodzic 9725cba1a2
commit b9ecdde14a
7 zmienionych plików z 775 dodań i 666 usunięć

3
.dockerignore 100644
Wyświetl plik

@ -0,0 +1,3 @@
node_modules
tests
tmp

6
.gitignore vendored
Wyświetl plik

@ -39,3 +39,9 @@ jspm_packages
# Elastic Beanstalk # Elastic Beanstalk
.elasticbeanstalk .elasticbeanstalk
.vscode
.vscode/formatter.json
.vscode/settings.json
.vscode/launch.json

Wyświetl plik

@ -30,8 +30,12 @@ RUN cd /staging/PotreeConverter && \
RUN mkdir /var/www RUN mkdir /var/www
WORKDIR "/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 npm install
RUN mkdir tmp
# Fix old version of gdal2tiles.py # Fix old version of gdal2tiles.py
# RUN (cd / && patch -p0) <patches/gdal2tiles.patch # RUN (cd / && patch -p0) <patches/gdal2tiles.patch

884
index.js
Wyświetl plik

@ -38,455 +38,508 @@ let TaskManager = require('./libs/TaskManager');
let Task = require('./libs/Task'); let Task = require('./libs/Task');
let odmOptions = require('./libs/odmOptions'); let odmOptions = require('./libs/odmOptions');
let Directories = require('./libs/Directories'); 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 = { let winstonStream = {
write: function(message, encoding){ write: function(message, encoding) {
logger.debug(message.slice(0, -1)); logger.debug(message.slice(0, -1));
} }
}; };
app.use(morgan('combined', { stream : winstonStream })); app.use(morgan('combined', { stream: winstonStream }));
app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(express.static('public')); app.use(express.static('public'));
app.use('/swagger.json', express.static('docs/swagger.json')); app.use('/swagger.json', express.static('docs/swagger.json'));
let upload = multer({ let upload = multer({
storage: multer.diskStorage({ storage: multer.diskStorage({
destination: (req, file, cb) => { destination: (req, file, cb) => {
let dstPath = path.join("tmp", req.id); let dstPath = path.join("tmp", req.id);
fs.exists(dstPath, exists => { fs.exists(dstPath, exists => {
if (!exists){ if (!exists) {
fs.mkdir(dstPath, undefined, () => { fs.mkdir(dstPath, undefined, () => {
cb(null, dstPath); cb(null, dstPath);
}); });
}else{ } else {
cb(null, dstPath); cb(null, dstPath);
} }
}); });
}, },
filename: (req, file, cb) => { filename: (req, file, cb) => {
cb(null, file.originalname); cb(null, file.originalname);
} }
}) })
}); });
let taskManager; let taskManager;
let server; let server;
/** @swagger /** @swagger
* /task/new: * /task/new:
* post: * post:
* description: Creates a new task and places it at the end of the processing queue * description: Creates a new task and places it at the end of the processing queue
* tags: [task] * tags: [task]
* consumes: * consumes:
* - multipart/form-data * - multipart/form-data
* parameters: * parameters:
* - * -
* name: images * name: images
* in: formData * in: formData
* description: Images to process, plus an optional GPC file. If included, the GPC file should have .txt extension * description: Images to process, plus an optional GPC file. If included, the GPC file should have .txt extension
* required: true * required: true
* type: file * type: file
* - * -
* name: name * name: zipurl
* in: formData * in: formData
* description: An optional name to be associated with the task * description: Images to process from a url zip file, plus an optional GPC file. If included, the GPC file should have .txt extension
* required: false * required: optional
* type: string * type: file
* - * -
* name: options * name: name
* in: formData * 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' * description: An optional name to be associated with the task
* required: false * required: false
* type: string * type: string
* responses: * -
* 200: * name: options
* description: Success * in: formData
* schema: * 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'
* type: object * required: false
* required: [uuid] * type: string
* properties: * responses:
* uuid: * 200:
* type: string * description: Success
* description: UUID of the newly created task * schema:
* default: * type: object
* description: Error * required: [uuid]
* schema: * properties:
* $ref: '#/definitions/Error' * 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) => { 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."}); if ((!req.files || req.files.length === 0) && !req.body.zipurl) res.json({ error: "Need at least 1 file or a zip file url." });
else{ else {
let srcPath = path.join("tmp", req.id); let srcPath = path.join("tmp", req.id);
let destPath = path.join(Directories.data, req.id); let destPath = path.join(Directories.data, req.id);
let destImagesPath = path.join(destPath, "images"); let destImagesPath = path.join(destPath, "images");
let destGpcPath = path.join(destPath, "gpc"); 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 => { async.series([
new Task(req.id, req.body.name, (err, task) => { // moved these up becaus ei need to populat ethem if zip file first
if (err) cb(err);
else{ cb => {
taskManager.addNew(task); odmOptions.filterOptions(req.body.options, (err, options) => {
res.json({uuid: req.id}); if (err) cb(err);
cb(); else {
} req.body.options = options;
}, req.body.options); cb(null);
} }
], err => { });
if (err) res.json({error: err.message}); },
});
} // 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 getTaskFromUuid = (req, res, next) => {
let task = taskManager.find(req.params.uuid); let task = taskManager.find(req.params.uuid);
if (task){ if (task) {
req.task = task; req.task = task;
next(); next();
}else res.json({error: `${req.params.uuid} not found`}); } else res.json({ error: `${req.params.uuid} not found` });
}; };
/** @swagger /** @swagger
* /task/{uuid}/info: * /task/{uuid}/info:
* get: * 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. * 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] * tags: [task]
* parameters: * parameters:
* - * -
* name: uuid * name: uuid
* in: path * in: path
* description: UUID of the task * description: UUID of the task
* required: true * required: true
* type: string * type: string
* responses: * responses:
* 200: * 200:
* description: Task Information * description: Task Information
* schema: * schema:
* title: TaskInfo * title: TaskInfo
* type: object * type: object
* required: [uuid, name, dateCreated, processingTime, status, options, imagesCount] * required: [uuid, name, dateCreated, processingTime, status, options, imagesCount]
* properties: * properties:
* uuid: * uuid:
* type: string * type: string
* description: UUID * description: UUID
* name: * name:
* type: string * type: string
* description: Name * description: Name
* dateCreated: * dateCreated:
* type: integer * type: integer
* description: Timestamp * description: Timestamp
* processingTime: * processingTime:
* type: integer * type: integer
* description: Milliseconds that have elapsed since the task started being processed. * description: Milliseconds that have elapsed since the task started being processed.
* status: * status:
* type: integer * type: integer
* description: Status code (10 = QUEUED, 20 = RUNNING, 30 = FAILED, 40 = COMPLETED, 50 = CANCELED) * description: Status code (10 = QUEUED, 20 = RUNNING, 30 = FAILED, 40 = COMPLETED, 50 = CANCELED)
* enum: [10, 20, 30, 40, 50] * enum: [10, 20, 30, 40, 50]
* options: * options:
* type: array * type: array
* description: List of options used to process this task * description: List of options used to process this task
* items: * items:
* type: object * type: object
* required: [name, value] * required: [name, value]
* properties: * properties:
* name: * name:
* type: string * type: string
* description: 'Option name (example: "odm_meshing-octreeDepth")' * description: 'Option name (example: "odm_meshing-octreeDepth")'
* value: * value:
* type: string * type: string
* description: 'Value (example: 9)' * description: 'Value (example: 9)'
* imagesCount: * imagesCount:
* type: integer * type: integer
* description: Number of images * description: Number of images
* default: * default:
* description: Error * description: Error
* schema: * schema:
* $ref: '#/definitions/Error' * $ref: '#/definitions/Error'
*/ */
app.get('/task/:uuid/info', getTaskFromUuid, (req, res) => { app.get('/task/:uuid/info', getTaskFromUuid, (req, res) => {
res.json(req.task.getInfo()); res.json(req.task.getInfo());
}); });
/** @swagger /** @swagger
* /task/{uuid}/output: * /task/{uuid}/output:
* get: * get:
* description: Retrieves the console output of the OpenDroneMap's process. Useful for monitoring execution and to provide updates to the user. * description: Retrieves the console output of the OpenDroneMap's process. Useful for monitoring execution and to provide updates to the user.
* tags: [task] * tags: [task]
* parameters: * parameters:
* - * -
* name: uuid * name: uuid
* in: path * in: path
* description: UUID of the task * description: UUID of the task
* required: true * required: true
* type: string * type: string
* - * -
* name: line * name: line
* in: query * 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). * 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 * default: 0
* required: false * required: false
* type: integer * type: integer
* responses: * responses:
* 200: * 200:
* description: Console Output * description: Console Output
* schema: * schema:
* type: string * type: string
* default: * default:
* description: Error * description: Error
* schema: * schema:
* $ref: '#/definitions/Error' * $ref: '#/definitions/Error'
*/ */
app.get('/task/:uuid/output', getTaskFromUuid, (req, res) => { 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 /** @swagger
* /task/{uuid}/download/{asset}: * /task/{uuid}/download/{asset}:
* get: * get:
* description: Retrieves an asset (the output of OpenDroneMap's processing) associated with a task * description: Retrieves an asset (the output of OpenDroneMap's processing) associated with a task
* tags: [task] * tags: [task]
* produces: [application/zip] * produces: [application/zip]
* parameters: * parameters:
* - name: uuid * - name: uuid
* in: path * in: path
* type: string * type: string
* description: UUID of the task * description: UUID of the task
* required: true * required: true
* - name: asset * - name: asset
* in: path * in: path
* type: string * type: string
* description: Type of asset to download. Use "all.zip" for zip file containing all assets. * description: Type of asset to download. Use "all.zip" for zip file containing all assets.
* required: true * required: true
* enum: * enum:
* - all.zip * - all.zip
* - orthophoto.tif * - orthophoto.tif
* responses: * responses:
* 200: * 200:
* description: Asset File * description: Asset File
* schema: * schema:
* type: file * type: file
* default: * default:
* description: Error message * description: Error message
* schema: * schema:
* $ref: '#/definitions/Error' * $ref: '#/definitions/Error'
*/ */
app.get('/task/:uuid/download/:asset', getTaskFromUuid, (req, res) => { app.get('/task/:uuid/download/:asset', getTaskFromUuid, (req, res) => {
let asset = req.params.asset !== undefined ? req.params.asset : "all.zip"; let asset = req.params.asset !== undefined ? req.params.asset : "all.zip";
let filePath = req.task.getAssetsArchivePath(asset); let filePath = req.task.getAssetsArchivePath(asset);
if (filePath){ if (filePath) {
if (fs.existsSync(filePath)){ if (fs.existsSync(filePath)) {
res.setHeader('Content-Disposition', `attachment; filename=${asset}`); res.setHeader('Content-Disposition', `attachment; filename=${asset}`);
res.setHeader('Content-Type', mime.lookup(asset)); res.setHeader('Content-Type', mime.lookup(asset));
res.setHeader('Content-Length', fs.statSync(filePath)["size"]); res.setHeader('Content-Length', fs.statSync(filePath)["size"]);
const filestream = fs.createReadStream(filePath); const filestream = fs.createReadStream(filePath);
filestream.pipe(res); filestream.pipe(res);
}else{ } else {
res.json({error: "Asset not ready"}); res.json({ error: "Asset not ready" });
} }
}else{ } else {
res.json({error: "Invalid asset"}); res.json({ error: "Invalid asset" });
} }
}); });
/** @swagger /** @swagger
* definition: * definition:
* Error: * Error:
* type: object * type: object
* required: * required:
* - error * - error
* properties: * properties:
* error: * error:
* type: string * type: string
* description: Description of the error * description: Description of the error
* Response: * Response:
* type: object * type: object
* required: * required:
* - success * - success
* properties: * properties:
* success: * success:
* type: boolean * type: boolean
* description: true if the command succeeded, false otherwise * description: true if the command succeeded, false otherwise
* error: * error:
* type: string * type: string
* description: Error message if an error occured * description: Error message if an error occured
*/ */
let uuidCheck = (req, res, next) => { 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(); else next();
}; };
let successHandler = res => { let successHandler = res => {
return err => { return err => {
if (!err) res.json({success: true}); if (!err) res.json({ success: true });
else res.json({success: false, error: err.message}); else res.json({ success: false, error: err.message });
}; };
}; };
/** @swagger /** @swagger
* /task/cancel: * /task/cancel:
* post: * post:
* description: Cancels a task (stops its execution, or prevents it from being executed) * description: Cancels a task (stops its execution, or prevents it from being executed)
* parameters: * parameters:
* - * -
* name: uuid * name: uuid
* in: body * in: body
* description: UUID of the task * description: UUID of the task
* required: true * required: true
* schema: * schema:
* type: string * type: string
* responses: * responses:
* 200: * 200:
* description: Command Received * description: Command Received
* schema: * schema:
* $ref: "#/definitions/Response" * $ref: "#/definitions/Response"
*/ */
app.post('/task/cancel', uuidCheck, (req, res) => { app.post('/task/cancel', uuidCheck, (req, res) => {
taskManager.cancel(req.body.uuid, successHandler(res)); taskManager.cancel(req.body.uuid, successHandler(res));
}); });
/** @swagger /** @swagger
* /task/remove: * /task/remove:
* post: * post:
* description: Removes a task and deletes all of its assets * description: Removes a task and deletes all of its assets
* parameters: * parameters:
* - * -
* name: uuid * name: uuid
* in: body * in: body
* description: UUID of the task * description: UUID of the task
* required: true * required: true
* schema: * schema:
* type: string * type: string
* responses: * responses:
* 200: * 200:
* description: Command Received * description: Command Received
* schema: * schema:
* $ref: "#/definitions/Response" * $ref: "#/definitions/Response"
*/ */
app.post('/task/remove', uuidCheck, (req, res) => { app.post('/task/remove', uuidCheck, (req, res) => {
taskManager.remove(req.body.uuid, successHandler(res)); taskManager.remove(req.body.uuid, successHandler(res));
}); });
/** @swagger /** @swagger
* /task/restart: * /task/restart:
* post: * post:
* description: Restarts a task that was previously canceled or that had failed to process * description: Restarts a task that was previously canceled or that had failed to process
* parameters: * parameters:
* - * -
* name: uuid * name: uuid
* in: body * in: body
* description: UUID of the task * description: UUID of the task
* required: true * required: true
* schema: * schema:
* type: string * type: string
* responses: * responses:
* 200: * 200:
* description: Command Received * description: Command Received
* schema: * schema:
* $ref: "#/definitions/Response" * $ref: "#/definitions/Response"
*/ */
app.post('/task/restart', uuidCheck, (req, res) => { app.post('/task/restart', uuidCheck, (req, res) => {
taskManager.restart(req.body.uuid, successHandler(res)); taskManager.restart(req.body.uuid, successHandler(res));
}); });
/** @swagger /** @swagger
* /options: * /options:
* get: * get:
* description: Retrieves the command line options that can be passed to process a task * description: Retrieves the command line options that can be passed to process a task
* tags: [server] * tags: [server]
* responses: * responses:
* 200: * 200:
* description: Options * description: Options
* schema: * schema:
* type: array * type: array
* items: * items:
* title: Option * title: Option
* type: object * type: object
* required: [name, type, value, domain, help] * required: [name, type, value, domain, help]
* properties: * properties:
* name: * name:
* type: string * type: string
* description: Command line option (exactly as it is passed to the OpenDroneMap process, minus the leading '--') * description: Command line option (exactly as it is passed to the OpenDroneMap process, minus the leading '--')
* type: * type:
* type: string * type: string
* description: Datatype of the value of this option * description: Datatype of the value of this option
* enum: * enum:
* - int * - int
* - float * - float
* - string * - string
* - bool * - bool
* value: * value:
* type: string * type: string
* description: Default value of this option * description: Default value of this option
* domain: * domain:
* type: string * type: string
* description: Valid range of values (for example, "positive integer" or "float > 0.0") * description: Valid range of values (for example, "positive integer" or "float > 0.0")
* help: * help:
* type: string * type: string
* description: Description of what this option does * description: Description of what this option does
*/ */
app.get('/options', (req, res) => { app.get('/options', (req, res) => {
odmOptions.getOptions((err, options) => { odmOptions.getOptions((err, options) => {
if (err) res.json({error: err.message}); if (err) res.json({ error: err.message });
else res.json(options); else res.json(options);
}); });
}); });
/** @swagger /** @swagger
* /info: * /info:
* get: * get:
* description: Retrieves information about this node * description: Retrieves information about this node
* tags: [server] * tags: [server]
* responses: * responses:
* 200: * 200:
* description: Info * description: Info
* schema: * schema:
* type: object * type: object
* required: [version, taskQueueCount] * required: [version, taskQueueCount]
* properties: * properties:
* version: * version:
* type: string * type: string
* description: Current version * description: Current version
* taskQueueCount: * taskQueueCount:
* type: integer * type: integer
* description: Number of tasks currently being processed or waiting to be processed * description: Number of tasks currently being processed or waiting to be processed
*/ */
app.get('/info', (req, res) => { app.get('/info', (req, res) => {
res.json({ res.json({
version: packageJson.version, version: packageJson.version,
@ -495,46 +548,47 @@ app.get('/info', (req, res) => {
}); });
let gracefulShutdown = done => { let gracefulShutdown = done => {
async.series([ async.series([
cb => taskManager.dumpTaskList(cb), cb => taskManager.dumpTaskList(cb),
cb => { cb => {
logger.info("Closing server"); logger.info("Closing server");
server.close(); server.close();
logger.info("Exiting..."); logger.info("Exiting...");
process.exit(0); process.exit(0);
} }
], done); ], done);
}; };
// listen for TERM signal .e.g. kill // listen for TERM signal .e.g. kill
process.on ('SIGTERM', gracefulShutdown); process.on('SIGTERM', gracefulShutdown);
// listen for INT signal e.g. Ctrl-C // listen for INT signal e.g. Ctrl-C
process.on ('SIGINT', gracefulShutdown); process.on('SIGINT', gracefulShutdown);
// Startup // Startup
if (config.test) logger.info("Running in test mode"); if (config.test) logger.info("Running in test mode");
let commands = [ let commands = [
cb => odmOptions.initialize(cb), cb => odmOptions.initialize(cb),
cb => { taskManager = new TaskManager(cb); }, cb => { taskManager = new TaskManager(cb); },
cb => { server = app.listen(config.port, err => { cb => {
if (!err) logger.info('Server has started on port ' + String(config.port)); server = app.listen(config.port, err => {
cb(err); if (!err) logger.info('Server has started on port ' + String(config.port));
}); cb(err);
} });
}
]; ];
if (config.powercycle){ if (config.powercycle) {
commands.push(cb => { commands.push(cb => {
logger.info("Power cycling is set, application will shut down..."); logger.info("Power cycling is set, application will shut down...");
process.exit(0); process.exit(0);
}); });
} }
async.series(commands, err => { async.series(commands, err => {
if (err){ if (err) {
logger.error("Error during startup: " + err.message); logger.error("Error during startup: " + err.message);
process.exit(1); process.exit(1);
} }
}); });

Wyświetl plik

@ -31,8 +31,10 @@
"multer": "^1.1.0", "multer": "^1.1.0",
"node-schedule": "^1.1.1", "node-schedule": "^1.1.1",
"node-uuid": "^1.4.7", "node-uuid": "^1.4.7",
"request": "^2.81.0",
"rimraf": "^2.5.3", "rimraf": "^2.5.3",
"swagger-jsdoc": "^1.3.1", "swagger-jsdoc": "^1.3.1",
"unzip": "^0.1.11",
"winston": "^2.2.0" "winston": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {

Wyświetl plik

@ -1,146 +1,164 @@
<!doctype html> <!doctype html>
<html class="no-js" lang=""> <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"> <head>
<style> <meta charset="utf-8">
body { <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
padding-top: 50px; <title>Node-OpenDroneMap</title>
padding-bottom: 20px; <meta name="description" content="">
} <meta name="viewport" content="width=device-width, initial-scale=1">
</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> <link rel="stylesheet" href="css/bootstrap.min.css">
</head> <style>
<body> body {
<!--[if lt IE 8]> 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> <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]--> <![endif]-->
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container"> <div class="container">
<div class="navbar-header"> <div class="navbar-header">
<a class="navbar-brand" href="/">Node-OpenDroneMap</a> <a class="navbar-brand" href="/">Node-OpenDroneMap</a>
<p class="navbar-text navbar-right">Open Source Drone Aerial Imagery Processing</a></p> <p class="navbar-text navbar-right">Open Source Drone Aerial Imagery Processing</a>
</p>
</div>
</div> </div>
</div>
</nav> </nav>
<div class="container"> <div class="container">
<!-- Example row of columns --> <!-- Example row of columns -->
<div class="row"> <div class="row">
<div class="col-md-5"> <div class="col-md-5">
<h2>New Task</h2> <h2>New Task</h2>
<br/> <br/>
<form enctype="multipart/form-data" onsubmit="return false;"> <form enctype="multipart/form-data" onsubmit="return false;">
<div class="form-group form-inline"> <div class="form-group form-inline">
<label for="taskName">Project Name:</lable> <input type="text" class="form-control" value="" id="taskName" /> <label for="taskName">Project Name:</lable> <input type="text" class="form-control" value="" id="taskName" />
</div> </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"> <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>
<div class="text-right"><input type="submit" class="btn btn-success" value="Start Task" id="btnUpload" /></div> <div id="zipFileInput" class="form-group hidden">
<div id="options"> <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 class="form-inline form-group form-horizontal"> <div id="errorBlock" class="help-block"></div>
<div data-bind="visible: error(), text: error()" class="alert alert-warning" role="alert"></div> </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 class="text-right"><input type="button" class="btn btn-info" value="From URL" id="btnShowImport" />
<div data-bind="visible: showOptions()"> <input type="button" class="btn btn-info hidden" value="File Upload" id="btnShowUpload" />
<div data-bind="foreach: options"> <input type="submit" class="btn btn-success" value="Start Task" id="btnUpload" />
<label data-bind="text: properties.name + (properties.domain ? ' (' + properties.domain + ')' : '')"></label><br/> <input type="submit" class="btn btn-success hidden" value="Get ZIP" id="btnImport" /></div>
<!-- ko if: properties.type !== 'bool' --> <div id="options">
<input type="text" class="form-control" data-bind="attr: {placeholder: properties.value}, value: value"> <div class="form-inline form-group form-horizontal">
<!-- /ko --> <div data-bind="visible: error(), text: error()" class="alert alert-warning" role="alert"></div>
<!-- ko if: properties.type === 'bool' --> <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 class="checkbox">
<label> <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 <input type="checkbox" data-bind="checked: value"> Enable
</label> </label>
</div> </div>
<!-- /ko --> <!-- /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-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> <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/> <br/><br/>
</div> </div>
</div>
</div>
</div>
</form>
</div> </div>
</div> <div class="col-md-7" id="taskList">
</div> <h2>Current Tasks (<span data-bind="text: tasks().length"></span>)</h2>
</form> <p data-bind="visible: tasks().length === 0">No running tasks.</p>
</div> <div data-bind="foreach: tasks">
<div class="col-md-7" id="taskList"> <div class="task" data-bind="css: {pulsePositive: info().status && info().status.code === 40, pulseNegative: info().status && info().status.code === 30}">
<h2>Current Tasks (<span data-bind="text: tasks().length"></span>)</h2> <p data-bind="visible: loading()">Retrieving <span data-bind="text: uuid"></span> ... <span class="glyphicon glyphicon-refresh spinning"></span></p>
<p data-bind="visible: tasks().length === 0">No running tasks.</p> <div data-bind="visible: !loading() && !info().error">
<div data-bind="foreach: tasks"> <div class="taskItem"><strong>UUID:</strong>
<div class="task" data-bind="css: {pulsePositive: info().status && info().status.code === 40, pulseNegative: info().status && info().status.code === 30}"> <a data-bind="text: info().uuid, attr: {href: '/task/' + info().uuid + '/info'}"></a>
<p data-bind="visible: loading()">Retrieving <span data-bind="text: uuid"></span> ... <span class="glyphicon glyphicon-refresh spinning"></span></p> </div>
<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>UUID:</strong> <a data-bind="text: info().uuid, attr: {href: '/task/' + info().uuid + '/info'}"></a></div> <div class="taskItem"><strong>Images:</strong> <span data-bind="text: info().imagesCount"></span></div>
<div class="taskItem"><strong>Name:</strong> <span data-bind="text: info().name"></span></div> <div class="taskItem"><strong>Status:</strong> <span data-bind="text: statusDescr()"></span></div>
<div class="taskItem"><strong>Images:</strong> <span data-bind="text: info().imagesCount"></span></div> <div class="taskItem"><strong>Time Elapsed:</strong> <span data-bind="text: timeElapsed()"></span></div>
<div class="taskItem"><strong>Status:</strong> <span data-bind="text: statusDescr()"></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>
<div class="taskItem"><strong>Time Elapsed:</strong> <span data-bind="text: timeElapsed()"></span></div> <textarea class="consoleOutput" data-bind="value: output().join(''), visible: viewingOutput(), event: {mouseover: consoleMouseOver, mouseout: consoleMouseOut}, attr: {id: 'console_' + uuid}"></textarea>
<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> <span data-bind="css: 'statusIcon glyphicon ' + icon()"></span>
<div class="actionButtons"> <div class="actionButtons">
<button data-bind="click: download, visible: showDownload()" type="button" class="btn btn-primary btn-sm" > <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 <span class="glyphicon glyphicon-download-alt"></span> Download All Assets
</button> </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 <span class="glyphicon glyphicon-picture"></span> Download Orthophoto
</button> </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 <span class="glyphicon glyphicon-remove-circle"></span> Cancel
</button> </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 <span class="glyphicon glyphicon-play"></span> Restart
</button> </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 <span class="glyphicon glyphicon-remove-circle"></span> Remove
</button> </button>
</div> </div>
</div> </div>
<!-- Error messages --> <!-- Error messages -->
<div data-bind="visible: info().error !== undefined"> <div data-bind="visible: info().error !== undefined">
<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-remove-circle"></span> Remove <span class="glyphicon glyphicon-remove-circle"></span> Remove
</button> </button>
</div>
</div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</div>
<hr> <hr>
<footer> <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>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> <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> </footer>
</div> <!-- /container --> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> </div>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.11.2.min.js"><\/script>')</script> <!-- /container -->
<script src="js/vendor/bootstrap.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="js/vendor/knockout-3.4.0.js"></script> <script>
<script src="js/fileinput.js" type="text/javascript"></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> <script src="js/main.js"></script>
</body> </body>
</html>
</html>

Wyświetl plik

@ -15,30 +15,30 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
$(function(){ $(function() {
function hoursMinutesSecs(t){ function hoursMinutesSecs(t) {
var ch = 60 * 60 * 1000, var ch = 60 * 60 * 1000,
cm = 60 * 1000, cm = 60 * 1000,
h = Math.floor(t / ch), h = Math.floor(t / ch),
m = Math.floor( (t - h * ch) / cm), m = Math.floor((t - h * ch) / cm),
s = Math.round( (t - h * ch - m * cm) / 1000), s = Math.round((t - h * ch - m * cm) / 1000),
pad = function(n){ return n < 10 ? '0' + n : n; }; pad = function(n) { return n < 10 ? '0' + n : n; };
if( s === 60 ){ if (s === 60) {
m++; m++;
s = 0; s = 0;
} }
if( m === 60 ){ if (m === 60) {
h++; h++;
m = 0; m = 0;
} }
return [pad(h), pad(m), pad(s)].join(':'); return [pad(h), pad(m), pad(s)].join(':');
} }
function TaskList(){ function TaskList() {
var uuids = JSON.parse(localStorage.getItem("odmTaskList") || "[]"); var uuids = JSON.parse(localStorage.getItem("odmTaskList") || "[]");
if (Object.prototype.toString.call(uuids) !== "[object Array]") uuids = []; 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); return new Task(uuid);
})); }));
} }
@ -46,14 +46,13 @@ $(function(){
this.tasks.push(task); this.tasks.push(task);
this.saveTaskListToLocalStorage(); this.saveTaskListToLocalStorage();
}; };
TaskList.prototype.saveTaskListToLocalStorage = function(){ TaskList.prototype.saveTaskListToLocalStorage = function() {
localStorage.setItem("odmTaskList", JSON.stringify($.map(this.tasks(), function(task){ localStorage.setItem("odmTaskList", JSON.stringify($.map(this.tasks(), function(task) {
return task.uuid; return task.uuid;
}) })));
));
}; };
TaskList.prototype.remove = function(task){ TaskList.prototype.remove = function(task) {
this.tasks.remove(function(t){ this.tasks.remove(function(t) {
return t === task; return t === task;
}); });
this.saveTaskListToLocalStorage(); this.saveTaskListToLocalStorage();
@ -67,7 +66,7 @@ $(function(){
CANCELED: 50 CANCELED: 50
}; };
function Task(uuid){ function Task(uuid) {
var self = this; var self = this;
this.uuid = uuid; this.uuid = uuid;
@ -101,90 +100,90 @@ $(function(){
} }
}; };
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) {
if(statusCodes[this.info().status.code]){ if (statusCodes[this.info().status.code]) {
return statusCodes[this.info().status.code].descr; return statusCodes[this.info().status.code].descr;
}else return "Unknown (Status Code: " + this.info().status.code + ")"; } else return "Unknown (Status Code: " + this.info().status.code + ")";
}else return "-"; } else return "-";
}, this); }, this);
this.icon = ko.pureComputed(function(){ this.icon = ko.pureComputed(function() {
if (this.info().status && this.info().status.code){ if (this.info().status && this.info().status.code) {
if(statusCodes[this.info().status.code]){ if (statusCodes[this.info().status.code]) {
return statusCodes[this.info().status.code].icon; return statusCodes[this.info().status.code].icon;
}else return "glyphicon-question-sign"; } else return "glyphicon-question-sign";
}else return ""; } else return "";
}, this); }, this);
this.showCancel = ko.pureComputed(function(){ this.showCancel = ko.pureComputed(function() {
return this.info().status && return this.info().status &&
(this.info().status.code === codes.QUEUED || this.info().status.code === codes.RUNNING); (this.info().status.code === codes.QUEUED || this.info().status.code === codes.RUNNING);
}, this); }, this);
this.showRestart = ko.pureComputed(function(){ this.showRestart = ko.pureComputed(function() {
return this.info().status && return this.info().status &&
(this.info().status.code === codes.CANCELED); (this.info().status.code === codes.CANCELED);
}, this); }, this);
this.showRemove = ko.pureComputed(function(){ this.showRemove = ko.pureComputed(function() {
return this.info().status && return this.info().status &&
(this.info().status.code === codes.FAILED || this.info().status.code === codes.COMPLETED || this.info().status.code === codes.CANCELED); (this.info().status.code === codes.FAILED || this.info().status.code === codes.COMPLETED || this.info().status.code === codes.CANCELED);
}, this); }, this);
this.showDownload = ko.pureComputed(function(){ this.showDownload = ko.pureComputed(function() {
return this.info().status && return this.info().status &&
(this.info().status.code === codes.COMPLETED); (this.info().status.code === codes.COMPLETED);
}, this); }, this);
this.startRefreshingInfo(); this.startRefreshingInfo();
} }
Task.prototype.refreshInfo = function(){ Task.prototype.refreshInfo = function() {
var self = this; var self = this;
var url = "/task/" + this.uuid + "/info"; var url = "/task/" + this.uuid + "/info";
$.get(url) $.get(url)
.done(function(json){ .done(function(json) {
// Track time // Track time
if (json.processingTime && json.processingTime !== -1){ if (json.processingTime && json.processingTime !== -1) {
self.timeElapsed(hoursMinutesSecs(json.processingTime)); self.timeElapsed(hoursMinutesSecs(json.processingTime));
} }
self.info(json); self.info(json);
}) })
.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.consoleMouseOver = function(){ this.autoScrollOutput = false; }; Task.prototype.consoleMouseOver = function() { this.autoScrollOutput = false; };
Task.prototype.consoleMouseOut = function(){ this.autoScrollOutput = true; }; Task.prototype.consoleMouseOut = function() { this.autoScrollOutput = true; };
Task.prototype.resetOutput = function(){ Task.prototype.resetOutput = function() {
this.viewOutputLine = 0; this.viewOutputLine = 0;
this.autoScrollOutput = true; this.autoScrollOutput = true;
this.output.removeAll(); this.output.removeAll();
}; };
Task.prototype.viewOutput = function(){ Task.prototype.viewOutput = function() {
var self = this; var self = this;
function fetchOutput(){ function fetchOutput() {
var url = "/task/" + self.uuid + "/output"; var url = "/task/" + self.uuid + "/output";
$.get(url, {line: self.viewOutputLine}) $.get(url, { line: self.viewOutputLine })
.done(function(output){ .done(function(output) {
for (var i in output){ for (var i in output) {
self.output.push(output[i]); 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());
} }
} if (output.length) {
}) self.viewOutputLine += output.length;
.fail(function(){ if (self.autoScrollOutput) {
self.info({error: url + " is unreachable."}); 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); this.fetchOutputInterval = setInterval(fetchOutput, 2000);
fetchOutput(); fetchOutput();
this.viewingOutput(true); this.viewingOutput(true);
}; };
Task.prototype.hideOutput = function(){ Task.prototype.hideOutput = function() {
if (this.fetchOutputInterval) clearInterval(this.fetchOutputInterval); if (this.fetchOutputInterval) clearInterval(this.fetchOutputInterval);
this.viewingOutput(false); this.viewingOutput(false);
}; };
@ -192,12 +191,12 @@ $(function(){
var self = this; var self = this;
this.stopRefreshingInfo(); this.stopRefreshingInfo();
this.refreshInfo(); this.refreshInfo();
this.refreshInterval = setInterval(function(){ this.refreshInterval = setInterval(function() {
self.refreshInfo(); self.refreshInfo();
}, 500); // TODO: change to larger value }, 500); // TODO: change to larger value
}; };
Task.prototype.stopRefreshingInfo = function() { Task.prototype.stopRefreshingInfo = function() {
if (this.refreshInterval){ if (this.refreshInterval) {
clearInterval(this.refreshInterval); clearInterval(this.refreshInterval);
this.refreshInterval = null; this.refreshInterval = null;
} }
@ -206,62 +205,62 @@ $(function(){
var self = this; var self = this;
var url = "/task/remove"; var url = "/task/remove";
function doRemove(){ function doRemove() {
$.post(url, { $.post(url, {
uuid: self.uuid uuid: self.uuid
}) })
.done(function(json){ .done(function(json) {
if (json.success || self.info().error){ if (json.success || self.info().error) {
taskList.remove(self); taskList.remove(self);
}else{ } else {
self.info({error: json.error}); self.info({ error: json.error });
} }
self.stopRefreshingInfo(); self.stopRefreshingInfo();
}) })
.fail(function(){ .fail(function() {
self.info({error: url + " is unreachable."}); self.info({ error: url + " is unreachable." });
self.stopRefreshingInfo(); 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(); if (confirm("Are you sure?")) doRemove();
}else{ } else {
doRemove(); doRemove();
} }
}; };
function genApiCall(url, onSuccess){ function genApiCall(url, onSuccess) {
return function(){ return function() {
var self = this; var self = this;
$.post(url, { $.post(url, {
uuid: this.uuid uuid: this.uuid
}) })
.done(function(json){ .done(function(json) {
if (json.success){ if (json.success) {
if (onSuccess !== undefined) onSuccess(self, json); if (onSuccess !== undefined) onSuccess(self, json);
self.startRefreshingInfo(); self.startRefreshingInfo();
}else{ } else {
self.stopRefreshingInfo();
self.info({ error: json.error });
}
})
.fail(function() {
self.info({ error: url + " is unreachable." });
self.stopRefreshingInfo(); 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.cancel = genApiCall("/task/cancel");
Task.prototype.restart = genApiCall("/task/restart", function(task){ Task.prototype.restart = genApiCall("/task/restart", function(task) {
task.resetOutput(); task.resetOutput();
}); });
Task.prototype.download = function(){ Task.prototype.download = function() {
location.href = "/task/" + this.uuid + "/download/all.zip"; location.href = "/task/" + this.uuid + "/download/all.zip";
}; };
Task.prototype.downloadOrthophoto = function(){ Task.prototype.downloadOrthophoto = function() {
location.href = "/task/" + this.uuid + "/download/orthophoto.tif"; location.href = "/task/" + this.uuid + "/download/orthophoto.tif";
}; };
@ -276,52 +275,75 @@ $(function(){
elErrorContainer: '#errorBlock', elErrorContainer: '#errorBlock',
showUpload: false, showUpload: false,
uploadAsync: false, uploadAsync: false,
uploadExtraData: function(){ uploadExtraData: function() {
return { return {
name: $("#taskName").val(), name: $("#taskName").val(),
zipurl: $("#zipurl").val(),
options: JSON.stringify(optionsModel.getUserOptions()) options: JSON.stringify(optionsModel.getUserOptions())
}; };
} }
}); });
$("#btnUpload").click(function(){ $("#btnUpload").click(function() {
$("#btnUpload").attr('disabled', true) $("#btnUpload").attr('disabled', true)
.val("Uploading..."); .val("Uploading...");
// Start upload // Start upload
$("#images").fileinput('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(); var btnUploadLabel = $("#btnUpload").val();
$("#images") $("#images")
.on('filebatchuploadsuccess', function(e, params){ .on('filebatchuploadsuccess', function(e, params) {
$("#images").fileinput('reset'); $("#images").fileinput('reset');
if (params.response && params.response.uuid){ if (params.response && params.response.uuid) {
taskList.add(new Task(params.response.uuid)); taskList.add(new Task(params.response.uuid));
} }
}) })
.on('filebatchuploadcomplete', function(){ .on('filebatchuploadcomplete', function() {
$("#btnUpload").removeAttr('disabled') $("#btnUpload").removeAttr('disabled')
.val(btnUploadLabel); .val(btnUploadLabel);
}) })
.on('filebatchuploaderror', console.warn); .on('filebatchuploaderror', console.warn);
// Load options // Load options
function Option(properties){ function Option(properties) {
this.properties = properties; this.properties = properties;
this.value = ko.observable(); this.value = ko.observable();
} }
Option.prototype.resetToDefault = function(){ Option.prototype.resetToDefault = function() {
this.value(undefined); this.value(undefined);
}; };
function OptionsModel(){ function OptionsModel() {
var self = this; var self = this;
this.options = ko.observableArray(); this.options = ko.observableArray();
this.options.subscribe(function(){ this.options.subscribe(function() {
setTimeout(function(){ setTimeout(function() {
$('#options [data-toggle="tooltip"]').tooltip(); $('#options [data-toggle="tooltip"]').tooltip();
}, 100); }, 100);
}); });
@ -329,23 +351,23 @@ $(function(){
this.error = ko.observable(); this.error = ko.observable();
$.get("/options") $.get("/options")
.done(function(json){ .done(function(json) {
if (json.error) self.error(json.error); if (json.error) self.error(json.error);
else{ else {
for (var i in json){ for (var i in json) {
self.options.push(new Option(json[i])); self.options.push(new Option(json[i]));
}
} }
} })
}) .fail(function() {
.fail(function(){ self.error("options are not available.");
self.error("options are not available."); });
});
} }
OptionsModel.prototype.getUserOptions = function(){ OptionsModel.prototype.getUserOptions = function() {
var result = []; 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]; var opt = this.options()[i];
if (opt.value() !== undefined){ if (opt.value() !== undefined) {
result.push({ result.push({
name: opt.properties.name, name: opt.properties.name,
value: opt.value() value: opt.value()
@ -357,4 +379,4 @@ $(function(){
var optionsModel = new OptionsModel(); var optionsModel = new OptionsModel();
ko.applyBindings(optionsModel, document.getElementById("options")); ko.applyBindings(optionsModel, document.getElementById("options"));
}); });