1
0
Fork 0

Port Aardvark to FOTF

This commit is contained in:
Alan Plum 2016-04-06 18:25:30 +02:00
parent 5a3e42e289
commit 5d765be524
No known key found for this signature in database
GPG Key ID: 8ED72A9A323B6EFD
13 changed files with 1080 additions and 1788 deletions

View File

@ -1,14 +1,10 @@
/*jshint globalstrict: true */ 'use strict';
/*global applicationContext*/
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief A Foxx.Controller to show all Foxx Applications
///
/// @file
///
/// DISCLAIMER /// DISCLAIMER
/// ///
/// Copyright 2010-2013 triagens GmbH, Cologne, Germany /// Copyright 2010-2013 triAGENS GmbH, Cologne, Germany
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
/// ///
/// Licensed under the Apache License, Version 2.0 (the "License"); /// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License. /// you may not use this file except in compliance with the License.
@ -22,150 +18,117 @@
/// See the License for the specific language governing permissions and /// See the License for the specific language governing permissions and
/// limitations under the License. /// limitations under the License.
/// ///
/// Copyright holder is triAGENS GmbH, Cologne, Germany /// Copyright holder is ArangoDB GmbH, Cologne, Germany
/// ///
/// @author Michael Hackstein /// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany /// @author Alan Plum
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
"use strict"; const underscore = require('lodash');
const cluster = require('@arangodb/cluster');
const joi = require('joi');
const httperr = require('http-errors');
const contentDisposition = require('content-disposition');
const internal = require('internal');
const db = require('@arangodb').db;
const notifications = require('@arangodb/configuration').notifications;
const createRouter = require('@arangodb/foxx/router');
const NOT_FOUND = require('@arangodb').errors.ERROR_ARANGO_DOCUMENT_NOT_FOUND.code;
var Foxx = require("@arangodb/foxx"); const router = createRouter();
var publicController = new Foxx.Controller(applicationContext); module.exports = router;
var controller = new Foxx.Controller(applicationContext);
var underscore = require("lodash");
var cluster = require("@arangodb/cluster");
var joi = require("joi");
var util = require("util");
var internal = require("internal");
var contentDisposition = require('content-disposition');
var notifications = require("@arangodb/configuration").notifications;
var db = require("@arangodb").db;
var foxxInstallKey = joi.string().required().description(
"The _key attribute, where the information of this Foxx-Install is stored."
);
var foxxes = new (require("./lib/foxxes").Foxxes)(); router.get('/whoAmI', function(req, res) {
var FoxxManager = require("@arangodb/foxx/manager"); let user = null;
var UnauthorizedError = require("http-errors").Unauthorized; if (internal.options()['server.disable-authentication']) {
user = false;
publicController.activateSessions({ } else if (req.session && req.session.uid) {
autoCreateSession: false,
cookie: {name: "arango_sid_" + db._name()}
});
publicController.get("/whoAmI", function(req, res) {
var uid = req.session && req.session.get("uid");
var user = null;
if (uid) {
var users = Foxx.getExports("_system/users").userStorage;
try { try {
user = users.get(uid).get("user"); const doc = db._users.document(req.session.uid);
user = doc.user;
} catch (e) { } catch (e) {
if (!(e instanceof users.errors.UserNotFound)) { if (!e.isArangoError || e.errorNum !== NOT_FOUND) {
throw e; throw e;
} }
req.session.setUser(null); req.session.uid = null;
} }
} else if (internal.options()["server.disable-authentication"]) {
user = false;
} }
res.json({user: user}); res.json({user});
}); });
publicController.destroySession("/logout", function (req, res) { router.post('/logout', function (req, res) {
req.session.uid = null;
res.json({success: true}); res.json({success: true});
}); });
publicController.post("/login", function (req, res) { router.post('/login', function (req, res) {
if (req.session) { if (!req.session) {
req.session.set({uid: null, userDate: null}); req.session = module.context.sessions.new();
} else {
req.session = publicController.sessions.getSessionStorage().create();
} }
var users = Foxx.getExports("_system/users").userStorage;
var credentials = req.parameters.credentials; const doc = db._users.firstExample({user: req.body.username});
var user = users.resolve(credentials.get("username")); const valid = module.context.auth.verify(
if (!user) throw new UnauthorizedError(); doc ? doc.authData.simple : null,
var auth = Foxx.getExports("_system/simple-auth").auth; req.body.password
var valid = auth.verifyPassword(user.get("authData").simple, credentials.get("password")); );
if (!valid) throw new UnauthorizedError();
req.session.setUser(user); if (!valid) {
req.session.save(); throw new httperr.Unauthorized();
res.json({ }
user: user.get("user")
}); const user = doc.user;
}).bodyParam("credentials", { req.session.uid = doc._key;
type: Foxx.Model.extend({ res.json({user});
})
.body({
username: joi.string().required(), username: joi.string().required(),
password: joi.string().required() password: joi.string().required().allow('')
}), }, 'Login credentials.');
description: "Login credentials."
router.get('/unauthorized', function() {
throw new httperr.Unauthorized();
}); });
publicController.get("/unauthorized", function() { router.get('/index.html', function(req, res) {
throw new UnauthorizedError(); res.redirect(req.makeAbsolute('standalone.html'));
}); });
publicController.get("/index.html", function(req, res) { const authRouter = createRouter();
var prefix = '/_db/' + encodeURIComponent(req.database) + applicationContext.mount; router.use(authRouter);
res.status(302); authRouter.use((req, res, next) => {
res.set("Location", prefix + "/standalone.html"); if (!internal.options()['server.disable-authentication'] && !req.user) {
}); throw new httperr.Unauthorized();
controller.activateSessions({
autoCreateSession: false,
cookie: {name: "arango_sid_" + db._name()}
});
controller.allRoutes
.errorResponse(UnauthorizedError, 401, "unauthorized")
.onlyIf(function (req, res) {
if (!internal.options()["server.disable-authentication"] && (!req.session || !req.session.get('uid'))) {
throw new UnauthorizedError();
} }
next();
}); });
controller.apiDocumentation('/api', { authRouter.get('/api/*', module.context.createSwaggerHandler(
(req) => ({
appPath: req.queryParams.mount,
swaggerJson(req, res) { swaggerJson(req, res) {
var filename = applicationContext.fileName('api-docs.json'); res.sendFile(module.context.fileName('api-docs.json'), {lastModified: true});
res.sendFile(filename, {lastModified: true});
} }
}); })
));
/** Is version check allowed authRouter.get('shouldCheckVersion', function(req, res) {
*
* Check if version check is allowed
*/
controller.get("shouldCheckVersion", function(req, res) {
var versions = notifications.versions(); var versions = notifications.versions();
res.json(Boolean(versions && versions.enableVersionNotification));
})
.summary('Is version check allowed')
.description('Check if version check is allowed.');
if (!versions || versions.enableVersionNotification === false) { authRouter.post('disableVersionCheck', function(req, res) {
res.json(false);
} else {
res.json(true);
}
});
/** Disable version check
*
* Disable the version check in web interface
*/
controller.post("disableVersionCheck", function(req, res) {
notifications.setVersions({ notifications.setVersions({
enableVersionNotification: false enableVersionNotification: false
}); });
res.json("ok"); res.json('ok');
}); })
.summary('Disable version check')
.description('Disable the version check in web interface');
/** Explains a query authRouter.post('/query/explain', function(req, res) {
*
* Explains a query in a more user-friendly way than the query
* _api/explain
*
*/
controller.post("/query/explain", function(req, res) {
var explain, query = req.body().query, var explain, query = req.body().query,
bindVars = req.body().bindVars; bindVars = req.body().bindVars;
@ -173,39 +136,35 @@ controller.post("/query/explain", function(req, res) {
if (query.length > 0) { if (query.length > 0) {
try { try {
if (bindVars) { if (bindVars) {
explain = require("@arangodb/aql/explainer").explain({ explain = require('@arangodb/aql/explainer').explain({
query: query, query: query,
bindVars: bindVars bindVars: bindVars
}, {colors: false}, false, bindVars); }, {colors: false}, false, bindVars);
} }
else { else {
explain = require("@arangodb/aql/explainer").explain(query, {colors: false}, false); explain = require('@arangodb/aql/explainer').explain(query, {colors: false}, false);
} }
} }
catch (e) { catch (e) {
explain = JSON.stringify(e); explain = JSON.stringify(e);
}  }
} }
res.json({msg: explain}); res.json({msg: explain});
}).summary("Explains a query") })
.notes("This function gives useful query information"); .summary('Explains a query')
.description('Explains a query in a more user-friendly way than the query_api/explain');
/** Download stored queries authRouter.post('/query/upload/:user', function(req, res) {
* var user = req.params('user');
* Download and export all queries from the given username. var queries, doc, queriesToSave;
*
*/
controller.post("/query/upload/:user", function(req, res) {
var user = req.params("user");
var queries, userColl, queriesToSave;
queries = req.body(); queries = req.body();
userColl = db._users.byExample({"user": user}).toArray()[0]; doc = db._users.byExample({'user': user}).toArray()[0];
queriesToSave = userColl.userData.queries || [ ]; queriesToSave = doc.userData.queries || [ ];
underscore.each(queries, function(newq) { underscore.each(queries, function(newq) {
var found = false, i; var found = false, i;
@ -216,7 +175,7 @@ controller.post("/query/upload/:user", function(req, res) {
break; break;
} }
} }
if (! found) { if (!found) {
queriesToSave.push(newq); queriesToSave.push(newq);
} }
}); });
@ -227,23 +186,18 @@ controller.post("/query/upload/:user", function(req, res) {
} }
}; };
var result = db._users.update(userColl, toUpdate, true); var result = db._users.update(doc, toUpdate, true);
res.json(result); res.json(result);
}).summary("Upload user queries") })
.notes("This function uploads all given user queries"); .summary('Upload user queries')
.description('This function uploads all given user queries');
/** Download stored queries authRouter.get('/query/download/:user', function(req, res) {
* var user = req.params('user');
* Download and export all queries from the given username. var result = db._users.byExample({'user': user}).toArray()[0];
*
*/
controller.get("/query/download/:user", function(req, res) { res.set('Content-Type', 'application/json');
var user = req.params("user"); res.set('Content-Disposition', contentDisposition('queries.json'));
var result = db._users.byExample({"user": user}).toArray()[0];
res.set("Content-Type", "application/json");
res.set("Content-Disposition", contentDisposition('queries.json'));
if (result === null || result === undefined) { if (result === null || result === undefined) {
res.json([]); res.json([]);
@ -252,20 +206,15 @@ controller.get("/query/download/:user", function(req, res) {
res.json(result.userData.queries || []); res.json(result.userData.queries || []);
} }
}).summary("Download all user queries") })
.notes("This function downloads all user queries from the given user"); .summary('Download stored queries')
.description('Download and export all queries from the given username.');
/** Download a query result authRouter.get('/query/result/download/:query', function(req, res) {
* var query = req.params('query'),
* Download and export all queries from the given username.
*
*/
controller.get("/query/result/download/:query", function(req, res) {
var query = req.params("query"),
parsedQuery; parsedQuery;
var internal = require("internal"); var internal = require('internal');
query = internal.base64Decode(query); query = internal.base64Decode(query);
try { try {
parsedQuery = JSON.parse(query); parsedQuery = JSON.parse(query);
@ -274,31 +223,26 @@ controller.get("/query/result/download/:query", function(req, res) {
} }
var result = db._query(parsedQuery.query, parsedQuery.bindVars).toArray(); var result = db._query(parsedQuery.query, parsedQuery.bindVars).toArray();
res.set("Content-Type", "application/json"); res.set('Content-Type', 'application/json');
res.set("Content-Disposition", contentDisposition('results.json')); res.set('Content-Disposition', contentDisposition('results.json'));
res.json(result); res.json(result);
}).summary("Download the result of a query") })
.notes("This function downloads the result of a user query."); .summary('Download the result of a query')
.description('This function downloads the result of a user query.');
/** Create sample graphs authRouter.post('/graph-examples/create/:name', function(req, res) {
* var name = req.params('name'), g,
* Create one of the given sample graphs. examples = require('@arangodb/graph-examples/example-graph.js');
*
*/
controller.post("/graph-examples/create/:name", function(req, res) {
var name = req.params("name"), g,
examples = require("@arangodb/graph-examples/example-graph.js");
if (name === 'knows_graph') { if (name === 'knows_graph') {
g = examples.loadGraph("knows_graph"); g = examples.loadGraph('knows_graph');
} }
else if (name === 'social') { else if (name === 'social') {
g = examples.loadGraph("social"); g = examples.loadGraph('social');
} }
else if (name === 'routeplanner') { else if (name === 'routeplanner') {
g = examples.loadGraph("routeplanner"); g = examples.loadGraph('routeplanner');
} }
if (typeof g === 'object') { if (typeof g === 'object') {
@ -308,16 +252,11 @@ controller.post("/graph-examples/create/:name", function(req, res) {
res.json({error: true}); res.json({error: true});
} }
}).summary("Create a sample graph") })
.notes("This function executes the internal scripts to create one example graph."); .summary('Create sample graphs')
.description('Create one of the given sample graphs.');
/** Store job id's in db authRouter.post('/job', function(req, res) {
*
* Create a new job id entry in a specific system database with a given id.
*
*/
controller.post("/job", function(req, res) {
if (req.body().id && req.body().collection && req.body().type && req.body().desc) { if (req.body().id && req.body().collection && req.body().type && req.body().desc) {
@ -336,34 +275,22 @@ controller.post("/job", function(req, res) {
res.json(false); res.json(false);
} }
}).summary("Store job id of a running job") })
.notes("This function stores a job id into a system collection."); .summary('Store job id of a running job')
.description('Create a new job id entry in a specific system database with a given id.');
/** Delete all jobs
*
* Delete an existing job id entry in a specific system database with a given id.
*
*/
controller.del("/job/", function(req, res) {
authRouter.delete('/job/', function(req, res) {
db._frontend.removeByExample({ db._frontend.removeByExample({
model: 'job' model: 'job'
}, true); }, true);
res.json(true); res.json(true);
})
.summary('Delete all jobs')
.description('Delete all jobs in a specific system database with a given id.');
}).summary("Store job id of a running job") authRouter.delete('/job/:id', function(req, res) {
.notes("This function stores a job id into a system collection.");
/** Delete a job id var id = req.params('id');
*
* Delete an existing job id entry in a specific system database with a given id.
*
*/
controller.del("/job/:id", function(req, res) {
var id = req.params("id");
if (id) { if (id) {
db._frontend.removeByExample({ db._frontend.removeByExample({
@ -375,27 +302,15 @@ controller.del("/job/:id", function(req, res) {
res.json(false); res.json(false);
} }
}).summary("Store job id of a running job") })
.notes("This function stores a job id into a system collection."); .summary('Delete a job id')
.description('Delete an existing job id entry in a specific system database with a given id.');
/** Return all job id's authRouter.get('/job', function(req, res) {
*
* Return all job id's which are stored in a system database.
*
*/
controller.get("/job", function(req, res) {
var result = db._frontend.all().toArray(); var result = db._frontend.all().toArray();
res.json(result); res.json(result);
}).summary("Return all job ids.") })
.notes("This function returns the job ids of all currently running jobs."); .summary('Return all job ids.')
// ----------------------------------------------------------------------------- .description('This function returns the job ids of all currently running jobs.');
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
/// Local Variables:
/// mode: outline-minor
/// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint"
/// End:

View File

@ -1,13 +1,10 @@
/*global applicationContext*/ 'use strict';
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief A Foxx.Controller to show all Foxx Applications
///
/// @file
///
/// DISCLAIMER /// DISCLAIMER
/// ///
/// Copyright 2010-2013 triagens GmbH, Cologne, Germany /// Copyright 2010-2013 triAGENS GmbH, Cologne, Germany
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
/// ///
/// Licensed under the Apache License, Version 2.0 (the "License"); /// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License. /// you may not use this file except in compliance with the License.
@ -21,80 +18,62 @@
/// See the License for the specific language governing permissions and /// See the License for the specific language governing permissions and
/// limitations under the License. /// limitations under the License.
/// ///
/// Copyright holder is triAGENS GmbH, Cologne, Germany /// Copyright holder is ArangoDB GmbH, Cologne, Germany
/// ///
/// @author Michael Hackstein /// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany /// @author Alan Plum
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
(function() { const _ = require('lodash');
"use strict"; const dd = require('dedent');
const httperr = require('http-errors');
const cluster = require('@arangodb/cluster');
const createRouter = require('@arangodb/foxx/router');
const internal = require('internal');
const plans = require('./repositories/plans.js');
// Initialize a new FoxxController called controller under the urlPrefix: "cluster". const router = createRouter();
var FoxxController = require("@arangodb/foxx").Controller, module.exports = router;
UnauthorizedError = require("http-errors").Unauthorized,
internal = require("internal"),
controller = new FoxxController(applicationContext),
cluster = require("@arangodb/cluster"),
load = require("internal").download,
db = require("internal").db,
_ = require("lodash");
controller.activateSessions({ router.use((req, res, next) => {
autoCreateSession: false, if (!internal.options()['server.disable-authentication'] && !req.user) {
cookie: {name: "arango_sid_" + db._name()} throw new httperr.Unauthorized();
});
controller.allRoutes
.errorResponse(UnauthorizedError, 401, "unauthorized")
.onlyIf(function (req, res) {
if (!internal.options()["server.disable-authentication"] && (!req.session || !req.session.get('uid'))) {
throw new UnauthorizedError();
} }
}); next();
});
/** Plan and start a new cluster router.get('/amICoordinator', function(req, res) {
*
* This will plan a new cluster with the information
* given in the body
*/
controller.get("/amICoordinator", function(req, res) {
res.json(cluster.isCoordinator()); res.json(cluster.isCoordinator());
}); })
.summary('Plan and start a new cluster')
.description('This will plan a new cluster with the information given in the body');
if (cluster.isCluster()) { if (cluster.isCluster()) {
// only make these functions available in cluster mode! // only make these functions available in cluster mode!
var Communication = require("@arangodb/cluster/agency-communication"), var Communication = require("@arangodb/cluster/agency-communication");
comm = new Communication.Communication(), var comm = new Communication.Communication();
beats = comm.sync.Heartbeats(), var beats = comm.sync.Heartbeats();
diff = comm.diff.current, var diff = comm.diff.current;
servers = comm.current.DBServers(), var servers = comm.current.DBServers();
dbs = comm.current.Databases(), var dbs = comm.current.Databases();
coords = comm.current.Coordinators(); var coords = comm.current.Coordinators();
router.get("/ClusterType", function(req, res) {
/** Get the type of the cluster
*
* Returns a string containing the cluster type
* Possible anwers:
* - testSetup
* - symmetricalSetup
* - asymmetricalSetup
*
*/
controller.get("/ClusterType", function(req, res) {
// Not yet implemented // Not yet implemented
res.json({ res.json({
type: "symmetricSetup" type: "symmetricSetup"
}); });
}); })
.summary('Get the type of the cluster')
.description(dd`
Returns a string containing the cluster type
Possible anwers:
- testSetup
- symmetricalSetup
- asymmetricalSetup
`);
/** Get all DBServers router.get("/DBServers", function(req, res) {
*
* Get a list of all running and expected DBServers
* within the cluster
*/
controller.get("/DBServers", function(req, res) {
var resList = [], var resList = [],
list = servers.getList(), list = servers.getList(),
diffList = diff.DBServers(), diffList = diff.DBServers(),
@ -119,9 +98,11 @@
resList.push(v); resList.push(v);
}); });
res.json(resList); res.json(resList);
}); })
.summary('Get all DBServers')
.description('Get a list of all running and expected DBServers within the cluster');
controller.get("/Coordinators", function(req, res) { router.get("/Coordinators", function(req, res) {
var resList = [], var resList = [],
list = coords.getList(), list = coords.getList(),
diffList = diff.Coordinators(), diffList = diff.Coordinators(),
@ -143,46 +124,39 @@
res.json(resList); res.json(resList);
}); });
controller.get("/Databases", function(req, res) { router.get("/Databases", function(req, res) {
var list = dbs.getList(); var list = dbs.getList();
res.json(_.map(list, function(d) { res.json(_.map(list, (name) => ({name})));
return {name: d};
}));
}); });
controller.get("/:dbname/Collections", function(req, res) { router.get("/:dbname/Collections", function(req, res) {
var dbname = req.params("dbname"), var dbname = req.params("dbname"),
selected = dbs.select(dbname); selected = dbs.select(dbname);
try { try {
res.json(_.map(selected.getCollections(), res.json(_.map(
function(c) { selected.getCollections(),
return {name: c}; (name) => ({name})
}) ));
);
} catch(e) { } catch(e) {
res.json([]); res.json([]);
} }
}); });
controller.get("/:dbname/:colname/Shards", function(req, res) { router.get("/:dbname/:colname/Shards", function(req, res) {
var dbname = req.params("dbname"), var dbname = req.params("dbname");
colname = req.params("colname"), var colname = req.params("colname");
selected = dbs.select(dbname).collection(colname); var selected = dbs.select(dbname).collection(colname);
res.json(selected.getShardsByServers()); res.json(selected.getShardsByServers());
}); });
controller.get("/:dbname/:colname/Shards/:servername", function(req, res) { router.get("/:dbname/:colname/Shards/:servername", function(req, res) {
var dbname = req.params("dbname"), var dbname = req.params("dbname");
colname = req.params("colname"), var colname = req.params("colname");
servername = req.params("servername"), var servername = req.params("servername");
selected = dbs.select(dbname).collection(colname); var selected = dbs.select(dbname).collection(colname);
res.json(_.map(selected.getShardsForServer(servername), res.json(_.map(
function(c) { selected.getShardsForServer(servername),
return {id: c}; (c) => ({id: c})
}) ));
);
}); });
}
} // end isCluster()
}());

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,13 +1,10 @@
/*global applicationContext*/ 'use strict';
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief A Foxx.Controller to generate new FoxxApps
///
/// @file
///
/// DISCLAIMER /// DISCLAIMER
/// ///
/// Copyright 2010-2014 triagens GmbH, Cologne, Germany /// Copyright 2010-2014 triAGENS GmbH, Cologne, Germany
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
/// ///
/// Licensed under the Apache License, Version 2.0 (the "License"); /// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License. /// you may not use this file except in compliance with the License.
@ -21,44 +18,41 @@
/// See the License for the specific language governing permissions and /// See the License for the specific language governing permissions and
/// limitations under the License. /// limitations under the License.
/// ///
/// Copyright holder is triAGENS GmbH, Cologne, Germany /// Copyright holder is ArangoDB GmbH, Cologne, Germany
/// ///
/// @author Michael Hackstein /// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany /// @author Alan Plum
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
(function() {
"use strict";
var FoxxController = require("@arangodb/foxx").Controller, const joi = require('joi');
UnauthorizedError = require("http-errors").Unauthorized, const httperr = require('http-errors');
internal = require("internal"), const internal = require('internal');
Configuration = require("./models/configuration").Model, const FoxxManager = require('@arangodb/foxx/manager');
controller = new FoxxController(applicationContext), const createRouter = require('@arangodb/foxx/router');
db = require("internal").db,
FoxxManager = require("@arangodb/foxx/manager");
controller.activateSessions({
autoCreateSession: false,
cookie: {name: "arango_sid_" + db._name()}
});
controller.allRoutes const router = createRouter();
.errorResponse(UnauthorizedError, 401, "unauthorized") module.exports = router;
.onlyIf(function (req, res) {
if (!internal.options()["server.disable-authentication"] && (!req.session || !req.session.get('uid'))) { router.use((req, res, next) => {
throw new UnauthorizedError(); if (!internal.options()['server.disable-authentication'] && !req.user) {
throw new httperr.Unauthorized();
} }
}); next();
});
controller.get("/devMode", function(req, res) { router.get('/devMode', (req, res) => res.json(false));
res.json(false);
});
controller.post("/generate", function(req, res) { router.post('/generate', (req, res) => res.json(
var conf = req.params("configuration"); FoxxManager.install('EMPTY', '/todo', req.body)
res.json(FoxxManager.install("EMPTY", "/todo", conf)); ))
}).bodyParam("configuration", { .body(joi.object({
description: "The configuration for the template.", applicationContext: joi.string().optional(),
type: Configuration path: joi.string().optional(),
}); name: joi.string().required(),
}()); collectionNames: joi.array().required(),
authenticated: joi.boolean().required(),
author: joi.string().required(),
description: joi.string().required(),
license: joi.string().required()
}), 'The configuration for the template.');

