mirror of https://gitee.com/bigwinds/arangodb
Merge branch 'devel' of ssh://github.com/ArangoDB/ArangoDB into devel
This commit is contained in:
commit
638ce07e6d
|
@ -135,11 +135,46 @@ string *text*. Positions start at 0.
|
||||||
is not contained in *text*, -1 is returned.
|
is not contained in *text*, -1 is returned.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
FIND_LAST("foobarbaz", "ba"), // 6
|
FIND_LAST("foobarbaz", "ba") // 6
|
||||||
FIND_LAST("foobarbaz", "ba", 7), // -1
|
FIND_LAST("foobarbaz", "ba", 7) // -1
|
||||||
FIND_LAST("foobarbaz", "ba", 0, 4) // 3
|
FIND_LAST("foobarbaz", "ba", 0, 4) // 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!SUBSECTION JSON_PARSE()
|
||||||
|
|
||||||
|
`JSON_PARSE(text) → value`
|
||||||
|
|
||||||
|
Return an AQL value described by the JSON-encoded input string.
|
||||||
|
|
||||||
|
- **text** (string): the string to parse as JSON
|
||||||
|
- returns **value** (mixed): the value corresponding to the given JSON text.
|
||||||
|
For input values that are no valid JSON strings, the function will return *null*.
|
||||||
|
|
||||||
|
```js
|
||||||
|
JSON_PARSE("123") // 123
|
||||||
|
JSON_PARSE("[ true, false, 2 ]") // [ true, false, 2 ]
|
||||||
|
JSON_PARSE("\\\"abc\\\"") // "abc"
|
||||||
|
JSON_PARSE("{\\\"a\\\": 1}") // { a : 1 }
|
||||||
|
JSON_PARSE("abc") // null
|
||||||
|
```
|
||||||
|
|
||||||
|
!SUBSECTION JSON_STRINGIFY()
|
||||||
|
|
||||||
|
`JSON_STRINGIFY(value) → text`
|
||||||
|
|
||||||
|
Return a JSON string representation of the input value.
|
||||||
|
|
||||||
|
- **value** (mixed): the value to convert to a JSON string
|
||||||
|
- returns **text** (string): the JSON string representing *value*.
|
||||||
|
For input values that cannot be converted to JSON, the function
|
||||||
|
will return *null*.
|
||||||
|
|
||||||
|
```js
|
||||||
|
JSON_STRINGIFY("1") // "1"
|
||||||
|
JSON_STRINGIFY("abc") // "\"abc\""
|
||||||
|
JSON_STRINGIFY("[1, 2, 3]") // "[1,2,3]"
|
||||||
|
```
|
||||||
|
|
||||||
!SUBSECTION LEFT()
|
!SUBSECTION LEFT()
|
||||||
|
|
||||||
`LEFT(value, length) → substring`
|
`LEFT(value, length) → substring`
|
||||||
|
@ -160,7 +195,6 @@ LEFT("foobar", 10) // "foobar"
|
||||||
|
|
||||||
`LENGTH(str) → length`
|
`LENGTH(str) → length`
|
||||||
|
|
||||||
|
|
||||||
Determine the character length of a string.
|
Determine the character length of a string.
|
||||||
|
|
||||||
- **str** (string): a string. If a number is passed, it will be casted to string first.
|
- **str** (string): a string. If a number is passed, it will be casted to string first.
|
||||||
|
|
|
@ -50,6 +50,10 @@
|
||||||
* [Sharding](ShardingInterface/README.md)
|
* [Sharding](ShardingInterface/README.md)
|
||||||
* [Monitoring](AdministrationAndMonitoring/README.md)
|
* [Monitoring](AdministrationAndMonitoring/README.md)
|
||||||
* [Endpoints](Endpoints/README.md)
|
* [Endpoints](Endpoints/README.md)
|
||||||
|
# * [Foxx Services](Foxx/README.md)
|
||||||
|
# * [Management](Foxx/Management.md)
|
||||||
|
# * [Configuration](Foxx/Configuration.md)
|
||||||
|
# * [Dependencies](Foxx/Dependencies.md)
|
||||||
* [User Management](UserManagement/README.md)
|
* [User Management](UserManagement/README.md)
|
||||||
* [Tasks](Tasks/README.md)
|
* [Tasks](Tasks/README.md)
|
||||||
* [Agency](Agency/README.md)
|
* [Agency](Agency/README.md)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
!CHAPTER Configuration
|
|
@ -0,0 +1 @@
|
||||||
|
!CHAPTER Dependencies
|
|
@ -0,0 +1,19 @@
|
||||||
|
!CHAPTER Management
|
||||||
|
|
||||||
|
!SECTION List installed services
|
||||||
|
|
||||||
|
!SECTION Install a new service
|
||||||
|
|
||||||
|
!SECTION Upgrade an installed service
|
||||||
|
|
||||||
|
!SECTION Replace an installed service
|
||||||
|
|
||||||
|
!SECTION Uninstall a service
|
||||||
|
|
||||||
|
!SECTION Run a service's tests
|
||||||
|
|
||||||
|
!SECTION Run a service script
|
||||||
|
|
||||||
|
!SECTION Enable development mode
|
||||||
|
|
||||||
|
!SECTION Disable development mode
|
|
@ -0,0 +1,3 @@
|
||||||
|
!CHAPTER HTTP Interface for Foxx Services
|
||||||
|
|
||||||
|
This chapter describes the REST interface for managing [Foxx services](../../Manual/Foxx/index.html).
|
|
@ -55,9 +55,9 @@ On success an object with the following attributes is returned:
|
||||||
generating keys and supplying own key values in the *_key* attribute
|
generating keys and supplying own key values in the *_key* attribute
|
||||||
of documents is considered an error.
|
of documents is considered an error.
|
||||||
|
|
||||||
**Note**: some other collection properties, such as *type*, *isVolatile*,
|
**Note**: except for *waitForSync*, *journalSize* and *name*, collection
|
||||||
*numberOfShards* or *shardKeys* cannot be changed once a collection is
|
properties **cannot be changed** once a collection is created. To rename
|
||||||
created.
|
a collection, the rename endpoint must be used.
|
||||||
|
|
||||||
@RESTRETURNCODES
|
@RESTRETURNCODES
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ endmacro ()
|
||||||
|
|
||||||
# installs a readme file converting EOL ----------------------------------------
|
# installs a readme file converting EOL ----------------------------------------
|
||||||
macro (install_readme input output)
|
macro (install_readme input output)
|
||||||
set(where "${CMAKE_INSTALL_FULL_DOCDIR}")
|
set(where "${CMAKE_INSTALL_DOCDIR}")
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
# the windows installer contains the readme in the top level directory:
|
# the windows installer contains the readme in the top level directory:
|
||||||
set(where ".")
|
set(where ".")
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
// /
|
// /
|
||||||
// / DISCLAIMER
|
// / DISCLAIMER
|
||||||
// /
|
// /
|
||||||
// / Copyright 2014 ArangoDB GmbH, Cologne, Germany
|
// / Copyright 2016 ArangoDB GmbH, Cologne, Germany
|
||||||
// /
|
// /
|
||||||
// / Licensed under the Apache License, Version 2.0 (the "License")
|
// / Licensed under the Apache License, Version 2.0 (the "License")
|
||||||
// / you may not use this file except in compliance with the License.
|
// / you may not use this file except in compliance with the License.
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
|
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||||
// /
|
// /
|
||||||
// / @author Dr. Frank Celler
|
// / @author Dr. Frank Celler
|
||||||
// / @author Copyright 2014, ArangoDB GmbH, Cologne, Germany
|
// / @author Copyright 2014-2016, ArangoDB GmbH, Cologne, Germany
|
||||||
// / @author Copyright 2012, triAGENS GmbH, Cologne, Germany
|
// / @author Copyright 2012, triAGENS GmbH, Cologne, Germany
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ var foxxManager = require('@arangodb/foxx/manager');
|
||||||
var easyPostCallback = actions.easyPostCallback;
|
var easyPostCallback = actions.easyPostCallback;
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief sets up a Foxx application
|
// / @brief sets up a Foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -52,7 +52,7 @@ actions.defineHttp({
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief tears down a Foxx application
|
// / @brief tears down a Foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -70,7 +70,7 @@ actions.defineHttp({
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief installs a Foxx application
|
// / @brief installs a Foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -83,13 +83,13 @@ actions.defineHttp({
|
||||||
var appInfo = body.appInfo;
|
var appInfo = body.appInfo;
|
||||||
var mount = body.mount;
|
var mount = body.mount;
|
||||||
var options = body.options;
|
var options = body.options;
|
||||||
return foxxManager.install(appInfo, mount, options);
|
return foxxManager.install(appInfo, mount, options).simpleJSON();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief uninstalls a Foxx application
|
// / @brief uninstalls a Foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -102,13 +102,13 @@ actions.defineHttp({
|
||||||
var mount = body.mount;
|
var mount = body.mount;
|
||||||
var options = body.options || {};
|
var options = body.options || {};
|
||||||
|
|
||||||
return foxxManager.uninstall(mount, options);
|
return foxxManager.uninstall(mount, options).simpleJSON();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief replaces a Foxx application
|
// / @brief replaces a Foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -122,13 +122,13 @@ actions.defineHttp({
|
||||||
var mount = body.mount;
|
var mount = body.mount;
|
||||||
var options = body.options;
|
var options = body.options;
|
||||||
|
|
||||||
return foxxManager.replace(appInfo, mount, options);
|
return foxxManager.replace(appInfo, mount, options).simpleJSON();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief upgrades a Foxx application
|
// / @brief upgrades a Foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -142,13 +142,13 @@ actions.defineHttp({
|
||||||
var mount = body.mount;
|
var mount = body.mount;
|
||||||
var options = body.options;
|
var options = body.options;
|
||||||
|
|
||||||
return foxxManager.upgrade(appInfo, mount, options);
|
return foxxManager.upgrade(appInfo, mount, options).simpleJSON();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief configures a Foxx application
|
// / @brief configures a Foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -163,14 +163,14 @@ actions.defineHttp({
|
||||||
if (options && options.configuration) {
|
if (options && options.configuration) {
|
||||||
options = options.configuration;
|
options = options.configuration;
|
||||||
}
|
}
|
||||||
|
foxxManager.setConfiguration(mount, {configuration: options || {}});
|
||||||
return foxxManager.configure(mount, {configuration: options || {}});
|
return foxxManager.lookupService(mount).simpleJSON();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief Gets the configuration of a Foxx application
|
// / @brief Gets the configuration of a Foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -188,7 +188,7 @@ actions.defineHttp({
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief configures a Foxx application's dependencies
|
// / @brief configures a Foxx service's dependencies
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -201,13 +201,14 @@ actions.defineHttp({
|
||||||
var mount = body.mount;
|
var mount = body.mount;
|
||||||
var options = body.options;
|
var options = body.options;
|
||||||
|
|
||||||
return foxxManager.updateDeps(mount, {dependencies: options || {}});
|
foxxManager.setDependencies(mount, {dependencies: options || {}});
|
||||||
|
return foxxManager.lookupService(mount).simpleJSON();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief Gets the dependencies of a Foxx application
|
// / @brief Gets the dependencies of a Foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -219,13 +220,24 @@ actions.defineHttp({
|
||||||
callback: function (body) {
|
callback: function (body) {
|
||||||
var mount = body.mount;
|
var mount = body.mount;
|
||||||
|
|
||||||
return foxxManager.dependencies(mount);
|
const deps = foxxManager.dependencies(mount);
|
||||||
|
for (const key of Object.keys(deps)) {
|
||||||
|
const dep = deps[key];
|
||||||
|
deps[key] = {
|
||||||
|
definition: dep,
|
||||||
|
title: dep.title,
|
||||||
|
current: dep.current
|
||||||
|
};
|
||||||
|
delete dep.title;
|
||||||
|
delete dep.current;
|
||||||
|
}
|
||||||
|
return deps;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief Toggles the development mode of a foxx application
|
// / @brief Toggles the development mode of a foxx service
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
actions.defineHttp({
|
actions.defineHttp({
|
||||||
|
@ -238,9 +250,9 @@ actions.defineHttp({
|
||||||
var mount = body.mount;
|
var mount = body.mount;
|
||||||
var activate = body.activate;
|
var activate = body.activate;
|
||||||
if (activate) {
|
if (activate) {
|
||||||
return foxxManager.development(mount);
|
return foxxManager.development(mount).simpleJSON();
|
||||||
} else {
|
} else {
|
||||||
return foxxManager.production(mount);
|
return foxxManager.production(mount).simpleJSON();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -125,7 +125,7 @@ installer.use(function (req, res, next) {
|
||||||
const configuration = FoxxManager.configuration(mount);
|
const configuration = FoxxManager.configuration(mount);
|
||||||
res.json(Object.assign(
|
res.json(Object.assign(
|
||||||
{error: false, configuration},
|
{error: false, configuration},
|
||||||
service
|
service.simpleJSON()
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ foxxRouter.delete('/', function (req, res) {
|
||||||
});
|
});
|
||||||
res.json(Object.assign(
|
res.json(Object.assign(
|
||||||
{error: false},
|
{error: false},
|
||||||
service
|
service.simpleJSON()
|
||||||
));
|
));
|
||||||
})
|
})
|
||||||
.queryParam('teardown', joi.boolean().default(true))
|
.queryParam('teardown', joi.boolean().default(true))
|
||||||
|
@ -238,7 +238,8 @@ foxxRouter.get('/config', function (req, res) {
|
||||||
foxxRouter.patch('/config', function (req, res) {
|
foxxRouter.patch('/config', function (req, res) {
|
||||||
const mount = decodeURIComponent(req.queryParams.mount);
|
const mount = decodeURIComponent(req.queryParams.mount);
|
||||||
const configuration = req.body;
|
const configuration = req.body;
|
||||||
res.json(FoxxManager.configure(mount, {configuration}));
|
FoxxManager.setConfiguration(mount, {configuration});
|
||||||
|
res.json(FoxxManager.configuration(mount));
|
||||||
})
|
})
|
||||||
.body(joi.object().optional(), 'Configuration to apply.')
|
.body(joi.object().optional(), 'Configuration to apply.')
|
||||||
.summary('Set the configuration for a service')
|
.summary('Set the configuration for a service')
|
||||||
|
@ -249,7 +250,18 @@ foxxRouter.patch('/config', function (req, res) {
|
||||||
|
|
||||||
foxxRouter.get('/deps', function (req, res) {
|
foxxRouter.get('/deps', function (req, res) {
|
||||||
const mount = decodeURIComponent(req.queryParams.mount);
|
const mount = decodeURIComponent(req.queryParams.mount);
|
||||||
res.json(FoxxManager.dependencies(mount));
|
const deps = FoxxManager.dependencies(mount);
|
||||||
|
for (const key of Object.keys(deps)) {
|
||||||
|
const dep = deps[key];
|
||||||
|
deps[key] = {
|
||||||
|
definition: dep,
|
||||||
|
title: dep.title,
|
||||||
|
current: dep.current
|
||||||
|
};
|
||||||
|
delete dep.title;
|
||||||
|
delete dep.current;
|
||||||
|
}
|
||||||
|
res.json(deps);
|
||||||
})
|
})
|
||||||
.summary('Get the dependencies for a service')
|
.summary('Get the dependencies for a service')
|
||||||
.description(dd`
|
.description(dd`
|
||||||
|
@ -260,9 +272,10 @@ foxxRouter.get('/deps', function (req, res) {
|
||||||
foxxRouter.patch('/deps', function (req, res) {
|
foxxRouter.patch('/deps', function (req, res) {
|
||||||
const mount = decodeURIComponent(req.queryParams.mount);
|
const mount = decodeURIComponent(req.queryParams.mount);
|
||||||
const dependencies = req.body;
|
const dependencies = req.body;
|
||||||
res.json(FoxxManager.updateDeps(mount, {dependencies}));
|
FoxxManager.setDependencies(mount, {dependencies});
|
||||||
|
res.json(FoxxManager.dependencies(mount));
|
||||||
})
|
})
|
||||||
.body(joi.object().optional(), 'Dependency settings to apply.')
|
.body(joi.object().optional(), 'Dependency options to apply.')
|
||||||
.summary('Set the dependencies for a service')
|
.summary('Set the dependencies for a service')
|
||||||
.description(dd`
|
.description(dd`
|
||||||
Used to overwrite the dependencies options for services.
|
Used to overwrite the dependencies options for services.
|
||||||
|
@ -300,7 +313,7 @@ foxxRouter.post('/scripts/:name', function (req, res) {
|
||||||
foxxRouter.patch('/devel', function (req, res) {
|
foxxRouter.patch('/devel', function (req, res) {
|
||||||
const mount = decodeURIComponent(req.queryParams.mount);
|
const mount = decodeURIComponent(req.queryParams.mount);
|
||||||
const activate = Boolean(req.body);
|
const activate = Boolean(req.body);
|
||||||
res.json(FoxxManager[activate ? 'development' : 'production'](mount));
|
res.json(FoxxManager[activate ? 'development' : 'production'](mount).simpleJSON());
|
||||||
})
|
})
|
||||||
.body(joi.boolean().optional())
|
.body(joi.boolean().optional())
|
||||||
.summary('Activate/Deactivate development mode for a service')
|
.summary('Activate/Deactivate development mode for a service')
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="navlogo">
|
<div class="navlogo">
|
||||||
<a class="logo big" href="#"><img id="ArangoDBLogo" class="arangodbLogo" src="img/arangodb-edition-optimized.svg"/></a>
|
<a class="logo big" href="#"><img id="ArangoDBLogo" class="arangodbLogo" src="img/arangodb-edition-optimized.svg"/></a>
|
||||||
<a class="logo small" href="#"><img class="arangodbLogo" src="img/arangodb_logo_small.png"/></a>
|
<a class="logo small" href="#"><img class="arangodbLogo" src="img/arangodb_logo_small.png"/></a>
|
||||||
<a class="version"><span>VERSION: </span><span id="currentVersion"></span></a>
|
<a class="version"><span id="currentVersion"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div id="progressPlaceholderIcon"></div> -->
|
<!-- <div id="progressPlaceholderIcon"></div> -->
|
||||||
<div class="statmenu" id="statisticBar">
|
<div class="statmenu" id="statisticBar">
|
||||||
|
|
|
@ -139,7 +139,7 @@
|
||||||
window.versionHelper.fromString(data.version);
|
window.versionHelper.fromString(data.version);
|
||||||
|
|
||||||
$('.navbar #currentVersion').html(
|
$('.navbar #currentVersion').html(
|
||||||
' ' + data.version.substr(0, 7) + '<i class="fa fa-exclamation-circle"></i>'
|
data.version.substr(0, 7) + '<i class="fa fa-exclamation-circle"></i>'
|
||||||
);
|
);
|
||||||
|
|
||||||
window.parseVersions = function (json) {
|
window.parseVersions = function (json) {
|
||||||
|
|
|
@ -455,24 +455,35 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
_.each(obj.vertices, function (node) {
|
_.each(obj.vertices, function (node) {
|
||||||
vertices[node._id] = {
|
if (node !== null) {
|
||||||
id: node._id,
|
vertices[node._id] = {
|
||||||
label: node._key,
|
id: node._id,
|
||||||
// size: 0.3,
|
label: node._key,
|
||||||
color: color,
|
size: 0.3,
|
||||||
x: Math.random(),
|
color: color,
|
||||||
y: Math.random()
|
x: Math.random(),
|
||||||
};
|
y: Math.random()
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var nodeIds = [];
|
||||||
_.each(vertices, function (node) {
|
_.each(vertices, function (node) {
|
||||||
returnObj.nodes.push(node);
|
returnObj.nodes.push(node);
|
||||||
|
nodeIds.push(node.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
_.each(edges, function (edge) {
|
_.each(edges, function (edge) {
|
||||||
returnObj.edges.push(edge);
|
if (nodeIds.includes(edge.source) && nodeIds.includes(edge.target)) {
|
||||||
|
returnObj.edges.push(edge);
|
||||||
|
}
|
||||||
|
/* how to handle not correct data?
|
||||||
|
else {
|
||||||
|
console.log('target to from is missing');
|
||||||
|
}
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
} else if (type === 'array') {
|
} else if (type === 'array') {
|
||||||
_.each(data, function (edge) {
|
_.each(data, function (edge) {
|
||||||
|
|
|
@ -257,7 +257,7 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
top: 120px;
|
top: 135px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"description": "ArangoDB Admin Web Interface",
|
"description": "ArangoDB Admin Web Interface",
|
||||||
"author": "ArangoDB GmbH",
|
"author": "ArangoDB GmbH",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"license": "Apache License, Version 2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"arangodb": "^3.0.0-0 || ^3.0.0"
|
"arangodb": "^3.0.0-0 || ^3.0.0"
|
||||||
|
|
|
@ -0,0 +1,337 @@
|
||||||
|
'use strict';
|
||||||
|
const _ = require('lodash');
|
||||||
|
const dd = require('dedent');
|
||||||
|
const fs = require('fs');
|
||||||
|
const joi = require('joi');
|
||||||
|
const semver = require('semver');
|
||||||
|
|
||||||
|
const fm = require('@arangodb/foxx/manager');
|
||||||
|
const fmu = require('@arangodb/foxx/manager-utils');
|
||||||
|
const createRouter = require('@arangodb/foxx/router');
|
||||||
|
const reporters = Object.keys(require('@arangodb/mocha').reporters);
|
||||||
|
const schemas = require('./schemas');
|
||||||
|
|
||||||
|
const router = createRouter();
|
||||||
|
module.context.registerType('multipart/form-data', require('./multipart'));
|
||||||
|
module.context.use(router);
|
||||||
|
|
||||||
|
|
||||||
|
const serviceToJson = (service) => (
|
||||||
|
{
|
||||||
|
mount: service.mount,
|
||||||
|
path: service.basePath,
|
||||||
|
name: service.manifest.name,
|
||||||
|
version: service.manifest.version,
|
||||||
|
development: service.isDevelopment,
|
||||||
|
legacy: service.legacy,
|
||||||
|
manifest: service.manifest,
|
||||||
|
options: _.pick(service.options, ['configuration', 'dependencies'])
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function isLegacy (service) {
|
||||||
|
const range = service.manifest.engines && service.manifest.engines.arangodb;
|
||||||
|
return range ? semver.gtr('3.0.0', range) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeUploadToTempFile (buffer) {
|
||||||
|
const filename = fs.getTempFile('foxx-manager', true);
|
||||||
|
fs.writeFileSync(filename, buffer);
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
router.get((req, res) => {
|
||||||
|
res.json(
|
||||||
|
fmu.getStorage().toArray()
|
||||||
|
.map((service) => (
|
||||||
|
{
|
||||||
|
mount: service.mount,
|
||||||
|
name: service.manifest.name,
|
||||||
|
version: service.manifest.version,
|
||||||
|
development: service.isDevelopment,
|
||||||
|
legacy: isLegacy(service)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.response(200, joi.array().items(schemas.shortInfo).required(), `Array of service descriptions.`)
|
||||||
|
.summary(`List installed services`)
|
||||||
|
.description(dd`
|
||||||
|
Fetches a list of services installed in the current database.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
router.post((req, res) => {
|
||||||
|
let source = req.body.source;
|
||||||
|
if (source instanceof Buffer) {
|
||||||
|
source = writeUploadToTempFile(source);
|
||||||
|
}
|
||||||
|
const service = fm.install(
|
||||||
|
source,
|
||||||
|
req.queryParams.mount,
|
||||||
|
_.omit(req.queryParams, ['mount'])
|
||||||
|
);
|
||||||
|
res.json(serviceToJson(service));
|
||||||
|
})
|
||||||
|
.body(schemas.service, ['multipart/form-data', 'application/json'], `Service to be installed.`)
|
||||||
|
.queryParam('mount', schemas.mount, `Mount path the service should be installed at.`)
|
||||||
|
.queryParam('setup', schemas.flag.default(true), `Run the service's setup script.`)
|
||||||
|
.queryParam('legacy', schemas.flag.default(false), `Service should be installed in 2.8 legacy compatibility mode.`)
|
||||||
|
.response(201, schemas.fullInfo, `Description of the installed service.`)
|
||||||
|
.summary(`Install new service`)
|
||||||
|
.description(dd`
|
||||||
|
Installs the given new service at the given mount path.
|
||||||
|
|
||||||
|
The service source can be specified as either an absolute local file path,
|
||||||
|
a fully qualified URL reachable from the database server,
|
||||||
|
or as a binary zip file using multipart form upload.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
const instanceRouter = createRouter();
|
||||||
|
instanceRouter.use((req, res, next) => {
|
||||||
|
const mount = req.queryParams.mount;
|
||||||
|
try {
|
||||||
|
req.service = fm.lookupService(mount);
|
||||||
|
} catch (e) {
|
||||||
|
res.throw(400, `No service installed at mount path "${mount}".`, e);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
})
|
||||||
|
.queryParam('mount', schemas.mount, `Mount path of the installed service.`);
|
||||||
|
router.use(instanceRouter);
|
||||||
|
|
||||||
|
|
||||||
|
const serviceRouter = createRouter();
|
||||||
|
instanceRouter.use('/service', serviceRouter);
|
||||||
|
|
||||||
|
serviceRouter.get((req, res) => {
|
||||||
|
res.json(serviceToJson(req.service));
|
||||||
|
})
|
||||||
|
.response(200, schemas.fullInfo, `Description of the service.`)
|
||||||
|
.summary(`Service description`)
|
||||||
|
.description(dd`
|
||||||
|
Fetches detailed information for the service at the given mount path.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
serviceRouter.patch((req, res) => {
|
||||||
|
let source = req.body.source;
|
||||||
|
if (source instanceof Buffer) {
|
||||||
|
source = writeUploadToTempFile(source);
|
||||||
|
}
|
||||||
|
const service = fm.upgrade(
|
||||||
|
source,
|
||||||
|
req.queryParams.mount,
|
||||||
|
_.omit(req.queryParams, ['mount'])
|
||||||
|
);
|
||||||
|
res.json(serviceToJson(service));
|
||||||
|
})
|
||||||
|
.body(schemas.service, ['multipart/form-data', 'application/json'], `Service to be installed.`)
|
||||||
|
.queryParam('teardown', schemas.flag.default(false), `Run the old service's teardown script.`)
|
||||||
|
.queryParam('setup', schemas.flag.default(true), `Run the new service's setup script.`)
|
||||||
|
.queryParam('legacy', schemas.flag.default(false), `Service should be installed in 2.8 legacy compatibility mode.`)
|
||||||
|
.response(200, schemas.fullInfo, `Description of the new service.`)
|
||||||
|
.summary(`Upgrade service`)
|
||||||
|
.description(dd`
|
||||||
|
Installs the given new service on top of the service currently installed at the given mount path.
|
||||||
|
This is only recommended for switching between different versions of the same service.
|
||||||
|
|
||||||
|
The service source can be specified as either an absolute local file path,
|
||||||
|
a fully qualified URL reachable from the database server,
|
||||||
|
or as a binary zip file using multipart form upload.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
serviceRouter.put((req, res) => {
|
||||||
|
let source = req.body.source;
|
||||||
|
if (source instanceof Buffer) {
|
||||||
|
source = writeUploadToTempFile(source);
|
||||||
|
}
|
||||||
|
const service = fm.replace(
|
||||||
|
source,
|
||||||
|
req.queryParams.mount,
|
||||||
|
_.omit(req.queryParams, ['mount'])
|
||||||
|
);
|
||||||
|
res.json(serviceToJson(service));
|
||||||
|
})
|
||||||
|
.body(schemas.service, ['multipart/form-data', 'application/json'], `Service to be installed.`)
|
||||||
|
.queryParam('teardown', schemas.flag.default(true), `Run the old service's teardown script.`)
|
||||||
|
.queryParam('setup', schemas.flag.default(true), `Run the new service's setup script.`)
|
||||||
|
.queryParam('legacy', schemas.flag.default(false), `Service should be installed in 2.8 legacy compatibility mode.`)
|
||||||
|
.response(200, schemas.fullInfo, `Description of the new service.`)
|
||||||
|
.summary(`Replace service`)
|
||||||
|
.description(dd`
|
||||||
|
Removes the service at the given mount path from the database and file system.
|
||||||
|
Then installs the given new service at the same mount path.
|
||||||
|
|
||||||
|
The service source can be specified as either an absolute local file path,
|
||||||
|
a fully qualified URL reachable from the database server,
|
||||||
|
or as a binary zip file using multipart form upload.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
serviceRouter.delete((req, res) => {
|
||||||
|
fm.uninstall(
|
||||||
|
req.queryParams.mount,
|
||||||
|
_.omit(req.queryParams, ['mount'])
|
||||||
|
);
|
||||||
|
res.status(204);
|
||||||
|
})
|
||||||
|
.queryParam('teardown', schemas.flag.default(true), `Run the service's teardown script.`)
|
||||||
|
.response(204, null, `Empty response.`)
|
||||||
|
.summary(`Uninstall service`)
|
||||||
|
.description(dd`
|
||||||
|
Removes the service at the given mount path from the database and file system.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
const configRouter = createRouter();
|
||||||
|
instanceRouter.use('/configuration', configRouter)
|
||||||
|
.response(200, schemas.configs, `Configuration options of the service.`);
|
||||||
|
|
||||||
|
configRouter.get((req, res) => {
|
||||||
|
res.json(fm.configuration(req.service.mount));
|
||||||
|
})
|
||||||
|
.summary(`Get configuration options`)
|
||||||
|
.description(dd`
|
||||||
|
Fetches the current configuration for the service at the given mount path.
|
||||||
|
`);
|
||||||
|
|
||||||
|
configRouter.patch((req, res) => {
|
||||||
|
const warnings = fm.setConfiguration(req.service.mount, {
|
||||||
|
configuration: req.body,
|
||||||
|
replace: false
|
||||||
|
});
|
||||||
|
const values = fm.configuration(req.service.mount, {simple: true});
|
||||||
|
res.json({values, warnings});
|
||||||
|
})
|
||||||
|
.body(joi.object().required(), `Object mapping configuration names to values.`)
|
||||||
|
.summary(`Update configuration options`)
|
||||||
|
.description(dd`
|
||||||
|
Replaces the given service's configuration.
|
||||||
|
Any omitted options will be ignored.
|
||||||
|
`);
|
||||||
|
|
||||||
|
configRouter.put((req, res) => {
|
||||||
|
const warnings = fm.setConfiguration(req.service.mount, {
|
||||||
|
configuration: req.body,
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
const values = fm.configuration(req.service.mount, {simple: true});
|
||||||
|
res.json({values, warnings});
|
||||||
|
})
|
||||||
|
.body(joi.object().required(), `Object mapping configuration names to values.`)
|
||||||
|
.summary(`Replace configuration options`)
|
||||||
|
.description(dd`
|
||||||
|
Replaces the given service's configuration completely.
|
||||||
|
Any omitted options will be reset to their default values or marked as unconfigured.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
const depsRouter = createRouter();
|
||||||
|
instanceRouter.use('/dependencies', depsRouter)
|
||||||
|
.response(200, schemas.deps, `Dependency options of the service.`);
|
||||||
|
|
||||||
|
depsRouter.get((req, res) => {
|
||||||
|
res.json(fm.dependencies(req.service.mount));
|
||||||
|
})
|
||||||
|
.summary(`Get dependency options`)
|
||||||
|
.description(dd`
|
||||||
|
Fetches the current dependencies for service at the given mount path.
|
||||||
|
`);
|
||||||
|
|
||||||
|
depsRouter.patch((req, res) => {
|
||||||
|
const warnings = fm.setDependencies(req.service.mount, {
|
||||||
|
dependencies: req.body,
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
const values = fm.dependencies(req.service.mount, {simple: true});
|
||||||
|
res.json({values, warnings});
|
||||||
|
})
|
||||||
|
.body(joi.object().required(), `Object mapping dependency aliases to mount paths.`)
|
||||||
|
.summary(`Update dependency options`)
|
||||||
|
.description(dd`
|
||||||
|
Replaces the given service's dependencies.
|
||||||
|
Any omitted dependencies will be ignored.
|
||||||
|
`);
|
||||||
|
|
||||||
|
depsRouter.put((req, res) => {
|
||||||
|
const warnings = fm.setDependencies(req.service.mount, {
|
||||||
|
dependencies: req.body,
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
const values = fm.dependencies(req.service.mount, {simple: true});
|
||||||
|
res.json({values, warnings});
|
||||||
|
})
|
||||||
|
.body(joi.object().required(), `Object mapping dependency aliases to mount paths.`)
|
||||||
|
.summary(`Replace dependency options`)
|
||||||
|
.description(dd`
|
||||||
|
Replaces the given service's dependencies completely.
|
||||||
|
Any omitted dependencies will be disabled.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
const devRouter = createRouter();
|
||||||
|
instanceRouter.use('/development', devRouter)
|
||||||
|
.response(200, schemas.fullInfo, `Description of the service.`);
|
||||||
|
|
||||||
|
devRouter.post((req, res) => {
|
||||||
|
const service = fm.development(req.service);
|
||||||
|
res.json(serviceToJson(service));
|
||||||
|
})
|
||||||
|
.summary(`Enable development mode`)
|
||||||
|
.description(dd`
|
||||||
|
Puts the service into development mode.
|
||||||
|
The service will be re-installed from the filesystem for each request.
|
||||||
|
`);
|
||||||
|
|
||||||
|
devRouter.delete((req, res) => {
|
||||||
|
const service = fm.production(req.service);
|
||||||
|
res.json(serviceToJson(service));
|
||||||
|
})
|
||||||
|
.summary(`Disable development mode`)
|
||||||
|
.description(dd`
|
||||||
|
Puts the service at the given mount path into production mode.
|
||||||
|
Changes to the service's code will no longer be reflected automatically.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
instanceRouter.post('/run/:name', (req, res) => {
|
||||||
|
const service = req.service;
|
||||||
|
const scriptName = req.pathParams.name;
|
||||||
|
res.json(fm.runScript(scriptName, service.mount, req.body) || null);
|
||||||
|
})
|
||||||
|
.body(joi.any(), `Optional script arguments.`)
|
||||||
|
.pathParam('name', joi.string().required(), `Name of the script to run`)
|
||||||
|
.response(200, joi.any().default(null), `Script result if any.`)
|
||||||
|
.summary(`Run service script`)
|
||||||
|
.description(dd`
|
||||||
|
Runs the given script for the service at the given mount path.
|
||||||
|
Returns the exports of the script, if any.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
instanceRouter.post('/tests', (req, res) => {
|
||||||
|
const service = req.service;
|
||||||
|
const reporter = req.queryParams.reporter || null;
|
||||||
|
res.json(fm.runTests(service.mount, {reporter}));
|
||||||
|
})
|
||||||
|
.queryParam('reporter', joi.only(...reporters).optional(), `Test reporter to use`)
|
||||||
|
.response(200, joi.object(), `Test results.`)
|
||||||
|
.summary(`Run service tests`)
|
||||||
|
.description(dd`
|
||||||
|
Runs the tests for the service at the given mount path and returns the results.
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
instanceRouter.get('/readme', (req, res) => {
|
||||||
|
const service = req.service;
|
||||||
|
res.send(fm.readme(service.mount));
|
||||||
|
})
|
||||||
|
.response(200, ['text/plain'], `Raw README contents.`)
|
||||||
|
.summary(`Service README`)
|
||||||
|
.description(dd`
|
||||||
|
Fetches the service's README or README.md file's contents if any.
|
||||||
|
`);
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "foxx-manager",
|
||||||
|
"description": "Foxx management service.",
|
||||||
|
"author": "ArangoDB GmbH",
|
||||||
|
"version": "3.1.0",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
|
||||||
|
"engines": {
|
||||||
|
"arangodb": "^3.1.0"
|
||||||
|
},
|
||||||
|
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/arangodb/arangodb.git"
|
||||||
|
},
|
||||||
|
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "Alan Plum",
|
||||||
|
"email": "me@pluma.io"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"main": "index.js"
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
'use strict';
|
||||||
|
const _ = require('lodash');
|
||||||
|
const assert = require('assert');
|
||||||
|
const contentDisposition = require('content-disposition');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fromClient (body) {
|
||||||
|
assert(
|
||||||
|
Array.isArray(body) && body.every((part) => (
|
||||||
|
part && typeof part === 'object'
|
||||||
|
&& part.headers && typeof part.headers === 'object'
|
||||||
|
&& part.data instanceof Buffer
|
||||||
|
)),
|
||||||
|
`Expecting a multipart array, not ${body ? typeof body : String(body)}`
|
||||||
|
);
|
||||||
|
const parsedBody = {};
|
||||||
|
for (const part of body) {
|
||||||
|
const headers = {};
|
||||||
|
for (const key of Object.keys(part.headers)) {
|
||||||
|
headers[key.toLowerCase()] = part.headers[key];
|
||||||
|
}
|
||||||
|
const dispositionHeader = headers['content-disposition'];
|
||||||
|
if (!dispositionHeader) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const disposition = contentDisposition.parse(dispositionHeader);
|
||||||
|
if (disposition.type !== 'form-data' || !disposition.parameters.name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const filename = disposition.parameters.filename;
|
||||||
|
const type = headers['content-type'];
|
||||||
|
const value = (type || filename) ? part.data : part.data.toString('utf-8');
|
||||||
|
if (filename) {
|
||||||
|
value.filename = filename;
|
||||||
|
}
|
||||||
|
if (type || filename) {
|
||||||
|
value.headers = _.omit(headers, ['content-disposition']);
|
||||||
|
}
|
||||||
|
parsedBody[disposition.parameters.name] = value;
|
||||||
|
}
|
||||||
|
return parsedBody;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,46 @@
|
||||||
|
'use strict';
|
||||||
|
const joi = require('joi');
|
||||||
|
const fmu = require('@arangodb/foxx/manager-utils');
|
||||||
|
|
||||||
|
exports.mount = joi.string().regex(/(?:\/[-_0-9a-z]+)+/i).required()
|
||||||
|
.description(`Mount path relative to the database root`);
|
||||||
|
|
||||||
|
exports.flag = joi.alternatives().try(
|
||||||
|
joi.boolean().description(`Boolean flag`),
|
||||||
|
joi.number().integer().min(0).max(1).description(`Numeric flag for PHP compatibility`)
|
||||||
|
).default(false);
|
||||||
|
|
||||||
|
exports.shortInfo = joi.object({
|
||||||
|
mount: exports.mount.description(`Mount path of the service`),
|
||||||
|
name: joi.string().optional().description(`Name of the service`),
|
||||||
|
version: joi.string().optional().description(`Version of the service`),
|
||||||
|
development: joi.boolean().default(false).description(`Whether development mode is enabled`),
|
||||||
|
legacy: joi.boolean().default(false).description(`Whether the service is running in legacy mode`)
|
||||||
|
}).required();
|
||||||
|
|
||||||
|
exports.fullInfo = exports.shortInfo.keys({
|
||||||
|
path: joi.string().required().description(`File system path of the service`),
|
||||||
|
manifest: joi.object().required().description(`Full manifest of the service`),
|
||||||
|
options: joi.object().required().description(`Configuration and dependency option values`)
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.configs = joi.object().pattern(/.+/, joi.object({
|
||||||
|
value: joi.any().optional().description(`Current value of the configuration option`),
|
||||||
|
default: joi.any().optional().description(`Default value of the configuration option`),
|
||||||
|
type: joi.only(Object.keys(fmu.parameterTypes)).default('string').description(`Type of the configuration option`),
|
||||||
|
description: joi.string().optional().description(`Human-readable description of the configuration option`),
|
||||||
|
required: joi.boolean().default(true).description(`Whether the configuration option is required`)
|
||||||
|
}).required().description(`Configuration option`)).required();
|
||||||
|
|
||||||
|
exports.deps = joi.object().pattern(/.+/, joi.object({
|
||||||
|
name: joi.string().default('*').description(`Name of the dependency`),
|
||||||
|
version: joi.string().default('*').description(`Version of the dependency`),
|
||||||
|
required: joi.boolean().default(true).description(`Whether the dependency is required`)
|
||||||
|
}).required().description(`Dependency option`)).required();
|
||||||
|
|
||||||
|
exports.service = joi.object({
|
||||||
|
source: joi.alternatives(
|
||||||
|
joi.string().description(`Local file path or URL of the service to be installed`),
|
||||||
|
joi.object().type(Buffer).description(`Zip bundle of the service to be installed`)
|
||||||
|
).required().description(`Local file path, URL or zip bundle of the service to be installed`)
|
||||||
|
}).required();
|
|
@ -3,7 +3,7 @@
|
||||||
"description": "ArangoDB Graph Module",
|
"description": "ArangoDB Graph Module",
|
||||||
"author": "ArangoDB GmbH",
|
"author": "ArangoDB GmbH",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"license": "Apache License, Version 2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"arangodb": "^3.0.0-0 || ^3.0.0"
|
"arangodb": "^3.0.0-0 || ^3.0.0"
|
||||||
|
@ -23,4 +23,3 @@
|
||||||
|
|
||||||
"main": "gharial.js"
|
"main": "gharial.js"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,11 +46,13 @@ var pathRegex = /^((\.{0,2}(\/|\\))|(~\/)|[a-zA-Z]:\\)/;
|
||||||
|
|
||||||
const DEFAULT_REPLICATION_FACTOR_SYSTEM = internal.DEFAULT_REPLICATION_FACTOR_SYSTEM;
|
const DEFAULT_REPLICATION_FACTOR_SYSTEM = internal.DEFAULT_REPLICATION_FACTOR_SYSTEM;
|
||||||
|
|
||||||
var getReadableName = function (name) {
|
function getReadableName (name) {
|
||||||
return name.split(/([-_]|\s)+/).map(function (token) {
|
return name.charAt(0).toUpperCase() + name.substr(1)
|
||||||
return token.slice(0, 1).toUpperCase() + token.slice(1);
|
.replace(/([-_]|\s)+/g, ' ')
|
||||||
}).join(' ');
|
.replace(/([a-z])([A-Z])/g, (m) => `${m[0]} ${m[1]}`)
|
||||||
};
|
.replace(/([A-Z])([A-Z][a-z])/g, (m) => `${m[0]} ${m[1]}`)
|
||||||
|
.replace(/\s([a-z])/g, (m) => ` ${m[0].toUpperCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
var getStorage = function () {
|
var getStorage = function () {
|
||||||
var c = db._collection('_apps');
|
var c = db._collection('_apps');
|
||||||
|
@ -271,7 +273,7 @@ function mountedService (mount) {
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function updateService (mount, update) {
|
function updateService (mount, update) {
|
||||||
return getStorage().updateByExample({mount: mount}, update);
|
return getStorage().replaceByExample({mount: mount}, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -47,7 +47,7 @@ function deleteFrom (obj) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var reporters = {
|
exports.reporters = {
|
||||||
stream: StreamReporter,
|
stream: StreamReporter,
|
||||||
suite: SuiteReporter,
|
suite: SuiteReporter,
|
||||||
default: DefaultReporter
|
default: DefaultReporter
|
||||||
|
@ -58,10 +58,10 @@ exports.run = function runMochaTests (run, files, reporterName) {
|
||||||
files = [files];
|
files = [files];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reporterName && !reporters[reporterName]) {
|
if (reporterName && !exports.reporters[reporterName]) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Unknown test reporter: ' + reporterName
|
'Unknown test reporter: ' + reporterName
|
||||||
+ ' Known reporters: ' + Object.keys(reporters).join(', ')
|
+ ' Known reporters: ' + Object.keys(exports.reporters).join(', ')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ exports.run = function runMochaTests (run, files, reporterName) {
|
||||||
var _stdoutWrite = global.process.stdout.write;
|
var _stdoutWrite = global.process.stdout.write;
|
||||||
global.process.stdout.write = function () {};
|
global.process.stdout.write = function () {};
|
||||||
|
|
||||||
var Reporter = reporterName ? reporters[reporterName] : reporters.default;
|
var Reporter = reporterName ? exports.reporters[reporterName] : exports.reporters.default;
|
||||||
var reporter, runner;
|
var reporter, runner;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -984,15 +984,15 @@ function foxxRouting (req, res, options, next) {
|
||||||
var mount = options.mount;
|
var mount = options.mount;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var app = foxxManager.lookupService(mount);
|
var service = foxxManager.lookupService(mount);
|
||||||
var devel = app.isDevelopment;
|
var devel = service.isDevelopment;
|
||||||
|
|
||||||
if (devel || !options.hasOwnProperty('routing')) {
|
if (devel || !options.hasOwnProperty('routing')) {
|
||||||
delete options.error;
|
delete options.error;
|
||||||
|
|
||||||
if (devel) {
|
if (devel) {
|
||||||
foxxManager.rescanFoxx(mount); // TODO can move this to somewhere else?
|
foxxManager.rescanFoxx(mount); // TODO can move this to somewhere else?
|
||||||
app = foxxManager.lookupService(mount);
|
service = foxxManager.lookupService(mount);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.routing = flattenRoutingTree(buildRoutingTree([foxxManager.routes(mount)]));
|
options.routing = flattenRoutingTree(buildRoutingTree([foxxManager.routes(mount)]));
|
||||||
|
|
|
@ -327,45 +327,6 @@ ArangoCollection.prototype.removeByExample = function (example,
|
||||||
var i;
|
var i;
|
||||||
var cluster = require('@arangodb/cluster');
|
var cluster = require('@arangodb/cluster');
|
||||||
|
|
||||||
if (cluster.isCoordinator()) {
|
|
||||||
var dbName = require('internal').db._name();
|
|
||||||
var shards = cluster.shardList(dbName, this.name());
|
|
||||||
var coord = { coordTransactionID: ArangoClusterComm.getId() };
|
|
||||||
var options = { coordTransactionID: coord.coordTransactionID, timeout: 360 };
|
|
||||||
|
|
||||||
if (limit > 0 && shards.length > 1) {
|
|
||||||
var err = new ArangoError();
|
|
||||||
err.errorNum = internal.errors.ERROR_CLUSTER_UNSUPPORTED.code;
|
|
||||||
err.errorMessage = 'limit is not supported in sharded collections';
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
shards.forEach(function (shard) {
|
|
||||||
ArangoClusterComm.asyncRequest('put',
|
|
||||||
'shard:' + shard,
|
|
||||||
dbName,
|
|
||||||
'/_api/simple/remove-by-example',
|
|
||||||
JSON.stringify({
|
|
||||||
collection: shard,
|
|
||||||
example: example,
|
|
||||||
waitForSync: waitForSync,
|
|
||||||
limit: limit || undefined
|
|
||||||
}),
|
|
||||||
{ },
|
|
||||||
options);
|
|
||||||
});
|
|
||||||
|
|
||||||
var deleted = 0;
|
|
||||||
var results = cluster.wait(coord, shards.length);
|
|
||||||
for (i = 0; i < results.length; ++i) {
|
|
||||||
var body = JSON.parse(results[i].body);
|
|
||||||
deleted += (body.deleted || 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return deleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
var query = buildExampleQuery(this, example, limit);
|
var query = buildExampleQuery(this, example, limit);
|
||||||
var opts = { waitForSync: waitForSync };
|
var opts = { waitForSync: waitForSync };
|
||||||
query.query += ' REMOVE doc IN @@collection OPTIONS ' + JSON.stringify(opts);
|
query.query += ' REMOVE doc IN @@collection OPTIONS ' + JSON.stringify(opts);
|
||||||
|
@ -412,48 +373,6 @@ ArangoCollection.prototype.replaceByExample = function (example,
|
||||||
limit = tmp_options.limit;
|
limit = tmp_options.limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cluster = require('@arangodb/cluster');
|
|
||||||
|
|
||||||
if (cluster.isCoordinator()) {
|
|
||||||
var dbName = require('internal').db._name();
|
|
||||||
var shards = cluster.shardList(dbName, this.name());
|
|
||||||
var coord = { coordTransactionID: ArangoClusterComm.getId() };
|
|
||||||
var options = { coordTransactionID: coord.coordTransactionID, timeout: 360 };
|
|
||||||
|
|
||||||
if (limit > 0 && shards.length > 1) {
|
|
||||||
var err2 = new ArangoError();
|
|
||||||
err2.errorNum = internal.errors.ERROR_CLUSTER_UNSUPPORTED.code;
|
|
||||||
err2.errorMessage = 'limit is not supported in sharded collections';
|
|
||||||
|
|
||||||
throw err2;
|
|
||||||
}
|
|
||||||
|
|
||||||
shards.forEach(function (shard) {
|
|
||||||
ArangoClusterComm.asyncRequest('put',
|
|
||||||
'shard:' + shard,
|
|
||||||
dbName,
|
|
||||||
'/_api/simple/replace-by-example',
|
|
||||||
JSON.stringify({
|
|
||||||
collection: shard,
|
|
||||||
example: example,
|
|
||||||
newValue: newValue,
|
|
||||||
waitForSync: waitForSync,
|
|
||||||
limit: limit || undefined
|
|
||||||
}),
|
|
||||||
{ },
|
|
||||||
options);
|
|
||||||
});
|
|
||||||
|
|
||||||
var replaced = 0;
|
|
||||||
var results = cluster.wait(coord, shards.length), i;
|
|
||||||
for (i = 0; i < results.length; ++i) {
|
|
||||||
var body = JSON.parse(results[i].body);
|
|
||||||
replaced += (body.replaced || 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return replaced;
|
|
||||||
}
|
|
||||||
|
|
||||||
var query = buildExampleQuery(this, example, limit);
|
var query = buildExampleQuery(this, example, limit);
|
||||||
var opts = { waitForSync: waitForSync };
|
var opts = { waitForSync: waitForSync };
|
||||||
query.query += ' REPLACE doc WITH @newValue IN @@collection OPTIONS ' + JSON.stringify(opts);
|
query.query += ' REPLACE doc WITH @newValue IN @@collection OPTIONS ' + JSON.stringify(opts);
|
||||||
|
@ -510,49 +429,6 @@ ArangoCollection.prototype.updateByExample = function (example,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var cluster = require('@arangodb/cluster');
|
|
||||||
|
|
||||||
if (cluster.isCoordinator()) {
|
|
||||||
var dbName = require('internal').db._name();
|
|
||||||
var shards = cluster.shardList(dbName, this.name());
|
|
||||||
var coord = { coordTransactionID: ArangoClusterComm.getId() };
|
|
||||||
var options = { coordTransactionID: coord.coordTransactionID, timeout: 360 };
|
|
||||||
|
|
||||||
if (limit > 0 && shards.length > 1) {
|
|
||||||
var err2 = new ArangoError();
|
|
||||||
err2.errorNum = internal.errors.ERROR_CLUSTER_UNSUPPORTED.code;
|
|
||||||
err2.errorMessage = 'limit is not supported in sharded collections';
|
|
||||||
|
|
||||||
throw err2;
|
|
||||||
}
|
|
||||||
|
|
||||||
shards.forEach(function (shard) {
|
|
||||||
ArangoClusterComm.asyncRequest('put',
|
|
||||||
'shard:' + shard,
|
|
||||||
dbName,
|
|
||||||
'/_api/simple/update-by-example',
|
|
||||||
JSON.stringify({
|
|
||||||
collection: shard,
|
|
||||||
example: example,
|
|
||||||
newValue: newValue,
|
|
||||||
waitForSync: waitForSync,
|
|
||||||
keepNull: keepNull,
|
|
||||||
mergeObjects: mergeObjects,
|
|
||||||
limit: limit || undefined
|
|
||||||
}),
|
|
||||||
{ },
|
|
||||||
options);
|
|
||||||
});
|
|
||||||
|
|
||||||
var updated = 0;
|
|
||||||
var results = cluster.wait(coord, shards.length), i;
|
|
||||||
for (i = 0; i < results.length; ++i) {
|
|
||||||
var body = JSON.parse(results[i].body);
|
|
||||||
updated += (body.updated || 0);
|
|
||||||
}
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
var query = buildExampleQuery(this, example, limit);
|
var query = buildExampleQuery(this, example, limit);
|
||||||
var opts = { waitForSync: waitForSync, keepNull: keepNull, mergeObjects: mergeObjects };
|
var opts = { waitForSync: waitForSync, keepNull: keepNull, mergeObjects: mergeObjects };
|
||||||
query.query += ' UPDATE doc WITH @newValue IN @@collection OPTIONS ' + JSON.stringify(opts);
|
query.query += ' UPDATE doc WITH @newValue IN @@collection OPTIONS ' + JSON.stringify(opts);
|
||||||
|
|
|
@ -190,6 +190,7 @@ const manifestSchema = {
|
||||||
var serviceCache = {};
|
var serviceCache = {};
|
||||||
var usedSystemMountPoints = [
|
var usedSystemMountPoints = [
|
||||||
'/_admin/aardvark', // Admin interface.
|
'/_admin/aardvark', // Admin interface.
|
||||||
|
'/_api/foxx', // Foxx management API.
|
||||||
'/_api/gharial' // General_Graph API.
|
'/_api/gharial' // General_Graph API.
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -807,6 +808,10 @@ function patchManifestFile (servicePath, patchData) {
|
||||||
fs.write(filename, JSON.stringify(manifest, null, 2));
|
fs.write(filename, JSON.stringify(manifest, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLocalFile (path) {
|
||||||
|
return utils.pathRegex.test(path) && !fs.isDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief Copies a service from local, either zip file or folder, to mount path
|
// / @brief Copies a service from local, either zip file or folder, to mount path
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -960,7 +965,7 @@ function scanFoxx (mount, options) {
|
||||||
initCache();
|
initCache();
|
||||||
var service = _scanFoxx(mount, options);
|
var service = _scanFoxx(mount, options);
|
||||||
reloadRouting();
|
reloadRouting();
|
||||||
return service.simpleJSON();
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1000,9 +1005,6 @@ function _buildServiceInPath (serviceInfo, path, options) {
|
||||||
installServiceFromRemote(serviceInfo, path);
|
installServiceFromRemote(serviceInfo, path);
|
||||||
} else if (utils.pathRegex.test(serviceInfo)) {
|
} else if (utils.pathRegex.test(serviceInfo)) {
|
||||||
installServiceFromLocal(serviceInfo, path);
|
installServiceFromLocal(serviceInfo, path);
|
||||||
} else if (/^uploads[\/\\]tmp-/.test(serviceInfo)) {
|
|
||||||
serviceInfo = joinPath(fs.getTempPath(), serviceInfo);
|
|
||||||
installServiceFromLocal(serviceInfo, path);
|
|
||||||
} else {
|
} else {
|
||||||
if (!options || options.refresh !== false) {
|
if (!options || options.refresh !== false) {
|
||||||
try {
|
try {
|
||||||
|
@ -1121,8 +1123,11 @@ function install (serviceInfo, mount, options) {
|
||||||
[ [ 'Install information', 'string' ],
|
[ [ 'Install information', 'string' ],
|
||||||
[ 'Mount path', 'string' ] ],
|
[ 'Mount path', 'string' ] ],
|
||||||
[ serviceInfo, mount ]);
|
[ serviceInfo, mount ]);
|
||||||
|
if (/^uploads[\/\\]tmp-/.test(serviceInfo)) {
|
||||||
|
serviceInfo = joinPath(fs.getTempPath(), serviceInfo);
|
||||||
|
}
|
||||||
utils.validateMount(mount);
|
utils.validateMount(mount);
|
||||||
let hasToBeDistributed = /^uploads[\/\\]tmp-/.test(serviceInfo);
|
let hasToBeDistributed = isLocalFile(serviceInfo);
|
||||||
var service = _install(serviceInfo, mount, options, true);
|
var service = _install(serviceInfo, mount, options, true);
|
||||||
options = options || {};
|
options = options || {};
|
||||||
if (ArangoServerState.isCoordinator() && !options.__clusterDistribution) {
|
if (ArangoServerState.isCoordinator() && !options.__clusterDistribution) {
|
||||||
|
@ -1169,7 +1174,7 @@ function install (serviceInfo, mount, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reloadRouting();
|
reloadRouting();
|
||||||
return service.simpleJSON();
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1277,7 +1282,7 @@ function uninstall (mount, options) {
|
||||||
}
|
}
|
||||||
var service = _uninstall(mount, options);
|
var service = _uninstall(mount, options);
|
||||||
reloadRouting();
|
reloadRouting();
|
||||||
return service.simpleJSON();
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1292,10 +1297,13 @@ function replace (serviceInfo, mount, options) {
|
||||||
[ [ 'Install information', 'string' ],
|
[ [ 'Install information', 'string' ],
|
||||||
[ 'Mount path', 'string' ] ],
|
[ 'Mount path', 'string' ] ],
|
||||||
[ serviceInfo, mount ]);
|
[ serviceInfo, mount ]);
|
||||||
|
if (/^uploads[\/\\]tmp-/.test(serviceInfo)) {
|
||||||
|
serviceInfo = joinPath(fs.getTempPath(), serviceInfo);
|
||||||
|
}
|
||||||
utils.validateMount(mount);
|
utils.validateMount(mount);
|
||||||
_validateService(serviceInfo, mount);
|
_validateService(serviceInfo, mount);
|
||||||
options = options || {};
|
options = options || {};
|
||||||
let hasToBeDistributed = /^uploads[\/\\]tmp-/.test(serviceInfo);
|
let hasToBeDistributed = isLocalFile(serviceInfo);
|
||||||
if (ArangoServerState.isCoordinator() && !options.__clusterDistribution) {
|
if (ArangoServerState.isCoordinator() && !options.__clusterDistribution) {
|
||||||
let name = ArangoServerState.id();
|
let name = ArangoServerState.id();
|
||||||
let coordinators = ArangoClusterInfo.getCoordinators().filter(function (c) {
|
let coordinators = ArangoClusterInfo.getCoordinators().filter(function (c) {
|
||||||
|
@ -1345,7 +1353,7 @@ function replace (serviceInfo, mount, options) {
|
||||||
});
|
});
|
||||||
var service = _install(serviceInfo, mount, options, true);
|
var service = _install(serviceInfo, mount, options, true);
|
||||||
reloadRouting();
|
reloadRouting();
|
||||||
return service.simpleJSON();
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1360,10 +1368,13 @@ function upgrade (serviceInfo, mount, options) {
|
||||||
[ [ 'Install information', 'string' ],
|
[ [ 'Install information', 'string' ],
|
||||||
[ 'Mount path', 'string' ] ],
|
[ 'Mount path', 'string' ] ],
|
||||||
[ serviceInfo, mount ]);
|
[ serviceInfo, mount ]);
|
||||||
|
if (/^uploads[\/\\]tmp-/.test(serviceInfo)) {
|
||||||
|
serviceInfo = joinPath(fs.getTempPath(), serviceInfo);
|
||||||
|
}
|
||||||
utils.validateMount(mount);
|
utils.validateMount(mount);
|
||||||
_validateService(serviceInfo, mount);
|
_validateService(serviceInfo, mount);
|
||||||
options = options || {};
|
options = options || {};
|
||||||
let hasToBeDistributed = /^uploads[\/\\]tmp-/.test(serviceInfo);
|
let hasToBeDistributed = isLocalFile(serviceInfo);
|
||||||
if (ArangoServerState.isCoordinator() && !options.__clusterDistribution) {
|
if (ArangoServerState.isCoordinator() && !options.__clusterDistribution) {
|
||||||
let name = ArangoServerState.id();
|
let name = ArangoServerState.id();
|
||||||
let coordinators = ArangoClusterInfo.getCoordinators().filter(function (c) {
|
let coordinators = ArangoClusterInfo.getCoordinators().filter(function (c) {
|
||||||
|
@ -1428,7 +1439,7 @@ function upgrade (serviceInfo, mount, options) {
|
||||||
});
|
});
|
||||||
var service = _install(serviceInfo, mount, options, true);
|
var service = _install(serviceInfo, mount, options, true);
|
||||||
reloadRouting();
|
reloadRouting();
|
||||||
return service.simpleJSON();
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1480,7 +1491,7 @@ function setDevelopment (mount) {
|
||||||
[ [ 'Mount path', 'string' ] ],
|
[ [ 'Mount path', 'string' ] ],
|
||||||
[ mount ]);
|
[ mount ]);
|
||||||
var service = _toggleDevelopment(mount, true);
|
var service = _toggleDevelopment(mount, true);
|
||||||
return service.simpleJSON();
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1493,77 +1504,69 @@ function setProduction (mount) {
|
||||||
[ [ 'Mount path', 'string' ] ],
|
[ [ 'Mount path', 'string' ] ],
|
||||||
[ mount ]);
|
[ mount ]);
|
||||||
var service = _toggleDevelopment(mount, false);
|
var service = _toggleDevelopment(mount, false);
|
||||||
return service.simpleJSON();
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief Configure the service at the mountpoint
|
// / @brief Configure the service at the mountpoint
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function configure (mount, options) {
|
function setConfiguration (mount, options) {
|
||||||
checkParameter(
|
checkParameter(
|
||||||
'configure(<mount>)',
|
'setConfiguration(<mount>)',
|
||||||
[ [ 'Mount path', 'string' ] ],
|
[ [ 'Mount path', 'string' ] ],
|
||||||
[ mount ]);
|
[ mount ]);
|
||||||
utils.validateMount(mount, true);
|
utils.validateMount(mount, true);
|
||||||
var service = lookupService(mount);
|
var service = lookupService(mount);
|
||||||
var invalid = service.applyConfiguration(options.configuration || {});
|
var warnings = service.applyConfiguration(options.configuration, options.replace);
|
||||||
if (invalid.length > 0) {
|
|
||||||
// TODO Error handling
|
|
||||||
console.log(invalid);
|
|
||||||
}
|
|
||||||
utils.updateService(mount, service.toJSON());
|
utils.updateService(mount, service.toJSON());
|
||||||
reloadRouting();
|
reloadRouting();
|
||||||
return service.simpleJSON();
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief Set up dependencies of the service at the mountpoint
|
// / @brief Set up dependencies of the service at the mountpoint
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function updateDeps (mount, options) {
|
function setDependencies (mount, options) {
|
||||||
checkParameter(
|
checkParameter(
|
||||||
'updateDeps(<mount>)',
|
'setDependencies(<mount>)',
|
||||||
[ [ 'Mount path', 'string' ] ],
|
[ [ 'Mount path', 'string' ] ],
|
||||||
[ mount ]);
|
[ mount ]);
|
||||||
utils.validateMount(mount, true);
|
utils.validateMount(mount, true);
|
||||||
var service = lookupService(mount);
|
var service = lookupService(mount);
|
||||||
var invalid = service.applyDependencies(options.dependencies || {});
|
var warnings = service.applyDependencies(options.dependencies, options.replace);
|
||||||
if (invalid.length > 0) {
|
|
||||||
// TODO Error handling
|
|
||||||
console.log(invalid);
|
|
||||||
}
|
|
||||||
utils.updateService(mount, service.toJSON());
|
utils.updateService(mount, service.toJSON());
|
||||||
reloadRouting();
|
reloadRouting();
|
||||||
return service.simpleJSON();
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief Get the configuration for the service at the given mountpoint
|
// / @brief Get the configuration for the service at the given mountpoint
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function configuration (mount) {
|
function configuration (mount, options) {
|
||||||
checkParameter(
|
checkParameter(
|
||||||
'configuration(<mount>)',
|
'configuration(<mount>)',
|
||||||
[ [ 'Mount path', 'string' ] ],
|
[ [ 'Mount path', 'string' ] ],
|
||||||
[ mount ]);
|
[ mount ]);
|
||||||
utils.validateMount(mount, true);
|
utils.validateMount(mount, true);
|
||||||
var service = lookupService(mount);
|
var service = lookupService(mount);
|
||||||
return service.getConfiguration();
|
return service.getConfiguration(options && options.simple);
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
// / @brief Get the dependencies for the service at the given mountpoint
|
// / @brief Get the dependencies for the service at the given mountpoint
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function dependencies (mount) {
|
function dependencies (mount, options) {
|
||||||
checkParameter(
|
checkParameter(
|
||||||
'dependencies(<mount>)',
|
'dependencies(<mount>)',
|
||||||
[ [ 'Mount path', 'string' ] ],
|
[ [ 'Mount path', 'string' ] ],
|
||||||
[ mount ]);
|
[ mount ]);
|
||||||
utils.validateMount(mount, true);
|
utils.validateMount(mount, true);
|
||||||
var service = lookupService(mount);
|
var service = lookupService(mount);
|
||||||
return service.getDependencies();
|
return service.getDependencies(options && options.simple);
|
||||||
}
|
}
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1621,8 +1624,8 @@ exports.replace = replace;
|
||||||
exports.upgrade = upgrade;
|
exports.upgrade = upgrade;
|
||||||
exports.development = setDevelopment;
|
exports.development = setDevelopment;
|
||||||
exports.production = setProduction;
|
exports.production = setProduction;
|
||||||
exports.configure = configure;
|
exports.setConfiguration = setConfiguration;
|
||||||
exports.updateDeps = updateDeps;
|
exports.setDependencies = setDependencies;
|
||||||
exports.configuration = configuration;
|
exports.configuration = configuration;
|
||||||
exports.dependencies = dependencies;
|
exports.dependencies = dependencies;
|
||||||
exports.requireService = requireService;
|
exports.requireService = requireService;
|
||||||
|
|
|
@ -90,12 +90,11 @@ module.exports =
|
||||||
|
|
||||||
this.configuration = createConfiguration(this.manifest.configuration);
|
this.configuration = createConfiguration(this.manifest.configuration);
|
||||||
this.dependencies = createDependencies(this.manifest.dependencies, this.options.dependencies);
|
this.dependencies = createDependencies(this.manifest.dependencies, this.options.dependencies);
|
||||||
const warnings = this.applyConfiguration(this.options.configuration);
|
const warnings = this.applyConfiguration(this.options.configuration, false);
|
||||||
if (warnings.length) {
|
if (warnings) {
|
||||||
console.warnLines(dd`
|
console.warnLines(`Stored configuration for service "${data.mount}" has errors:\n ${
|
||||||
Stored configuration for app "${data.mount}" has errors:
|
Object.keys(warnings).map((key) => warnings[key]).join('\n ')
|
||||||
${warnings.join('\n ')}
|
}`);
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.thumbnail = null;
|
this.thumbnail = null;
|
||||||
|
@ -110,30 +109,40 @@ module.exports =
|
||||||
this.legacy = range ? semver.gtr('3.0.0', range) : false;
|
this.legacy = range ? semver.gtr('3.0.0', range) : false;
|
||||||
if (this.legacy) {
|
if (this.legacy) {
|
||||||
console.debugLines(dd`
|
console.debugLines(dd`
|
||||||
Service "${this.mount}" is running in legacy compatibility mode.
|
Service "${this.mount}" is running in legacy compatibility mode.
|
||||||
Requested version "${range}" is lower than "3.0.0".
|
Requested version "${range}" is lower than "3.0.0".
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._reset();
|
this._reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
applyConfiguration (config) {
|
applyConfiguration (config, replace) {
|
||||||
|
if (!config) {
|
||||||
|
config = {};
|
||||||
|
}
|
||||||
const definitions = this.manifest.configuration;
|
const definitions = this.manifest.configuration;
|
||||||
const warnings = [];
|
const configNames = Object.keys(config);
|
||||||
|
const knownNames = Object.keys(definitions);
|
||||||
|
const omittedNames = knownNames.filter((name) => !configNames.includes(name));
|
||||||
|
const names = replace ? configNames.concat(omittedNames) : configNames;
|
||||||
|
const warnings = {};
|
||||||
|
|
||||||
for (const name of Object.keys(config)) {
|
for (const name of names) {
|
||||||
const rawValue = config[name];
|
if (!knownNames.includes(name)) {
|
||||||
const def = definitions[name];
|
warnings[name] = 'is not allowed';
|
||||||
if (!def) {
|
continue;
|
||||||
warnings.push(`Unexpected option "${name}"`);
|
|
||||||
return warnings;
|
|
||||||
}
|
}
|
||||||
|
const def = definitions[name];
|
||||||
|
const rawValue = config[name];
|
||||||
|
|
||||||
if (def.required === false && (rawValue === undefined || rawValue === null || rawValue === '')) {
|
if (rawValue === undefined || rawValue === null || rawValue === '') {
|
||||||
delete this.options.configuration[name];
|
this.options.configuration[name] = undefined;
|
||||||
this.configuration[name] = def.default;
|
this.configuration[name] = def.default;
|
||||||
return warnings;
|
if (def.required !== false) {
|
||||||
|
warnings[name] = 'is required';
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validate = parameterTypes[def.type];
|
const validate = parameterTypes[def.type];
|
||||||
|
@ -143,7 +152,7 @@ module.exports =
|
||||||
if (validate.isJoi) {
|
if (validate.isJoi) {
|
||||||
const result = validate.required().validate(rawValue);
|
const result = validate.required().validate(rawValue);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
warning = result.error.message.replace(/^"value"/, `"${name}"`);
|
warning = result.error.message.replace(/^"value"\s+/, '');
|
||||||
} else {
|
} else {
|
||||||
parsedValue = result.value;
|
parsedValue = result.value;
|
||||||
}
|
}
|
||||||
|
@ -151,19 +160,56 @@ module.exports =
|
||||||
try {
|
try {
|
||||||
parsedValue = validate(rawValue);
|
parsedValue = validate(rawValue);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
warning = `"${name}": ${e.message}`;
|
warning = e.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warning) {
|
if (warning) {
|
||||||
warnings.push(warning);
|
warnings[name] = warning;
|
||||||
} else {
|
} else {
|
||||||
this.options.configuration[name] = rawValue;
|
this.options.configuration[name] = rawValue;
|
||||||
this.configuration[name] = parsedValue;
|
this.configuration[name] = parsedValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return warnings;
|
return Object.keys(warnings).length ? warnings : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyDependencies (deps, replace) {
|
||||||
|
if (!deps) {
|
||||||
|
deps = {};
|
||||||
|
}
|
||||||
|
const definitions = this.manifest.dependencies;
|
||||||
|
const depsNames = Object.keys(deps);
|
||||||
|
const knownNames = Object.keys(definitions);
|
||||||
|
const omittedNames = knownNames.filter((name) => !depsNames.includes(name));
|
||||||
|
const names = replace ? depsNames.concat(omittedNames) : depsNames;
|
||||||
|
const warnings = {};
|
||||||
|
|
||||||
|
for (const name of names) {
|
||||||
|
if (!knownNames.includes(name)) {
|
||||||
|
warnings[name] = 'is not allowed';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const def = definitions[name];
|
||||||
|
const value = deps[name];
|
||||||
|
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
this.options.dependencies[name] = undefined;
|
||||||
|
if (def.required !== false) {
|
||||||
|
warnings[name] = 'is required';
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
warnings[name] = 'must be a string';
|
||||||
|
} else {
|
||||||
|
this.options.dependencies[name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(warnings).length ? warnings : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildRoutes () {
|
buildRoutes () {
|
||||||
|
@ -180,10 +226,10 @@ module.exports =
|
||||||
err = err.cause;
|
err = err.cause;
|
||||||
}
|
}
|
||||||
console.warnLines(dd`
|
console.warnLines(dd`
|
||||||
Failed to build API documentation for "${this.mount}"!
|
Failed to build API documentation for "${this.mount}"!
|
||||||
This is likely a bug in your Foxx service.
|
This is likely a bug in your Foxx service.
|
||||||
Check the route methods you are using to document your API.
|
Check the route methods you are using to document your API.
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
this.docs = {
|
this.docs = {
|
||||||
swagger: '2.0',
|
swagger: '2.0',
|
||||||
|
@ -220,14 +266,14 @@ module.exports =
|
||||||
}
|
}
|
||||||
if (logLevel) {
|
if (logLevel) {
|
||||||
console[logLevel](`Service "${
|
console[logLevel](`Service "${
|
||||||
service.mount
|
service.mount
|
||||||
}" encountered error ${
|
}" encountered error ${
|
||||||
e.statusCode || 500
|
e.statusCode || 500
|
||||||
} while handling ${
|
} while handling ${
|
||||||
req.requestType
|
req.requestType
|
||||||
} ${
|
} ${
|
||||||
req.absoluteUrl()
|
req.absoluteUrl()
|
||||||
}`);
|
}`);
|
||||||
console[`${logLevel}Lines`](e.stack);
|
console[`${logLevel}Lines`](e.stack);
|
||||||
let err = e.cause;
|
let err = e.cause;
|
||||||
while (err && err.stack) {
|
while (err && err.stack) {
|
||||||
|
@ -270,22 +316,6 @@ module.exports =
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
applyDependencies (deps) {
|
|
||||||
const definitions = this.manifest.dependencies;
|
|
||||||
const warnings = [];
|
|
||||||
|
|
||||||
for (const name of Object.keys(deps)) {
|
|
||||||
const dfn = definitions[name];
|
|
||||||
if (!dfn) {
|
|
||||||
warnings.push(`Unexpected dependency "${name}"`);
|
|
||||||
} else {
|
|
||||||
this.options.dependencies[name] = deps[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return warnings;
|
|
||||||
}
|
|
||||||
|
|
||||||
_PRINT (context) {
|
_PRINT (context) {
|
||||||
context.output += `[FoxxService at "${this.mount}"]`;
|
context.output += `[FoxxService at "${this.mount}"]`;
|
||||||
}
|
}
|
||||||
|
@ -354,11 +384,11 @@ module.exports =
|
||||||
const options = this.options.dependencies;
|
const options = this.options.dependencies;
|
||||||
for (const name of Object.keys(definitions)) {
|
for (const name of Object.keys(definitions)) {
|
||||||
const dfn = definitions[name];
|
const dfn = definitions[name];
|
||||||
deps[name] = simple ? options[name] : {
|
const value = options[name];
|
||||||
definition: dfn,
|
deps[name] = simple ? value : Object.assign({}, dfn, {
|
||||||
title: getReadableName(name),
|
title: getReadableName(name),
|
||||||
current: options[name]
|
current: value
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
return deps;
|
return deps;
|
||||||
}
|
}
|
||||||
|
@ -369,7 +399,7 @@ module.exports =
|
||||||
return _.some(config, function (cfg) {
|
return _.some(config, function (cfg) {
|
||||||
return cfg.current === undefined && cfg.required !== false;
|
return cfg.current === undefined && cfg.required !== false;
|
||||||
}) || _.some(deps, function (dep) {
|
}) || _.some(deps, function (dep) {
|
||||||
return dep.current === undefined && dep.definition.required !== false;
|
return dep.current === undefined && dep.required !== false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,23 +24,23 @@ describe('Foxx Manager', function () {
|
||||||
var deps1 = {hello: {name: 'world', required: true, version: '*'}};
|
var deps1 = {hello: {name: 'world', required: true, version: '*'}};
|
||||||
var deps2 = {clobbered: {name: 'completely', required: true, version: '*'}};
|
var deps2 = {clobbered: {name: 'completely', required: true, version: '*'}};
|
||||||
|
|
||||||
var app = FoxxManager.lookupService(mount);
|
var service = FoxxManager.lookupService(mount);
|
||||||
expect(app.manifest.dependencies).to.eql({});
|
expect(service.manifest.dependencies).to.eql({});
|
||||||
var filename = app.main.context.fileName('manifest.json');
|
var filename = service.main.context.fileName('manifest.json');
|
||||||
var rawJson = fs.readFileSync(filename, 'utf-8');
|
var rawJson = fs.readFileSync(filename, 'utf-8');
|
||||||
var json = JSON.parse(rawJson);
|
var json = JSON.parse(rawJson);
|
||||||
|
|
||||||
json.dependencies = deps1;
|
json.dependencies = deps1;
|
||||||
fs.writeFileSync(filename, JSON.stringify(json));
|
fs.writeFileSync(filename, JSON.stringify(json));
|
||||||
FoxxManager.scanFoxx(mount, {replace: true});
|
FoxxManager.scanFoxx(mount, {replace: true});
|
||||||
app = FoxxManager.lookupService(mount);
|
service = FoxxManager.lookupService(mount);
|
||||||
expect(app.manifest.dependencies).to.eql(deps1);
|
expect(service.manifest.dependencies).to.eql(deps1);
|
||||||
|
|
||||||
json.dependencies = deps2;
|
json.dependencies = deps2;
|
||||||
fs.writeFileSync(filename, JSON.stringify(json));
|
fs.writeFileSync(filename, JSON.stringify(json));
|
||||||
FoxxManager.scanFoxx(mount, {replace: true});
|
FoxxManager.scanFoxx(mount, {replace: true});
|
||||||
app = FoxxManager.lookupService(mount);
|
service = FoxxManager.lookupService(mount);
|
||||||
expect(app.manifest.dependencies).to.eql(deps2);
|
expect(service.manifest.dependencies).to.eql(deps2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue