Add loading and error status to detail pages

This adds a loading indicator to detail pages and makes sure that the
page content is not displayed until fully loaded. This prevents the
pages from displaying with partial content and having the updated
values pop in. It also adds an error indicator with a message when
there is an error loading the page. The page content will not display
if there is an error. This prevents the user from seeing the page in
an error state with missing information and broken links.

Change-Id: If26553b0650f38b92e9624baa73e531714baeef3
Closes-Bug: #1561102
This commit is contained in:
Justin Pomeroy 2017-07-10 07:23:57 +08:00 committed by Jacky Hu
parent d57d7a4ebd
commit cd74d86dac
19 changed files with 781 additions and 335 deletions

View File

@ -23,7 +23,8 @@
HealthMonitorDetailController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.rowActions',
'$routeParams'
'$routeParams',
'$q'
];
/**
@ -36,12 +37,15 @@
* @param api The LBaaS v2 API service.
* @param rowActions The LBaaS v2 health monitor row actions service.
* @param $routeParams The angular $routeParams service.
* @param $q The angular service for promises.
* @returns undefined
*/
function HealthMonitorDetailController(api, rowActions, $routeParams) {
function HealthMonitorDetailController(api, rowActions, $routeParams, $q) {
var ctrl = this;
ctrl.loading = true;
ctrl.error = false;
ctrl.actions = rowActions.init($routeParams.loadbalancerId,
$routeParams.listenerId,
$routeParams.poolId).actions;
@ -51,18 +55,46 @@
////////////////////////////////
function init() {
api.getHealthMonitor($routeParams.healthmonitorId).success(set('healthmonitor'));
api.getPool($routeParams.poolId).success(set('pool'));
api.getListener($routeParams.listenerId).success(set('listener'));
api.getLoadBalancer($routeParams.loadbalancerId).success(set('loadbalancer'));
ctrl.healthmonitor = null;
ctrl.pool = null;
ctrl.listener = null;
ctrl.loadbalancer = null;
ctrl.loading = true;
ctrl.error = false;
$q.all([
api.getHealthMonitor($routeParams.healthmonitorId)
.then(success('healthmonitor'), fail('healthmonitor')),
api.getPool($routeParams.poolId)
.then(success('pool'), fail('pool')),
api.getListener($routeParams.listenerId)
.then(success('listener'), fail('listener')),
api.getLoadBalancer($routeParams.loadbalancerId)
.then(success('loadbalancer'), fail('loadbalancer'))
]).then(postInit, initError);
}
function set(property) {
return angular.bind(null, function setProp(property, value) {
ctrl[property] = value;
function success(property) {
return angular.bind(null, function setProp(property, response) {
ctrl[property] = response.data;
}, property);
}
function fail(property) {
return angular.bind(null, function setProp(property, error) {
ctrl[property] = null;
throw error;
}, property);
}
function postInit() {
ctrl.loading = false;
}
function initError() {
ctrl.loading = false;
ctrl.error = true;
}
}
})();

View File

@ -17,26 +17,42 @@
'use strict';
describe('LBaaS v2 Healthmonitor Detail Controller', function() {
var lbaasv2API, ctrl;
var lbaasv2API, $controller, apiFail, qAllFail;
function fakeAPI() {
function fakePromise(data, reject) {
return {
success: function(callback) {
callback('foo');
then: function(success, fail) {
if (reject) {
fail();
} else {
success({ data: data });
}
return fakePromise();
}
};
}
function fakeAPI() {
return fakePromise('foo', apiFail);
}
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: loadbalancer });
return fakePromise({ provisioning_status: 'ACTIVE' });
}
function qAll() {
return fakePromise(null, qAllFail);
}
function createController() {
return $controller('HealthMonitorDetailController', {
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId',
poolId: 'poolId',
healthmonitorId: 'healthmonitorId'
}
};
});
}
///////////////////////
@ -47,24 +63,24 @@
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
apiFail = false;
qAllFail = false;
$provide.value('$q', { all: qAll });
}));
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getHealthMonitor').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getPool').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
var controller = $injector.get('$controller');
ctrl = controller('HealthMonitorDetailController', {
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId',
poolId: 'poolId',
healthmonitorId: 'healthmonitorId'
}
});
$controller = $injector.get('$controller');
}));
it('should invoke lbaasv2 apis', function() {
var ctrl = createController();
expect(lbaasv2API.getHealthMonitor).toHaveBeenCalledWith('healthmonitorId');
expect(lbaasv2API.getPool).toHaveBeenCalledWith('poolId');
expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId');
@ -75,6 +91,21 @@
expect(ctrl.healthmonitor).toBe('foo');
});
it('should throw error on API fail', function() {
apiFail = true;
var init = function() {
createController();
};
expect(init).toThrow();
});
it('should set error state if any APIs fail', function() {
qAllFail = true;
var ctrl = createController();
expect(ctrl.loading).toBe(false);
expect(ctrl.error).toBe(true);
});
});
})();

View File