View File

@ -1,14 +1,10 @@
/*global applicationContext */ 'use strict';
"use strict";
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief A Foxx.Controller to show all Foxx Applications
///
/// @file
///
/// DISCLAIMER /// DISCLAIMER
/// ///
/// Copyright 2010-2013 triagens GmbH, Cologne, Germany /// Copyright 2010-2013 triAGENS GmbH, Cologne, Germany
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
/// ///
/// Licensed under the Apache License, Version 2.0 (the "License"); /// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License. /// you may not use this file except in compliance with the License.
@ -22,407 +18,255 @@
/// See the License for the specific language governing permissions and /// See the License for the specific language governing permissions and
/// limitations under the License. /// limitations under the License.
/// ///
/// Copyright holder is triAGENS GmbH, Cologne, Germany /// Copyright holder is ArangoDB GmbH, Cologne, Germany
/// ///
/// @author Michael Hackstein /// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany /// @author Alan Plum
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
(function() {
var internal = require("internal"); const fs = require('fs');
var db = require("@arangodb").db; const joi = require('joi');
var NotFound = require("http-errors").NotFound; const marked = require('marked');
var FoxxController = require("@arangodb/foxx").Controller; const httperr = require('http-errors');
var UnauthorizedError = require("http-errors").Unauthorized; const internal = require('internal');
var controller = new FoxxController(applicationContext); const highlightAuto = require('highlight.js').highlightAuto;
var ArangoError = require("@arangodb").ArangoError; const FoxxManager = require('@arangodb/foxx/manager');
var FoxxManager = require("@arangodb/foxx/manager"); const fmUtils = require('@arangodb/foxx/manager-utils');
var fmUtils = require("@arangodb/foxx/manager-utils"); const createRouter = require('@arangodb/foxx/router');
var actions = require("@arangodb/actions");
var joi = require("joi");
var marked = require("marked");
var highlightAuto = require("highlight.js").highlightAuto;
var docu = require("./lib/swagger").Swagger;
var underscore = require("lodash");
var contentDisposition = require('content-disposition');
var mountPoint = {
type: joi.string().required().description(
"The mount point of the app. Has to be url-encoded."
)
};
var scriptName = {
type: joi.string().required().description(
"The name of an app's script to run."
)
};
var fs = require("fs");
var defaultThumb = require("./lib/defaultThumbnail").defaultThumb;
controller.activateSessions({ const DEFAULT_THUMBNAIL = module.context.fileName('default-thumbnail.png');
autoCreateSession: false,
cookie: {name: "arango_sid_" + db._name()}
});
controller.allRoutes const mountSchema = joi.string().required().description(
.errorResponse(UnauthorizedError, 401, "unauthorized") 'The mount point of the service. Has to be url-encoded.'
.onlyIf(function (req, res) { );
if (!internal.options()["server.disable-authentication"] && (!req.session || !req.session.get('uid'))) {
throw new UnauthorizedError(); const router = createRouter();
module.exports = router;
router.use((req, res, next) => {
if (!internal.options()['server.disable-authentication'] && !req.user) {
throw new httperr.Unauthorized();
} }
}); next();
});
controller.extend({ function installApp(req, res, appInfo, options) {
installer: function() { var mount = decodeURIComponent(req.params('mount'));
this.queryParam("mount", mountPoint); var upgrade = req.params('upgrade');
this.queryParam("upgrade", {type: joi.boolean().optional().description( var replace = req.params('replace');
"Trigger to upgrade the app installed at the mountpoint. Triggers setup." var service;
)});
this.queryParam("replace", {type: joi.boolean().optional().description(
"Trigger to replace the app installed at the mountpoint. Triggers teardown and setup."
)});
}
});
// ------------------------------------------------------------
// SECTION install
// ------------------------------------------------------------
var validateMount = function(req) {
var mount = req.params("mount");
// Validation
mount = decodeURIComponent(mount);
return mount;
};
var installApp = function(req, res, appInfo, options) {
var mount = validateMount(req);
var upgrade = req.params("upgrade") || false;
var replace = req.params("replace") || false;
var app;
if (upgrade) { if (upgrade) {
app = FoxxManager.upgrade(appInfo, mount, options); service = FoxxManager.upgrade(appInfo, mount, options);
} else if (replace) { } else if (replace) {
app = FoxxManager.replace(appInfo, mount, options); service = FoxxManager.replace(appInfo, mount, options);
} else { } else {
app = FoxxManager.install(appInfo, mount, options); service = FoxxManager.install(appInfo, mount, options);
} }
var config = FoxxManager.configuration(mount); var config = FoxxManager.configuration(mount);
res.json({ res.json({
error: false, error: false,
configuration: config, configuration: config,
name: app.name, name: service.name,
version: app.version version: service.version
}); });
}; }
/** Install a Foxx from the store const installer = createRouter();
* router.use(installer)
* Downloads a Foxx from the store and installs it at the given mount .queryParam('mount', mountSchema)
*/ .queryParam('upgrade', joi.boolean().default(false).description(
controller.put("/store", function(req, res) { 'Trigger to upgrade the service installed at the mountpoint. Triggers setup.'
var content = JSON.parse(req.requestBody), ))
name = content.name, .queryParam('replace', joi.boolean().default(false).description(
version = content.version; 'Trigger to replace the service installed at the mountpoint. Triggers teardown and setup.'
installApp(req, res, name + ":" + version); ));
})
.installer();
/** Install a Foxx from Github installer.put('/store', function (req, res) {
* const content = JSON.parse(req.requestBody);
* Install a Foxx with user/repository and version const name = content.name;
*/ const version = content.version;
controller.put("/git", function (req, res) { installApp(req, res, `${name}:${version}`);
var content = JSON.parse(req.requestBody), })
url = content.url, .summary('Install a Foxx from the store')
version = content.version; .description('Downloads a Foxx from the store and installs it at the given mount.');
installApp(req, res, "git:" + url + ":" + (version || "master"));
})
.installer();
/** Generate a new foxx installer.put('/git', function (req, res) {
* const content = JSON.parse(req.requestBody);
* Generate a new empty foxx on the given mount point const url = content.url;
*/ const version = content.version || 'master';
controller.put("/generate", function (req, res) { installApp(req, res, `git:${url}:${version}`);
var info = JSON.parse(req.requestBody); })
installApp(req, res, "EMPTY", info); .summary('Install a Foxx from Github')
}) .description('Install a Foxx with user/repository and version.');
.installer();
/** Install a Foxx from temporary zip file installer.put('/generate', function (req, res) {
* const info = JSON.parse(req.requestBody);
* Install a Foxx from the given zip path. installApp(req, res, 'EMPTY', info);
* This path has to be created via _api/upload })
*/ .summary('Generate a new foxx')
controller.put("/zip", function (req, res) { .description('Generate a new empty foxx on the given mount point');
var content = JSON.parse(req.requestBody),
file = content.zipFile; installer.put('/zip', function (req, res) {
const content = JSON.parse(req.requestBody);
const file = content.zipFile;
installApp(req, res, file); installApp(req, res, file);
}) })
.installer(); .summary('Install a Foxx from temporary zip file')
.description('Install a Foxx from the given zip path. This path has to be created via _api/upload');
router.delete('/', function (req, res) {
/** Uninstall a Foxx var mount = decodeURIComponent(req.params('mount'));
*
* Uninstall the Foxx at the given mount-point.
*/
controller.delete("/", function (req, res) {
var mount = validateMount(req);
var runTeardown = req.parameters.teardown; var runTeardown = req.parameters.teardown;
var app = FoxxManager.uninstall(mount, { var service = FoxxManager.uninstall(mount, {
teardown: runTeardown, teardown: runTeardown,
force: true force: true
}); });
res.json({ res.json({
error: false, error: false,
name: app.name, name: service.name,
version: app.version version: service.version
}); });
}) })
.queryParam("mount", mountPoint) .queryParam('mount', mountSchema)
.queryParam("teardown", joi.boolean().default(true)); .queryParam('teardown', joi.boolean().default(true))
.summary('Uninstall a Foxx')
.description('Uninstall the Foxx at the given mount-point.');
// ------------------------------------------------------------ router.get('/', function (req, res) {
// SECTION information const foxxes = FoxxManager.listJson();
// ------------------------------------------------------------ foxxes.forEach((foxx) => {
const readme = FoxxManager.readme(foxx.mount);
/** List all Foxxes
*
* Get a List of all running foxxes
*/
controller.get('/', function (req, res) {
var foxxes = FoxxManager.listJson();
foxxes.forEach(function (foxx) {
var readme = FoxxManager.readme(foxx.mount);
if (readme) { if (readme) {
foxx.readme = marked(readme, { foxx.readme = marked(readme, {
highlight(code) { highlight: (code) => highlightAuto(code).value
return highlightAuto(code).value;
}
}); });
} }
}); });
res.json(foxxes); res.json(foxxes);
}); })
.summary('List all Foxxes')
.description('Get a List of all running foxxes.');
/** Get the thumbnail of a Foxx router.get('/thumbnail', function (req, res) {
* var mount = decodeURIComponent(req.params('mount'));
* Used to request the thumbnail of the given Foxx in order to display it on the screen. var service = FoxxManager.lookupApp(mount);
*/ res.sendFile(service.thumbnail || DEFAULT_THUMBNAIL);
controller.get("/thumbnail", function (req, res) { })
res.transformations = [ "base64decode" ]; .queryParam('mount', mountSchema)
var mount = validateMount(req); .summary('Get the thumbnail of a Foxx')
var app = FoxxManager.lookupApp(mount); .description('Request the thumbnail of the given Foxx in order to display it on the screen.');
if (app.hasOwnProperty("thumbnail") && app.thumbnail !== null) {
res.body = app.thumbnail;
} else {
res.body = defaultThumb;
}
// evil mimetype detection attempt... router.get('/config', function (req, res) {
var start = require("internal").base64Decode(res.body.substr(0, 8)); var mount = decodeURIComponent(req.params('mount'));
if (start.indexOf("PNG") !== -1) {
res.contentType = "image/png";
}
})
.queryParam("mount", mountPoint);
/** Get the configuration for an app
*
* Used to request the configuration options for apps
*/
controller.get("/config", function(req, res) {
var mount = validateMount(req);
res.json(FoxxManager.configuration(mount)); res.json(FoxxManager.configuration(mount));
}) })
.queryParam("mount", mountPoint); .queryParam('mount', mountSchema)
.summary('Get the configuration for a service')
.description('Used to request the configuration options for services');
/** Set the configuration for an app router.patch('/config', function (req, res) {
* var mount = decodeURIComponent(req.params('mount'));
* Used to overwrite the configuration options for apps var data = req.body;
*/
controller.patch("/config", function(req, res) {
var mount = validateMount(req);
var data = req.body();
res.json(FoxxManager.configure(mount, {configuration: data})); res.json(FoxxManager.configure(mount, {configuration: data}));
}) })
.queryParam("mount", mountPoint); .queryParam('mount', mountSchema)
.summary('Set the configuration for a service')
.description('Used to overwrite the configuration options for services');
/** Get the dependencies for an app router.get('/deps', function (req, res) {
* var mount = decodeURIComponent(req.params('mount'));
* Used to request the dependencies options for apps
*/
controller.get("/deps", function(req, res) {
var mount = validateMount(req);
res.json(FoxxManager.dependencies(mount)); res.json(FoxxManager.dependencies(mount));
}) })
.queryParam("mount", mountPoint); .queryParam('mount', mountSchema)
.summary('Get the dependencies for a service')
.description('Used to request the dependencies options for services');
/** Set the dependencies for an app router.patch('/deps', function (req, res) {
* var mount = decodeURIComponent(req.params('mount'));
* Used to overwrite the dependencies options for apps var data = req.body;
*/
controller.patch("/deps", function(req, res) {
var mount = validateMount(req);
var data = req.body();
res.json(FoxxManager.updateDeps(mount, {dependencies: data})); res.json(FoxxManager.updateDeps(mount, {dependencies: data}));
}) })
.queryParam("mount", mountPoint); .queryParam('mount', mountSchema)
.summary('Set the dependencies for a service')
.description('Used to overwrite the dependencies options for services');
/** Run tests for an app router.post('/tests', function (req, res) {
* var options = req.body;
* Used to run the tests of an app var mount = decodeURIComponent(req.params('mount'));
*/
controller.post("/tests", function (req, res) {
var options = req.body();
var mount = validateMount(req);
res.json(FoxxManager.runTests(mount, options)); res.json(FoxxManager.runTests(mount, options));
}) })
.queryParam("mount", mountPoint); .queryParam('mount', mountSchema)
.summary('Run tests for a service')
.description('Used to run the tests of a service');
/** Run a script for an app router.post('/scripts/:name', function (req, res) {
* const mount = decodeURIComponent(req.params('mount'));
* Used to trigger any script of an app const name = req.params('name');
*/ const argv = req.body;
controller.post("/scripts/:name", function (req, res) {
var mount = validateMount(req);
var name = req.params("name");
var argv = req.params("argv");
try { try {
res.json(FoxxManager.runScript(name, mount, argv)); res.json(FoxxManager.runScript(name, mount, argv));
} catch (e) { } catch (e) {
throw e.cause || e; throw e.cause || e;
} }
}) })
.queryParam("mount", mountPoint) .queryParam('mount', mountSchema)
.pathParam("name", scriptName) .pathParam('name', joi.string().required(), 'The name of a service\'s script to run.')
.bodyParam( .body('argv', joi.any().default(null), 'Options to pass to the script.')
"argv", .summary('Run a script for a service')
joi.any().default(null) .description('Used to trigger any script of a service');
.description('Options to pass to the script.')
);
/** Trigger setup script for an app router.patch('/setup', function (req, res) {
* const mount = decodeURIComponent(req.params('mount'));
* Used to trigger the setup script of an app res.json(FoxxManager.runScript('setup', mount));
*/ })
controller.patch("/setup", function(req, res) { .queryParam('mount', mountSchema)
var mount = validateMount(req); .summary('Trigger setup script for a service')
res.json(FoxxManager.runScript("setup", mount)); .description('Used to trigger the setup script of a service');
})
.queryParam("mount", mountPoint);
router.patch('/teardown', function (req, res) {
const mount = decodeURIComponent(req.params('mount'));
res.json(FoxxManager.runScript('teardown', mount));
})
.queryParam('mount', mountSchema)
.summary('Trigger teardown script for a service')
.description('Used to trigger the teardown script of a service');
/** Trigger teardown script for an app router.patch('/devel', function (req, res) {
* const mount = decodeURIComponent(req.params('mount'));
* Used to trigger the teardown script of an app const activate = Boolean(req.body);
*/ res.json(FoxxManager[activate ? 'development' : 'production'](mount));
controller.patch("/teardown", function(req, res) { })
var mount = validateMount(req); .queryParam('mount', mountSchema)
res.json(FoxxManager.runScript("teardown", mount)); .summary('Activate/Deactivate development mode for a service')
}) .description('Used to toggle between production and development mode');
.queryParam("mount", mountPoint);
router.get('/download/zip', function (req, res) {
/** Activate/Deactivate development mode for an app var mount = decodeURIComponent(req.params('mount'));
* var service = FoxxManager.lookupApp(mount);
* Used to toggle between production and development mode var dir = fs.join(fs.makeAbsolute(service.root), service.path);
*/
controller.patch("/devel", function(req, res) {
var mount = validateMount(req);
var activate = Boolean(req.body());
if (activate) {
res.json(FoxxManager.development(mount));
} else {
res.json(FoxxManager.production(mount));
}
})
.queryParam("mount", mountPoint);
/** Download an app as zip archive
*
* Download a foxx app packed in a zip archive
*/
controller.get("/download/zip", function(req, res) {
var mount = validateMount(req);
var app = FoxxManager.lookupApp(mount);
var dir = fs.join(fs.makeAbsolute(app.root), app.path);
var zipPath = fmUtils.zipDirectory(dir); var zipPath = fmUtils.zipDirectory(dir);
res.set("Content-Type", "application/octet-stream"); res.download(zipPath, `${service.name}@${service.version}.zip`);
res.set("Content-Disposition", contentDisposition(`${app.name}@${app.version}.zip`)); })
res.body = fs.readFileSync(zipPath); .queryParam('mount', mountSchema)
}) .summary('Download a service as zip archive')
.queryParam("mount", mountPoint); .description('Download a foxx service packed in a zip archive');
router.get('/fishbowl', function (req, res) {
// ------------------------------------------------------------
// SECTION store
// ------------------------------------------------------------
/** List all Foxxes in Fishbowl
*
* Get the information for all Apps availbale in the Fishbowl and ready for download
*
*/
controller.get('/fishbowl', function (req, res) {
FoxxManager.update(); FoxxManager.update();
res.json(FoxxManager.availableJson()); res.json(FoxxManager.availableJson());
}).summary("List of all foxx apps submitted to the fishbowl store.") })
.notes("This function contacts the fishbowl and reports which apps are available for install") .summary('List of all foxx services submitted to the Foxx store.')
.errorResponse(ArangoError, 503, "Could not connect to store."); .description('This function contacts the fishbowl and reports which services are available for install');
router.get('/docs/standalone/*', module.context.createSwaggerHandler(
(req) => ({
// ------------------------------------------------------------ appPath: req.queryParams.mount
// SECTION documentation
// ------------------------------------------------------------
controller.apiDocumentation('/docs/standalone', function (req, res) {
return {
appPath: req.parameters.mount
};
});
controller.apiDocumentation('/docs', function (req, res) {
return {
indexFile: 'index-alt.html',
appPath: req.parameters.mount
};
});
/** Returns the billboard URL for swagger
*
* Returns the billboard URL for the application mounted
* at the given mountpoint
*/
controller.get('/billboard', function(req, res) {
var mount = decodeURIComponent(decodeURIComponent(req.params("mount")));
var path = req.protocol + "://" + req.headers.host +
"/_db/" + encodeURIComponent(req.database) +
"/_admin/aardvark/foxxes/docu";
res.json({
swaggerVersion: "1.1",
basePath: path,
apis: [
{path: mount}
]
});
}) })
.queryParam("mount", mountPoint); ));
/** Returns the generated Swagger JSON description for one foxx router.get('/docs/*', module.context.createSwaggerHandler(
* (req) => ({
* This function returns the Swagger JSON API description of the foxx indexFile: 'index-alt.html',
* installed under the given mount point. appPath: req.queryParams.mount
*/ })
controller.get('/docu/*', function(req, res) { ));
var mount = "";
underscore.each(req.suffix, function(part) {
mount += "/" + part;
});
res.json(docu(mount));
});
}());

