elasticsearch-paramedic-rack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +24 -0
- data/elasticsearch-paramedic-rack.gemspec +24 -0
- data/lib/elasticsearch-paramedic-rack.rb +10 -0
- data/lib/elasticsearch-paramedic-rack/middelware.rb +23 -0
- data/lib/elasticsearch-paramedic-rack/version.rb +7 -0
- data/public/elasticsearch-paramedic/audio/alert-green.mp3 +0 -0
- data/public/elasticsearch-paramedic/audio/alert-red.mp3 +0 -0
- data/public/elasticsearch-paramedic/audio/alert-yellow.mp3 +0 -0
- data/public/elasticsearch-paramedic/css/app.css +312 -0
- data/public/elasticsearch-paramedic/css/libs/cubism.css +57 -0
- data/public/elasticsearch-paramedic/css/libs/style.css +499 -0
- data/public/elasticsearch-paramedic/img/apple-touch-icon.png +0 -0
- data/public/elasticsearch-paramedic/img/glyphicons-halflings-white.png +0 -0
- data/public/elasticsearch-paramedic/img/glyphicons-halflings.png +0 -0
- data/public/elasticsearch-paramedic/img/spinner.gif +0 -0
- data/public/elasticsearch-paramedic/index.html +196 -0
- data/public/elasticsearch-paramedic/js/app.js +474 -0
- data/public/elasticsearch-paramedic/js/cubism.js +109 -0
- data/public/elasticsearch-paramedic/js/libs/colorbrewer.min.js +1 -0
- data/public/elasticsearch-paramedic/js/libs/cubism.elasticsearch.js +273 -0
- data/public/elasticsearch-paramedic/js/libs/cubism.v1.js +986 -0
- data/public/elasticsearch-paramedic/js/libs/cubism.v1.min.js +1 -0
- data/public/elasticsearch-paramedic/js/libs/d3.v2.min.js +4 -0
- data/public/elasticsearch-paramedic/js/libs/ember-0.9.8.js +20150 -0
- data/public/elasticsearch-paramedic/js/libs/ember-0.9.8.min.js +14 -0
- data/public/elasticsearch-paramedic/js/libs/jquery-1.7.2.min.js +4 -0
- data/test/middelware_test.rb +63 -0
- data/test/test_helper.rb +12 -0
- metadata +134 -0
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,196 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Paramedic</title>
|
5
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
6
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
7
|
+
<link rel="apple-touch-icon" href="img/apple-touch-icon.png">
|
8
|
+
<link rel="stylesheet" href="css/libs/style.css">
|
9
|
+
<link rel="stylesheet" href="css/libs/cubism.css">
|
10
|
+
<link rel="stylesheet" href="css/app.css">
|
11
|
+
<!--[if lt IE 9]><script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
|
12
|
+
</head>
|
13
|
+
<body>
|
14
|
+
<header class="clearfix">
|
15
|
+
<script type="text/x-handlebars">
|
16
|
+
{{#with App.cluster}}
|
17
|
+
|
18
|
+
<section {{bindAttr class=":cluster_name status"}}>
|
19
|
+
<span class="label">Cluster Name</span>
|
20
|
+
<h1>{{cluster_name}}</h1>
|
21
|
+
|
22
|
+
<p>
|
23
|
+
<span class="label">Status</span>
|
24
|
+
<span {{bindAttr class=":status status"}}>{{status}}</span>
|
25
|
+
<span class="label">Nodes</span>
|
26
|
+
{{number_of_nodes}}
|
27
|
+
<span class="label">Docs</span>
|
28
|
+
{{#bind docs_count}}{{number_with_delimiter docs_count}}{{/bind}}
|
29
|
+
</p>
|
30
|
+
</section>
|
31
|
+
|
32
|
+
<section class="shards">
|
33
|
+
<p><span class="label">Shards</span></p>
|
34
|
+
<p><span class="label darker">Primary</span>
|
35
|
+
{{active_primary_shards}}</p>
|
36
|
+
<p><span class="label darker">Relocating</span>
|
37
|
+
{{relocating_shards}}</p>
|
38
|
+
</section>
|
39
|
+
|
40
|
+
<section class="shards">
|
41
|
+
<p><span class="label"> </span></p>
|
42
|
+
<p><span class="label darker">Initializing</span>
|
43
|
+
{{initializing_shards}}</p>
|
44
|
+
<p><span class="label darker">Unassigned</span>
|
45
|
+
{{unassigned_shards}}</p>
|
46
|
+
</section>
|
47
|
+
|
48
|
+
{{#with App }}
|
49
|
+
<section {{bindAttr class=":endpoint refresh_allowed:polling-active refreshing:polling-in-progress"}}>
|
50
|
+
{{/with}}
|
51
|
+
<p title="Change ElasticSearch URL">
|
52
|
+
<span class="label">URL</span>
|
53
|
+
{{view Ember.TextField valueBinding="App.elasticsearch_url" id="elasticsearch_url"}}
|
54
|
+
</p>
|
55
|
+
|
56
|
+
<p class="refresh">
|
57
|
+
<span class="icon-refresh"></span>
|
58
|
+
<span class="refresh-label">Refresh every</span>
|
59
|
+
<span class="refresh-controls">
|
60
|
+
{{view Ember.Select
|
61
|
+
contentBinding="App.refresh_intervals"
|
62
|
+
selectionBinding="App.refresh_interval"
|
63
|
+
optionLabelPath="content.label"
|
64
|
+
optionValuePath="content.value"}}
|
65
|
+
<button {{action "toggle" target="App.toggleRefreshAllowedButton"}}>{{App.toggleRefreshAllowedButton.text}}</button>
|
66
|
+
</span>
|
67
|
+
</p>
|
68
|
+
<p class="alerts">
|
69
|
+
{{view Ember.Checkbox id="sound-enabled" checkedBinding="App.sounds_enabled"}}
|
70
|
+
<label for="sound-enabled" class="dimmed">Sounds?</label>
|
71
|
+
</p>
|
72
|
+
</section>
|
73
|
+
|
74
|
+
|
75
|
+
{{/with}}
|
76
|
+
</script>
|
77
|
+
</header>
|
78
|
+
|
79
|
+
<section id="cubism">
|
80
|
+
<script type="text/x-handlebars">
|
81
|
+
<h2 class="label clear">
|
82
|
+
Stats
|
83
|
+
<small><a {{action "toggle" target="App.toggleChart"}}>{{App.toggleChart.text}}</a></small>
|
84
|
+
</h2>
|
85
|
+
</script>
|
86
|
+
<div id="chart"></div>
|
87
|
+
</section>
|
88
|
+
|
89
|
+
<h2 class="label">Nodes</h2>
|
90
|
+
<script type="text/x-handlebars">
|
91
|
+
<div id="nodes" class="clearfix">
|
92
|
+
{{#each App.nodes}}
|
93
|
+
<div {{bindAttr class=":node master"}}>
|
94
|
+
<h3><span {{bindAttr class="master:icon-star"}}></span> {{name}}</h3>
|
95
|
+
<div class="meta">
|
96
|
+
<p><span class="label">ID: </span>{{id}}</p>
|
97
|
+
<p><span class="label">IP: </span>{{http_address}}</p>
|
98
|
+
<p><span class="label">Host: </span>{{hostname}}</p>
|
99
|
+
<p><span class="label">Load: </span>{{load}}</p>
|
100
|
+
<p><span class="label">Size: </span>{{disk}}</p>
|
101
|
+
<p><span class="label">Docs: </span>{{#bind docs}}{{number_with_delimiter docs}}{{/bind}}</p>
|
102
|
+
<p><span class="label">Heap: </span>{{jvm_heap_used}}
|
103
|
+
<small class="dimmed" title="Heap max">/{{jvm_heap_max}}</small></p>
|
104
|
+
</div>
|
105
|
+
</div>
|
106
|
+
{{/each}}
|
107
|
+
</div>
|
108
|
+
</script>
|
109
|
+
<br>
|
110
|
+
|
111
|
+
<h2 class="label">Indices</h2>
|
112
|
+
<script type="text/x-handlebars">
|
113
|
+
<div id="indices">
|
114
|
+
{{#each index in App.indices.sorted}}
|
115
|
+
{{#with index}}
|
116
|
+
<div {{bindAttr class=":index :clearfix state show_detail:expanded"}}>
|
117
|
+
<div class="basic-info clearfix">
|
118
|
+
<h3><a {{bindAttr href="url"}} title="Browse Index">{{name}}</a></h3>
|
119
|
+
|
120
|
+
<div class="buttons">
|
121
|
+
{{#unless closed}}
|
122
|
+
<button {{action "showDetail" target="App.indices"}}>
|
123
|
+
{{#if show_detail}}Hide details{{else}}Show details{{/if}}
|
124
|
+
</button>
|
125
|
+
{{/unless}}
|
126
|
+
</div>
|
127
|
+
|
128
|
+
<div class="shards">
|
129
|
+
{{#each shards}}
|
130
|
+
<div {{bindAttr class=":shard primary state recovery.stage" title="state"}}>
|
131
|
+
{{name}}
|
132
|
+
</div>
|
133
|
+
{{/each}}
|
134
|
+
</div>
|
135
|
+
|
136
|
+
<div class="meta">
|
137
|
+
<p>
|
138
|
+
{{settings.number_of_shards}} shards /
|
139
|
+
{{settings.number_of_replicas}} replicas /
|
140
|
+
{{#bind docs}}{{number_with_delimiter docs}}{{/bind}} docs /
|
141
|
+
{{size}} /
|
142
|
+
{{indexing.index_time}} indexing /
|
143
|
+
{{search.query_time}} querying /
|
144
|
+
{{state}}
|
145
|
+
</p>
|
146
|
+
</div>
|
147
|
+
</div>
|
148
|
+
|
149
|
+
<!-- Shard Allocation -->
|
150
|
+
{{#if show_detail}}
|
151
|
+
<div class="extra-info shards clearfix">
|
152
|
+
{{#unless show_detail_loaded}}
|
153
|
+
<div class="loading">Waiting for data...</div>
|
154
|
+
{{/unless}}
|
155
|
+
{{#each nodes}}
|
156
|
+
<div class="node">
|
157
|
+
<h3>{{name}} <small>{{hostname}}</small></h3>
|
158
|
+
<div class="clearfix">
|
159
|
+
{{#each shards}}
|
160
|
+
<div {{bindAttr class=":shard primary state recovery.stage" title="state"}}>
|
161
|
+
<h3>{{name}}</h3>
|
162
|
+
<div class="meta">
|
163
|
+
<p>{{state}}/{{recovery.stage}}</p>
|
164
|
+
<p>
|
165
|
+
{{recovery.time}}
|
166
|
+
{{recovery.size}}
|
167
|
+
</p>
|
168
|
+
</div>
|
169
|
+
</div>
|
170
|
+
{{/each}}
|
171
|
+
</div>
|
172
|
+
</div>
|
173
|
+
{{/each}}
|
174
|
+
</div>
|
175
|
+
{{/if}}
|
176
|
+
</div>
|
177
|
+
{{/with}}
|
178
|
+
{{/each}}
|
179
|
+
</ul>
|
180
|
+
</script>
|
181
|
+
|
182
|
+
<audio id="alert-green" src="audio/alert-green.mp3"></audio>
|
183
|
+
<audio id="alert-yellow" src="audio/alert-yellow.mp3"></audio>
|
184
|
+
<audio id="alert-red" src="audio/alert-red.mp3"></audio>
|
185
|
+
|
186
|
+
<script src="js/libs/jquery-1.7.2.min.js"></script>
|
187
|
+
<!-- <script src="js/libs/ember-0.9.8.min.js"></script> -->
|
188
|
+
<script src="js/libs/ember-0.9.8.js"></script>
|
189
|
+
<script src="js/libs/colorbrewer.min.js"></script>
|
190
|
+
<script src="js/libs/d3.v2.min.js"></script>
|
191
|
+
<script src="js/libs/cubism.v1.js"></script>
|
192
|
+
<script src="js/libs/cubism.elasticsearch.js"></script>
|
193
|
+
<script src="js/app.js"></script>
|
194
|
+
<script src="js/cubism.js"></script>
|
195
|
+
</body>
|
196
|
+
</html>
|
@@ -0,0 +1,474 @@
|
|
1
|
+
function l(m) { Ember.Logger.log(m); }
|
2
|
+
|
3
|
+
var App = Em.Application.create({
|
4
|
+
name: "Paramedic",
|
5
|
+
|
6
|
+
ready: function() {
|
7
|
+
l(App.name + ' (re)loaded.')
|
8
|
+
App.__initialize_page()
|
9
|
+
App.__perform_refresh()
|
10
|
+
App.__initialize_cubism()
|
11
|
+
return this._super()
|
12
|
+
},
|
13
|
+
|
14
|
+
elasticsearch_url: function() {
|
15
|
+
var location = window.location
|
16
|
+
return (/_plugin/.test(location.href.toString())) ? location.protocol + "//" + location.host : "http://localhost:9200"
|
17
|
+
}(),
|
18
|
+
|
19
|
+
refresh_intervals : Ember.ArrayController.create({
|
20
|
+
content: [
|
21
|
+
{label: '1 sec', value: 1000},
|
22
|
+
{label: '5 sec', value: 5000},
|
23
|
+
{label: '15 sec', value: 15*1000},
|
24
|
+
{label: '1 min', value: 60*1000},
|
25
|
+
{label: '5 min', value: 5*60*1000},
|
26
|
+
{label: '15 min', value: 15*60*1000}
|
27
|
+
]
|
28
|
+
}),
|
29
|
+
|
30
|
+
refresh_allowed: true,
|
31
|
+
sounds_enabled: false,
|
32
|
+
|
33
|
+
__perform_refresh: function() {
|
34
|
+
App.cluster.__perform_refresh()
|
35
|
+
App.nodes.__perform_refresh()
|
36
|
+
App.indices.__perform_refresh()
|
37
|
+
},
|
38
|
+
|
39
|
+
__initialize_cubism: function() {
|
40
|
+
App.Cubism.setup()
|
41
|
+
},
|
42
|
+
|
43
|
+
__initialize_page: function() {
|
44
|
+
$("link[rel=apple-touch-icon]").attr("href", App.apple_touch_icon_b64)
|
45
|
+
}
|
46
|
+
});
|
47
|
+
|
48
|
+
App.refresh_interval = App.refresh_intervals.toArray()[1]
|
49
|
+
|
50
|
+
// ===== Models ===================================================================================
|
51
|
+
|
52
|
+
App.Cluster = Ember.Object.extend({
|
53
|
+
});
|
54
|
+
|
55
|
+
App.Node = Ember.Object.extend({
|
56
|
+
});
|
57
|
+
|
58
|
+
App.Index = Ember.Object.extend({
|
59
|
+
url: function() {
|
60
|
+
return App.elasticsearch_url + '/' + this.name + '/_search?pretty'
|
61
|
+
}.property("name").cacheable(),
|
62
|
+
|
63
|
+
closed: function() {
|
64
|
+
return (this.state && this.state == 'close')
|
65
|
+
}.property("state").cacheable()
|
66
|
+
});
|
67
|
+
|
68
|
+
App.Index.Shard = Ember.Object.extend({
|
69
|
+
});
|
70
|
+
|
71
|
+
// ===== Controllers ==============================================================================
|
72
|
+
|
73
|
+
App.cluster = Ember.Object.create({
|
74
|
+
content: App.Cluster.create({}),
|
75
|
+
|
76
|
+
refresh: function() {
|
77
|
+
clearTimeout(App.cluster.poller)
|
78
|
+
setTimeout(function() { App.set("refreshing", false) }, 1000)
|
79
|
+
App.cluster.poller = setTimeout( function() { App.cluster.__perform_refresh() }, App.refresh_interval.value )
|
80
|
+
},
|
81
|
+
|
82
|
+
__perform_refresh: function() {
|
83
|
+
if (!App.refresh_allowed) { return }
|
84
|
+
var self = this;
|
85
|
+
|
86
|
+
var __load_cluster_info = function(data) {
|
87
|
+
App.cluster.setProperties(data)
|
88
|
+
App.cluster.refresh();
|
89
|
+
}
|
90
|
+
|
91
|
+
App.set("refreshing", true)
|
92
|
+
$.getJSON(App.elasticsearch_url+"/_cluster/health", __load_cluster_info);
|
93
|
+
}
|
94
|
+
});
|
95
|
+
|
96
|
+
App.nodes = Ember.ArrayController.create({
|
97
|
+
content: [],
|
98
|
+
|
99
|
+
contains: function(item) {
|
100
|
+
return (Ember.typeOf(item) == 'string') ? this.mapProperty('id').contains(item) : this._super();
|
101
|
+
},
|
102
|
+
|
103
|
+
refresh: function() {
|
104
|
+
clearTimeout(App.nodes.poller)
|
105
|
+
setTimeout(function() { App.set("refreshing", false) }, 1000)
|
106
|
+
App.nodes.poller = setTimeout( function() { App.nodes.__perform_refresh() }, App.refresh_interval.value )
|
107
|
+
},
|
108
|
+
|
109
|
+
__perform_refresh: function() {
|
110
|
+
if (!App.refresh_allowed) { return }
|
111
|
+
var self = this;
|
112
|
+
|
113
|
+
var __load_nodes_info = function(data) {
|
114
|
+
for (var node_id in data.nodes) {
|
115
|
+
if ( !self.contains(node_id) ) self.addObject(App.Node.create({ id: node_id }))
|
116
|
+
var node = self.findProperty("id", node_id)
|
117
|
+
.set("name", data.nodes[node_id]['name'])
|
118
|
+
.set("hostname", data.nodes[node_id]['hostname'])
|
119
|
+
.set("http_address", data.nodes[node_id]['http_address'])
|
120
|
+
.set("jvm_heap_max", data.nodes[node_id]['jvm']['mem']['heap_max'])
|
121
|
+
.set("start_time", data.nodes[node_id]['jvm']['start_time'])
|
122
|
+
}
|
123
|
+
|
124
|
+
// Remove missing nodes from the collection
|
125
|
+
// TODO: Use model instance identity, contains(), etc
|
126
|
+
//
|
127
|
+
self.forEach(function(item) {
|
128
|
+
var loc = self.content.length || 0
|
129
|
+
while(--loc >= 0) {
|
130
|
+
var curObject = self.content.objectAt(loc)
|
131
|
+
if ( item && !Ember.keys(data.nodes).contains(item.id) && curObject.id === item.id) {
|
132
|
+
self.content.removeAt(loc)
|
133
|
+
}
|
134
|
+
}
|
135
|
+
})
|
136
|
+
|
137
|
+
App.nodes.refresh();
|
138
|
+
};
|
139
|
+
|
140
|
+
var __load_nodes_stats = function(data) {
|
141
|
+
for (var node_id in data.nodes) {
|
142
|
+
var node = self.findProperty("id", node_id)
|
143
|
+
if (node) {
|
144
|
+
node
|
145
|
+
.set("disk", data.nodes[node_id]['indices']['store']['size'])
|
146
|
+
.set("docs", data.nodes[node_id]['indices']['docs']['count'])
|
147
|
+
.set("load", data.nodes[node_id]['os']['load_average'][0].toFixed(3))
|
148
|
+
.set("cpu", data.nodes[node_id]['process']['cpu']['percent'])
|
149
|
+
.set("jvm_heap_used", data.nodes[node_id]['jvm']['mem']['heap_used'])
|
150
|
+
}
|
151
|
+
}
|
152
|
+
};
|
153
|
+
|
154
|
+
App.set("refreshing", true)
|
155
|
+
$.getJSON(App.elasticsearch_url+"/_cluster/nodes?jvm", __load_nodes_info);
|
156
|
+
$.getJSON(App.elasticsearch_url+"/_cluster/nodes/stats?indices&os&process&jvm", __load_nodes_stats);
|
157
|
+
}
|
158
|
+
});
|
159
|
+
|
160
|
+
App.indices = Ember.ArrayController.create({
|
161
|
+
content: [],
|
162
|
+
|
163
|
+
contains: function(item) {
|
164
|
+
return (Ember.typeOf(item) == 'string') ? this.mapProperty('name').contains(item) : this._super();
|
165
|
+
},
|
166
|
+
|
167
|
+
refresh: function() {
|
168
|
+
clearTimeout(App.indices.poller)
|
169
|
+
setTimeout(function() { App.set("refreshing", false) }, 1000)
|
170
|
+
App.indices.poller = setTimeout( function() { App.indices.__perform_refresh() }, App.refresh_interval.value )
|
171
|
+
},
|
172
|
+
|
173
|
+
sorted: function() {
|
174
|
+
return this.get("content")
|
175
|
+
.toArray()
|
176
|
+
.sort(function(a,b) { if (a.name < b.name) return -1; if (a.name > b.name) return 1; return 0; })
|
177
|
+
}.property("content.@each").cacheable(),
|
178
|
+
|
179
|
+
showDetail: function(event) {
|
180
|
+
// l(event.context.name)
|
181
|
+
// l(this)
|
182
|
+
event.context.toggleProperty("show_detail")
|
183
|
+
},
|
184
|
+
|
185
|
+
__perform_refresh: function() {
|
186
|
+
if (!App.refresh_allowed) { return }
|
187
|
+
var self = this;
|
188
|
+
|
189
|
+
var __load_cluster_state = function(data) {
|
190
|
+
for (var index_name in data.metadata.indices) {
|
191
|
+
// Mark master node
|
192
|
+
//
|
193
|
+
var master_node = App.nodes.content.findProperty("id", data.master_node)
|
194
|
+
if (master_node) master_node.set("master", true)
|
195
|
+
|
196
|
+
// Create or find an index
|
197
|
+
//
|
198
|
+
if ( !self.contains(index_name) ) self.addObject(App.Index.create({ name: index_name }))
|
199
|
+
var index = self.findProperty("name", index_name)
|
200
|
+
|
201
|
+
// Update index properties
|
202
|
+
//
|
203
|
+
index
|
204
|
+
.set("state", data.metadata.indices[index_name]['state'])
|
205
|
+
|
206
|
+
.set("settings", Ember.Object.create({
|
207
|
+
number_of_replicas: data.metadata.indices[index_name]['settings']['index.number_of_replicas'],
|
208
|
+
number_of_shards: data.metadata.indices[index_name]['settings']['index.number_of_shards']
|
209
|
+
}))
|
210
|
+
|
211
|
+
.set("aliases", data.metadata.indices[index_name]['aliases'])
|
212
|
+
|
213
|
+
// Shards
|
214
|
+
//
|
215
|
+
var shards = [],
|
216
|
+
primaries = [],
|
217
|
+
replicas = [],
|
218
|
+
unassigned = []
|
219
|
+
|
220
|
+
index
|
221
|
+
.set("shards", function() {
|
222
|
+
if (data.routing_table.indices[index_name]) {
|
223
|
+
|
224
|
+
for (var shard_name in data.routing_table.indices[index_name]['shards']) {
|
225
|
+
|
226
|
+
data.routing_table.indices[index_name]['shards'][shard_name].forEach(function(s) {
|
227
|
+
|
228
|
+
var shard = App.Index.Shard.create({name: shard_name})
|
229
|
+
shard.set("state", s.state)
|
230
|
+
.set("primary", s.primary)
|
231
|
+
.set("index", s.index)
|
232
|
+
.set("node_id", s.node)
|
233
|
+
.set("relocating_node_id", s.relocating_node)
|
234
|
+
|
235
|
+
if (s.primary) primaries .addObject(shard)
|
236
|
+
if (!s.primary && s.node) replicas .addObject(shard)
|
237
|
+
if (!s.primary && !s.node) unassigned.addObject(shard)
|
238
|
+
});
|
239
|
+
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
// Sort unassingned shards to series [0 .. n, 0 .. n]
|
244
|
+
// [0, 0, 1, 1, 2, 2] becomes: [0, 1, 2, 0, 1, 2]
|
245
|
+
//
|
246
|
+
var unassigned_sorted = []
|
247
|
+
unassigned_sorted.length = unassigned.length
|
248
|
+
|
249
|
+
var num_shards = primaries.length,
|
250
|
+
num_replicas = unassigned.length/num_shards;
|
251
|
+
|
252
|
+
for (var i = 0; i < num_shards; i++) {
|
253
|
+
// Create slices: [0, 0]; [1, 1]; [2, 2]
|
254
|
+
unassigned.slice(i*num_replicas, i*num_replicas+num_replicas).forEach(function(item,index) {
|
255
|
+
// Position for first slices: 0, 3
|
256
|
+
// Position for second slices: 1, 4
|
257
|
+
// Position for third slices: 2, 5
|
258
|
+
var position = i + num_shards * index
|
259
|
+
unassigned_sorted[position] = item
|
260
|
+
})
|
261
|
+
};
|
262
|
+
|
263
|
+
unassigned_sorted = unassigned_sorted.filter(function(i){return i != null})
|
264
|
+
return shards.concat(primaries, replicas, unassigned_sorted)
|
265
|
+
}())
|
266
|
+
|
267
|
+
if (index.show_detail) {
|
268
|
+
index.set("nodes", function() {
|
269
|
+
var nodes = []
|
270
|
+
if (data.routing_table.indices[index_name]) {
|
271
|
+
for (var shard_name in data.routing_table.indices[index_name]['shards']) {
|
272
|
+
|
273
|
+
data.routing_table.indices[index_name]['shards'][shard_name].forEach(function(shard_data) {
|
274
|
+
if (shard_data.node) {
|
275
|
+
|
276
|
+
// Find the node
|
277
|
+
// var node = App.nodes.content.findProperty("id", shard_data.node)
|
278
|
+
var node = nodes.findProperty("id", shard_data.node)
|
279
|
+
if (!node) {
|
280
|
+
var node = App.Node.create( App.nodes.content.findProperty("id", shard_data.node) )
|
281
|
+
nodes.addObject(node)
|
282
|
+
}
|
283
|
+
|
284
|
+
// Initialize node.shards
|
285
|
+
if (node && !node.shards) node.set("shards", [])
|
286
|
+
|
287
|
+
// Find shard in index.shards
|
288
|
+
var shard = index.shards.find(function(item) {
|
289
|
+
return item.name == shard_data.shard && item.node_id == shard_data.node && item.index == shard_data.index
|
290
|
+
})
|
291
|
+
|
292
|
+
// Remove shard from node.shards
|
293
|
+
node.shards.forEach(function(item, index) {
|
294
|
+
if (item.name == shard_data.shard && item.node_id == shard_data.node && item.index == shard_data.index) {
|
295
|
+
node.shards.removeAt(index)
|
296
|
+
}
|
297
|
+
})
|
298
|
+
|
299
|
+
// Add (possibly updated) shard back into collection
|
300
|
+
if (shard) { node.shards.addObject(shard) }
|
301
|
+
|
302
|
+
node.set("shards", node.shards.sort(function(a,b) { return a.name > b.name; }))
|
303
|
+
}
|
304
|
+
});
|
305
|
+
};
|
306
|
+
}
|
307
|
+
index.set("show_detail_loaded", true)
|
308
|
+
return nodes
|
309
|
+
}())
|
310
|
+
}
|
311
|
+
|
312
|
+
// Remove deleted indices from the collection
|
313
|
+
// TODO: Use model instance identity for this
|
314
|
+
//
|
315
|
+
self.forEach(function(item) {
|
316
|
+
// console.log(item.name)
|
317
|
+
var loc = self.content.length || 0
|
318
|
+
while(--loc >= 0) {
|
319
|
+
var curObject = self.content.objectAt(loc)
|
320
|
+
if ( item && !Ember.keys(data.metadata.indices).contains(item.name) && curObject.name === item.name) {
|
321
|
+
self.content.removeAt(loc)
|
322
|
+
}
|
323
|
+
}
|
324
|
+
})
|
325
|
+
|
326
|
+
}
|
327
|
+
};
|
328
|
+
|
329
|
+
var __load_indices_stats = function(data) {
|
330
|
+
App.cluster.set("docs_count",
|
331
|
+
data._all.primaries.docs ? data._all.primaries.docs.count : 0)
|
332
|
+
|
333
|
+
for (var index_name in data._all.indices) {
|
334
|
+
var index = self.findProperty("name", index_name)
|
335
|
+
if (!index) continue
|
336
|
+
|
337
|
+
index
|
338
|
+
.set("size", data._all.indices[index_name]['primaries']['store']['size'])
|
339
|
+
.set("size_in_bytes", data._all.indices[index_name]['primaries']['store']['size_in_bytes'])
|
340
|
+
.set("docs", data._all.indices[index_name]['primaries']['docs']['count'])
|
341
|
+
.set("indexing", data._all.indices[index_name]['primaries']['indexing'])
|
342
|
+
.set("search", data._all.indices[index_name]['primaries']['search'])
|
343
|
+
.set("get", data._all.indices[index_name]['primaries']['get'])
|
344
|
+
}
|
345
|
+
};
|
346
|
+
|
347
|
+
var __load_indices_status = function(data) {
|
348
|
+
for (var index_name in data.indices) {
|
349
|
+
var index = self.findProperty("name", index_name)
|
350
|
+
if (!index) continue
|
351
|
+
if (!index.show_detail) continue
|
352
|
+
|
353
|
+
for (var shard_name in data.indices[index_name]['shards']) {
|
354
|
+
// var shard = index.shards.findProperty("name", shard_name)
|
355
|
+
|
356
|
+
data.indices[index_name]['shards'][shard_name].forEach(function(shard_data) {
|
357
|
+
var shard = index.shards.find(function(item) {
|
358
|
+
return item.name == shard_name && item.node_id == shard_data['routing']['node']
|
359
|
+
})
|
360
|
+
// if (!shard) continue
|
361
|
+
if (shard) {
|
362
|
+
|
363
|
+
// l(shard_data)
|
364
|
+
shard
|
365
|
+
.set("size", shard_data.index.size)
|
366
|
+
// .set("docs", shard_data.docs.num_docs)
|
367
|
+
shard
|
368
|
+
.set("recovery", function() {
|
369
|
+
var recovery_type = shard_data['peer_recovery'] ? 'peer_recovery' : 'gateway_recovery'
|
370
|
+
|
371
|
+
return {
|
372
|
+
stage: shard_data[recovery_type].stage,
|
373
|
+
time: shard_data[recovery_type].time,
|
374
|
+
progress: shard_data[recovery_type].index.progress,
|
375
|
+
size: shard_data[recovery_type].index.size,
|
376
|
+
reused_size: shard_data[recovery_type].index.reused_size
|
377
|
+
}
|
378
|
+
}())
|
379
|
+
}
|
380
|
+
});
|
381
|
+
}
|
382
|
+
}
|
383
|
+
};
|
384
|
+
|
385
|
+
App.set("refreshing", true)
|
386
|
+
$.getJSON(App.elasticsearch_url+"/_cluster/state", __load_cluster_state);
|
387
|
+
$.getJSON(App.elasticsearch_url+"/_stats", __load_indices_stats);
|
388
|
+
$.getJSON(App.elasticsearch_url+"/_status?recovery=true", __load_indices_status);
|
389
|
+
|
390
|
+
// Schedule next run
|
391
|
+
//
|
392
|
+
App.indices.refresh();
|
393
|
+
}
|
394
|
+
});
|
395
|
+
|
396
|
+
// ===== Views ==================================================================================
|
397
|
+
|
398
|
+
App.toggleRefreshAllowedButton = Ember.View.create({
|
399
|
+
text: 'Stop',
|
400
|
+
|
401
|
+
toggle: function(event) {
|
402
|
+
this.set("text", ( App.refresh_allowed == true ) ? 'Start' : 'Stop')
|
403
|
+
App.toggleProperty("refresh_allowed")
|
404
|
+
}
|
405
|
+
});
|
406
|
+
|
407
|
+
App.toggleChart = Ember.View.create({
|
408
|
+
text: 'Hide',
|
409
|
+
|
410
|
+
toggle: function(event) {
|
411
|
+
var chart = $("#chart"),
|
412
|
+
visible = chart.is(":visible")
|
413
|
+
|
414
|
+
this.set("text", visible ? 'Show' : 'Hide')
|
415
|
+
visible ? chart.hide('fast') : chart.show('fast')
|
416
|
+
}
|
417
|
+
});
|
418
|
+
|
419
|
+
// ===== Observers ==============================================================================
|
420
|
+
|
421
|
+
App.addObserver('elasticsearch_url', function(event) {
|
422
|
+
// TODO: Use the `blur` event, so we're not trying to load partial URLs
|
423
|
+
Ember.Logger.log("ElasticSearch URL changed to " + this.get("elasticsearch_url"))
|
424
|
+
App.cluster.set("content", App.Cluster.create({}))
|
425
|
+
App.nodes.set("content", [])
|
426
|
+
App.indices.set("content", [])
|
427
|
+
App.ready()
|
428
|
+
App.Cubism.reset()
|
429
|
+
});
|
430
|
+
|
431
|
+
App.addObserver('refresh_interval', function() {
|
432
|
+
Ember.Logger.log("Refresh interval changed to " + App.refresh_interval.label)
|
433
|
+
App.ready()
|
434
|
+
});
|
435
|
+
|
436
|
+
App.addObserver('refresh_allowed', function() {
|
437
|
+
App.refresh_allowed ? App.Cubism.start() : App.Cubism.stop()
|
438
|
+
App.__perform_refresh()
|
439
|
+
});
|
440
|
+
|
441
|
+
App.nodes.addObserver('@each.name', function() {
|
442
|
+
// Wait until we have node names...
|
443
|
+
if ( !App.nodes.everyProperty("name") ) return;
|
444
|
+
|
445
|
+
Ember.Logger.log("Nodes changed to: " + App.nodes.mapProperty("name").join("; "))
|
446
|
+
App.Cubism.reset()
|
447
|
+
});
|
448
|
+
|
449
|
+
App.cluster.addObserver('cluster_name', function() {
|
450
|
+
$('title').text('Paramedic | ' + this.get('cluster_name'))
|
451
|
+
});
|
452
|
+
|
453
|
+
App.cluster.addObserver('status', function() {
|
454
|
+
if (App.get("sounds_enabled")) {
|
455
|
+
// FIXME: When running as a plugin, audio won't play again when `var a = $('#alert-'+this.get("status"))[0]`
|
456
|
+
var a = new Audio('audio/alert-'+this.get("status")+'.mp3')
|
457
|
+
a.volume=0.7
|
458
|
+
a.play()
|
459
|
+
}
|
460
|
+
});
|
461
|
+
|
462
|
+
// ===== Helpers ================================================================================
|
463
|
+
|
464
|
+
Handlebars.registerHelper('number_with_delimiter', function(property) {
|
465
|
+
var delimiter = ' '
|
466
|
+
, value = (isNaN(this)) ? Ember.getPath(this, property) : this.toString();
|
467
|
+
// console.log(this, property, value)
|
468
|
+
// Credit: http://stackoverflow.com/a/2254896/95696
|
469
|
+
return value ? value.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1"+delimiter) : value
|
470
|
+
});
|
471
|
+
|
472
|
+
// ===== Varia ==================================================================================
|
473
|
+
|
474
|
+
App.apple_touch_icon_b64 = "data:image/png;base64,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";
|