Commit a0cc96c7 by Johannes Edmeier

Resolve dynamic views lazily

When the list contains a lots of applications many requests are made for dynamic views (e.g. heapdump, flyway, logfile, ...). With this commit the availability check for these views is deferred until the user opens toggles the dropdown. This should lead to far fewer requests. fixes #346 Additionally the url list is not anymore overlapped then having a lot of tabs for the detail views. fixes #347
parent cbb2dcb4
......@@ -15,80 +15,76 @@
*/
(function (sbaModules, angular) {
'use strict';
var module = angular.module('sba-applications-activiti', ['sba-applications']);
sbaModules.push(module.name);
'use strict';
var module = angular.module('sba-applications-activiti', ['sba-applications']);
sbaModules.push(module.name);
module.controller('activitiCtrl', ['$scope', '$http', 'application', function ($scope, $http, application) {
$scope.application = application;
$http.get('api/applications/' + application.id + '/activiti').then(function (response) {
var activiti = response.data;
$scope.summary = [];
$scope.summary.push({
key: 'Completed Task Count Today',
value: activiti.completedTaskCountToday
});
$scope.summary.push({
key: 'Process Definition Count',
value: activiti.processDefinitionCount
});
$scope.summary.push({
key: 'Cached Process Definition Count',
value: activiti.cachedProcessDefinitionCount
});
$scope.summary.push({
key: 'Completed Task Count',
value: activiti.completedTaskCount
});
$scope.summary.push({
key: 'Completed Activities',
value: activiti.completedActivities
});
$scope.summary.push({
key: 'Open Task Count',
value: activiti.openTaskCount
});
$scope.processes = [];
for (var i = 0; i < activiti.deployedProcessDefinitions.length; i++) {
var process = activiti.deployedProcessDefinitions[i];
var runningProcessInstanceCount = activiti.runningProcessInstanceCount[process];
var completedProcessInstanceCount = activiti.completedProcessInstanceCount[process];
$scope.processes.push({
name: process,
running: runningProcessInstanceCount,
completed: completedProcessInstanceCount
});
}
}).catch(function (response) {
$scope.error = response.data;
module.controller('activitiCtrl', ['$scope', '$http', 'application', function ($scope, $http, application) {
$scope.application = application;
$http.get('api/applications/' + application.id + '/activiti').then(function (response) {
var activiti = response.data;
$scope.summary = [];
$scope.summary.push({
key: 'Completed Task Count Today',
value: activiti.completedTaskCountToday
});
$scope.summary.push({
key: 'Process Definition Count',
value: activiti.processDefinitionCount
});
$scope.summary.push({
key: 'Cached Process Definition Count',
value: activiti.cachedProcessDefinitionCount
});
$scope.summary.push({
key: 'Completed Task Count',
value: activiti.completedTaskCount
});
$scope.summary.push({
key: 'Completed Activities',
value: activiti.completedActivities
});
$scope.summary.push({
key: 'Open Task Count',
value: activiti.openTaskCount
});
$scope.processes = [];
for (var i = 0; i < activiti.deployedProcessDefinitions.length; i++) {
var process = activiti.deployedProcessDefinitions[i];
var runningProcessInstanceCount = activiti.runningProcessInstanceCount[process];
var completedProcessInstanceCount = activiti.completedProcessInstanceCount[process];
$scope.processes.push({
name: process,
running: runningProcessInstanceCount,
completed: completedProcessInstanceCount
});
}]);
}
}).catch(function (response) {
$scope.error = response.data;
});
}]);
module.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('applications.activiti', {
url: '/activiti',
templateUrl: 'applications-activiti/activiti.html',
controller: 'activitiCtrl'
});
}]);
module.run(['ApplicationViews', '$http', function (ApplicationViews, $http) {
ApplicationViews.register({
order: 100,
title: 'Activiti',
state: 'applications.activiti',
show: function (application) {
if (!application.managementUrl || !application.statusInfo.status || application.statusInfo.status === 'OFFLINE') {
return false;
}
module.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('applications.activiti', {
url: '/activiti',
templateUrl: 'applications-activiti/activiti.html',
controller: 'activitiCtrl'
});
}]);
return $http.get('api/applications/' + application.id + '/configprops').then(
function (response) {
return response.data.processEngineEndpoint !== undefined;
}).catch(function () {
return false;
});
}
});
}]);
module.run(['ApplicationViews', '$http', function (ApplicationViews, $http) {
ApplicationViews.register({
order: 100,
title: 'Activiti',
state: 'applications.activiti',
show: function (application) {
return $http.get('api/applications/' + application.id + '/configprops').then(
function (response) {
return response.data.processEngineEndpoint !== undefined;
}).catch(function () {
return false;
});
}
});
}]);
} (sbaModules, angular));
......@@ -59,9 +59,6 @@ module.run(function (ApplicationViews, $sce, $q, $http) {
title: $sce.trustAsHtml('<i class="fa fa-gear fa-fw"></i>Hystrix'),
state: 'applications.hystrix',
show: function (application) {
if (!application.managementUrl || !application.statusInfo.status || application.statusInfo.status === 'OFFLINE') {
return false;
}
return isEventSourceAvailable('api/applications/' + application.id + '/hystrix.stream');
}
});
......
......@@ -47,9 +47,6 @@ module.run(function (ApplicationViews, $sce) {
ApplicationViews.register({
order: 0,
title: $sce.trustAsHtml('<i class="fa fa-info fa-fw"></i>Details'),
state: 'applications.details',
show: function (application) {
return application.managementUrl && application.statusInfo.status !== null && application.statusInfo.status !== 'OFFLINE';
}
state: 'applications.details'
});
});
......@@ -36,9 +36,6 @@ module.run(function (ApplicationViews, $sce) {
ApplicationViews.register({
order: 10,
title: $sce.trustAsHtml('<i class="fa fa-server fa-fw"></i>Environment'),
state: 'applications.environment',
show: function (application) {
return application.managementUrl && application.statusInfo.status !== null && application.statusInfo.status !== 'OFFLINE';
}
state: 'applications.environment'
});
});
......@@ -36,9 +36,6 @@ module.run(function (ApplicationViews, $http, $sce) {
title: $sce.trustAsHtml('<i class="fa fa-database fa-fw"></i>Flyway'),
state: 'applications.flyway',
show: function (application) {
if (!application.managementUrl || !application.statusInfo.status || application.statusInfo.status === 'OFFLINE') {
return false;
}
return $http.head('api/applications/' + application.id + '/flyway').then(function () {
return true;
}).catch(function () {
......
......@@ -28,9 +28,6 @@ module.run(function ($sce, $http, ApplicationViews) {
href: 'api/applications/{id}/heapdump',
target: '_blank',
show: function (application) {
if (!application.managementUrl || !application.statusInfo.status || application.statusInfo.status === 'OFFLINE') {
return false;
}
return $http({ method: 'OPTIONS', url: 'api/applications/' + application.id + '/heapdump' }).then(function (response) {
return response.headers('Allow') === 'GET,HEAD'; //Test the exact headers, in case the DispatcherServlet responses to the request for older boot-versions
}).catch(function () {
......
......@@ -44,9 +44,6 @@ module.run(function (ApplicationViews, $sce) {
ApplicationViews.register({
order: 40,
title: $sce.trustAsHtml('<i class="fa fa-cogs fa-fw"></i>JMX'),
state: 'applications.jmx',
show: function (application) {
return application.managementUrl && application.statusInfo.status !== null && application.statusInfo.status !== 'OFFLINE';
}
state: 'applications.jmx'
});
});
......@@ -37,9 +37,6 @@ module.run(function (ApplicationViews, $http, $sce) {
title: $sce.trustAsHtml('<i class="fa fa-database fa-fw"></i>Liquibase'),
state: 'applications.liquibase',
show: function (application) {
if (!application.managementUrl || !application.statusInfo.status || application.statusInfo.status === 'OFFLINE') {
return false;
}
return $http.head('api/applications/' + application.id + '/liquibase').then(function () {
return true;
}).catch(function () {
......
......@@ -40,9 +40,6 @@ module.run(function (ApplicationViews, $sce, $http) {
title: $sce.trustAsHtml('<i class="fa fa-file-text-o fa-fw"></i>Log'),
state: 'applications.logfile',
show: function (application) {
if (!application.managementUrl || !application.statusInfo.status || application.statusInfo.status === 'OFFLINE') {
return false;
}
return $http.head('api/applications/' + application.id + '/logfile').then(function () {
return true;
}).catch(function () {
......
......@@ -37,9 +37,6 @@ module.run(function (ApplicationViews, $sce) {
ApplicationViews.register({
order: 30,
title: $sce.trustAsHtml('<i class="fa fa-sliders fa-fw"></i>Logging'),
state: 'applications.logging',
show: function (application) {
return application.managementUrl && application.statusInfo.status !== null && application.statusInfo.status !== 'OFFLINE';
}
state: 'applications.logging'
});
});
......@@ -37,9 +37,6 @@ module.run(function (ApplicationViews, $sce) {
ApplicationViews.register({
order: 5,
title: $sce.trustAsHtml('<i class="fa fa-bar-chart fa-fw"></i>Metrics'),
state: 'applications.metrics',
show: function (application) {
return application.managementUrl && application.statusInfo.status !== null && application.statusInfo.status !== 'OFFLINE';
}
state: 'applications.metrics'
});
});
......@@ -37,9 +37,6 @@ module.run(function (ApplicationViews, $sce) {
ApplicationViews.register({
order: 50,
title: $sce.trustAsHtml('<i class="fa fa-list fa-fw"></i>Threads'),
state: 'applications.threads',
show: function (application) {
return application.managementUrl && application.statusInfo.status !== null && application.statusInfo.status !== 'OFFLINE';
}
state: 'applications.threads'
});
});
......@@ -38,9 +38,6 @@ module.run(function (ApplicationViews, $sce) {
ApplicationViews.register({
order: 60,
title: $sce.trustAsHtml('<i class="fa fa-eye fa-fw"></i>Trace'),
state: 'applications.trace',
show: function (application) {
return application.managementUrl && application.statusInfo.status !== null && application.statusInfo.status !== 'OFFLINE';
}
state: 'applications.trace'
});
});
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
module.exports = {
bindings: {
application: '<detailsFor'
},
controller: function (ApplicationViews) {
'ngInject';
var ctrl = this;
ctrl.primaryView = null;
ctrl.secondaryViews = [];
ctrl.resolveViews = null;
ctrl.$onChanges = function () {
var result = ApplicationViews.getApplicationViews(ctrl.application);
ctrl.primaryView = result.views[0];
ctrl.secondaryViews = result.views.slice(1);
ctrl.resolveViews = result.resolve;
};
},
template: require('./btnDetailViews.tpl.html')
};
<div ng-show="$ctrl.application.managementUrl &amp;&amp; $ctrl.application.statusInfo.status !== null &amp;&amp; $ctrl.application.statusInfo.status !== 'OFFLINE'"
class="btn-group">
<a ng-show="$ctrl.primaryView" ng-href="{{$ctrl.primaryView.href}}" target="{{$ctrl.primaryView.target}}" class="btn btn-success"
ng-bind-html="$ctrl.primaryView.title"></a>
<button class="btn btn-success dropdown-toggle" data-toggle="dropdown" ng-show="$ctrl.secondaryViews" ng-click="$ctrl.resolveViews()"><i class="fa fa-caret-down"></i></button>
<ul class="dropdown-menu">
<li ng-repeat="view in $ctrl.secondaryViews track by view.href" ng-show="view.show == true">
<a ng-href="{{view.href}}" target="{{view.target}}" ng-bind-html="view.title"></a>
</li>
</ul>
</div>
\ No newline at end of file
......@@ -15,7 +15,7 @@
*/
'use strict';
module.exports = function ($rootScope, $scope, $state, ApplicationViews, NotificationFilters) {
module.exports = function ($rootScope, $scope, $state, NotificationFilters) {
'ngInject';
$scope.notificationFilters = null;
$scope.notificationFiltersSupported = false;
......@@ -37,10 +37,6 @@ module.exports = function ($rootScope, $scope, $state, ApplicationViews, Notific
descending: false
};
$scope.viewsForApplication = function (application) {
return ApplicationViews.getApplicationViews(application);
};
$scope.orderBy = function (column) {
if (column === $scope.order.column) {
$scope.order.descending = !$scope.order.descending;
......
......@@ -19,5 +19,11 @@ module.exports = function ($scope, application, ApplicationViews) {
'ngInject';
$scope.application = application;
$scope.views = ApplicationViews.getApplicationViews(application);
var views = ApplicationViews.getApplicationViews(application);
views.resolve();
$scope.views = views.views;
};
......@@ -33,7 +33,7 @@
background-color: #666;
border: 0;
padding: 9px 13px 8px 13px;
border-bottom: 1px solid #34302D;
border-bottom: 1px solid transparent;
}
.header--application .nav-tabs > .active > a,
.header--application .nav-tabs > .active > a:hover,
......@@ -46,26 +46,23 @@
border-top: 4px solid #6db33f;
padding: 5px 12px 8px 12px;
}
.header--application .application--status {
font-size: 24px;
line-height: 24px;
margin: 20px;
}
.header--application .application--title {
color: #f1f1f1;
font-size: 24px;
line-height: 24px;
margin: 20px 20px 20px 0;
margin: 20px 20px 0 0;
display: inline-block;
}
.application--urls {
list-style: none;
text-align: right;
font-size: 14px;
color: #888;
margin-top: 20px;
height: 0;
margin: 5px 0 5px 0;
display: block;
float: right;
}
.application--urls > li >a {
.application--urls > li > a {
color: #f1f1f1;
}
.application--urls > li > a:hover,
......
......@@ -39,6 +39,7 @@ module.component('sbaNotificationSettings', require('./components/notificationSe
module.component('sbaPopover', require('./components/popover.js'));
module.component('sbaLimitedText', require('./components/limitedText.js'));
module.component('sbaStatusInfo', require('./components/statusInfo.js'));
module.component('sbaBtnDetailViews', require('./components/btnDetailViews.js'));
require('./css/module.css');
......@@ -74,6 +75,10 @@ module.run(function ($rootScope, $state, Notification, Application, ApplicationG
var refresh = function (group, application) {
application.info = {};
if (application.statusInfo.status === 'OFFLINE') {
return;
}
application.refreshing = true;
application.getInfo().then(function (response) {
var info = response.data;
......
......@@ -15,38 +15,53 @@
*/
'use strict';
var angular = require('angular');
module.exports = function ($state, $q) {
'ngInject';
var views = [];
this.register = function (view) {
views.push(view);
views.sort(function (v1, v2) {
return (v1.order || 0) - (v2.order || 0);
});
};
this.getApplicationViews = function (application) {
var applicationViews = [];
var instantiateView = function (view, application) {
var appView = {
order: view.order,
show: view.show || true,
title: view.title
};
views.forEach(function (view) {
$q.when(!view.show || view.show(application)).then(function (result) {
if (result) {
var appView = angular.copy(view);
if (view.state) {
appView.href = $state.href(view.state, {
id: application.id
});
} else {
appView.href = view.href.replace('{id}', application.id);
appView.target = '_blank';
}
if (view.state) {
appView.href = $state.href(view.state, {
id: application.id
});
} else {
appView.href = view.href.replace('{id}', application.id);
appView.target = '_blank';
}
return appView;
};
applicationViews.push(appView);
applicationViews.sort(function (v1, v2) {
return (v1.order || 0) - (v2.order || 0);
this.getApplicationViews = function (application) {
var result = views.map(function (view) {
return instantiateView(view, application);
});
var resolveDynamicViews = function () {
var deferred = $q.defer();
result.forEach(function (view) {
if (typeof view.show === 'function') {
$q.when(view.show(application)).then(function (result) {
view.show = result;
deferred.notify(view);
});
return deferred.promise;
}
});
});
return applicationViews;
};
return { views: result, resolve: resolveDynamicViews };
};
};
<div class="header--application">
<div class="navbar-inner">
<div class="container-fluid">
<div class="application--status pull-left">
<div class="status-{{application.statusInfo.status}}" ng-bind="application.statusInfo.status"></div>
<div class="application--title">
<span class="status-{{application.statusInfo.status}}" ng-bind="application.statusInfo.status"></span>
{{application.name}} <small class="muted">({{application.id}})</small>
</div>
<div class="application--title pull-left">{{application.name}} <small class="muted">({{application.id}})</small></div>
<ul class="application--urls pull-right">
<ul class="application--urls">
<li ng-if="application.healthUrl"><a href="{{ application.healthUrl }}" target="_blank" title="Health URL">{{ application.healthUrl }}<i class="fa fa-heartbeat fa-fw" ></i></a></li>
<li ng-if="application.managementUrl"><a href="{{ application.managementUrl }}" target="_blank" title="Management URL">{{ application.managementUrl }}<i class="fa fa-wrench fa-fw"></i></a></li>
<li ng-if="application.serviceUrl"><a href="{{ application.serviceUrl }}" target="_blank" title="Service URL">{{ application.serviceUrl }}<i class="fa fa-home fa-fw" ></i></a></li>
......@@ -13,7 +13,7 @@
</div>
<div class="container-fluid">
<ul class="nav nav-tabs">
<li ng-repeat="view in views" ng-class="{active: $state.includes(view.state)}"><a ng-href="{{view.href}}" target="{{view.target}}"><span ng-bind-html="view.title"></span></a></li>
<li ng-repeat="view in views" ng-class="{active: $state.includes(view.state)}" ng-show="view.show"><a ng-href="{{view.href}}" target="{{view.target}}"><span ng-bind-html="view.title"></span></a></li>
</ul>
</div>
</div>
......
......@@ -49,18 +49,8 @@
</td>
<td>
<sba-notification-settings ng-if="notificationFilters" application="application" filters="notificationFilters" refresh-callback="loadFilters()"></sba-notification-settings>
<div class="btn-group">
<a ng-if="views.length > 0" ng-href="{{views[0].href}}" target="{{views[0].target}}" class="btn btn-success" ng-bind-html="views[0].title"></a>
<button class="btn btn-success dropdown-toggle" data-toggle="dropdown" ng-if="views.length > 1">
<i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu">
<li ng-repeat="view in views.slice(1)">
<a ng-href="{{view.href}}" target="{{view.target}}" ng-bind-html="view.title"></a>
</li>
</ul>
</div>
<div class="btn-group" title="remove" ng-if="application.source == 'http-api'">
<sba-btn-detail-views details-for="application"></sba-btn-detail-views>
<div class="btn-group" title="remove">
<a class="btn btn-danger" ng-click="remove(application)"><i class="fa fa-times"></i></a>
</div>
</td>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment