2 * Copyright 2010-2013 Ben Birch
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this software except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 (function( $, app, i18n ) {
18 var ui = app.ns("ui");
19 var services = app.ns("services");
21 // ( master ) master = true, data = true
22 // ( coordinator ) master = true, data = false
23 // ( worker ) master = false, data = true;
24 // ( client ) master = false, data = false;
27 function nodeSort_name(a, b) {
28 if (!(a.cluster && b.cluster)) {
31 return a.cluster.name.toString().localeCompare( b.cluster.name.toString() );
34 function nodeSort_addr( a, b ) {
35 if (!(a.cluster && b.cluster)) {
38 return a.cluster.transport_address.toString().localeCompare( b.cluster.transport_address.toString() );
41 function nodeSort_type( a, b ) {
42 if (!(a.cluster && b.cluster)) {
47 } else if( b.master_node ) {
49 } else if( a.data_node && !b.data_node ) {
51 } else if( b.data_node && !a.data_node ) {
54 return a.cluster.name.toString().localeCompare( b.cluster.name.toString() );
58 var NODE_SORT_TYPES = {
59 "Sort.ByName": nodeSort_name,
60 "Sort.ByAddress": nodeSort_addr,
61 "Sort.ByType": nodeSort_type
64 function nodeFilter_none( a ) {
68 function nodeFilter_clients( a ) {
69 return (a.master_node || a.data_node );
73 ui.ClusterOverview = ui.Page.extend({
75 cluster: null // (reqired) an instanceof app.services.Cluster
79 this.cluster = this.config.cluster;
80 this.prefs = services.Preferences.instance();
81 this._clusterState = this.config.clusterState;
82 this._clusterState.on("data", this.draw_handler );
83 this._refreshButton = new ui.RefreshButton({
84 onRefresh: this.refresh.bind(this),
85 onChange: function( btn ) {
86 if( btn.value === -1 ) {
91 var nodeSortPref = this.prefs.get("clusterOverview-nodeSort") || Object.keys(NODE_SORT_TYPES)[0];
92 this._nodeSort = NODE_SORT_TYPES[ nodeSortPref ];
93 this._nodeSortMenu = new ui.MenuButton({
94 label: i18n.text( "Preference.SortCluster" ),
95 menu: new ui.SelectMenuPanel({
97 items: Object.keys( NODE_SORT_TYPES ).map( function( k ) {
98 return { text: i18n.text( k ), value: k };
100 onSelect: function( panel, event ) {
101 this._nodeSort = NODE_SORT_TYPES[ event.value ];
102 this.prefs.set("clusterOverview-nodeSort", event.value );
107 this._indicesSort = this.prefs.get( "clusterOverview-indicesSort") || "desc";
108 this._indicesSortMenu = new ui.MenuButton({
109 label: i18n.text( "Preference.SortIndices" ),
110 menu: new ui.SelectMenuPanel({
111 value: this._indicesSort,
113 { value: "desc", text: i18n.text( "SortIndices.Descending" ) },
114 { value: "asc", text: i18n.text( "SortIndices.Ascending" ) } ],
115 onSelect: function( panel, event ) {
116 this._indicesSort = event.value;
117 this.prefs.set( "clusterOverview-indicesSort", this._indicesSort );
122 this._aliasRenderer = this.prefs.get( "clusterOverview-aliasRender" ) || "full";
123 this._aliasMenu = new ui.MenuButton({
124 label: i18n.text( "Preference.ViewAliases" ),
125 menu: new ui.SelectMenuPanel({
126 value: this._aliasRenderer,
128 { value: "full", text: i18n.text( "ViewAliases.Grouped" ) },
129 { value: "list", text: i18n.text( "ViewAliases.List" ) },
130 { value: "none", text: i18n.text( "ViewAliases.None" ) } ],
131 onSelect: function( panel, event ) {
132 this._aliasRenderer = event.value;
133 this.prefs.set( "clusterOverview-aliasRender", this._aliasRenderer );
138 this._indexFilter = new ui.TextField({
139 value: this.prefs.get("clusterOverview-indexFilter"),
140 placeholder: i18n.text( "Overview.IndexFilter" ),
141 onchange: function( indexFilter ) {
142 this.prefs.set("clusterOverview-indexFilter", indexFilter.val() );
146 this.el = $(this._main_template());
147 this.tablEl = this.el.find(".uiClusterOverview-table");
151 this._clusterState.removeObserver( "data", this.draw_handler );
153 refresh: function() {
154 this._refreshButton.disable();
155 this._clusterState.refresh();
157 draw_handler: function() {
158 var data = this._clusterState;
161 var indexFilterRe = new RegExp( this._indexFilter.val() );
162 indexFilter = function(s) { return indexFilterRe.test(s); };
164 indexFilter = function() { return true; };
166 var clusterState = data.clusterState;
167 var status = data.status;
168 var nodeStats = data.nodeStats;
169 var clusterNodes = data.clusterNodes;
173 var nodeIndices = {};
174 var indexIndices = {}, indexIndicesIndex = 0;
175 function newNode(n) {
179 master_node: clusterState.master_node === n
182 function newIndex(i) {
188 function getIndexForNode(n) {
189 return nodeIndices[n] = (n in nodeIndices) ? nodeIndices[n] : nodes.push(newNode(n)) - 1;
191 function getIndexForIndex(routings, i) {
192 var index = indexIndices[i] = (i in indexIndices) ?
193 (routings[indexIndices[i]] = routings[indexIndices[i]] || newIndex(i)) && indexIndices[i]
194 : ( ( routings[indexIndicesIndex] = newIndex(i) ) && indexIndicesIndex++ );
198 $.each(clusterNodes.nodes, function(name, node) {
199 getIndexForNode(name);
203 $.each(clusterState.routing_table.indices, function(name, index){
204 indexNames.push(name);
207 if (this._indicesSort === "desc") indexNames.reverse();
208 indexNames.filter( indexFilter ).forEach(function(name) {
209 var indexObject = clusterState.routing_table.indices[name];
210 $.each(indexObject.shards, function(name, shard) {
211 shard.forEach(function(replica){
212 var node = replica.node;
213 if(node === null) { node = "Unassigned"; }
214 var index = replica.index;
215 var shard = replica.shard;
216 var routings = nodes[getIndexForNode(node)].routings;
217 var indexIndex = getIndexForIndex(routings, index);
218 var replicas = routings[indexIndex].replicas;
219 if(node === "Unassigned" || !indexObject.shards[shard]) {
220 replicas.push({ replica: replica });
224 status: indexObject.shards[shard].filter(function(replica) {
225 return replica.node === node;
232 indices = indices.map(function(index){
236 metadata: clusterState.metadata.indices[index],
237 status: status.indices[index]
240 $.each(clusterState.metadata.indices, function(name, index) {
241 if(index.state === "close" && indexFilter( name )) {
250 nodes.forEach(function(node) {
251 node.stats = nodeStats.nodes[node.name];
252 var cluster = clusterNodes.nodes[node.name];
253 node.cluster = cluster || { name: "<unknown>" };
254 node.data_node = !( cluster && cluster.attributes && cluster.attributes.data === "false" );
255 for(var i = 0; i < indices.length; i++) {
256 node.routings[i] = node.routings[i] || { name: indices[i].name, replicas: [] };
257 node.routings[i].max_number_of_shards = indices[i].metadata.settings["index.number_of_shards"];
258 node.routings[i].open = indices[i].state === "open";
261 var aliasesIndex = {};
263 var indexClone = indices.map(function() { return false; });
264 $.each(clusterState.metadata.indices, function(name, index) {
265 index.aliases.forEach(function(alias) {
266 var aliasIndex = aliasesIndex[alias] = (alias in aliasesIndex) ? aliasesIndex[alias] : aliases.push( { name: alias, max: -1, min: 999, indices: [].concat(indexClone) }) - 1;
267 var indexIndex = indexIndices[name];
268 var aliasRow = aliases[aliasIndex];
269 aliasRow.min = Math.min(aliasRow.min, indexIndex);
270 aliasRow.max = Math.max(aliasRow.max, indexIndex);
271 aliasRow.indices[indexIndex] = indices[indexIndex];
274 cluster.aliases = aliases;
275 cluster.nodes = nodes
276 .filter( nodeFilter_none )
277 .sort( this._nodeSort );
278 indices.unshift({ name: null });
279 this._drawNodesView( cluster, indices );
280 this._refreshButton.enable();
282 _drawNodesView: function( cluster, indices ) {
283 this._nodesView && this._nodesView.remove();
284 this._nodesView = new ui.NodesView({
285 onRedraw: function() {
288 interactive: ( this._refreshButton.value === -1 ),
289 aliasRenderer: this._aliasRenderer,
290 cluster: this.cluster,
296 this._nodesView.attach( this.tablEl );
298 _main_template: function() {
299 return { tag: "DIV", id: this.id(), cls: "uiClusterOverview", children: [
301 label: i18n.text("Overview.PageTitle"),
304 this._indicesSortMenu,
312 { tag: "DIV", cls: "uiClusterOverview-table" }
317 })( this.jQuery, this.app, this.i18n );