Skip to content

Commit

Permalink
Include a view showing the status of the cluster
Browse files Browse the repository at this point in the history
When a node is part of a cluster, i.e. /info/options returns a
ClusterSize > 1, a new menu item will appear in the UI that allows the
user to get a glance of the cluster the node forms a part of
  • Loading branch information
pgermishuys committed Oct 27, 2015
1 parent f177ebc commit 3383d8c
Show file tree
Hide file tree
Showing 23 changed files with 385 additions and 5 deletions.
3 changes: 3 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ gulp.task('html', function () {
templateForModule('./src/js/modules/dashboard/views/*.tpl.html',
'./src/js/modules/dashboard/templates',
'es-ui.dashboard.templates');
templateForModule('./src/js/modules/clusterstatus/views/*.tpl.html',
'./src/js/modules/clusterstatus/templates',
'es-ui.clusterstatus.templates');
templateForModule('./src/js/modules/streams/views/*.tpl.html',
'./src/js/modules/streams/templates',
'es-ui.streams.templates');
Expand Down
2 changes: 2 additions & 0 deletions src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ define([
'./modules/projections/module',
'./modules/admin/module',
'./modules/dashboard/module',
'./modules/clusterstatus/module',
'./modules/streams/module',
'./modules/security/module',
'./modules/users/module',
Expand All @@ -23,6 +24,7 @@ define([
'es-ui.projections',
'es-ui.admin',
'es-ui.dashboard',
'es-ui.clusterstatus',
'es-ui.streams',
'es-ui.users',
'es-ui.competing',
Expand Down
4 changes: 3 additions & 1 deletion src/js/config/urls.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ define(['es-ui'], function (app) {
shutdown: '/admin/shutdown',
scavenge: '/admin/scavenge'
},
gossip: '/gossip',
users: {
list: '/users/',
get: '/users/%s', // %s - user name
Expand Down Expand Up @@ -64,7 +65,8 @@ define(['es-ui'], function (app) {
},
system:{
subsystems: '/sys/subsystems',
info: '/info'
info: '/info',
options: '/info/options'
},
competing:{
subscriptions: '/subscriptions',
Expand Down
16 changes: 16 additions & 0 deletions src/js/modules/clusterstatus/_index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
define([
'angular',
'uiRouter',
'./controllers/_index',
'./services/_index',
'./templates/templates'
], function (ng) {

'use strict';
return ng.module('es-ui.clusterstatus', [
'ui.router',
'es-ui.clusterstatus.templates',
'es-ui.clusterstatus.services',
'es-ui.clusterstatus.controllers',
]);
});
10 changes: 10 additions & 0 deletions src/js/modules/clusterstatus/config/urls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
angular.module('esManager')
.factory('urls', function urls(){
return{
gossip:'/gossip',
options:'/info/options',
system:{
info: '/info'
}
}
});
40 changes: 40 additions & 0 deletions src/js/modules/clusterstatus/controllers/ClusterStatusListCtrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
define(['./_module'], function (app) {

'use strict';

return app.controller('ClusterStatusListCtrl', ['$scope', 'poller', 'ClusterStatusService', 'InfoService',
function ClusterStatusListCtrl($scope, poller, clusterStatusService, infoService) {
infoService.getOptions().then(
function(response){
var options = response.data;
for (var index in options) {
if(options[index].name == "ClusterSize" && options[index].value > 1){
setupGossipPoller();
}
}
},
function(){

});
function setupGossipPoller(){
var gossipQuery = poller.create({
interval: 1000,
action: clusterStatusService.gossip,
params: []
});
gossipQuery.start();
gossipQuery.promise.then(null, null, function (response) {
$scope.lastUpdatedTime = new Date();
if (response.error) {
$scope.errorMessage = "couldn't connect to manager";
} else {
$scope.errorMessage = '';
$scope.nodes = response.members;
}
});
}
$scope.$on('$destroy', function () {
poller.clear();
});
}]);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
define(['./_module'], function (app) {

'use strict';

return app.controller('ClusterStatusSnapshotCtrl', ['$scope', 'dateFilter', 'poller', 'SprintfService', 'ClusterStatusService',
function ClusterStatusSnapshotCtrl($scope, dateFilter, poller, sprintf, clusterStatusService) {
function format(data) {
var nodes = data.members;
var snapshotLines = sprintf.format('%s\n', 'Snapshot taken at ' + dateFilter(new Date(), 'yyyy-MM-d HH:mm:ss'));
snapshotLines += sprintf.format("%-31s %-31s %-23s %-23s %-7s %-14s %-19s %-10s\n", "Internal Tcp", "External Tcp", "Internal Http", "External Http", "Status", "State", "Timestamp (UTC)", "Checkpoints");
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
snapshotLines += sprintf.format("%-30s %-30s %-22s %-22s %-6s %-12s %19s %-90s\n",
node.internalTcpIp + ':' + node.internalTcpPort + ' (' + (node.internalSecureTcpPort || 'n/a') + ')',
node.externalTcpIp + ':' + node.externalTcpPort + ' (' + (node.externalSecureTcpPort || 'n/a') + ')',
node.internalHttpIp + ':' + node.internalHttpPort,
node.externalHttpIp + ':' + node.externalHttpPort,
node.isAlive ? 'Alive' : 'Dead',
node.state,
dateFilter(node.timeStamp, 'yyyy-MM-d HH:mm:ss'),
node.state == 'Manager'
? 'n/a'
: sprintf.format('L%d/W%d/C%d/E%d@%d:{%s}', node.lastCommitPosition, node.writerCheckpoint, node.chaserCheckpoint,
node.epochNumber, node.epochPosition, node.epochId))
};
$scope.snapshot = snapshotLines;
}
$scope.$on('$destroy', function () {
poller.clear();
});
clusterStatusService.gossip().success(format);
}]);
});
4 changes: 4 additions & 0 deletions src/js/modules/clusterstatus/controllers/_index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
define([
'./ClusterStatusListCtrl',
'./ClusterStatusSnapshotCtrl',
], function () {});
5 changes: 5 additions & 0 deletions src/js/modules/clusterstatus/controllers/_module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
define(['angular'], function (ng) {
'use strict';
return ng.module('es-ui.clusterstatus.controllers', [
]);
});
40 changes: 40 additions & 0 deletions src/js/modules/clusterstatus/module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* global define */
/*jshint sub: true */

define(['./_index'], function (app) {
'use strict';

return app.config([
'$stateProvider',
function ($stateProvider) {

$stateProvider
// ========================================CLUSTERSTATUS============
.state('clusterstatus', {
parent: 'app',
url: 'clusterstatus',
templateUrl: 'clusterstatus.tpl.html',
abstract: true,
data: {
title: 'Cluster Status'
}
})
.state('clusterstatus.list', {
url: '',
templateUrl: 'clusterstatus.list.tpl.html',
controller: 'ClusterStatusListCtrl',

data: {
title: 'Cluster Status'
}
})
.state('clusterstatus.snapshot', {
url: '/snapshot',
templateUrl: 'clusterstatus.snapshot.tpl.html',
controller: 'ClusterStatusSnapshotCtrl',
data: {
title: 'Cluster Status Snapshot'
}
});
}]);
});
21 changes: 21 additions & 0 deletions src/js/modules/clusterstatus/services/ClusterStatusService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
define(['./_module'], function (app) {

'use strict';

return app.provider('ClusterStatusService', function () {

this.$get = [
'$http', 'urls', 'UrlBuilder',
function ($http, urls, urlBuilder) {

return {
gossip: function () {
var url = urlBuilder.build(urls.gossip);

return $http.get(url);
}
};
}
];
});
});
3 changes: 3 additions & 0 deletions src/js/modules/clusterstatus/services/_index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
define([
'./ClusterStatusService',
], function () {});
4 changes: 4 additions & 0 deletions src/js/modules/clusterstatus/services/_module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
define(['angular'], function (ng) {
'use strict';
return ng.module('es-ui.clusterstatus.services', []);
});
54 changes: 54 additions & 0 deletions src/js/modules/clusterstatus/templates/templates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
define(['angular'], function (angular) {'use strict'; (function(module) {
try {
module = angular.module('es-ui.clusterstatus.templates');
} catch (e) {
module = angular.module('es-ui.clusterstatus.templates', []);
}
module.run(['$templateCache', function($templateCache) {
$templateCache.put('clusterstatus.list.tpl.html',
'<header class=page-header><h2 class=page-title>Cluster Status</h2><ul class=page-nav><li class=page-nav__item><a ui-sref=^.snapshot>Snapshot</a></li></ul></header><div><table class=table-nodes><thead><tr><th>Internal Tcp</th><th>External Tcp</th><th>Internal Http</th><th>External Http</th><th>Status</th><th>State</th><th>Timestamp (UTC)</th><th>Checkpoints</th><th>Actions</th></tr><tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead><tbody><tr ng-repeat="node in nodes" ng-class="{dead: !node.isAlive}"><td>{{ node.internalTcpIp }}:{{ node.internalTcpPort }}</td><td>{{ node.externalTcpIp }}:{{ node.externalTcpPort }}</td><td>{{ node.internalHttpIp }}:{{ node.internalHttpPort }}</td><td>{{ node.externalHttpIp }}:{{ node.externalHttpPort }}</td><td ng-class="{dead: !node.isAlive}"><text ng-if=node.isAlive>Alive</text><text ng-if=!node.isAlive>Dead</text></td><td><text ng-if="node.state == \'Master\'"><b>{{ node.state }}</b></text><text ng-if="node.state == \'Manager\'" style=color:lightgray>{{ node.state }}</text><text ng-if="node.state != \'Master\' && node.state != \'Manager\'">{{ node.state }}</text></td><td>{{ node.timeStamp | date : \'yyyy-MM-d HH:mm:ss\' }}</td><td><text ng-if="node.state == \'Manager\'">n/a</text><text ng-if="node.state != \'Manager\'">L{{ node.lastCommitPosition}} / W {{node.writerCheckpoint}} / C {{node.chaserCheckpoint}}<br>E{{ node.epochNumber }} @ {{ node.epochPosition }} : { {{ node.epochId }} }</text></td><td style=text-align:center><ul class=page-nav><li class=page-nav__item><a ng-href=http://{{node.externalHttpIp}}:{{node.externalHttpPort}}/ping target=_blank>ping</a></li><li class=page-nav__item><a ng-href=http://{{node.externalHttpIp}}:{{node.externalHttpPort}} target=_blank>show website</a></li><li class=page-nav__item><a ng-href=http://{{node.internalHttpIp}}:{{node.internalHttpPort}}/gossip target=_blank>show gossip</a></li></ul></td></tr><tr ng-hide=nodes><td colspan=9><em>No nodes in the cluster</em></td></tr></tbody></table><div class=last-updated>Last updated: <span>{{ lastUpdatedTime | date : \'yyyy-MM-d HH:mm:ss\' }}</span></div></div><script>function selectText(element) {\n' +
' var doc = document\n' +
' , text = doc.getElementById(element)\n' +
' , range\n' +
' , selection;\n' +
' if (doc.body.createTextRange) { //ms\n' +
' range = doc.body.createTextRange();\n' +
' range.moveToElementText(text);\n' +
' range.select();\n' +
' } else if (window.getSelection) { //all others\n' +
' selection = window.getSelection();\n' +
' range = doc.createRange();\n' +
' range.selectNodeContents(text);\n' +
' selection.removeAllRanges();\n' +
' selection.addRange(range);\n' +
' }\n' +
' }</script>');
}]);
})();

(function(module) {
try {
module = angular.module('es-ui.clusterstatus.templates');
} catch (e) {
module = angular.module('es-ui.clusterstatus.templates', []);
}
module.run(['$templateCache', function($templateCache) {
$templateCache.put('clusterstatus.snapshot.tpl.html',
'<header class=page-header><h2 class=page-title>Cluster Status Snapshot</h2><ul class=page-nav><li class=page-nav__item><a ui-sref=clusterstatus.list>Back</a></li></ul></header><pre ng-bind=snapshot>\n' +
'\n' +
'</pre>');
}]);
})();

(function(module) {
try {
module = angular.module('es-ui.clusterstatus.templates');
} catch (e) {
module = angular.module('es-ui.clusterstatus.templates', []);
}
module.run(['$templateCache', function($templateCache) {
$templateCache.put('clusterstatus.tpl.html',
'<div ui-view></div>');
}]);
})();
});
99 changes: 99 additions & 0 deletions src/js/modules/clusterstatus/views/clusterstatus.list.tpl.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<header class="page-header">
<h2 class="page-title">Cluster Status</h2>
<ul class="page-nav">
<li class="page-nav__item">
<a ui-sref="^.snapshot">Snapshot</a>
</li>
</ul>
</header>
<div>
<table class="table-nodes">
<thead>
<tr>
<th>Internal Tcp</th>
<th>External Tcp</th>
<th>Internal Http</th>
<th>External Http</th>
<th>Status</th>
<th>State</th>
<th>Timestamp (UTC)</th>
<th>Checkpoints</th>
<th>Actions</th>
</tr>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="node in nodes" ng-class="{dead: !node.isAlive}">
<td>{{ node.internalTcpIp }}:{{ node.internalTcpPort }}</td>
<td>{{ node.externalTcpIp }}:{{ node.externalTcpPort }}</td>
<td>{{ node.internalHttpIp }}:{{ node.internalHttpPort }}</td>
<td>{{ node.externalHttpIp }}:{{ node.externalHttpPort }}</td>
<td ng-class="{dead: !node.isAlive}">
<text ng-if="node.isAlive">Alive</text>
<text ng-if="!node.isAlive">Dead</text>
</td>
<td>
<text ng-if="node.state == 'Master'"><b>{{ node.state }}</b></text>
<text ng-if="node.state == 'Manager'" style="color:lightgray">{{ node.state }}</text>
<text ng-if="node.state != 'Master' && node.state != 'Manager'">{{ node.state }}</text>
</td>
<td>{{ node.timeStamp | date : 'yyyy-MM-d HH:mm:ss' }}</td>
<td>
<text ng-if="node.state == 'Manager'">n/a</text>
<text ng-if="node.state != 'Manager'">
L{{ node.lastCommitPosition}} / W {{node.writerCheckpoint}} / C {{node.chaserCheckpoint}}<br />
E{{ node.epochNumber }} @ {{ node.epochPosition }} : { {{ node.epochId }} }
</text>
</td>
<td style="text-align:center;">
<ul class="page-nav">
<li class="page-nav__item">
<a ng-href="http://{{node.externalHttpIp}}:{{node.externalHttpPort}}/ping" target="_blank">ping</a>
</li>
<li class="page-nav__item">
<a ng-href="http://{{node.externalHttpIp}}:{{node.externalHttpPort}}" target="_blank">show website</a>
</li>
<li class="page-nav__item">
<a ng-href="http://{{node.internalHttpIp}}:{{node.internalHttpPort}}/gossip" target="_blank">show gossip</a>
</li>
</ul>
</td>
</tr>
<tr ng-hide="nodes">
<td colspan="9">
<em>No nodes in the cluster</em>
</td>
</tr>
</tbody>
</table>
<div class="last-updated">Last updated: <span>{{ lastUpdatedTime | date : 'yyyy-MM-d HH:mm:ss' }}</span></div>
</div>
<script type="text/javascript">
function selectText(element) {
var doc = document
, text = doc.getElementById(element)
, range
, selection;
if (doc.body.createTextRange) { //ms
range = doc.body.createTextRange();
range.moveToElementText(text);
range.select();
} else if (window.getSelection) { //all others
selection = window.getSelection();
range = doc.createRange();
range.selectNodeContents(text);
selection.removeAllRanges();
selection.addRange(range);
}
}
</script>
Loading

0 comments on commit 3383d8c

Please sign in to comment.