View File

@ -0,0 +1,45 @@
'use strict';
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Alan Plum
////////////////////////////////////////////////////////////////////////////////
const db = require('@arangodb').db;
const sessionsMiddleware = require('@arangodb/foxx/sessions');
const cookieTransport = require('@arangodb/foxx/sessions/transports/cookie');
const collectionStorage = require('@arangodb/foxx/sessions/storages/collection');
const auth = require('@arangodb/foxx/auth');
module.context.sessions = collectionStorage('_sessions');
module.context.users = db._collection('_users');
module.context.auth = auth('sha256');
module.context.use(sessionsMiddleware({
autoCreate: false,
transport: cookieTransport(`arango_sid_${db._name()}`),
storage: module.context.sessions
}));
module.context.use(require('./aardvark'));
module.context.use('/foxxes', require('./foxxes'));
module.context.use('/cluster', require('./cluster'));
module.context.use('/statistics', require('./statistics'));
module.context.use('/templates', require('./foxxTemplates'));

File diff suppressed because one or more lines are too long

View File

@ -1,247 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
/// @brief A TODO-List Foxx-Application written for ArangoDB
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2013 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
exports.Foxxes = function () {
"use strict";
// Define the Repository
var aal = require("internal").db._collection("_tmp_aal"),
foxxmanager = require("@arangodb/foxx/manager"),
_ = require("lodash"),
ArangoError = require("@arangodb").ArangoError,
fs = require("fs");
// Define the functionality to create a new foxx
this.store = function (content) {
throw {
code: 501,
message: "To be implemented."
};
};
this.thumbnail = function (mount) {
var example = aal.firstExample({
mount: mount
});
if (example === undefined || example === null) {
return defaultThumb;
}
var thumb = example.thumbnail;
if (thumb === undefined || thumb === null) {
thumb = defaultThumb;
}
return thumb;
};
this.install = function (name, mount, version, options) {
if (version) {
name = "app:" + name + ":" + version;
}
return foxxmanager.mount(name, mount, _.extend({}, options, { setup: true }));
};
// Define the functionality to uninstall an installed foxx
this.uninstall = function (key) {
// key is mountpoint
foxxmanager.teardown(key);
return foxxmanager.unmount(key);
};
// Define the functionality to deactivate an installed foxx.
this.deactivate = function (key) {
throw {
code: 501,
message: "To be implemented."
};
};
// Define the functionality to activate an installed foxx.
this.activate = function (key) {
throw {
code: 501,
message: "To be implemented."
};
};
// Define the functionality to display all foxxes
this.viewAll = function () {
var result = aal.toArray().concat(foxxmanager.developmentMounts());
result.forEach(function(r, i) {
// inject development flag
if (r._key.match(/^dev:/)) {
result[i] = _.extend({}, r);
result[i].development = true;
}
});
return result;
};
// Define the functionality to update one foxx.
this.update = function (id, content) {
throw {
code: 501,
message: "To be implemented."
};
};
this.move = function(key, app, mount, prefix) {
var success;
try {
success = foxxmanager.mount(app, mount, {collectionPrefix: prefix});
foxxmanager.unmount(key);
} catch (e) {
return {
error: true,
status: 409,
message: "Mount-Point already in use"
};
}
return success;
};
// TODO: merge with functionality js/client/modules/@arangodb/foxx/manager.js
this.repackZipFile = function (path) {
if (! fs.exists(path) || ! fs.isDirectory(path)) {
throw "'" + String(path) + "' is not a directory";
}
var tree = fs.listTree(path);
var files = [];
var i;
for (i = 0; i < tree.length; ++i) {
var filename = fs.join(path, tree[i]);
if (fs.isFile(filename)) {
files.push(tree[i]);
}
}
if (files.length === 0) {
throw "Directory '" + String(path) + "' is empty";
}
var tempFile = fs.getTempFile("downloads", false);
fs.zipFile(tempFile, path, files);
return tempFile;
};
// TODO: merge with functionality js/client/modules/@arangodb/foxx/manager.js
this.inspectUploadedFile = function (filename) {
var console = require("console");
if (! fs.isFile(filename)) {
throw "Unable to find zip file";
}
var i;
var path;
try {
path = fs.getTempFile("zip", false);
}
catch (err1) {
console.error("cannot get temp file: %s", String(err1));
throw err1;
}
try {
fs.unzipFile(filename, path, false, true);
}
catch (err2) {
console.error("cannot unzip file '%s' into directory '%s': %s", filename, path, String(err2));
throw err2;
}
// .............................................................................
// locate the manifest file
// .............................................................................
var tree = fs.listTree(path).sort(function(a,b) {
return a.length - b.length;
});
var found;
var mf = "manifest.json";
var re = /[\/\\\\]manifest\.json$/; // Windows!
for (i = 0; i < tree.length && found === undefined; ++i) {
var tf = tree[i];
if (re.test(tf) || tf === mf) {
found = tf;
break;
}
}
if (found === "undefined") {
fs.removeDirectoryRecursive(path);
throw "Cannot find manifest file in zip file";
}
var mp;
if (found === mf) {
mp = ".";
}
else {
mp = found.substr(0, found.length - mf.length - 1);
}
var manifest = JSON.parse(fs.read(fs.join(path, found)));
var absolutePath = this.repackZipFile(fs.join(path, mp));
var result = {
name : manifest.name,
version: manifest.version,
filename: absolutePath.substr(fs.getTempPath().length + 1),
configuration: manifest.configuration || {}
};
fs.removeDirectoryRecursive(path);
return result;
};
// Inspect a foxx in tmp zip file
this.inspect = function (path) {
var fullPath = fs.join(fs.getTempPath(), path);
try {
var result = this.inspectUploadedFile(fullPath);
fs.remove(fullPath);
return result;
} catch (e) {
throw new ArangoError();
}
};
};

