From bb3b85939f1847b8594e9b1aad6460257c27e3f4 Mon Sep 17 00:00:00 2001 From: Leonardo Menezes Date: Wed, 23 Mar 2016 14:05:21 +0100 Subject: [PATCH] initial commit --- .gitignore | 24 + Gruntfile.js | 65 + app/controllers/Application.scala | 12 + .../ClusterOverviewController.scala | 33 + app/controllers/HostsController.scala | 20 + app/controllers/Main.scala | 10 + .../ClearIndexCacheController.scala | 9 + .../elasticsearch/CloseIndexController.scala | 10 + .../elasticsearch/DeleteIndexController.scala | 10 + .../DisableShardAllocationController.scala | 9 + .../ElasticsearchController.scala | 27 + .../EnableShardAllocationController.scala | 9 + .../elasticsearch/GetIndexMapping.scala | 9 + .../elasticsearch/GetIndexSettings.scala | 9 + .../elasticsearch/NodeStatsController.scala | 9 + .../elasticsearch/OpenIndexController.scala | 10 + .../OptimizeIndexController.scala | 12 + .../elasticsearch/PutClusterSettings.scala | 9 + .../RefreshIndexController.scala | 9 + app/elastic/ElasticClient.scala | 74 ++ app/elastic/ElasticResponse.scala | 17 + app/models/ClusterHealth.scala | 25 + app/models/overview/ClusterOverview.scala | 140 +++ app/views/Index.scala.html | 33 + build.sbt | 21 + conf/application.conf | 35 + conf/logger.xml | 33 + conf/routes | 31 + package.json | 44 + project/build.properties | 1 + project/plugins.sbt | 16 + public/alerts.html | 15 + public/app.js | 1099 +++++++++++++++++ public/connect.html | 36 + public/css/app.css | 382 ++++++ public/css/lib.css | 0 public/img/favicon.png | Bin 0 -> 687 bytes public/info.html | 13 + public/lib.js | 0 public/modal.html | 19 + public/navbar.html | 28 + public/overview.html | 201 +++ public/pagination.html | 17 + public/stats.html | 62 + src/controllers/alerts.js | 21 + src/controllers/connect.js | 37 + src/controllers/modal.js | 15 + src/controllers/navbar.js | 25 + src/controllers/overview.js | 232 ++++ src/directives/pagination.js | 128 ++ src/directives/progress.js | 19 + src/directives/shard.js | 15 + src/filters/bytes.js | 18 + src/main.js | 15 + src/overview/index_filter.js | 99 ++ src/overview/node_filter.js | 55 + src/services/alerts.js | 83 ++ src/services/data.js | 190 +++ src/services/modal.js | 22 + src/services/page.js | 63 + src/stats/stats.js | 46 + .../ClusterOverviewControllerSpec.scala | 21 + .../overview/ClusterDisabledAllocation.scala | 24 + .../overview/ClusterInitializingShards.scala | 721 +++++++++++ .../models/overview/ClusterOverviewSpec.scala | 121 ++ .../overview/ClusterRelocatingShards.scala | 862 +++++++++++++ test/models/overview/ClusterStub.scala | 25 + test/models/overview/ClusterWithData.scala | 715 +++++++++++ test/models/overview/ClusterWithoutData.scala | 462 +++++++ 69 files changed, 6681 insertions(+) create mode 100644 .gitignore create mode 100644 Gruntfile.js create mode 100644 app/controllers/Application.scala create mode 100644 app/controllers/ClusterOverviewController.scala create mode 100644 app/controllers/HostsController.scala create mode 100644 app/controllers/Main.scala create mode 100644 app/controllers/elasticsearch/ClearIndexCacheController.scala create mode 100644 app/controllers/elasticsearch/CloseIndexController.scala create mode 100644 app/controllers/elasticsearch/DeleteIndexController.scala create mode 100644 app/controllers/elasticsearch/DisableShardAllocationController.scala create mode 100644 app/controllers/elasticsearch/ElasticsearchController.scala create mode 100644 app/controllers/elasticsearch/EnableShardAllocationController.scala create mode 100644 app/controllers/elasticsearch/GetIndexMapping.scala create mode 100644 app/controllers/elasticsearch/GetIndexSettings.scala create mode 100644 app/controllers/elasticsearch/NodeStatsController.scala create mode 100644 app/controllers/elasticsearch/OpenIndexController.scala create mode 100644 app/controllers/elasticsearch/OptimizeIndexController.scala create mode 100644 app/controllers/elasticsearch/PutClusterSettings.scala create mode 100644 app/controllers/elasticsearch/RefreshIndexController.scala create mode 100644 app/elastic/ElasticClient.scala create mode 100644 app/elastic/ElasticResponse.scala create mode 100644 app/models/ClusterHealth.scala create mode 100644 app/models/overview/ClusterOverview.scala create mode 100644 app/views/Index.scala.html create mode 100644 build.sbt create mode 100644 conf/application.conf create mode 100644 conf/logger.xml create mode 100644 conf/routes create mode 100644 package.json create mode 100644 project/build.properties create mode 100644 project/plugins.sbt create mode 100644 public/alerts.html create mode 100644 public/app.js create mode 100644 public/connect.html create mode 100755 public/css/app.css create mode 100644 public/css/lib.css create mode 100644 public/img/favicon.png create mode 100644 public/info.html create mode 100644 public/lib.js create mode 100644 public/modal.html create mode 100644 public/navbar.html create mode 100644 public/overview.html create mode 100644 public/pagination.html create mode 100644 public/stats.html create mode 100644 src/controllers/alerts.js create mode 100644 src/controllers/connect.js create mode 100644 src/controllers/modal.js create mode 100644 src/controllers/navbar.js create mode 100644 src/controllers/overview.js create mode 100644 src/directives/pagination.js create mode 100644 src/directives/progress.js create mode 100644 src/directives/shard.js create mode 100644 src/filters/bytes.js create mode 100644 src/main.js create mode 100644 src/overview/index_filter.js create mode 100644 src/overview/node_filter.js create mode 100644 src/services/alerts.js create mode 100644 src/services/data.js create mode 100644 src/services/modal.js create mode 100644 src/services/page.js create mode 100644 src/stats/stats.js create mode 100644 test/controllers/ClusterOverviewControllerSpec.scala create mode 100644 test/models/overview/ClusterDisabledAllocation.scala create mode 100644 test/models/overview/ClusterInitializingShards.scala create mode 100644 test/models/overview/ClusterOverviewSpec.scala create mode 100644 test/models/overview/ClusterRelocatingShards.scala create mode 100644 test/models/overview/ClusterStub.scala create mode 100644 test/models/overview/ClusterWithData.scala create mode 100644 test/models/overview/ClusterWithoutData.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1e57e85b --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Extracted from https://github.com/ulrich/macaron-factory/blob/master/.gitignore +# Ignore all dotfiles... +.* +# except for .gitignore +!.gitignore + +# Ignore Play! working directory # +db +eclipse +lib +log +logs +modules +precompiled +project/project +project/target +target +tmp +test-result +server.pid +*.iml +*.eml +activator-*.sbt +node_modules diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..35bc3e63 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,65 @@ +module.exports = function(grunt) { + + grunt.initConfig({ + clean: { + dist: { + src: ['_site/dist'] + } + }, + watch: { + scripts: { + files: ['src/**/*.*', 'src/*.*'], + tasks: ['build'], + options: { + spawn: false + } + } + }, + copy: { + main: { + files: [ + ] + } + }, + concat: { + vendorjs: { + src: [ + ], + dest: 'public/lib.js' + }, + vendorcss: { + src: [ + ], + dest: 'public/css/lib.css' + }, + appjs: { + src: [ + 'src/main.js', + 'src/*/*.js' + ], + dest: 'public/app.js' + }, + }, + jshint: { + cerebro: { + src: [ + + ] + } + }, + qunit: { + all: [] + } + }); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.loadNpmTasks("grunt-jscs"); + grunt.registerTask('dev', ['watch']) + grunt.registerTask('build', + ['clean', 'copy', 'concat' ]); +}; diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala new file mode 100644 index 00000000..eb6c0274 --- /dev/null +++ b/app/controllers/Application.scala @@ -0,0 +1,12 @@ +package controllers + +import play.api.mvc.{Action, Controller} +import play.api.http.MimeTypes + +object Application extends Controller { + + def index = Action { + Ok(views.html.Index()) + } + +} diff --git a/app/controllers/ClusterOverviewController.scala b/app/controllers/ClusterOverviewController.scala new file mode 100644 index 00000000..cb0d72b4 --- /dev/null +++ b/app/controllers/ClusterOverviewController.scala @@ -0,0 +1,33 @@ +package controllers + +import elastic.ElasticClient._ +import models.overview.ClusterOverview +import play.api.mvc.{Action, Controller} + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + + +object ClusterOverviewController extends Controller { + + def index = Action.async { + request => { + val host = request.queryString.getOrElse("host", Seq("http://localhost:9200")).head + try { + val response = Future.sequence( + Seq(clusterState(host), nodesStats(host), indicesStats(host), clusterSettings(host), aliases(host), clusterHealth(host), nodes(host), main(host)) + ).map { f => + new ClusterOverview(f(0).body, f(1).body, f(2).body, f(3).body, f(4).body, f(5).body, f(6).body, f(7).body).json + }.recover { + case e => + throw e + } + response.map(Ok(_)) + } catch { + case _ => Future.successful(Status(500)(s"Cannot connect to $host")) + } + } + + } + +} diff --git a/app/controllers/HostsController.scala b/app/controllers/HostsController.scala new file mode 100644 index 00000000..47f6dc2b --- /dev/null +++ b/app/controllers/HostsController.scala @@ -0,0 +1,20 @@ +package controllers + +import play.api.Play +import play.api.libs.json.{JsArray, JsString} +import play.api.mvc.{Action, Controller} + + +class HostsController extends Controller { + + def index = Action { + request => { + val hosts = Play.current.configuration.getConfigSeq("hosts") match { + case Some(a) => a.map { b => b.getString("host").get } + case None => Seq() + } + Ok(JsArray(hosts.map(JsString(_)))) + } + } + +} diff --git a/app/controllers/Main.scala b/app/controllers/Main.scala new file mode 100644 index 00000000..4235a94c --- /dev/null +++ b/app/controllers/Main.scala @@ -0,0 +1,10 @@ +package controllers + +import controllers.elasticsearch.ElasticsearchController +import elastic.ElasticClient + +object Main extends ElasticsearchController { + + def index = processRequest(ElasticClient.main(_)) + +} diff --git a/app/controllers/elasticsearch/ClearIndexCacheController.scala b/app/controllers/elasticsearch/ClearIndexCacheController.scala new file mode 100644 index 00000000..c4e19e58 --- /dev/null +++ b/app/controllers/elasticsearch/ClearIndexCacheController.scala @@ -0,0 +1,9 @@ +package controllers.elasticsearch + +import elastic.ElasticClient + +class ClearIndexCacheController extends ElasticsearchController { + + def index(indices: String) = processRequest(ElasticClient.clearIndexCache(indices, _)) + +} diff --git a/app/controllers/elasticsearch/CloseIndexController.scala b/app/controllers/elasticsearch/CloseIndexController.scala new file mode 100644 index 00000000..af03d964 --- /dev/null +++ b/app/controllers/elasticsearch/CloseIndexController.scala @@ -0,0 +1,10 @@ +package controllers.elasticsearch + + +import elastic.ElasticClient + +class CloseIndexController extends ElasticsearchController { + + def index(indices: String) = processRequest(ElasticClient.closeIndex(indices, _)) + +} diff --git a/app/controllers/elasticsearch/DeleteIndexController.scala b/app/controllers/elasticsearch/DeleteIndexController.scala new file mode 100644 index 00000000..80eeb21d --- /dev/null +++ b/app/controllers/elasticsearch/DeleteIndexController.scala @@ -0,0 +1,10 @@ +package controllers.elasticsearch + + +import elastic.ElasticClient + +class DeleteIndexController extends ElasticsearchController { + + def index(indices: String) = processRequest(ElasticClient.deleteIndex(indices, _)) + +} diff --git a/app/controllers/elasticsearch/DisableShardAllocationController.scala b/app/controllers/elasticsearch/DisableShardAllocationController.scala new file mode 100644 index 00000000..64ab5c2b --- /dev/null +++ b/app/controllers/elasticsearch/DisableShardAllocationController.scala @@ -0,0 +1,9 @@ +package controllers.elasticsearch + +import elastic.ElasticClient + +class DisableShardAllocationController extends ElasticsearchController { + + def index() = processRequest(ElasticClient.disableShardAllocation(_)) + +} diff --git a/app/controllers/elasticsearch/ElasticsearchController.scala b/app/controllers/elasticsearch/ElasticsearchController.scala new file mode 100644 index 00000000..1e2d4e5a --- /dev/null +++ b/app/controllers/elasticsearch/ElasticsearchController.scala @@ -0,0 +1,27 @@ +package controllers.elasticsearch + +import elastic.ElasticResponse +import play.api.Logger +import play.api.mvc.{Action, Controller} + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +trait ElasticsearchController extends Controller { + + protected val logger = Logger("elastic") + + def processRequest(f: (String => Future[ElasticResponse])) = Action.async { + request => + val host = request.queryString.getOrElse("host", Seq("http://localhost:9200")).head + try { + val response = f(host) + response.map { r => + Status(r.status)(r.body) + } + } catch { + case _ => Future.successful(Status(500)(s"Cannot connect to $host")) + } + } + +} diff --git a/app/controllers/elasticsearch/EnableShardAllocationController.scala b/app/controllers/elasticsearch/EnableShardAllocationController.scala new file mode 100644 index 00000000..ae1c4e2d --- /dev/null +++ b/app/controllers/elasticsearch/EnableShardAllocationController.scala @@ -0,0 +1,9 @@ +package controllers.elasticsearch + +import elastic.ElasticClient + +class EnableShardAllocationController extends ElasticsearchController { + + def index() = processRequest(ElasticClient.enableShardAllocation(_)) + +} diff --git a/app/controllers/elasticsearch/GetIndexMapping.scala b/app/controllers/elasticsearch/GetIndexMapping.scala new file mode 100644 index 00000000..0ea24517 --- /dev/null +++ b/app/controllers/elasticsearch/GetIndexMapping.scala @@ -0,0 +1,9 @@ +package controllers.elasticsearch + +import elastic.ElasticClient + +class GetIndexMapping extends ElasticsearchController { + + def index(indices: String) = processRequest(ElasticClient.getIndexMapping(indices, _)) + +} diff --git a/app/controllers/elasticsearch/GetIndexSettings.scala b/app/controllers/elasticsearch/GetIndexSettings.scala new file mode 100644 index 00000000..78165adb --- /dev/null +++ b/app/controllers/elasticsearch/GetIndexSettings.scala @@ -0,0 +1,9 @@ +package controllers.elasticsearch + +import elastic.ElasticClient + +class GetIndexSettings extends ElasticsearchController { + + def index(indices: String) = processRequest(ElasticClient.getIndexSettings(indices, _)) + +} diff --git a/app/controllers/elasticsearch/NodeStatsController.scala b/app/controllers/elasticsearch/NodeStatsController.scala new file mode 100644 index 00000000..568b41e3 --- /dev/null +++ b/app/controllers/elasticsearch/NodeStatsController.scala @@ -0,0 +1,9 @@ +package controllers.elasticsearch + +import elastic.ElasticClient + +class NodeStatsController extends ElasticsearchController { + + def index(nodes: String) = processRequest(ElasticClient.nodesStats(nodes, _)) + +} diff --git a/app/controllers/elasticsearch/OpenIndexController.scala b/app/controllers/elasticsearch/OpenIndexController.scala new file mode 100644 index 00000000..dc7b8949 --- /dev/null +++ b/app/controllers/elasticsearch/OpenIndexController.scala @@ -0,0 +1,10 @@ +package controllers.elasticsearch + + +import elastic.ElasticClient + +class OpenIndexController extends ElasticsearchController { + + def index(indices: String) = processRequest(ElasticClient.openIndex(indices, _)) + +} diff --git a/app/controllers/elasticsearch/OptimizeIndexController.scala b/app/controllers/elasticsearch/OptimizeIndexController.scala new file mode 100644 index 00000000..b4b8ab78 --- /dev/null +++ b/app/controllers/elasticsearch/OptimizeIndexController.scala @@ -0,0 +1,12 @@ +package controllers.elasticsearch + +import elastic.ElasticClient +import play.api.mvc.{Action, Controller} + +import scala.concurrent.ExecutionContext.Implicits.global + +class OptimizeIndexController extends ElasticsearchController { + + def index(indices: String) = processRequest(ElasticClient.optimizeIndex(indices, _)) + +} diff --git a/app/controllers/elasticsearch/PutClusterSettings.scala b/app/controllers/elasticsearch/PutClusterSettings.scala new file mode 100644 index 00000000..39321ae6 --- /dev/null +++ b/app/controllers/elasticsearch/PutClusterSettings.scala @@ -0,0 +1,9 @@ +package controllers.elasticsearch + +import elastic.ElasticClient + +class PutClusterSettings extends ElasticsearchController { + + def index = processRequest(ElasticClient.putClusterSettings("", _)) + +} diff --git a/app/controllers/elasticsearch/RefreshIndexController.scala b/app/controllers/elasticsearch/RefreshIndexController.scala new file mode 100644 index 00000000..2c7645ae --- /dev/null +++ b/app/controllers/elasticsearch/RefreshIndexController.scala @@ -0,0 +1,9 @@ +package controllers.elasticsearch + +import elastic.ElasticClient + +class RefreshIndexController extends ElasticsearchController { + + def index(indices: String) = processRequest(ElasticClient.refreshIndex(indices, _)) + +} diff --git a/app/elastic/ElasticClient.scala b/app/elastic/ElasticClient.scala new file mode 100644 index 00000000..843d3e94 --- /dev/null +++ b/app/elastic/ElasticClient.scala @@ -0,0 +1,74 @@ +package elastic + +import play.api.Play.current +import play.api.libs.ws.WS +import play.api.mvc.Results.EmptyContent + +trait ElasticClient { + + def main(host: String) = + ElasticResponse(WS.url(s"$host").get()) + + def clusterState(host: String) = + ElasticResponse(WS.url(s"$host/_cluster/state/master_node,routing_table,routing_nodes,blocks").get()) + + def indicesStats(host: String) = + ElasticResponse(WS.url(s"$host/_stats/docs,store").get()) + + def nodesStats(host: String) = + ElasticResponse(WS.url(s"$host/_nodes/stats/jvm,fs,os,process").get()) + + def nodesStats(node: String, host: String) = + ElasticResponse(WS.url(s"$host/_nodes/$node/stats?human").get()) + + def clusterSettings(host: String) = + ElasticResponse(WS.url(s"$host/_cluster/settings").get()) + + def aliases(host: String) = + ElasticResponse(WS.url(s"$host/_aliases").get()) + + def clusterHealth(host: String) = + ElasticResponse(WS.url(s"$host/_cluster/health").get()) + + def nodes(host: String) = + ElasticResponse(WS.url(s"$host/_nodes/_all/os,jvm").get()) + + def closeIndex(index: String, host: String) = + ElasticResponse(WS.url(s"$host/$index/_close").post(EmptyContent())) + + def openIndex(index: String, host: String) = + ElasticResponse(WS.url(s"$host/$index/_open").post(EmptyContent())) + + def refreshIndex(index: String, host: String) = + ElasticResponse(WS.url(s"$host/$index/_refresh").post(EmptyContent())) + + def optimizeIndex(index: String, host: String) = + ElasticResponse(WS.url(s"$host/$index/_optimize").post(EmptyContent())) + + def clearIndexCache(index: String, host: String) = + ElasticResponse(WS.url(s"$host/$index/_cache/clear").post(EmptyContent())) + + def deleteIndex(index: String, host: String) = + ElasticResponse(WS.url(s"$host/$index").delete()) + + def getIndexSettings(index: String, host: String) = + ElasticResponse(WS.url(s"$host/$index/_settings").get()) + + def getIndexMapping(index: String, host: String) = + ElasticResponse(WS.url(s"$host/$index/_mapping").get()) + + def putClusterSettings(settings: String, host: String) = + ElasticResponse(WS.url(s"$host/_cluster/settings").put(settings)) + + private def allocationSettings(value: String) = + s"""{"transient": {"cluster": {"routing": {"allocation": {"enable": \"$value\"}}}}}""" + + def enableShardAllocation(host: String) = + putClusterSettings(allocationSettings("all"), host) + + def disableShardAllocation(host: String) = + putClusterSettings(allocationSettings("none"), host) + +} + +object ElasticClient extends ElasticClient diff --git a/app/elastic/ElasticResponse.scala b/app/elastic/ElasticResponse.scala new file mode 100644 index 00000000..61d9a76b --- /dev/null +++ b/app/elastic/ElasticResponse.scala @@ -0,0 +1,17 @@ +package elastic + +import play.api.libs.json.JsValue +import play.api.libs.ws.WSResponse + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +case class ElasticResponse(status: Int, body: JsValue) + +object ElasticResponse { + + def apply(response: Future[WSResponse]): Future[ElasticResponse] = response.map { + r => ElasticResponse(r.status, r.json) + } + +} diff --git a/app/models/ClusterHealth.scala b/app/models/ClusterHealth.scala new file mode 100644 index 00000000..63ecfbdd --- /dev/null +++ b/app/models/ClusterHealth.scala @@ -0,0 +1,25 @@ +package models + +import play.api.libs.json.{JsObject, JsString, JsValue} + +class ClusterHealth(data: JsValue) { + + def json() = { + JsObject(Seq( + "cluster_name" -> (data \ "cluster_name").as[JsString], + "status" -> (data \ "status").as[JsString], + "number_of_nodes" -> (data \ "number_of_nodes").as[JsString], + "active_primary_shards" -> (data \ "active_primary_shards").as[JsString], + "active_shards" -> (data \ "active_shards").as[JsString], + "relocating_shards" -> (data \ "relocating_shards").as[JsString], + "initializing_shards" -> (data \ "initializing_shards").as[JsString], + "unassigned_shards" -> (data \ "unassigned_shards").as[JsString], + "delayed_unassigned_shards" -> (data \ "delayed_unassigned_shards").as[JsString], + "number_of_pending_tasks" -> (data \ "number_of_pending_tasks").as[JsString], + "number_of_in_flight_fetch" -> (data \ "number_of_in_flight_fetch").as[JsString], + "task_max_waiting_in_queue_millis" -> (data \ "task_max_waiting_in_queue_millis").as[JsString], + "active_shards_percent_as_number" -> (data \ "active_shards_percent_as_number").as[JsString] + )) + } + +} diff --git a/app/models/overview/ClusterOverview.scala b/app/models/overview/ClusterOverview.scala new file mode 100644 index 00000000..39127fc2 --- /dev/null +++ b/app/models/overview/ClusterOverview.scala @@ -0,0 +1,140 @@ +package models.overview + +import elastic.ElasticClient._ +import play.api.libs.json._ + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +class ClusterOverview(clusterState: JsValue, nodesStats: JsValue, indicesStats: JsValue, + clusterSettings: JsValue, aliases: JsValue, clusterHealth: JsValue, + nodes: JsValue, main: JsValue) { + + def json: JsValue = { + val indices = Indices(clusterState, indicesStats, aliases) + val clusterNodes = Nodes(clusterState, nodesStats, nodes) + + val persistentAllocation = (clusterSettings \ "persistent" \ "cluster" \ "routing" \ "allocation" \ "enable").asOpt[String].getOrElse("all") + val transientAllocation = (clusterSettings \ "transient" \ "cluster" \ "routing" \ "allocation" \ "enable").asOpt[String] + val shardAllocation = transientAllocation.getOrElse(persistentAllocation).equals("all") + + JsObject(Seq( + // clusterHealth + "cluster_name" -> (clusterHealth \ "cluster_name").as[JsString], + "status" -> (clusterHealth \ "status").as[JsString], + "number_of_nodes" -> (clusterHealth \ "number_of_nodes").as[JsNumber], + "active_primary_shards" -> (clusterHealth \ "active_primary_shards").as[JsNumber], + "active_shards" -> (clusterHealth \ "active_shards").as[JsNumber], + "relocating_shards" -> (clusterHealth \ "relocating_shards").as[JsNumber], + "initializing_shards" -> (clusterHealth \ "initializing_shards").as[JsNumber], + "unassigned_shards" -> (clusterHealth \ "unassigned_shards").as[JsNumber], + // indicesStats + "docs_count" -> (indicesStats \ "_all" \ "primaries" \ "docs" \ "count").asOpt[JsNumber].getOrElse(JsNumber(0)), + "size_in_bytes" -> (indicesStats \ "_all" \ "total" \ "store" \ "size_in_bytes").asOpt[JsNumber].getOrElse(JsNumber(0)), + "total_indices" -> JsNumber(indices.size), + "closed_indices" -> JsNumber(indices.count { idx => (idx \ "closed").as[Boolean] } ), + "special_indices" -> JsNumber(indices.count { idx => (idx \ "special").as[Boolean] } ), + "indices" -> JsArray(indices), + "nodes" -> JsArray(clusterNodes), + "shard_allocation" -> JsBoolean(shardAllocation) + )) + } +} + +object Nodes { + + def apply(clusterState: JsValue, nodesStats: JsValue, nodes: JsValue): Seq[JsValue] = { + val currentMaster = (clusterState \ "master_node").as[String] + (nodes \ "nodes").as[JsObject].value.map { case (nodeId, info) => + val master = (info \ "attributes" \ "master").asOpt[String].getOrElse("true").equals("true") + val data = (info \ "attributes" \ "data").asOpt[String].getOrElse("true").equals("true") + val client = (info \ "attributes" \ "client").asOpt[String].getOrElse("false").equals("true") + + val stats = (nodesStats \ "nodes" \ nodeId).as[JsObject] + val totalInBytes = (stats \ "fs" \ "total" \ "total_in_bytes").asOpt[Long].getOrElse(0l) // FIXME: 1.X + val diskFreeInBytes = (stats \ "fs" \ "total" \ "free_in_bytes").asOpt[Long].getOrElse(0l) // FIXME: 1.X + val cpuPercent = (stats \ "process" \ "cpu" \ "percent").asOpt[JsNumber].getOrElse((stats \ "os" \ "user").as[JsNumber]) + Json.obj( + "id" -> JsString(nodeId), + "current_master" -> JsBoolean(nodeId.equals(currentMaster)), + "name" -> (info \ "name").as[JsString], + "host" -> (info \ "host").as[JsString], + "ip" -> (info \ "ip").as[JsString], + "es_version" -> (info \ "version").as[JsString], + "jvm_version" -> (info \ "jvm" \ "version").as[JsString], + "load_average" -> JsNumber(BigDecimal((stats \ "os" \ "load_average").asOpt[Int].getOrElse(0))),// FIXME: 1.X + "available_processors" -> (info \ "os" \ "available_processors").as[JsNumber], + "cpu_percent" -> cpuPercent, + "master" -> JsBoolean(master && !client), + "data" -> JsBoolean(data && !client), + "client" -> JsBoolean(client || !master && !data), + "heap" -> Json.obj( + "used" -> (stats \ "jvm" \ "mem" \ "heap_used_in_bytes").as[JsNumber], + "committed" -> (stats \ "jvm" \ "mem" \ "heap_committed_in_bytes").as[JsNumber], + "used_percent" -> (stats \ "jvm" \ "mem" \ "heap_used_percent").as[JsNumber], + "max" -> (stats \ "jvm" \ "mem" \ "heap_max_in_bytes").as[JsNumber] + ), + "disk" -> Json.obj( + "total_in_bytes" -> JsNumber(totalInBytes), + "disk_free_in_bytes" -> JsNumber(diskFreeInBytes), + "used_percent" -> JsNumber(100 - (100 * (diskFreeInBytes.toFloat / totalInBytes.toFloat)).toInt) + ) + ) + }.toSeq + } + +} + +object Indices { + + def apply(clusterState: JsValue, indicesStats: JsValue, aliases: JsValue) = { + val routingTable = (clusterState \ "routing_table" \ "indices").as[JsObject] + val openIndices = routingTable.value.map { case (index, shards) => + val stats = (indicesStats \ "indices" \ index).asOpt[JsObject].getOrElse(Json.obj()) + val indexShards = (shards \ "shards").as[JsObject].values.flatMap { case shards => + shards.as[JsArray].value.map { instance => + (instance \ "node").asOpt[String].getOrElse("unassigned") -> instance + } + }.groupBy(_._1).mapValues { shards => JsArray(shards.map(_._2).toSeq) }.toSeq + + val numShards = (shards \ "shards").as[JsObject].keys.size + val numReplicas = (shards \ "shards" \ "0").as[JsArray].value.size - 1 + + val unhealthy = indexShards.find(_._1.equals("unassigned")).isDefined + val special = index.startsWith(".") + + JsObject(Seq( + "name" -> JsString(index), + "closed" -> JsBoolean(false), + "special" -> JsBoolean(special), + "unhealthy" -> JsBoolean(unhealthy), + "doc_count" -> (stats \ "primaries" \ "docs" \ "count").asOpt[JsNumber].getOrElse(JsNumber(0)), + "deleted_docs" -> (stats \ "primaries" \ "docs" \ "deleted").asOpt[JsNumber].getOrElse(JsNumber(0)), + "size_in_bytes" -> (stats \ "primaries" \ "store" \ "size_in_bytes").asOpt[JsNumber].getOrElse(JsNumber(0)), + "total_size_in_bytes" -> (stats \ "total" \ "store" \ "size_in_bytes").asOpt[JsNumber].getOrElse(JsNumber(0)), + "aliases" -> JsArray((aliases \ index \ "aliases").as[JsObject].keys.map(JsString(_)).toSeq), + "num_shards" -> JsNumber(numShards), + "num_replicas" -> JsNumber(numReplicas), + "shards" -> JsObject(indexShards) + )) + }.toSeq + + openIndices ++ ClosedIndices(clusterState) + } + +} + +object ClosedIndices { + + def apply(clusterState: JsValue) = { + val blocks = (clusterState \ "blocks" \ "indices").asOpt[JsObject].getOrElse(Json.obj()) + blocks.keys.collect { + case index if (blocks \ index \ "4").asOpt[JsObject].isDefined => + Json.obj( + "name" -> JsString(index), + "closed" -> JsBoolean(true), + "special" -> JsBoolean(index.startsWith(".")) + ) + } + } +} diff --git a/app/views/Index.scala.html b/app/views/Index.scala.html new file mode 100644 index 00000000..72578de8 --- /dev/null +++ b/app/views/Index.scala.html @@ -0,0 +1,33 @@ + + + + + + + + + {{title}} + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+ + diff --git a/build.sbt b/build.sbt new file mode 100644 index 00000000..e0d51b25 --- /dev/null +++ b/build.sbt @@ -0,0 +1,21 @@ +name := "cerebro" + +version := "0.0.1" + +scalaVersion := "2.11.6" + +libraryDependencies ++= Seq( + "org.webjars" %% "webjars-play" % "2.4.0-1", + "org.webjars" % "angularjs" % "1.5.0", + "org.webjars" % "bootstrap" % "4.0.0-alpha.2", + "org.webjars" % "font-awesome" % "4.5.0", + "org.webjars.bower" % "tether" % "1.1.1", + "org.elasticsearch" % "elasticsearch" % "2.2.0", + "com.typesafe.play" %% "play-ws" % "2.4.6", + "org.specs2" %% "specs2-junit" % "3.6.5", + "org.specs2" %% "specs2-core" % "3.6.5" +) + +lazy val root = (project in file(".")).enablePlugins(PlayScala) + +pipelineStages := Seq(digest, gzip) diff --git a/conf/application.conf b/conf/application.conf new file mode 100644 index 00000000..4753280f --- /dev/null +++ b/conf/application.conf @@ -0,0 +1,35 @@ +# This is the main configuration file for the application. +# ~~~~~ + +# Secret key +# ~~~~~ +# The secret key is used to secure cryptographics functions. +# If you deploy your application to several instances be sure to use the same key! +application.secret="ki:s:[[@=Ag?QI`W2jMwkY:eqvrJ]JqoJyi2axj3ZvOv^/KavOT4ViJSv?6YY4[N" + +# The application languages +# ~~~~~ +application.langs="en" + +hosts = [ + { + host = "http://localhost:9200" + }, + { + host = "http://localhost:9201" + } +] + +# Logger +# ~~~~~ +# You can also configure logback (http://logback.qos.ch/), by providing a logger.xml file in the conf directory . + +# Root logger: +logger.root=ERROR + +# Logger used by the framework: +logger.play=INFO + +# Logger provided to your application: +logger.application=DEBUG + diff --git a/conf/logger.xml b/conf/logger.xml new file mode 100644 index 00000000..7e15b77b --- /dev/null +++ b/conf/logger.xml @@ -0,0 +1,33 @@ + + + + + + ${application.home}/logs/application.log + + %date - [%level] - from %logger in %thread %n%message%n%xException%n + + + + + + %coloredLevel %logger{15} - %message%n%xException{5} + + + + + + + + + + + + + + + + + + + diff --git a/conf/routes b/conf/routes new file mode 100644 index 00000000..be5643ae --- /dev/null +++ b/conf/routes @@ -0,0 +1,31 @@ +# Routes +# This file defines all application routes (Higher priority routes first) +# ~~~~ + + +# Home page +GET / controllers.Application.index() + +# +GET /main controllers.Main.index() +GET /apis/overview controllers.ClusterOverviewController.index() +POST /apis/:indices/_close @controllers.elasticsearch.CloseIndexController.index(indices) +POST /apis/:indices/_open @controllers.elasticsearch.OpenIndexController.index(indices) +POST /apis/:indices/_optimize @controllers.elasticsearch.OptimizeIndexController.index(indices) +POST /apis/:indices/_cache/clear @controllers.elasticsearch.ClearIndexCacheController.index(indices) +POST /apis/:indices/_refresh @controllers.elasticsearch.RefreshIndexController.index(indices) +DELETE /apis/:indices/_delete @controllers.elasticsearch.DeleteIndexController.index(indices) +GET /apis/:index/_settings @controllers.elasticsearch.GetIndexSettings.index(index) +GET /apis/:index/_mapping @controllers.elasticsearch.GetIndexMapping.index(index) +PUT /apis/_cluster/settings @controllers.elasticsearch.PutClusterSettings.index +GET /apis/_nodes/:nodes/stats @controllers.elasticsearch.NodeStatsController.index(nodes) +PUT /apis/disable_allocation @controllers.elasticsearch.DisableShardAllocationController.index() +PUT /apis/enable_allocation @controllers.elasticsearch.EnableShardAllocationController.index() + +GET /apis/hosts @controllers.HostsController.index + + +# Map the JS resource paths +GET /webjars/*file controllers.WebJarAssets.at(file) +GET /assets/*file controllers.Assets.at(path="/public", file) +GET /*file controllers.Assets.versioned(path="/public", file: Asset) diff --git a/package.json b/package.json new file mode 100644 index 00000000..2de481bf --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "cerebro", + "version": "0.0.1", + "description": "cerebro - elasticsearch admin", + "main": "index.html", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git://github.com/lmenezes/cerebro.git" + }, + "author": "Leonardo Menezes", + "license": "MIT", + "bugs": { + "url": "https://github.com/lmenezes/cerebro/issues" + }, + "devDependencies": { + "grunt": "^0.4.5", + "grunt-contrib-clean": "^0.5.0", + "grunt-contrib-concat": "^0.3.0", + "grunt-contrib-connect": "^0.5.0", + "grunt-contrib-copy": "^0.4.1", + "grunt-contrib-jshint": "^0.8.0", + "grunt-contrib-qunit": "^0.4.0", + "grunt-contrib-watch": "^0.5.3", + "grunt-jscs": "^1.8.0", + "grunt-karma": "^0.6.2", + "karma": "~0.10.9", + "karma-chrome-launcher": "~0.1.2", + "karma-coffee-preprocessor": "~0.1.2", + "karma-firefox-launcher": "~0.1.3", + "karma-html2js-preprocessor": "~0.1.0", + "karma-jasmine": "~0.1.5", + "karma-phantomjs-launcher": "~0.1.2", + "karma-requirejs": "~0.2.1", + "karma-script-launcher": "~0.1.0", + "requirejs": "~2.1.10" + }, + "directories": { + "test": "tests" + }, + "dependencies": {} +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 00000000..be6c454f --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.5 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 00000000..b748c8fb --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,16 @@ +// Comment to get more information during initialization +logLevel := Level.Warn + +// The Typesafe repository +resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/" + +// Use the Play sbt plugin for Play projects +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.6") + +addSbtPlugin("com.typesafe.sbt" % "sbt-rjs" % "1.0.4") + +addSbtPlugin("com.typesafe.sbt" % "sbt-digest" % "1.0.0") + +addSbtPlugin("com.typesafe.sbt" % "sbt-gzip" % "1.0.0") + +addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0") diff --git a/public/alerts.html b/public/alerts.html new file mode 100644 index 00000000..40da8906 --- /dev/null +++ b/public/alerts.html @@ -0,0 +1,15 @@ +
+
+ + + +   + + {{a.message}} + + {{a.message}} +
+
{{a.getResponse()}}
+
+
+
diff --git a/public/app.js b/public/app.js new file mode 100644 index 00000000..64882e19 --- /dev/null +++ b/public/app.js @@ -0,0 +1,1099 @@ +'use strict'; + +angular.module('cerebro', ['ngRoute']).config(['$routeProvider', + function($routeProvider) { + $routeProvider. + when('/overview', { + templateUrl: 'overview.html', + controller: 'OverviewController' + }). + when('/connect', { + templateUrl: 'connect.html', + controller: 'ConnectController' + }). + otherwise({redirectTo: '/connect'}); + }]); + +angular.module('cerebro').controller('AlertsController', ['$scope', 'AlertService', + function($scope, AlertService) { + + $scope.alerts = []; + + $scope.$watch( + function() { + return AlertService.alerts; + }, + function(newValue, oldValue) { + $scope.alerts = AlertService.alerts; + } + ); + + $scope.remove = function(id) { + AlertService.remove(id); + }; + + } + +]); + +angular.module('cerebro').controller('ConnectController', [ + '$scope', '$location', 'DataService', 'AlertService', + function($scope, $location, DataService, AlertService) { + + $scope.hosts = undefined; + + $scope.connecting = false; + + $scope.host = undefined; + + DataService.getHosts( + function(hosts) { + $scope.hosts = hosts; + }, + function(error) { + + } + ); + + $scope.connect = function(host) { + if (host) { + $scope.connecting = true; + DataService.setHost( + host, + function(response) { + $location.path("/overview"); + $scope.host = DataService.getHost(); + }, + function(response) { + $scope.connecting = false; + AlertService.error("Error connecting to " + host, response); + } + ); + } + }; + + }]); + +angular.module('cerebro').controller('ModalController', ['$scope', 'ModalService', + function($scope, ModalService) { + + $scope.service = ModalService; + + $scope.close = function() { + $scope.service.close(); + }; + + $scope.confirm = function() { + $scope.service.confirm(); + }; + + } +]); + +angular.module('cerebro').controller('NavbarController', ['PageService', '$scope', '$http', 'DataService', + function (PageService, $scope, $http, DataService) { + + $scope.status = undefined; + $scope.cluster_name = undefined; + $scope.host = undefined; + + $scope.$watch( + function () { + return DataService.getData(); + }, + function (data) { + if (data) { + $scope.status = data.status; + $scope.cluster_name = data.cluster_name; + $scope.host = DataService.getHost(); + } else { + $scope.status = undefined; + $scope.cluster_name = undefined; + $scope.host = undefined; + } + } + ); + + }]); + +angular.module('cerebro').controller('OverviewController', ['$scope', '$http', '$window', 'DataService', 'AlertService', 'ModalService', + function ($scope, $http, $window, DataService, AlertService, ModalService) { + + $scope.indices = undefined; + $scope.nodes = undefined; + $scope.unassigned_shards = 0; + $scope.indices_filter = new IndexFilter('', true, false, true, true, 0); + $scope.nodes_filter = new NodeFilter('', true, false, false, 0); + $scope.closed_indices = 0; + $scope.special_indices = 0; + $scope.expandedView = false; + $scope.shardAllocation = true; + + $scope.getPageSize = function() { + return Math.max(Math.round($window.innerWidth / 280), 1); + }; + + $scope.paginator = new Paginator(1, $scope.getPageSize(), [], $scope.indices_filter); + + $scope.page = $scope.paginator.getPage(); + + $($window).resize(function() { + $scope.$apply(function() { + $scope.paginator.setPageSize($scope.getPageSize()); + }); + }); + + $scope.$watch( + function() { + return DataService.getData(); + }, + function(data) { + if (data) { + $scope.setIndices(data.indices); + $scope.setNodes(data.nodes); + $scope.unassigned_shards = data.unassigned_shards; + $scope.closed_indices = data.closed_indices; + $scope.special_indices = data.special_indices; + $scope.shardAllocation = data.shard_allocation; + } else { + $scope.indices = undefined; + $scope.nodes = undefined; + } + } + ); + + $scope.$watch('paginator', function() { + if (DataService.getData()) { + $scope.setIndices(DataService.getData().indices); + } + }, true); + + $scope.setIndices = function(indices) { + $scope.paginator.setCollection(indices); + $scope.page = $scope.paginator.getPage(); + }; + + $scope.$watch('nodes_filter', function() { + if (DataService.getData()) { + $scope.setNodes(DataService.getData().nodes); + } + }, + true); + + $scope.setNodes = function(nodes) { + $scope.nodes = nodes.filter(function(node) { + return $scope.nodes_filter.matches(node); + }); + }; + + var success = function(data) { + DataService.forceRefresh(); + AlertService.success('Operation successfully executed', data); + }; + + var error = function(data) { + AlertService.error('Operation failed', data); + }; + + var displayInfo = function(info) { + ModalService.showInfo(info); + }; + + $scope.openIndex = function(index) { + ModalService.promptConfirmation( + 'Open ' + index + '?', + function() { + DataService.openIndex(index, success, error); + } + ); + }; + + $scope.closeIndex = function(index) { + ModalService.promptConfirmation( + 'Close ' + index + '?', + function() { + DataService.closeIndex(index, success, error); + } + ); + }; + + $scope.deleteIndex = function(index) { + ModalService.promptConfirmation( + 'Delete ' + index + '?', + function() { + DataService.deleteIndex(index, success, error); + } + ); + }; + + $scope.clearIndexCache = function(index) { + ModalService.promptConfirmation( + 'Clear ' + index + ' cache?', + function() { + DataService.clearIndexCache(index, success, error); + } + ); + }; + + $scope.refreshIndex = function(index) { + ModalService.promptConfirmation( + 'Refresh index ' + index + '?', + function() { + DataService.refreshIndex(index, success, error); + } + ); + }; + + $scope.optimizeIndex = function(index) { + ModalService.promptConfirmation( + 'Optimize index ' + index + '?', + function() { + DataService.optimizeIndex(index, success, error); + } + ); + }; + + // Mass actions + + $scope.closeIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Close all ' + indices.length + ' selected indices?', + function() { + DataService.closeIndex(indices.join(","), success, error); + } + ); + }; + + $scope.openIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Open all ' + indices.length + ' selected indices?', + function() { + DataService.openIndex(indices.join(","), success, error); + } + ); + }; + + $scope.optimizeIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Optimize all ' + indices.length + ' selected indices?', + function() { + DataService.optimizeIndex(indices.join(","), success, error); + } + ); + }; + + $scope.refreshIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Refresh all ' + indices.length + ' selected indices?', + function() { + DataService.refreshIndex(indices.join(","), success, error); + } + ); + }; + + $scope.clearIndicesCache = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Clear all ' + indices.length + ' selected indices cache?', + function() { + DataService.clearIndexCache(indices.join(","), success, error); + } + ); + }; + + $scope.deleteIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Delete all ' + indices.length + ' selected indices?', + function() { + DataService.deleteIndex(indices.join(","), success, error); + } + ); + }; + + $scope.nodeStats = function(node) { + DataService.nodeStats(node, displayInfo, error); + }; + + $scope.getIndexSettings = function (index) { + DataService.getIndexSettings(index, displayInfo, error); + }; + + $scope.getIndexMapping = function (index) { + DataService.getIndexMapping(index, displayInfo, error); + }; + + $scope.disableShardAllocation = function () { + DataService.disableShardAllocation(success, error); + }; + + $scope.enableShardAllocation = function () { + DataService.enableShardAllocation(success, error); + }; + + }]); + +angular.module('cerebro').directive('ngPagination', ['$document', function($document) { + + return { + scope: { + paginator: '=paginator', + page: '=page', + label: '=label' + }, + templateUrl: 'pagination.html', + link: function(scope, element, attrs) { + var handler = function(event) { + var $target = $(event.target); + if ($target.is('input, textarea')) { + return; + } + if (event.keyCode == 39 && scope.page.next) { + scope.$apply(function() { + scope.paginator.nextPage(); + event.preventDefault(); + }); + } + if (event.keyCode == 37 && scope.page.previous) { + scope.$apply(function() { + scope.paginator.previousPage(); + event.preventDefault(); + }); + } + }; + + $document.bind('keydown', handler); + element.on('$destroy', function() { + $document.unbind('keydown', handler); + }); + } + }; +}]); + +function Paginator(page, pageSize, collection, filter) { + + this.filter = filter; + + this.page = page; + + this.pageSize = pageSize; + + this.$collection = collection ? collection : []; + + this.nextPage = function() { + this.page += 1; + }; + + this.previousPage = function() { + this.page -= 1; + }; + + this.setPageSize = function(newSize) { + this.pageSize = newSize; + }; + + this.getPageSize = function() { + return this.pageSize; + }; + + this.getCurrentPage = function() { + return this.page; + }; + + this.getPage = function() { + var results = this.getResults(); + var total = results.length; + + var first = total > 0 ? ((this.page - 1) * this.pageSize) + 1 : 0; + while (total < first) { + this.previousPage(); + first = (this.page - 1) * this.pageSize + 1; + } + var lastPage = this.page * this.pageSize > total; + var last = lastPage ? total : this.page * this.pageSize; + + var elements = total > 0 ? results.slice(first - 1, last) : []; + + var next = this.pageSize * this.page < total; + var previous = this.page > 1; + while (elements.length < this.pageSize) { + elements.push(null); + } + return new Page(elements, total, first, last, next, previous); + }; + + this.setCollection = function(collection) { + if (this.filter.getSorting()) { + this.$collection = collection.sort(this.filter.getSorting()); + } else { + this.$collection = collection; + } + }; + + this.getResults = function() { + var filter = this.filter; + var collection = this.$collection; + if (filter.isBlank()) { + return collection; + } else { + var filtered = []; + collection.forEach(function(item) { + if (filter.matches(item)) { + filtered.push(item); + } + }); + return filtered; + } + }; + + this.getCollection = function() { + return this.$collection; + }; + +} + +function Page(elements, total, first, last, next, previous) { + this.elements = elements; + this.total = total; + this.first = first; + this.last = last; + this.next = next; + this.previous = previous; +} + + +angular.module('cerebro').directive('ngProgress', + function () { + + return { + scope: { + value: '=value', + max: '=max', + text: '=text' + }, + template: function (elem, attrs) { + return '{{text}}' + + '' + + '{{value}}%' + + '' + } + }; + } +); + +angular.module('cerebro').directive('ngShard', + function () { + + return { + scope: { + shard: '=shard' + }, + template: function (elem, attrs) { + return '' + + '{{shard.shard}}' + + ''; + } + }; + } +); + +angular.module('cerebro').filter('bytes', function() { + + var UNITS = ['b', 'KB', 'MB', 'GB', 'TB', 'PB']; + + function stringify(bytes) { + if (bytes > 0) { + var e = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, e)).toFixed(2) + UNITS[e]; + } else { + return 0 + UNITS[0]; + } + } + + return function(bytes) { + return stringify(bytes); + }; + +}); + +function IndexFilter(name, closed, special, healthy, asc, timestamp) { + this.name = name; + this.closed = closed; + this.special = special; + this.healthy = healthy; + this.sort = 'name'; + this.asc = asc; + this.timestamp = timestamp; + + this.getSorting = function() { + var asc = this.asc; + switch (this.sort) { + case 'name': + return function(a, b) { + if (asc) { + return a.name.localeCompare(b.name); + } else { + return b.name.localeCompare(a.name); + } + }; + default: + return undefined; + } + }; + + this.clone = function() { + return new IndexFilter( + this.name, + this.closed, + this.special, + this.healthy, + this.asc, + this.timestamp + ); + }; + + this.equals = function(other) { + return ( + other !== null && + this.name === other.name && + this.closed === other.closed && + this.special === other.special && + this.healthy === other.healthy && + this.asc === other.asc && + this.timestamp === other.timestamp + ); + }; + + this.isBlank = function() { + return ( + !this.name && + this.closed && + this.special && + this.healthy && + this.asc + ); + }; + + this.matches = function(index) { + var matches = true; + if (!this.special && index.special) { + matches = false; + } + if (!this.closed && index.closed) { + matches = false; + } + // Hide healthy == show unhealthy only + if (!this.healthy && !index.unhealthy) { + matches = false; + } + if (matches && this.name) { + try { + var regExp = new RegExp(this.name.trim(), 'i'); + matches = regExp.test(index.name); + if (!matches && index.aliases) { + for (var idx = 0; idx < index.aliases.length; idx++) { + if ((matches = regExp.test(index.aliases[idx]))) { + break; + } + } + } + } + catch (err) { // if not valid regexp, still try normal matching + matches = index.name.indexOf(this.name.toLowerCase()) != -1; + if (!matches) { + for (var idx = 0; idx < index.aliases.length; idx++) { + var alias = index.aliases[idx].toLowerCase(); + matches = true; + if ((matches = (alias.indexOf(this.name.toLowerCase()) != -1))) { + break; + } + } + } + } + } + return matches; + }; + +} + +function NodeFilter(name, data, master, client, timestamp) { + this.name = name; + this.data = data; + this.master = master; + this.client = client; + this.timestamp = timestamp; + + this.clone = function() { + return new NodeFilter(this.name, this.data, this.master, this.client); + }; + + this.getSorting = function() { + return undefined; + }; + + this.equals = function(other) { + return ( + other !== null && + this.name == other.name && + this.data == other.data && + this.master == other.master && + this.client == other.client && + this.timestamp == other.timestamp + ); + }; + + this.isBlank = function() { + return !this.name && (this.data && this.master && this.client); + }; + + this.matches = function(node) { + if (this.isBlank()) { + return true; + } else { + return this.matchesName(node.name) && this.matchesType(node); + } + }; + + this.matchesType = function(node) { + return ( + node.data && this.data || + node.master && this.master || + node.client && this.client + ); + }; + + this.matchesName = function(name) { + if (this.name) { + return name.toLowerCase().indexOf(this.name.toLowerCase()) != -1; + } else { + return true; + } + }; + +} + +var Alert = function(message, response, level, _class, icon) { + var currentDate = new Date(); + this.message = message; + this.response = response; + this.level = level; + this.class = _class; + this.icon = icon; + this.timestamp = currentDate; + this.id = 'alert_box_' + currentDate.getTime(); + + this.hasResponse = function() { + return this.response; + }; + + this.getResponse = function() { + if (this.response) { + return JSON.stringify(this.response, undefined, 2); + } + }; +}; + +angular.module('cerebro').factory('AlertService', function() { + this.maxAlerts = 3; + + this.alerts = []; + + // removes ALL alerts + this.clear = function() { + this.alerts.length = 0; + }; + + // remove a particular alert message + this.remove = function(id) { + $('#' + id).fadeTo(1000, 0).slideUp(200, function() { + $(this).remove(); + }); + this.alerts = this.alerts.filter(function(a) { + return id != a.id; + }); + }; + + // creates an error alert + this.error = function(msg, resp, timeout) { + timeout = timeout ? timeout : 7500; + var alert = new Alert(msg, resp, 'error', 'red', 'fa fa-warning'); + return this.addAlert(alert, timeout); + }; + + // creates an info alert + this.info = function(msg, resp, timeout) { + timeout = timeout ? timeout : 2500; + var alert = new Alert(msg, resp, 'info', 'blue', 'fa fa-info'); + return this.addAlert(alert, timeout); + }; + + // creates success alert + this.success = function(msg, resp, timeout) { + timeout = timeout ? timeout : 2500; + var alert = new Alert(msg, resp, 'success', 'green', 'fa fa-check'); + return this.addAlert(alert, timeout); + }; + + // creates a warn alert + this.warn = function(msg, resp, timeout) { + timeout = timeout ? timeout : 5000; + var alert = new Alert(msg, resp, 'warn', 'yellow', 'fa fa-info'); + return this.addAlert(alert, timeout); + }; + + this.addAlert = function(alert, timeout) { + this.alerts.unshift(alert); + var service = this; + setTimeout(function() { + service.remove(alert.id); + }, timeout); + if (this.alerts.length >= this.maxAlerts) { + this.alerts.length = 3; + } + return alert.id; + }; + + return this; +}); + +angular.module('cerebro').factory('DataService', function ($rootScope, $timeout, $http, $location) { + + var data = undefined; // current data + + var host = undefined; + + var baseUrl = $location.protocol() + '://' + $location.host() + ':' + $location.port(); + + var refresh = function(success, error) { + if (host) { + var config = { + method: 'GET', + url: baseUrl + '/apis/overview', + params: {host: host} + }; + $http(config). + success(function(response) { + data = response; + if (success) { + success(response); + } + }). + error(function(response) { + data = undefined; + if (error) { + error(response); + } + }); + } else { + $location.path("/connect"); + } + }; + + var autoRefresh = function () { + refresh(); + $timeout(autoRefresh, 3000); + }; + + this.getData = function() { + return data; + }; + + this.forceRefresh = function() { + refresh(); + }; + + this.getHost = function() { + return host; + }; + + this.setHost = function(newHost, success, error) { + data = undefined; + host = newHost; + refresh(success, error); + }; + + autoRefresh(); + + this.closeIndex = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_close', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.openIndex = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_open', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.optimizeIndex = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_optimize', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.refreshIndex = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_refresh', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.clearIndexCache = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_cache/clear', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.deleteIndex = function(index, success, error) { + var config = { + method: 'DELETE', + url: baseUrl + '/apis/' + index + '/_delete', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.getIndexSettings = function(index, success, error) { + var config = { + method: 'GET', + url: baseUrl + '/apis/' + index + '/_settings', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.getIndexMapping = function(index, success, error) { + var config = { + method: 'GET', + url: baseUrl + '/apis/' + index + '/_mapping', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.nodeStats = function(node, success, error) { + var config = { + method: 'GET', + url: baseUrl + '/apis/_nodes/' + node + '/stats', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.enableShardAllocation = function(success, error) { + var config = { + method: 'PUT', + url: baseUrl + '/apis/enable_allocation', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.disableShardAllocation = function(success, error) { + var config = { + method: 'PUT', + url: baseUrl + '/apis/disable_allocation', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.getHosts = function (success, error) { + var config = { + method: 'GET', + url: baseUrl + '/apis/hosts' + }; + $http(config).success(success).error(error); + }; + + return this; + +}); + +angular.module('cerebro').factory('ModalService', ['$sce', function ($sce) { + + this.text = undefined; + this.confirm = undefined; + + this.info = undefined; + + this.promptConfirmation = function (body, confirmCallback) { + this.text = body; + this.confirm = confirmCallback; + this.info = undefined; + }; + + this.showInfo = function (info) { + this.info = $sce.trustAsHtml(JSON.stringify(info, '', 2)); + //$scope.body= $sce.trustAsHtml(JSONTree.create(info)); + this.confirm = undefined; + this.text = undefined; + }; + + return this; +}]); + +angular.module('cerebro').factory('PageService', ['DataService', '$rootScope', '$document', + function (DataService, $rootScope, $document) { + + var link = $document[0].querySelector('link[rel~=\'icon\']'); + var clusterName = undefined; + var clusterStatus = undefined; + + if (link) { + var faviconUrl = link.href; + var img = $document[0].createElement('img'); + img.src = faviconUrl; + } + + $rootScope.$watch( + function () { + return DataService.getData(); + }, + function (data) { + if (data) { + setPageTitle(data.cluster_name); + setFavIconColor(data.status); + } + } + ); + + var setPageTitle = function (newClusterName) { + if (clusterName !== newClusterName) { + if (newClusterName) { + clusterName = newClusterName; + $rootScope.title = 'cerebro[' + clusterName + ']'; + } else { + clusterName = undefined; + $rootScope.title = 'cerebro - no connection'; + } + } + }; + + var setFavIconColor = function(newClusterStatus) { + if (link && clusterStatus !== newClusterStatus) { + clusterStatus = newClusterStatus; + try { + var colors = {green: '#1AC98E', yellow: '#E4D836', red: '#E64759'}; + var color = clusterStatus ? colors[clusterStatus] : '#222426'; + var canvas = $document[0].createElement('canvas'); + canvas.width = 16; + canvas.height = 16; + var context = canvas.getContext('2d'); + context.drawImage(img, 0, 0); + context.globalCompositeOperation = 'source-in'; + context.fillStyle = color; + context.fillRect(0, 0, 16, 16); + context.fill(); + link.type = 'image/png'; + link.href = canvas.toDataURL(); + } catch (exception) { + // + } + } + }; + + return this; + + }]); + +angular.module('cerebro').controller('StatsController', ['$scope', '$http', 'DataService', + function ($scope, $http, DataService) { + + $scope.number_of_nodes = undefined; + + $scope.indices = undefined; + + $scope.active_primary_shards = undefined; + $scope.active_shards = undefined; + $scope.relocating_shards = undefined; + $scope.initializing_shards = undefined; + $scope.unassigned_shards = undefined; + $scope.total_shards = undefined; + + $scope.docs_count = undefined; + + $scope.size_in_bytes = undefined; + + $scope.cluster_name = undefined; + + $scope.$watch( + function () { + return DataService.getData(); + }, + function (data) { + if (data) { + $scope.number_of_nodes = data.number_of_nodes; + $scope.indices = data.indices.length; + $scope.active_primary_shards = data.active_primary_shards; + $scope.active_shards = data.active_shards; + $scope.relocating_shards = data.relocating_shards; + $scope.initializing_shards = data.initializing_shards; + $scope.unassigned_shards = data.unassigned_shards; + $scope.docs_count = data.docs_count; + $scope.size_in_bytes = data.size_in_bytes; + $scope.cluster_name = data.cluster_name; + + $scope.total_shards = $scope.active_shards + + $scope.relocating_shards + + $scope.initializing_shards + + $scope.unassigned_shards; + } + } + ); + + }]); \ No newline at end of file diff --git a/public/connect.html b/public/connect.html new file mode 100644 index 00000000..b72a3c78 --- /dev/null +++ b/public/connect.html @@ -0,0 +1,36 @@ +
+
+
+

Welcome to Cerebro

+
+
+
+ +
+ + + + +
+ + + + {{host}} +
+
+
+ + +
+
+
+
+ + Connecting + + +
+
+
+
+
diff --git a/public/css/app.css b/public/css/app.css new file mode 100755 index 00000000..0882a6a3 --- /dev/null +++ b/public/css/app.css @@ -0,0 +1,382 @@ +html, body { + height: 100%; +} +body { + padding-top: 50px; +} + +.content { + padding-top: 20px; + padding-bottom: 10px; + font-size: 13px; + font-weight: 300; + height: 100%; + min-height: 100%; +} + +.navbar-fixed-top { + border: 0; +} + +.main { + padding: 20px; +} + +@media (min-width: 768px) { + .main { + padding-right: 40px; + padding-left: 40px; + } +} + +.main .page-header { + margin-top: 0; +} + +/** Overview **/ +table.shard-map { + table-layout: fixed; + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Chrome/Safari/Opera */ + -khtml-user-select: none; /* Konqueror */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE/Edge */ + user-select: none; /* non-prefixed version, currently */ +} + +@-moz-document url-prefix() { + table.shard-map { + table-layout: auto; + } + table.shard-map tr td { + width: 16.67%; + } +} + +.table.shard-map td, .table th { + padding: .4rem !important; +} + +.disabled { + opacity: 0.2; +} + +.title { + font-weight: 500; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: block; +} + +.subtitle { + font-size: 11px; + font-weight: 400; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: block; +} + +.detail { + font-size: 10px; + font-weight: 300; +} + +/** Navbar **/ +.navbar-red { + /*background-color: #222426 !important;*/ + border-bottom: 5px solid #E64759 !important; +} + +.navbar-yellow { + /*background-color: #222426 !important;*/ + border-bottom: 5px solid #E4D836 !important; +} + +.navbar-green { + /*background-color: #222426 !important;*/ + border-bottom: 5px solid #1AC98E !important; +} +.navbar- { + border-bottom: 5px solid #55595c !important; +} + +/** Shards **/ +.shard { + display: inline-block; + width: 22px; + height: 22px; + vertical-align: middle; + text-align: center; + line-height: 16px; + margin-top: 2px; + margin-bottom: 2px; +} + +.shard-started { + border: 2px solid #1AC98E; + color: #1AC98E; +} + +.shard-initializing { + border: 1px solid #1CA8DD; + color: #1CA8DD; +} + +.shard-relocated { + border: 1px solid #9F85FF; + color: #9F85FF; +} + +.shard-recovering { + border: 1px solid #E4D836; + color: #E4D836; +} + +.shard-unassigned { + border: 1px solid #8B8F95; + color: #8B8F95; +} + +.shard-replica { + border: 1px dashed; + opacity: 0.7; +} + +/** Cluster stats **/ +.stats { + padding-top: 0px; + padding-bottom: 20px; +} + +.stat { + border: 1px solid #55595c; + text-align: center; + width: 100%; + display: inline-block; + min-height: 32px; + margin-top: 5px; + margin-bottom: 5px; + height: auto; + line-height: 28px; +} + +.stat-value { + font-size: 16px; +} + +.stat-description { + font-size: 12px; + font-weight: 300; +} + +/** Thin progress bars **/ +.row-condensed { + margin-right: -.1875rem !important; + margin-left: -.1875rem !important; +} +.col-condensed { + margin-left: 0px; + margin-right: 0px; + padding-left: 2px !important; + padding-right: 2px !important; +} +.progress-thin { + height: 2px !important; + margin-bottom: 0em !important; +} + +/** Colors **/ + +.label-details { + background-color: #1CA8DD; +} + +/** Forms Inverse **/ +.form-inverse { + background-color: #434749 !important; + border-color: #4d5154 !important; + color: #fff !important; +} +.form-inverse:-webkit-input-placeholder { color: white; font-weight: 800; } +.form-inverse:-moz-placeholder { color: white; } +.form-inverse:-moz-placeholder { color: white; } +.form-inverse:-ms-input-placeholder { color: white; } + +/** Nodes **/ +.node-badges { + width: 20px; + display: inline-block; + float: left; +} +.node-info { + margin-left: 20px; +} + +.normal-action { + cursor: pointer; +} + +.bordered { + border: 1px solid #ccc; +} +.pgnt-info { + text-align: center; + width: 100%; + display: block; + line-height: 20px; + vertical-align: middle; + height: 20px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.pgnt-btn { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + display: block; + font-size: 12px; + font-weight: 300; + height: 20px; + line-height: 18px; + text-align: center; + width: 29px; +} +.inactive-action { + color: #8B8F95; +} +.form-element { + margin-bottom: 10px; +} +.inline-checkbox { + margin-top: 5px; +} +.page-ctrl { + margin-top: 8px; + margin-bottom: 5px; +} +.table-control { + line-height: 50px !important; +} +.node-labels { + padding-top: 5px; +} +.dropdown-menu { + color: #fff !important; + background-color: #373a3c !important; + font-size: 12px !important; + font-weight: 300; +} +.dropdown-item { + color: #fff !important; +} +.dropdown-item:hover { + color: #373a3c !important; +} + +.red { + color: #E64759 !important; +} + +.yellow { + color: #E4D836 !important; +} + +.green { + color: #1AC98E !important; +} +.blue { + color: #1CA8DD !important; +} +.alerts { + padding-top: 40px; + position: fixed; + padding-left: 50px; + right: 25px; + width: 50%; + top: 30px; + z-index: 1000000; + min-width: 400px; +} +.alert-block { + font-size: 12px; + font-weight: 300; + padding-top: 10px; + padding-bottom: 0px; + text-align: left; + opacity: 0.90; +} + +.alert { + margin-bottom: 0px; + padding-top: 6px; + padding-bottom: 6px; + background: #434749; + border: 1px solid #4d5154 !important; +} +.modal-dialog { + margin-top: 120px !important; +} +.modal-content { + background: #373a3c !important; + border: 1px solid #434749 !important; + color: #fff; +} +.modal-body { + color: #fff; + font-size: 13px !important; +} +.modal-footer { + border: none !important; +} +.modal-header { + border: none !important; +} +.btn { + background: #434749 !important; + border: 2px solid !important; + font-size: 13px !important; + opacity: 0.7 !important; +} +.btn:hover { + opacity: 1 !important; +} +.btn-default { + color: #8B8F95 !important; + border-color: #8B8F95 !important; +} +.btn-default { + color: #8B8F95 !important; + border-color: #8B8F95 !important; +} +.btn-primary { + color: #1AC98E !important; + border-color: #1AC98E !important; +} +.nav-element { + display: block; + padding-top: .425rem; + padding-bottom: .425rem; +} +.table-header { + font-size: 15px; + font-weight: 400; +} +.connect-page { + display: table-cell; + vertical-align: middle; + float: none; +} + +.navbar-dark .navbar-nav .nav-link { + font-weight: 300 !important; +} diff --git a/public/css/lib.css b/public/css/lib.css new file mode 100644 index 00000000..e69de29b diff --git a/public/img/favicon.png b/public/img/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d92d2ae47434d9a61c90bc205e099b673b9dd5 GIT binary patch literal 687 zcmV;g0#N;lP)ezT{T_ZJ?}AL z5NC{NW(ESID=>(O3&Eg8 zmA9J&6c`h4_f6L;=bU>_H8aNG`kfvCj9zomNt)?O;rzWqZs0LEt%1WB218%1fo9uB zsW^yhBR7C(mqN%GEK9&msg0~ zWY?#bf4q8G-~2KttQZ($odJvy&_-~f?9*ThK@fwR$U^1)p*8=_+^3BXx0$i1BC8XC zr21u6D5nVK&^!dOAw&|1E;qC3uFNj3*Jj#&%Oje@0D-nhfmM*o%^5f}-pxQ07(95H z3|LoV>V19w#rLgmRmtVy9!T3M3FUE3><0T8&b3yEsWcLW`0(=1+qsqc(k(ymBLK0h zK!6(6$7MX~M`-QA2$wk7n(7hhkJ}4Rwi-Vd(_ZFX1Yk7TXuB0IJYpo@kLb2G8m)E{ z`9v=!hi}fOytKckfN^C@6+Z*+MVI9-W_p@_3yyR#UYc0FTpD}i#k>c!wYCS)4v@E$ zchZCo=zV@)`v^$;V18ixdjFMY#q^2$wEX%{f(XD8POnsn$bpbClpC@hPxjzyO>pY|*pF3UU2tYcCN?rUk{Sskej70Mmu9vPwMYhO1m{AxAt(zqDT|0jP7FaX=6 V`?~}E4H^Id002ovPDHLkV1hC)G==~G literal 0 HcmV?d00001 diff --git a/public/info.html b/public/info.html new file mode 100644 index 00000000..c51fc547 --- /dev/null +++ b/public/info.html @@ -0,0 +1,13 @@ + diff --git a/public/lib.js b/public/lib.js new file mode 100644 index 00000000..e69de29b diff --git a/public/modal.html b/public/modal.html new file mode 100644 index 00000000..42595782 --- /dev/null +++ b/public/modal.html @@ -0,0 +1,19 @@ + diff --git a/public/navbar.html b/public/navbar.html new file mode 100644 index 00000000..b2a5076f --- /dev/null +++ b/public/navbar.html @@ -0,0 +1,28 @@ + diff --git a/public/overview.html b/public/overview.html new file mode 100644 index 00000000..c33d663a --- /dev/null +++ b/public/overview.html @@ -0,0 +1,201 @@ +
+
+
+
+
+
+ +
+
+ + closed ({{closed_indices}}) +
+
+ + special ({{special_indices}}) +
+
+
+
+
+
+ +
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ +
+ + {{index.aliases[0]}}  + +
+
+ + shards: {{index.num_shards}} * {{index.num_replicas}} | + docs: {{index.doc_count | number}} | + size: {{index.size_in_bytes | bytes}} + +   +
+
+
+
+ {{unassigned_shards}} unassigned shards +
+
+ show only unhealthy indices + show all indices +
+
+ + + +
+
+
+
+
+ + +
+
+ +
+
+
+
+ + {{node.name}} + +
+
{{node.host}}
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ JVM: {{node.jvm_version}} + ES: {{node.es_version}} +
+
+ + + +
+
diff --git a/public/pagination.html b/public/pagination.html new file mode 100644 index 00000000..c95de1bb --- /dev/null +++ b/public/pagination.html @@ -0,0 +1,17 @@ +
+ + + + + + + + + + + + + + {{page.first | number:0}}-{{page.last | number:0}} of {{page.total | number:0}} {{label || ''}} + +
\ No newline at end of file diff --git a/public/stats.html b/public/stats.html new file mode 100644 index 00000000..aab77950 --- /dev/null +++ b/public/stats.html @@ -0,0 +1,62 @@ +
+
+
+
+
+ + + {{cluster_name}} + + +
+
+ + + {{number_of_nodes | number:0}} + + + nodes + + +
+
+ + + {{indices | number:0}} + + + indices + + +
+
+ + + {{total_shards | number:0}} + + + shards + + +
+
+ + + {{docs_count | number:0}} + + + docs + + +
+
+ + + {{size_in_bytes | bytes}} + + +
+
+
+
+
diff --git a/src/controllers/alerts.js b/src/controllers/alerts.js new file mode 100644 index 00000000..e0fd3b80 --- /dev/null +++ b/src/controllers/alerts.js @@ -0,0 +1,21 @@ +angular.module('cerebro').controller('AlertsController', ['$scope', 'AlertService', + function($scope, AlertService) { + + $scope.alerts = []; + + $scope.$watch( + function() { + return AlertService.alerts; + }, + function(newValue, oldValue) { + $scope.alerts = AlertService.alerts; + } + ); + + $scope.remove = function(id) { + AlertService.remove(id); + }; + + } + +]); diff --git a/src/controllers/connect.js b/src/controllers/connect.js new file mode 100644 index 00000000..364a2e2e --- /dev/null +++ b/src/controllers/connect.js @@ -0,0 +1,37 @@ +angular.module('cerebro').controller('ConnectController', [ + '$scope', '$location', 'DataService', 'AlertService', + function($scope, $location, DataService, AlertService) { + + $scope.hosts = undefined; + + $scope.connecting = false; + + $scope.host = undefined; + + DataService.getHosts( + function(hosts) { + $scope.hosts = hosts; + }, + function(error) { + + } + ); + + $scope.connect = function(host) { + if (host) { + $scope.connecting = true; + DataService.setHost( + host, + function(response) { + $location.path("/overview"); + $scope.host = DataService.getHost(); + }, + function(response) { + $scope.connecting = false; + AlertService.error("Error connecting to " + host, response); + } + ); + } + }; + + }]); diff --git a/src/controllers/modal.js b/src/controllers/modal.js new file mode 100644 index 00000000..52ab9c71 --- /dev/null +++ b/src/controllers/modal.js @@ -0,0 +1,15 @@ +angular.module('cerebro').controller('ModalController', ['$scope', 'ModalService', + function($scope, ModalService) { + + $scope.service = ModalService; + + $scope.close = function() { + $scope.service.close(); + }; + + $scope.confirm = function() { + $scope.service.confirm(); + }; + + } +]); diff --git a/src/controllers/navbar.js b/src/controllers/navbar.js new file mode 100644 index 00000000..4a9fd36b --- /dev/null +++ b/src/controllers/navbar.js @@ -0,0 +1,25 @@ +angular.module('cerebro').controller('NavbarController', ['PageService', '$scope', '$http', 'DataService', + function (PageService, $scope, $http, DataService) { + + $scope.status = undefined; + $scope.cluster_name = undefined; + $scope.host = undefined; + + $scope.$watch( + function () { + return DataService.getData(); + }, + function (data) { + if (data) { + $scope.status = data.status; + $scope.cluster_name = data.cluster_name; + $scope.host = DataService.getHost(); + } else { + $scope.status = undefined; + $scope.cluster_name = undefined; + $scope.host = undefined; + } + } + ); + + }]); diff --git a/src/controllers/overview.js b/src/controllers/overview.js new file mode 100644 index 00000000..8aabaa53 --- /dev/null +++ b/src/controllers/overview.js @@ -0,0 +1,232 @@ +angular.module('cerebro').controller('OverviewController', ['$scope', '$http', '$window', 'DataService', 'AlertService', 'ModalService', + function ($scope, $http, $window, DataService, AlertService, ModalService) { + + $scope.indices = undefined; + $scope.nodes = undefined; + $scope.unassigned_shards = 0; + $scope.indices_filter = new IndexFilter('', true, false, true, true, 0); + $scope.nodes_filter = new NodeFilter('', true, false, false, 0); + $scope.closed_indices = 0; + $scope.special_indices = 0; + $scope.expandedView = false; + $scope.shardAllocation = true; + + $scope.getPageSize = function() { + return Math.max(Math.round($window.innerWidth / 280), 1); + }; + + $scope.paginator = new Paginator(1, $scope.getPageSize(), [], $scope.indices_filter); + + $scope.page = $scope.paginator.getPage(); + + $($window).resize(function() { + $scope.$apply(function() { + $scope.paginator.setPageSize($scope.getPageSize()); + }); + }); + + $scope.$watch( + function() { + return DataService.getData(); + }, + function(data) { + if (data) { + $scope.setIndices(data.indices); + $scope.setNodes(data.nodes); + $scope.unassigned_shards = data.unassigned_shards; + $scope.closed_indices = data.closed_indices; + $scope.special_indices = data.special_indices; + $scope.shardAllocation = data.shard_allocation; + } else { + $scope.indices = undefined; + $scope.nodes = undefined; + } + } + ); + + $scope.$watch('paginator', function() { + if (DataService.getData()) { + $scope.setIndices(DataService.getData().indices); + } + }, true); + + $scope.setIndices = function(indices) { + $scope.paginator.setCollection(indices); + $scope.page = $scope.paginator.getPage(); + }; + + $scope.$watch('nodes_filter', function() { + if (DataService.getData()) { + $scope.setNodes(DataService.getData().nodes); + } + }, + true); + + $scope.setNodes = function(nodes) { + $scope.nodes = nodes.filter(function(node) { + return $scope.nodes_filter.matches(node); + }); + }; + + var success = function(data) { + DataService.forceRefresh(); + AlertService.success('Operation successfully executed', data); + }; + + var error = function(data) { + AlertService.error('Operation failed', data); + }; + + var displayInfo = function(info) { + ModalService.showInfo(info); + }; + + $scope.openIndex = function(index) { + ModalService.promptConfirmation( + 'Open ' + index + '?', + function() { + DataService.openIndex(index, success, error); + } + ); + }; + + $scope.closeIndex = function(index) { + ModalService.promptConfirmation( + 'Close ' + index + '?', + function() { + DataService.closeIndex(index, success, error); + } + ); + }; + + $scope.deleteIndex = function(index) { + ModalService.promptConfirmation( + 'Delete ' + index + '?', + function() { + DataService.deleteIndex(index, success, error); + } + ); + }; + + $scope.clearIndexCache = function(index) { + ModalService.promptConfirmation( + 'Clear ' + index + ' cache?', + function() { + DataService.clearIndexCache(index, success, error); + } + ); + }; + + $scope.refreshIndex = function(index) { + ModalService.promptConfirmation( + 'Refresh index ' + index + '?', + function() { + DataService.refreshIndex(index, success, error); + } + ); + }; + + $scope.optimizeIndex = function(index) { + ModalService.promptConfirmation( + 'Optimize index ' + index + '?', + function() { + DataService.optimizeIndex(index, success, error); + } + ); + }; + + // Mass actions + + $scope.closeIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Close all ' + indices.length + ' selected indices?', + function() { + DataService.closeIndex(indices.join(","), success, error); + } + ); + }; + + $scope.openIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Open all ' + indices.length + ' selected indices?', + function() { + DataService.openIndex(indices.join(","), success, error); + } + ); + }; + + $scope.optimizeIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Optimize all ' + indices.length + ' selected indices?', + function() { + DataService.optimizeIndex(indices.join(","), success, error); + } + ); + }; + + $scope.refreshIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Refresh all ' + indices.length + ' selected indices?', + function() { + DataService.refreshIndex(indices.join(","), success, error); + } + ); + }; + + $scope.clearIndicesCache = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Clear all ' + indices.length + ' selected indices cache?', + function() { + DataService.clearIndexCache(indices.join(","), success, error); + } + ); + }; + + $scope.deleteIndices = function() { + var indices = $scope.paginator.getResults().map(function(index) { + return index.name; + }); + ModalService.promptConfirmation( + 'Delete all ' + indices.length + ' selected indices?', + function() { + DataService.deleteIndex(indices.join(","), success, error); + } + ); + }; + + $scope.nodeStats = function(node) { + DataService.nodeStats(node, displayInfo, error); + }; + + $scope.getIndexSettings = function (index) { + DataService.getIndexSettings(index, displayInfo, error); + }; + + $scope.getIndexMapping = function (index) { + DataService.getIndexMapping(index, displayInfo, error); + }; + + $scope.disableShardAllocation = function () { + DataService.disableShardAllocation(success, error); + }; + + $scope.enableShardAllocation = function () { + DataService.enableShardAllocation(success, error); + }; + + }]); diff --git a/src/directives/pagination.js b/src/directives/pagination.js new file mode 100644 index 00000000..44ee6e48 --- /dev/null +++ b/src/directives/pagination.js @@ -0,0 +1,128 @@ +angular.module('cerebro').directive('ngPagination', ['$document', function($document) { + + return { + scope: { + paginator: '=paginator', + page: '=page', + label: '=label' + }, + templateUrl: 'pagination.html', + link: function(scope, element, attrs) { + var handler = function(event) { + var $target = $(event.target); + if ($target.is('input, textarea')) { + return; + } + if (event.keyCode == 39 && scope.page.next) { + scope.$apply(function() { + scope.paginator.nextPage(); + event.preventDefault(); + }); + } + if (event.keyCode == 37 && scope.page.previous) { + scope.$apply(function() { + scope.paginator.previousPage(); + event.preventDefault(); + }); + } + }; + + $document.bind('keydown', handler); + element.on('$destroy', function() { + $document.unbind('keydown', handler); + }); + } + }; +}]); + +function Paginator(page, pageSize, collection, filter) { + + this.filter = filter; + + this.page = page; + + this.pageSize = pageSize; + + this.$collection = collection ? collection : []; + + this.nextPage = function() { + this.page += 1; + }; + + this.previousPage = function() { + this.page -= 1; + }; + + this.setPageSize = function(newSize) { + this.pageSize = newSize; + }; + + this.getPageSize = function() { + return this.pageSize; + }; + + this.getCurrentPage = function() { + return this.page; + }; + + this.getPage = function() { + var results = this.getResults(); + var total = results.length; + + var first = total > 0 ? ((this.page - 1) * this.pageSize) + 1 : 0; + while (total < first) { + this.previousPage(); + first = (this.page - 1) * this.pageSize + 1; + } + var lastPage = this.page * this.pageSize > total; + var last = lastPage ? total : this.page * this.pageSize; + + var elements = total > 0 ? results.slice(first - 1, last) : []; + + var next = this.pageSize * this.page < total; + var previous = this.page > 1; + while (elements.length < this.pageSize) { + elements.push(null); + } + return new Page(elements, total, first, last, next, previous); + }; + + this.setCollection = function(collection) { + if (this.filter.getSorting()) { + this.$collection = collection.sort(this.filter.getSorting()); + } else { + this.$collection = collection; + } + }; + + this.getResults = function() { + var filter = this.filter; + var collection = this.$collection; + if (filter.isBlank()) { + return collection; + } else { + var filtered = []; + collection.forEach(function(item) { + if (filter.matches(item)) { + filtered.push(item); + } + }); + return filtered; + } + }; + + this.getCollection = function() { + return this.$collection; + }; + +} + +function Page(elements, total, first, last, next, previous) { + this.elements = elements; + this.total = total; + this.first = first; + this.last = last; + this.next = next; + this.previous = previous; +} + diff --git a/src/directives/progress.js b/src/directives/progress.js new file mode 100644 index 00000000..075bdb42 --- /dev/null +++ b/src/directives/progress.js @@ -0,0 +1,19 @@ +angular.module('cerebro').directive('ngProgress', + function () { + + return { + scope: { + value: '=value', + max: '=max', + text: '=text' + }, + template: function (elem, attrs) { + return '{{text}}' + + '' + + '{{value}}%' + + '' + } + }; + } +); diff --git a/src/directives/shard.js b/src/directives/shard.js new file mode 100644 index 00000000..16be5f2b --- /dev/null +++ b/src/directives/shard.js @@ -0,0 +1,15 @@ +angular.module('cerebro').directive('ngShard', + function () { + + return { + scope: { + shard: '=shard' + }, + template: function (elem, attrs) { + return '' + + '{{shard.shard}}' + + ''; + } + }; + } +); diff --git a/src/filters/bytes.js b/src/filters/bytes.js new file mode 100644 index 00000000..94d0d8c2 --- /dev/null +++ b/src/filters/bytes.js @@ -0,0 +1,18 @@ +angular.module('cerebro').filter('bytes', function() { + + var UNITS = ['b', 'KB', 'MB', 'GB', 'TB', 'PB']; + + function stringify(bytes) { + if (bytes > 0) { + var e = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, e)).toFixed(2) + UNITS[e]; + } else { + return 0 + UNITS[0]; + } + } + + return function(bytes) { + return stringify(bytes); + }; + +}); diff --git a/src/main.js b/src/main.js new file mode 100644 index 00000000..476b777b --- /dev/null +++ b/src/main.js @@ -0,0 +1,15 @@ +'use strict'; + +angular.module('cerebro', ['ngRoute']).config(['$routeProvider', + function($routeProvider) { + $routeProvider. + when('/overview', { + templateUrl: 'overview.html', + controller: 'OverviewController' + }). + when('/connect', { + templateUrl: 'connect.html', + controller: 'ConnectController' + }). + otherwise({redirectTo: '/connect'}); + }]); diff --git a/src/overview/index_filter.js b/src/overview/index_filter.js new file mode 100644 index 00000000..2931cd02 --- /dev/null +++ b/src/overview/index_filter.js @@ -0,0 +1,99 @@ +function IndexFilter(name, closed, special, healthy, asc, timestamp) { + this.name = name; + this.closed = closed; + this.special = special; + this.healthy = healthy; + this.sort = 'name'; + this.asc = asc; + this.timestamp = timestamp; + + this.getSorting = function() { + var asc = this.asc; + switch (this.sort) { + case 'name': + return function(a, b) { + if (asc) { + return a.name.localeCompare(b.name); + } else { + return b.name.localeCompare(a.name); + } + }; + default: + return undefined; + } + }; + + this.clone = function() { + return new IndexFilter( + this.name, + this.closed, + this.special, + this.healthy, + this.asc, + this.timestamp + ); + }; + + this.equals = function(other) { + return ( + other !== null && + this.name === other.name && + this.closed === other.closed && + this.special === other.special && + this.healthy === other.healthy && + this.asc === other.asc && + this.timestamp === other.timestamp + ); + }; + + this.isBlank = function() { + return ( + !this.name && + this.closed && + this.special && + this.healthy && + this.asc + ); + }; + + this.matches = function(index) { + var matches = true; + if (!this.special && index.special) { + matches = false; + } + if (!this.closed && index.closed) { + matches = false; + } + // Hide healthy == show unhealthy only + if (!this.healthy && !index.unhealthy) { + matches = false; + } + if (matches && this.name) { + try { + var regExp = new RegExp(this.name.trim(), 'i'); + matches = regExp.test(index.name); + if (!matches && index.aliases) { + for (var idx = 0; idx < index.aliases.length; idx++) { + if ((matches = regExp.test(index.aliases[idx]))) { + break; + } + } + } + } + catch (err) { // if not valid regexp, still try normal matching + matches = index.name.indexOf(this.name.toLowerCase()) != -1; + if (!matches) { + for (var idx = 0; idx < index.aliases.length; idx++) { + var alias = index.aliases[idx].toLowerCase(); + matches = true; + if ((matches = (alias.indexOf(this.name.toLowerCase()) != -1))) { + break; + } + } + } + } + } + return matches; + }; + +} diff --git a/src/overview/node_filter.js b/src/overview/node_filter.js new file mode 100644 index 00000000..4d2628f4 --- /dev/null +++ b/src/overview/node_filter.js @@ -0,0 +1,55 @@ +function NodeFilter(name, data, master, client, timestamp) { + this.name = name; + this.data = data; + this.master = master; + this.client = client; + this.timestamp = timestamp; + + this.clone = function() { + return new NodeFilter(this.name, this.data, this.master, this.client); + }; + + this.getSorting = function() { + return undefined; + }; + + this.equals = function(other) { + return ( + other !== null && + this.name == other.name && + this.data == other.data && + this.master == other.master && + this.client == other.client && + this.timestamp == other.timestamp + ); + }; + + this.isBlank = function() { + return !this.name && (this.data && this.master && this.client); + }; + + this.matches = function(node) { + if (this.isBlank()) { + return true; + } else { + return this.matchesName(node.name) && this.matchesType(node); + } + }; + + this.matchesType = function(node) { + return ( + node.data && this.data || + node.master && this.master || + node.client && this.client + ); + }; + + this.matchesName = function(name) { + if (this.name) { + return name.toLowerCase().indexOf(this.name.toLowerCase()) != -1; + } else { + return true; + } + }; + +} diff --git a/src/services/alerts.js b/src/services/alerts.js new file mode 100644 index 00000000..146d7cba --- /dev/null +++ b/src/services/alerts.js @@ -0,0 +1,83 @@ +var Alert = function(message, response, level, _class, icon) { + var currentDate = new Date(); + this.message = message; + this.response = response; + this.level = level; + this.class = _class; + this.icon = icon; + this.timestamp = currentDate; + this.id = 'alert_box_' + currentDate.getTime(); + + this.hasResponse = function() { + return this.response; + }; + + this.getResponse = function() { + if (this.response) { + return JSON.stringify(this.response, undefined, 2); + } + }; +}; + +angular.module('cerebro').factory('AlertService', function() { + this.maxAlerts = 3; + + this.alerts = []; + + // removes ALL alerts + this.clear = function() { + this.alerts.length = 0; + }; + + // remove a particular alert message + this.remove = function(id) { + $('#' + id).fadeTo(1000, 0).slideUp(200, function() { + $(this).remove(); + }); + this.alerts = this.alerts.filter(function(a) { + return id != a.id; + }); + }; + + // creates an error alert + this.error = function(msg, resp, timeout) { + timeout = timeout ? timeout : 7500; + var alert = new Alert(msg, resp, 'error', 'red', 'fa fa-warning'); + return this.addAlert(alert, timeout); + }; + + // creates an info alert + this.info = function(msg, resp, timeout) { + timeout = timeout ? timeout : 2500; + var alert = new Alert(msg, resp, 'info', 'blue', 'fa fa-info'); + return this.addAlert(alert, timeout); + }; + + // creates success alert + this.success = function(msg, resp, timeout) { + timeout = timeout ? timeout : 2500; + var alert = new Alert(msg, resp, 'success', 'green', 'fa fa-check'); + return this.addAlert(alert, timeout); + }; + + // creates a warn alert + this.warn = function(msg, resp, timeout) { + timeout = timeout ? timeout : 5000; + var alert = new Alert(msg, resp, 'warn', 'yellow', 'fa fa-info'); + return this.addAlert(alert, timeout); + }; + + this.addAlert = function(alert, timeout) { + this.alerts.unshift(alert); + var service = this; + setTimeout(function() { + service.remove(alert.id); + }, timeout); + if (this.alerts.length >= this.maxAlerts) { + this.alerts.length = 3; + } + return alert.id; + }; + + return this; +}); diff --git a/src/services/data.js b/src/services/data.js new file mode 100644 index 00000000..0d616775 --- /dev/null +++ b/src/services/data.js @@ -0,0 +1,190 @@ +angular.module('cerebro').factory('DataService', function ($rootScope, $timeout, $http, $location) { + + var data = undefined; // current data + + var host = undefined; + + var baseUrl = $location.protocol() + '://' + $location.host() + ':' + $location.port(); + + var refresh = function(success, error) { + if (host) { + var config = { + method: 'GET', + url: baseUrl + '/apis/overview', + params: {host: host} + }; + $http(config). + success(function(response) { + data = response; + if (success) { + success(response); + } + }). + error(function(response) { + data = undefined; + if (error) { + error(response); + } + }); + } else { + $location.path("/connect"); + } + }; + + var autoRefresh = function () { + refresh(); + $timeout(autoRefresh, 3000); + }; + + this.getData = function() { + return data; + }; + + this.forceRefresh = function() { + refresh(); + }; + + this.getHost = function() { + return host; + }; + + this.setHost = function(newHost, success, error) { + data = undefined; + host = newHost; + refresh(success, error); + }; + + autoRefresh(); + + this.closeIndex = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_close', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.openIndex = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_open', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.optimizeIndex = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_optimize', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.refreshIndex = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_refresh', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.clearIndexCache = function(index, success, error) { + var config = { + method: 'POST', + url: baseUrl + '/apis/' + index + '/_cache/clear', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.deleteIndex = function(index, success, error) { + var config = { + method: 'DELETE', + url: baseUrl + '/apis/' + index + '/_delete', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.getIndexSettings = function(index, success, error) { + var config = { + method: 'GET', + url: baseUrl + '/apis/' + index + '/_settings', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.getIndexMapping = function(index, success, error) { + var config = { + method: 'GET', + url: baseUrl + '/apis/' + index + '/_mapping', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.nodeStats = function(node, success, error) { + var config = { + method: 'GET', + url: baseUrl + '/apis/_nodes/' + node + '/stats', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.enableShardAllocation = function(success, error) { + var config = { + method: 'PUT', + url: baseUrl + '/apis/enable_allocation', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.disableShardAllocation = function(success, error) { + var config = { + method: 'PUT', + url: baseUrl + '/apis/disable_allocation', + params: {host: host} + }; + $http(config). + success(success). + error(error); + }; + + this.getHosts = function (success, error) { + var config = { + method: 'GET', + url: baseUrl + '/apis/hosts' + }; + $http(config).success(success).error(error); + }; + + return this; + +}); diff --git a/src/services/modal.js b/src/services/modal.js new file mode 100644 index 00000000..1d02fe70 --- /dev/null +++ b/src/services/modal.js @@ -0,0 +1,22 @@ +angular.module('cerebro').factory('ModalService', ['$sce', function ($sce) { + + this.text = undefined; + this.confirm = undefined; + + this.info = undefined; + + this.promptConfirmation = function (body, confirmCallback) { + this.text = body; + this.confirm = confirmCallback; + this.info = undefined; + }; + + this.showInfo = function (info) { + this.info = $sce.trustAsHtml(JSON.stringify(info, '', 2)); + //$scope.body= $sce.trustAsHtml(JSONTree.create(info)); + this.confirm = undefined; + this.text = undefined; + }; + + return this; +}]); diff --git a/src/services/page.js b/src/services/page.js new file mode 100644 index 00000000..4a167c23 --- /dev/null +++ b/src/services/page.js @@ -0,0 +1,63 @@ +angular.module('cerebro').factory('PageService', ['DataService', '$rootScope', '$document', + function (DataService, $rootScope, $document) { + + var link = $document[0].querySelector('link[rel~=\'icon\']'); + var clusterName = undefined; + var clusterStatus = undefined; + + if (link) { + var faviconUrl = link.href; + var img = $document[0].createElement('img'); + img.src = faviconUrl; + } + + $rootScope.$watch( + function () { + return DataService.getData(); + }, + function (data) { + if (data) { + setPageTitle(data.cluster_name); + setFavIconColor(data.status); + } + } + ); + + var setPageTitle = function (newClusterName) { + if (clusterName !== newClusterName) { + if (newClusterName) { + clusterName = newClusterName; + $rootScope.title = 'cerebro[' + clusterName + ']'; + } else { + clusterName = undefined; + $rootScope.title = 'cerebro - no connection'; + } + } + }; + + var setFavIconColor = function(newClusterStatus) { + if (link && clusterStatus !== newClusterStatus) { + clusterStatus = newClusterStatus; + try { + var colors = {green: '#1AC98E', yellow: '#E4D836', red: '#E64759'}; + var color = clusterStatus ? colors[clusterStatus] : '#222426'; + var canvas = $document[0].createElement('canvas'); + canvas.width = 16; + canvas.height = 16; + var context = canvas.getContext('2d'); + context.drawImage(img, 0, 0); + context.globalCompositeOperation = 'source-in'; + context.fillStyle = color; + context.fillRect(0, 0, 16, 16); + context.fill(); + link.type = 'image/png'; + link.href = canvas.toDataURL(); + } catch (exception) { + // + } + } + }; + + return this; + + }]); diff --git a/src/stats/stats.js b/src/stats/stats.js new file mode 100644 index 00000000..bfd7e1c6 --- /dev/null +++ b/src/stats/stats.js @@ -0,0 +1,46 @@ +angular.module('cerebro').controller('StatsController', ['$scope', '$http', 'DataService', + function ($scope, $http, DataService) { + + $scope.number_of_nodes = undefined; + + $scope.indices = undefined; + + $scope.active_primary_shards = undefined; + $scope.active_shards = undefined; + $scope.relocating_shards = undefined; + $scope.initializing_shards = undefined; + $scope.unassigned_shards = undefined; + $scope.total_shards = undefined; + + $scope.docs_count = undefined; + + $scope.size_in_bytes = undefined; + + $scope.cluster_name = undefined; + + $scope.$watch( + function () { + return DataService.getData(); + }, + function (data) { + if (data) { + $scope.number_of_nodes = data.number_of_nodes; + $scope.indices = data.indices.length; + $scope.active_primary_shards = data.active_primary_shards; + $scope.active_shards = data.active_shards; + $scope.relocating_shards = data.relocating_shards; + $scope.initializing_shards = data.initializing_shards; + $scope.unassigned_shards = data.unassigned_shards; + $scope.docs_count = data.docs_count; + $scope.size_in_bytes = data.size_in_bytes; + $scope.cluster_name = data.cluster_name; + + $scope.total_shards = $scope.active_shards + + $scope.relocating_shards + + $scope.initializing_shards + + $scope.unassigned_shards; + } + } + ); + + }]); \ No newline at end of file diff --git a/test/controllers/ClusterOverviewControllerSpec.scala b/test/controllers/ClusterOverviewControllerSpec.scala new file mode 100644 index 00000000..50276382 --- /dev/null +++ b/test/controllers/ClusterOverviewControllerSpec.scala @@ -0,0 +1,21 @@ +package controllers + +import models.overview.ClusterInitializingShards +import org.specs2.Specification + +class ClusterOverviewControllerSpec extends Specification { + + def is = + s2""" + ClusterOverviewController should + + return cluster_name $clusterName + """ + + def clusterName = { + (clusterWithoutData \ "cluster_name").as[String] mustEqual "elasticsearch" + (clusterWithData \ "cluster_name").as[String] mustEqual "elasticsearch" + (clusterInitializing \ "cluster_name").as[String] mustEqual "elasticsearch" + (clusterRelocating \ "cluster_name").as[String] mustEqual "elasticsearch" + } +} diff --git a/test/models/overview/ClusterDisabledAllocation.scala b/test/models/overview/ClusterDisabledAllocation.scala new file mode 100644 index 00000000..4801bb42 --- /dev/null +++ b/test/models/overview/ClusterDisabledAllocation.scala @@ -0,0 +1,24 @@ +package models.overview + +import play.api.libs.json.Json + +object ClusterDisabledAllocation extends ClusterWithData { + + override val clusterSettings = Json.parse( + """ + |{ + | "persistent" : { }, + | "transient" : { + | "cluster" : { + | "routing" : { + | "allocation" : { + | "enable" : "none" + | } + | } + | } + | } + |} + """.stripMargin + ) + +} diff --git a/test/models/overview/ClusterInitializingShards.scala b/test/models/overview/ClusterInitializingShards.scala new file mode 100644 index 00000000..cb2d696f --- /dev/null +++ b/test/models/overview/ClusterInitializingShards.scala @@ -0,0 +1,721 @@ +package models.overview + +import play.api.libs.json.Json + +object ClusterInitializingShards { + + def apply() = new ClusterOverview(clusterState, nodesStats, indicesStats, clusterSettings, aliases, clusterHealth, nodes, main) + + val clusterState = Json.parse( + """ + |{ + | "cluster_name" : "elasticsearch", + | "master_node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "blocks" : { }, + | "routing_table" : { + | "indices" : { + | "hello" : { + | "shards" : { + | "1" : [ { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 1, + | "index" : "hello", + | "version" : 16, + | "allocation_id" : { + | "id" : "rNdtAPz_RhKVBp6dpAH1cw" + | }, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 1, + | "index" : "hello", + | "version" : 16, + | "allocation_id" : { + | "id" : "hlwc94lZRvOoBoaxyEWIGg" + | } + | } ], + | "4" : [ { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 4, + | "index" : "hello", + | "version" : 10, + | "allocation_id" : { + | "id" : "Kyne0gDVQEasq9VxUUsxbg" + | } + | }, { + | "state" : "UNASSIGNED", + | "primary" : false, + | "node" : null, + | "relocating_node" : null, + | "shard" : 4, + | "index" : "hello", + | "version" : 10, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | } ], + | "2" : [ { + | "state" : "STARTED", + | "primary" : true, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 2, + | "index" : "hello", + | "version" : 15, + | "allocation_id" : { + | "id" : "rN62kibSRZq0RxcwxEHKKw" + | } + | }, { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 2, + | "index" : "hello", + | "version" : 15, + | "allocation_id" : { + | "id" : "2G3Hs3CnT5uvhpeOmHZyYg" + | }, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | } ], + | "3" : [ { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 3, + | "index" : "hello", + | "version" : 11, + | "allocation_id" : { + | "id" : "NhG91IW6RCW1KAbSx67O9g" + | }, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 3, + | "index" : "hello", + | "version" : 11, + | "allocation_id" : { + | "id" : "b4Gdtk7uTuSINaZ_YdaJdg" + | } + | } ], + | "0" : [ { + | "state" : "STARTED", + | "primary" : true, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 0, + | "index" : "hello", + | "version" : 17, + | "allocation_id" : { + | "id" : "13mrI6FhRjGEk7xG7xYJvg" + | } + | }, { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 0, + | "index" : "hello", + | "version" : 17, + | "allocation_id" : { + | "id" : "FbfzrPiySEaOzGMRZRDf7w" + | }, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | } ] + | } + | } + | } + | }, + | "routing_nodes" : { + | "unassigned" : [ { + | "state" : "UNASSIGNED", + | "primary" : false, + | "node" : null, + | "relocating_node" : null, + | "shard" : 4, + | "index" : "hello", + | "version" : 10, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | } ], + | "nodes" : { + | "VOiMU2k5SuStH3-X1uuBGw" : [ { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 1, + | "index" : "hello", + | "version" : 16, + | "allocation_id" : { + | "id" : "rNdtAPz_RhKVBp6dpAH1cw" + | }, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 2, + | "index" : "hello", + | "version" : 15, + | "allocation_id" : { + | "id" : "rN62kibSRZq0RxcwxEHKKw" + | } + | }, { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 3, + | "index" : "hello", + | "version" : 11, + | "allocation_id" : { + | "id" : "NhG91IW6RCW1KAbSx67O9g" + | }, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 0, + | "index" : "hello", + | "version" : 17, + | "allocation_id" : { + | "id" : "13mrI6FhRjGEk7xG7xYJvg" + | } + | } ], + | "cPsT9o5FQ3WRnvqSTXHiVQ" : [ { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 1, + | "index" : "hello", + | "version" : 16, + | "allocation_id" : { + | "id" : "hlwc94lZRvOoBoaxyEWIGg" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 4, + | "index" : "hello", + | "version" : 10, + | "allocation_id" : { + | "id" : "Kyne0gDVQEasq9VxUUsxbg" + | } + | }, { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 2, + | "index" : "hello", + | "version" : 15, + | "allocation_id" : { + | "id" : "2G3Hs3CnT5uvhpeOmHZyYg" + | }, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 3, + | "index" : "hello", + | "version" : 11, + | "allocation_id" : { + | "id" : "b4Gdtk7uTuSINaZ_YdaJdg" + | } + | }, { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 0, + | "index" : "hello", + | "version" : 17, + | "allocation_id" : { + | "id" : "FbfzrPiySEaOzGMRZRDf7w" + | }, + | "unassigned_info" : { + | "reason" : "REPLICA_ADDED", + | "at" : "2016-03-19T14:13:39.833Z" + | } + | } ] + | } + | } + |} + """.stripMargin + ) + + val nodesStats = Json.parse( + """ + |{ + | "cluster_name" : "elasticsearch", + | "nodes" : { + | "VOiMU2k5SuStH3-X1uuBGw" : { + | "timestamp" : 1458396821721, + | "name" : "Random", + | "transport_address" : "127.0.0.1:9301", + | "host" : "127.0.0.1", + | "ip" : [ "127.0.0.1:9301", "NONE" ], + | "os" : { + | "timestamp" : 1458396821721, + | "load_average" : 3.48583984375, + | "mem" : { + | "total_in_bytes" : 8589934592, + | "free_in_bytes" : 57360384, + | "used_in_bytes" : 8532574208, + | "free_percent" : 1, + | "used_percent" : 99 + | }, + | "swap" : { + | "total_in_bytes" : 2147483648, + | "free_in_bytes" : 1292369920, + | "used_in_bytes" : 855113728 + | } + | }, + | "process" : { + | "timestamp" : 1458396821721, + | "open_file_descriptors" : 283, + | "max_file_descriptors" : 10240, + | "cpu" : { + | "percent" : 0, + | "total_in_millis" : 204184 + | }, + | "mem" : { + | "total_virtual_in_bytes" : 5328396288 + | } + | }, + | "jvm" : { + | "timestamp" : 1458396821721, + | "uptime_in_millis" : 1013104, + | "mem" : { + | "heap_used_in_bytes" : 65155904, + | "heap_used_percent" : 6, + | "heap_committed_in_bytes" : 259522560, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_used_in_bytes" : 68894384, + | "non_heap_committed_in_bytes" : 70033408, + | "pools" : { + | "young" : { + | "used_in_bytes" : 17017328, + | "max_in_bytes" : 286326784, + | "peak_used_in_bytes" : 71630848, + | "peak_max_in_bytes" : 286326784 + | }, + | "survivor" : { + | "used_in_bytes" : 2022296, + | "max_in_bytes" : 35782656, + | "peak_used_in_bytes" : 8912896, + | "peak_max_in_bytes" : 35782656 + | }, + | "old" : { + | "used_in_bytes" : 46116280, + | "max_in_bytes" : 715849728, + | "peak_used_in_bytes" : 46116280, + | "peak_max_in_bytes" : 715849728 + | } + | } + | }, + | "threads" : { + | "count" : 99, + | "peak_count" : 103 + | }, + | "gc" : { + | "collectors" : { + | "young" : { + | "collection_count" : 173, + | "collection_time_in_millis" : 861 + | }, + | "old" : { + | "collection_count" : 1, + | "collection_time_in_millis" : 16 + | } + | } + | }, + | "buffer_pools" : { + | "direct" : { + | "count" : 126, + | "used_in_bytes" : 20107771, + | "total_capacity_in_bytes" : 20107771 + | }, + | "mapped" : { + | "count" : 7, + | "used_in_bytes" : 531368, + | "total_capacity_in_bytes" : 531368 + | } + | } + | }, + | "fs" : { + | "timestamp" : 1458396821721, + | "total" : { + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 41476603904, + | "available_in_bytes" : 41214459904 + | }, + | "data" : [ { + | "path" : "/Users/leonardo.menezes/Downloads/elasticsearch-2.1.0/data/elasticsearch/nodes/1", + | "mount" : "/ (/dev/disk1)", + | "type" : "hfs", + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 41476603904, + | "available_in_bytes" : 41214459904 + | } ] + | } + | }, + | "cPsT9o5FQ3WRnvqSTXHiVQ" : { + | "timestamp" : 1458396821720, + | "name" : "Cecilia Reyes", + | "transport_address" : "127.0.0.1:9300", + | "host" : "127.0.0.1", + | "ip" : [ "127.0.0.1:9300", "NONE" ], + | "os" : { + | "timestamp" : 1458396821720, + | "load_average" : 3.48583984375, + | "mem" : { + | "total_in_bytes" : 8589934592, + | "free_in_bytes" : 57360384, + | "used_in_bytes" : 8532574208, + | "free_percent" : 1, + | "used_percent" : 99 + | }, + | "swap" : { + | "total_in_bytes" : 2147483648, + | "free_in_bytes" : 1292369920, + | "used_in_bytes" : 855113728 + | } + | }, + | "process" : { + | "timestamp" : 1458396821720, + | "open_file_descriptors" : 309, + | "max_file_descriptors" : 10240, + | "cpu" : { + | "percent" : 0, + | "total_in_millis" : 439157 + | }, + | "mem" : { + | "total_virtual_in_bytes" : 5336465408 + | } + | }, + | "jvm" : { + | "timestamp" : 1458396821721, + | "uptime_in_millis" : 12257739, + | "mem" : { + | "heap_used_in_bytes" : 152293128, + | "heap_used_percent" : 14, + | "heap_committed_in_bytes" : 259522560, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_used_in_bytes" : 80998704, + | "non_heap_committed_in_bytes" : 82624512, + | "pools" : { + | "young" : { + | "used_in_bytes" : 42576208, + | "max_in_bytes" : 286326784, + | "peak_used_in_bytes" : 71630848, + | "peak_max_in_bytes" : 286326784 + | }, + | "survivor" : { + | "used_in_bytes" : 4755944, + | "max_in_bytes" : 35782656, + | "peak_used_in_bytes" : 8912896, + | "peak_max_in_bytes" : 35782656 + | }, + | "old" : { + | "used_in_bytes" : 104965192, + | "max_in_bytes" : 715849728, + | "peak_used_in_bytes" : 104965192, + | "peak_max_in_bytes" : 715849728 + | } + | } + | }, + | "threads" : { + | "count" : 102, + | "peak_count" : 106 + | }, + | "gc" : { + | "collectors" : { + | "young" : { + | "collection_count" : 252, + | "collection_time_in_millis" : 1611 + | }, + | "old" : { + | "collection_count" : 1, + | "collection_time_in_millis" : 12 + | } + | } + | }, + | "buffer_pools" : { + | "direct" : { + | "count" : 206, + | "used_in_bytes" : 28678155, + | "total_capacity_in_bytes" : 28678155 + | }, + | "mapped" : { + | "count" : 9, + | "used_in_bytes" : 536675, + | "total_capacity_in_bytes" : 536675 + | } + | }, + | "classes" : { + | "current_loaded_count" : 7623, + | "total_loaded_count" : 7623, + | "total_unloaded_count" : 0 + | } + | }, + | "fs" : { + | "timestamp" : 1458396821721, + | "total" : { + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 41476603904, + | "available_in_bytes" : 41214459904 + | }, + | "data" : [ { + | "path" : "/Users/leonardo.menezes/Downloads/elasticsearch-2.1.0/data/elasticsearch/nodes/0", + | "mount" : "/ (/dev/disk1)", + | "type" : "hfs", + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 41476603904, + | "available_in_bytes" : 41214459904 + | } ] + | } + | } + | } + |} + """.stripMargin + ) + + val indicesStats = Json.parse( + """ + |{ + | "_shards" : { + | "total" : 10, + | "successful" : 5, + | "failed" : 0 + | }, + | "_all" : { + | "primaries" : { + | "docs" : { + | "count" : 108680, + | "deleted" : 0 + | }, + | "store" : { + | "size_in_bytes" : 2026271, + | "throttle_time_in_millis" : 0 + | } + | }, + | "total" : { + | "docs" : { + | "count" : 108680, + | "deleted" : 0 + | }, + | "store" : { + | "size_in_bytes" : 2026271, + | "throttle_time_in_millis" : 0 + | } + | } + | }, + | "indices" : { + | "hello" : { + | "primaries" : { + | "docs" : { + | "count" : 108680, + | "deleted" : 0 + | }, + | "store" : { + | "size_in_bytes" : 2026271, + | "throttle_time_in_millis" : 0 + | } + | }, + | "total" : { + | "docs" : { + | "count" : 108680, + | "deleted" : 0 + | }, + | "store" : { + | "size_in_bytes" : 2026271, + | "throttle_time_in_millis" : 0 + | } + | } + | } + | } + |} + """.stripMargin + ) + + val clusterSettings = Json.parse( + """ + |{ + | "persistent" : { }, + | "transient" : { } + |} + """.stripMargin + ) + + val aliases = Json.parse( + """ + |{ + | "hello" : { + | "aliases" : { } + | } + |} + """.stripMargin + ) + + val clusterHealth = Json.parse( + """ + |{ + | "cluster_name" : "elasticsearch", + | "status" : "yellow", + | "timed_out" : false, + | "number_of_nodes" : 2, + | "number_of_data_nodes" : 2, + | "active_primary_shards" : 5, + | "active_shards" : 5, + | "relocating_shards" : 0, + | "initializing_shards" : 4, + | "unassigned_shards" : 1, + | "delayed_unassigned_shards" : 0, + | "number_of_pending_tasks" : 0, + | "number_of_in_flight_fetch" : 0, + | "task_max_waiting_in_queue_millis" : 0, + | "active_shards_percent_as_number" : 50 + |} + """.stripMargin + ) + + val nodes = Json.parse( + """ + |{ + | "cluster_name" : "elasticsearch", + | "nodes" : { + | "VOiMU2k5SuStH3-X1uuBGw" : { + | "name" : "Random", + | "transport_address" : "127.0.0.1:9301", + | "host" : "127.0.0.1", + | "ip" : "127.0.0.1", + | "version" : "2.1.0", + | "build" : "72cd1f1", + | "http_address" : "127.0.0.1:9201", + | "os" : { + | "refresh_interval_in_millis" : 1000, + | "available_processors" : 8, + | "allocated_processors" : 8 + | }, + | "jvm" : { + | "pid" : 16419, + | "version" : "1.8.0_72", + | "vm_name" : "Java HotSpot(TM) 64-Bit Server VM", + | "vm_version" : "25.72-b15", + | "vm_vendor" : "Oracle Corporation", + | "start_time_in_millis" : 1458393717991, + | "mem" : { + | "heap_init_in_bytes" : 268435456, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_init_in_bytes" : 2555904, + | "non_heap_max_in_bytes" : 0, + | "direct_max_in_bytes" : 1037959168 + | }, + | "gc_collectors" : [ "ParNew", "ConcurrentMarkSweep" ], + | "memory_pools" : [ "Code Cache", "Metaspace", "Compressed Class Space", "Par Eden Space", "Par Survivor Space", "CMS Old Gen" ] + | } + | }, + | "cPsT9o5FQ3WRnvqSTXHiVQ" : { + | "name" : "Cecilia Reyes", + | "transport_address" : "127.0.0.1:9300", + | "host" : "127.0.0.1", + | "ip" : "127.0.0.1", + | "version" : "2.1.0", + | "build" : "72cd1f1", + | "http_address" : "127.0.0.1:9200", + | "os" : { + | "refresh_interval_in_millis" : 1000, + | "name" : "Mac OS X", + | "arch" : "x86_64", + | "version" : "10.11.3", + | "available_processors" : 8, + | "allocated_processors" : 8 + | }, + | "jvm" : { + | "pid" : 60169, + | "version" : "1.8.0_72", + | "vm_name" : "Java HotSpot(TM) 64-Bit Server VM", + | "vm_version" : "25.72-b15", + | "vm_vendor" : "Oracle Corporation", + | "start_time_in_millis" : 1458345474505, + | "mem" : { + | "heap_init_in_bytes" : 268435456, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_init_in_bytes" : 2555904, + | "non_heap_max_in_bytes" : 0, + | "direct_max_in_bytes" : 1037959168 + | }, + | "gc_collectors" : [ "ParNew", "ConcurrentMarkSweep" ], + | "memory_pools" : [ "Code Cache", "Metaspace", "Compressed Class Space", "Par Eden Space", "Par Survivor Space", "CMS Old Gen" ] + | } + | } + | } + |} + """.stripMargin + ) + + val main = Json.parse( + """ + |{ + | "name" : "Cecilia Reyes", + | "cluster_name" : "elasticsearch", + | "version" : { + | "number" : "2.1.0", + | "build_hash" : "72cd1f1a3eee09505e036106146dc1949dc5dc87", + | "build_timestamp" : "2015-11-18T22:40:03Z", + | "build_snapshot" : false, + | "lucene_version" : "5.3.1" + | }, + | "tagline" : "You Know, for Search" + |} + """.stripMargin + ) + +} diff --git a/test/models/overview/ClusterOverviewSpec.scala b/test/models/overview/ClusterOverviewSpec.scala new file mode 100644 index 00000000..1a614fe4 --- /dev/null +++ b/test/models/overview/ClusterOverviewSpec.scala @@ -0,0 +1,121 @@ +package models.overview + +import org.specs2.Specification + +object ClusterOverviewSpec extends Specification { + + def is = + s2""" + ClusterOverview should + + return cluster_name $clusterName + return number of nodes $numberOfNodes + return number of active primary shards $activePrimaryShards + return number of active shards $activeShards + return number of relocating shards $relocatingShards + return number of initializing shards $initializingShards + return number of unassigned shards $unassignedShards + return cluster doc count $docsCount + return cluster size in bytes $sizeInBytes + return number of indices $totalIndices + return number of closed indices $closedIndices + return number of special indices $specialIndices + return state of shard allocation $shardAllocation + """ + + val clusterWithoutData = ClusterWithoutData().json + val clusterWithData = ClusterWithData().json + val clusterInitializing = ClusterInitializingShards().json + val clusterRelocating = ClusterRelocatingShards().json + val clusterDiabledAllocation = ClusterDisabledAllocation().json + + def clusterName = { + (clusterWithoutData \ "cluster_name").as[String] mustEqual "elasticsearch" + (clusterWithData \ "cluster_name").as[String] mustEqual "elasticsearch" + (clusterInitializing \ "cluster_name").as[String] mustEqual "elasticsearch" + (clusterRelocating \ "cluster_name").as[String] mustEqual "elasticsearch" + } + + def numberOfNodes = { + (clusterWithoutData \ "number_of_nodes").as[Int] mustEqual 2 + (clusterWithData \ "number_of_nodes").as[Int] mustEqual 2 + (clusterInitializing \ "number_of_nodes").as[Int] mustEqual 2 + (clusterRelocating \ "number_of_nodes").as[Int] mustEqual 3 + } + + def activePrimaryShards = { + (clusterWithoutData \ "active_primary_shards").as[Int] mustEqual 0 + (clusterWithData \ "active_primary_shards").as[Int] mustEqual 8 + (clusterInitializing \ "active_primary_shards").as[Int] mustEqual 5 + (clusterRelocating \ "active_primary_shards").as[Int] mustEqual 5 + } + + def activeShards = { + (clusterWithoutData \ "active_shards").as[Int] mustEqual 0 + (clusterWithData \ "active_shards").as[Int] mustEqual 11 + (clusterInitializing \ "active_shards").as[Int] mustEqual 5 + (clusterRelocating \ "active_primary_shards").as[Int] mustEqual 5 + } + + def relocatingShards = { + (clusterWithoutData \ "relocating_shards").as[Int] mustEqual 0 + (clusterWithData \ "relocating_shards").as[Int] mustEqual 0 + (clusterInitializing \ "relocating_shards").as[Int] mustEqual 0 + (clusterRelocating \ "relocating_shards").as[Int] mustEqual 2 + } + + def initializingShards = { + (clusterWithoutData \ "initializing_shards").as[Int] mustEqual 0 + (clusterWithData \ "initializing_shards").as[Int] mustEqual 0 + (clusterInitializing \ "initializing_shards").as[Int] mustEqual 4 + (clusterRelocating \ "initializing_shards").as[Int] mustEqual 0 + } + + def unassignedShards = { + (clusterWithoutData \ "unassigned_shards").as[Int] mustEqual 0 + (clusterWithData \ "unassigned_shards").as[Int] mustEqual 0 + (clusterInitializing \ "unassigned_shards").as[Int] mustEqual 1 + (clusterRelocating \ "unassigned_shards").as[Int] mustEqual 0 + } + + def docsCount = { + (clusterWithoutData \ "docs_count").as[Int] mustEqual 0 + (clusterWithData \ "docs_count").as[Int] mustEqual 3 + (clusterInitializing \ "docs_count").as[Int] mustEqual 108680 + (clusterRelocating \ "docs_count").as[Int] mustEqual 108680 + } + + def sizeInBytes = { + (clusterWithoutData \ "size_in_bytes").as[Int] mustEqual 0 + (clusterWithData \ "size_in_bytes").as[Int] mustEqual 16184 + (clusterInitializing \ "size_in_bytes").as[Int] mustEqual 2026271 + (clusterRelocating \ "size_in_bytes").as[Int] mustEqual 4052542 + } + + def totalIndices = { + (clusterWithoutData \ "total_indices").as[Int] mustEqual 0 + (clusterWithData \ "total_indices").as[Int] mustEqual 3 + (clusterInitializing \ "total_indices").as[Int] mustEqual 1 + (clusterRelocating \ "total_indices").as[Int] mustEqual 1 + } + + def closedIndices = { + (clusterWithoutData \ "closed_indices").as[Int] mustEqual 0 + (clusterWithData \ "closed_indices").as[Int] mustEqual 1 + (clusterInitializing \ "closed_indices").as[Int] mustEqual 0 + (clusterRelocating \ "closed_indices").as[Int] mustEqual 0 + } + + def specialIndices = { + (clusterWithoutData \ "special_indices").as[Int] mustEqual 0 + (clusterWithData \ "special_indices").as[Int] mustEqual 1 + (clusterInitializing \ "special_indices").as[Int] mustEqual 0 + (clusterRelocating \ "special_indices").as[Int] mustEqual 0 + } + + def shardAllocation = { + (clusterWithData \ "shard_allocation").as[Boolean] mustEqual true + (clusterDiabledAllocation \ "shard_allocation").as[Boolean] mustEqual false + } + +} diff --git a/test/models/overview/ClusterRelocatingShards.scala b/test/models/overview/ClusterRelocatingShards.scala new file mode 100644 index 00000000..4a0974e7 --- /dev/null +++ b/test/models/overview/ClusterRelocatingShards.scala @@ -0,0 +1,862 @@ +package models.overview + +import play.api.libs.json.Json + +object ClusterRelocatingShards extends ClusterStub { + + val clusterState = Json.parse( + """ + |{ + | "cluster_name" : "elasticsearch", + | "master_node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "blocks" : { }, + | "routing_table" : { + | "indices" : { + | "hello" : { + | "shards" : { + | "1" : [ { + | "state" : "RELOCATING", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : "xIcHb7CPRH-_m4VGCtBV-w", + | "shard" : 1, + | "index" : "hello", + | "version" : 18, + | "expected_shard_size_in_bytes" : 407699, + | "allocation_id" : { + | "id" : "rNdtAPz_RhKVBp6dpAH1cw", + | "relocation_id" : "avGC6WQeTzqRnQyBE9O_bQ" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 1, + | "index" : "hello", + | "version" : 18, + | "allocation_id" : { + | "id" : "hlwc94lZRvOoBoaxyEWIGg" + | } + | } ], + | "4" : [ { + | "state" : "STARTED", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 4, + | "index" : "hello", + | "version" : 12, + | "allocation_id" : { + | "id" : "A4Dfk71HTriXU1OVBFQIeA" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 4, + | "index" : "hello", + | "version" : 12, + | "allocation_id" : { + | "id" : "Kyne0gDVQEasq9VxUUsxbg" + | } + | } ], + | "2" : [ { + | "state" : "STARTED", + | "primary" : true, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 2, + | "index" : "hello", + | "version" : 16, + | "allocation_id" : { + | "id" : "rN62kibSRZq0RxcwxEHKKw" + | } + | }, { + | "state" : "STARTED", + | "primary" : false, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 2, + | "index" : "hello", + | "version" : 16, + | "allocation_id" : { + | "id" : "2G3Hs3CnT5uvhpeOmHZyYg" + | } + | } ], + | "3" : [ { + | "state" : "STARTED", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 3, + | "index" : "hello", + | "version" : 12, + | "allocation_id" : { + | "id" : "NhG91IW6RCW1KAbSx67O9g" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 3, + | "index" : "hello", + | "version" : 12, + | "allocation_id" : { + | "id" : "b4Gdtk7uTuSINaZ_YdaJdg" + | } + | } ], + | "0" : [ { + | "state" : "STARTED", + | "primary" : true, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 0, + | "index" : "hello", + | "version" : 19, + | "allocation_id" : { + | "id" : "13mrI6FhRjGEk7xG7xYJvg" + | } + | }, { + | "state" : "RELOCATING", + | "primary" : false, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : "xIcHb7CPRH-_m4VGCtBV-w", + | "shard" : 0, + | "index" : "hello", + | "version" : 19, + | "expected_shard_size_in_bytes" : 405582, + | "allocation_id" : { + | "id" : "FbfzrPiySEaOzGMRZRDf7w", + | "relocation_id" : "fU5MEf2SSyGi40BscFJQsw" + | } + | } ] + | } + | } + | } + | }, + | "routing_nodes" : { + | "unassigned" : [ ], + | "nodes" : { + | "VOiMU2k5SuStH3-X1uuBGw" : [ { + | "state" : "RELOCATING", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : "xIcHb7CPRH-_m4VGCtBV-w", + | "shard" : 1, + | "index" : "hello", + | "version" : 18, + | "expected_shard_size_in_bytes" : 407699, + | "allocation_id" : { + | "id" : "rNdtAPz_RhKVBp6dpAH1cw", + | "relocation_id" : "avGC6WQeTzqRnQyBE9O_bQ" + | } + | }, { + | "state" : "STARTED", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 4, + | "index" : "hello", + | "version" : 12, + | "allocation_id" : { + | "id" : "A4Dfk71HTriXU1OVBFQIeA" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 2, + | "index" : "hello", + | "version" : 16, + | "allocation_id" : { + | "id" : "rN62kibSRZq0RxcwxEHKKw" + | } + | }, { + | "state" : "STARTED", + | "primary" : false, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 3, + | "index" : "hello", + | "version" : 12, + | "allocation_id" : { + | "id" : "NhG91IW6RCW1KAbSx67O9g" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "VOiMU2k5SuStH3-X1uuBGw", + | "relocating_node" : null, + | "shard" : 0, + | "index" : "hello", + | "version" : 19, + | "allocation_id" : { + | "id" : "13mrI6FhRjGEk7xG7xYJvg" + | } + | } ], + | "xIcHb7CPRH-_m4VGCtBV-w" : [ { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "xIcHb7CPRH-_m4VGCtBV-w", + | "relocating_node" : "VOiMU2k5SuStH3-X1uuBGw", + | "shard" : 1, + | "index" : "hello", + | "version" : 18, + | "expected_shard_size_in_bytes" : 407699, + | "allocation_id" : { + | "id" : "avGC6WQeTzqRnQyBE9O_bQ", + | "relocation_id" : "rNdtAPz_RhKVBp6dpAH1cw" + | } + | }, { + | "state" : "INITIALIZING", + | "primary" : false, + | "node" : "xIcHb7CPRH-_m4VGCtBV-w", + | "relocating_node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "shard" : 0, + | "index" : "hello", + | "version" : 19, + | "expected_shard_size_in_bytes" : 405582, + | "allocation_id" : { + | "id" : "fU5MEf2SSyGi40BscFJQsw", + | "relocation_id" : "FbfzrPiySEaOzGMRZRDf7w" + | } + | } ], + | "cPsT9o5FQ3WRnvqSTXHiVQ" : [ { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 1, + | "index" : "hello", + | "version" : 18, + | "allocation_id" : { + | "id" : "hlwc94lZRvOoBoaxyEWIGg" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 4, + | "index" : "hello", + | "version" : 12, + | "allocation_id" : { + | "id" : "Kyne0gDVQEasq9VxUUsxbg" + | } + | }, { + | "state" : "STARTED", + | "primary" : false, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 2, + | "index" : "hello", + | "version" : 16, + | "allocation_id" : { + | "id" : "2G3Hs3CnT5uvhpeOmHZyYg" + | } + | }, { + | "state" : "STARTED", + | "primary" : true, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : null, + | "shard" : 3, + | "index" : "hello", + | "version" : 12, + | "allocation_id" : { + | "id" : "b4Gdtk7uTuSINaZ_YdaJdg" + | } + | }, { + | "state" : "RELOCATING", + | "primary" : false, + | "node" : "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node" : "xIcHb7CPRH-_m4VGCtBV-w", + | "shard" : 0, + | "index" : "hello", + | "version" : 19, + | "expected_shard_size_in_bytes" : 405582, + | "allocation_id" : { + | "id" : "FbfzrPiySEaOzGMRZRDf7w", + | "relocation_id" : "fU5MEf2SSyGi40BscFJQsw" + | } + | } ] + | } + | } + |} + """.stripMargin + ) + + val nodesStats = Json.parse( + """ + |{ + | "cluster_name" : "elasticsearch", + | "nodes" : { + | "VOiMU2k5SuStH3-X1uuBGw" : { + | "timestamp" : 1458467882703, + | "name" : "Random", + | "transport_address" : "127.0.0.1:9301", + | "host" : "127.0.0.1", + | "ip" : [ "127.0.0.1:9301", "NONE" ], + | "os" : { + | "timestamp" : 1458467882703, + | "load_average" : 3.46435546875, + | "mem" : { + | "total_in_bytes" : 8589934592, + | "free_in_bytes" : 160845824, + | "used_in_bytes" : 8429088768, + | "free_percent" : 2, + | "used_percent" : 98 + | }, + | "swap" : { + | "total_in_bytes" : 3221225472, + | "free_in_bytes" : 1782317056, + | "used_in_bytes" : 1438908416 + | } + | }, + | "process" : { + | "timestamp" : 1458467882703, + | "open_file_descriptors" : 341, + | "max_file_descriptors" : 10240, + | "cpu" : { + | "percent" : 0, + | "total_in_millis" : 221649 + | }, + | "mem" : { + | "total_virtual_in_bytes" : 5311496192 + | } + | }, + | "jvm" : { + | "timestamp" : 1458467882703, + | "uptime_in_millis" : 2812465, + | "mem" : { + | "heap_used_in_bytes" : 109357464, + | "heap_used_percent" : 10, + | "heap_committed_in_bytes" : 259522560, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_used_in_bytes" : 70464888, + | "non_heap_committed_in_bytes" : 71409664, + | "pools" : { + | "young" : { + | "used_in_bytes" : 60805240, + | "max_in_bytes" : 286326784, + | "peak_used_in_bytes" : 71630848, + | "peak_max_in_bytes" : 286326784 + | }, + | "survivor" : { + | "used_in_bytes" : 2424584, + | "max_in_bytes" : 35782656, + | "peak_used_in_bytes" : 8912896, + | "peak_max_in_bytes" : 35782656 + | }, + | "old" : { + | "used_in_bytes" : 46127640, + | "max_in_bytes" : 715849728, + | "peak_used_in_bytes" : 46127640, + | "peak_max_in_bytes" : 715849728 + | } + | } + | }, + | "threads" : { + | "count" : 81, + | "peak_count" : 103 + | }, + | "gc" : { + | "collectors" : { + | "young" : { + | "collection_count" : 174, + | "collection_time_in_millis" : 866 + | }, + | "old" : { + | "collection_count" : 1, + | "collection_time_in_millis" : 16 + | } + | } + | }, + | "buffer_pools" : { + | "direct" : { + | "count" : 125, + | "used_in_bytes" : 20117010, + | "total_capacity_in_bytes" : 20117010 + | }, + | "mapped" : { + | "count" : 16, + | "used_in_bytes" : 1065237, + | "total_capacity_in_bytes" : 1065237 + | } + | } + | }, + | "fs" : { + | "timestamp" : 1458467882703, + | "total" : { + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 40472023040, + | "available_in_bytes" : 40209879040 + | }, + | "data" : [ { + | "path" : "/Users/leonardo.menezes/Downloads/elasticsearch-2.1.0/data/elasticsearch/nodes/1", + | "mount" : "/ (/dev/disk1)", + | "type" : "hfs", + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 40472023040, + | "available_in_bytes" : 40209879040 + | } ] + | } + | }, + | "xIcHb7CPRH-_m4VGCtBV-w" : { + | "timestamp" : 1458467882703, + | "name" : "Force", + | "transport_address" : "127.0.0.1:9302", + | "host" : "127.0.0.1", + | "ip" : [ "127.0.0.1:9302", "NONE" ], + | "os" : { + | "timestamp" : 1458467882703, + | "load_average" : 3.46435546875, + | "mem" : { + | "total_in_bytes" : 8589934592, + | "free_in_bytes" : 160845824, + | "used_in_bytes" : 8429088768, + | "free_percent" : 2, + | "used_percent" : 98 + | }, + | "swap" : { + | "total_in_bytes" : 3221225472, + | "free_in_bytes" : 1782317056, + | "used_in_bytes" : 1438908416 + | } + | }, + | "process" : { + | "timestamp" : 1458467882703, + | "open_file_descriptors" : 287, + | "max_file_descriptors" : 10240, + | "cpu" : { + | "percent" : 0, + | "total_in_millis" : 9698 + | }, + | "mem" : { + | "total_virtual_in_bytes" : 5304852480 + | } + | }, + | "jvm" : { + | "timestamp" : 1458467882703, + | "uptime_in_millis" : 20926, + | "mem" : { + | "heap_used_in_bytes" : 61247544, + | "heap_used_percent" : 5, + | "heap_committed_in_bytes" : 259522560, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_used_in_bytes" : 45875264, + | "non_heap_committed_in_bytes" : 46505984, + | "pools" : { + | "young" : { + | "used_in_bytes" : 42389104, + | "max_in_bytes" : 286326784, + | "peak_used_in_bytes" : 71630848, + | "peak_max_in_bytes" : 286326784 + | }, + | "survivor" : { + | "used_in_bytes" : 7531480, + | "max_in_bytes" : 35782656, + | "peak_used_in_bytes" : 8912888, + | "peak_max_in_bytes" : 35782656 + | }, + | "old" : { + | "used_in_bytes" : 11326960, + | "max_in_bytes" : 715849728, + | "peak_used_in_bytes" : 11326960, + | "peak_max_in_bytes" : 715849728 + | } + | } + | }, + | "threads" : { + | "count" : 78, + | "peak_count" : 78 + | }, + | "gc" : { + | "collectors" : { + | "young" : { + | "collection_count" : 4, + | "collection_time_in_millis" : 52 + | }, + | "old" : { + | "collection_count" : 1, + | "collection_time_in_millis" : 16 + | } + | } + | }, + | "buffer_pools" : { + | "direct" : { + | "count" : 79, + | "used_in_bytes" : 16786963, + | "total_capacity_in_bytes" : 16786963 + | }, + | "mapped" : { + | "count" : 0, + | "used_in_bytes" : 0, + | "total_capacity_in_bytes" : 0 + | } + | } + | }, + | "fs" : { + | "timestamp" : 1458467882704, + | "total" : { + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 40471994368, + | "available_in_bytes" : 40209850368 + | }, + | "data" : [ { + | "path" : "/Users/leonardo.menezes/Downloads/elasticsearch-2.1.0/data/elasticsearch/nodes/2", + | "mount" : "/ (/dev/disk1)", + | "type" : "hfs", + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 40471994368, + | "available_in_bytes" : 40209850368 + | } ] + | } + | }, + | "cPsT9o5FQ3WRnvqSTXHiVQ" : { + | "timestamp" : 1458467882703, + | "name" : "Cecilia Reyes", + | "transport_address" : "127.0.0.1:9300", + | "host" : "127.0.0.1", + | "ip" : [ "127.0.0.1:9300", "NONE" ], + | "os" : { + | "timestamp" : 1458467882703, + | "load_average" : 3.46435546875, + | "mem" : { + | "total_in_bytes" : 8589934592, + | "free_in_bytes" : 160845824, + | "used_in_bytes" : 8429088768, + | "free_percent" : 2, + | "used_percent" : 98 + | }, + | "swap" : { + | "total_in_bytes" : 3221225472, + | "free_in_bytes" : 1782317056, + | "used_in_bytes" : 1438908416 + | } + | }, + | "process" : { + | "timestamp" : 1458467882703, + | "open_file_descriptors" : 356, + | "max_file_descriptors" : 10240, + | "cpu" : { + | "percent" : 0, + | "total_in_millis" : 461242 + | }, + | "mem" : { + | "total_virtual_in_bytes" : 5319544832 + | } + | }, + | "jvm" : { + | "timestamp" : 1458467882703, + | "uptime_in_millis" : 14057100, + | "mem" : { + | "heap_used_in_bytes" : 80014736, + | "heap_used_percent" : 7, + | "heap_committed_in_bytes" : 259522560, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_used_in_bytes" : 81637648, + | "non_heap_committed_in_bytes" : 83148800, + | "pools" : { + | "young" : { + | "used_in_bytes" : 61420544, + | "max_in_bytes" : 286326784, + | "peak_used_in_bytes" : 71630848, + | "peak_max_in_bytes" : 286326784 + | }, + | "survivor" : { + | "used_in_bytes" : 1478648, + | "max_in_bytes" : 35782656, + | "peak_used_in_bytes" : 8912896, + | "peak_max_in_bytes" : 35782656 + | }, + | "old" : { + | "used_in_bytes" : 17115544, + | "max_in_bytes" : 715849728, + | "peak_used_in_bytes" : 104965192, + | "peak_max_in_bytes" : 715849728 + | } + | } + | }, + | "threads" : { + | "count" : 85, + | "peak_count" : 106 + | }, + | "gc" : { + | "collectors" : { + | "young" : { + | "collection_count" : 254, + | "collection_time_in_millis" : 1635 + | }, + | "old" : { + | "collection_count" : 2, + | "collection_time_in_millis" : 134 + | } + | } + | }, + | "buffer_pools" : { + | "direct" : { + | "count" : 173, + | "used_in_bytes" : 28642823, + | "total_capacity_in_bytes" : 28642823 + | }, + | "mapped" : { + | "count" : 15, + | "used_in_bytes" : 891872, + | "total_capacity_in_bytes" : 891872 + | } + | }, + | "classes" : { + | "current_loaded_count" : 7609, + | "total_loaded_count" : 7623, + | "total_unloaded_count" : 14 + | } + | }, + | "fs" : { + | "timestamp" : 1458467882703, + | "total" : { + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 40472023040, + | "available_in_bytes" : 40209879040 + | }, + | "data" : [ { + | "path" : "/Users/leonardo.menezes/Downloads/elasticsearch-2.1.0/data/elasticsearch/nodes/0", + | "mount" : "/ (/dev/disk1)", + | "type" : "hfs", + | "total_in_bytes" : 249804886016, + | "free_in_bytes" : 40472023040, + | "available_in_bytes" : 40209879040 + | } ] + | } + | } + | } + |} + """.stripMargin + ) + + val indicesStats = Json.parse( + """ + |{ + | "_shards" : { + | "total" : 10, + | "successful" : 10, + | "failed" : 0 + | }, + | "_all" : { + | "primaries" : { + | "docs" : { + | "count" : 108680, + | "deleted" : 0 + | }, + | "store" : { + | "size_in_bytes" : 2026271, + | "throttle_time_in_millis" : 0 + | } + | }, + | "total" : { + | "docs" : { + | "count" : 217360, + | "deleted" : 0 + | }, + | "store" : { + | "size_in_bytes" : 4052542, + | "throttle_time_in_millis" : 0 + | } + | } + | }, + | "indices" : { + | "hello" : { + | "primaries" : { + | "docs" : { + | "count" : 108680, + | "deleted" : 0 + | }, + | "store" : { + | "size_in_bytes" : 2026271, + | "throttle_time_in_millis" : 0 + | } + | }, + | "total" : { + | "docs" : { + | "count" : 217360, + | "deleted" : 0 + | }, + | "store" : { + | "size_in_bytes" : 4052542, + | "throttle_time_in_millis" : 0 + | } + | } + | } + | } + |} + """.stripMargin + ) + + val clusterSettings = Json.parse( + """ + |{ + | "persistent" : { }, + | "transient" : { } + |} + """.stripMargin + ) + + val aliases = Json.parse( + """ + |{ + | "hello" : { + | "aliases" : { } + | } + |} + """.stripMargin + ) + + val clusterHealth = Json.parse( + """ + |{ + | "cluster_name" : "elasticsearch", + | "status" : "green", + | "timed_out" : false, + | "number_of_nodes" : 3, + | "number_of_data_nodes" : 3, + | "active_primary_shards" : 5, + | "active_shards" : 10, + | "relocating_shards" : 2, + | "initializing_shards" : 0, + | "unassigned_shards" : 0, + | "delayed_unassigned_shards" : 0, + | "number_of_pending_tasks" : 0, + | "number_of_in_flight_fetch" : 0, + | "task_max_waiting_in_queue_millis" : 0, + | "active_shards_percent_as_number" : 100 + |} + """.stripMargin + ) + + val nodes = Json.parse( + """ + |{ + | "cluster_name" : "elasticsearch", + | "nodes" : { + | "VOiMU2k5SuStH3-X1uuBGw" : { + | "name" : "Random", + | "transport_address" : "127.0.0.1:9301", + | "host" : "127.0.0.1", + | "ip" : "127.0.0.1", + | "version" : "2.1.0", + | "build" : "72cd1f1", + | "http_address" : "127.0.0.1:9201", + | "os" : { + | "refresh_interval_in_millis" : 1000, + | "available_processors" : 8, + | "allocated_processors" : 8 + | }, + | "jvm" : { + | "pid" : 16419, + | "version" : "1.8.0_72", + | "vm_name" : "Java HotSpot(TM) 64-Bit Server VM", + | "vm_version" : "25.72-b15", + | "vm_vendor" : "Oracle Corporation", + | "start_time_in_millis" : 1458393717991, + | "mem" : { + | "heap_init_in_bytes" : 268435456, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_init_in_bytes" : 2555904, + | "non_heap_max_in_bytes" : 0, + | "direct_max_in_bytes" : 1037959168 + | }, + | "gc_collectors" : [ "ParNew", "ConcurrentMarkSweep" ], + | "memory_pools" : [ "Code Cache", "Metaspace", "Compressed Class Space", "Par Eden Space", "Par Survivor Space", "CMS Old Gen" ] + | } + | }, + | "xIcHb7CPRH-_m4VGCtBV-w" : { + | "name" : "Force", + | "transport_address" : "127.0.0.1:9302", + | "host" : "127.0.0.1", + | "ip" : "127.0.0.1", + | "version" : "2.1.0", + | "build" : "72cd1f1", + | "http_address" : "127.0.0.1:9202", + | "os" : { + | "refresh_interval_in_millis" : 1000, + | "available_processors" : 8, + | "allocated_processors" : 8 + | }, + | "jvm" : { + | "pid" : 76352, + | "version" : "1.8.0_72", + | "vm_name" : "Java HotSpot(TM) 64-Bit Server VM", + | "vm_version" : "25.72-b15", + | "vm_vendor" : "Oracle Corporation", + | "start_time_in_millis" : 1458467861836, + | "mem" : { + | "heap_init_in_bytes" : 268435456, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_init_in_bytes" : 2555904, + | "non_heap_max_in_bytes" : 0, + | "direct_max_in_bytes" : 1037959168 + | }, + | "gc_collectors" : [ "ParNew", "ConcurrentMarkSweep" ], + | "memory_pools" : [ "Code Cache", "Metaspace", "Compressed Class Space", "Par Eden Space", "Par Survivor Space", "CMS Old Gen" ] + | } + | }, + | "cPsT9o5FQ3WRnvqSTXHiVQ" : { + | "name" : "Cecilia Reyes", + | "transport_address" : "127.0.0.1:9300", + | "host" : "127.0.0.1", + | "ip" : "127.0.0.1", + | "version" : "2.1.0", + | "build" : "72cd1f1", + | "http_address" : "127.0.0.1:9200", + | "os" : { + | "refresh_interval_in_millis" : 1000, + | "name" : "Mac OS X", + | "arch" : "x86_64", + | "version" : "10.11.3", + | "available_processors" : 8, + | "allocated_processors" : 8 + | }, + | "jvm" : { + | "pid" : 60169, + | "version" : "1.8.0_72", + | "vm_name" : "Java HotSpot(TM) 64-Bit Server VM", + | "vm_version" : "25.72-b15", + | "vm_vendor" : "Oracle Corporation", + | "start_time_in_millis" : 1458345474505, + | "mem" : { + | "heap_init_in_bytes" : 268435456, + | "heap_max_in_bytes" : 1037959168, + | "non_heap_init_in_bytes" : 2555904, + | "non_heap_max_in_bytes" : 0, + | "direct_max_in_bytes" : 1037959168 + | }, + | "gc_collectors" : [ "ParNew", "ConcurrentMarkSweep" ], + | "memory_pools" : [ "Code Cache", "Metaspace", "Compressed Class Space", "Par Eden Space", "Par Survivor Space", "CMS Old Gen" ] + | } + | } + | } + |} + """.stripMargin + ) + + val main = Json.parse( + """ + |{ + | "name" : "Cecilia Reyes", + | "cluster_name" : "elasticsearch", + | "version" : { + | "number" : "2.1.0", + | "build_hash" : "72cd1f1a3eee09505e036106146dc1949dc5dc87", + | "build_timestamp" : "2015-11-18T22:40:03Z", + | "build_snapshot" : false, + | "lucene_version" : "5.3.1" + | }, + | "tagline" : "You Know, for Search" + |} + """.stripMargin + ) + +} diff --git a/test/models/overview/ClusterStub.scala b/test/models/overview/ClusterStub.scala new file mode 100644 index 00000000..20008cbf --- /dev/null +++ b/test/models/overview/ClusterStub.scala @@ -0,0 +1,25 @@ +package models.overview + +import play.api.libs.json.JsValue + +trait ClusterStub { + + def apply() = new ClusterOverview(clusterState, nodesStats, indicesStats, clusterSettings, aliases, clusterHealth, nodes, main) + + val clusterState: JsValue + + val nodesStats: JsValue + + val indicesStats: JsValue + + val clusterSettings: JsValue + + val aliases: JsValue + + val clusterHealth: JsValue + + val nodes: JsValue + + val main: JsValue + +} diff --git a/test/models/overview/ClusterWithData.scala b/test/models/overview/ClusterWithData.scala new file mode 100644 index 00000000..304260df --- /dev/null +++ b/test/models/overview/ClusterWithData.scala @@ -0,0 +1,715 @@ +package models.overview + +import play.api.libs.json.Json + +trait ClusterWithData extends ClusterStub { + + val clusterState = Json.parse( + """ + |{ + | "cluster_name": "elasticsearch", + | "master_node": "cPsT9o5FQ3WRnvqSTXHiVQ", + | "blocks" : { + | "indices" : { + | "foo" : { + | "4" : { + | "description" : "index closed", + | "retryable" : false, + | "levels" : [ "read", "write" ] + | } + | } + | } + | }, + | "routing_table": { + | "indices": { + | "bar": { + | "shards": { + | "0": [ + | { + | "state": "STARTED", + | "primary": false, + | "node": "MoDcZdJkQGK2RpYTvJhQlA", + | "relocating_node": null, + | "shard": 0, + | "index": "bar", + | "version": 3, + | "allocation_id": { + | "id": "ns_A3bOnS26LHP9aMMoNqQ" + | } + | }, + | { + | "state": "STARTED", + | "primary": true, + | "node": "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node": null, + | "shard": 0, + | "index": "bar", + | "version": 3, + | "allocation_id": { + | "id": "KpTuITnDRju5huuD7K42JQ" + | } + | } + | ] + | } + | }, + | ".foobar": { + | "shards": { + | "0": [ + | { + | "state": "STARTED", + | "primary": true, + | "node": "MoDcZdJkQGK2RpYTvJhQlA", + | "relocating_node": null, + | "shard": 0, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "av2CBQ7ZR6mpYP4hN45SFQ" + | } + | } + | ], + | "1": [ + | { + | "state": "STARTED", + | "primary": true, + | "node": "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node": null, + | "shard": 1, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "WA41NgmPRdyuV1Bdf3xAIw" + | } + | } + | ], + | "2": [ + | { + | "state": "STARTED", + | "primary": true, + | "node": "MoDcZdJkQGK2RpYTvJhQlA", + | "relocating_node": null, + | "shard": 2, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "9i-1Ze0iTyyGreKtd6uNlQ" + | } + | } + | ], + | "3": [ + | { + | "state": "STARTED", + | "primary": true, + | "node": "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node": null, + | "shard": 3, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "QqhRDD_DST6P0By3QaKjug" + | } + | } + | ], + | "4": [ + | { + | "state": "STARTED", + | "primary": true, + | "node": "MoDcZdJkQGK2RpYTvJhQlA", + | "relocating_node": null, + | "shard": 4, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "w30rCs_vRIeWWNdPD6yinA" + | } + | } + | ] + | } + | } + | } + | }, + | "routing_nodes": { + | "unassigned": [], + | "nodes": { + | "MoDcZdJkQGK2RpYTvJhQlA": [ + | { + | "state": "STARTED", + | "primary": false, + | "node": "MoDcZdJkQGK2RpYTvJhQlA", + | "relocating_node": null, + | "shard": 0, + | "index": "bar", + | "version": 3, + | "allocation_id": { + | "id": "ns_A3bOnS26LHP9aMMoNqQ" + | } + | }, + | { + | "state": "STARTED", + | "primary": true, + | "node": "MoDcZdJkQGK2RpYTvJhQlA", + | "relocating_node": null, + | "shard": 4, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "w30rCs_vRIeWWNdPD6yinA" + | } + | }, + | { + | "state": "STARTED", + | "primary": true, + | "node": "MoDcZdJkQGK2RpYTvJhQlA", + | "relocating_node": null, + | "shard": 2, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "9i-1Ze0iTyyGreKtd6uNlQ" + | } + | }, + | { + | "state": "STARTED", + | "primary": true, + | "node": "MoDcZdJkQGK2RpYTvJhQlA", + | "relocating_node": null, + | "shard": 0, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "av2CBQ7ZR6mpYP4hN45SFQ" + | } + | } + | ], + | "cPsT9o5FQ3WRnvqSTXHiVQ": [ + | { + | "state": "STARTED", + | "primary": true, + | "node": "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node": null, + | "shard": 0, + | "index": "bar", + | "version": 3, + | "allocation_id": { + | "id": "KpTuITnDRju5huuD7K42JQ" + | } + | }, + | { + | "state": "STARTED", + | "primary": true, + | "node": "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node": null, + | "shard": 1, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "WA41NgmPRdyuV1Bdf3xAIw" + | } + | }, + | { + | "state": "STARTED", + | "primary": true, + | "node": "cPsT9o5FQ3WRnvqSTXHiVQ", + | "relocating_node": null, + | "shard": 3, + | "index": ".foobar", + | "version": 2, + | "allocation_id": { + | "id": "QqhRDD_DST6P0By3QaKjug" + | } + | } + | ] + | } + | } + |} + """.stripMargin + ) + + val nodesStats = Json.parse( + """ + |{ + | "cluster_name": "elasticsearch", + | "nodes": { + | "MoDcZdJkQGK2RpYTvJhQlA": { + | "timestamp": 1458349671429, + | "name": "Solara", + | "transport_address": "127.0.0.1:9301", + | "host": "127.0.0.1", + | "ip": [ + | "127.0.0.1:9301", + | "NONE" + | ], + | "os": { + | "timestamp": 1458349670762, + | "load_average": 3.34326171875, + | "mem": { + | "total_in_bytes": 8589934592, + | "free_in_bytes": 33009664, + | "used_in_bytes": 8556924928, + | "free_percent": 0, + | "used_percent": 100 + | }, + | "swap": { + | "total_in_bytes": 2147483648, + | "free_in_bytes": 1729626112, + | "used_in_bytes": 417857536 + | } + | }, + | "process": { + | "timestamp": 1458349670762, + | "open_file_descriptors": 271, + | "max_file_descriptors": 10240, + | "cpu": { + | "percent": 0, + | "total_in_millis": 46412 + | }, + | "mem": { + | "total_virtual_in_bytes": 5282828288 + | } + | }, + | "jvm": { + | "timestamp": 1458349670762, + | "uptime_in_millis": 4187845, + | "mem": { + | "heap_used_in_bytes": 86912928, + | "heap_used_percent": 8, + | "heap_committed_in_bytes": 259522560, + | "heap_max_in_bytes": 1037959168, + | "non_heap_used_in_bytes": 54344920, + | "non_heap_committed_in_bytes": 55156736, + | "pools": { + | "young": { + | "used_in_bytes": 67421744, + | "max_in_bytes": 286326784, + | "peak_used_in_bytes": 71630848, + | "peak_max_in_bytes": 286326784 + | }, + | "survivor": { + | "used_in_bytes": 6942440, + | "max_in_bytes": 35782656, + | "peak_used_in_bytes": 8912888, + | "peak_max_in_bytes": 35782656 + | }, + | "old": { + | "used_in_bytes": 12548744, + | "max_in_bytes": 715849728, + | "peak_used_in_bytes": 12548744, + | "peak_max_in_bytes": 715849728 + | } + | } + | }, + | "threads": { + | "count": 71, + | "peak_count": 87 + | }, + | "gc": { + | "collectors": { + | "young": { + | "collection_count": 5, + | "collection_time_in_millis": 76 + | }, + | "old": { + | "collection_count": 1, + | "collection_time_in_millis": 18 + | } + | } + | }, + | "buffer_pools": { + | "direct": { + | "count": 79, + | "used_in_bytes": 14167303, + | "total_capacity_in_bytes": 14167303 + | }, + | "mapped": { + | "count": 0, + | "used_in_bytes": 0, + | "total_capacity_in_bytes": 0 + | } + | } + | }, + | "fs": { + | "timestamp": 1458349670762, + | "total": { + | "total_in_bytes": 249804886016, + | "free_in_bytes": 41525211136, + | "available_in_bytes": 41263067136 + | }, + | "data": [ + | { + | "path": "/Users/leonardo.menezes/Downloads/elasticsearch-2.1.0/data/elasticsearch/nodes/1", + | "mount": "/ (/dev/disk1)", + | "type": "hfs", + | "total_in_bytes": 249804886016, + | "free_in_bytes": 41525211136, + | "available_in_bytes": 41263067136 + | } + | ] + | } + | }, + | "cPsT9o5FQ3WRnvqSTXHiVQ": { + | "timestamp": 1458349671429, + | "name": "Cecilia Reyes", + | "transport_address": "127.0.0.1:9300", + | "host": "127.0.0.1", + | "ip": [ + | "127.0.0.1:9300", + | "NONE" + | ], + | "os": { + | "timestamp": 1458349670762, + | "load_average": 3.34326171875, + | "mem": { + | "total_in_bytes": 8589934592, + | "free_in_bytes": 33009664, + | "used_in_bytes": 8556924928, + | "free_percent": 0, + | "used_percent": 100 + | }, + | "swap": { + | "total_in_bytes": 2147483648, + | "free_in_bytes": 1729626112, + | "used_in_bytes": 417857536 + | } + | }, + | "process": { + | "timestamp": 1458349670762, + | "open_file_descriptors": 280, + | "max_file_descriptors": 10240, + | "cpu": { + | "percent": 0, + | "total_in_millis": 49096 + | }, + | "mem": { + | "total_virtual_in_bytes": 5301309440 + | } + | }, + | "jvm": { + | "timestamp": 1458349670762, + | "uptime_in_millis": 4196395, + | "mem": { + | "heap_used_in_bytes": 70134792, + | "heap_used_percent": 6, + | "heap_committed_in_bytes": 259522560, + | "heap_max_in_bytes": 1037959168, + | "non_heap_used_in_bytes": 61118128, + | "non_heap_committed_in_bytes": 62324736, + | "pools": { + | "young": { + | "used_in_bytes": 52733792, + | "max_in_bytes": 286326784, + | "peak_used_in_bytes": 71630848, + | "peak_max_in_bytes": 286326784 + | }, + | "survivor": { + | "used_in_bytes": 2375848, + | "max_in_bytes": 35782656, + | "peak_used_in_bytes": 8912896, + | "peak_max_in_bytes": 35782656 + | }, + | "old": { + | "used_in_bytes": 15025152, + | "max_in_bytes": 715849728, + | "peak_used_in_bytes": 15025152, + | "peak_max_in_bytes": 715849728 + | } + | } + | }, + | "threads": { + | "count": 77, + | "peak_count": 106 + | }, + | "gc": { + | "collectors": { + | "young": { + | "collection_count": 7, + | "collection_time_in_millis": 93 + | }, + | "old": { + | "collection_count": 1, + | "collection_time_in_millis": 12 + | } + | } + | }, + | "buffer_pools": { + | "direct": { + | "count": 153, + | "used_in_bytes": 23280699, + | "total_capacity_in_bytes": 23280699 + | }, + | "mapped": { + | "count": 0, + | "used_in_bytes": 0, + | "total_capacity_in_bytes": 0 + | } + | }, + | "classes": { + | "current_loaded_count": 7446, + | "total_loaded_count": 7446, + | "total_unloaded_count": 0 + | } + | }, + | "fs": { + | "timestamp": 1458349670762, + | "total": { + | "total_in_bytes": 249804886016, + | "free_in_bytes": 41525211136, + | "available_in_bytes": 41263067136 + | }, + | "data": [ + | { + | "path": "/Users/leonardo.menezes/Downloads/elasticsearch-2.1.0/data/elasticsearch/nodes/0", + | "mount": "/ (/dev/disk1)", + | "type": "hfs", + | "total_in_bytes": 249804886016, + | "free_in_bytes": 41525211136, + | "available_in_bytes": 41263067136 + | } + | ] + | } + | } + | } + |} + """.stripMargin + ) + + val indicesStats = Json.parse( + """ + |{ + | "_shards": { + | "total": 11, + | "successful": 11, + | "failed": 0 + | }, + | "_all": { + | "primaries": { + | "docs": { + | "count": 3, + | "deleted": 0 + | }, + | "store": { + | "size_in_bytes": 9902, + | "throttle_time_in_millis": 0 + | } + | }, + | "total": { + | "docs": { + | "count": 5, + | "deleted": 0 + | }, + | "store": { + | "size_in_bytes": 16184, + | "throttle_time_in_millis": 0 + | } + | } + | }, + | "indices": { + | "bar": { + | "primaries": { + | "docs": { + | "count": 1, + | "deleted": 0 + | }, + | "store": { + | "size_in_bytes": 3076, + | "throttle_time_in_millis": 0 + | } + | }, + | "total": { + | "docs": { + | "count": 2, + | "deleted": 0 + | }, + | "store": { + | "size_in_bytes": 6152, + | "throttle_time_in_millis": 0 + | } + | } + | }, + | ".foobar": { + | "primaries": { + | "docs": { + | "count": 1, + | "deleted": 0 + | }, + | "store": { + | "size_in_bytes": 3620, + | "throttle_time_in_millis": 0 + | } + | }, + | "total": { + | "docs": { + | "count": 1, + | "deleted": 0 + | }, + | "store": { + | "size_in_bytes": 3620, + | "throttle_time_in_millis": 0 + | } + | } + | } + | } + |} + """.stripMargin + ) + + val clusterSettings = Json.parse( + """ + |{ + | "persistent": {}, + | "transient": {} + |} + """.stripMargin + ) + + val aliases = Json.parse( + """ + |{ + | "bar": { + | "aliases": { + | "active": {} + | } + | }, + | ".foobar": { + | "aliases": {} + | } + |} + """.stripMargin + ) + + val clusterHealth = Json.parse( + """ + |{ + | "cluster_name": "elasticsearch", + | "status": "green", + | "timed_out": false, + | "number_of_nodes": 2, + | "number_of_data_nodes": 2, + | "active_primary_shards": 8, + | "active_shards": 11, + | "relocating_shards": 0, + | "initializing_shards": 0, + | "unassigned_shards": 0, + | "delayed_unassigned_shards": 0, + | "number_of_pending_tasks": 0, + | "number_of_in_flight_fetch": 0, + | "task_max_waiting_in_queue_millis": 0, + | "active_shards_percent_as_number": 100 + |} + """.stripMargin + ) + + val nodes = Json.parse( + """ + |{ + | "cluster_name": "elasticsearch", + | "nodes": { + | "MoDcZdJkQGK2RpYTvJhQlA": { + | "name": "Solara", + | "transport_address": "127.0.0.1:9301", + | "host": "127.0.0.1", + | "ip": "127.0.0.1", + | "version": "2.1.0", + | "build": "72cd1f1", + | "http_address": "127.0.0.1:9201", + | "os": { + | "refresh_interval_in_millis": 1000, + | "available_processors": 8, + | "allocated_processors": 8 + | }, + | "jvm": { + | "pid": 60238, + | "version": "1.8.0_72", + | "vm_name": "Java HotSpot(TM) 64-Bit Server VM", + | "vm_version": "25.72-b15", + | "vm_vendor": "Oracle Corporation", + | "start_time_in_millis": 1458345483045, + | "mem": { + | "heap_init_in_bytes": 268435456, + | "heap_max_in_bytes": 1037959168, + | "non_heap_init_in_bytes": 2555904, + | "non_heap_max_in_bytes": 0, + | "direct_max_in_bytes": 1037959168 + | }, + | "gc_collectors": [ + | "ParNew", + | "ConcurrentMarkSweep" + | ], + | "memory_pools": [ + | "Code Cache", + | "Metaspace", + | "Compressed Class Space", + | "Par Eden Space", + | "Par Survivor Space", + | "CMS Old Gen" + | ] + | } + | }, + | "cPsT9o5FQ3WRnvqSTXHiVQ": { + | "name": "Cecilia Reyes", + | "transport_address": "127.0.0.1:9300", + | "host": "127.0.0.1", + | "ip": "127.0.0.1", + | "version": "2.1.0", + | "build": "72cd1f1", + | "http_address": "127.0.0.1:9200", + | "os": { + | "refresh_interval_in_millis": 1000, + | "name": "Mac OS X", + | "arch": "x86_64", + | "version": "10.11.3", + | "available_processors": 8, + | "allocated_processors": 8 + | }, + | "jvm": { + | "pid": 60169, + | "version": "1.8.0_72", + | "vm_name": "Java HotSpot(TM) 64-Bit Server VM", + | "vm_version": "25.72-b15", + | "vm_vendor": "Oracle Corporation", + | "start_time_in_millis": 1458345474505, + | "mem": { + | "heap_init_in_bytes": 268435456, + | "heap_max_in_bytes": 1037959168, + | "non_heap_init_in_bytes": 2555904, + | "non_heap_max_in_bytes": 0, + | "direct_max_in_bytes": 1037959168 + | }, + | "gc_collectors": [ + | "ParNew", + | "ConcurrentMarkSweep" + | ], + | "memory_pools": [ + | "Code Cache", + | "Metaspace", + | "Compressed Class Space", + | "Par Eden Space", + | "Par Survivor Space", + | "CMS Old Gen" + | ] + | } + | } + | } + |} + """.stripMargin + ) + + val main = Json.parse( + """ + |{ + | "name": "Cecilia Reyes", + | "cluster_name": "elasticsearch", + | "version": { + | "number": "2.1.0", + | "build_hash": "72cd1f1a3eee09505e036106146dc1949dc5dc87", + | "build_timestamp": "2015-11-18T22:40:03Z", + | "build_snapshot": false, + | "lucene_version": "5.3.1" + | }, + | "tagline": "You Know, for Search" + |} + """.stripMargin + ) + +} + +object ClusterWithData extends ClusterWithData diff --git a/test/models/overview/ClusterWithoutData.scala b/test/models/overview/ClusterWithoutData.scala new file mode 100644 index 00000000..2398e93a --- /dev/null +++ b/test/models/overview/ClusterWithoutData.scala @@ -0,0 +1,462 @@ +package models.overview + +import play.api.libs.json.Json + +object ClusterWithoutData extends ClusterStub { + + val clusterState = Json.parse( + """ + |{ + | "cluster_name":"elasticsearch", + | "master_node":"cPsT9o5FQ3WRnvqSTXHiVQ", + | "blocks":{ + | + | }, + | "routing_table":{ + | "indices":{ + | + | } + | }, + | "routing_nodes":{ + | "unassigned":[ + | + | ], + | "nodes":{ + | "MoDcZdJkQGK2RpYTvJhQlA":[ + | + | ], + | "cPsT9o5FQ3WRnvqSTXHiVQ":[ + | + | ] + | } + | } + |} + """.stripMargin + ) + + val nodesStats = Json.parse( + """ + |{ + | "cluster_name":"elasticsearch", + | "nodes":{ + | "MoDcZdJkQGK2RpYTvJhQlA":{ + | "timestamp":1458346589015, + | "name":"Solara", + | "transport_address":"127.0.0.1:9301", + | "host":"127.0.0.1", + | "ip":[ + | "127.0.0.1:9301", + | "NONE" + | ], + | "os":{ + | "timestamp":1458346589015, + | "load_average":3.17138671875, + | "mem":{ + | "total_in_bytes":8589934592, + | "free_in_bytes":101085184, + | "used_in_bytes":8488849408, + | "free_percent":1, + | "used_percent":99 + | }, + | "swap":{ + | "total_in_bytes":2147483648, + | "free_in_bytes":1736966144, + | "used_in_bytes":410517504 + | } + | }, + | "process":{ + | "timestamp":1458346589015, + | "open_file_descriptors":257, + | "max_file_descriptors":10240, + | "cpu":{ + | "percent":0, + | "total_in_millis":24084 + | }, + | "mem":{ + | "total_virtual_in_bytes":5274689536 + | } + | }, + | "jvm":{ + | "timestamp":1458346589015, + | "uptime_in_millis":1106048, + | "mem":{ + | "heap_used_in_bytes":28420720, + | "heap_used_percent":2, + | "heap_committed_in_bytes":259522560, + | "heap_max_in_bytes":1037959168, + | "non_heap_used_in_bytes":50725848, + | "non_heap_committed_in_bytes":51486720, + | "pools":{ + | "young":{ + | "used_in_bytes":8929536, + | "max_in_bytes":286326784, + | "peak_used_in_bytes":71630848, + | "peak_max_in_bytes":286326784 + | }, + | "survivor":{ + | "used_in_bytes":6942440, + | "max_in_bytes":35782656, + | "peak_used_in_bytes":8912888, + | "peak_max_in_bytes":35782656 + | }, + | "old":{ + | "used_in_bytes":12548744, + | "max_in_bytes":715849728, + | "peak_used_in_bytes":12548744, + | "peak_max_in_bytes":715849728 + | } + | } + | }, + | "threads":{ + | "count":67, + | "peak_count":87 + | }, + | "gc":{ + | "collectors":{ + | "young":{ + | "collection_count":5, + | "collection_time_in_millis":76 + | }, + | "old":{ + | "collection_count":1, + | "collection_time_in_millis":18 + | } + | } + | }, + | "buffer_pools":{ + | "direct":{ + | "count":71, + | "used_in_bytes":13640062, + | "total_capacity_in_bytes":13640062 + | }, + | "mapped":{ + | "count":0, + | "used_in_bytes":0, + | "total_capacity_in_bytes":0 + | } + | } + | }, + | "fs":{ + | "timestamp":1458346589015, + | "total":{ + | "total_in_bytes":249804886016, + | "free_in_bytes":41567444992, + | "available_in_bytes":41305300992 + | }, + | "data":[ + | { + | "path":"/Users/leonardo.menezes/Downloads/elasticsearch-2.1.0/data/elasticsearch/nodes/1", + | "mount":"/ (/dev/disk1)", + | "type":"hfs", + | "total_in_bytes":249804886016, + | "free_in_bytes":41567444992, + | "available_in_bytes":41305300992 + | } + | ] + | } + | }, + | "cPsT9o5FQ3WRnvqSTXHiVQ":{ + | "timestamp":1458346589015, + | "name":"Cecilia Reyes", + | "transport_address":"127.0.0.1:9300", + | "host":"127.0.0.1", + | "ip":[ + | "127.0.0.1:9300", + | "NONE" + | ], + | "os":{ + | "timestamp":1458346589015, + | "load_average":3.17138671875, + | "mem":{ + | "total_in_bytes":8589934592, + | "free_in_bytes":101085184, + | "used_in_bytes":8488849408, + | "free_percent":1, + | "used_percent":99 + | }, + | "swap":{ + | "total_in_bytes":2147483648, + | "free_in_bytes":1736966144, + | "used_in_bytes":410517504 + | } + | }, + | "process":{ + | "timestamp":1458346589015, + | "open_file_descriptors":265, + | "max_file_descriptors":10240, + | "cpu":{ + | "percent":0, + | "total_in_millis":23221 + | }, + | "mem":{ + | "total_virtual_in_bytes":5287575552 + | } + | }, + | "jvm":{ + | "timestamp":1458346589015, + | "uptime_in_millis":1114598, + | "mem":{ + | "heap_used_in_bytes":24190184, + | "heap_used_percent":2, + | "heap_committed_in_bytes":259522560, + | "heap_max_in_bytes":1037959168, + | "non_heap_used_in_bytes":53616440, + | "non_heap_committed_in_bytes":54919168, + | "pools":{ + | "young":{ + | "used_in_bytes":4830480, + | "max_in_bytes":286326784, + | "peak_used_in_bytes":71630848, + | "peak_max_in_bytes":286326784 + | }, + | "survivor":{ + | "used_in_bytes":4334552, + | "max_in_bytes":35782656, + | "peak_used_in_bytes":8912896, + | "peak_max_in_bytes":35782656 + | }, + | "old":{ + | "used_in_bytes":15025152, + | "max_in_bytes":715849728, + | "peak_used_in_bytes":15025152, + | "peak_max_in_bytes":715849728 + | } + | } + | }, + | "threads":{ + | "count":72, + | "peak_count":106 + | }, + | "gc":{ + | "collectors":{ + | "young":{ + | "collection_count":6, + | "collection_time_in_millis":85 + | }, + | "old":{ + | "collection_count":1, + | "collection_time_in_millis":12 + | } + | } + | }, + | "buffer_pools":{ + | "direct":{ + | "count":122, + | "used_in_bytes":18508157, + | "total_capacity_in_bytes":18508157 + | }, + | "mapped":{ + | "count":0, + | "used_in_bytes":0, + | "total_capacity_in_bytes":0 + | } + | }, + | "classes":{ + | "current_loaded_count":6988, + | "total_loaded_count":6988, + | "total_unloaded_count":0 + | } + | }, + | "fs":{ + | "timestamp":1458346589015, + | "total":{ + | "total_in_bytes":249804886016, + | "free_in_bytes":41567444992, + | "available_in_bytes":41305300992 + | }, + | "data":[ + | { + | "path":"/Users/leonardo.menezes/Downloads/elasticsearch-2.1.0/data/elasticsearch/nodes/0", + | "mount":"/ (/dev/disk1)", + | "type":"hfs", + | "total_in_bytes":249804886016, + | "free_in_bytes":41567444992, + | "available_in_bytes":41305300992 + | } + | ] + | } + | } + | } + |} + """.stripMargin + ) + + val indicesStats = Json.parse( + """ + |{ + | "_shards":{ + | "total":0, + | "successful":0, + | "failed":0 + | }, + | "_all":{ + | "primaries":{ + | + | }, + | "total":{ + | + | } + | }, + | "indices":{ + | + | } + |} + """.stripMargin + ) + + val clusterSettings = Json.parse( + """ + |{ + | "persistent":{ + | + | }, + | "transient":{ + | + | } + |} + """.stripMargin + ) + + val aliases = Json.parse( + """ + |{ + | + |} + """.stripMargin + ) + + val clusterHealth = Json.parse( + """ + |{ + | "cluster_name":"elasticsearch", + | "status":"green", + | "timed_out":false, + | "number_of_nodes":2, + | "number_of_data_nodes":2, + | "active_primary_shards":0, + | "active_shards":0, + | "relocating_shards":0, + | "initializing_shards":0, + | "unassigned_shards":0, + | "delayed_unassigned_shards":0, + | "number_of_pending_tasks":0, + | "number_of_in_flight_fetch":0, + | "task_max_waiting_in_queue_millis":0, + | "active_shards_percent_as_number":100 + |} + """.stripMargin + ) + + val nodes = Json.parse( + """ + |{ + | "cluster_name":"elasticsearch", + | "nodes":{ + | "MoDcZdJkQGK2RpYTvJhQlA":{ + | "name":"Solara", + | "transport_address":"127.0.0.1:9301", + | "host":"127.0.0.1", + | "ip":"127.0.0.1", + | "version":"2.1.0", + | "build":"72cd1f1", + | "http_address":"127.0.0.1:9201", + | "os":{ + | "refresh_interval_in_millis":1000, + | "available_processors":8, + | "allocated_processors":8 + | }, + | "jvm":{ + | "pid":60238, + | "version":"1.8.0_72", + | "vm_name":"Java HotSpot(TM) 64-Bit Server VM", + | "vm_version":"25.72-b15", + | "vm_vendor":"Oracle Corporation", + | "start_time_in_millis":1458345483045, + | "mem":{ + | "heap_init_in_bytes":268435456, + | "heap_max_in_bytes":1037959168, + | "non_heap_init_in_bytes":2555904, + | "non_heap_max_in_bytes":0, + | "direct_max_in_bytes":1037959168 + | }, + | "gc_collectors":[ + | "ParNew", + | "ConcurrentMarkSweep" + | ], + | "memory_pools":[ + | "Code Cache", + | "Metaspace", + | "Compressed Class Space", + | "Par Eden Space", + | "Par Survivor Space", + | "CMS Old Gen" + | ] + | } + | }, + | "cPsT9o5FQ3WRnvqSTXHiVQ":{ + | "name":"Cecilia Reyes", + | "transport_address":"127.0.0.1:9300", + | "host":"127.0.0.1", + | "ip":"127.0.0.1", + | "version":"2.1.0", + | "build":"72cd1f1", + | "http_address":"127.0.0.1:9200", + | "os":{ + | "refresh_interval_in_millis":1000, + | "name":"Mac OS X", + | "arch":"x86_64", + | "version":"10.11.3", + | "available_processors":8, + | "allocated_processors":8 + | }, + | "jvm":{ + | "pid":60169, + | "version":"1.8.0_72", + | "vm_name":"Java HotSpot(TM) 64-Bit Server VM", + | "vm_version":"25.72-b15", + | "vm_vendor":"Oracle Corporation", + | "start_time_in_millis":1458345474505, + | "mem":{ + | "heap_init_in_bytes":268435456, + | "heap_max_in_bytes":1037959168, + | "non_heap_init_in_bytes":2555904, + | "non_heap_max_in_bytes":0, + | "direct_max_in_bytes":1037959168 + | }, + | "gc_collectors":[ + | "ParNew", + | "ConcurrentMarkSweep" + | ], + | "memory_pools":[ + | "Code Cache", + | "Metaspace", + | "Compressed Class Space", + | "Par Eden Space", + | "Par Survivor Space", + | "CMS Old Gen" + | ] + | } + | } + | } + |} + """.stripMargin + ) + + val main = Json.parse( + """ + |{ + | "name":"Cecilia Reyes", + | "cluster_name":"elasticsearch", + | "version":{ + | "number":"2.1.0", + | "build_hash":"72cd1f1a3eee09505e036106146dc1949dc5dc87", + | "build_timestamp":"2015-11-18T22:40:03Z", + | "build_snapshot":false, + | "lucene_version":"5.3.1" + | }, + | "tagline":"You Know, for Search" + |} + """.stripMargin + ) + +}