From 92f02fbce303f58faa27b75caf77417d3da44e83 Mon Sep 17 00:00:00 2001 From: Michael Hackstein Date: Thu, 8 May 2014 19:09:17 +0200 Subject: [PATCH 1/3] Fixed design bugs in graph viewer --- .../frontend/js/graphViewer/ui/modalDialogHelper.js | 11 +++++++---- .../system/aardvark/frontend/scss/_collection.scss | 1 - js/apps/system/aardvark/frontend/scss/_modals.scss | 1 + js/apps/system/aardvark/frontend/scss/generated.css | 5 ++--- js/apps/system/aardvark/manifest.json | 1 - 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/js/apps/system/aardvark/frontend/js/graphViewer/ui/modalDialogHelper.js b/js/apps/system/aardvark/frontend/js/graphViewer/ui/modalDialogHelper.js index 42ee23dac9..41f029af2c 100644 --- a/js/apps/system/aardvark/frontend/js/graphViewer/ui/modalDialogHelper.js +++ b/js/apps/system/aardvark/frontend/js/graphViewer/ui/modalDialogHelper.js @@ -245,6 +245,7 @@ var modalDialogHelper = modalDialogHelper || {}; i, id = idPre + idPost, lastId = 1, + buttonTh = document.createElement("th"), addLineButton = document.createElement("button"), input = document.createElement("input"), addNewLine = function(content) { @@ -252,6 +253,7 @@ var modalDialogHelper = modalDialogHelper || {}; var innerTr = document.createElement("tr"), innerLabelTh = document.createElement("th"), innerContentTh = document.createElement("th"), + innerButtonTh = document.createElement("th"), innerInput = document.createElement("input"), removeRow = document.createElement("button"), lastItem; @@ -276,14 +278,15 @@ var modalDialogHelper = modalDialogHelper || {}; table.removeChild(innerTr); rows.splice(rows.indexOf(innerTr), 1 ); }; - - innerContentTh.appendChild(removeRow); + innerButtonTh.appendChild(removeRow); + innerTr.appendChild(innerButtonTh); rows.push(innerTr); }; input.type = "text"; input.id = id + "_1"; contentTh.appendChild(input); - contentTh.appendChild(addLineButton); + buttonTh.appendChild(addLineButton); + tr.appendChild(buttonTh); addLineButton.onclick = function() { addNewLine(); }; @@ -309,7 +312,7 @@ var modalDialogHelper = modalDialogHelper || {}; // Set Classnames and attributes. div.id = idprefix + "modal"; - div.className = "modal hide fade"; + div.className = "modal hide fade createModalDialog"; div.setAttribute("tabindex", "-1"); div.setAttribute("role", "dialog"); div.setAttribute("aria-labelledby", "myModalLabel"); diff --git a/js/apps/system/aardvark/frontend/scss/_collection.scss b/js/apps/system/aardvark/frontend/scss/_collection.scss index 78bf097a2d..857aef5ac5 100644 --- a/js/apps/system/aardvark/frontend/scss/_collection.scss +++ b/js/apps/system/aardvark/frontend/scss/_collection.scss @@ -5,7 +5,6 @@ text-align: left; width: 20% !important; - input, select, textarea { margin-top: 10px; diff --git a/js/apps/system/aardvark/frontend/scss/_modals.scss b/js/apps/system/aardvark/frontend/scss/_modals.scss index e8fc53dc96..43b234bffb 100644 --- a/js/apps/system/aardvark/frontend/scss/_modals.scss +++ b/js/apps/system/aardvark/frontend/scss/_modals.scss @@ -42,6 +42,7 @@ margin-bottom: 10px; margin-top: 10px; } + } .icon-info-sign { diff --git a/js/apps/system/aardvark/frontend/scss/generated.css b/js/apps/system/aardvark/frontend/scss/generated.css index 0c148ab1e7..4d18c4c094 100644 --- a/js/apps/system/aardvark/frontend/scss/generated.css +++ b/js/apps/system/aardvark/frontend/scss/generated.css @@ -3424,8 +3424,8 @@ pre.gv-object-view { margin-left: 0; } .dashboard-interior-chart { - height: 222px; - background-color: white; } + background-color: white; + height: 222px; } .dashboard-large-chart { height: 250px; @@ -4276,7 +4276,6 @@ input.gv-radio-button { font-weight: 400 !important; text-align: left; width: 20% !important; } - .collectionTh input, .collectionTh select, .collectionTh textarea { margin-top: 10px; } diff --git a/js/apps/system/aardvark/manifest.json b/js/apps/system/aardvark/manifest.json index f42979cc9b..56b4656aae 100644 --- a/js/apps/system/aardvark/manifest.json +++ b/js/apps/system/aardvark/manifest.json @@ -37,7 +37,6 @@ "frontend/js/templates/editListEntryView.ejs", "frontend/js/templates/footerView.ejs", "frontend/js/templates/foxxActiveView.ejs", - "frontend/js/templates/foxxEditView.ejs", "frontend/js/templates/foxxInstalledView.ejs", "frontend/js/templates/graphManagementView.ejs", "frontend/js/templates/graphView.ejs", From 9b5f0157470819fe52d4c8246c1424c798f9673c Mon Sep 17 00:00:00 2001 From: Max Neunhoeffer Date: Thu, 8 May 2014 22:38:52 +0200 Subject: [PATCH 2/3] Prepare setup for cluster arangorestore. --- .../RestHandler/RestReplicationHandler.cpp | 327 +++++++++++++++++- arangod/RestHandler/RestReplicationHandler.h | 32 ++ arangosh/V8Client/arangodump.cpp | 2 +- arangosh/V8Client/arangorestore.cpp | 56 ++- 4 files changed, 403 insertions(+), 14 deletions(-) diff --git a/arangod/RestHandler/RestReplicationHandler.cpp b/arangod/RestHandler/RestReplicationHandler.cpp index 93509bfc68..7a39c18bb2 100644 --- a/arangod/RestHandler/RestReplicationHandler.cpp +++ b/arangod/RestHandler/RestReplicationHandler.cpp @@ -195,10 +195,6 @@ Handler::status_t RestReplicationHandler::execute() { goto BAD_CALL; } - if (isCoordinatorError()) { - return status_t(Handler::HANDLER_DONE); - } - handleCommandRestoreCollection(); } else if (command == "restore-indexes") { @@ -206,10 +202,6 @@ Handler::status_t RestReplicationHandler::execute() { goto BAD_CALL; } - if (isCoordinatorError()) { - return status_t(Handler::HANDLER_DONE); - } - handleCommandRestoreIndexes(); } else if (command == "restore-data") { @@ -219,7 +211,7 @@ Handler::status_t RestReplicationHandler::execute() { #ifdef TRI_ENABLE_CLUSTER if (ServerState::instance()->isCoordinator()) { - handleTrampolineCoordinator(); + handleCommandRestoreDataCoordinator(); } else { handleCommandRestoreData(); @@ -1941,7 +1933,20 @@ void RestReplicationHandler::handleCommandRestoreCollection () { TRI_server_id_t remoteServerId = 0; // TODO string errorMsg; - int res = processRestoreCollection(json, overwrite, recycleIds, force, remoteServerId, errorMsg); +#ifdef TRI_ENABLE_CLUSTER + int res; + if (ServerState::instance()->isCoordinator()) { + res = processRestoreCollectionCoordinator(json, overwrite, recycleIds, + force, remoteServerId, errorMsg); + } + else { + res = processRestoreCollection(json, overwrite, recycleIds, force, + remoteServerId, errorMsg); + } +#else + int res = processRestoreCollection(json, overwrite, recycleIds, force, + remoteServerId, errorMsg); +#endif TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); @@ -1982,7 +1987,17 @@ void RestReplicationHandler::handleCommandRestoreIndexes () { TRI_server_id_t remoteServerId = 0; // TODO string errorMsg; +#ifdef TRI_ENABLE_CLUSTER + int res; + if (ServerState::instance()->isCoordinator()) { + res = processRestoreIndexesCoordinator(json, force, remoteServerId, errorMsg); + } + else { + res = processRestoreIndexes(json, force, remoteServerId, errorMsg); + } +#else int res = processRestoreIndexes(json, force, remoteServerId, errorMsg); +#endif TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); @@ -2124,6 +2139,135 @@ int RestReplicationHandler::processRestoreCollection (TRI_json_t const* collecti return TRI_ERROR_NO_ERROR; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief restores the structure of a collection, coordinator case +//////////////////////////////////////////////////////////////////////////////// + +#ifdef TRI_ENABLE_CLUSTER +int RestReplicationHandler::processRestoreCollectionCoordinator ( + TRI_json_t const* collection, + bool dropExisting, + bool reuseId, + bool force, + TRI_server_id_t remoteServerId, + string& errorMsg) { + + if (! JsonHelper::isArray(collection)) { + errorMsg = "collection declaration is invalid"; + + return TRI_ERROR_HTTP_BAD_PARAMETER; + } + + TRI_json_t const* parameters = JsonHelper::getArrayElement(collection, "parameters"); + + if (! JsonHelper::isArray(parameters)) { + errorMsg = "collection parameters declaration is invalid"; + + return TRI_ERROR_HTTP_BAD_PARAMETER; + } + + TRI_json_t const* indexes = JsonHelper::getArrayElement(collection, "indexes"); + + if (! JsonHelper::isList(indexes)) { + errorMsg = "collection indexes declaration is invalid"; + + return TRI_ERROR_HTTP_BAD_PARAMETER; + } + + const string name = JsonHelper::getStringValue(parameters, "name", ""); + + if (name.empty()) { + errorMsg = "collection name is missing"; + + return TRI_ERROR_HTTP_BAD_PARAMETER; + } + + if (JsonHelper::getBooleanValue(parameters, "deleted", false)) { + // we don't care about deleted collections + return TRI_ERROR_NO_ERROR; + } + + TRI_vocbase_col_t* col = 0; + + if (reuseId) { + TRI_json_t const* idString = JsonHelper::getArrayElement(parameters, "cid"); + + if (! JsonHelper::isString(idString)) { + errorMsg = "collection id is missing"; + + return TRI_ERROR_HTTP_BAD_PARAMETER; + } + + TRI_voc_cid_t cid = StringUtils::uint64(idString->_value._string.data, idString->_value._string.length - 1); + + // first look up the collection by the cid + col = TRI_LookupCollectionByIdVocBase(_vocbase, cid); + } + + + if (col == 0) { + // not found, try name next + col = TRI_LookupCollectionByNameVocBase(_vocbase, name.c_str()); + } + + // drop an existing collection if it exists + if (col != 0) { + if (dropExisting) { + int res = TRI_DropCollectionVocBase(_vocbase, col, remoteServerId); + + if (res == TRI_ERROR_FORBIDDEN) { + // some collections must not be dropped + + // instead, truncate them + CollectionNameResolver resolver(_vocbase); + SingleCollectionWriteTransaction, UINT64_MAX> trx(_vocbase, resolver, col->_cid); + + res = trx.begin(); + if (res != TRI_ERROR_NO_ERROR) { + return res; + } + TRI_barrier_t* barrier = TRI_CreateBarrierElement(&(trx.primaryCollection()->_barrierList)); + + if (barrier == 0) { + return TRI_ERROR_INTERNAL; + } + + res = trx.truncate(false); + res = trx.finish(res); + + TRI_FreeBarrier(barrier); + + return res; + } + + if (res != TRI_ERROR_NO_ERROR) { + errorMsg = "unable to drop collection '" + name + "': " + string(TRI_errno_string(res)); + + return res; + } + } + else { + int res = TRI_ERROR_ARANGO_DUPLICATE_NAME; + + errorMsg = "unable to create collection '" + name + "': " + string(TRI_errno_string(res)); + + return res; + } + } + + // now re-create the collection + int res = createCollection(parameters, &col, reuseId, remoteServerId); + + if (res != TRI_ERROR_NO_ERROR) { + errorMsg = "unable to create collection: " + string(TRI_errno_string(res)); + + return res; + } + + return TRI_ERROR_NO_ERROR; +} +#endif + //////////////////////////////////////////////////////////////////////////////// /// @brief restores the indexes of a collection TODO MOVE //////////////////////////////////////////////////////////////////////////////// @@ -2226,6 +2370,112 @@ int RestReplicationHandler::processRestoreIndexes (TRI_json_t const* collection, return TRI_ERROR_NO_ERROR; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief restores the indexes of a collection, coordinator case +//////////////////////////////////////////////////////////////////////////////// + +#ifdef TRI_ENABLE_CLUSTER +int RestReplicationHandler::processRestoreIndexesCoordinator ( + TRI_json_t const* collection, + bool force, + TRI_server_id_t remoteServerId, + string& errorMsg) { + + if (! JsonHelper::isArray(collection)) { + errorMsg = "collection declaration is invalid"; + + return TRI_ERROR_HTTP_BAD_PARAMETER; + } + + TRI_json_t const* parameters = JsonHelper::getArrayElement(collection, "parameters"); + + if (! JsonHelper::isArray(parameters)) { + errorMsg = "collection parameters declaration is invalid"; + + return TRI_ERROR_HTTP_BAD_PARAMETER; + } + + TRI_json_t const* indexes = JsonHelper::getArrayElement(collection, "indexes"); + + if (! JsonHelper::isList(indexes)) { + errorMsg = "collection indexes declaration is invalid"; + + return TRI_ERROR_HTTP_BAD_PARAMETER; + } + + const size_t n = indexes->_value._objects._length; + if (n == 0) { + // nothing to do + return TRI_ERROR_NO_ERROR; + } + + const string name = JsonHelper::getStringValue(parameters, "name", ""); + + if (name.empty()) { + errorMsg = "collection name is missing"; + + return TRI_ERROR_HTTP_BAD_PARAMETER; + } + + if (JsonHelper::getBooleanValue(parameters, "deleted", false)) { + // we don't care about deleted collections + return TRI_ERROR_NO_ERROR; + } + + // look up the collection + TRI_vocbase_col_t* col = TRI_LookupCollectionByNameVocBase(_vocbase, name.c_str()); + + if (col == 0) { + errorMsg = "could not find collection '" + name + "'"; + return TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND; + } + + int res = TRI_UseCollectionVocBase(_vocbase, col); + + if (res != TRI_ERROR_NO_ERROR) { + return res; + } + + TRI_primary_collection_t* primary = col->_collection; + + TRI_ReadLockReadWriteLock(&_vocbase->_inventoryLock); + + TRI_WRITE_LOCK_DOCUMENTS_INDEXES_PRIMARY_COLLECTION(primary); + + for (size_t i = 0; i < n; ++i) { + TRI_json_t const* idxDef = (TRI_json_t const*) TRI_AtVector(&indexes->_value._objects, i); + TRI_index_t* idx = 0; + + // {"id":"229907440927234","type":"hash","unique":false,"fields":["x","Y"]} + + res = TRI_FromJsonIndexDocumentCollection((TRI_document_collection_t*) primary, idxDef, &idx); + + if (res != TRI_ERROR_NO_ERROR) { + errorMsg = "could not create index: " + string(TRI_errno_string(res)); + break; + } + else { + assert(idx != 0); + + res = TRI_SaveIndex(primary, idx, remoteServerId); + + if (res != TRI_ERROR_NO_ERROR) { + errorMsg = "could not save index: " + string(TRI_errno_string(res)); + break; + } + } + } + + TRI_WRITE_UNLOCK_DOCUMENTS_INDEXES_PRIMARY_COLLECTION(primary); + + TRI_ReadUnlockReadWriteLock(&_vocbase->_inventoryLock); + + TRI_ReleaseCollectionVocBase(_vocbase, col); + + return TRI_ERROR_NO_ERROR; +} +#endif + //////////////////////////////////////////////////////////////////////////////// /// @brief apply the data from a collection dump or the continuous log //////////////////////////////////////////////////////////////////////////////// @@ -2569,6 +2819,63 @@ void RestReplicationHandler::handleCommandRestoreData () { } } +//////////////////////////////////////////////////////////////////////////////// +/// @brief restores the data of a collection, coordinator case +//////////////////////////////////////////////////////////////////////////////// + +#ifdef TRI_ENABLE_CLUSTER +void RestReplicationHandler::handleCommandRestoreDataCoordinator () { + char const* value = _request->value("collection"); + + if (value == 0) { + generateError(HttpResponse::BAD, + TRI_ERROR_HTTP_BAD_PARAMETER, + "invalid collection parameter"); + return; + } + + CollectionNameResolver resolver(_vocbase); + + TRI_voc_cid_t cid = resolver.getCollectionId(value); + + if (cid == 0) { + generateError(HttpResponse::BAD, + TRI_ERROR_HTTP_BAD_PARAMETER, + "invalid collection parameter"); + return; + } + + bool recycleIds = false; + value = _request->value("recycleIds"); + if (value != 0) { + recycleIds = StringUtils::boolean(value); + } + + bool force = false; + value = _request->value("force"); + if (value != 0) { + force = StringUtils::boolean(value); + } + + TRI_server_id_t remoteServerId = 0; // TODO + string errorMsg; + + int res = processRestoreData(resolver, cid, remoteServerId, recycleIds, force, errorMsg); + + if (res != TRI_ERROR_NO_ERROR) { + generateError(HttpResponse::SERVER_ERROR, res); + } + else { + TRI_json_t result; + + TRI_InitArrayJson(TRI_CORE_MEM_ZONE, &result); + TRI_Insert3ArrayJson(TRI_CORE_MEM_ZONE, &result, "result", TRI_CreateBooleanJson(TRI_CORE_MEM_ZONE, true)); + + generateResult(&result); + } +} +#endif + //////////////////////////////////////////////////////////////////////////////// /// @brief dumps the data of a collection /// diff --git a/arangod/RestHandler/RestReplicationHandler.h b/arangod/RestHandler/RestReplicationHandler.h index 6da1164537..8e4739fb11 100644 --- a/arangod/RestHandler/RestReplicationHandler.h +++ b/arangod/RestHandler/RestReplicationHandler.h @@ -234,6 +234,19 @@ namespace triagens { TRI_server_id_t, std::string&); +//////////////////////////////////////////////////////////////////////////////// +/// @brief restores the structure of a collection, coordinator case +//////////////////////////////////////////////////////////////////////////////// + +#ifdef TRI_ENABLE_CLUSTER + int processRestoreCollectionCoordinator (struct TRI_json_s const*, + bool, + bool, + bool, + TRI_server_id_t, + std::string&); +#endif + //////////////////////////////////////////////////////////////////////////////// /// @brief restores the indexes of a collection TODO MOVE //////////////////////////////////////////////////////////////////////////////// @@ -243,6 +256,17 @@ namespace triagens { TRI_server_id_t, std::string&); +//////////////////////////////////////////////////////////////////////////////// +/// @brief restores the indexes of a collection, coordinator case +//////////////////////////////////////////////////////////////////////////////// + +#ifdef TRI_ENABLE_CLUSTER + int processRestoreIndexesCoordinator (struct TRI_json_s const*, + bool, + TRI_server_id_t, + std::string&); +#endif + //////////////////////////////////////////////////////////////////////////////// /// @brief apply a single marker from the collection dump //////////////////////////////////////////////////////////////////////////////// @@ -283,6 +307,14 @@ namespace triagens { void handleCommandRestoreData (); +//////////////////////////////////////////////////////////////////////////////// +/// @brief handle a restore command for a specific collection, coordinator case +//////////////////////////////////////////////////////////////////////////////// + +#ifdef TRI_ENABLE_CLUSTER + void handleCommandRestoreDataCoordinator (); +#endif + //////////////////////////////////////////////////////////////////////////////// /// @brief handle a dump command for a specific collection //////////////////////////////////////////////////////////////////////////////// diff --git a/arangosh/V8Client/arangodump.cpp b/arangosh/V8Client/arangodump.cpp index 727f3616ca..bda6c7f62e 100644 --- a/arangosh/V8Client/arangodump.cpp +++ b/arangosh/V8Client/arangodump.cpp @@ -377,7 +377,7 @@ static string GetArangoVersion () { } //////////////////////////////////////////////////////////////////////////////// -/// @brief fetch the version from the server +/// @brief check if server is a coordinator of a cluster //////////////////////////////////////////////////////////////////////////////// static bool GetArangoIsCluster () { diff --git a/arangosh/V8Client/arangorestore.cpp b/arangosh/V8Client/arangorestore.cpp index 43d532083c..ec0f7e4f43 100644 --- a/arangosh/V8Client/arangorestore.cpp +++ b/arangosh/V8Client/arangorestore.cpp @@ -142,6 +142,12 @@ static bool RecycleIds = false; static bool Force = false; +//////////////////////////////////////////////////////////////////////////////// +/// @brief cluster mode flag +//////////////////////////////////////////////////////////////////////////////// + +static bool clusterMode = false; + //////////////////////////////////////////////////////////////////////////////// /// @brief statistics //////////////////////////////////////////////////////////////////////////////// @@ -364,6 +370,47 @@ static string GetArangoVersion () { return version; } +//////////////////////////////////////////////////////////////////////////////// +/// @brief check if server is a coordinator of a cluster +//////////////////////////////////////////////////////////////////////////////// + +static bool GetArangoIsCluster () { + map headers; + string command = "return ArangoServerState.role();"; + + SimpleHttpResult* response = Client->request(HttpRequest::HTTP_REQUEST_POST, + "/_admin/execute?returnAsJSON=true", + command.c_str(), + command.size(), + headers); + + if (response == 0 || ! response->isComplete()) { + if (response != 0) { + delete response; + } + + return false; + } + + string role = "UNDEFINED"; + + if (response->getHttpReturnCode() == HttpResponse::OK) { + // default value + role.assign(response->getBody().c_str(), response->getBody().length()); + } + else { + if (response->wasHttpError()) { + Client->setErrorMessage(GetHttpErrorMessage(response), false); + } + + Connection->disconnect(); + } + + delete response; + + return role == "\"COORDINATOR\""; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief send the request to re-create a collection //////////////////////////////////////////////////////////////////////////////// @@ -450,8 +497,7 @@ static int SendRestoreIndexes (TRI_json_t const* json, /// @brief send the request to load data into a collection //////////////////////////////////////////////////////////////////////////////// -static int SendRestoreData (string const& cid, - string const& cname, +static int SendRestoreData (string const& cname, char const* buffer, size_t bufferSize, string& errorMsg) { @@ -743,7 +789,7 @@ static int ProcessInputDirectory (string& errorMsg) { Stats._totalBatches++; - int res = SendRestoreData(cid, cname, buffer.begin(), length, errorMsg); + int res = SendRestoreData(cname, buffer.begin(), length, errorMsg); if (res != TRI_ERROR_NO_ERROR) { TRI_CLOSE(fd); @@ -933,6 +979,10 @@ int main (int argc, char* argv[]) { } } + if (major >= 2) { + // Version 1.4 did not yet have a cluster mode + clusterMode = GetArangoIsCluster(); + } if (Progress) { cout << "Connected to ArangoDB '" << BaseClient.endpointServer()->getSpecification() << endl; From a9bd2b3259520b87b4fba6c5e48585cb21fc1c85 Mon Sep 17 00:00:00 2001 From: Michael Hackstein Date: Thu, 8 May 2014 23:22:15 +0200 Subject: [PATCH 3/3] Fixed include order of backbone models and collections --- js/apps/system/aardvark/manifest.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/js/apps/system/aardvark/manifest.json b/js/apps/system/aardvark/manifest.json index 56b4656aae..de7fad486b 100644 --- a/js/apps/system/aardvark/manifest.json +++ b/js/apps/system/aardvark/manifest.json @@ -259,12 +259,6 @@ "cluster.js": { "files": [ - "clusterFrontend/js/collections/arangoClusterStatisticsCollection.js", - "clusterFrontend/js/collections/clusterCollections.js", - "clusterFrontend/js/collections/clusterCoordinators.js", - "clusterFrontend/js/collections/clusterDatabases.js", - "clusterFrontend/js/collections/clusterServers.js", - "clusterFrontend/js/collections/clusterShards.js", "clusterFrontend/js/models/clusterCollection.js", "clusterFrontend/js/models/clusterCoordinator.js", "clusterFrontend/js/models/clusterDatabase.js", @@ -272,6 +266,12 @@ "clusterFrontend/js/models/clusterServer.js", "clusterFrontend/js/models/clusterShard.js", "clusterFrontend/js/models/clusterType.js", + "clusterFrontend/js/collections/arangoClusterStatisticsCollection.js", + "clusterFrontend/js/collections/clusterCollections.js", + "clusterFrontend/js/collections/clusterCoordinators.js", + "clusterFrontend/js/collections/clusterDatabases.js", + "clusterFrontend/js/collections/clusterServers.js", + "clusterFrontend/js/collections/clusterShards.js", "frontend/js/models/arangoDocument.js", "frontend/js/models/arangoStatistics.js", "frontend/js/models/arangoStatisticsDescription.js",