Merge "Add loading and error status to detail pages"

This commit is contained in:
Jenkins 2017-07-27 17:05:53 +00:00 committed by Gerrit Code Review
commit 54e965a87d
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>