@ -1,39 +1,42 @@
<div ng-controller="HealthMonitorDetailController as ctrl">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li>
<li class="active">{$ ::(ctrl.healthmonitor.name || ctrl.healthmonitor.id) $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.healthmonitor"
ng-if="ctrl.healthmonitor" class="actions_column pull-right"></actions>
</ol>
</div>
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Type</dt>
<dd>{$ ::ctrl.healthmonitor.type $}</dd>
<dt translate>Delay</dt>
<dd>{$ ::ctrl.healthmonitor.delay $}</dd>
<dt translate>Max Retries</dt>
<dd>{$ ::ctrl.healthmonitor.max_retries $}</dd>
<dt translate>Timeout</dt>
<dd>{$ ::ctrl.healthmonitor.timeout $}</dd>
<dt translate ng-if="::ctrl.healthmonitor.http_method">HTTP Method</dt>
<dd ng-if="::ctrl.healthmonitor.http_method">{$ ::ctrl.healthmonitor.http_method $}</dd>
<dt translate ng-if="::ctrl.healthmonitor.expected_codes">Expected Codes</dt>
<dd ng-if="::ctrl.healthmonitor.expected_codes">{$ ::ctrl.healthmonitor.expected_codes $}</dd>
<dt translate ng-if="::ctrl.healthmonitor.url_path">URL Path</dt>
<dd ng-if="::ctrl.healthmonitor.url_path">{$ ::ctrl.healthmonitor.url_path $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.healthmonitor.admin_state_up | yesno $}</dd>
<dt translate>Monitor ID</dt>
<dd>{$ ::ctrl.healthmonitor.id $}</dd>
<dt translate>Tenant ID</dt>
<dd>{$ ::ctrl.healthmonitor.tenant_id $}</dd>
</dl>
<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
<div ng-if="!ctrl.loading && !ctrl.error">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li>
<li class="active">{$ ::(ctrl.healthmonitor.name || ctrl.healthmonitor.id) $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.healthmonitor"
ng-if="ctrl.healthmonitor" class="actions_column pull-right"></actions>
</ol>
</div>
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Type</dt>
<dd>{$ ::ctrl.healthmonitor.type $}</dd>
<dt translate>Delay</dt>
<dd>{$ ::ctrl.healthmonitor.delay $}</dd>
<dt translate>Max Retries</dt>
<dd>{$ ::ctrl.healthmonitor.max_retries $}</dd>
<dt translate>Timeout</dt>
<dd>{$ ::ctrl.healthmonitor.timeout $}</dd>
<dt translate ng-if="::ctrl.healthmonitor.http_method">HTTP Method</dt>
<dd ng-if="::ctrl.healthmonitor.http_method">{$ ::ctrl.healthmonitor.http_method $}</dd>
<dt translate ng-if="::ctrl.healthmonitor.expected_codes">Expected Codes</dt>
<dd ng-if="::ctrl.healthmonitor.expected_codes">{$ ::ctrl.healthmonitor.expected_codes $}</dd>
<dt translate ng-if="::ctrl.healthmonitor.url_path">URL Path</dt>
<dd ng-if="::ctrl.healthmonitor.url_path">{$ ::ctrl.healthmonitor.url_path $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.healthmonitor.admin_state_up | yesno $}</dd>
<dt translate>Monitor ID</dt>
<dd>{$ ::ctrl.healthmonitor.id $}</dd>
<dt translate>Tenant ID</dt>
<dd>{$ ::ctrl.healthmonitor.tenant_id $}</dd>
</dl>
</div>
</div>
</div>
</div>

View File

@ -68,8 +68,9 @@
}
}
/* Progress indicator in the table while items are loading */
[table-status] {
/* Progress indicator while data is loading */
[table-status],
detail-status {
.progress {
margin: 0px auto;
width: 25%;
@ -78,4 +79,13 @@
width: 100%;
}
}
}
detail-status {
.progress {
margin-top: 25vh;
}
.error-actions {
text-align: center;
margin-top: 10px;
}
}

View File