View File

@ -1,66 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
/// @brief functionality to expose API documentation for Foxx apps
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2013 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
(function() {
"use strict";
// Get details of one specific installed foxx.
exports.Swagger = function (mount) {
var foxx_manager = require("@arangodb/foxx/manager");
var result = {},
apis = [],
pathes,
regex = /(:)([^\/]*)/g,
i,
url,
api,
ops;
var routes = foxx_manager.routes(mount);
result.swaggerVersion = "1.1";
result.basePath = mount;
result.apis = apis;
result.models = routes.models;
pathes = routes.routes;
for (i in pathes) {
if (!pathes[i].internal && pathes[i].url.methods !== undefined) {
url = pathes[i].url.match;
api = {};
ops = [];
url = url.replace(regex, "{$2}");
api.path = url;
ops.push(pathes[i].docs);
api.operations = ops;
apis.push(api);
}
}
return result;
};
}());

View File

@ -2,11 +2,11 @@
"name": "aardvark", "name": "aardvark",
"description": "ArangoDB Admin Web Interface", "description": "ArangoDB Admin Web Interface",
"author": "ArangoDB GmbH", "author": "ArangoDB GmbH",
"version": "1.0", "version": "3.0.0",
"license": "Apache License, Version 2.0", "license": "Apache License, Version 2.0",
"isSystem": true,
"engines": { "engines": {
"arangodb": "^2.8.0" "arangodb": "^3.0.0-0 || ^3.0.0"
}, },
"repository": { "repository": {
@ -29,13 +29,8 @@
} }
], ],
"controllers": { "main": "index.js",
"/": "aardvark.js", "defaultDocument": "index.html",
"/foxxes": "foxxes.js",
"/cluster": "cluster.js",
"/statistics": "statistics.js",
"/templates": "foxxTemplates.js"
},
"files": { "files": {
"/standalone.html": { "/standalone.html": {

View File

@ -1,50 +0,0 @@
/*jslint indent: 2, nomen: true, maxlen: 100 */
////////////////////////////////////////////////////////////////////////////////
/// @brief A configuration model for foxx templates
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
(function() {
"use strict";
var Foxx = require("@arangodb/foxx"),
joi = require("joi"),
Configuration;
Configuration = Foxx.Model.extend({
schema: {
applicationContext: joi.string().optional(),
path: joi.string().optional(),
name: joi.string().required(),
collectionNames: joi.array().required(),
authenticated: joi.boolean().required(),
author: joi.string().required(),
description: joi.string().required(),
license: joi.string().required()
}
});
exports.Model = Configuration;
}());

View File

@ -1,12 +1,10 @@
'use strict';
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief A TODO-List Foxx-Application written for ArangoDB
///
/// @file This Document represents the repository communicating with ArangoDB
///
/// DISCLAIMER /// DISCLAIMER
/// ///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany /// Copyright 2010-2013 triAGENS GmbH, Cologne, Germany
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
/// ///
/// Licensed under the Apache License, Version 2.0 (the "License"); /// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License. /// you may not use this file except in compliance with the License.
@ -20,89 +18,71 @@
/// See the License for the specific language governing permissions and /// See the License for the specific language governing permissions and
/// limitations under the License. /// limitations under the License.
/// ///
/// Copyright holder is triAGENS GmbH, Cologne, Germany /// Copyright holder is ArangoDB GmbH, Cologne, Germany
/// ///
/// @author Michael Hackstein /// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany /// @author Alan Plum
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
(function () { const db = require('@arangodb').db;
"use strict"; const version = String(require('@arangodb/database-version').CURRENT_VERSION);
const collection = db._collection('_cluster_kickstarter_plans');
var _ = require("lodash"), module.exports = {
Foxx = require("@arangodb/foxx"), hasConfig() {
version = require("@arangodb/database-version").CURRENT_VERSION + "", // do not use new String, there is bug in update return Boolean(this.loadConfig());
Plans;
Plans = Foxx.Repository.extend({
hasConfig: function() {
if (this.loadConfig()) {
return true;
}
return false;
}, },
clear: function() { clear() {
this.collection.truncate(); collection.truncate();
}, },
loadConfig: function() { loadConfig() {
return this.collection.any(); return collection.any();
}, },
getPlan: function() { getPlan() {
return this.loadConfig().plan; return this.loadConfig().plan;
}, },
getRunInfo: function() { getRunInfo() {
return this.loadConfig().runInfo; return this.loadConfig().runInfo;
}, },
getCredentials: function() { getCredentials() {
return this.loadConfig().user; return this.loadConfig().user;
}, },
updateConfig: function(config) { updateConfig(config) {
var old = this.loadConfig(); const old = this.loadConfig();
this.collection.update(old._id, config); collection.update(old._id, config);
return true; return true;
}, },
saveCredentials: function(user, passwd) { saveCredentials(name, passwd) {
var config = { const config = {user: {name, passwd}};
user: { const old = this.loadConfig();
name: user, collection.update(old._id, config);
passwd: passwd
}
};
var old = this.loadConfig();
this.collection.update(old._id, config);
return true; return true;
}, },
storeConfig: function(config) { storeConfig(config) {
this.collection.truncate(); collection.truncate();
config.version = version; config.version = version;
this.collection.save(config); collection.save(config);
return true; return true;
}, },
removeRunInfo: function() { removeRunInfo() {
var old = this.loadConfig(); const old = this.loadConfig();
delete old.runInfo; delete old.runInfo;
this.collection.replace(old._id, old); collection.replace(old._id, old);
return true; return true;
}, },
replaceRunInfo: function(newInfo) { replaceRunInfo(runInfo) {
var old = this.loadConfig(); const old = this.loadConfig();
this.collection.update(old._id, { collection.update(old._id, {runInfo, version});
runInfo: newInfo,
version: version
});
return true; return true;
} }
}); };
exports.Repository = Plans;
}());

View File

@ -1,13 +1,11 @@
/*global applicationContext, ArangoServerState, ArangoClusterInfo, ArangoClusterComm*/ /*global ArangoServerState, ArangoClusterInfo, ArangoClusterComm*/
'use strict';
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief A Foxx.Controller to handle the statistics
///
/// @file
///
/// DISCLAIMER /// DISCLAIMER
/// ///
/// Copyright 2014 triagens GmbH, Cologne, Germany /// Copyright 2014 triAGENS GmbH, Cologne, Germany
/// Copyright 2016 ArangoDB GmbH, Cologne, Germany
/// ///
/// Licensed under the Apache License, Version 2.0 (the "License"); /// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License. /// you may not use this file except in compliance with the License.
@ -21,34 +19,45 @@
/// See the License for the specific language governing permissions and /// See the License for the specific language governing permissions and
/// limitations under the License. /// limitations under the License.
/// ///
/// Copyright holder is triAGENS GmbH, Cologne, Germany /// Copyright holder is ArangoDB GmbH, Cologne, Germany
/// ///
/// @author Dr. Frank Celler /// @author Dr. Frank Celler
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany /// @author Alan Plum
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
(function() {
"use strict";
var internal = require("internal");
var cluster = require("@arangodb/cluster");
var actions = require("@arangodb/actions");
var FoxxController = require("@arangodb/foxx").Controller; var internal = require("internal");
var UnauthorizedError = require("http-errors").Unauthorized; var cluster = require("@arangodb/cluster");
var controller = new FoxxController(applicationContext);
var db = require("@arangodb").db;
var STATISTICS_INTERVAL = require("@arangodb/statistics").STATISTICS_INTERVAL; var db = require("@arangodb").db;
var STATISTICS_HISTORY_INTERVAL = require("@arangodb/statistics").STATISTICS_HISTORY_INTERVAL;
// ----------------------------------------------------------------------------- var STATISTICS_INTERVAL = require("@arangodb/statistics").STATISTICS_INTERVAL;
// --SECTION-- private functions var STATISTICS_HISTORY_INTERVAL = require("@arangodb/statistics").STATISTICS_HISTORY_INTERVAL;
// -----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////// const joi = require("joi");
/// @brief percentChange const httperr = require("http-errors");
//////////////////////////////////////////////////////////////////////////////// const createRouter = require("@arangodb/foxx/router");
function percentChange (current, prev, section, src) { const router = createRouter();
module.exports = router;
const startOffsetSchema = joi.number().default(
() => internal.time() - STATISTICS_INTERVAL * 10,
"Default offset"
);
const clusterIdSchema = joi.string().default(
() => cluster.isCluster() ? ArangoServerState.id() : undefined,
"Default DB server"
);
// -----------------------------------------------------------------------------
// --SECTION-- private functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief percentChange
////////////////////////////////////////////////////////////////////////////////
function percentChange (current, prev, section, src) {
if (prev === null) { if (prev === null) {
return 0; return 0;
@ -61,13 +70,13 @@
} }
return 0; return 0;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief percentChange2 /// @brief percentChange2
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function percentChange2 (current, prev, section, src1, src2) { function percentChange2 (current, prev, section, src1, src2) {
if (prev === null) { if (prev === null) {
return 0; return 0;
@ -80,13 +89,13 @@
} }
return 0; return 0;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief computeStatisticsRaw /// @brief computeStatisticsRaw
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
var STAT_SERIES = { var STAT_SERIES = {
avgTotalTime: [ "client", "avgTotalTime" ], avgTotalTime: [ "client", "avgTotalTime" ],
avgRequestTime: [ "client", "avgRequestTime" ], avgRequestTime: [ "client", "avgRequestTime" ],
avgQueueTime: [ "client", "avgQueueTime" ], avgQueueTime: [ "client", "avgQueueTime" ],
@ -109,17 +118,17 @@
userTimePerSecond: [ "system", "userTimePerSecond" ], userTimePerSecond: [ "system", "userTimePerSecond" ],
majorPageFaultsPerSecond: [ "system", "majorPageFaultsPerSecond" ], majorPageFaultsPerSecond: [ "system", "majorPageFaultsPerSecond" ],
minorPageFaultsPerSecond: [ "system", "minorPageFaultsPerSecond" ] minorPageFaultsPerSecond: [ "system", "minorPageFaultsPerSecond" ]
}; };
var STAT_DISTRIBUTION = { var STAT_DISTRIBUTION = {
totalTimeDistributionPercent: [ "client", "totalTimePercent" ], totalTimeDistributionPercent: [ "client", "totalTimePercent" ],
requestTimeDistributionPercent: [ "client", "requestTimePercent" ], requestTimeDistributionPercent: [ "client", "requestTimePercent" ],
queueTimeDistributionPercent: [ "client", "queueTimePercent" ], queueTimeDistributionPercent: [ "client", "queueTimePercent" ],
bytesSentDistributionPercent: [ "client", "bytesSentPercent" ], bytesSentDistributionPercent: [ "client", "bytesSentPercent" ],
bytesReceivedDistributionPercent: [ "client", "bytesReceivedPercent" ] bytesReceivedDistributionPercent: [ "client", "bytesReceivedPercent" ]
}; };
function computeStatisticsRaw (result, start, clusterId) { function computeStatisticsRaw (result, start, clusterId) {
var filter = ""; var filter = "";
@ -244,13 +253,13 @@
result.nextStart = lastRaw.time; result.nextStart = lastRaw.time;
result.waitFor = (lastRaw.time + STATISTICS_INTERVAL) - internal.time(); result.waitFor = (lastRaw.time + STATISTICS_INTERVAL) - internal.time();
} }
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief computeStatisticsRaw15M /// @brief computeStatisticsRaw15M
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function computeStatisticsRaw15M (result, start, clusterId) { function computeStatisticsRaw15M (result, start, clusterId) {
var filter = ""; var filter = "";
@ -315,13 +324,13 @@
result.clientConnections15M = 0; result.clientConnections15M = 0;
result.clientConnections15MPercentChange = 0; result.clientConnections15MPercentChange = 0;
} }
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief computeStatisticsShort /// @brief computeStatisticsShort
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function computeStatisticsShort (start, clusterId) { function computeStatisticsShort (start, clusterId) {
var result = {}; var result = {};
@ -329,13 +338,13 @@
computeStatisticsRaw15M(result, start, clusterId); computeStatisticsRaw15M(result, start, clusterId);
return result; return result;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief computeStatisticsValues /// @brief computeStatisticsValues
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function computeStatisticsValues (result, values, attrs) { function computeStatisticsValues (result, values, attrs) {
var key; var key;
@ -358,13 +367,13 @@
} }
} }
} }
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief computeStatisticsLong /// @brief computeStatisticsLong
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
function computeStatisticsLong (attrs, clusterId) { function computeStatisticsLong (attrs, clusterId) {
var short = { times: [] }; var short = { times: [] };
@ -411,73 +420,34 @@
} }
} }
if (! attrs.times) { if (!attrs.times) {
delete long.times; delete long.times;
} }
return long; return long;
}
router.use((req, res, next) => {
if (!internal.options()['server.disable-authentication'] && !req.user) {
throw new httperr.Unauthorized();
} }
next();
});
// ----------------------------------------------------------------------------- router.get("short", function (req, res) {
// --SECTION-- public routes
// -----------------------------------------------------------------------------
controller.activateSessions({
autoCreateSession: false,
cookie: {name: "arango_sid_" + db._name()}
});
controller.allRoutes
.errorResponse(UnauthorizedError, 401, "unauthorized")
.onlyIf(function (req) {
if (!internal.options()["server.disable-authentication"] && (!req.session || !req.session.get('uid'))) {
throw new UnauthorizedError();
}
});
////////////////////////////////////////////////////////////////////////////////
/// @brief short term history
////////////////////////////////////////////////////////////////////////////////
controller.get("short", function (req, res) {
var start = req.params("start"); var start = req.params("start");
var dbServer = req.params("DBserver"); var clusterId = req.params("DBserver");
if (start !== null && start !== undefined) {
start = parseFloat(start, 10);
}
else {
start = internal.time() - STATISTICS_INTERVAL * 10;
}
var clusterId;
if (dbServer === undefined) {
if (cluster.isCluster()) {
clusterId = ArangoServerState.id();
}
}
else {
clusterId = dbServer;
}
var series = computeStatisticsShort(start, clusterId); var series = computeStatisticsShort(start, clusterId);
res.json(series); res.json(series);
}).summary("Returns the statistics") })
.notes("This function is used to get the statistics history."); .queryParam("start", startOffsetSchema)
.queryParam("DBserver", clusterIdSchema)
.summary("Short term history")
.description("This function is used to get the statistics history.");
//////////////////////////////////////////////////////////////////////////////// router.get("long", function (req, res) {
/// @brief long term history
////////////////////////////////////////////////////////////////////////////////
controller.get("long", function (req, res) {
var filter = req.params("filter"); var filter = req.params("filter");
var dbServer = req.params("DBserver"); var clusterId = req.params("DBserver");
if (filter === undefined) {
actions.resultError(req, res, actions.HTTP_BAD, "required parameter 'filter' was not given");
}
var attrs = {}; var attrs = {};
var s = filter.split(","); var s = filter.split(",");
@ -487,42 +457,23 @@
attrs[s[i]] = true; attrs[s[i]] = true;
} }
var clusterId;
if (dbServer === undefined) {
if (cluster.isCluster()) {
clusterId = ArangoServerState.id();
}
}
else {
clusterId = dbServer;
}
var series = computeStatisticsLong(attrs, clusterId); var series = computeStatisticsLong(attrs, clusterId);
res.json(series); res.json(series);
}).summary("Returns the aggregated history") })
.notes("This function is used to get the aggregated statistics history."); .queryParam("filter", joi.string().required())
.queryParam("DBserver", clusterIdSchema)
.summary("Long term history")
.description("This function is used to get the aggregated statistics history.");
//////////////////////////////////////////////////////////////////////////////// router.get("cluster", function (req, res) {
/// @brief cluster statistics history if (!cluster.isCoordinator()) {
//////////////////////////////////////////////////////////////////////////////// throw new httperr.Forbidden("only allowed on coordinator");
controller.get("cluster", function (req, res) {
if (! cluster.isCoordinator()) {
actions.resultError(req, res, actions.HTTP_FORBIDDEN, 0, "only allowed on coordinator");
return;
}
if (! req.parameters.hasOwnProperty("DBserver")) {
actions.resultError(req, res, actions.HTTP_BAD, "required parameter DBserver was not given");
return;
} }
var DBserver = req.parameters.DBserver; var DBserver = req.parameters.DBserver;
var type = req.parameters.type; var type = req.parameters.type;
var coord = { coordTransactionID: ArangoClusterInfo.uniqid() }; var coord = { coordTransactionID: ArangoClusterInfo.uniqid() };
var options = { coordTransactionID: coord.coordTransactionID, timeout:10 }; var options = { coordTransactionID: coord.coordTransactionID, timeout: 10 };
if (type !== "short" && type !== "long") { if (type !== "short" && type !== "long") {
type = "short"; type = "short";
@ -545,51 +496,36 @@
var op = ArangoClusterComm.asyncRequest( var op = ArangoClusterComm.asyncRequest(
"GET", "GET",
"server:"+DBserver, "server:" + DBserver,
"_system", "_system",
url, url,
"", "",
{}, {},
options); options
);
var r = ArangoClusterComm.wait(op); var r = ArangoClusterComm.wait(op);
res.contentType = "application/json; charset=utf-8";
if (r.status === "RECEIVED") { if (r.status === "RECEIVED") {
res.responseCode = actions.HTTP_OK; res.set("content-type", "application/json; charset=utf-8");
res.body = r.body; res.body = r.body;
} } else if (r.status === "TIMEOUT") {
else if (r.status === "TIMEOUT") { throw new httperr.BadRequest("operation timed out");
res.responseCode = actions.HTTP_BAD; } else {
res.body = JSON.stringify( {"error":true, "errorMessage": "operation timed out"}); var body;
}
else {
res.responseCode = actions.HTTP_BAD;
var bodyobj;
try { try {
bodyobj = JSON.parse(r.body); body = JSON.parse(r.body);
} } catch (e) {
catch (err) { // noop
} }
res.body = JSON.stringify({ throw Object.assign(
"error":true, new httperr.BadRequest("error from DBserver, possibly DBserver unknown"),
"errorMessage": "error from DBserver, possibly DBserver unknown", {extra: {body}}
"body": bodyobj );
} );
} }
}).summary("Returns the complete or partial history of a cluster member") })
.notes("This function is used to get the complete or partial statistics history of a cluster member."); .queryParam("DBserver", joi.string().required())
.summary("Cluster statistics history")
}()); .description("This function is used to get the complete or partial statistics history of a cluster member.");
// -----------------------------------------------------------------------------
// --SECTION-- END-OF-FILE
// -----------------------------------------------------------------------------
// Local Variables:
// mode: outline-minor
// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @\\}"
// End: