diff --git a/js/apps/system/_admin/aardvark/APP/aardvark.js b/js/apps/system/_admin/aardvark/APP/aardvark.js index f4a1dfca92..4c1a95e7f2 100644 --- a/js/apps/system/_admin/aardvark/APP/aardvark.js +++ b/js/apps/system/_admin/aardvark/APP/aardvark.js @@ -1,14 +1,10 @@ -/*jshint globalstrict: true */ -/*global applicationContext*/ +'use strict'; //////////////////////////////////////////////////////////////////////////////// -/// @brief A Foxx.Controller to show all Foxx Applications -/// -/// @file -/// /// 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"); /// 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 /// limitations under the License. /// -/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @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"); -var publicController = new Foxx.Controller(applicationContext); -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." -); +const router = createRouter(); +module.exports = router; -var foxxes = new (require("./lib/foxxes").Foxxes)(); -var FoxxManager = require("@arangodb/foxx/manager"); -var UnauthorizedError = require("http-errors").Unauthorized; - -publicController.activateSessions({ - 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; +router.get('/whoAmI', function(req, res) { + let user = null; + if (internal.options()['server.disable-authentication']) { + user = false; + } else if (req.session && req.session.uid) { try { - user = users.get(uid).get("user"); + const doc = db._users.document(req.session.uid); + user = doc.user; } catch (e) { - if (!(e instanceof users.errors.UserNotFound)) { + if (!e.isArangoError || e.errorNum !== NOT_FOUND) { 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}); }); -publicController.post("/login", function (req, res) { - if (req.session) { - req.session.set({uid: null, userDate: null}); - } else { - req.session = publicController.sessions.getSessionStorage().create(); +router.post('/login', function (req, res) { + if (!req.session) { + req.session = module.context.sessions.new(); } - var users = Foxx.getExports("_system/users").userStorage; - var credentials = req.parameters.credentials; - var user = users.resolve(credentials.get("username")); - if (!user) throw new UnauthorizedError(); - var auth = Foxx.getExports("_system/simple-auth").auth; - var valid = auth.verifyPassword(user.get("authData").simple, credentials.get("password")); - if (!valid) throw new UnauthorizedError(); - req.session.setUser(user); - req.session.save(); - res.json({ - user: user.get("user") - }); -}).bodyParam("credentials", { - type: Foxx.Model.extend({ - username: joi.string().required(), - password: joi.string().required() - }), - description: "Login credentials." -}); -publicController.get("/unauthorized", function() { - throw new UnauthorizedError(); -}); + const doc = db._users.firstExample({user: req.body.username}); + const valid = module.context.auth.verify( + doc ? doc.authData.simple : null, + req.body.password + ); -publicController.get("/index.html", function(req, res) { - var prefix = '/_db/' + encodeURIComponent(req.database) + applicationContext.mount; - - res.status(302); - res.set("Location", prefix + "/standalone.html"); -}); - -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(); + if (!valid) { + throw new httperr.Unauthorized(); } + + const user = doc.user; + req.session.uid = doc._key; + res.json({user}); +}) +.body({ + username: joi.string().required(), + password: joi.string().required().allow('') +}, 'Login credentials.'); + +router.get('/unauthorized', function() { + throw new httperr.Unauthorized(); }); -controller.apiDocumentation('/api', { - swaggerJson(req, res) { - var filename = applicationContext.fileName('api-docs.json'); - res.sendFile(filename, {lastModified: true}); +router.get('/index.html', function(req, res) { + res.redirect(req.makeAbsolute('standalone.html')); +}); + +const authRouter = createRouter(); +router.use(authRouter); + +authRouter.use((req, res, next) => { + if (!internal.options()['server.disable-authentication'] && !req.user) { + throw new httperr.Unauthorized(); } + next(); }); -/** Is version check allowed - * - * Check if version check is allowed - */ -controller.get("shouldCheckVersion", function(req, res) { +authRouter.get('/api/*', module.context.createSwaggerHandler( + (req) => ({ + appPath: req.queryParams.mount, + swaggerJson(req, res) { + res.sendFile(module.context.fileName('api-docs.json'), {lastModified: true}); + } + }) +)); + +authRouter.get('shouldCheckVersion', function(req, res) { 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) { - res.json(false); - } else { - res.json(true); - } -}); - -/** Disable version check - * - * Disable the version check in web interface - */ -controller.post("disableVersionCheck", function(req, res) { +authRouter.post('disableVersionCheck', function(req, res) { notifications.setVersions({ enableVersionNotification: false }); - res.json("ok"); -}); + res.json('ok'); +}) +.summary('Disable version check') +.description('Disable the version check in web interface'); -/** Explains a query - * - * Explains a query in a more user-friendly way than the query - * _api/explain - * - */ -controller.post("/query/explain", function(req, res) { +authRouter.post('/query/explain', function(req, res) { var explain, query = req.body().query, bindVars = req.body().bindVars; @@ -173,39 +136,35 @@ controller.post("/query/explain", function(req, res) { if (query.length > 0) { try { if (bindVars) { - explain = require("@arangodb/aql/explainer").explain({ + explain = require('@arangodb/aql/explainer').explain({ query: query, bindVars: bindVars }, {colors: false}, false, bindVars); } else { - explain = require("@arangodb/aql/explainer").explain(query, {colors: false}, false); + explain = require('@arangodb/aql/explainer').explain(query, {colors: false}, false); } } catch (e) { explain = JSON.stringify(e); - }  + } } 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 - * - * Download and export all queries from the given username. - * - */ -controller.post("/query/upload/:user", function(req, res) { - var user = req.params("user"); - var queries, userColl, queriesToSave; +authRouter.post('/query/upload/:user', function(req, res) { + var user = req.params('user'); + var queries, doc, queriesToSave; queries = req.body(); - userColl = db._users.byExample({"user": user}).toArray()[0]; - queriesToSave = userColl.userData.queries || [ ]; + doc = db._users.byExample({'user': user}).toArray()[0]; + queriesToSave = doc.userData.queries || [ ]; underscore.each(queries, function(newq) { var found = false, i; @@ -216,7 +175,7 @@ controller.post("/query/upload/:user", function(req, res) { break; } } - if (! found) { + if (!found) { 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); -}).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 - * - * Download and export all queries from the given username. - * - */ +authRouter.get('/query/download/:user', function(req, res) { + var user = req.params('user'); + var result = db._users.byExample({'user': user}).toArray()[0]; -controller.get("/query/download/:user", function(req, res) { - var user = req.params("user"); - var result = db._users.byExample({"user": user}).toArray()[0]; - - res.set("Content-Type", "application/json"); - res.set("Content-Disposition", contentDisposition('queries.json')); + res.set('Content-Type', 'application/json'); + res.set('Content-Disposition', contentDisposition('queries.json')); if (result === null || result === undefined) { res.json([]); @@ -252,20 +206,15 @@ controller.get("/query/download/:user", function(req, res) { 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 - * - * Download and export all queries from the given username. - * - */ - -controller.get("/query/result/download/:query", function(req, res) { - var query = req.params("query"), +authRouter.get('/query/result/download/:query', function(req, res) { + var query = req.params('query'), parsedQuery; - var internal = require("internal"); + var internal = require('internal'); query = internal.base64Decode(query); try { 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(); - res.set("Content-Type", "application/json"); - res.set("Content-Disposition", contentDisposition('results.json')); + res.set('Content-Type', 'application/json'); + res.set('Content-Disposition', contentDisposition('results.json')); 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 - * - * Create one of the given sample graphs. - * - */ - -controller.post("/graph-examples/create/:name", function(req, res) { - var name = req.params("name"), g, - examples = require("@arangodb/graph-examples/example-graph.js"); +authRouter.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') { - g = examples.loadGraph("knows_graph"); + g = examples.loadGraph('knows_graph'); } else if (name === 'social') { - g = examples.loadGraph("social"); + g = examples.loadGraph('social'); } else if (name === 'routeplanner') { - g = examples.loadGraph("routeplanner"); + g = examples.loadGraph('routeplanner'); } if (typeof g === 'object') { @@ -308,16 +252,11 @@ controller.post("/graph-examples/create/:name", function(req, res) { 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 - * - * Create a new job id entry in a specific system database with a given id. - * - */ - -controller.post("/job", function(req, res) { +authRouter.post('/job', function(req, res) { 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); } -}).summary("Store job id of a running job") - .notes("This function stores a job id into a system collection."); - - /** Delete all jobs - * - * Delete an existing job id entry in a specific system database with a given id. - * - */ - -controller.del("/job/", function(req, res) { +}) +.summary('Store job id of a running job') +.description('Create a new job id entry in a specific system database with a given id.'); +authRouter.delete('/job/', function(req, res) { db._frontend.removeByExample({ model: 'job' }, 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") - .notes("This function stores a job id into a system collection."); +authRouter.delete('/job/:id', function(req, res) { - /** Delete a job 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"); + var id = req.params('id'); if (id) { db._frontend.removeByExample({ @@ -375,27 +302,15 @@ controller.del("/job/:id", function(req, res) { 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 - * - * Return all job id's which are stored in a system database. - * - */ - -controller.get("/job", function(req, res) { +authRouter.get('/job', function(req, res) { var result = db._frontend.all().toArray(); res.json(result); -}).summary("Return all job ids.") - .notes("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: +}) +.summary('Return all job ids.') +.description('This function returns the job ids of all currently running jobs.'); diff --git a/js/apps/system/_admin/aardvark/APP/cluster.js b/js/apps/system/_admin/aardvark/APP/cluster.js index 4f649a0e31..a7259ea490 100644 --- a/js/apps/system/_admin/aardvark/APP/cluster.js +++ b/js/apps/system/_admin/aardvark/APP/cluster.js @@ -1,13 +1,10 @@ -/*global applicationContext*/ +'use strict'; //////////////////////////////////////////////////////////////////////////////// -/// @brief A Foxx.Controller to show all Foxx Applications -/// -/// @file -/// /// 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"); /// you may not use this file except in compliance with the License. @@ -21,168 +18,145 @@ /// See the License for the specific language governing permissions and /// limitations under the License. /// -/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Michael Hackstein -/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany +/// @author Alan Plum //////////////////////////////////////////////////////////////////////////////// -(function() { - "use strict"; +const _ = require('lodash'); +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". - var FoxxController = require("@arangodb/foxx").Controller, - 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"); +const router = createRouter(); +module.exports = router; - controller.activateSessions({ - autoCreateSession: false, - cookie: {name: "arango_sid_" + db._name()} +router.use((req, res, next) => { + if (!internal.options()['server.disable-authentication'] && !req.user) { + throw new httperr.Unauthorized(); + } + next(); +}); + +router.get('/amICoordinator', function(req, res) { + 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()) { + // only make these functions available in cluster mode! + var Communication = require("@arangodb/cluster/agency-communication"); + var comm = new Communication.Communication(); + var beats = comm.sync.Heartbeats(); + var diff = comm.diff.current; + var servers = comm.current.DBServers(); + var dbs = comm.current.Databases(); + var coords = comm.current.Coordinators(); + + router.get("/ClusterType", function(req, res) { + // Not yet implemented + res.json({ + type: "symmetricSetup" + }); + }) + .summary('Get the type of the cluster') + .description(dd` + Returns a string containing the cluster type + Possible anwers: + - testSetup + - symmetricalSetup + - asymmetricalSetup + `); + + router.get("/DBServers", function(req, res) { + var resList = [], + list = servers.getList(), + diffList = diff.DBServers(), + didBeat = beats.didBeat(), + serving = beats.getServing(); + + _.each(list, function(v, k) { + v.name = k; + resList.push(v); + if (!_.contains(didBeat, k)) { + v.status = "critical"; + return; + } + if (v.role === "primary" && !_.contains(serving, k)) { + v.status = "warning"; + return; + } + v.status = "ok"; + }); + _.each(diffList.missing, function(v) { + v.status = "missing"; + resList.push(v); + }); + res.json(resList); + }) + .summary('Get all DBServers') + .description('Get a list of all running and expected DBServers within the cluster'); + + router.get("/Coordinators", function(req, res) { + var resList = [], + list = coords.getList(), + diffList = diff.Coordinators(), + didBeat = beats.didBeat(); + + _.each(list, function(v, k) { + v.name = k; + resList.push(v); + if (!_.contains(didBeat, k)) { + v.status = "critical"; + return; + } + v.status = "ok"; + }); + _.each(diffList.missing, function(v) { + v.status = "missing"; + resList.push(v); + }); + res.json(resList); }); - 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(); + router.get("/Databases", function(req, res) { + var list = dbs.getList(); + res.json(_.map(list, (name) => ({name}))); + }); + + router.get("/:dbname/Collections", function(req, res) { + var dbname = req.params("dbname"), + selected = dbs.select(dbname); + try { + res.json(_.map( + selected.getCollections(), + (name) => ({name}) + )); + } catch(e) { + res.json([]); } }); - /** Plan and start a new cluster - * - * This will plan a new cluster with the information - * given in the body - */ - controller.get("/amICoordinator", function(req, res) { - res.json(cluster.isCoordinator()); + router.get("/:dbname/:colname/Shards", function(req, res) { + var dbname = req.params("dbname"); + var colname = req.params("colname"); + var selected = dbs.select(dbname).collection(colname); + res.json(selected.getShardsByServers()); }); - if (cluster.isCluster()) { - // only make these functions available in cluster mode! - var Communication = require("@arangodb/cluster/agency-communication"), - comm = new Communication.Communication(), - beats = comm.sync.Heartbeats(), - diff = comm.diff.current, - servers = comm.current.DBServers(), - dbs = comm.current.Databases(), - coords = comm.current.Coordinators(); - - - /** 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 - res.json({ - type: "symmetricSetup" - }); - }); - - /** Get all DBServers - * - * Get a list of all running and expected DBServers - * within the cluster - */ - controller.get("/DBServers", function(req, res) { - var resList = [], - list = servers.getList(), - diffList = diff.DBServers(), - didBeat = beats.didBeat(), - serving = beats.getServing(); - - _.each(list, function(v, k) { - v.name = k; - resList.push(v); - if (!_.contains(didBeat, k)) { - v.status = "critical"; - return; - } - if (v.role === "primary" && !_.contains(serving, k)) { - v.status = "warning"; - return; - } - v.status = "ok"; - }); - _.each(diffList.missing, function(v) { - v.status = "missing"; - resList.push(v); - }); - res.json(resList); - }); - - controller.get("/Coordinators", function(req, res) { - var resList = [], - list = coords.getList(), - diffList = diff.Coordinators(), - didBeat = beats.didBeat(); - - _.each(list, function(v, k) { - v.name = k; - resList.push(v); - if (!_.contains(didBeat, k)) { - v.status = "critical"; - return; - } - v.status = "ok"; - }); - _.each(diffList.missing, function(v) { - v.status = "missing"; - resList.push(v); - }); - res.json(resList); - }); - - controller.get("/Databases", function(req, res) { - var list = dbs.getList(); - res.json(_.map(list, function(d) { - return {name: d}; - })); - }); - - controller.get("/:dbname/Collections", function(req, res) { - var dbname = req.params("dbname"), - selected = dbs.select(dbname); - try { - res.json(_.map(selected.getCollections(), - function(c) { - return {name: c}; - }) - ); - } catch(e) { - res.json([]); - } - }); - - controller.get("/:dbname/:colname/Shards", function(req, res) { - var dbname = req.params("dbname"), - colname = req.params("colname"), - selected = dbs.select(dbname).collection(colname); - res.json(selected.getShardsByServers()); - }); - - controller.get("/:dbname/:colname/Shards/:servername", function(req, res) { - var dbname = req.params("dbname"), - colname = req.params("colname"), - servername = req.params("servername"), - selected = dbs.select(dbname).collection(colname); - res.json(_.map(selected.getShardsForServer(servername), - function(c) { - return {id: c}; - }) - ); - }); - - } // end isCluster() - -}()); + router.get("/:dbname/:colname/Shards/:servername", function(req, res) { + var dbname = req.params("dbname"); + var colname = req.params("colname"); + var servername = req.params("servername"); + var selected = dbs.select(dbname).collection(colname); + res.json(_.map( + selected.getShardsForServer(servername), + (c) => ({id: c}) + )); + }); +} diff --git a/js/apps/system/_admin/aardvark/APP/default-thumbnail.png b/js/apps/system/_admin/aardvark/APP/default-thumbnail.png new file mode 100644 index 0000000000..7055eb47dc Binary files /dev/null and b/js/apps/system/_admin/aardvark/APP/default-thumbnail.png differ diff --git a/js/apps/system/_admin/aardvark/APP/foxxTemplates.js b/js/apps/system/_admin/aardvark/APP/foxxTemplates.js index 64bc6e11a1..d9496d2bb2 100644 --- a/js/apps/system/_admin/aardvark/APP/foxxTemplates.js +++ b/js/apps/system/_admin/aardvark/APP/foxxTemplates.js @@ -1,13 +1,10 @@ -/*global applicationContext*/ +'use strict'; //////////////////////////////////////////////////////////////////////////////// -/// @brief A Foxx.Controller to generate new FoxxApps -/// -/// @file -/// /// 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"); /// 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 /// limitations under the License. /// -/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Michael Hackstein -/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany +/// @author Alan Plum //////////////////////////////////////////////////////////////////////////////// -(function() { - "use strict"; - var FoxxController = require("@arangodb/foxx").Controller, - UnauthorizedError = require("http-errors").Unauthorized, - internal = require("internal"), - Configuration = require("./models/configuration").Model, - controller = new FoxxController(applicationContext), - db = require("internal").db, - FoxxManager = require("@arangodb/foxx/manager"); +const joi = require('joi'); +const httperr = require('http-errors'); +const internal = require('internal'); +const FoxxManager = require('@arangodb/foxx/manager'); +const createRouter = require('@arangodb/foxx/router'); - 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(); - } - }); +const router = createRouter(); +module.exports = router; - controller.get("/devMode", function(req, res) { - res.json(false); - }); +router.use((req, res, next) => { + if (!internal.options()['server.disable-authentication'] && !req.user) { + throw new httperr.Unauthorized(); + } + next(); +}); - controller.post("/generate", function(req, res) { - var conf = req.params("configuration"); - res.json(FoxxManager.install("EMPTY", "/todo", conf)); - }).bodyParam("configuration", { - description: "The configuration for the template.", - type: Configuration - }); -}()); +router.get('/devMode', (req, res) => res.json(false)); + +router.post('/generate', (req, res) => res.json( + FoxxManager.install('EMPTY', '/todo', req.body) +)) +.body(joi.object({ + 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() +}), 'The configuration for the template.'); diff --git a/js/apps/system/_admin/aardvark/APP/foxxes.js b/js/apps/system/_admin/aardvark/APP/foxxes.js index f33b9d1cd5..357216503c 100644 --- a/js/apps/system/_admin/aardvark/APP/foxxes.js +++ b/js/apps/system/_admin/aardvark/APP/foxxes.js @@ -1,14 +1,10 @@ -/*global applicationContext */ -"use strict"; +'use strict'; //////////////////////////////////////////////////////////////////////////////// -/// @brief A Foxx.Controller to show all Foxx Applications -/// -/// @file -/// /// 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"); /// 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 /// limitations under the License. /// -/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Michael Hackstein -/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany +/// @author Alan Plum //////////////////////////////////////////////////////////////////////////////// -(function() { - var internal = require("internal"); - var db = require("@arangodb").db; - var NotFound = require("http-errors").NotFound; - var FoxxController = require("@arangodb/foxx").Controller; - var UnauthorizedError = require("http-errors").Unauthorized; - var controller = new FoxxController(applicationContext); - var ArangoError = require("@arangodb").ArangoError; - var FoxxManager = require("@arangodb/foxx/manager"); - var fmUtils = require("@arangodb/foxx/manager-utils"); - 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; +const fs = require('fs'); +const joi = require('joi'); +const marked = require('marked'); +const httperr = require('http-errors'); +const internal = require('internal'); +const highlightAuto = require('highlight.js').highlightAuto; +const FoxxManager = require('@arangodb/foxx/manager'); +const fmUtils = require('@arangodb/foxx/manager-utils'); +const createRouter = require('@arangodb/foxx/router'); - controller.activateSessions({ - autoCreateSession: false, - cookie: {name: "arango_sid_" + db._name()} +const DEFAULT_THUMBNAIL = module.context.fileName('default-thumbnail.png'); + +const mountSchema = joi.string().required().description( + 'The mount point of the service. Has to be url-encoded.' +); + +const router = createRouter(); +module.exports = router; + +router.use((req, res, next) => { + if (!internal.options()['server.disable-authentication'] && !req.user) { + throw new httperr.Unauthorized(); + } + next(); +}); + +function installApp(req, res, appInfo, options) { + var mount = decodeURIComponent(req.params('mount')); + var upgrade = req.params('upgrade'); + var replace = req.params('replace'); + var service; + if (upgrade) { + service = FoxxManager.upgrade(appInfo, mount, options); + } else if (replace) { + service = FoxxManager.replace(appInfo, mount, options); + } else { + service = FoxxManager.install(appInfo, mount, options); + } + var config = FoxxManager.configuration(mount); + res.json({ + error: false, + configuration: config, + name: service.name, + version: service.version }); +} - 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(); +const installer = createRouter(); +router.use(installer) +.queryParam('mount', mountSchema) +.queryParam('upgrade', joi.boolean().default(false).description( + 'Trigger to upgrade the service installed at the mountpoint. Triggers setup.' +)) +.queryParam('replace', joi.boolean().default(false).description( + 'Trigger to replace the service installed at the mountpoint. Triggers teardown and setup.' +)); + +installer.put('/store', function (req, res) { + const content = JSON.parse(req.requestBody); + const name = content.name; + const version = content.version; + installApp(req, res, `${name}:${version}`); +}) +.summary('Install a Foxx from the store') +.description('Downloads a Foxx from the store and installs it at the given mount.'); + +installer.put('/git', function (req, res) { + const content = JSON.parse(req.requestBody); + const url = content.url; + const version = content.version || 'master'; + installApp(req, res, `git:${url}:${version}`); +}) +.summary('Install a Foxx from Github') +.description('Install a Foxx with user/repository and version.'); + +installer.put('/generate', function (req, res) { + const info = JSON.parse(req.requestBody); + installApp(req, res, 'EMPTY', info); +}) +.summary('Generate a new foxx') +.description('Generate a new empty foxx on the given mount point'); + +installer.put('/zip', function (req, res) { + const content = JSON.parse(req.requestBody); + const file = content.zipFile; + installApp(req, res, file); +}) +.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) { + var mount = decodeURIComponent(req.params('mount')); + var runTeardown = req.parameters.teardown; + var service = FoxxManager.uninstall(mount, { + teardown: runTeardown, + force: true + }); + res.json({ + error: false, + name: service.name, + version: service.version + }); +}) +.queryParam('mount', mountSchema) +.queryParam('teardown', joi.boolean().default(true)) +.summary('Uninstall a Foxx') +.description('Uninstall the Foxx at the given mount-point.'); + +router.get('/', function (req, res) { + const foxxes = FoxxManager.listJson(); + foxxes.forEach((foxx) => { + const readme = FoxxManager.readme(foxx.mount); + if (readme) { + foxx.readme = marked(readme, { + highlight: (code) => highlightAuto(code).value + }); } }); + res.json(foxxes); +}) +.summary('List all Foxxes') +.description('Get a List of all running foxxes.'); - controller.extend({ - installer: function() { - this.queryParam("mount", mountPoint); - this.queryParam("upgrade", {type: joi.boolean().optional().description( - "Trigger to upgrade the app installed at the mountpoint. Triggers setup." - )}); - this.queryParam("replace", {type: joi.boolean().optional().description( - "Trigger to replace the app installed at the mountpoint. Triggers teardown and setup." - )}); - } - }); +router.get('/thumbnail', function (req, res) { + var mount = decodeURIComponent(req.params('mount')); + var service = FoxxManager.lookupApp(mount); + res.sendFile(service.thumbnail || DEFAULT_THUMBNAIL); +}) +.queryParam('mount', mountSchema) +.summary('Get the thumbnail of a Foxx') +.description('Request the thumbnail of the given Foxx in order to display it on the screen.'); - // ------------------------------------------------------------ - // SECTION install - // ------------------------------------------------------------ +router.get('/config', function (req, res) { + var mount = decodeURIComponent(req.params('mount')); + res.json(FoxxManager.configuration(mount)); +}) +.queryParam('mount', mountSchema) +.summary('Get the configuration for a service') +.description('Used to request the configuration options for services'); - var validateMount = function(req) { - var mount = req.params("mount"); - // Validation - mount = decodeURIComponent(mount); - return mount; - }; +router.patch('/config', function (req, res) { + var mount = decodeURIComponent(req.params('mount')); + var data = req.body; + res.json(FoxxManager.configure(mount, {configuration: data})); +}) +.queryParam('mount', mountSchema) +.summary('Set the configuration for a service') +.description('Used to overwrite the configuration options for services'); - 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) { - app = FoxxManager.upgrade(appInfo, mount, options); - } else if (replace) { - app = FoxxManager.replace(appInfo, mount, options); - } else { - app = FoxxManager.install(appInfo, mount, options); - } - var config = FoxxManager.configuration(mount); - res.json({ - error: false, - configuration: config, - name: app.name, - version: app.version - }); - }; +router.get('/deps', function (req, res) { + var mount = decodeURIComponent(req.params('mount')); + res.json(FoxxManager.dependencies(mount)); +}) +.queryParam('mount', mountSchema) +.summary('Get the dependencies for a service') +.description('Used to request the dependencies options for services'); - /** Install a Foxx from the store - * - * Downloads a Foxx from the store and installs it at the given mount - */ - controller.put("/store", function(req, res) { - var content = JSON.parse(req.requestBody), - name = content.name, - version = content.version; - installApp(req, res, name + ":" + version); +router.patch('/deps', function (req, res) { + var mount = decodeURIComponent(req.params('mount')); + var data = req.body; + res.json(FoxxManager.updateDeps(mount, {dependencies: data})); +}) +.queryParam('mount', mountSchema) +.summary('Set the dependencies for a service') +.description('Used to overwrite the dependencies options for services'); + +router.post('/tests', function (req, res) { + var options = req.body; + var mount = decodeURIComponent(req.params('mount')); + res.json(FoxxManager.runTests(mount, options)); +}) +.queryParam('mount', mountSchema) +.summary('Run tests for a service') +.description('Used to run the tests of a service'); + +router.post('/scripts/:name', function (req, res) { + const mount = decodeURIComponent(req.params('mount')); + const name = req.params('name'); + const argv = req.body; + try { + res.json(FoxxManager.runScript(name, mount, argv)); + } catch (e) { + throw e.cause || e; + } +}) +.queryParam('mount', mountSchema) +.pathParam('name', joi.string().required(), 'The name of a service\'s script to run.') +.body('argv', joi.any().default(null), 'Options to pass to the script.') +.summary('Run a script for a service') +.description('Used to trigger any script of a service'); + +router.patch('/setup', function (req, res) { + const mount = decodeURIComponent(req.params('mount')); + res.json(FoxxManager.runScript('setup', mount)); +}) +.queryParam('mount', mountSchema) +.summary('Trigger setup script for a service') +.description('Used to trigger the setup script of a service'); + +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'); + +router.patch('/devel', function (req, res) { + const mount = decodeURIComponent(req.params('mount')); + const activate = Boolean(req.body); + res.json(FoxxManager[activate ? 'development' : 'production'](mount)); +}) +.queryParam('mount', mountSchema) +.summary('Activate/Deactivate development mode for a service') +.description('Used to toggle between production and development mode'); + +router.get('/download/zip', function (req, res) { + var mount = decodeURIComponent(req.params('mount')); + var service = FoxxManager.lookupApp(mount); + var dir = fs.join(fs.makeAbsolute(service.root), service.path); + var zipPath = fmUtils.zipDirectory(dir); + res.download(zipPath, `${service.name}@${service.version}.zip`); +}) +.queryParam('mount', mountSchema) +.summary('Download a service as zip archive') +.description('Download a foxx service packed in a zip archive'); + +router.get('/fishbowl', function (req, res) { + FoxxManager.update(); + res.json(FoxxManager.availableJson()); +}) +.summary('List of all foxx services submitted to the Foxx 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 }) - .installer(); +)); - /** Install a Foxx from Github - * - * Install a Foxx with user/repository and version - */ - controller.put("/git", function (req, res) { - var content = JSON.parse(req.requestBody), - url = content.url, - version = content.version; - installApp(req, res, "git:" + url + ":" + (version || "master")); +router.get('/docs/*', module.context.createSwaggerHandler( + (req) => ({ + indexFile: 'index-alt.html', + appPath: req.queryParams.mount }) - .installer(); - - /** Generate a new foxx - * - * Generate a new empty foxx on the given mount point - */ - controller.put("/generate", function (req, res) { - var info = JSON.parse(req.requestBody); - installApp(req, res, "EMPTY", info); - }) - .installer(); - - /** Install a Foxx from temporary zip file - * - * Install a Foxx from the given zip path. - * This path has to be created via _api/upload - */ - controller.put("/zip", function (req, res) { - var content = JSON.parse(req.requestBody), - file = content.zipFile; - installApp(req, res, file); - }) - .installer(); - - - /** Uninstall a Foxx - * - * Uninstall the Foxx at the given mount-point. - */ - controller.delete("/", function (req, res) { - var mount = validateMount(req); - var runTeardown = req.parameters.teardown; - var app = FoxxManager.uninstall(mount, { - teardown: runTeardown, - force: true - }); - res.json({ - error: false, - name: app.name, - version: app.version - }); - }) - .queryParam("mount", mountPoint) - .queryParam("teardown", joi.boolean().default(true)); - - // ------------------------------------------------------------ - // SECTION information - // ------------------------------------------------------------ - - /** 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) { - foxx.readme = marked(readme, { - highlight(code) { - return highlightAuto(code).value; - } - }); - } - }); - res.json(foxxes); - }); - - /** Get the thumbnail of a Foxx - * - * Used to request the thumbnail of the given Foxx in order to display it on the screen. - */ - controller.get("/thumbnail", function (req, res) { - res.transformations = [ "base64decode" ]; - var mount = validateMount(req); - var app = FoxxManager.lookupApp(mount); - if (app.hasOwnProperty("thumbnail") && app.thumbnail !== null) { - res.body = app.thumbnail; - } else { - res.body = defaultThumb; - } - - // evil mimetype detection attempt... - var start = require("internal").base64Decode(res.body.substr(0, 8)); - 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)); - }) - .queryParam("mount", mountPoint); - - /** Set the configuration for an app - * - * Used to overwrite the configuration options for apps - */ - controller.patch("/config", function(req, res) { - var mount = validateMount(req); - var data = req.body(); - res.json(FoxxManager.configure(mount, {configuration: data})); - }) - .queryParam("mount", mountPoint); - - /** Get the dependencies for an app - * - * Used to request the dependencies options for apps - */ - controller.get("/deps", function(req, res) { - var mount = validateMount(req); - res.json(FoxxManager.dependencies(mount)); - }) - .queryParam("mount", mountPoint); - - /** Set the dependencies for an app - * - * Used to overwrite the dependencies options for apps - */ - controller.patch("/deps", function(req, res) { - var mount = validateMount(req); - var data = req.body(); - res.json(FoxxManager.updateDeps(mount, {dependencies: data})); - }) - .queryParam("mount", mountPoint); - - /** Run tests for an app - * - * Used to run the tests of an app - */ - controller.post("/tests", function (req, res) { - var options = req.body(); - var mount = validateMount(req); - res.json(FoxxManager.runTests(mount, options)); - }) - .queryParam("mount", mountPoint); - - /** Run a script for an app - * - * Used to trigger any script of an app - */ - controller.post("/scripts/:name", function (req, res) { - var mount = validateMount(req); - var name = req.params("name"); - var argv = req.params("argv"); - try { - res.json(FoxxManager.runScript(name, mount, argv)); - } catch (e) { - throw e.cause || e; - } - }) - .queryParam("mount", mountPoint) - .pathParam("name", scriptName) - .bodyParam( - "argv", - joi.any().default(null) - .description('Options to pass to the script.') - ); - - /** Trigger setup script for an app - * - * Used to trigger the setup script of an app - */ - controller.patch("/setup", function(req, res) { - var mount = validateMount(req); - res.json(FoxxManager.runScript("setup", mount)); - }) - .queryParam("mount", mountPoint); - - - /** Trigger teardown script for an app - * - * Used to trigger the teardown script of an app - */ - controller.patch("/teardown", function(req, res) { - var mount = validateMount(req); - res.json(FoxxManager.runScript("teardown", mount)); - }) - .queryParam("mount", mountPoint); - - - /** Activate/Deactivate development mode for an app - * - * Used to toggle between production and development mode - */ - 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); - res.set("Content-Type", "application/octet-stream"); - res.set("Content-Disposition", contentDisposition(`${app.name}@${app.version}.zip`)); - res.body = fs.readFileSync(zipPath); - }) - .queryParam("mount", mountPoint); - - - // ------------------------------------------------------------ - // 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(); - 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") - .errorResponse(ArangoError, 503, "Could not connect to store."); - - - - // ------------------------------------------------------------ - // 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 - * - * This function returns the Swagger JSON API description of the foxx - * installed under the given mount point. - */ - controller.get('/docu/*', function(req, res) { - var mount = ""; - underscore.each(req.suffix, function(part) { - mount += "/" + part; - }); - res.json(docu(mount)); - }); - -}()); +)); diff --git a/js/apps/system/_admin/aardvark/APP/index.js b/js/apps/system/_admin/aardvark/APP/index.js new file mode 100644 index 0000000000..ae6f6b11b6 --- /dev/null +++ b/js/apps/system/_admin/aardvark/APP/index.js @@ -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')); diff --git a/js/apps/system/_admin/aardvark/APP/lib/defaultThumbnail.js b/js/apps/system/_admin/aardvark/APP/lib/defaultThumbnail.js deleted file mode 100644 index 233bf8701a..0000000000 --- a/js/apps/system/_admin/aardvark/APP/lib/defaultThumbnail.js +++ /dev/null @@ -1,28 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// @brief The default thumbnail 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 -//////////////////////////////////////////////////////////////////////////////// - -exports.defaultThumb = "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"; diff --git a/js/apps/system/_admin/aardvark/APP/lib/foxxes.js b/js/apps/system/_admin/aardvark/APP/lib/foxxes.js deleted file mode 100644 index 609b7fd9ef..0000000000 --- a/js/apps/system/_admin/aardvark/APP/lib/foxxes.js +++ /dev/null @@ -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(); - } - }; - -}; diff --git a/js/apps/system/_admin/aardvark/APP/lib/swagger.js b/js/apps/system/_admin/aardvark/APP/lib/swagger.js deleted file mode 100644 index 2414adb3f5..0000000000 --- a/js/apps/system/_admin/aardvark/APP/lib/swagger.js +++ /dev/null @@ -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; - }; -}()); diff --git a/js/apps/system/_admin/aardvark/APP/manifest.json b/js/apps/system/_admin/aardvark/APP/manifest.json index 1ac9365f35..4bd1c6746f 100644 --- a/js/apps/system/_admin/aardvark/APP/manifest.json +++ b/js/apps/system/_admin/aardvark/APP/manifest.json @@ -2,11 +2,11 @@ "name": "aardvark", "description": "ArangoDB Admin Web Interface", "author": "ArangoDB GmbH", - "version": "1.0", + "version": "3.0.0", "license": "Apache License, Version 2.0", - "isSystem": true, + "engines": { - "arangodb": "^2.8.0" + "arangodb": "^3.0.0-0 || ^3.0.0" }, "repository": { @@ -29,13 +29,8 @@ } ], - "controllers": { - "/": "aardvark.js", - "/foxxes": "foxxes.js", - "/cluster": "cluster.js", - "/statistics": "statistics.js", - "/templates": "foxxTemplates.js" - }, + "main": "index.js", + "defaultDocument": "index.html", "files": { "/standalone.html": { diff --git a/js/apps/system/_admin/aardvark/APP/models/configuration.js b/js/apps/system/_admin/aardvark/APP/models/configuration.js deleted file mode 100644 index 73bd5a94d6..0000000000 --- a/js/apps/system/_admin/aardvark/APP/models/configuration.js +++ /dev/null @@ -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; -}()); diff --git a/js/apps/system/_admin/aardvark/APP/repositories/plans.js b/js/apps/system/_admin/aardvark/APP/repositories/plans.js index c2bdf30134..b8475a3387 100644 --- a/js/apps/system/_admin/aardvark/APP/repositories/plans.js +++ b/js/apps/system/_admin/aardvark/APP/repositories/plans.js @@ -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 /// -/// 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"); /// 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 /// limitations under the License. /// -/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Michael Hackstein -/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany +/// @author Alan Plum //////////////////////////////////////////////////////////////////////////////// -(function () { - "use strict"; +const db = require('@arangodb').db; +const version = String(require('@arangodb/database-version').CURRENT_VERSION); +const collection = db._collection('_cluster_kickstarter_plans'); - var _ = require("lodash"), - Foxx = require("@arangodb/foxx"), - version = require("@arangodb/database-version").CURRENT_VERSION + "", // do not use new String, there is bug in update - Plans; +module.exports = { + hasConfig() { + return Boolean(this.loadConfig()); + }, - Plans = Foxx.Repository.extend({ - hasConfig: function() { - if (this.loadConfig()) { - return true; - } - return false; - }, + clear() { + collection.truncate(); + }, - clear: function() { - this.collection.truncate(); - }, + loadConfig() { + return collection.any(); + }, - loadConfig: function() { - return this.collection.any(); - }, + getPlan() { + return this.loadConfig().plan; + }, - getPlan: function() { - return this.loadConfig().plan; - }, + getRunInfo() { + return this.loadConfig().runInfo; + }, - getRunInfo: function() { - return this.loadConfig().runInfo; - }, + getCredentials() { + return this.loadConfig().user; + }, - getCredentials: function() { - return this.loadConfig().user; - }, + updateConfig(config) { + const old = this.loadConfig(); + collection.update(old._id, config); + return true; + }, - updateConfig: function(config) { - var old = this.loadConfig(); - this.collection.update(old._id, config); - return true; - }, + saveCredentials(name, passwd) { + const config = {user: {name, passwd}}; + const old = this.loadConfig(); + collection.update(old._id, config); + return true; + }, - saveCredentials: function(user, passwd) { - var config = { - user: { - name: user, - passwd: passwd - } - }; - var old = this.loadConfig(); - this.collection.update(old._id, config); - return true; - }, + storeConfig(config) { + collection.truncate(); + config.version = version; + collection.save(config); + return true; + }, - storeConfig: function(config) { - this.collection.truncate(); - config.version = version; - this.collection.save(config); - return true; - }, + removeRunInfo() { + const old = this.loadConfig(); + delete old.runInfo; + collection.replace(old._id, old); + return true; + }, - removeRunInfo: function() { - var old = this.loadConfig(); - delete old.runInfo; - this.collection.replace(old._id, old); - return true; - }, - - replaceRunInfo: function(newInfo) { - var old = this.loadConfig(); - this.collection.update(old._id, { - runInfo: newInfo, - version: version - }); - return true; - } - }); - - exports.Repository = Plans; -}()); + replaceRunInfo(runInfo) { + const old = this.loadConfig(); + collection.update(old._id, {runInfo, version}); + return true; + } +}; diff --git a/js/apps/system/_admin/aardvark/APP/statistics.js b/js/apps/system/_admin/aardvark/APP/statistics.js index 68f83d2610..d17f4493bc 100644 --- a/js/apps/system/_admin/aardvark/APP/statistics.js +++ b/js/apps/system/_admin/aardvark/APP/statistics.js @@ -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 /// -/// 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"); /// you may not use this file except in compliance with the License. @@ -21,575 +19,513 @@ /// See the License for the specific language governing permissions and /// limitations under the License. /// -/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @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 UnauthorizedError = require("http-errors").Unauthorized; - var controller = new FoxxController(applicationContext); - var db = require("@arangodb").db; +var internal = require("internal"); +var cluster = require("@arangodb/cluster"); - var STATISTICS_INTERVAL = require("@arangodb/statistics").STATISTICS_INTERVAL; - var STATISTICS_HISTORY_INTERVAL = require("@arangodb/statistics").STATISTICS_HISTORY_INTERVAL; +var db = require("@arangodb").db; - // ----------------------------------------------------------------------------- - // --SECTION-- private functions - // ----------------------------------------------------------------------------- +var STATISTICS_INTERVAL = require("@arangodb/statistics").STATISTICS_INTERVAL; +var STATISTICS_HISTORY_INTERVAL = require("@arangodb/statistics").STATISTICS_HISTORY_INTERVAL; - //////////////////////////////////////////////////////////////////////////////// - /// @brief percentChange - //////////////////////////////////////////////////////////////////////////////// +const joi = require("joi"); +const httperr = require("http-errors"); +const createRouter = require("@arangodb/foxx/router"); - function percentChange (current, prev, section, src) { +const router = createRouter(); +module.exports = router; - if (prev === null) { - return 0; - } +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" +); - var p = prev[section][src]; +// ----------------------------------------------------------------------------- +// --SECTION-- private functions +// ----------------------------------------------------------------------------- - if (p !== 0) { - return (current[section][src] - p) / p; - } +//////////////////////////////////////////////////////////////////////////////// +/// @brief percentChange +//////////////////////////////////////////////////////////////////////////////// +function percentChange (current, prev, section, src) { + + if (prev === null) { return 0; } - //////////////////////////////////////////////////////////////////////////////// - /// @brief percentChange2 - //////////////////////////////////////////////////////////////////////////////// + var p = prev[section][src]; - function percentChange2 (current, prev, section, src1, src2) { + if (p !== 0) { + return (current[section][src] - p) / p; + } - if (prev === null) { - return 0; - } + return 0; +} - var p = prev[section][src1] - prev[section][src2]; +//////////////////////////////////////////////////////////////////////////////// +/// @brief percentChange2 +//////////////////////////////////////////////////////////////////////////////// - if (p !== 0) { - return (current[section][src1] - current[section][src2] - p) / p; - } +function percentChange2 (current, prev, section, src1, src2) { + if (prev === null) { return 0; } - //////////////////////////////////////////////////////////////////////////////// - /// @brief computeStatisticsRaw - //////////////////////////////////////////////////////////////////////////////// + var p = prev[section][src1] - prev[section][src2]; - var STAT_SERIES = { - avgTotalTime: [ "client", "avgTotalTime" ], - avgRequestTime: [ "client", "avgRequestTime" ], - avgQueueTime: [ "client", "avgQueueTime" ], - avgIoTime: [ "client", "avgIoTime" ], + if (p !== 0) { + return (current[section][src1] - current[section][src2] - p) / p; + } - bytesSentPerSecond: [ "client", "bytesSentPerSecond" ], - bytesReceivedPerSecond: [ "client", "bytesReceivedPerSecond" ], + return 0; +} - asyncPerSecond: [ "http", "requestsAsyncPerSecond" ], - optionsPerSecond: [ "http", "requestsOptionsPerSecond" ], - putsPerSecond: [ "http", "requestsPutPerSecond" ], - headsPerSecond: [ "http", "requestsHeadPerSecond" ], - postsPerSecond: [ "http", "requestsPostPerSecond" ], - getsPerSecond: [ "http", "requestsGetPerSecond" ], - deletesPerSecond: [ "http", "requestsDeletePerSecond" ], - othersPerSecond: [ "http", "requestsOptionsPerSecond" ], - patchesPerSecond: [ "http", "requestsPatchPerSecond" ], +//////////////////////////////////////////////////////////////////////////////// +/// @brief computeStatisticsRaw +//////////////////////////////////////////////////////////////////////////////// - systemTimePerSecond: [ "system", "systemTimePerSecond" ], - userTimePerSecond: [ "system", "userTimePerSecond" ], - majorPageFaultsPerSecond: [ "system", "majorPageFaultsPerSecond" ], - minorPageFaultsPerSecond: [ "system", "minorPageFaultsPerSecond" ] - }; +var STAT_SERIES = { + avgTotalTime: [ "client", "avgTotalTime" ], + avgRequestTime: [ "client", "avgRequestTime" ], + avgQueueTime: [ "client", "avgQueueTime" ], + avgIoTime: [ "client", "avgIoTime" ], - var STAT_DISTRIBUTION = { - totalTimeDistributionPercent: [ "client", "totalTimePercent" ], - requestTimeDistributionPercent: [ "client", "requestTimePercent" ], - queueTimeDistributionPercent: [ "client", "queueTimePercent" ], - bytesSentDistributionPercent: [ "client", "bytesSentPercent" ], - bytesReceivedDistributionPercent: [ "client", "bytesReceivedPercent" ] - }; + bytesSentPerSecond: [ "client", "bytesSentPerSecond" ], + bytesReceivedPerSecond: [ "client", "bytesReceivedPerSecond" ], - function computeStatisticsRaw (result, start, clusterId) { + asyncPerSecond: [ "http", "requestsAsyncPerSecond" ], + optionsPerSecond: [ "http", "requestsOptionsPerSecond" ], + putsPerSecond: [ "http", "requestsPutPerSecond" ], + headsPerSecond: [ "http", "requestsHeadPerSecond" ], + postsPerSecond: [ "http", "requestsPostPerSecond" ], + getsPerSecond: [ "http", "requestsGetPerSecond" ], + deletesPerSecond: [ "http", "requestsDeletePerSecond" ], + othersPerSecond: [ "http", "requestsOptionsPerSecond" ], + patchesPerSecond: [ "http", "requestsPatchPerSecond" ], - var filter = ""; + systemTimePerSecond: [ "system", "systemTimePerSecond" ], + userTimePerSecond: [ "system", "userTimePerSecond" ], + majorPageFaultsPerSecond: [ "system", "majorPageFaultsPerSecond" ], + minorPageFaultsPerSecond: [ "system", "minorPageFaultsPerSecond" ] +}; - if (clusterId !== undefined) { - filter = " FILTER s.clusterId == @clusterId "; +var STAT_DISTRIBUTION = { + totalTimeDistributionPercent: [ "client", "totalTimePercent" ], + requestTimeDistributionPercent: [ "client", "requestTimePercent" ], + queueTimeDistributionPercent: [ "client", "queueTimePercent" ], + bytesSentDistributionPercent: [ "client", "bytesSentPercent" ], + bytesReceivedDistributionPercent: [ "client", "bytesReceivedPercent" ] +}; + +function computeStatisticsRaw (result, start, clusterId) { + + var filter = ""; + + if (clusterId !== undefined) { + filter = " FILTER s.clusterId == @clusterId "; + } + + var values = db._query( + "FOR s IN _statistics " + + " FILTER s.time > @start " + + filter + + " SORT s.time " + + " return s", + { start: start - 2 * STATISTICS_INTERVAL, clusterId: clusterId }); + + result.times = []; + + var key; + + for (key in STAT_SERIES) { + if (STAT_SERIES.hasOwnProperty(key)) { + result[key] = []; + } + } + + var lastRaw = null; + var lastRaw2 = null; + var path; + + // read the last entries + while (values.hasNext()) { + var stat = values.next(); + + lastRaw2 = lastRaw; + lastRaw = stat; + + if (stat.time <= start) { + continue; } - var values = db._query( - "FOR s IN _statistics " - + " FILTER s.time > @start " - + filter - + " SORT s.time " - + " return s", - { start: start - 2 * STATISTICS_INTERVAL, clusterId: clusterId }); - - result.times = []; - - var key; + result.times.push(stat.time); for (key in STAT_SERIES) { if (STAT_SERIES.hasOwnProperty(key)) { - result[key] = []; + path = STAT_SERIES[key]; + + result[key].push(stat[path[0]][path[1]]); } } - - var lastRaw = null; - var lastRaw2 = null; - var path; - - // read the last entries - while (values.hasNext()) { - var stat = values.next(); - - lastRaw2 = lastRaw; - lastRaw = stat; - - if (stat.time <= start) { - continue; - } - - result.times.push(stat.time); - - for (key in STAT_SERIES) { - if (STAT_SERIES.hasOwnProperty(key)) { - path = STAT_SERIES[key]; - - result[key].push(stat[path[0]][path[1]]); - } - } - } - - // have at least one entry, use it - if (lastRaw !== null) { - for (key in STAT_DISTRIBUTION) { - if (STAT_DISTRIBUTION.hasOwnProperty(key)) { - path = STAT_DISTRIBUTION[key]; - - result[key] = lastRaw[path[0]][path[1]]; - } - } - - result.numberOfThreadsCurrent = lastRaw.system.numberOfThreads; - result.numberOfThreadsPercentChange = percentChange(lastRaw, lastRaw2, 'system', 'numberOfThreads'); - - result.virtualSizeCurrent = lastRaw.system.virtualSize; - result.virtualSizePercentChange = percentChange(lastRaw, lastRaw2, 'system', 'virtualSize'); - - result.residentSizeCurrent = lastRaw.system.residentSize; - result.residentSizePercent = lastRaw.system.residentSizePercent; - - result.asyncPerSecondCurrent = lastRaw.http.requestsAsyncPerSecond; - result.asyncPerSecondPercentChange = percentChange(lastRaw, lastRaw2, 'http', 'requestsAsyncPerSecond'); - - result.syncPerSecondCurrent = lastRaw.http.requestsTotalPerSecond - lastRaw.http.requestsAsyncPerSecond; - result.syncPerSecondPercentChange - = percentChange2(lastRaw, lastRaw2, 'http', 'requestsTotalPerSecond', 'requestsAsyncPerSecond'); - - result.clientConnectionsCurrent = lastRaw.client.httpConnections; - result.clientConnectionsPercentChange = percentChange(lastRaw, lastRaw2, 'client', 'httpConnections'); - } - - // have no entry, add nulls - else { - for (key in STAT_DISTRIBUTION) { - if (STAT_DISTRIBUTION.hasOwnProperty(key)) { - result[key] = { values: [0,0,0,0,0,0,0], cuts: [0,0,0,0,0,0] }; - } - } - - var ps = internal.processStatistics(); - - result.numberOfThreadsCurrent = ps.numberOfThreads; - result.numberOfThreadsPercentChange = 0; - - result.virtualSizeCurrent = ps.virtualSize; - result.virtualSizePercentChange = 0; - - result.residentSizeCurrent = ps.residentSize; - result.residentSizePercent = ps.residentSizePercent; - - result.asyncPerSecondCurrent = 0; - result.asyncPerSecondPercentChange = 0; - - result.syncPerSecondCurrent = 0; - result.syncPerSecondPercentChange = 0; - - result.clientConnectionsCurrent = 0; - result.clientConnectionsPercentChange = 0; - } - - // add physical memory - var ss = internal.serverStatistics(); - - result.physicalMemory = ss.physicalMemory; - - // add next start time - if (lastRaw === null) { - result.nextStart = internal.time(); - result.waitFor = STATISTICS_INTERVAL; - } - else { - result.nextStart = lastRaw.time; - result.waitFor = (lastRaw.time + STATISTICS_INTERVAL) - internal.time(); - } } - //////////////////////////////////////////////////////////////////////////////// - /// @brief computeStatisticsRaw15M - //////////////////////////////////////////////////////////////////////////////// + // have at least one entry, use it + if (lastRaw !== null) { + for (key in STAT_DISTRIBUTION) { + if (STAT_DISTRIBUTION.hasOwnProperty(key)) { + path = STAT_DISTRIBUTION[key]; - function computeStatisticsRaw15M (result, start, clusterId) { - - var filter = ""; - - if (clusterId !== undefined) { - filter = " FILTER s.clusterId == @clusterId "; + result[key] = lastRaw[path[0]][path[1]]; + } } - var values = db._query( - "FOR s IN _statistics15 " - + " FILTER s.time > @start " - + filter - + " SORT s.time " - + " return s", - { start: start - 2 * STATISTICS_HISTORY_INTERVAL, clusterId: clusterId }); + result.numberOfThreadsCurrent = lastRaw.system.numberOfThreads; + result.numberOfThreadsPercentChange = percentChange(lastRaw, lastRaw2, 'system', 'numberOfThreads'); - var lastRaw = null; - var lastRaw2 = null; + result.virtualSizeCurrent = lastRaw.system.virtualSize; + result.virtualSizePercentChange = percentChange(lastRaw, lastRaw2, 'system', 'virtualSize'); - // read the last entries - while (values.hasNext()) { - var stat = values.next(); + result.residentSizeCurrent = lastRaw.system.residentSize; + result.residentSizePercent = lastRaw.system.residentSizePercent; - lastRaw2 = lastRaw; - lastRaw = stat; - } + result.asyncPerSecondCurrent = lastRaw.http.requestsAsyncPerSecond; + result.asyncPerSecondPercentChange = percentChange(lastRaw, lastRaw2, 'http', 'requestsAsyncPerSecond'); - // have at least one entry, use it - if (lastRaw !== null) { - result.numberOfThreads15M = lastRaw.system.numberOfThreads; - result.numberOfThreads15MPercentChange = percentChange(lastRaw, lastRaw2, 'system', 'numberOfThreads'); - - result.virtualSize15M = lastRaw.system.virtualSize; - result.virtualSize15MPercentChange = percentChange(lastRaw, lastRaw2, 'system', 'virtualSize'); - - result.asyncPerSecond15M = lastRaw.http.requestsAsyncPerSecond; - result.asyncPerSecond15MPercentChange = percentChange(lastRaw, lastRaw2, 'http', 'requestsAsyncPerSecond'); - - result.syncPerSecond15M = lastRaw.http.requestsTotalPerSecond - lastRaw.http.requestsAsyncPerSecond; - result.syncPerSecond15MPercentChange + result.syncPerSecondCurrent = lastRaw.http.requestsTotalPerSecond - lastRaw.http.requestsAsyncPerSecond; + result.syncPerSecondPercentChange = percentChange2(lastRaw, lastRaw2, 'http', 'requestsTotalPerSecond', 'requestsAsyncPerSecond'); - result.clientConnections15M = lastRaw.client.httpConnections; - result.clientConnections15MPercentChange = percentChange(lastRaw, lastRaw2, 'client', 'httpConnections'); - } - - // have no entry, add nulls - else { - var ps = internal.processStatistics(); - - result.numberOfThreads15M = ps.numberOfThreads; - result.numberOfThreads15MPercentChange = 0; - - result.virtualSize15M = ps.virtualSize; - result.virtualSize15MPercentChange = 0; - - result.asyncPerSecond15M = 0; - result.asyncPerSecond15MPercentChange = 0; - - result.syncPerSecond15M = 0; - result.syncPerSecond15MPercentChange = 0; - - result.clientConnections15M = 0; - result.clientConnections15MPercentChange = 0; - } + result.clientConnectionsCurrent = lastRaw.client.httpConnections; + result.clientConnectionsPercentChange = percentChange(lastRaw, lastRaw2, 'client', 'httpConnections'); } - //////////////////////////////////////////////////////////////////////////////// - /// @brief computeStatisticsShort - //////////////////////////////////////////////////////////////////////////////// - - function computeStatisticsShort (start, clusterId) { - - var result = {}; - - computeStatisticsRaw(result, start, clusterId); - computeStatisticsRaw15M(result, start, clusterId); - - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - /// @brief computeStatisticsValues - //////////////////////////////////////////////////////////////////////////////// - - function computeStatisticsValues (result, values, attrs) { - - var key; - - for (key in attrs) { - if (attrs.hasOwnProperty(key)) { - result[key] = []; + // have no entry, add nulls + else { + for (key in STAT_DISTRIBUTION) { + if (STAT_DISTRIBUTION.hasOwnProperty(key)) { + result[key] = { values: [0,0,0,0,0,0,0], cuts: [0,0,0,0,0,0] }; } } - while (values.hasNext()) { - var stat = values.next(); + var ps = internal.processStatistics(); - result.times.push(stat.time); + result.numberOfThreadsCurrent = ps.numberOfThreads; + result.numberOfThreadsPercentChange = 0; - for (key in attrs) { - if (attrs.hasOwnProperty(key) && STAT_SERIES.hasOwnProperty(key)) { - var path = STAT_SERIES[key]; + result.virtualSizeCurrent = ps.virtualSize; + result.virtualSizePercentChange = 0; - result[key].push(stat[path[0]][path[1]]); - } - } - } + result.residentSizeCurrent = ps.residentSize; + result.residentSizePercent = ps.residentSizePercent; + + result.asyncPerSecondCurrent = 0; + result.asyncPerSecondPercentChange = 0; + + result.syncPerSecondCurrent = 0; + result.syncPerSecondPercentChange = 0; + + result.clientConnectionsCurrent = 0; + result.clientConnectionsPercentChange = 0; } - //////////////////////////////////////////////////////////////////////////////// - /// @brief computeStatisticsLong - //////////////////////////////////////////////////////////////////////////////// + // add physical memory + var ss = internal.serverStatistics(); - function computeStatisticsLong (attrs, clusterId) { + result.physicalMemory = ss.physicalMemory; - var short = { times: [] }; + // add next start time + if (lastRaw === null) { + result.nextStart = internal.time(); + result.waitFor = STATISTICS_INTERVAL; + } + else { + result.nextStart = lastRaw.time; + result.waitFor = (lastRaw.time + STATISTICS_INTERVAL) - internal.time(); + } +} - var filter = ""; +//////////////////////////////////////////////////////////////////////////////// +/// @brief computeStatisticsRaw15M +//////////////////////////////////////////////////////////////////////////////// - if (clusterId !== undefined) { - filter = " FILTER s.clusterId == @clusterId "; - } +function computeStatisticsRaw15M (result, start, clusterId) { - var values = db._query( - "FOR s IN _statistics " + var filter = ""; + + if (clusterId !== undefined) { + filter = " FILTER s.clusterId == @clusterId "; + } + + var values = db._query( + "FOR s IN _statistics15 " + + " FILTER s.time > @start " + filter - + " SORT s.time " - + " return s", - { clusterId: clusterId }); - - computeStatisticsValues(short, values, attrs); - - var filter2 = ""; - var end = 0; - - if (short.times.length !== 0) { - filter2 = " FILTER s.time < @end "; - end = short.times[0]; - } - - values = db._query( - "FOR s IN _statistics15 " - + filter - + filter2 + " SORT s.time " + " return s", - { end: end, clusterId: clusterId }); + { start: start - 2 * STATISTICS_HISTORY_INTERVAL, clusterId: clusterId }); - var long = { times: [] }; + var lastRaw = null; + var lastRaw2 = null; - computeStatisticsValues(long, values, attrs); + // read the last entries + while (values.hasNext()) { + var stat = values.next(); - var key; - - for (key in attrs) { - if (attrs.hasOwnProperty(key) && long.hasOwnProperty(key)) { - long[key] = long[key].concat(short[key]); - } - } - - if (! attrs.times) { - delete long.times; - } - - return long; + lastRaw2 = lastRaw; + lastRaw = stat; } - // ----------------------------------------------------------------------------- - // --SECTION-- public routes - // ----------------------------------------------------------------------------- + // have at least one entry, use it + if (lastRaw !== null) { + result.numberOfThreads15M = lastRaw.system.numberOfThreads; + result.numberOfThreads15MPercentChange = percentChange(lastRaw, lastRaw2, 'system', 'numberOfThreads'); - controller.activateSessions({ - autoCreateSession: false, - cookie: {name: "arango_sid_" + db._name()} - }); + result.virtualSize15M = lastRaw.system.virtualSize; + result.virtualSize15MPercentChange = percentChange(lastRaw, lastRaw2, 'system', 'virtualSize'); - controller.allRoutes - .errorResponse(UnauthorizedError, 401, "unauthorized") - .onlyIf(function (req) { - if (!internal.options()["server.disable-authentication"] && (!req.session || !req.session.get('uid'))) { - throw new UnauthorizedError(); + result.asyncPerSecond15M = lastRaw.http.requestsAsyncPerSecond; + result.asyncPerSecond15MPercentChange = percentChange(lastRaw, lastRaw2, 'http', 'requestsAsyncPerSecond'); + + result.syncPerSecond15M = lastRaw.http.requestsTotalPerSecond - lastRaw.http.requestsAsyncPerSecond; + result.syncPerSecond15MPercentChange + = percentChange2(lastRaw, lastRaw2, 'http', 'requestsTotalPerSecond', 'requestsAsyncPerSecond'); + + result.clientConnections15M = lastRaw.client.httpConnections; + result.clientConnections15MPercentChange = percentChange(lastRaw, lastRaw2, 'client', 'httpConnections'); + } + + // have no entry, add nulls + else { + var ps = internal.processStatistics(); + + result.numberOfThreads15M = ps.numberOfThreads; + result.numberOfThreads15MPercentChange = 0; + + result.virtualSize15M = ps.virtualSize; + result.virtualSize15MPercentChange = 0; + + result.asyncPerSecond15M = 0; + result.asyncPerSecond15MPercentChange = 0; + + result.syncPerSecond15M = 0; + result.syncPerSecond15MPercentChange = 0; + + result.clientConnections15M = 0; + result.clientConnections15MPercentChange = 0; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief computeStatisticsShort +//////////////////////////////////////////////////////////////////////////////// + +function computeStatisticsShort (start, clusterId) { + + var result = {}; + + computeStatisticsRaw(result, start, clusterId); + computeStatisticsRaw15M(result, start, clusterId); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief computeStatisticsValues +//////////////////////////////////////////////////////////////////////////////// + +function computeStatisticsValues (result, values, attrs) { + + var key; + + for (key in attrs) { + if (attrs.hasOwnProperty(key)) { + result[key] = []; } - }); + } - //////////////////////////////////////////////////////////////////////////////// - /// @brief short term history - //////////////////////////////////////////////////////////////////////////////// + while (values.hasNext()) { + var stat = values.next(); - controller.get("short", function (req, res) { - var start = req.params("start"); - var dbServer = req.params("DBserver"); + result.times.push(stat.time); - if (start !== null && start !== undefined) { - start = parseFloat(start, 10); - } - else { - start = internal.time() - STATISTICS_INTERVAL * 10; - } + for (key in attrs) { + if (attrs.hasOwnProperty(key) && STAT_SERIES.hasOwnProperty(key)) { + var path = STAT_SERIES[key]; - var clusterId; - - if (dbServer === undefined) { - if (cluster.isCluster()) { - clusterId = ArangoServerState.id(); + result[key].push(stat[path[0]][path[1]]); } } - else { - clusterId = dbServer; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief computeStatisticsLong +//////////////////////////////////////////////////////////////////////////////// + +function computeStatisticsLong (attrs, clusterId) { + + var short = { times: [] }; + + var filter = ""; + + if (clusterId !== undefined) { + filter = " FILTER s.clusterId == @clusterId "; + } + + var values = db._query( + "FOR s IN _statistics " + + filter + + " SORT s.time " + + " return s", + { clusterId: clusterId }); + + computeStatisticsValues(short, values, attrs); + + var filter2 = ""; + var end = 0; + + if (short.times.length !== 0) { + filter2 = " FILTER s.time < @end "; + end = short.times[0]; + } + + values = db._query( + "FOR s IN _statistics15 " + + filter + + filter2 + + " SORT s.time " + + " return s", + { end: end, clusterId: clusterId }); + + var long = { times: [] }; + + computeStatisticsValues(long, values, attrs); + + var key; + + for (key in attrs) { + if (attrs.hasOwnProperty(key) && long.hasOwnProperty(key)) { + long[key] = long[key].concat(short[key]); + } + } + + if (!attrs.times) { + delete long.times; + } + + 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) { + var start = req.params("start"); + var clusterId = req.params("DBserver"); + var series = computeStatisticsShort(start, clusterId); + res.json(series); +}) +.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) { + var filter = req.params("filter"); + var clusterId = req.params("DBserver"); + + var attrs = {}; + var s = filter.split(","); + var i; + + for (i = 0; i < s.length; ++i) { + attrs[s[i]] = true; + } + + var series = computeStatisticsLong(attrs, clusterId); + res.json(series); +}) +.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) { + if (!cluster.isCoordinator()) { + throw new httperr.Forbidden("only allowed on coordinator"); + } + + var DBserver = req.parameters.DBserver; + var type = req.parameters.type; + var coord = { coordTransactionID: ArangoClusterInfo.uniqid() }; + var options = { coordTransactionID: coord.coordTransactionID, timeout: 10 }; + + if (type !== "short" && type !== "long") { + type = "short"; + } + + var url = "/_admin/statistics/" + type; + var sep = "?"; + + if (req.parameters.hasOwnProperty("start")) { + url += sep + "start=" + encodeURIComponent(req.params("start")); + sep = "&"; + } + + if (req.parameters.hasOwnProperty("filter")) { + url += sep + "filter=" + encodeURIComponent(req.params("filter")); + sep = "&"; + } + + url += sep + "DBserver=" + encodeURIComponent(DBserver); + + var op = ArangoClusterComm.asyncRequest( + "GET", + "server:" + DBserver, + "_system", + url, + "", + {}, + options + ); + + var r = ArangoClusterComm.wait(op); + + if (r.status === "RECEIVED") { + res.set("content-type", "application/json; charset=utf-8"); + res.body = r.body; + } else if (r.status === "TIMEOUT") { + throw new httperr.BadRequest("operation timed out"); + } else { + var body; + + try { + body = JSON.parse(r.body); + } catch (e) { + // noop } - var series = computeStatisticsShort(start, clusterId); - - res.json(series); - }).summary("Returns the statistics") - .notes("This function is used to get the statistics history."); - - //////////////////////////////////////////////////////////////////////////////// - /// @brief long term history - //////////////////////////////////////////////////////////////////////////////// - - controller.get("long", function (req, res) { - var filter = req.params("filter"); - var dbServer = req.params("DBserver"); - - if (filter === undefined) { - actions.resultError(req, res, actions.HTTP_BAD, "required parameter 'filter' was not given"); - } - - var attrs = {}; - var s = filter.split(","); - var i; - - for (i = 0; i < s.length; ++i) { - attrs[s[i]] = true; - } - - var clusterId; - - if (dbServer === undefined) { - if (cluster.isCluster()) { - clusterId = ArangoServerState.id(); - } - } - else { - clusterId = dbServer; - } - - var series = computeStatisticsLong(attrs, clusterId); - - res.json(series); - }).summary("Returns the aggregated history") - .notes("This function is used to get the aggregated statistics history."); - - //////////////////////////////////////////////////////////////////////////////// - /// @brief cluster statistics history - //////////////////////////////////////////////////////////////////////////////// - - 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 type = req.parameters.type; - var coord = { coordTransactionID: ArangoClusterInfo.uniqid() }; - var options = { coordTransactionID: coord.coordTransactionID, timeout:10 }; - - if (type !== "short" && type !== "long") { - type = "short"; - } - - var url = "/_admin/statistics/" + type; - var sep = "?"; - - if (req.parameters.hasOwnProperty("start")) { - url += sep + "start=" + encodeURIComponent(req.params("start")); - sep = "&"; - } - - if (req.parameters.hasOwnProperty("filter")) { - url += sep + "filter=" + encodeURIComponent(req.params("filter")); - sep = "&"; - } - - url += sep + "DBserver=" + encodeURIComponent(DBserver); - - var op = ArangoClusterComm.asyncRequest( - "GET", - "server:"+DBserver, - "_system", - url, - "", - {}, - options); - - var r = ArangoClusterComm.wait(op); - - res.contentType = "application/json; charset=utf-8"; - - if (r.status === "RECEIVED") { - res.responseCode = actions.HTTP_OK; - res.body = r.body; - } - else if (r.status === "TIMEOUT") { - res.responseCode = actions.HTTP_BAD; - res.body = JSON.stringify( {"error":true, "errorMessage": "operation timed out"}); - } - else { - res.responseCode = actions.HTTP_BAD; - - var bodyobj; - - try { - bodyobj = JSON.parse(r.body); - } - catch (err) { - } - - res.body = JSON.stringify({ - "error":true, - "errorMessage": "error from DBserver, possibly DBserver unknown", - "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."); - -}()); -// ----------------------------------------------------------------------------- -// --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- - -// Local Variables: -// mode: outline-minor -// outline-regexp: "/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @\\}" -// End: + throw Object.assign( + new httperr.BadRequest("error from DBserver, possibly DBserver unknown"), + {extra: {body}} + ); + } +}) +.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.");