@ -23,7 +23,8 @@
ListenerDetailController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.listeners.actions.rowActions',
'$routeParams'
'$routeParams',
'$q'
];
/**
@ -36,12 +37,15 @@
* @param api The LBaaS v2 API service.
* @param rowActions The listener row actions service.
* @param $routeParams The angular $routeParams service.
* @param $q The angular service for promises.
* @returns undefined
*/
function ListenerDetailController(api, rowActions, $routeParams) {
function ListenerDetailController(api, rowActions, $routeParams, $q) {
var ctrl = this;
ctrl.loading = true;
ctrl.error = false;
ctrl.actions = rowActions.init($routeParams.loadbalancerId).actions;
init();
@ -49,16 +53,40 @@
////////////////////////////////
function init() {
api.getListener($routeParams.listenerId).success(set('listener'));
api.getLoadBalancer($routeParams.loadbalancerId).success(set('loadbalancer'));
ctrl.listener = null;
ctrl.loadbalancer = null;
ctrl.loading = true;
ctrl.error = false;
$q.all([
api.getListener($routeParams.listenerId)
.then(success('listener'), fail('listener')),
api.getLoadBalancer($routeParams.loadbalancerId)
.then(success('loadbalancer'), fail('loadbalancer'))
]).then(postInit, initError);
}
function set(property) {
return angular.bind(null, function setProp(property, value) {
ctrl[property] = value;
function success(property) {
return angular.bind(null, function setProp(property, response) {
ctrl[property] = response.data;
}, property);
}
function fail(property) {
return angular.bind(null, function setProp(property, error) {
ctrl[property] = null;
throw error;
}, property);
}
function postInit() {
ctrl.loading = false;
}
function initError() {
ctrl.loading = false;
ctrl.error = true;
}
}
})();

View File

@ -17,26 +17,40 @@
'use strict';
describe('LBaaS v2 Listener Detail Controller', function() {
var lbaasv2API, ctrl;
var lbaasv2API, $controller, apiFail, qAllFail;
function fakeAPI() {
function fakePromise(data, reject) {
return {
success: function(callback) {
callback('foo');
then: function(success, fail) {
if (reject) {
fail();
} else {
success({ data: data });
}
return fakePromise();
}
};
}
function fakeAPI() {
return fakePromise('foo', apiFail);
}
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: loadbalancer });
return fakePromise({ provisioning_status: 'ACTIVE' });
}
function qAll() {
return fakePromise(null, qAllFail);
}
function createController() {
return $controller('ListenerDetailController', {
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId'
}
};
});
}
///////////////////////
@ -48,6 +62,10 @@
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
apiFail = false;
qAllFail = false;
$provide.value('$q', { all: qAll });
$provide.value('$uibModal', {});
}));
@ -55,22 +73,32 @@
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getListener').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
var controller = $injector.get('$controller');
ctrl = controller('ListenerDetailController', {
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId'
}
});
$controller = $injector.get('$controller');
}));
it('should invoke lbaasv2 apis', function() {
var ctrl = createController();
expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId');
expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId');
expect(ctrl.loadbalancer).toEqual({ provisioning_status: 'ACTIVE' });
expect(ctrl.listener).toBe('foo');
});
it('should throw error on API fail', function() {
apiFail = true;
var init = function() {
createController();
};
expect(init).toThrow();
});
it('should set error state if any APIs fail', function() {
qAllFail = true;
var ctrl = createController();
expect(ctrl.loading).toBe(false);
expect(ctrl.error).toBe(true);
});
});
})();

View File

@ -1,39 +1,42 @@
<div ng-controller="ListenerDetailController as ctrl">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li class="active">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.listener" ng-if="ctrl.listener"
class="actions_column pull-right"></actions>
</ol>
<p ng-if="::ctrl.listener.description">{$ ::ctrl.listener.description $}</p>
</div>
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Protocol</dt>
<dd>{$ ::ctrl.listener.protocol $}</dd>
<dt translate>Protocol Port</dt>
<dd>{$ ::ctrl.listener.protocol_port $}</dd>
<dt translate>Connection Limit</dt>
<dd>{$ ctrl.listener.connection_limit | limit $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.listener.admin_state_up | yesno $}</dd>
<dt translate>Default Pool ID</dt>
<dd>
<a ng-href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.listener.default_pool_id $}" ng-if="ctrl.listener.default_pool_id">
{$ ::ctrl.listener.default_pool_id $}
</a>
<span ng-if="!ctrl.listener.default_pool_id">
{$ 'None' | translate $}
</span>
</dd>
<dt translate>Listener ID</dt>
<dd>{$ ::ctrl.listener.id $}</dd>
<dt translate>Tenant ID</dt>
<dd>{$ ::ctrl.listener.tenant_id $}</dd>
</dl>
<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
<div ng-if="!ctrl.loading && !ctrl.error">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li class="active">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.listener" ng-if="ctrl.listener"
class="actions_column pull-right"></actions>
</ol>
<p ng-if="::ctrl.listener.description">{$ ::ctrl.listener.description $}</p>
</div>
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Protocol</dt>
<dd>{$ ::ctrl.listener.protocol $}</dd>
<dt translate>Protocol Port</dt>
<dd>{$ ::ctrl.listener.protocol_port $}</dd>
<dt translate>Connection Limit</dt>
<dd>{$ ctrl.listener.connection_limit | limit $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.listener.admin_state_up | yesno $}</dd>
<dt translate>Default Pool ID</dt>
<dd>
<a ng-href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.listener.default_pool_id $}" ng-if="ctrl.listener.default_pool_id">
{$ ::ctrl.listener.default_pool_id $}
</a>
<span ng-if="!ctrl.listener.default_pool_id">
{$ 'None' | translate $}
</span>
</dd>
<dt translate>Listener ID</dt>
<dd>{$ ::ctrl.listener.id $}</dd>
<dt translate>Tenant ID</dt>
<dd>{$ ::ctrl.listener.tenant_id $}</dd>
</dl>
</div>
</div>
</div>
</div>

View File

