@ -0,0 +1,3 @@ |
||||
<div> |
||||
Nothing to see here folks |
||||
</div> |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 26 KiB |
@ -0,0 +1,58 @@ |
||||
<kibana-panel ng-controller='bbuzz' ng-init="init()"> |
||||
|
||||
<table class="table table-condensed" style="vertical-align: middle"> |
||||
<tr ng-style="cluster_color(cluster.status)"> |
||||
<td> |
||||
<span style="font-size:2.5em;line-height:50px"> |
||||
<strong>Cluster:</strong> {{cluster.cluster_name}} |
||||
</span> |
||||
</td> |
||||
<td></td> |
||||
<td style="text-align:right;vertical-align:middle"> |
||||
<form class="input-append" style="text-align:right"> |
||||
<input ng-model="settings.replicas" type="number" class="input-mini"> |
||||
<button class="btn btn-info" ng-click="set_replicas(settings.replicas)"><i class="icon-copy"></i></button> |
||||
</form> |
||||
</td> |
||||
</tr> |
||||
<tr ng-style="cluster_color('nothing')" style="vertical-align:middle" ng-repeat="(name,node) in nodes.info"> |
||||
<td style='font-size:1.5em;vertical-align:middle'> |
||||
<img style='font-size:1.5em;width:25px;height:25px;border-radius:20px;border:1px solid #000;margin-right: 10px' src="{{bbuzz.picture(node.transport_address)}}"> |
||||
{{node.name}} / {{bbuzz.ip_address(node.transport_address)}} |
||||
</td> |
||||
<td> |
||||
P |
||||
<div ng-repeat='(index,shards) in nodes.routing[name]' bs-tooltip="index" style='display:inline-block;'> |
||||
<div ng-repeat='shard in primary(shards,true)' ng-class='shard_class(shard.state)' style='display:inline-block;font-size:7pt;line-height:10px;width:10px;height:10px;border-radius:10px;color:#fff;text-align:center;background:{{bbuzz.index_color(index)}};border:0px;margin-right: 2px'></div> |
||||
</div> |
||||
<br> |
||||
R |
||||
<div ng-repeat='(index,shards) in nodes.routing[name]' bs-tooltip="index" style='display:inline-block;'> |
||||
<div ng-repeat='shard in primary(shards,false)' ng-class='shard_class(shard.state)' style='display:inline-block;font-size:7pt;line-height:10px;width:10px;height:10px;border-radius:10px;color:#fff;text-align:center;background:{{bbuzz.index_color(index)}};border:0px;margin-right: 2px'></div> |
||||
</div><br> |
||||
</td> |
||||
<td style="text-align: right;vertical-align: middle"> |
||||
<button ng-click="node_mode(bbuzz.ip_address(node.transport_address),'stop')" style="font-size:1.5em; margin-right:30px" class='icon-signout btn btn-danger'></button> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
<table style="vertical-align: middle" ng-show="nodes.dead.length > 0" class='table table-condensed'> |
||||
<tr ng-style="cluster_color('red')"> |
||||
<td colspan='100'> |
||||
<span style="line-height:15px"> |
||||
Missing nodes |
||||
</span> |
||||
</td> |
||||
</tr> |
||||
<tr ng-style="cluster_color('nothing')" ng-repeat="name in nodes.dead"> |
||||
<td colspan="2"> |
||||
<img style='width:30px;height:30px;border-radius:30px;border:2px solid #000;margin-right: 10px' src="{{bbuzz.picture(name)}}"> |
||||
{{bbuzz.ip_address(name)}} {{nodes.status[name]}} |
||||
</td> |
||||
<td style="text-align: right"> |
||||
<button ng-click="node_mode(bbuzz.ip_address(name),'start')" style="font-size:1.5em; margin-right:30px" class='icon-signin btn btn-info'></button> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
|
||||
</kibana-panel> |
@ -0,0 +1,372 @@ |
||||
/* |
||||
|
||||
## bbuzz |
||||
|
||||
A special panel for my Berlin Buzzwords talk |
||||
|
||||
### Parameters |
||||
* None |
||||
### Group Events |
||||
#### Sends |
||||
* get_time :: On panel initialization get time range to query |
||||
#### Receives |
||||
* time :: An object containing the time range to use and the index(es) to query |
||||
|
||||
*/ |
||||
angular.module('kibana.bbuzz', []) |
||||
.controller('bbuzz', function($scope, eventBus, $http, bbuzz) { |
||||
|
||||
// Set and populate defaults
|
||||
var _d = { |
||||
group : "default", |
||||
} |
||||
_.defaults($scope.panel,_d) |
||||
|
||||
$scope.init = function () { |
||||
$scope.nodes = {}; |
||||
$scope.settings = {}; |
||||
$scope.indices = []; |
||||
$scope.bbuzz = bbuzz |
||||
$scope.colors = bbuzz.colors(20) |
||||
eventBus.register($scope,'time', function(event,time){ |
||||
set_time(time) |
||||
}); |
||||
// Now that we're all setup, request the time from our group
|
||||
eventBus.broadcast($scope.$id,$scope.panel.group,'get_time') |
||||
} |
||||
|
||||
$scope.set_replicas = function(replicas) { |
||||
$http.put(config.elasticsearch+'/_settings', { |
||||
"index" : {"number_of_replicas" : replicas} |
||||
}).success(function (data) { |
||||
$scope.get_data(); |
||||
}); |
||||
} |
||||
|
||||
$scope.status = function(ip) { |
||||
var something = $http({ |
||||
url: "http://"+ip+':4567/status', |
||||
method: "GET" |
||||
}).error(function(data, status, headers, config) { |
||||
$scope.error = status; |
||||
}); |
||||
|
||||
return something.then(function(p) { |
||||
return p.data |
||||
}); |
||||
} |
||||
|
||||
$scope.node_mode = function(ip,mode) { |
||||
$http({method: 'GET', url: "http://"+ip+':4567/'+mode}). |
||||
success(function(data, status, headers, config) { |
||||
control.log(data) |
||||
$scope.get_data(); |
||||
}). |
||||
error(function(data, status, headers, config) { |
||||
console.log(data, status) |
||||
$scope.get_data(); |
||||
}); |
||||
} |
||||
|
||||
$scope.get_data = function() { |
||||
|
||||
// Get cluster health
|
||||
if (!_.isNumber($scope.settings.replicas)) { |
||||
$http({method: 'GET', url: config.elasticsearch+'/_settings'}). |
||||
success(function(data, status, headers, config) { |
||||
$scope.settings.replicas = parseInt(_.pairs(data)[0][1].settings['index.number_of_replicas']) |
||||
}). |
||||
error(function(data, status, headers, config) { |
||||
console.log('Could not contact elasticsearch at: '+config.elasticsearch) |
||||
}); |
||||
} |
||||
|
||||
// Get cluster health
|
||||
$http({method: 'GET', url: config.elasticsearch+'/_cluster/health'}). |
||||
success(function(data, status, headers, config) { |
||||
$scope.cluster = data
|
||||
}). |
||||
error(function(data, status, headers, config) { |
||||
console.log('Could not contact elasticsearch at: '+config.elasticsearch) |
||||
}); |
||||
|
||||
// And nodes list
|
||||
$http({method: 'GET', url: config.elasticsearch+'/_cluster/state'}). |
||||
success(function(data, status, headers, config) { |
||||
var i = [] |
||||
_.each(data.routing_nodes.nodes, function(v,node) { |
||||
i[node] = _.groupBy(v,'index') |
||||
}) |
||||
$scope.nodes.routing = i; |
||||
$scope.nodes.info = data.nodes |
||||
$scope.nodes.live = _.pluck(data.nodes,'transport_address'), |
||||
$scope.nodes.dead = _.difference(bbuzz.get_nodes(),_.pluck(data.nodes,'transport_address')) |
||||
//$scope.nodes.dead = ['inet[/10.126.115.79:9300]'];
|
||||
$scope.indices = _.union($scope.indices,_.keys(i)) |
||||
|
||||
_.each($scope.nodes.dead,function(node) { |
||||
$http({ |
||||
url: "http://"+bbuzz.ip_address(node)+':4567/status', |
||||
method: "GET" |
||||
}).success(function(data, status, headers, config) { |
||||
if(_.isUndefined($scope.nodes.status)) |
||||
$scope.nodes.status = {}; |
||||
$scope.nodes.status[node] = 'data'; |
||||
}) |
||||
}) |
||||
|
||||
}). |
||||
error(function(data, status, headers, config) { |
||||
console.log('Could not contact elasticsearch at: '+config.elasticsearch) |
||||
}); |
||||
} |
||||
|
||||
// returns list of shards sorted by index
|
||||
$scope.sorted_shards = function(shards) { |
||||
return _.sortBy(shards, function(shard) { |
||||
return shard.index |
||||
}) |
||||
} |
||||
|
||||
$scope.primary = function(shards,primary) { |
||||
return _.filter(shards, function(shard) { |
||||
return shard.primary === primary ? true: false; |
||||
}) |
||||
} |
||||
|
||||
$scope.shard_class = function(state) { |
||||
switch(state) |
||||
{ |
||||
case 'INITIALIZING': |
||||
return ['animated','flash','infinite'] |
||||
case 'RELOCATING': |
||||
return ['faded'] |
||||
default: |
||||
return '' |
||||
} |
||||
} |
||||
|
||||
$scope.cluster_class = function(state) { |
||||
switch(state) |
||||
{ |
||||
case 'green': |
||||
return 'btn-success' |
||||
case 'yellow': |
||||
return 'btn-warning' |
||||
case 'red':
|
||||
return 'btn-danger' |
||||
default: |
||||
return 'btn-inverse' |
||||
} |
||||
} |
||||
|
||||
$scope.cluster_color = function(state) { |
||||
switch(state) |
||||
{ |
||||
case 'green': |
||||
var style = {color: '#FFF', bg: '#5BB75B', light: '#62C462', dark: '#51A351'} |
||||
break; |
||||
case 'yellow': |
||||
var style = {color: '#FFF', bg: '#FAA732', light: '#FBB450', dark: '#F89406'} |
||||
break; |
||||
case 'red':
|
||||
var style = {color: '#FFF', bg: "#DA4F49", light: '#EE5F5B', dark: '#BD362F'} |
||||
break; |
||||
default: |
||||
var style = {color: '#000', bg: "#F5F5F5", light: '#FFFFFF', dark: '#E6E6E6'} |
||||
break; |
||||
} |
||||
return {
|
||||
'color': style.color, |
||||
'margin-bottom': '0px', |
||||
'font-weight': 200, |
||||
'background-color': style.bg, |
||||
'background-image': 'linear-gradient(to bottom, '+style.light+', '+style.dark+')', |
||||
'background-repeat': 'repeat-x', |
||||
'border-color': 'rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25)', |
||||
'border-style': 'solid', |
||||
'border-width': '1px', |
||||
'box-shadow': '0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 1px 2px rgba(0, 0, 0, 0.05)', |
||||
'padding': '10px' |
||||
}; |
||||
} |
||||
|
||||
function set_time(time) { |
||||
$scope.time = time; |
||||
$scope.get_data(); |
||||
} |
||||
|
||||
}).directive('started', function(eventBus) { |
||||
return { |
||||
restrict: 'C', |
||||
link: function(scope, elem, attrs, ctrl) { |
||||
console.log('shard') |
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(){ |
||||
render_panel(); |
||||
}); |
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function(){ |
||||
render_panel(); |
||||
}); |
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() { |
||||
|
||||
var scripts = $LAB.script("common/lib/panels/jquery.flot.js") |
||||
|
||||
// Populate element.
|
||||
scripts.wait(function(){ |
||||
try { |
||||
|
||||
} catch(e) { |
||||
elem.text(e) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
} |
||||
}; |
||||
}).service('bbuzz', function($timeout) { |
||||
var nodes = [];
|
||||
var indices = []; |
||||
|
||||
this.index_id = function(id) { |
||||
if (!_.contains(indices,id)) |
||||
indices.push(id) |
||||
return _.indexOf(indices,id) |
||||
} |
||||
|
||||
this.index_color = function(id) { |
||||
var i = this.index_id(id) |
||||
var colors = this.colors(indices.length) |
||||
return colors[i]; |
||||
} |
||||
|
||||
this.ip_address = function(transport_address) { |
||||
return transport_address.split(/[\/:]/)[1] |
||||
} |
||||
|
||||
this.node_id = function(id) { |
||||
if (!_.contains(nodes,id)) |
||||
nodes.push(id) |
||||
return _.indexOf(nodes,id) |
||||
} |
||||
|
||||
this.get_nodes = function() { |
||||
return nodes; |
||||
} |
||||
|
||||
this.picture = function(id) { |
||||
return 'panels/bbuzz/img/'+this.node_id(id)+'.jpg' |
||||
} |
||||
|
||||
this.colors = function(count) { |
||||
// produce colors as needed
|
||||
var seed = ['#86B22D','#BF6730','#1D7373','#BFB930','#BF3030','#77207D'] |
||||
var colors = [], variation = 0; |
||||
i = 0; |
||||
while (colors.length < count) { |
||||
var c; |
||||
if (seed.length == i) // check degenerate case
|
||||
c = new Color(100, 100, 100); |
||||
else |
||||
c = parseColor(seed[i]); |
||||
|
||||
// vary color if needed
|
||||
var sign = variation % 2 == 1 ? -1 : 1; |
||||
var factor = 1 + sign * Math.ceil(variation / 2) * 0.2; |
||||
c.scale(factor, factor, factor); |
||||
|
||||
// FIXME: if we're getting to close to something else,
|
||||
// we should probably skip this one
|
||||
colors.push(c.toString()); |
||||
|
||||
++i; |
||||
if (i >= seed.length) { |
||||
i = 0; |
||||
++variation; |
||||
} |
||||
} |
||||
return colors; |
||||
} |
||||
|
||||
function parseColor(str) { |
||||
var result; |
||||
// Look for #a0b1c2
|
||||
if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)) |
||||
return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)); |
||||
// Look for #fff
|
||||
if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)) |
||||
return new Color(parseInt(result[1]+result[1], 16), parseInt(result[2]+result[2], 16), parseInt(result[3]+result[3], 16)); |
||||
} |
||||
|
||||
// color helpers, inspiration from the jquery color animation
|
||||
// plugin by John Resig
|
||||
function Color (r, g, b, a) { |
||||
|
||||
var rgba = ['r','g','b','a']; |
||||
var x = 4; //rgba.length
|
||||
|
||||
while (-1<--x) { |
||||
this[rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0); |
||||
} |
||||
|
||||
this.toString = function() { |
||||
if (this.a >= 1.0) { |
||||
return "rgb("+[this.r,this.g,this.b].join(",")+")"; |
||||
} else { |
||||
return "rgba("+[this.r,this.g,this.b,this.a].join(",")+")"; |
||||
} |
||||
}; |
||||
|
||||
this.scale = function(rf, gf, bf, af) { |
||||
x = 4; //rgba.length
|
||||
while (-1<--x) { |
||||
if (arguments[x] != null) |
||||
this[rgba[x]] *= arguments[x]; |
||||
} |
||||
return this.normalize(); |
||||
}; |
||||
|
||||
this.adjust = function(rd, gd, bd, ad) { |
||||
x = 4; //rgba.length
|
||||
while (-1<--x) { |
||||
if (arguments[x] != null) |
||||
this[rgba[x]] += arguments[x]; |
||||
} |
||||
return this.normalize(); |
||||
}; |
||||
|
||||
this.clone = function() { |
||||
return new Color(this.r, this.b, this.g, this.a); |
||||
}; |
||||
|
||||
var limit = function(val,minVal,maxVal) { |
||||
return Math.max(Math.min(val, maxVal), minVal); |
||||
}; |
||||
|
||||
this.normalize = function() { |
||||
this.r = clamp(0, parseInt(this.r), 255); |
||||
this.g = clamp(0, parseInt(this.g), 255); |
||||
this.b = clamp(0, parseInt(this.b), 255); |
||||
this.a = clamp(0, this.a, 1); |
||||
return this; |
||||
}; |
||||
|
||||
this.normalize(); |
||||
} |
||||
|
||||
function clamp(min, value, max) { |
||||
if (value < min) |
||||
return min; |
||||
else if (value > max) |
||||
return max; |
||||
else |
||||
return value; |
||||
} |
||||
|
||||
|
||||
}) |