diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..15d56783
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,20 @@
+FROM openjdk:11-jre-slim
+
+ENV CEREBRO_VERSION 0.8.4
+
+ADD target/universal/cerebro-${CEREBRO_VERSION}.tgz /opt
+
+RUN mv /opt/cerebro-$CEREBRO_VERSION /opt/cerebro \
+ && apt-get update \
+ && apt-get install -y wget \
+ && rm -rf /var/lib/apt/lists/* \
+ && mkdir -p /opt/cerebro/logs \
+ && sed -i '//d' /opt/cerebro/conf/logback.xml \
+ && addgroup -gid 1000 cerebro \
+ && adduser -gid 1000 -uid 1000 cerebro \
+ && chown -R cerebro:cerebro /opt/cerebro
+
+WORKDIR /opt/cerebro
+EXPOSE 9000
+USER cerebro
+ENTRYPOINT [ "/opt/cerebro/bin/cerebro" ]
\ No newline at end of file
diff --git a/app/models/commons/NodeInfo.scala b/app/models/commons/NodeInfo.scala
index 77279b11..4e576707 100644
--- a/app/models/commons/NodeInfo.scala
+++ b/app/models/commons/NodeInfo.scala
@@ -8,7 +8,8 @@ trait NodeInfo {
"ml.machine_memory",
"xpack.installed",
"transform.node",
- "ml.max_open_jobs"
+ "ml.max_open_jobs",
+ "ml.max_jvm_size"
)
def attrs(info: JsValue) = {
diff --git a/app/models/commons/NodeRoles.scala b/app/models/commons/NodeRoles.scala
index 0719e13a..484f571f 100644
--- a/app/models/commons/NodeRoles.scala
+++ b/app/models/commons/NodeRoles.scala
@@ -16,7 +16,7 @@ object NodeRoles {
def apply(nodeInfo: JsValue): NodeRoles = {
// >= 7.10
- val dataRoles = Seq("data", "data_content", "data_hot", "data_warm", "data_cold").map(JsString)
+ val dataRoles = Seq("data", "data_content", "data_hot", "data_warm", "data_cold", "data_frozen").map(JsString)
(nodeInfo \ "roles").asOpt[JsArray] match {
case Some(JsArray(roles)) => // >= 5.X
diff --git a/app/models/nodes/Node.scala b/app/models/nodes/Node.scala
index f0d75b20..6641ebe5 100644
--- a/app/models/nodes/Node.scala
+++ b/app/models/nodes/Node.scala
@@ -29,7 +29,11 @@ object Node extends NodeInfo {
"master" -> JsBoolean(roles.master),
"coordinating" -> JsBoolean(roles.coordinating),
"ingest" -> JsBoolean(roles.ingest),
- "data" -> JsBoolean(roles.data)
+ "data" -> JsBoolean(roles.data),
+ "roles" -> JsArray(info \ "roles" match {
+ case JsDefined(JsArray(roles)) => roles
+ case _ => Seq.empty
+ })
)
}
diff --git a/app/models/overview/ClosedIndex.scala b/app/models/overview/ClosedIndex.scala
index 00dc6702..78463549 100644
--- a/app/models/overview/ClosedIndex.scala
+++ b/app/models/overview/ClosedIndex.scala
@@ -1,14 +1,17 @@
package models.overview
-import play.api.libs.json.{JsBoolean, JsString, Json}
+import play.api.libs.json.{JsBoolean, JsNumber, JsString, JsValue, Json}
object ClosedIndex {
- def apply(name: String) =
+ def apply(name: String, aliases: JsValue, numShards: JsNumber, numReplicas: JsNumber) =
Json.obj(
"name" -> JsString(name),
"closed" -> JsBoolean(true),
- "special" -> JsBoolean(name.startsWith("."))
+ "special" -> JsBoolean(name.startsWith(".")),
+ "aliases" -> aliases,
+ "num_shards" -> numShards,
+ "num_replicas" -> numReplicas
)
}
diff --git a/app/models/overview/ClusterOverview.scala b/app/models/overview/ClusterOverview.scala
index 95d77cd1..8c746743 100644
--- a/app/models/overview/ClusterOverview.scala
+++ b/app/models/overview/ClusterOverview.scala
@@ -59,9 +59,13 @@ object ClusterOverview {
Index(index, indexStats, shards, indexAliases, indexBlock)
}.toSeq
+ val metadata = (clusterState \ "metadata" \ "indices").as[JsObject]
val closedIndices = blocks.value.collect { // ES < 7.X does not return routing_table for closed indices
case (name, block) if !routingTable.contains(name) && (block \ "4").isDefined =>
- ClosedIndex(name)
+ val indexAliases = (metadata \ name \ "aliases").as[JsArray]
+ val numShards = JsNumber((metadata \ name \ "settings" \ "index" \ "number_of_shards").as[JsString].value.toInt)
+ val numReplicas = JsNumber((metadata \ name \ "settings" \ "index" \ "number_of_replicas").as[JsString].value.toInt)
+ ClosedIndex(name, indexAliases, numShards, numReplicas)
}
indices ++ closedIndices
diff --git a/app/models/overview/Index.scala b/app/models/overview/Index.scala
index 7eeeef30..924d66a0 100644
--- a/app/models/overview/Index.scala
+++ b/app/models/overview/Index.scala
@@ -17,6 +17,8 @@ object Index {
"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.as[JsObject].keys.map(JsString(_)).toSeq), // 1.4 < does not return aliases obj
+ "is_write_true_aliases" -> JsArray(aliases.as[JsObject].fieldSet.filter(
+ _._2.as[JsObject].fieldSet.contains("is_write_index", JsBoolean(true))).map(_._1).map(JsString(_)).toSeq),
"num_shards" -> JsNumber((routingTable \ "shards").as[JsObject].keys.map(_.toInt).max + 1),
"num_replicas" -> JsNumber((routingTable \ "shards" \ "0").as[JsArray].value.size - 1),
"shards" -> JsObject(shardMap)
diff --git a/app/models/overview/Node.scala b/app/models/overview/Node.scala
index c051f7f8..d33015be 100644
--- a/app/models/overview/Node.scala
+++ b/app/models/overview/Node.scala
@@ -29,6 +29,10 @@ object Node extends NodeInfo {
"data" -> JsBoolean(nodeRoles.data),
"coordinating" -> JsBoolean(nodeRoles.coordinating),
"ingest" -> JsBoolean(nodeRoles.ingest),
+ "roles" -> JsArray(info \ "roles" match {
+ case JsDefined(JsArray(roles)) => roles
+ case _ => Seq.empty
+ }),
"heap" -> Json.obj(
"used" -> (stats \ "jvm" \ "mem" \ "heap_used_in_bytes").as[JsNumber],
"committed" -> (stats \ "jvm" \ "mem" \ "heap_committed_in_bytes").as[JsNumber],
@@ -59,8 +63,10 @@ object Node extends NodeInfo {
}
def cpuPercent(nodeStats: JsValue): JsNumber = {
- val cpu = (nodeStats \ "os" \ "cpu" \ "percent").asOpt[Int].getOrElse(// 5.X
- (nodeStats \ "os" \ "cpu_percent").asOpt[Int].getOrElse(0) // FIXME 2.X
+ val cpu = (nodeStats \ "os" \ "cpu" \ "percent").asOpt[Int].filter(i => i > 0).getOrElse(// 5.X
+ (nodeStats \ "process" \ "cpu" \ "percent").asOpt[Int].filter(i => i > 0).getOrElse( // 7.X
+ (nodeStats \ "os" \ "cpu_percent").asOpt[Int].getOrElse(0) // FIXME 2.X
+ )
)
JsNumber(BigDecimal(cpu))
}
diff --git a/app/services/overview/OverviewDataService.scala b/app/services/overview/OverviewDataService.scala
index c0d0c988..7d55c001 100644
--- a/app/services/overview/OverviewDataService.scala
+++ b/app/services/overview/OverviewDataService.scala
@@ -14,7 +14,7 @@ class OverviewDataService @Inject()(client: ElasticClient) {
def overview(target: ElasticServer): Future[JsValue] = {
val apis = Seq(
- "_cluster/state/master_node,routing_table,blocks",
+ "_cluster/state/master_node,routing_table,blocks,metadata",
"_nodes/stats/jvm,fs,os,process?human=true",
"_stats/docs,store?ignore_unavailable=true",
"_cluster/settings",
diff --git a/public/js/app.js b/public/js/app.js
index 6e859d32..a29f4c55 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -1113,7 +1113,7 @@ angular.module('cerebro').controller('OverviewController', ['$scope', '$http',
$scope.special_indices = 0;
$scope.shardAllocation = true;
- $scope.indices_filter = new IndexFilter('', false, false, true, true, 0);
+ $scope.indices_filter = new IndexFilter('', [], false, false, true, true, 0);
$scope.nodes_filter = new NodeFilter('', true, false, false, false, 0);
$scope.getPageSize = function() {
@@ -1192,6 +1192,14 @@ angular.module('cerebro').controller('OverviewController', ['$scope', '$http',
$scope.$watch('nodes_filter', function() {
if ($scope.data) {
$scope.setNodes($scope.data.nodes);
+ if ($scope.nodes_filter.name) {
+ $scope.indices_filter.nodes = $scope.nodes.map(function (node) {
+ return node.id;
+ });
+ } else {
+ $scope.indices_filter.nodes = [];
+ }
+ $scope.setIndices($scope.data.indices);
}
},
true);
@@ -2265,8 +2273,9 @@ function GroupedSettings(settings) {
this.groups = Object.values(groups);
}
-function IndexFilter(name, closed, special, healthy, asc, timestamp) {
+function IndexFilter(name, nodes, closed, special, healthy, asc, timestamp) {
this.name = name;
+ this.nodes = nodes;
this.closed = closed;
this.special = special;
this.healthy = healthy;
@@ -2294,6 +2303,7 @@ function IndexFilter(name, closed, special, healthy, asc, timestamp) {
// eslint-disable-next-line no-unused-vars
return new IndexFilter(
this.name,
+ this.nodes,
this.closed,
this.special,
this.healthy,
@@ -2306,6 +2316,7 @@ function IndexFilter(name, closed, special, healthy, asc, timestamp) {
return (
other !== null &&
this.name === other.name &&
+ this.nodes === other.nodes &&
this.closed === other.closed &&
this.special === other.special &&
this.healthy === other.healthy &&
@@ -2360,6 +2371,12 @@ function IndexFilter(name, closed, special, healthy, asc, timestamp) {
}
}
}
+ if (matches && this.nodes.length > 0) {
+ var shardNodes = Object.keys(index.shards);
+ matches = this.nodes.filter(function(node) {
+ return shardNodes.indexOf(node) != -1;
+ }).length > 0;
+ }
return matches;
};
}
@@ -2402,11 +2419,53 @@ function NodeFilter(name, data, master, ingest, coordinating, timestamp) {
};
this.matches = function(node) {
- if (this.isBlank()) {
- return true;
- } else {
- return this.matchesName(node.name) && this.matchesType(node);
+ var matches = true;
+ if (!this.matchesType(node)) {
+ matches = false;
+ }
+ if (matches && this.name) {
+ try {
+ var regExp = new RegExp(this.name.trim(), 'i');
+ matches = regExp.test(node.name);
+ if (!matches) {
+ var attrs = Object.values(node.attributes);
+ for (var idx = 0; idx < attrs.length; idx++) {
+ if ((matches = regExp.test(attrs[idx]))) {
+ break;
+ }
+ }
+ }
+ if (!matches) {
+ for (idx = 0; idx < node.roles.length; idx++) {
+ if ((matches = regExp.test(node.roles[idx]))) {
+ break;
+ }
+ }
+ }
+ } catch (err) { // if not valid regexp, still try normal matching
+ matches = node.name.indexOf(this.name.toLowerCase()) != -1;
+ if (!matches) {
+ var _attrs = Object.values(node.attributes);
+ for (var _idx = 0; _idx < _attrs.length; _idx++) {
+ var attr = _attrs[_idx].toLowerCase();
+ matches = true;
+ if ((matches = (attr.indexOf(this.name.toLowerCase()) != -1))) {
+ break;
+ }
+ }
+ }
+ if (!matches) {
+ for (_idx = 0; _idx < node.roles.length; _idx++) {
+ var role = node.roles[_idx].toLowerCase();
+ matches = true;
+ if ((matches = (role.indexOf(this.name.toLowerCase()) != -1))) {
+ break;
+ }
+ }
+ }
+ }
}
+ return matches;
};
this.matchesType = function(node) {
@@ -2417,14 +2476,6 @@ function NodeFilter(name, data, master, ingest, coordinating, timestamp) {
node.coordinating && this.coordinating
);
};
-
- this.matchesName = function(name) {
- if (this.name) {
- return name.toLowerCase().indexOf(this.name.toLowerCase()) != -1;
- } else {
- return true;
- }
- };
}
// eslint-disable-next-line no-unused-vars
diff --git a/public/nodes/index.html b/public/nodes/index.html
index e157d682..00791cfe 100644
--- a/public/nodes/index.html
+++ b/public/nodes/index.html
@@ -2,7 +2,7 @@
+ placeholder="filter nodes and indices by nodes">
@@ -86,6 +86,9 @@
{{value}}
+
+ {{value}}
+
diff --git a/public/overview.html b/public/overview.html
index 81148df3..cd43d3ab 100644
--- a/public/overview.html
+++ b/public/overview.html
@@ -28,7 +28,7 @@
@@ -63,18 +63,18 @@
-
-
-
-
+
+
+
+
-
-
{{index.aliases[0]}}
-
(+{{index.aliases.length - 1}})
+
+
+ {{index.aliases[0]}}
+ {{index.aliases[0]}}
+
+
+ {{index.aliases[0]}}(+{{index.aliases.length - 1}})
+ {{index.aliases[0]}}(+{{index.aliases.length - 1}})
+
+
+
+ {{alias}}
+ {{alias}}
@@ -169,6 +179,7 @@
size: {{index.size_in_bytes | bytes}}
+ shards: {{index.num_shards}} * {{index.num_replicas + 1}}
index closed
@@ -235,9 +246,12 @@
{{value}}
+
+ {{value}}
+
-
+
JVM: {{node.jvm_version}}
ES: {{node.es_version}}
diff --git a/src/app/components/overview/controller.js b/src/app/components/overview/controller.js
index 68a3f294..cfc253d5 100644
--- a/src/app/components/overview/controller.js
+++ b/src/app/components/overview/controller.js
@@ -14,7 +14,7 @@ angular.module('cerebro').controller('OverviewController', ['$scope', '$http',
$scope.special_indices = 0;
$scope.shardAllocation = true;
- $scope.indices_filter = new IndexFilter('', false, false, true, true, 0);
+ $scope.indices_filter = new IndexFilter('', [], false, false, true, true, 0);
$scope.nodes_filter = new NodeFilter('', true, false, false, false, 0);
$scope.getPageSize = function() {
@@ -93,6 +93,14 @@ angular.module('cerebro').controller('OverviewController', ['$scope', '$http',
$scope.$watch('nodes_filter', function() {
if ($scope.data) {
$scope.setNodes($scope.data.nodes);
+ if ($scope.nodes_filter.name) {
+ $scope.indices_filter.nodes = $scope.nodes.map(function (node) {
+ return node.id;
+ });
+ } else {
+ $scope.indices_filter.nodes = [];
+ }
+ $scope.setIndices($scope.data.indices);
}
},
true);
diff --git a/src/app/shared/node_filter.js b/src/app/shared/node_filter.js
index e997c8c5..e7249dcb 100644
--- a/src/app/shared/node_filter.js
+++ b/src/app/shared/node_filter.js
@@ -36,11 +36,53 @@ function NodeFilter(name, data, master, ingest, coordinating, timestamp) {
};
this.matches = function(node) {
- if (this.isBlank()) {
- return true;
- } else {
- return this.matchesName(node.name) && this.matchesType(node);
+ var matches = true;
+ if (!this.matchesType(node)) {
+ matches = false;
}
+ if (matches && this.name) {
+ try {
+ var regExp = new RegExp(this.name.trim(), 'i');
+ matches = regExp.test(node.name);
+ if (!matches) {
+ var attrs = Object.values(node.attributes);
+ for (var idx = 0; idx < attrs.length; idx++) {
+ if ((matches = regExp.test(attrs[idx]))) {
+ break;
+ }
+ }
+ }
+ if (!matches) {
+ for (idx = 0; idx < node.roles.length; idx++) {
+ if ((matches = regExp.test(node.roles[idx]))) {
+ break;
+ }
+ }
+ }
+ } catch (err) { // if not valid regexp, still try normal matching
+ matches = node.name.indexOf(this.name.toLowerCase()) != -1;
+ if (!matches) {
+ var _attrs = Object.values(node.attributes);
+ for (var _idx = 0; _idx < _attrs.length; _idx++) {
+ var attr = _attrs[_idx].toLowerCase();
+ matches = true;
+ if ((matches = (attr.indexOf(this.name.toLowerCase()) != -1))) {
+ break;
+ }
+ }
+ }
+ if (!matches) {
+ for (_idx = 0; _idx < node.roles.length; _idx++) {
+ var role = node.roles[_idx].toLowerCase();
+ matches = true;
+ if ((matches = (role.indexOf(this.name.toLowerCase()) != -1))) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ return matches;
};
this.matchesType = function(node) {
@@ -51,12 +93,4 @@ function NodeFilter(name, data, master, ingest, coordinating, timestamp) {
node.coordinating && this.coordinating
);
};
-
- this.matchesName = function(name) {
- if (this.name) {
- return name.toLowerCase().indexOf(this.name.toLowerCase()) != -1;
- } else {
- return true;
- }
- };
}
diff --git a/test/models/overview/ClusterInitializingShards.scala b/test/models/overview/ClusterInitializingShards.scala
index 6e817b79..8a6a0007 100644
--- a/test/models/overview/ClusterInitializingShards.scala
+++ b/test/models/overview/ClusterInitializingShards.scala
@@ -12,6 +12,19 @@ object ClusterInitializingShards {
| "cluster_name" : "elasticsearch",
| "master_node" : "cPsT9o5FQ3WRnvqSTXHiVQ",
| "blocks" : { },
+ | "metadata" : {
+ | "indices" : {
+ | "hello" : {
+ | "settings" : {
+ | "index" : {
+ | "number_of_shards" : "1",
+ | "number_of_replicas" : "1"
+ | }
+ | },
+ | "aliases" : [ ]
+ | }
+ | }
+ | },
| "routing_table" : {
| "indices" : {
| "hello" : {
diff --git a/test/models/overview/ClusterRelocatingShards.scala b/test/models/overview/ClusterRelocatingShards.scala
index 8a0725b4..71724aeb 100644
--- a/test/models/overview/ClusterRelocatingShards.scala
+++ b/test/models/overview/ClusterRelocatingShards.scala
@@ -10,6 +10,19 @@ object ClusterRelocatingShards extends ClusterStub {
| "cluster_name" : "elasticsearch",
| "master_node" : "cPsT9o5FQ3WRnvqSTXHiVQ",
| "blocks" : { },
+ | "metadata" : {
+ | "indices" : {
+ | "hello" : {
+ | "settings" : {
+ | "index" : {
+ | "number_of_shards" : "1",
+ | "number_of_replicas" : "1"
+ | }
+ | },
+ | "aliases" : [ ]
+ | }
+ | }
+ | },
| "routing_table" : {
| "indices" : {
| "hello" : {
diff --git a/test/models/overview/ClusterWithData.scala b/test/models/overview/ClusterWithData.scala
index 50880ce8..69f4ffa5 100644
--- a/test/models/overview/ClusterWithData.scala
+++ b/test/models/overview/ClusterWithData.scala
@@ -20,6 +20,28 @@ trait ClusterWithData extends ClusterStub {
| }
| }
| },
+ | "metadata" : {
+ | "indices" : {
+ | "foo" : {
+ | "settings" : {
+ | "index" : {
+ | "number_of_shards" : "1",
+ | "number_of_replicas" : "1"
+ | }
+ | },
+ | "aliases" : [ ]
+ | },
+ | "bar" : {
+ | "settings" : {
+ | "index" : {
+ | "number_of_shards" : "1",
+ | "number_of_replicas" : "1"
+ | }
+ | },
+ | "aliases" : [ ]
+ | }
+ | }
+ | },
| "routing_table": {
| "indices": {
| "bar": {
diff --git a/test/models/overview/ClusterWithoutData.scala b/test/models/overview/ClusterWithoutData.scala
index f4756168..d1321705 100644
--- a/test/models/overview/ClusterWithoutData.scala
+++ b/test/models/overview/ClusterWithoutData.scala
@@ -16,6 +16,11 @@ object ClusterWithoutData extends ClusterStub {
| "indices":{
|
| }
+ | },
+ | "metadata":{
+ | "indices":{
+ |
+ | }
| }
|}
""".stripMargin