@ -50,6 +50,8 @@
) {
var ctrl = this;
ctrl.loading = true;
ctrl.error = false;
ctrl.actions = rowActions.actions;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
@ -60,11 +62,21 @@
////////////////////////////////
function init() {
api.getLoadBalancer($routeParams.loadbalancerId, true).success(success);
ctrl.loadbalancer = null;
ctrl.loading = true;
ctrl.error = false;
api.getLoadBalancer($routeParams.loadbalancerId, true).then(success, fail);
}
function success(response) {
ctrl.loadbalancer = response;
ctrl.loadbalancer = response.data;
ctrl.loading = false;
}
function fail(/*response*/) {
ctrl.loadbalancer = null;
ctrl.loading = false;
ctrl.error = true;
}
// Save the active state of the listeners tab in the global window object so it can stay

View File

@ -17,16 +17,28 @@
'use strict';
describe('LBaaS v2 Load Balancer Detail Controller', function() {
var lbaasv2API, ctrl, $scope, $window;
var lbaasv2API, $scope, $window, $controller, apiFail;
function fakeAPI() {
return {
success: function(callback) {
callback({ id: '1234' });
then: function(success, fail) {
if (apiFail && fail) {
fail();
} else {
success({ id: '1234' });
}
}
};
}
function createController() {
return $controller('LoadBalancerDetailController', {
$scope: $scope,
$window: $window,
$routeParams: { loadbalancerId: '1234' }
});
}
///////////////////////
beforeEach(module('horizon.framework.widgets'));
@ -36,6 +48,7 @@
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
apiFail = false;
$provide.value('$uibModal', {});
}));
@ -44,19 +57,16 @@
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(fakeAPI);
$scope = $injector.get('$rootScope').$new();
$window = {};
var controller = $injector.get('$controller');
ctrl = controller('LoadBalancerDetailController', {
$scope: $scope,
$window: $window,
$routeParams: { loadbalancerId: '1234' }
});
$controller = $injector.get('$controller');
}));
it('should invoke lbaasv2 apis', function() {
createController();
expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('1234', true);
});
it('should save changes to listeners tab active state', function() {
var ctrl = createController();
expect($window.listenersTabActive).toBeUndefined();
expect(ctrl.listenersTabActive).toBeUndefined();
ctrl.listenersTabActive = true;
@ -67,6 +77,13 @@
expect($window.listenersTabActive).toBe(false);
});
it('should set error state', function() {
apiFail = true;
var ctrl = createController();
expect(ctrl.loading).toBe(false);
expect(ctrl.error).toBe(true);
});
});
})();

View File

