0?d[1]:false}function a(e,f){var d=false;b.each(e,function(h,g){if(!d){d=g==f}});return d}b.fn.TinySort=b.fn.Tinysort=b.fn.tsort=b.fn.tinysort})(jQuery);
--------------------------------------------------------------------------------
/src/main/webapp/public/js/tmpl.js:
--------------------------------------------------------------------------------
1 |
2 | //Simple JavaScript Templating
3 | //John Resig - http://ejohn.org/ - MIT Licensed
4 | // http://ejohn.org/blog/javascript-micro-templating/
5 | (function(window, undefined) {
6 | var cache = {};
7 |
8 | window.tmpl = function tmpl(str, data) {
9 | try {
10 | // Figure out if we're getting a template, or if we need to
11 | // load the template - and be sure to cache the result.
12 | var fn = !/\W/.test(str) ?
13 | cache[str] = cache[str] ||
14 | tmpl(document.getElementById(str).innerHTML) :
15 |
16 | // Generate a reusable function that will serve as a template
17 | // generator (and which will be cached).
18 | new Function("obj",
19 | "var p=[],print=function(){p.push.apply(p,arguments);};" +
20 |
21 | // Introduce the data as local variables using with(){}
22 | "with(obj){p.push('" +
23 |
24 | // Convert the template into pure JavaScript
25 | str
26 | .replace(/[\r\t\n]/g, " ")
27 | .split("<%").join("\t")
28 | .replace(/((^|%>)[^\t]*)'/g, "$1\r")
29 | .replace(/\t=(.*?)%>/g, "',$1,'")
30 | .split("\t").join("');")
31 | .split("%>").join("p.push('")
32 | .split("\r").join("\\'")
33 | + "');}return p.join('');");
34 |
35 | //console.log(fn);
36 |
37 | // Provide some basic currying to the user
38 | return data ? fn(data) : fn;
39 | }catch(e) {
40 | console.log(e);
41 | }
42 | };
43 | })(window);
44 |
--------------------------------------------------------------------------------
/src/main/webapp/public/scripts/app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 | 'use strict';
17 |
18 | angular.module('springCloudDashboard', [
19 | 'ngResource',
20 | 'ngRoute',
21 | 'ui.router',
22 | 'ui.bootstrap',
23 | 'springCloudDashboard.services',
24 | 'nvd3ChartDirectives'
25 | ])
26 | .config(function ($stateProvider, $urlRouterProvider) {
27 | $urlRouterProvider
28 | .when('/', '/app')
29 | .otherwise('/');
30 | $stateProvider
31 | .state('overview', {
32 | url: '/app',
33 | templateUrl: 'views/overview.html',
34 | controller: 'overviewCtrl'
35 | })
36 | .state('overview.select', {
37 | url: '/:id',
38 | templateUrl: 'views/overview.selected.html',
39 | controller: 'overviewSelectedCtrl'
40 | })
41 | .state('about', {
42 | url: '/about',
43 | templateUrl: 'views/about.html'
44 | })
45 | .state('apps', {
46 | abstract:true,
47 | url: '/app/:appId/instance/:instanceId',
48 | controller: 'appsCtrl',
49 | templateUrl: 'views/apps.html',
50 | resolve: {
51 | instance: ['$stateParams', 'Instance' , function($stateParams, Instance) {
52 | return Instance.query({id: $stateParams.instanceId}).$promise;
53 | }]
54 | }
55 | })
56 | .state('history', {
57 | url: '/history',
58 | templateUrl: 'views/apps/history.html',
59 | controller: 'appsHistoryCtrl'
60 | })
61 | .state('circuit-breaker', {
62 | url: '/circuit-breaker/:type/:id',
63 | templateUrl: 'views/circuit-breaker/index.html',
64 | controller: 'circuitBreakerCtrl'
65 | })
66 | .state('apps.details', {
67 | url: '/details',
68 | templateUrl: 'views/apps/details.html',
69 | controller: 'detailsCtrl'
70 | })
71 | .state('apps.details.metrics', {
72 | url: '/metrics',
73 | templateUrl: 'views/apps/details/metrics.html',
74 | controller: 'detailsMetricsCtrl'
75 | })
76 | .state('apps.details.classpath', {
77 | url: '/classpath',
78 | templateUrl: 'views/apps/details/classpath.html',
79 | controller: 'detailsClasspathCtrl'
80 | })
81 | .state('apps.env', {
82 | url: '/env',
83 | templateUrl: 'views/apps/environment.html',
84 | controller: 'environmentCtrl'
85 | })
86 | .state('apps.logging', {
87 | url: '/logging',
88 | templateUrl: 'views/apps/logging.html',
89 | controller: 'loggingCtrl'
90 | })
91 | .state('apps.jmx', {
92 | url: '/jmx',
93 | templateUrl: 'views/apps/jmx.html',
94 | controller: 'jmxCtrl'
95 | })
96 | .state('apps.threads', {
97 | url: '/threads',
98 | templateUrl: 'views/apps/threads.html',
99 | controller: 'threadsCtrl'
100 | })
101 | .state('apps.trace', {
102 | url: '/trace',
103 | templateUrl: 'views/apps/trace.html',
104 | controller: 'traceCtrl'
105 | });
106 | })
107 | .run(function ($rootScope, $state, $stateParams, $log) {
108 | $rootScope.$state = $state;
109 | $rootScope.$stateParams = $stateParams;
110 | });
111 |
--------------------------------------------------------------------------------
/src/main/webapp/public/scripts/controllers/circuit-breaker.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 | 'use strict';
17 |
18 | angular.module('springCloudDashboard')
19 | .controller('circuitBreakerCtrl', ['$scope', '$stateParams', 'Instance',
20 | function ($scope, $stateParams, Instance) {
21 |
22 | if($stateParams.type == 'app') {
23 | var stream = "/circuitBreaker.stream?appName="+$stateParams.id;
24 | $scope.subtitle = $stateParams.id;
25 | } else if($stateParams.type == 'instance') {
26 | var stream = "/circuitBreaker.stream?instanceId="+$stateParams.id;
27 | var instance = Instance.query({id: $stateParams.id}, function(instance){
28 | $scope.subtitle = instance.name;
29 | });
30 | }
31 |
32 | // commands
33 | $scope.hystrixMonitor = new HystrixCommandMonitor('dependencies', {includeDetailIcon:false});
34 |
35 | // sort by error+volume by default
36 | $scope.hystrixMonitor.sortByErrorThenVolume();
37 |
38 | // start the EventSource which will open a streaming connection to the server
39 | $scope.source = new EventSource(stream);
40 |
41 | // add the listener that will process incoming events
42 | $scope.source.addEventListener('message', $scope.hystrixMonitor.eventSourceMessageListener, false);
43 |
44 | $scope.source.addEventListener('error', function(e) {
45 | if (e.eventPhase == EventSource.CLOSED) {
46 | // Connection was closed.
47 | console.log("Connection was closed on error: " + JSON.stringify(e));
48 | } else {
49 | console.log("Error occurred while streaming: " + JSON.stringify(e));
50 | }
51 | }, false);
52 |
53 | // thread pool
54 | $scope.dependencyThreadPoolMonitor = new HystrixThreadPoolMonitor('dependencyThreadPools');
55 |
56 | $scope.dependencyThreadPoolMonitor.sortByVolume();
57 |
58 | // start the EventSource which will open a streaming connection to the server
59 | $scope.threadSource = new EventSource(stream);
60 |
61 | // add the listener that will process incoming events
62 | $scope.threadSource.addEventListener('message', $scope.dependencyThreadPoolMonitor.eventSourceMessageListener, false);
63 |
64 | $scope.threadSource.addEventListener('error', function(e) {
65 | if (e.eventPhase == EventSource.CLOSED) {
66 | // Connection was closed.
67 | console.log("Connection was closed on error: " + e);
68 | } else {
69 | console.log("Error occurred while streaming: " + e);
70 | }
71 | }, false);
72 |
73 | $scope.$on('$destroy', function clearEventSource() {
74 | if($scope.source) { $scope.source.close(); delete $scope.source; }
75 | if($scope.threadSource) { $scope.threadSource.close(); delete $scope.threadSource; }
76 | })
77 | }]);
--------------------------------------------------------------------------------
/src/main/webapp/public/scripts/directives/richMetricBar.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 | 'use strict';
17 |
18 | angular.module('springCloudDashboard')
19 | .directive('richMetricBar', function() {
20 | return {
21 | restrict: 'E',
22 | scope: {
23 | metric: '=forMetric',
24 | globalMax: '=?globalMax'
25 | },
26 | link: function(scope) {
27 | scope.globalMax = scope.globalMax || scope.metric.max;
28 | scope.minWidth = (scope.metric.min / scope.globalMax * 100).toFixed(2);
29 | scope.avgWidth = (scope.metric.avg / scope.globalMax * 100).toFixed(2);
30 | scope.valueWidth = (scope.metric.value / scope.globalMax * 100).toFixed(2);
31 | scope.maxWidth = (scope.metric.max / scope.globalMax * 100).toFixed(2);
32 | },
33 | template: '\
34 | {{metric.name}} (count: {{metric.count}}) \
35 |
\
36 |
\
37 |
\
38 |
\
39 |
\
40 |
{{metric.value}}
\
41 |
\
42 |
\
43 |
{{metric.min}}
\
44 |
{{metric.max}}
\
45 |
{{metric.avg}}
\
46 |
\
47 |
\
48 |
{{metric.value}}
\
49 |
\
50 |
'
51 | }
52 | });
--------------------------------------------------------------------------------
/src/main/webapp/public/scripts/directives/simpleMetricBar.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 | 'use strict';
17 |
18 | angular.module('springCloudDashboard')
19 | .directive('simpleMetricBar', function() {
20 | return {
21 | restrict: 'E',
22 | scope: {
23 | metric: '=forMetric',
24 | globalMax: '=globalMax'
25 | },
26 | link: function(scope) {
27 | scope.valueWidth = (scope.metric.value / scope.globalMax * 100).toFixed(2);
28 | },
29 | template: '\
30 | {{metric.name}}\
31 |
\
32 |
{{metric.value}}
\
33 |
\
34 |
'
35 | };
36 | });
37 |
--------------------------------------------------------------------------------
/src/main/webapp/public/scripts/filters/filters.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 | 'use strict';
17 |
18 | angular.module('springCloudDashboard')
19 | .filter('timeInterval', function() {
20 | function padZero(i,n) {
21 | var s = i + "";
22 | while (s.length < n) s = "0" + s;
23 | return s;
24 | }
25 |
26 | return function(input) {
27 | var s = input || 0;
28 | var d = padZero(Math.floor(s / 86400000),2);
29 | var h = padZero(Math.floor(s % 86400000 / 3600000),2);
30 | var m = padZero(Math.floor(s % 3600000 / 60000),2);
31 | var s = padZero(Math.floor(s % 60000 / 1000),2);
32 | return d + ':' + h + ':' + m + ':' + s;
33 | }
34 | })
35 | .filter('classNameLoggerOnly', function() {
36 | return function(input, active) {
37 | if (!active) {
38 | return input;
39 | }
40 | var result = [];
41 | for (var j in input) {
42 | var name = input[j].name;
43 | var i = name.lastIndexOf('.') + 1;
44 | if ( name.charAt(i) === name.charAt(i).toUpperCase() ) {
45 | result.push(input[j]);
46 | }
47 | }
48 | return result;
49 | }
50 | })
51 | .filter('capitalize', function() {
52 | return function(input, active) {
53 | var s = input + "";
54 | return s.charAt(0).toUpperCase() + s.slice(1);
55 | }
56 | })
57 | .filter('flatten', function($filter) {
58 | var flatten = function (obj, prefix) {
59 | if (obj instanceof Date) {
60 | obj = $filter('date')(obj, 'dd.MM.yyyy HH:mm:ss');
61 | }
62 | if (typeof obj === 'boolean' || typeof obj === 'string' || typeof obj === 'number') {
63 | return (prefix ? prefix + ': ' : '') + obj;
64 | }
65 |
66 | var result = '';
67 | var first = true;
68 | angular.forEach(obj, function (value, key) {
69 | if (angular.isString(value) && (key === 'time' || key === 'timestamp')) {
70 | if (/^\d+$/.test(value)) {
71 | value = new Date(value * 1000);
72 | } else if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{4}$/.test(value)) {
73 | value = new Date(value);
74 | }
75 | }
76 |
77 | if (first) {
78 | first = false;
79 | } else {
80 | result = result + '\n';
81 | }
82 |
83 | result = result + flatten(value, prefix ? prefix + '.' + key : key);
84 | });
85 |
86 | return result;
87 | };
88 | return flatten;
89 | })
90 | .filter('joinArray', function() {
91 | return function (input, separator) {
92 | if (!Array.isArray(input) ) {
93 | return input;
94 | } else {
95 | return input.join(separator);
96 | }
97 | };
98 | })
99 | .filter('humanBytes', function () {
100 | var units = { B: Math.pow(1024, 0)
101 | , K: Math.pow(1024, 1)
102 | , M: Math.pow(1024, 2)
103 | , G: Math.pow(1024, 3)
104 | , T: Math.pow(1024, 4)
105 | , P: Math.pow(1024, 5)
106 | };
107 |
108 | return function (input, unit) {
109 | input = input || 0;
110 | unit = unit || 'B';
111 |
112 | var bytes = input * (units[unit] || 1 );
113 |
114 | var chosen = 'B';
115 | for (var u in units) {
116 | if (units[chosen] < units[u] && bytes >= units[u]) {
117 | chosen = u;
118 | }
119 | }
120 |
121 | return (bytes / units[chosen]).toFixed(1).replace(/\.0$/, '').replace(/,/g, '') + chosen;
122 | };
123 | })
124 | .filter('capitalize', function () {
125 | return function (input) {
126 | var s = input + '';
127 | return s.charAt(0).toUpperCase() + s.slice(1);
128 | };
129 | });
--------------------------------------------------------------------------------
/src/main/webapp/public/scripts/services/services.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by instancelicable 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.
15 | */
16 | 'use strict';
17 |
18 | angular.module('springCloudDashboard.services', ['ngResource'])
19 | .factory('Applications', ['$resource', function($resource) {
20 | return $resource(
21 | 'api/applications', {}, {
22 | query: { method:'GET', isArray:true }
23 | });
24 | }
25 | ])
26 | .service('ApplicationOverview', ['$http', function($http) {
27 | this.getCircuitBreakerInfo = function(application) {
28 | return $http.head('/circuitBreaker.stream', {
29 | params: {appName: application.name}
30 | }).success(function(response) {
31 | application.circuitBreaker = true;
32 | }).error(function() {
33 | application.circuitBreaker = false;
34 | });
35 | };
36 | }])
37 | .factory('Instance', ['$resource', 'InstanceDetails', function($resource, InstanceDetails) {
38 | return $resource(
39 | 'api/instance/:id', {}, {
40 | query: {
41 | method:'GET',
42 | transformResponse: function(data) {
43 | var instance = angular.fromJson(data);
44 | InstanceDetails.getCapabilities(instance);
45 | return instance;
46 | }
47 | }
48 | });
49 | }
50 | ])
51 | .factory('InstancesHistory', ['$resource', function($resource) {
52 | return $resource(
53 | 'api/registry/history', {}, {
54 | query: { method:'GET'}
55 | });
56 | }
57 | ])
58 | .service('InstanceOverview', ['$http', function($http) {
59 | this.getInfo = function(instance) {
60 | return $http.get('api/instance/'+ instance.id + '/info/').success(function(response) {
61 | var appInfo = response.app || response;
62 | instance.version = appInfo.version;
63 | instance.info = appInfo;
64 | }).error(function() {
65 | instance.version = '---';
66 | });
67 | };
68 | this.getHealth = function(instance) {
69 | return $http.get('api/instance/'+ instance.id + '/health/').success(function (response) {
70 | instance.health = response.status;
71 | }).error(function (response, httpStatus) {
72 | if (httpStatus === 503) {
73 | instance.health = response.status;
74 | } else if (httpStatus === 404) {
75 | instance.health = 'OFFLINE';
76 | } else {
77 | instance.health = 'UNKNOWN';
78 | }
79 | });
80 | };
81 | }])
82 | .service('InstanceDetails', ['$http', function($http) {
83 |
84 | var _this = this;
85 | this.isEndpointPresent = function(endpoint, configprops) {
86 | return !!configprops[endpoint];
87 | };
88 |
89 | this.getCapabilities = function(instance) {
90 | instance.capabilities = {};
91 |
92 | $http.get('api/instance/' + instance.id + '/configprops').success(function(configprops) {
93 | instance.capabilities.logfile = _this .isEndpointPresent('logfileEndpoint', configprops);
94 | instance.capabilities.restart = _this .isEndpointPresent('restartEndpoint', configprops);
95 | instance.capabilities.refresh = _this .isEndpointPresent('refreshEndpoint', configprops);
96 | instance.capabilities.pause = _this .isEndpointPresent('pauseEndpoint', configprops);
97 | instance.capabilities.resume = _this .isEndpointPresent('resumeEndpoint', configprops);
98 | });
99 | };
100 |
101 | this.getInfo = function(instance) {
102 | return $http.get('api/instance/'+ instance.id + '/info/');
103 | };
104 | this.getMetrics = function(instance) {
105 | return $http.get('api/instance/'+ instance.id + '/metrics/');
106 | };
107 | this.getEnv = function (instance, key) {
108 | if(key) {
109 | return $http.get('api/instance/' + instance.id + '/env' + (key ? '/' + key : '' ));
110 | } else {
111 | return $http.get('api/instance/'+ instance.id + '/env/');
112 | }
113 | };
114 | this.setEnv = function (instance, map) {
115 | return $http.post('api/instance/' + instance.id + '/env', '', {params: map});
116 | };
117 | this.resetEnv = function (instance) {
118 | return $http.post('api/instance/' + instance.id + '/env/reset');
119 | };
120 | this.refresh = function (instance) {
121 | return $http.post('api/instance/' + instance.id + '/refresh');
122 | };
123 | this.getHealth = function(instance) {
124 | return $http.get('api/instance/'+ instance.id + '/health/');
125 | };
126 | this.getTraces = function (instance) {
127 | return $http.get('api/instance/' + instance.id + '/trace');
128 | };
129 | this.getCircuitBreakerInfo = function(instance) {
130 | return $http.head('/circuitBreaker.stream', {
131 | params: {instanceId: instance.id}
132 | });
133 | };
134 | }])
135 | .service('InstanceLogging', ['$http' , 'Jolokia', function($http, jolokia) {
136 | var LOGBACK_MBEAN = 'ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator';
137 |
138 | this.getLoglevel = function(instance, loggers) {
139 | var requests = [];
140 | for (var j in loggers) {
141 | requests.push({ type: 'exec', mbean: LOGBACK_MBEAN, operation: 'getLoggerEffectiveLevel', arguments: [ loggers[j].name ] })
142 | }
143 | return jolokia.bulkRequest('api/instance/'+ instance.id + '/jolokia/', requests);
144 | };
145 |
146 | this.setLoglevel = function(instance, logger, level) {
147 | return jolokia.exec('api/instance/'+ instance.id + '/jolokia/', LOGBACK_MBEAN, 'setLoggerLevel' , [ logger, level] );
148 | };
149 |
150 | this.getAllLoggers = function(instance) {
151 | return jolokia.readAttr('api/instance/'+ instance.id + '/jolokia/', LOGBACK_MBEAN, 'LoggerList');
152 | }
153 | }])
154 | .service('InstanceJMX', ['$rootScope', 'Abbreviator', 'Jolokia', function($rootScope, Abbreviator, jolokia) {
155 | this.list = function(instance) {
156 | return jolokia.list('api/instance/'+ instance.id + '/jolokia/').then(function(response) {
157 | var domains = [];
158 | for (var rDomainName in response.value) {
159 | var rDomain = response.value[rDomainName];
160 | var domain = {name : rDomainName, beans: [] };
161 |
162 | for (var rBeanName in rDomain ) {
163 | var rBean = rDomain[rBeanName];
164 | var bean = { id : domain.name + ':' + rBeanName,
165 | name : '',
166 | nameProps: {},
167 | description : rBean.desc,
168 | operations : rBean.op,
169 | attributes : rBean.attr
170 | };
171 |
172 | var name = '';
173 | var type = '';
174 | var parts = rBeanName.split(',');
175 | for (var i in parts ) {
176 | var tokens = parts[i].split('=');
177 | if (tokens[0].toLowerCase() === 'name') {
178 | name = tokens[1];
179 | } else{
180 | bean.nameProps[tokens[0]] = tokens[1];
181 | if ((tokens[0].toLowerCase() === 'type' || tokens[0].toLowerCase() == 'j2eetype') && type.length ==0 ) {
182 | type = tokens[1];
183 | }
184 | }
185 | }
186 |
187 | if (name.length !== 0) {
188 | bean.name = name;
189 | }
190 | if ( type.length !== 0) {
191 | if (bean.name !== 0) {
192 | bean.name += ' ';
193 | }
194 | bean.name += '[' + Abbreviator.abbreviate(type, '.', 25, 1, 1) + ']';
195 | }
196 |
197 | if (bean.name.length === 0) {
198 | bean.name = rBeanName;
199 | }
200 |
201 | domain.beans.push(bean);
202 | }
203 |
204 | domains.push(domain);
205 | }
206 |
207 | return domains;
208 | }, function(response) {
209 | return response;
210 | });
211 | };
212 |
213 | this.readAllAttr = function(instance, bean) {
214 | return jolokia.read('api/instance/'+ instance.id + '/jolokia/', bean.id)
215 | };
216 |
217 | this.writeAttr = function(instance, bean, attr, val) {
218 | return jolokia.writeAttr('api/instance/'+ instance.id + '/jolokia/', bean.id, attr, val);
219 | };
220 |
221 | this.invoke = function(instance, bean, opname, args) {
222 | return jolokia.exec('api/instance/'+ instance.id + '/jolokia/', bean.id, opname, args);
223 | }
224 |
225 | }])
226 | .service('Abbreviator', [function() {
227 | function _computeDotIndexes(fqName, delimiter, preserveLast) {
228 | var dotArray = [];
229 |
230 | //iterate over String and find dots
231 | var lastIndex = -1;
232 | do {
233 | lastIndex = fqName.indexOf(delimiter, lastIndex + 1);
234 | if (lastIndex !== -1) {
235 | dotArray.push(lastIndex);
236 | }
237 | } while (lastIndex !== -1)
238 |
239 | // remove dots to preserve more than the last element
240 | for (var i = 0; i < preserveLast -1; i++ ) {
241 | dotArray.pop();
242 | }
243 |
244 | return dotArray;
245 | }
246 |
247 | function _computeLengthArray(fqName, targetLength, dotArray, shortenThreshold) {
248 | var lengthArray = [];
249 | var toTrim = fqName.length - targetLength;
250 |
251 | for (var i = 0; i < dotArray.length; i++) {
252 | var previousDotPosition = -1;
253 | if (i > 0) {
254 | previousDotPosition = dotArray[i - 1];
255 | }
256 |
257 | var len = dotArray[i] - previousDotPosition - 1;
258 | var newLen = (toTrim > 0 && len > shortenThreshold ? 1 : len);
259 |
260 | toTrim -= (len - newLen);
261 | lengthArray[i] = newLen + 1;
262 | }
263 |
264 | var lastDotIndex = dotArray.length - 1;
265 | lengthArray[dotArray.length] = fqName.length - dotArray[lastDotIndex];
266 |
267 | return lengthArray;
268 | }
269 |
270 | this.abbreviate = function(fqName, delimiter, targetLength, preserveLast, shortenThreshold) {
271 | if (fqName.length < targetLength) {
272 | return fqName;
273 | }
274 |
275 | var dotIndexesArray = _computeDotIndexes(fqName, delimiter, preserveLast);
276 |
277 | if (dotIndexesArray.length === 0) {
278 | return fqName;
279 | }
280 |
281 | var lengthArray = _computeLengthArray(fqName, targetLength, dotIndexesArray, shortenThreshold);
282 |
283 | var result = "";
284 | for (var i = 0; i <= dotIndexesArray.length; i++) {
285 | if (i === 0 ) {
286 | result += fqName.substr(0, lengthArray[i] -1);
287 | } else {
288 | result += fqName.substr(dotIndexesArray[i - 1], lengthArray[i]);
289 | }
290 | }
291 |
292 | return result;
293 | }
294 | }])
295 | .service('Jolokia', [ '$q' , '$rootScope', function($q){
296 | var outer = this;
297 | var j4p = new Jolokia();
298 |
299 |
300 | this.bulkRequest = function(url, requests) {
301 | var deferred = $q.defer();
302 | deferred.notify(requests);
303 |
304 | var hasError = false;
305 | var responses = [];
306 |
307 | j4p.request( requests,
308 | { url: url,
309 | method: 'post',
310 | success: function (response) {
311 | responses.push(response);
312 | if (responses.length >= requests.length) {
313 | if (!hasError) {
314 | deferred.resolve(responses);
315 | } else {
316 | deferred.resolve(responses);
317 | }
318 | }
319 | },
320 | error: function (response) {
321 | hasError = true;
322 | responses.push(response);
323 | if (responses.length >= requests.length) {
324 | deferred.reject(responses);
325 | }
326 | }
327 | });
328 |
329 | return deferred.promise;
330 | };
331 |
332 |
333 | this.request = function(url, request) {
334 | var deferred = $q.defer();
335 | deferred.notify(request);
336 |
337 | j4p.request( request,
338 | { url: url,
339 | method: 'post',
340 | success: function (response) {
341 | deferred.resolve(response);
342 | },
343 | error: function (response) {
344 | deferred.reject(response);
345 | }
346 | });
347 |
348 | return deferred.promise;
349 | };
350 |
351 | this.exec = function(url, mbean, op, args) {
352 | return outer.request(url, { type: 'exec', mbean: mbean, operation: op, arguments: args });
353 | };
354 |
355 | this.read = function(url, mbean) {
356 | return outer.request(url, { type: 'read', mbean: mbean });
357 | };
358 |
359 | this.readAttr = function(url, mbean, attr) {
360 | return outer.request(url, { type: 'read', mbean: mbean, attribute: attr });
361 | };
362 |
363 | this.writeAttr = function(url, mbean, attr, val) {
364 | return outer.request(url, { type: 'write', mbean: mbean, attribute: attr, value: val });
365 | };
366 |
367 | this.list = function(url) {
368 | return outer.request(url, { type: 'list' });
369 | }
370 | }])
371 | .service('MetricsHelper', [function() {
372 | this.find = function (metrics, regexes, callbacks) {
373 | for (var metric in metrics) {
374 | for (var i in regexes) {
375 | var match = regexes[i].exec(metric);
376 | if (match != null) {
377 | callbacks[i](metric, match, metrics[metric]);
378 | break;
379 | }
380 | }
381 | }
382 | }
383 | }])
384 | .service('InstanceThreads', ['$http', function($http) {
385 | this.getDump = function(instance) {
386 | return $http.get('api/instance/'+ instance.id + '/dump/');
387 | }
388 | } ]);
389 |
--------------------------------------------------------------------------------
/src/main/webapp/public/styles/circuit-breaker.css:
--------------------------------------------------------------------------------
1 | /** Command **/
2 | .dependencies .spacer {
3 | width: 100%;
4 | margin: 0 auto;
5 | padding-top:4px;
6 | clear:both;
7 | }
8 |
9 |
10 | .dependencies .last {
11 | margin-right: 0px;
12 | }
13 |
14 | .dependencies span.loading {
15 | display: block;
16 | padding-top: 6%;
17 | padding-bottom: 6%;
18 | color: gray;
19 | text-align: center;
20 | }
21 |
22 | .dependencies span.loading.failed {
23 | color: red;
24 | }
25 |
26 |
27 | .dependencies div.monitor {
28 | float: left;
29 | margin-right:5px;
30 | margin-top:5px;
31 | }
32 |
33 | .dependencies div.monitor p.name {
34 | font-weight:bold;
35 | font-size: 10pt;
36 | text-align: right;
37 | padding-bottom: 5px;
38 | }
39 |
40 | .dependencies div.monitor_data {
41 | margin: 0 auto;
42 | }
43 |
44 | /* override the HREF when we have specified it as a infotip to not act like a link */
45 | .dependencies div.monitor_data a.infotip {
46 | text-decoration: none;
47 | cursor: default;
48 | }
49 |
50 | .dependencies div.monitor_data div.counters {
51 | text-align: right;
52 | padding-bottom: 10px;
53 | font-size: 10pt;
54 | clear: both;
55 |
56 | }
57 |
58 | .dependencies div.monitor_data div.counters div.cell {
59 | display: inline;
60 | float: right;
61 | }
62 |
63 | .dependencies .borderRight {
64 | border-right: 1px solid grey;
65 | padding-right: 6px;
66 | padding-left: 8px;
67 | }
68 |
69 | .dependencies div.cell .line {
70 | display: block;
71 | }
72 |
73 | .dependencies div.monitor_data a,
74 | .dependencies span.rate_value {
75 | font-weight:bold;
76 | }
77 |
78 |
79 | .dependencies span.smaller {
80 | font-size: 8pt;
81 | color: grey;
82 | }
83 |
84 |
85 |
86 | .dependencies div.tableRow {
87 | width:100%;
88 | white-space: nowrap;
89 | font-size: 8pt;
90 | margin: 0 auto;
91 | clear:both;
92 | padding-left:26%;
93 | }
94 |
95 | .dependencies div.tableRow .cell {
96 | float:left;
97 | }
98 |
99 | .dependencies div.tableRow .header {
100 | width:20%;
101 | text-align:right;
102 | }
103 |
104 | .dependencies div.tableRow .data {
105 | width:30%;
106 | font-weight: bold;
107 | text-align:right;
108 | }
109 |
110 |
111 | .dependencies div.monitor {
112 | width: 245px; /* we want a fixed width instead of percentage as I want the boxes to be a set size and then fill in as many as can fit in each row ... this allows 3 columns on an iPad */
113 | height: 150px;
114 | }
115 |
116 | .dependencies .success {
117 | color: green;
118 | }
119 | .dependencies .shortCircuited {
120 | color: blue;
121 | }
122 | .dependencies .timeout {
123 | color: #FF9900; /* shade of orange */
124 | }
125 | .dependencies .failure {
126 | color: red;
127 | }
128 |
129 | .dependencies .rejected {
130 | color: purple;
131 | }
132 |
133 | .dependencies .exceptionsThrown {
134 | color: brown;
135 | }
136 |
137 | .dependencies div.monitor_data a.rate {
138 | color: black;
139 | font-size: 11pt;
140 | }
141 |
142 | .dependencies div.rate {
143 | padding-top: 1px;
144 | clear:both;
145 | text-align:right;
146 | }
147 |
148 | .dependencies .errorPercentage {
149 | color: grey;
150 | }
151 |
152 | .dependencies div.cell .errorPercentage {
153 | padding-left:5px;
154 | font-size: 12pt !important;
155 | }
156 |
157 |
158 | .dependencies div.monitor div.chart {
159 | }
160 |
161 | .dependencies div.monitor div.chart svg {
162 | }
163 |
164 | .dependencies div.monitor div.chart svg text {
165 | fill: white;
166 | }
167 |
168 |
169 | .dependencies div.circuitStatus {
170 | width:100%;
171 | white-space: nowrap;
172 | font-size: 9pt;
173 | margin: 0 auto;
174 | clear:both;
175 | text-align:right;
176 | padding-top: 4px;
177 | }
178 |
179 | .dependencies #hidden {
180 | width:1px;
181 | height:1px;
182 | background: lightgrey;
183 | display: none;
184 | }
185 |
186 |
187 |
188 | /* sparkline */
189 | .dependencies path {
190 | stroke: steelblue;
191 | stroke-width: 1;
192 | fill: none;
193 | }
194 |
195 | /** Thread Pool **/
196 | .dependencyThreadPools .spacer {
197 | width: 100%;
198 | margin: 0 auto;
199 | padding-top:4px;
200 | clear:both;
201 | }
202 |
203 |
204 | .dependencyThreadPools .last {
205 | margin-right: 0px;
206 | }
207 |
208 | .dependencyThreadPools span.loading {
209 | display: block;
210 | padding-top: 6%;
211 | padding-bottom: 6%;
212 | color: gray;
213 | text-align: center;
214 | }
215 |
216 | .dependencyThreadPools span.loading.failed {
217 | color: red;
218 | }
219 |
220 |
221 | .dependencyThreadPools div.monitor {
222 | float: left;
223 | margin-right:5px; /* these are tweaked to look good on desktop and iPad portrait, and fit things densely */
224 | margin-top:5px;
225 | }
226 |
227 | .dependencyThreadPools div.monitor p.name {
228 | font-weight:bold;
229 | font-size: 10pt;
230 | text-align: right;
231 | padding-bottom: 5px;
232 | }
233 |
234 | .dependencyThreadPools div.monitor_data {
235 | margin: 0 auto;
236 | }
237 |
238 | .dependencyThreadPools span.smaller {
239 | font-size: 8pt;
240 | color: grey;
241 | }
242 |
243 |
244 | .dependencyThreadPools div.tableRow {
245 | width:100%;
246 | white-space: nowrap;
247 | font-size: 8pt;
248 | margin: 0 auto;
249 | clear:both;
250 | }
251 |
252 | .dependencyThreadPools div.tableRow .cell {
253 | float:left;
254 | }
255 |
256 | .dependencyThreadPools div.tableRow .header {
257 | text-align:right;
258 | padding-right:5px;
259 | }
260 |
261 | .dependencyThreadPools div.tableRow .header.left {
262 | width:85px;
263 | }
264 |
265 | .dependencyThreadPools div.tableRow .header.right {
266 | width:75px;
267 | }
268 |
269 | .dependencyThreadPools div.tableRow .data {
270 | font-weight: bold;
271 | text-align:right;
272 | }
273 |
274 | .dependencyThreadPools div.tableRow .data.left {
275 | width:30px;
276 | }
277 |
278 | .dependencyThreadPools div.tableRow .data.right {
279 | width:45px;
280 | }
281 |
282 | .dependencyThreadPools div.monitor {
283 | width: 245px; /* we want a fixed width instead of percentage as I want the boxes to be a set size and then fill in as many as can fit in each row ... this allows 3 columns on an iPad */
284 | height: 110px;
285 | }
286 |
287 |
288 |
289 |
290 |
291 | /* override the HREF when we have specified it as a infotip to not act like a link */
292 | .dependencyThreadPools div.monitor_data a.infotip {
293 | text-decoration: none;
294 | cursor: default;
295 | }
296 |
297 | .dependencyThreadPools div.monitor_data a.rate {
298 | font-weight:bold;
299 | color: black;
300 | font-size: 11pt;
301 | }
302 |
303 | .dependencyThreadPools div.rate {
304 | padding-top: 1px;
305 | clear:both;
306 | text-align:right;
307 | }
308 |
309 | .dependencyThreadPools span.rate_value {
310 | font-weight:bold;
311 | }
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 | .dependencyThreadPools div.monitor div.chart {
320 | }
321 |
322 | .dependencyThreadPools div.monitor div.chart svg {
323 | }
324 |
325 | .dependencyThreadPools div.monitor div.chart svg text {
326 | fill: white;
327 | }
328 |
329 | .dependencyThreadPools #hidden {
330 | width:1px;
331 | height:1px;
332 | background: lightgrey;
333 | display: none;
334 | }
--------------------------------------------------------------------------------
/src/main/webapp/public/styles/main.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Varela Round';
3 | src: url('../fonts/varela_round-webfont.eot');
4 | src: url('../fonts/varela_round-webfont.eot?#iefix') format('embedded-opentype'),
5 | url('../fonts/varela_round-webfont.woff') format('woff'),
6 | url('../fonts/varela_round-webfont.ttf') format('truetype'),
7 | url('../fonts/varela_round-webfont.svg#varela_roundregular') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | @font-face {
13 | font-family: 'Montserrat';
14 | src: url('../fonts/montserrat-webfont.eot');
15 | src: url('../fonts/montserrat-webfont.eot?#iefix') format('embedded-opentype'),
16 | url('../fonts/montserrat-webfont.woff') format('woff'),
17 | url('../fonts/montserrat-webfont.ttf') format('truetype'),
18 | url('../fonts/montserrat-webfont.svg#montserratregular') format('svg');
19 | font-weight: normal;
20 | font-style: normal;
21 | }
22 |
23 | /** main classes **/
24 | html {
25 | position: relative;
26 | min-height: 100%;
27 | }
28 |
29 | body {
30 | background-color: #f1f1f1;
31 | font-family: "Varela Round",sans-serif;
32 | margin-bottom: 50px;
33 | }
34 |
35 | .content {
36 | margin-top: 20px;
37 | margin-bottom: 50px;
38 | }
39 |
40 | .center-block {
41 | display: block;
42 | margin-left: auto;
43 | margin-right: auto;
44 | }
45 |
46 | dd {
47 | margin-left: 10px;
48 | }
49 |
50 | a {
51 | color: #5fa134;
52 | -moz-user-select: none;
53 | }
54 |
55 | a:hover,
56 | a:active {
57 | color: #5fa134;
58 | }
59 |
60 | pre {
61 | white-space: pre;
62 | }
63 |
64 | .btn-inverse:hover, .btn-inverse:focus, .btn-inverse:active, .btn-inverse.active, .btn-inverse.disabled, .btn-inverse[disabled] {
65 | background-color: #222;
66 | color: #fff;
67 | }
68 |
69 | .glyphicon-white {
70 | color: #fff;
71 | }
72 |
73 | .btn-no-border-left {
74 | margin-left: -1px;
75 | border-bottom-left-radius: 0;
76 | border-top-left-radius: 0;
77 | }
78 |
79 | .btn-no-border-right {
80 | border-bottom-right-radius: 0;
81 | border-top-right-radius: 0;
82 | }
83 |
84 | .bottom-margin {
85 | margin-bottom: 20px;
86 | }
87 |
88 | .no-margin-left {
89 | margin-left: 0;
90 | }
91 |
92 | .main-loader-mask {
93 | display: block;
94 | position: absolute;
95 | height: auto;
96 | bottom: 0;
97 | top: 0;
98 | left: 0;
99 | right: 0;
100 | margin: 180px 15px 50px;
101 | background-color:#f1f1f1;
102 | z-index:9999;
103 | }
104 | .loading-big {
105 | display: block;
106 | position: absolute;
107 | bottom: 0;
108 | top: -30px;
109 | left: 0;
110 | right: 0;
111 | margin: 0;
112 | background: url("../img/loading-big.gif") no-repeat center;
113 | }
114 |
115 | /** TAB LEFT **/
116 | .tab-content {
117 | overflow: auto;
118 | }
119 |
120 | .tabs-left > .nav {
121 | border-right: 1px solid #ddd;
122 | float: left;
123 | margin-right: 19px;
124 | }
125 |
126 | /** NVD3 **/
127 | .nvd3 text, div.nvtooltip {
128 | font-size: 14px;
129 | }
130 |
131 | /** HEADER **/
132 | .header--navbar {
133 | margin: 0;
134 | }
135 |
136 | .navbar {
137 | border: 0;
138 | }
139 |
140 | .navbar-nav>li>span {
141 | padding-top: 15px;
142 | padding-bottom: 15px;
143 | line-height: 20px;
144 | display: block;
145 | font-size: 20px;
146 | }
147 |
148 | .navbar-static-top {
149 | font-family: Montserrat,sans-serif;
150 | position: absolute;
151 | z-index: 999;
152 | background-image: none;
153 | filter: none;
154 | background-color: #34302d;
155 | border: none;
156 | border-top: 4px solid #6db33f;
157 | box-shadow: none;
158 | position: relative;
159 | border-radius: 0;
160 | padding: 0;
161 | margin-bottom: 0;
162 | }
163 | .navbar-static-top .spring-logo--container {
164 | display: inline-block;
165 | }
166 | .navbar-static-top .spring-logo--container .spring-logo {
167 | margin: 12px 0 6px;
168 | width: 160px;
169 | height: 46px;
170 | display: inline-block;
171 | text-decoration: none;
172 | }
173 | .navbar-static-top .navbar-link.active a {
174 | background-color: #6db33f;
175 | box-shadow: none;
176 | }
177 | .navbar-static-top .navbar-link a {
178 | color: #eeeeee;
179 | text-transform: uppercase;
180 | text-shadow: none;
181 | font-size: 14px;
182 | line-height: 14px;
183 | padding: 28px 20px;
184 | transition: all 0.15s;
185 | -webkit-transition: all 0.15s;
186 | -moz-transition: all 0.15s;
187 | -o-transition: all 0.15s;
188 | -ms-transition: all 0.15s;
189 | }
190 | .navbar-static-top .navbar-link:hover a {
191 | color: #eeeeee;
192 | background-color: #6db33f;
193 | }
194 | .navbar-static-top .navbar-link.nav-search {
195 | padding: 20px 0 23px;
196 | }
197 | .navbar-static-top .navbar-link.nav-search .navbar-search--icon {
198 | color: #eeeeee;
199 | font-size: 24px;
200 | padding: 3px 16px 3px 18px;
201 | cursor: pointer;
202 | }
203 | .nav, .pagination, .carousel, .panel-title a {
204 | cursor: pointer;
205 | }
206 | .navbar-static-top .navbar-link.nav-search:hover .navbar-search--icon {
207 | text-shadow: 0 0 10px #6db33f;
208 | }
209 | .navbar-static-top .navbar-link.nav-search .search-input-close {
210 | display: none;
211 | }
212 | .navbar-static-top .navbar-link.nav-search.js-highlight {
213 | background-color: #6db33f;
214 | }
215 |
216 | a.spring-logo {
217 | background: url("../img/spring-logo.png") -1px -1px no-repeat;
218 | }
219 |
220 | a.spring-logo span {
221 | display: block;
222 | width: 160px;
223 | height: 46px;
224 | background: url("../img/spring-logo.png") -1px -48px no-repeat;
225 | opacity: 0;
226 | -moz-transition: opacity 0.12s ease-in-out;
227 | -webkit-transition: opacity 0.12s ease-in-out;
228 | -o-transition: opacity 0.12s ease-in-out;
229 | }
230 |
231 | a:hover.spring-logo span {
232 | opacity: 1;
233 | }
234 |
235 | .icon-bar {
236 | background-color: white;
237 | }
238 |
239 | /** FOOTER **/
240 | .footer {
241 | position: absolute;
242 | bottom: 0;
243 | width: 100%;
244 | background-color: #34302d;
245 | color: #eeeeee;
246 | height: 50px;
247 | padding: 20px 0;
248 | }
249 | .footer a {
250 | color: #6db33f;
251 | }
252 |
253 | /** LOGOS **/
254 | a.spring-boot-logo {
255 | background: url("../img/platform-spring-boot.png") -1px -1px no-repeat;
256 | }
257 |
258 | a.spring-boot-logo span {
259 | display: block;
260 | width: 160px;
261 | height: 50px;
262 | background: url("../img/platform-spring-boot.png") 20px -6px no-repeat;
263 | }
264 |
265 | a:hover.spring-boot-logo span {
266 | opacity: 1;
267 | }
268 |
269 | /** Status in Application Overview-View **/
270 | span.status-UP {
271 | color: #00AA00;
272 | font-weight: bold;
273 | }
274 |
275 | span.status-OFFLINE {
276 | color: #000000;
277 | font-weight: bold;
278 | }
279 |
280 | span.status-DOWN,
281 | span.status-OUT_OF_SERVICE {
282 | color: #DD0000;
283 | font-weight: bold;
284 | }
285 |
286 | span.status-UNKNOWN,
287 | span.status-STARTING {
288 | font-weight: bold;
289 | color: #FF8800;
290 | }
291 |
292 | span.refresh {
293 | display: block;
294 | width: 16px;
295 | height: 16px;
296 | background: url("../img/refresh.gif") no-repeat;
297 | }
298 |
299 | /** Headings and Tabs in Detail-View **/
300 | .panel-heading ,
301 | .panel-heading > .panel-title > a,
302 | .panel-heading > .panel-title > a:hover,
303 | .panel-heading > .panel-title > a:focus,
304 | .nav-tabs > .active > a,
305 | .nav-tabs > .active > a:hover,
306 | .nav-tabs > .active > a:focus,
307 | .nav-pills > li.active > a,
308 | .nav-pills > li.active > a:hover,
309 | .nav-pills > li.active > a:focus,
310 | .table > thead > tr > th {
311 | background-color: #34302D !important;
312 | border-color: #34302D !important;
313 | color: #f1f1f1 !important;
314 | }
315 |
316 | .nav > li > a {
317 | color: #838789;
318 | }
319 |
320 | .nav {
321 | margin-bottom: 0;
322 | }
323 |
324 | .nav-tabs > .active > a,
325 | .nav-tabs > .active > a:hover,
326 | .nav-tabs > .active > a:focus,
327 | .nav-pills > li.active > a,
328 | .nav-pills > li.active > a:hover,
329 | .nav-pills > li.active > a:focus,
330 | .table > thead > tr > th {
331 | background-color: #34302D !important;
332 | border-color: #34302D !important;
333 | color: #f1f1f1 !important;
334 | }
335 |
336 | .tabs-inverse > .nav-pills > li > a {
337 | background-color: #34302D !important;
338 | border-color: #34302D !important;
339 | color: #f1f1f1 !important;
340 | }
341 |
342 | .tabs-inverse > .nav-pills > li > a:hover,
343 | .tabs-inverse > .nav-pills > li > a:focus {
344 | color: #6db33f !important;
345 | }
346 |
347 | .tabs-inverse > .nav-pills > li.active > a {
348 | background-color: #6db33f !important;
349 | border-color: #6db33f !important;
350 | color: #f1f1f1 !important;
351 | }
352 |
353 |
354 | .tabs-inverse > .nav-pills > li.active > a > tab-heading > div > span.label-success {
355 | background-color: #34302D !important;
356 | }
357 |
358 | /** highlighted rows on Detail-Environment-View **/
359 | .table tr.highlight > td {
360 | background-color: #999 !important;
361 | font-weight: bold;
362 | }
363 |
364 | /** Tabs-list in domain-section in JMX-View **/
365 | .tabs-left > ul {
366 | max-width: 30%;
367 | overflow: hidden;
368 | }
369 |
370 | /** Circuit breaker **/
371 | .row-centered {
372 | width: 100%;
373 | margin: 0 auto;
374 | overflow: hidden;
375 | }
376 |
377 | .dependencies {
378 | line-height: 1;
379 | }
380 |
381 | .last {
382 | margin-right: 0px;
383 | }
384 |
385 | .menu_legend {
386 | margin-right: 20px;
387 | }
388 |
389 | .success {
390 | color: green;
391 | }
392 | .shortCircuited {
393 | color: blue;
394 | }
395 | .timeout {
396 | color: #FF9900; /* shade of orange */
397 | }
398 | .failure {
399 | color: red;
400 | }
401 |
402 | .rejected {
403 | color: purple;
404 | }
405 |
406 | .exceptionsThrown {
407 | color: brown;
408 | }
409 |
410 | /** Headings and Tabs in Detail-View **/
411 | .accordion-heading ,
412 | .accordion-heading > a,
413 | .accordion-heading > a:hover,
414 | .accordion-heading > a:focus,
415 | .nav-tabs > .active > a,
416 | .nav-tabs > .active > a:hover,
417 | .nav-tabs > .active > a:focus,
418 | .table > thead > tr > th {
419 | background-color: #34302D;
420 | border-color: #34302D;
421 | color: #f1f1f1;
422 | }
423 |
424 | .accordion-group,
425 | .boxed {
426 | border: 1px solid #34302D;
427 | }
428 |
429 | .sortable {
430 | cursor: pointer;
431 | }
432 |
433 | .sorted-ascending,
434 | .sorted-descending {
435 | color: #6db33f;
436 | }
437 |
438 | .sorted-ascending::after {
439 | content: " \25b2";
440 | }
441 | .sorted-descending::after {
442 | content: " \25bc";
443 | }
444 |
445 | .nav > li > a {
446 | color: #838789;
447 | }
448 |
449 | .nav {
450 | margin-bottom: 0;
451 | }
452 |
453 | /** Tabs-list in domain-section in JMX-View **/
454 | .tabs-left > ul {
455 | max-width: 30%;
456 | overflow: hidden;
457 | }
458 |
459 | /** health status**/
460 | dl.health-status > dt {
461 | border-bottom: 1px solid #ddd;
462 | background-color: #f9f9f9;
463 | line-height: 20px;
464 | padding: 8px;
465 | }
466 |
467 | dl.health-status {
468 | margin-top: 0px;
469 | }
470 |
471 | dl.health-status dl.health-status {
472 | margin-top: 20px !important;
473 | }
474 |
475 | dl.health-status td {
476 | border-top: none;
477 | border-bottom: 1px solid #ddd;
478 | }
479 |
480 |
481 | /** Metric Bars **/
482 | .bar-offset {
483 | float: left;
484 | box-sizing: border-box;
485 | height: 100%;
486 | }
487 |
488 | .with-marks {
489 | position: relative;
490 | }
491 |
492 | .bar-scale {
493 | position: relative;
494 | margin-top: -20px;
495 | margin-bottom: 40px;
496 | }
497 |
498 | .pitch-line {
499 | position: absolute;
500 | width: 1px;
501 | background-image: linear-gradient(to bottom, #333333 5px, transparent 5px);
502 | background-repeat: repeat-x;
503 | }
504 |
505 | .value-mark {
506 | position: absolute;
507 | border-left: 1px solid #ee5f5b;
508 | font-size: 12px;
509 | color: #fff;
510 | text-align: center;
511 | text-shadow: 0 -1px 0 rgba(0,0,0,0.25);
512 | }
513 |
514 | .mark-current {
515 | width: 1px;
516 | height: 20px;
517 | position: absolute;
518 | background-color: #dd514c;
519 | background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
520 | background-repeat: repeat-x;
521 | }
522 |
523 | /** Timeline **/
524 | .timeline {
525 | position: relative;
526 | list-style-type: none;
527 | padding: 1em 0 1em 120px;
528 | }
529 |
530 | .timeline:before {
531 | position: absolute;
532 | top: 0;
533 | content: ' ';
534 | display: block;
535 | width: 6px;
536 | height: 100%;
537 | margin-left: 0;
538 | background: rgb(80,80,80);
539 | background: -moz-linear-gradient(top, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%);
540 | background: -webkit-linear-gradient(top, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%);
541 | background: -o-linear-gradient(top, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%);
542 | background: -ms-linear-gradient(top, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%);
543 | background: linear-gradient(to bottom, rgba(80,80,80,0) 0%, rgb(80,80,80) 8%, rgb(80,80,80) 92%, rgba(80,80,80,0) 100%);
544 | z-index: 5;
545 | }
546 |
547 | .timeline li {
548 | padding: 1em 0;
549 | }
550 |
551 | .timeline li:after {
552 | content: "";
553 | display: block;
554 | height: 0;
555 | clear: both;
556 | visibility: hidden;
557 | }
558 |
559 | .timeline .event {
560 | position: relative;
561 | width: 100%;
562 | display: inline-block;
563 | left: 15px;
564 | padding-left: 5px;
565 | cursor:pointer;
566 | }
567 |
568 | .timeline .event .time {
569 | position: absolute;
570 | left: -120px;
571 | margin-left: -25px;
572 | display: inline-block;
573 | vertical-align: middle;
574 | text-align:right;
575 | width: 120px;
576 | }
577 |
578 | .timeline .event:before {
579 | content: ' ';
580 | display: block;
581 | width: 20px;
582 | height: 20px;
583 | background: #fff;
584 | border-radius: 10px;
585 | border: 4px solid #6db33f;
586 | z-index: 10;
587 | position: absolute;
588 | left: -6px;
589 | margin-left: -15px;
590 | }
591 |
592 | .timeline .event:hover:before {
593 | background: #ccc;
594 | }
595 |
596 | /** Application-Header **/
597 | .header--application .navbar-inner,
598 | .header--application-urls .navbar-inner {
599 | font-family: Montserrat,sans-serif;
600 | z-index: 999;
601 | filter: none;
602 | background: #666 none;
603 | border: none;
604 | border-bottom: 1px solid #34302D;
605 | box-shadow: none;
606 | position: relative;
607 | border-radius: 0;
608 | padding: 0;
609 | }
610 |
611 | .header--application .application--name {
612 | font-family: "Montserrat",sans-serif;
613 | font-size: 24px;
614 | line-height: 24px;
615 | color: #ebf1e7;
616 | display: inline-block;
617 | margin-top: 10px;
618 | margin-bottom: 10px;
619 | }
620 |
621 | .header--application .navbar-inner .navbar-link a {
622 | color: #eeeeee;
623 | text-transform: uppercase;
624 | text-shadow: none;
625 | font-size: 14px;
626 | line-height: 14px;
627 | padding: 16px 10px;
628 | transition: all 0.15s;
629 | -webkit-transition: all 0.15s;
630 | -moz-transition: all 0.15s;
631 | -o-transition: all 0.15s;
632 | -ms-transition: all 0.15s;
633 | }
634 |
635 | .header--application .navbar-inner .navbar-link:hover a ,
636 | .header--application .navbar-inner .navbar-link.active a {
637 | color: #ebf1e7;
638 | background-color: #666;
639 | box-shadow: none;
640 | }
641 |
642 | .header--application .navbar-inner .navbar-link a>span {
643 | transition: color 0.15s;
644 | -webkit-transition: color 0.15s;
645 | -moz-transition: color 0.15s;
646 | -o-transition: color 0.15s;
647 | -ms-transition: color 0.15s;
648 | }
649 |
650 | .header--application .navbar-inner .navbar-link:hover a>span,
651 | .header--application .navbar-inner .navbar-link.active a>span {
652 | border-top: 2px solid #6db33f;
653 | border-bottom: 2px solid #6db33f;
654 | }
655 |
656 | .header--application-urls .navbar-inner {
657 | background-color: #dedede;
658 | }
659 |
660 | .header--application-urls .navbar-inner li>a,
661 | .header--application-urls .navbar-inner li>a:hover {
662 | text-shadow: none;
663 | color: #34302D;
664 | }
665 |
666 | .header--application-urls .navbar-inner li>a:hover {
667 | text-decoration:underline;
668 | }
669 |
670 | /** ENV **/
671 | .refresh-env-alert {
672 | margin-top: 50px;
673 | }
--------------------------------------------------------------------------------
/src/main/webapp/public/views/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | This is an administration GUI for Spring Cloud applications. All applications has to register to the service registry (eg: Eureka).
4 |
5 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps.html:
--------------------------------------------------------------------------------
1 |
17 |
18 |
28 |
29 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps/details.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Error: {{ error }}
5 |
6 |
7 |
8 |
9 | Application raw JSON
10 |
11 |
12 | {{ key }} {{ value | flatten }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Git raw JSON
21 |
22 |
23 | {{ key }} {{ value | flatten }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Health Checks raw JSON
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | Memory raw JSON
44 |
45 |
46 |
47 | Total Memory ({{ metrics['mem.used'] / 1024 | number:2 }}M / {{ metrics['mem'] / 1024 | number:2 }}M)
48 |
49 |
51 | {{memPercentage}}%
52 |
53 |
54 |
55 |
56 |
57 |
58 | Heap Memory ({{ metrics['heap.used'] / 1024 | number:2 }}M / {{ metrics['heap.committed'] / 1024 | number:2 }}M)
59 |
60 |
62 | {{heapPercentage}}%
63 |
64 |
65 |
66 |
67 |
68 | Initial Heap (-Xms)
69 | {{metrics['heap.init'] / 1024 | number:2}}M
70 |
71 |
72 | Maximum Heap (-Xmx)
73 | {{metrics['heap'] / 1024 | number:2}}M
74 |
75 |
76 |
77 |
78 |
79 |
80 | JVM raw JSON
81 |
82 |
83 | Systemload (last minute average)
84 |
85 |
87 | {{metrics['systemload.averagepercent'] | number:0}}%
88 |
89 |
90 |
91 |
92 |
93 | Uptime
94 | {{ metrics['uptime'] + ticks | timeInterval }} [d:h:m:s]
95 |
96 |
97 | Available Processors
98 | {{ metrics['processors'] }}
99 |
100 |
101 | Current loaded Classes
102 | {{ metrics['classes']}}
103 |
104 |
105 | Total loaded Classes
106 | {{ metrics['classes.loaded']}}
107 |
108 |
109 | Unloaded Classes
110 | {{ metrics['classes.unloaded']}}
111 |
112 |
113 | Threads
114 | {{ metrics['threads'] }} total / {{ metrics['threads.daemon'] }} daemon / {{ metrics['threads.peak'] }} peak
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Garbage Collection raw JSON
123 |
124 | {{name | capitalize}} GC Count
125 | {{value.count}}
126 |
127 |
128 | {{name | capitalize}} GC Time
129 | {{value.time}} ms
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | Servlet Container
138 |
139 |
140 | Http sessions
141 | {{ metrics['httpsessions.active'] }} active / {{ metrics['httpsessions.max'] == -1 ? 'unlimited' : metrics['httpsessions.max'] + ' max' }}
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | Datasources
150 |
151 |
152 | {{name | capitalize}} Datasource Connections (active: {{ value.active }})
153 |
154 |
156 | {{value.usage * 100 | number}}%
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
178 |
179 |
180 |
181 |
200 |
201 |
216 |
217 |
247 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps/details/classpath.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Classpath raw JSON
7 |
8 |
9 |
10 |
11 |
12 | {{ element }}
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps/details/metrics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Counters
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Gauges
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps/environment.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Error: {{ error }}
4 |
5 |
6 |
7 | Profiles raw JSON
8 |
9 |
10 | {{ profile }}
11 |
12 |
13 | No profiles active
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Overriden properties
23 |
24 |
25 | {{ key }}
26 | {{ value }}
27 |
28 |
29 |
30 |
31 |
32 | {{key}}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | Refresh beans
46 |
47 |
48 | Overrride values!
49 | Reset overrides
50 |
51 |
52 |
53 |
{{ overrides.changes }}
54 |
55 |
56 | Error: {{ overrides.error }}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | {{item.key}}
77 |
78 |
79 | {{ property.key }}
80 | {{ property.value }}
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps/history.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Service registry history
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Date
14 | Instance
15 |
16 |
17 |
18 |
19 | {{ instance.date | date : 'short' }}
20 | {{ instance.id }}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Date
30 | Instance
31 |
32 |
33 |
34 |
35 | {{ instance.date | date : 'short' }}
36 | {{ instance.id }}
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps/jmx.html:
--------------------------------------------------------------------------------
1 |
2 | Error: {{ error }}
3 |
4 |
5 |
6 |
7 |
8 | Domain {{domain.name}}
9 |
10 |
11 |
12 |
13 |
14 | MBean {{bean.name }}
15 |
16 |
17 |
18 | {{bean.name}}{{bean.description}}
19 |
20 |
21 | Id
22 | {{ bean.id }}
23 | {{ name }}
24 | {{ value }}
25 |
26 |
27 |
57 |
58 |
59 | Operations
60 |
61 |
62 | {{name}}
63 | {{op.ret}}
64 | {{op.desc}}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
102 |
103 |
125 |
126 |
153 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps/logging.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ filteredLoggers.length }}/{{ loggers.length }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{ logger.name }}
21 |
22 | TRACE
23 | DEBUG
24 | INFO
25 | WARN
26 | ERROR
27 | OFF
28 |
29 |
30 |
31 |
32 |
33 | show more
34 |
35 |
36 |
37 |
38 | show all
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps/threads.html:
--------------------------------------------------------------------------------
1 |
2 | Error: {{ error }}
3 |
4 |
5 |
6 |
7 |
8 | Dump all threads!
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | New {{ threadStats.NEW }}
17 |
18 |
19 | Runnable {{ threadStats.RUNNABLE }}
20 |
21 |
22 | Blocked {{ threadStats.BLOCKED }}
23 |
24 |
25 | Waiting {{ threadStats.WAITING }}
26 |
27 |
28 | Timed waiting {{ threadStats.TIMED_WAITING }}
29 |
30 |
31 | Terminated {{ threadStats.TERMINATED }}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | {{thread.threadId}}
42 | {{thread.threadName}}
43 | {{thread.threadState}} suspended
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Blocked count {{thread.blockedCount}}
52 | Blocked time {{thread.blockedTime}}
53 | Waited count {{thread.waitedCount}}
54 | Waited time {{thread.waitedTime}}
55 |
56 |
57 |
58 |
59 |
60 | Lock name {{thread.lockName}}
61 | Lock owner id {{thread.lockOwnerId}}
62 | Lock owner name {{thread.lockOwnerName}}
63 |
64 |
65 |
66 | {{el.className}}.{{el.methodName}}({{el.fileName}}:{{el.lineNumber}}) native
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/apps/trace.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Error: {{ error }}
4 |
5 |
18 |
19 |
20 |
21 |
22 | {{trace.timestamp | date:'HH:mm:ss.sss'}}
23 | {{trace.timestamp | date:'dd.MM.yyyy'}}
24 |
25 |
{{trace.info.method}} {{trace.info.path}}
26 |
{{trace.info | json}}
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/circuit-breaker/circuit-container.html:
--------------------------------------------------------------------------------
1 |
2 | <%
3 | var displayName = name;
4 | var toolTip = "";
5 | if(displayName.length > 32) {
6 | displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length);
7 | toolTip = "title=\"" + name + "\"";
8 | }
9 | %>
10 |
11 |
12 |
13 | <% if(includeDetailIcon) { %>
14 |
style="padding-right:16px">
15 | <%= displayName %>
16 |
17 |
18 | <% } else { %>
19 |
><%= displayName %>
20 | <% } %>
21 |
22 |
25 |
26 |
27 |
40 |
41 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/circuit-breaker/circuit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
14 |
19 |
20 |
21 |
24 |
27 |
28 |
29 | <% if(propertyValue_circuitBreakerForceClosed) { %>
30 | [ Forced Closed ]
31 | <% } %>
32 | <% if(propertyValue_circuitBreakerForceOpen) { %>
33 | Circuit Forced Open
34 | <% } else { %>
35 | <% if(isCircuitBreakerOpen == reportingHosts) { %>
36 | Circuit Open
37 | <% } else if(isCircuitBreakerOpen == 0) { %>
38 | Circuit Closed
39 | <% } else {
40 | /* We have some circuits that are open */
41 | %>
42 | Circuit <%= isCircuitBreakerOpen.replace("true", "Open").replace("false", "Closed") %>)
43 | <% } %>
44 | <% } %>
45 |
46 |
47 |
48 |
49 |
50 | <% if(typeof reportingHosts != 'undefined') { %>
51 |
52 |
<%= reportingHosts %>
53 | <% } else { %>
54 |
55 |
Single
56 | <% } %>
57 |
58 |
<%= getInstanceAverage(latencyExecute['90'], reportingHosts, false) %> ms
59 |
60 |
61 |
62 |
<%= getInstanceAverage(latencyExecute['50'], reportingHosts, false) %> ms
63 |
64 |
<%= getInstanceAverage(latencyExecute['99'], reportingHosts, false) %> ms
65 |
66 |
67 |
68 |
<%= latencyExecute_mean %> ms
69 |
70 |
<%= getInstanceAverage(latencyExecute['99.5'], reportingHosts, false) %> ms
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/circuit-breaker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Circuit breakers status
13 | {{ subtitle }}
14 |
15 |
16 |
17 |
18 | Commands
19 |
20 |
21 |
22 | Sort
23 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 | Thread pools
51 |
52 |
53 | Sort
54 |
58 |
59 |
60 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/circuit-breaker/threadpool-container.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%
4 | var displayName = name;
5 | var toolTip = "";
6 | if(displayName.length > 32) {
7 | displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length);
8 | toolTip = "title=\"" + name + "\"";
9 | }
10 | %>
11 |
12 |
13 |
14 |
17 |
18 |
19 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/circuit-breaker/threadpool.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
<%= currentActiveCount%>
16 |
17 |
18 |
<%= addCommas(rollingMaxActiveThreads)%>
19 |
20 |
21 |
22 |
23 |
<%= currentQueueSize %>
24 |
25 |
<%= addCommas(rollingCountThreadsExecuted)%>
26 |
27 |
28 |
29 |
<%= currentPoolSize %>
30 |
31 |
<%= propertyValue_queueSizeRejectionThreshold %>
32 |
33 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/overview.html:
--------------------------------------------------------------------------------
1 |
2 |
Spring Cloud Dashboard
3 | Here you'll find all Spring Cloud applications that registered on the registry.
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{application.name}}
17 |
18 | {{ application.instanceUp }} / {{ application.instanceCount }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/main/webapp/public/views/overview.selected.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{selectedApp.name}}
5 |
6 |
9 |
10 |
11 |
12 |
13 | Instance
14 | Version
15 | Info
16 | Status
17 | Health
18 |
19 |
20 |
21 |
22 | {{ instance.name }}{{ instance.url }}
23 | {{ instance.version }}
24 | {{value | flatten:name}}
25 | {{ instance.status }}
26 |
27 | {{ instance.health }}
28 |
29 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/webapp/templates/dashboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Spring Cloud Dashboard
7 |
8 |
9 |
10 |
11 |
12 |
13 |
26 |
27 |
28 |
53 |
54 |
57 |
58 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/test/java/com/github/vanroy/cloud/dashboard/DashboardApplicationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 | package com.github.vanroy.cloud.dashboard;
17 |
18 | import java.util.Collection;
19 | import java.util.Date;
20 | import java.util.List;
21 | import java.util.Map;
22 |
23 | import com.github.vanroy.cloud.dashboard.repository.RegistryRepository;
24 | import com.github.vanroy.cloud.dashboard.turbine.MockStreamServlet;
25 | import org.junit.Test;
26 | import org.junit.runner.RunWith;
27 | import org.springframework.boot.SpringApplication;
28 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
29 | import org.springframework.boot.context.embedded.LocalServerPort;
30 | import org.springframework.boot.test.context.SpringBootTest;
31 | import org.springframework.boot.test.web.client.TestRestTemplate;
32 | import org.springframework.boot.web.servlet.ServletRegistrationBean;
33 | import org.springframework.context.annotation.Bean;
34 | import org.springframework.context.annotation.Configuration;
35 | import org.springframework.http.HttpStatus;
36 | import org.springframework.http.ResponseEntity;
37 | import org.springframework.test.context.junit4.SpringRunner;
38 |
39 | import com.google.common.collect.ImmutableList;
40 |
41 | import com.github.vanroy.cloud.dashboard.config.EnableCloudDashboard;
42 | import com.github.vanroy.cloud.dashboard.model.Application;
43 | import com.github.vanroy.cloud.dashboard.model.Instance;
44 | import com.github.vanroy.cloud.dashboard.model.InstanceHistory;
45 | import com.github.vanroy.cloud.dashboard.repository.ApplicationRepository;
46 |
47 | import static org.junit.Assert.assertEquals;
48 | import static org.junit.Assert.assertNotNull;
49 |
50 | /**
51 | *
52 | * Integration test to verify the correct functionality of the REST API.
53 | *
54 | * @author Dennis Schulte
55 | */
56 | @RunWith(SpringRunner.class)
57 | @SpringBootTest(
58 | classes = DashboardApplicationTest.DashboardApplication.class,
59 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
60 | )
61 | public class DashboardApplicationTest {
62 |
63 | @LocalServerPort
64 | private int port = 0;
65 |
66 | @Test
67 | public void testGetApplications() {
68 | @SuppressWarnings("rawtypes")
69 | ResponseEntity entity = new TestRestTemplate().getForEntity("http://localhost:" + port + "/api/applications", List.class);
70 | assertNotNull(entity.getBody());
71 | assertEquals(2, entity.getBody().size());
72 | Map application = (Map) entity.getBody().get(0);
73 | assertEquals("MESSAGES", application.get("name"));
74 | assertEquals(3, ((List)application.get("instances")).size());
75 | assertEquals(HttpStatus.OK, entity.getStatusCode());
76 | }
77 |
78 | @Configuration
79 | @EnableAutoConfiguration
80 | @EnableCloudDashboard
81 | public static class DashboardApplication {
82 |
83 | public static void main(String[] args) {
84 | SpringApplication.run(DashboardApplicationTest.DashboardApplication.class, args);
85 | }
86 |
87 | @Bean
88 | public ServletRegistrationBean hystrixStreamServlet() {
89 | return new ServletRegistrationBean(new MockStreamServlet("/hystrix.stream"), "/hystrix.stream");
90 | }
91 |
92 | @Bean(name="applicationRepository")
93 | public ApplicationRepository eurekaRepository() {
94 | return new ApplicationRepository() {
95 |
96 | @Override
97 | public Collection findAll() {
98 | return ImmutableList.of(
99 | new Application("MESSAGES",
100 | ImmutableList.of(
101 | new Instance("http://localhost:8761", "INSTANCE 1", "ID1", "UP"),
102 | new Instance("http://localhost:8002", "INSTANCE 2", "ID2", "DOWN"),
103 | new Instance("http://localhost:8003", "INSTANCE 3", "ID3", "STARTING")
104 | )),
105 | new Application("FRONT",
106 | ImmutableList.of(
107 | new Instance("http://localhost:8001", "FRONT INSTANCE 1", "FRONT ID1","OUT_OF_SERVICE"),
108 | new Instance("http://localhost:8002", "FRONT INSTANCE 2", "FRONT ID2","DOWN"),
109 | new Instance("http://localhost:8003", "FRONT INSTANCE 3", "FRONT ID3","UNKNOWN")
110 | ))
111 | );
112 | }
113 |
114 | @Override
115 | public Application findByName(String name) {
116 | return new Application(name, null);
117 | }
118 |
119 | @Override
120 | public String getApplicationCircuitBreakerStreamUrl(String name) {
121 | if(name.equalsIgnoreCase("MESSAGES")) {
122 | return "http://localhost:8761/hystrix.stream";
123 | } else {
124 | return "http://localhost:8761/hystrixFail.stream";
125 | }
126 | //return "http://localhost:8001/turbine.stream?cluster="+name;
127 | }
128 |
129 | @Override
130 | public String getInstanceCircuitBreakerStreamUrl(String instanceId) {
131 | if(instanceId.equalsIgnoreCase("ID1")) {
132 | return "http://localhost:8761/hystrix.stream";
133 | } else {
134 | return "http://localhost:8761/hystrixFail.stream";
135 | }
136 | }
137 |
138 | @Override
139 | public Instance findInstance(String id) {
140 | return new Instance("http://localhost:8002", "INSTANCE "+id, id, "UP");
141 | }
142 |
143 | @Override
144 | public String getInstanceManagementUrl(String id) {
145 | return "http://localhost:8761/";
146 | }
147 | };
148 | }
149 |
150 | @Bean
151 | public RegistryRepository eurekaRegistryRepository() {
152 |
153 | return new RegistryRepository() {
154 |
155 | @Override
156 | public List getRegisteredInstanceHistory() {
157 | return ImmutableList.of(
158 | new InstanceHistory("INSTANCE 1", new Date()),
159 | new InstanceHistory("INSTANCE 2", new Date()),
160 | new InstanceHistory("INSTANCE 3", new Date())
161 | );
162 | }
163 |
164 | @Override
165 | public List getCanceledInstanceHistory() {
166 | return ImmutableList.of(
167 | new InstanceHistory("CANCELLED INSTANCE 1", new Date()),
168 | new InstanceHistory("CANCELLED INSTANCE 2", new Date()),
169 | new InstanceHistory("CANCELLED INSTANCE 3", new Date())
170 | );
171 | }
172 | };
173 | }
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/src/test/java/com/github/vanroy/cloud/dashboard/controller/ApplicationControllerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
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.
15 | */
16 | package com.github.vanroy.cloud.dashboard.controller;
17 |
18 | public class ApplicationControllerTest {
19 |
20 | private ApplicationController controller;
21 | /*
22 | @Before
23 | public void setup() {
24 | controller = new RegistryController(new ApplicationRegistry(new SimpleApplicationStore(),
25 | new HashingApplicationUrlIdGenerator()));
26 | }
27 |
28 | @Test
29 | public void get() {
30 | Application app = new Application("http://localhost", "FOO");
31 | app = controller.register(app).getBody();
32 |
33 | ResponseEntity response = controller.get(app.getId());
34 | assertEquals(HttpStatus.OK, response.getStatusCode());
35 | assertEquals("http://localhost", response.getBody().getUrl());
36 | assertEquals("FOO", response.getBody().getName());
37 | }
38 |
39 | @Test
40 | public void get_notFound() {
41 | controller.register(new Application("http://localhost", "FOO"));
42 |
43 | ResponseEntity> response = controller.get("unknown");
44 | assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
45 | }
46 |
47 | @Test
48 | public void applications() {
49 | Application app = new Application("http://localhost", "FOO");
50 | app = controller.register(app).getBody();
51 |
52 | Collection applications = controller.applications(null);
53 | assertEquals(1, applications.size());
54 | assertTrue(applications.contains(app));
55 | }
56 |
57 | @Test
58 | public void applicationsByName() {
59 | Application app = new Application("http://localhost:2", "FOO");
60 | app = controller.register(app).getBody();
61 | Application app2 = new Application("http://localhost:1", "FOO");
62 | app2 = controller.register(app2).getBody();
63 | Application app3 = new Application("http://localhost:3", "BAR");
64 | controller.register(app3).getBody();
65 |
66 | Collection applications = controller.applications("FOO");
67 | assertEquals(2, applications.size());
68 | assertTrue(applications.contains(app));
69 | assertTrue(applications.contains(app2));
70 | assertFalse(applications.contains(app3));
71 | }
72 | */
73 | }
74 |
--------------------------------------------------------------------------------
/src/test/java/com/github/vanroy/cloud/dashboard/turbine/MockStreamServlet.java:
--------------------------------------------------------------------------------
1 | package com.github.vanroy.cloud.dashboard.turbine;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 | import java.io.StringWriter;
8 | import java.nio.charset.Charset;
9 |
10 | import javax.servlet.ServletException;
11 | import javax.servlet.http.HttpServlet;
12 | import javax.servlet.http.HttpServletRequest;
13 | import javax.servlet.http.HttpServletResponse;
14 |
15 | import org.slf4j.Logger;
16 | import org.slf4j.LoggerFactory;
17 |
18 | /**
19 | * Simulate an event stream URL by retrieving pre-canned data instead of going to live servers.
20 | */
21 | public class MockStreamServlet extends HttpServlet {
22 | private static final long serialVersionUID = 1L;
23 | private static final Logger logger = LoggerFactory.getLogger(MockStreamServlet.class);
24 | private String fileName;
25 |
26 | public MockStreamServlet(String fileName) {
27 | super();
28 | this.fileName = fileName;
29 | }
30 |
31 | /**
32 | * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
33 | */
34 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
35 |
36 | if("HEAD".equalsIgnoreCase(request.getMethod())) {
37 | response.setStatus(200);
38 | return;
39 | }
40 |
41 | int delay = 500;
42 | String delayArg = request.getParameter("delay");
43 | if (delayArg != null) {
44 | delay = Integer.parseInt(delayArg);
45 | }
46 |
47 | int batch = 1;
48 | String batchArg = request.getParameter("batch");
49 | if (batchArg != null) {
50 | batch = Integer.parseInt(batchArg);
51 | }
52 |
53 | String data = getFileFromPackage(fileName);
54 | String lines[] = data.split("\n");
55 |
56 | response.setContentType("text/event-stream");
57 | response.setCharacterEncoding("UTF-8");
58 |
59 | int batchCount = 0;
60 | // loop forever unless the user closes the connection
61 | for (;;) {
62 | for (String s : lines) {
63 | s = s.trim();
64 | if (s.length() > 0) {
65 | try {
66 | response.getWriter().println(s);
67 | response.getWriter().println(""); // a newline is needed after each line for the events to trigger
68 | response.getWriter().flush();
69 | batchCount++;
70 | } catch (Exception e) {
71 | logger.warn("Exception writing mock data to output.", e);
72 | // most likely the user closed the connection
73 | return;
74 | }
75 | if (batchCount == batch) {
76 | // we insert the delay whenever we finish a batch
77 | try {
78 | // simulate the delays we get from the real feed
79 | Thread.sleep(delay);
80 | } catch (InterruptedException e) {
81 | // ignore
82 | }
83 | // reset
84 | batchCount = 0;
85 | }
86 | }
87 | }
88 | }
89 | }
90 |
91 | private String getFileFromPackage(String filename) {
92 | try {
93 | InputStream is = this.getClass().getResourceAsStream(filename);
94 | try {
95 | /* this is FAR too much work just to get a string from a file */
96 | BufferedReader in = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
97 | StringWriter s = new StringWriter();
98 | int c = -1;
99 | while ((c = in.read()) > -1) {
100 | s.write(c);
101 | }
102 | return s.toString();
103 | } finally {
104 | is.close();
105 | }
106 | } catch (Exception e) {
107 | throw new RuntimeException("Could not find file: " + filename, e);
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.resources.cachePeriod=3600
2 | server.port=8761
3 | info.version=1.0.0
4 | info.stage=test
5 | logging.file=/tmp/log.log
6 | spring.application.name=spring-cloud-dashboard-example
--------------------------------------------------------------------------------
/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------