@ -1,54 +1,57 @@
<div ng-controller="LoadBalancerDetailController as ctrl">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li class="active">{$ ctrl.loadbalancer.name || ctrl.loadbalancer.id $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.loadbalancer"
ng-if="ctrl.loadbalancer" class="actions_column pull-right"></actions>
</ol>
<p ng-if="::ctrl.loadbalancer.description">{$ ::ctrl.loadbalancer.description $}</p>
<ul class="list-inline">
<li>
<strong translate>IP Address</strong>
{$ ::ctrl.loadbalancer.vip_address $}
</li>
<li>
<strong translate>Operating Status</strong>
{$ ctrl.loadbalancer.operating_status | decode:ctrl.operatingStatus $}
</li>
<li>
<strong translate>Provisioning Status</strong>
{$ ctrl.loadbalancer.provisioning_status | decode:ctrl.provisioningStatus $}
</li>
</ul>
</div>
<tabset>
<tab heading="{$ 'Overview' | translate $}">
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Provider</dt>
<dd>{$ ::ctrl.loadbalancer.provider $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.loadbalancer.admin_state_up | yesno $}</dd>
<dt translate ng-if="ctrl.loadbalancer.floating_ip !== undefined">Floating IP Address</dt>
<dd ng-if="ctrl.loadbalancer.floating_ip !== undefined">{$ ctrl.loadbalancer.floating_ip.ip | noValue:('None' | translate) $}</dd>
<dt translate>Load Balancer ID</dt>
<dd>{$ ::ctrl.loadbalancer.id $}</dd>
<dt translate>Subnet ID</dt>
<dd>
<a target="_self" ng-href="project/networks/subnets/{$ ::ctrl.loadbalancer.vip_subnet_id $}/detail">{$ ::ctrl.loadbalancer.vip_subnet_id $}</a>
</dd>
<dt translate>Port ID</dt>
<dd>
<a target="_self" ng-href="project/networks/ports/{$ ::ctrl.loadbalancer.vip_port_id $}/detail">{$ ::ctrl.loadbalancer.vip_port_id $}</a>
</dd>
</dl>
<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
<div ng-if="!ctrl.loading && !ctrl.error">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li class="active">{$ ctrl.loadbalancer.name || ctrl.loadbalancer.id $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.loadbalancer"
ng-if="ctrl.loadbalancer" class="actions_column pull-right"></actions>
</ol>
<p ng-if="::ctrl.loadbalancer.description">{$ ::ctrl.loadbalancer.description $}</p>
<ul class="list-inline">
<li>
<strong translate>IP Address</strong>
{$ ::ctrl.loadbalancer.vip_address $}
</li>
<li>
<strong translate>Operating Status</strong>
{$ ctrl.loadbalancer.operating_status | decode:ctrl.operatingStatus $}
</li>
<li>
<strong translate>Provisioning Status</strong>
{$ ctrl.loadbalancer.provisioning_status | decode:ctrl.provisioningStatus $}
</li>
</ul>
</div>
<tabset>
<tab heading="{$ 'Overview' | translate $}">
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Provider</dt>
<dd>{$ ::ctrl.loadbalancer.provider $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.loadbalancer.admin_state_up | yesno $}</dd>
<dt translate ng-if="ctrl.loadbalancer.floating_ip !== undefined">Floating IP Address</dt>
<dd ng-if="ctrl.loadbalancer.floating_ip !== undefined">{$ ctrl.loadbalancer.floating_ip.ip | noValue:('None' | translate) $}</dd>
<dt translate>Load Balancer ID</dt>
<dd>{$ ::ctrl.loadbalancer.id $}</dd>
<dt translate>Subnet ID</dt>
<dd>
<a target="_self" ng-href="project/networks/subnets/{$ ::ctrl.loadbalancer.vip_subnet_id $}/detail">{$ ::ctrl.loadbalancer.vip_subnet_id $}</a>
</dd>
<dt translate>Port ID</dt>
<dd>
<a target="_self" ng-href="project/networks/ports/{$ ::ctrl.loadbalancer.vip_port_id $}/detail">{$ ::ctrl.loadbalancer.vip_port_id $}</a>
</dd>
</dl>
</div>
</div>
</div>
</tab>
<tab heading="{$ 'Listeners' | translate $}" active="ctrl.listenersTabActive">
<ng-include src="'static/dashboard/project/lbaasv2/listeners/table.html'"></ng-include>
</tab>
</tabset>
</tab>
<tab heading="{$ 'Listeners' | translate $}" active="ctrl.listenersTabActive">
<ng-include src="'static/dashboard/project/lbaasv2/listeners/table.html'"></ng-include>
</tab>
</tabset>
</div>
</div>

View File

@ -24,6 +24,7 @@
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.members.actions.rowActions',
'$routeParams',
'$q',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.members.service'
];
@ -38,16 +39,19 @@
* @param api The LBaaS v2 API service.
* @param rowActions The pool members row actions service.
* @param $routeParams The angular $routeParams service.
* @param $q The angular service for promises.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param membersService The LBaaS v2 members service.
* @returns undefined
*/
function MemberDetailController(
api, rowActions, $routeParams, loadBalancersService, membersService
api, rowActions, $routeParams, $q, loadBalancersService, membersService
) {
var ctrl = this;
ctrl.loading = true;
ctrl.error = false;
ctrl.actions = rowActions.init($routeParams.loadbalancerId, $routeParams.poolId).actions;
ctrl.loadbalancerId = $routeParams.loadbalancerId;
ctrl.listenerId = $routeParams.listenerId;
@ -60,25 +64,53 @@
////////////////////////////////
function init() {
api.getMember($routeParams.poolId, $routeParams.memberId).success(memberSuccess);
api.getPool($routeParams.poolId).success(set('pool'));
api.getListener($routeParams.listenerId).success(set('listener'));
api.getLoadBalancer($routeParams.loadbalancerId).success(set('loadbalancer'));
ctrl.member = null;
ctrl.pool = null;
ctrl.listener = null;
ctrl.loadbalancer = null;
ctrl.loading = true;
ctrl.error = false;
$q.all([
api.getMember($routeParams.poolId, $routeParams.memberId)
.then(success('member'), fail('member')),
api.getPool($routeParams.poolId)
.then(success('pool'), fail('pool')),
api.getListener($routeParams.listenerId)
.then(success('listener'), fail('listener')),
api.getLoadBalancer($routeParams.loadbalancerId)
.then(success('loadbalancer'), fail('loadbalancer'))
]).then(postInit, initError);
}
function set(property) {
return angular.bind(null, function setProp(property, value) {
ctrl[property] = value;
function success(property) {
return angular.bind(null, function setProp(property, response) {
ctrl[property] = response.data;
if (property === 'member') {
membersService.associateMemberStatuses(
ctrl.loadbalancerId,
ctrl.listenerId,
ctrl.poolId,
[ctrl.member]);
}
}, property);
}
function memberSuccess(response) {
ctrl.member = response;
membersService.associateMemberStatuses(
ctrl.loadbalancerId,
ctrl.listenerId,
ctrl.poolId,
[ctrl.member]);
function fail(property) {
return angular.bind(null, function setProp(property, error) {
ctrl[property] = null;
throw error;
}, property);
}
function postInit() {
ctrl.loading = false;
}
function initError() {
ctrl.loading = false;
ctrl.error = true;
}
}

View File

@ -17,26 +17,42 @@
'use strict';
describe('LBaaS v2 Member Detail Controller', function() {
var controller, lbaasv2API, membersService, ctrl, actions;
var $controller, lbaasv2API, membersService, apiFail, qAllFail, actions;
function fakeAPI() {
function fakePromise(data, reject) {
return {
success: function(callback) {
callback('foo');
then: function(success, fail) {
if (reject) {
fail();
} else {
success({ data: data });
}
return fakePromise();
}
};
}
function fakeAPI() {
return fakePromise('foo', apiFail);
}
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: loadbalancer });
return fakePromise({ provisioning_status: 'ACTIVE' });
}
function qAll() {
return fakePromise(null, qAllFail);
}
function createController() {
return $controller('MemberDetailController', {
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId',
poolId: 'poolId',
memberId: 'memberId'
}
};
});
}
///////////////////////
@ -48,6 +64,10 @@
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
apiFail = false;
qAllFail = false;
$provide.value('$q', { all: qAll });
$provide.value('$uibModal', {});
$provide.value('horizon.dashboard.project.lbaasv2.members.actions.rowActions', {
init: function() {
@ -68,25 +88,15 @@
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(actions, 'init').and.callThrough();
spyOn(membersService, 'associateMemberStatuses');
controller = $injector.get('$controller');
ctrl = controller('MemberDetailController', {
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId',
poolId: 'poolId',
memberId: 'memberId'
}
});
$controller = $injector.get('$controller');
}));
it('should invoke lbaasv2 apis', function() {
var ctrl = createController();
expect(lbaasv2API.getMember).toHaveBeenCalledWith('poolId','memberId');
expect(lbaasv2API.getPool).toHaveBeenCalledWith('poolId');
expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId');
expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId');
});
it('should initialize the controller properties correctly', function() {
expect(ctrl.loadbalancerId).toBeDefined();
expect(ctrl.listenerId).toBeDefined();
expect(ctrl.poolId).toBeDefined();
@ -97,10 +107,26 @@
});
it('should invoke the "associateMemberStatuses" method', function() {
var ctrl = createController();
expect(membersService.associateMemberStatuses).toHaveBeenCalledWith(
ctrl.loadbalancerId, ctrl.listenerId, ctrl.poolId, [ctrl.member]);
});
it('should throw error on API fail', function() {
apiFail = true;
var init = function() {
createController();
};
expect(init).toThrow();
});
it('should set error state if any APIs fail', function() {
qAllFail = true;
var ctrl = createController();
expect(ctrl.loading).toBe(false);
expect(ctrl.error).toBe(true);
});
});
})();

View File

@ -1,35 +1,38 @@
<div ng-controller="MemberDetailController as ctrl">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li>
<li class="active">{$ ::(ctrl.member.name || ctrl.member.id) $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.member"
ng-if="ctrl.member" class="actions_column pull-right"></actions>
</ol>
</div>
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Address</dt>
<dd>{$ ::ctrl.member.address $}</dd>
<dt translate>Protocol Port</dt>
<dd>{$ ::ctrl.member.protocol_port $}</dd>
<dt translate>Weight</dt>
<dd>{$ ctrl.member.weight $}</dd>
<dt translate>Operating Status</dt>
<dd>{$ ctrl.member.operating_status | decode:ctrl.operatingStatus $}</dd>
<dt translate>Provisioning Status</dt>
<dd>{$ ctrl.member.provisioning_status | decode:ctrl.provisioningStatus $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.member.admin_state_up | yesno $}</dd>
<dt translate>Member ID</dt>
<dd>{$ ::ctrl.member.id $}</dd>
<dt translate>Tenant ID</dt>
<dd>{$ ::ctrl.member.tenant_id $}</dd>
</dl>
<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
<div ng-if="!ctrl.loading && !ctrl.error">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</a></li>
<li class="active">{$ ::(ctrl.member.name || ctrl.member.id) $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.member"
ng-if="ctrl.member" class="actions_column pull-right"></actions>
</ol>
</div>
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Address</dt>
<dd>{$ ::ctrl.member.address $}</dd>
<dt translate>Protocol Port</dt>
<dd>{$ ::ctrl.member.protocol_port $}</dd>
<dt translate>Weight</dt>
<dd>{$ ctrl.member.weight $}</dd>
<dt translate>Operating Status</dt>
<dd>{$ ctrl.member.operating_status | decode:ctrl.operatingStatus $}</dd>
<dt translate>Provisioning Status</dt>
<dd>{$ ctrl.member.provisioning_status | decode:ctrl.provisioningStatus $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.member.admin_state_up | yesno $}</dd>
<dt translate>Member ID</dt>
<dd>{$ ::ctrl.member.id $}</dd>
<dt translate>Tenant ID</dt>
<dd>{$ ::ctrl.member.tenant_id $}</dd>
</dl>
</div>
</div>
</div>
</div>
</div>

View File

@ -26,7 +26,8 @@
'$routeParams',
'horizon.framework.util.i18n.gettext',
'$window',
'$scope'
'$scope',
'$q'
];
/**
@ -42,12 +43,15 @@
* @param gettext The horizon gettext function for translation.
* @param $window Angular's reference to the browser window object.
* @param $scope The angular scope object.
* @param $q The angular service for promises.
* @returns undefined
*/
function PoolDetailController(api, rowActions, $routeParams, gettext, $window, $scope) {
function PoolDetailController(api, rowActions, $routeParams, gettext, $window, $scope, $q) {
var ctrl = this;
ctrl.loading = true;
ctrl.error = false;
ctrl.loadBalancerAlgorithm = {
ROUND_ROBIN: gettext('Round Robin'),
LEAST_CONNECTIONS: gettext('Least Connections'),
@ -61,17 +65,43 @@
////////////////////////////////
function init() {
api.getPool($routeParams.poolId).success(set('pool'));
api.getListener($routeParams.listenerId).success(set('listener'));
api.getLoadBalancer($routeParams.loadbalancerId).success(set('loadbalancer'));
ctrl.pool = null;
ctrl.listener = null;
ctrl.loadbalancer = null;
ctrl.loading = true;
ctrl.error = false;
$q.all([
api.getPool($routeParams.poolId)
.then(success('pool'), fail('pool')),
api.getListener($routeParams.listenerId)
.then(success('listener'), fail('listener')),
api.getLoadBalancer($routeParams.loadbalancerId)
.then(success('loadbalancer'), fail('loadbalancer'))
]).then(postInit, initError);
}
function set(property) {
return angular.bind(null, function setProp(property, value) {
ctrl[property] = value;
function success(property) {
return angular.bind(null, function setProp(property, response) {
ctrl[property] = response.data;
}, property);
}
function fail(property) {
return angular.bind(null, function setProp(property, error) {
ctrl[property] = null;
throw error;
}, property);
}
function postInit() {
ctrl.loading = false;
}
function initError() {
ctrl.loading = false;
ctrl.error = true;
}
// Save the active state of the members tab in the global window object so it can stay
// active after reloading the route following an action.
$scope.$watch(function() {

View File

@ -17,26 +17,43 @@
'use strict';
describe('LBaaS v2 Pool Detail Controller', function() {
var lbaasv2API, ctrl, $scope, $window;
var lbaasv2API, $scope, $window, $controller, apiFail, qAllFail;
function fakeAPI() {
function fakePromise(data, reject) {
return {
success: function(callback) {
callback('foo');
then: function(success, fail) {
if (reject) {
fail();
} else {
success({ data: data });
}
return fakePromise();
}
};
}
function fakeAPI() {
return fakePromise('foo', apiFail);
}
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: loadbalancer });
return fakePromise({ provisioning_status: 'ACTIVE' });
}
function qAll() {
return fakePromise(null, qAllFail);
}
function createController() {
return $controller('PoolDetailController', {
$scope: $scope,
$window: $window,
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId',
poolId: 'poolId'
}
};
});
}
///////////////////////
@ -47,6 +64,13 @@
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
apiFail = false;
qAllFail = false;
$provide.value('$q', { all: qAll });
}));
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getPool').and.callFake(fakeAPI);
@ -54,19 +78,11 @@
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
$scope = $injector.get('$rootScope').$new();
$window = {};
var controller = $injector.get('$controller');
ctrl = controller('PoolDetailController', {
$scope: $scope,
$window: $window,
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId',
poolId: 'poolId'
}
});
$controller = $injector.get('$controller');
}));
it('should invoke lbaasv2 apis', function() {
var ctrl = createController();
expect(lbaasv2API.getPool).toHaveBeenCalledWith('poolId');
expect(lbaasv2API.getListener).toHaveBeenCalledWith('listenerId');
expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId');
@ -76,10 +92,12 @@
});
it('should define mapping for the load balancer algorithm', function() {
var ctrl = createController();
expect(ctrl.loadBalancerAlgorithm).toBeDefined();
});
it('should save changes to members tab active state', function() {
var ctrl = createController();
expect($window.membersTabActive).toBeUndefined();
expect(ctrl.membersTabActive).toBeUndefined();
ctrl.membersTabActive = true;
@ -90,6 +108,21 @@
expect($window.membersTabActive).toBe(false);
});
it('should throw error on API fail', function() {
apiFail = true;
var init = function() {
createController();
};
expect(init).toThrow();
});
it('should set error state if any APIs fail', function() {
qAllFail = true;
var ctrl = createController();
expect(ctrl.loading).toBe(false);
expect(ctrl.error).toBe(true);
});
});
})();

View File

@ -1,47 +1,50 @@
<div ng-controller="PoolDetailController as ctrl">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li class="active">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.pool"
ng-if="ctrl.pool" class="actions_column pull-right"></actions>
</ol>
<p ng-if="::ctrl.pool.description">{$ ::ctrl.pool.description $}</p>
</div>
<tabset>
<tab heading="{$ 'Overview' | translate $}">
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Protocol</dt>
<dd>{$ ::ctrl.pool.protocol $}</dd>
<dt translate>Load Balancer Algorithm</dt>
<dd>{$ ctrl.pool.lb_algorithm | decode:ctrl.loadBalancerAlgorithm $}</dd>
<dt translate>Session Persistence</dt>
<dd>{$ ctrl.pool.session_persistence | noValue:('None' | translate) $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.pool.admin_state_up | yesno $}</dd>
<dt translate>Health Monitor ID</dt>
<dd>
<a ng-href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}/healthmonitors/{$ ::ctrl.pool.healthmonitor_id $}" ng-if="ctrl.pool.healthmonitor_id">
{$ ::ctrl.pool.healthmonitor_id $}
</a>
<span ng-if="!ctrl.pool.healthmonitor_id">
{$ 'None' | translate $}
</span>
</dd>
<dt translate>Pool ID</dt>
<dd>{$ ::ctrl.pool.id $}</dd>
<dt translate>Tenant ID</dt>
<dd>{$ ::ctrl.pool.tenant_id $}</dd>
</dl>
<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
<div ng-if="!ctrl.loading && !ctrl.error">
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li class="active">{$ ::(ctrl.pool.name || ctrl.pool.id) $}</li>
<actions allowed="ctrl.actions" type="row" item="ctrl.pool"
ng-if="ctrl.pool" class="actions_column pull-right"></actions>
</ol>
<p ng-if="::ctrl.pool.description">{$ ::ctrl.pool.description $}</p>
</div>
<tabset>
<tab heading="{$ 'Overview' | translate $}">
<div class="row">
<div class="col-md-6 detail">
<dl class="dl-horizontal">
<dt translate>Protocol</dt>
<dd>{$ ::ctrl.pool.protocol $}</dd>
<dt translate>Load Balancer Algorithm</dt>
<dd>{$ ctrl.pool.lb_algorithm | decode:ctrl.loadBalancerAlgorithm $}</dd>
<dt translate>Session Persistence</dt>
<dd>{$ ctrl.pool.session_persistence | noValue:('None' | translate) $}</dd>
<dt translate>Admin State Up</dt>
<dd>{$ ctrl.pool.admin_state_up | yesno $}</dd>
<dt translate>Health Monitor ID</dt>
<dd>
<a ng-href="project/ngloadbalancersv2/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}/pools/{$ ::ctrl.pool.id $}/healthmonitors/{$ ::ctrl.pool.healthmonitor_id $}" ng-if="ctrl.pool.healthmonitor_id">
{$ ::ctrl.pool.healthmonitor_id $}
</a>
<span ng-if="!ctrl.pool.healthmonitor_id">
{$ 'None' | translate $}
</span>
</dd>
<dt translate>Pool ID</dt>
<dd>{$ ::ctrl.pool.id $}</dd>
<dt translate>Tenant ID</dt>
<dd>{$ ::ctrl.pool.tenant_id $}</dd>
</dl>
</div>
</div>
</div>
</tab>
<tab heading="{$ 'Members' | translate $}" active="ctrl.membersTabActive">
<ng-include src="'static/dashboard/project/lbaasv2/members/table.html'"></ng-include>
</tab>
</tabset>
</tab>
<tab heading="{$ 'Members' | translate $}" active="ctrl.membersTabActive">
<ng-include src="'static/dashboard/project/lbaasv2/members/table.html'"></ng-include>
</tab>
</tabset>
</div>
</div>

View File

@ -0,0 +1,53 @@
/*
* Copyright 2016 IBM Corp.
*
* 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.
*/
(function() {
'use strict';
angular
.module('horizon.dashboard.project.lbaasv2')
.directive('detailStatus', detailStatus);
detailStatus.$inject = [
'horizon.dashboard.project.lbaasv2.basePath'
];
/**
* @ngdoc directive
* @name horizon.dashboard.project.lbaasv2:detailStatus
* @description
* The `detailStatus` directive provides a status indicator while loading detail pages. It will
* show a loading indicator while the page is loading and an error indicator if there is an
* error loading the page.
* @restrict E
*
* @example
* ```
* <detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
* ```
*/
function detailStatus(basePath) {
var directive = {
restrict: 'E',
templateUrl: basePath + 'widgets/detail/detail-status.html',
scope: {
loading: '=',
error: '='
}
};
return directive;
}
}());

View File

@ -0,0 +1,87 @@
/*
* Copyright 2016 IBM Corp.
*
* 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.
*/
(function() {
'use strict';
function digestMarkup(scope, compile, markup) {
var element = angular.element(markup);
compile(element)(scope);
scope.$apply();
return element;
}
describe('detailStatus directive', function() {
var $scope, $compile, markup, ctrl;
beforeEach(module('templates'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function($injector) {
$compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new();
ctrl = {
loading: true,
error: false
};
$scope.ctrl = ctrl;
markup = '<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>';
}));
it('initially shows loading status', function() {
var element = digestMarkup($scope, $compile, markup);
expect(element).toBeDefined();
expect(element.children().length).toBe(1);
expect(element.find('.progress-bar').hasClass('progress-bar-striped')).toBe(true);
expect(element.find('.progress-bar').hasClass('progress-bar-danger')).toBe(false);
expect(element.find('.progress-bar > span').length).toBe(1);
expect(element.find('.progress-bar > span').hasClass('sr-only')).toBe(true);
expect(element.find('.progress-bar > span').text().trim()).toBe('Loading');
expect(element.find('.message').length).toBe(0);
});
it('indicates error status on error', function() {
var element = digestMarkup($scope, $compile, markup);
expect(element).toBeDefined();
ctrl.loading = false;
ctrl.error = true;
$scope.$apply();
expect(element.children().length).toBe(1);
expect(element.find('.progress-bar').hasClass('progress-bar-striped')).toBe(false);
expect(element.find('.progress-bar').hasClass('progress-bar-danger')).toBe(true);
expect(element.find('.progress-bar > span').length).toBe(1);
expect(element.find('.progress-bar > span').hasClass('sr-only')).toBe(false);
expect(element.find('.progress-bar > span').text().trim())
.toBe('An error occurred. Please try again later.');
expect(element.find('.error-actions').length).toBe(1);
expect(element.find('.error-actions > a').text().trim()).toBe('Back');
});
it('goes away when done loading', function() {
var element = digestMarkup($scope, $compile, markup);
expect(element).toBeDefined();
ctrl.loading = false;
ctrl.error = false;
$scope.$apply();
expect(element.children().length).toBe(0);
});
});
}());

View File

@ -0,0 +1,12 @@
<div ng-if="loading || error">
<div class="progress">
<div class="progress-bar" role="progressbar"
ng-class="{ 'progress-bar-striped active': !error, 'progress-bar-danger': error }">
<span ng-if="!error" class="sr-only" translate>Loading</span>
<span ng-if="error" translate>An error occurred. Please try again later.</span>
</div>
</div>
<div class="error-actions" ng-if="error">
<a href="javascript:window.history.back();" translate>Back</a>
</div>
</div>