Catch up with horizon framework

* Use resource registry to reduce boilerplate
* Resolve resources before routing
* Better organization of the page structure

Change-Id: I9fdf0d1bc3389cac0f4cd955dcd76920b7c0f90b
Story: 1713855
Task: 5449
Story: 1713854
Task: 5219
Story: 1713853
Task: 5366
This commit is contained in:
Jacky Hu 2017-12-06 15:36:53 +08:00
parent 8788fcbf55
commit 3594784322
124 changed files with 4526 additions and 5104 deletions

View File

@ -661,6 +661,24 @@ class Pools(generic.View):
""" """
url_regex = r'lbaas/pools/$' url_regex = r'lbaas/pools/$'
@rest_utils.ajax()
def get(self, request):
"""List of pools for the current project.
The listing result is an object with property "items".
"""
loadbalancer_id = request.GET.get('loadbalancerId')
listener_id = request.GET.get('listenerId')
conn = _get_sdk_connection(request)
pool_list = _sdk_object_to_list(conn.load_balancer.pools(
project_id=request.user.project_id))
if loadbalancer_id or listener_id:
pool_list = self._filter_pools(pool_list,
loadbalancer_id,
listener_id)
return {'items': pool_list}
@rest_utils.ajax() @rest_utils.ajax()
def post(self, request): def post(self, request):
"""Create a new pool. """Create a new pool.
@ -672,6 +690,24 @@ class Pools(generic.View):
'listener_id': request.DATA.get('parentResourceId')} 'listener_id': request.DATA.get('parentResourceId')}
return create_pool(request, **kwargs) return create_pool(request, **kwargs)
def _filter_pools(self, pool_list, loadbalancer_id, listener_id):
filtered_pools = []
for pool in pool_list:
if loadbalancer_id:
if pool['loadbalancers'][0]['id'] == loadbalancer_id:
if listener_id:
if (pool['listeners'] and
pool['listeners'][0]['id'] == listener_id):
filtered_pools.append(pool)
else:
filtered_pools.append(pool)
elif (pool['listeners'] and
pool['listeners'][0]['id'] == listener_id):
filtered_pools.append(pool)
return filtered_pools
@urls.register @urls.register
class Pool(generic.View): class Pool(generic.View):
@ -819,6 +855,26 @@ class HealthMonitors(generic.View):
""" """
url_regex = r'lbaas/healthmonitors/$' url_regex = r'lbaas/healthmonitors/$'
@rest_utils.ajax()
def get(self, request):
"""List of health monitors for the current project.
The listing result is an object with property "items".
"""
pool_id = request.GET.get('poolId')
conn = _get_sdk_connection(request)
health_monitor_list = _sdk_object_to_list(
conn.load_balancer.health_monitors(
project_id=request.user.project_id
)
)
if pool_id:
health_monitor_list = self._filter_health_monitors(
health_monitor_list,
pool_id)
return {'items': health_monitor_list}
@rest_utils.ajax() @rest_utils.ajax()
def post(self, request): def post(self, request):
"""Create a new health monitor. """Create a new health monitor.
@ -828,6 +884,15 @@ class HealthMonitors(generic.View):
'pool_id': request.DATA.get('parentResourceId')} 'pool_id': request.DATA.get('parentResourceId')}
return create_health_monitor(request, **kwargs) return create_health_monitor(request, **kwargs)
def _filter_health_monitors(self, health_monitor_list, pool_id):
filtered_health_monitors = []
for health_monitor in health_monitor_list:
if health_monitor['pools'][0]['id'] == pool_id:
filtered_health_monitors.append(health_monitor)
return filtered_health_monitors
@urls.register @urls.register
class HealthMonitor(generic.View): class HealthMonitor(generic.View):

View File

@ -46,6 +46,7 @@
createListener: createListener, createListener: createListener,
editListener: editListener, editListener: editListener,
deleteListener: deleteListener, deleteListener: deleteListener,
getPools: getPools,
getPool: getPool, getPool: getPool,
createPool: createPool, createPool: createPool,
editPool: editPool, editPool: editPool,
@ -54,6 +55,7 @@
getMember: getMember, getMember: getMember,
deleteMember: deleteMember, deleteMember: deleteMember,
editMember: editMember, editMember: editMember,
getHealthMonitors: getHealthMonitors,
getHealthMonitor: getHealthMonitor, getHealthMonitor: getHealthMonitor,
deleteHealthMonitor: deleteHealthMonitor, deleteHealthMonitor: deleteHealthMonitor,
createHealthMonitor: createHealthMonitor, createHealthMonitor: createHealthMonitor,
@ -242,6 +244,38 @@
// Pools // Pools
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.getPools
* @description
* Get the list of pools.
* If a loadbalancer ID is passed as a parameter, the returning list of
* pools will be filtered to include only those pools under the
* specified loadbalancer.
* @param {string} loadbalancerId
* Specifies the id of the loadbalancer to request pools for.
* @param {string} listenerId
* Specifies the id of the listener to request pools for.
*
* The listing result is an object with property "items". Each item is
* a pool.
*/
function getPools(loadbalancerId, listenerId) {
var params = $.extend({},
{
loadbalancerId: loadbalancerId,
listenerId: listenerId
}
);
if (!$.isEmptyObject(params)) {
params = { params: params };
}
return apiService.get('/api/lbaas/pools/', params)
.error(function () {
toastService.add('error', gettext('Unable to retrieve pools.'));
});
}
/** /**
* @name horizon.app.core.openstack-service-api.lbaasv2.getPool * @name horizon.app.core.openstack-service-api.lbaasv2.getPool
* @description * @description
@ -408,6 +442,28 @@
* Specifies the id of the health monitor. * Specifies the id of the health monitor.
*/ */
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.getHealthMonitors
* @description
* Get the list of healthmonitors.
* If a pool ID is passed as a parameter, the returning list of
* healthmonitors will be filtered to include only those healthmonitors under the
* specified pool.
* @param {string} id
* Specifies the id of the pool to request healthmonitors for.
*
* The listing result is an object with property "items". Each item is
* a healthmonitor.
*/
function getHealthMonitors(id) {
var params = id ? {params: {poolId: id}} : {};
return apiService.get('/api/lbaas/healthmonitors/', params)
.error(function () {
toastService.add('error', gettext('Unable to retrieve health monitors.'));
});
}
function getHealthMonitor(monitorId) { function getHealthMonitor(monitorId) {
return apiService.get('/api/lbaas/healthmonitors/' + monitorId + '/') return apiService.get('/api/lbaas/healthmonitors/' + monitorId + '/')
.error(function () { .error(function () {

View File

@ -90,6 +90,37 @@
error: 'Unable to retrieve listener.', error: 'Unable to retrieve listener.',
testInput: [ '1234', false ] testInput: [ '1234', false ]
}, },
{
func: 'getPools',
method: 'get',
path: '/api/lbaas/pools/',
error: 'Unable to retrieve pools.',
testInput: [ '1234' ],
data: { params: { loadbalancerId: '1234' } }
},
{
func: 'getPools',
method: 'get',
path: '/api/lbaas/pools/',
error: 'Unable to retrieve pools.',
testInput: [ '1234', '5678' ],
data: { params: { loadbalancerId: '1234', listenerId: '5678' } }
},
{
func: 'getPools',
method: 'get',
path: '/api/lbaas/pools/',
error: 'Unable to retrieve pools.',
testInput: [ undefined, '5678' ],
data: { params: { listenerId: '5678' } }
},
{
func: 'getPools',
method: 'get',
path: '/api/lbaas/pools/',
data: {},
error: 'Unable to retrieve pools.'
},
{ {
func: 'getPool', func: 'getPool',
method: 'get', method: 'get',
@ -142,6 +173,21 @@
data: { weight: 2 }, data: { weight: 2 },
testInput: [ '1234', '5678', { weight: 2 } ] testInput: [ '1234', '5678', { weight: 2 } ]
}, },
{
func: 'getHealthMonitors',
method: 'get',
path: '/api/lbaas/healthmonitors/',
error: 'Unable to retrieve health monitors.',
testInput: [ '1234' ],
data: { params: { poolId: '1234' } }
},
{
func: 'getHealthMonitors',
method: 'get',
path: '/api/lbaas/healthmonitors/',
data: {},
error: 'Unable to retrieve health monitors.'
},
{ {
func: 'getHealthMonitor', func: 'getHealthMonitor',
method: 'get', method: 'get',

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,68 +22,46 @@
.factory('horizon.dashboard.project.lbaasv2.healthmonitors.actions.create', createService); .factory('horizon.dashboard.project.lbaasv2.healthmonitors.actions.create', createService);
createService.$inject = [ createService.$inject = [
'$q', 'horizon.dashboard.project.lbaasv2.healthmonitors.resourceType',
'$location', 'horizon.framework.util.actions.action-result.service',
'horizon.dashboard.project.lbaasv2.workflow.modal', 'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext', 'horizon.framework.util.i18n.gettext'
'horizon.framework.util.q.extensions'
]; ];
/** /**
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.healthmonitors.actions.createService * @name horizon.dashboard.project.lbaasv2.healthmonitors.actions.createService
*
* @description * @description
* Provides the service for creating a health monitor resource. * Provides the service for creating a health monitor resource.
* @param $q The angular service for promises. *
* @param $location The angular $location service. * @param resourceType The health monitor resource type.
* @param actionResultService The horizon action result service.
* @param workflowModal The LBaaS workflow modal service. * @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
* @param qExtensions Horizon extensions to the $q service. *
* @returns The health monitor create service. * @returns The health monitor create service.
*/ */
function createService($q, $location, workflowModal, policy, gettext, qExtensions) { function createService(resourceType, actionResultService, workflowModal, policy, gettext) {
var loadbalancerId, listenerId, poolId, statePromise; return workflowModal.init({
var create = workflowModal.init({
controller: 'CreateHealthMonitorWizardController', controller: 'CreateHealthMonitorWizardController',
message: gettext('A new health monitor is being created.'), message: gettext('A new health monitor is being created.'),
handle: onCreate, handle: handle,
allowed: allowed allowed: allowed
}); });
var service = { function allowed() {
init: init, return policy.ifAllowed({ rules: [['neutron', 'create_health_monitor']] });
create: create
};
return service;
//////////////
function init(_loadbalancerId_, _listenerId_, _statePromise_) {
loadbalancerId = _loadbalancerId_;
listenerId = _listenerId_;
statePromise = _statePromise_;
return service;
} }
function allowed(pool) { function handle(response) {
poolId = pool.id; var newHealthMonitor = response.data;
return $q.all([ return actionResultService.getActionResult()
statePromise, .created(resourceType, newHealthMonitor.id)
qExtensions.booleanAsPromise(!pool.health_monitor_id), .result;
policy.ifAllowed({ rules: [['neutron', 'create_health_monitor']] })
]);
} }
function onCreate(response) {
var healthMonitorId = response.data.id;
$location.path('project/load_balancer/' + loadbalancerId + '/listeners/' +
listenerId + '/pools/' + poolId + '/healthmonitors/' + healthMonitorId);
}
} }
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,89 +18,25 @@
'use strict'; 'use strict';
describe('LBaaS v2 Create Health Monitor Action Service', function() { describe('LBaaS v2 Create Health Monitor Action Service', function() {
var scope, $q, $location, policy, init, service, loadBalancerState; var policy, service;
function allowed(item) {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var promise = service.create.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith(
{rules: [['neutron', 'create_health_monitor']]});
return allowed;
}
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {
open: function() {
return {
result: {
then: function(func) {
func({ data: { id: 'healthmonitor1' } });
}
}
};
}
});
}));
beforeEach(inject(function ($injector) { beforeEach(inject(function ($injector) {
scope = $injector.get('$rootScope').$new();
$q = $injector.get('$q');
policy = $injector.get('horizon.app.core.openstack-service-api.policy'); policy = $injector.get('horizon.app.core.openstack-service-api.policy');
$location = $injector.get('$location');
service = $injector.get('horizon.dashboard.project.lbaasv2.healthmonitors.actions.create'); service = $injector.get('horizon.dashboard.project.lbaasv2.healthmonitors.actions.create');
init = service.init;
loadBalancerState = $q.defer();
})); }));
it('should define the correct service properties', function() { it('should check policy to allow creating a health monitor', function() {
expect(service.init).toBeDefined(); spyOn(policy, 'ifAllowed').and.returnValue(true);
expect(service.create).toBeDefined(); expect(service.allowed()).toBe(true);
expect(policy.ifAllowed)
.toHaveBeenCalledWith({rules: [['neutron', 'create_health_monitor']]});
}); });
it('should have the "allowed" and "perform" functions', function() { it('should handle the action result properly', function() {
expect(service.create.allowed).toBeDefined(); var result = service.handle({data: {id: 1}});
expect(service.create.perform).toBeDefined(); expect(result.created[0].id).toBe(1);
});
it('should allow creating a health monitor under an ACTIVE load balancer', function() {
loadBalancerState.resolve();
init('active', '1', loadBalancerState.promise);
expect(allowed({})).toBe(true);
});
it('should not allow creating a health monitor under a NON-ACTIVE load balancer', function() {
loadBalancerState.reject();
init('non-active', '1', loadBalancerState.promise);
expect(allowed({})).toBe(false);
});
it('should not allow creating a health monitor if one already exists', function() {
loadBalancerState.resolve();
init('active', '1', loadBalancerState.promise);
expect(allowed({ health_monitor_id: '1234' })).toBe(false);
});
it('should redirect after create', function() {
loadBalancerState.resolve();
spyOn($location, 'path').and.callThrough();
init('loadbalancer1', 'listener1', loadBalancerState.promise).create.allowed({id: 'pool1'});
service.create.perform();
expect($location.path).toHaveBeenCalledWith(
'project/load_balancer/loadbalancer1/listeners/listener1/pools/pool1/' +
'healthmonitors/healthmonitor1');
}); });
}); });

View File

@ -32,6 +32,7 @@
$scope, $routeParams, model, workflowService, gettext $scope, $routeParams, model, workflowService, gettext
) { ) {
var loadbalancerId = $routeParams.loadbalancerId; var loadbalancerId = $routeParams.loadbalancerId;
var poolId = $routeParams.poolId;
var scope = $scope; var scope = $scope;
scope.model = model; scope.model = model;
scope.submit = scope.model.submit; scope.submit = scope.model.submit;
@ -40,7 +41,7 @@
'fa fa-cloud-download', 'fa fa-cloud-download',
['monitor'] ['monitor']
); );
scope.model.initialize('monitor', false, loadbalancerId, scope.launchContext.id); scope.model.initialize('monitor', false, loadbalancerId, poolId);
} }
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,9 +22,9 @@
.factory('horizon.dashboard.project.lbaasv2.healthmonitors.actions.delete', deleteService); .factory('horizon.dashboard.project.lbaasv2.healthmonitors.actions.delete', deleteService);
deleteService.$inject = [ deleteService.$inject = [
'$q', 'horizon.dashboard.project.lbaasv2.healthmonitors.resourceType',
'horizon.framework.util.actions.action-result.service',
'$location', '$location',
'$route',
'horizon.framework.widgets.modal.deleteModalService', 'horizon.framework.widgets.modal.deleteModalService',
'horizon.app.core.openstack-service-api.lbaasv2', 'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
@ -33,24 +34,27 @@
/** /**
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.healthmonitors.actions.deleteService * @name horizon.dashboard.project.lbaasv2.healthmonitors.actions.deleteService
*
* @description * @description
* Brings up the delete health monitor confirmation modal dialog. * Brings up the delete health monitor confirmation modal dialog.
* On submit, deletes selected health monitor. * On submit, deletes selected health monitor.
* On cancel, does nothing. * On cancel, does nothing.
* @param $q The angular service for promises. *
* @param resourceType The health monitor resource type.
* @param actionResultService The horizon action result service.
* @param $location The angular $location service. * @param $location The angular $location service.
* @param $route The angular $route service.
* @param deleteModal The horizon delete modal service. * @param deleteModal The horizon delete modal service.
* @param api The LBaaS v2 API service. * @param api The LBaaS v2 API service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
*
* @returns The health monitor delete service. * @returns The health monitor delete service.
*/ */
function deleteService( function deleteService(
$q, $location, $route, deleteModal, api, policy, gettext resourceType, actionResultService, $location, deleteModal, api, policy, gettext
) { ) {
var loadbalancerId, listenerId, poolId, statePromise; var loadbalancerId, listenerId, poolId;
var context = { var context = {
labels: { labels: {
title: gettext('Confirm Delete Health Monitor'), title: gettext('Confirm Delete Health Monitor'),
@ -68,50 +72,51 @@
var service = { var service = {
perform: perform, perform: perform,
allowed: allowed, allowed: allowed,
init: init deleteResult: deleteResult // exposed just for testing
}; };
return service; return service;
////////////// //////////////
function init(_loadbalancerId_, _listenerId_, _poolId_, _statePromise_) {
loadbalancerId = _loadbalancerId_;
listenerId = _listenerId_;
poolId = _poolId_;
statePromise = _statePromise_;
return service;
}
function perform(item) {
deleteModal.open({ $emit: actionComplete }, [item], context);
}
function allowed(/*item*/) { function allowed(/*item*/) {
return $q.all([ // This rule is made up and should therefore always pass. I assume at some point there
statePromise, // will be a valid rule similar to this that we will want to use.
// This rule is made up and should therefore always pass. I assume at some point there return policy.ifAllowed({ rules: [['neutron', 'delete_health_monitor']] });
// will be a valid rule similar to this that we will want to use.
policy.ifAllowed({ rules: [['neutron', 'delete_health_monitor']] })
]);
} }
function deleteItem(id) { function perform(items, scope) {
return api.deleteHealthMonitor(id, true); var healthMonitors = angular.isArray(items) ? items : [items];
healthMonitors.map(function(item) {
loadbalancerId = item.loadbalancerId;
listenerId = item.listenerId;
poolId = item.poolId;
});
return deleteModal.open(scope, healthMonitors, context).then(deleteResult);
} }
function actionComplete(eventType) { function deleteResult(deleteModalResult) {
if (eventType === context.failedEvent) { // To make the result of this action generically useful, reformat the return
// Error, reload page // from the deleteModal into a standard form
$route.reload(); var actionResult = actionResultService.getActionResult();
} else { deleteModalResult.pass.forEach(function markDeleted(item) {
// Success, go back to pool details page actionResult.deleted(resourceType, item.context.id);
});
deleteModalResult.fail.forEach(function markFailed(item) {
actionResult.failed(resourceType, item.context.id);
});
if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) {
var path = 'project/load_balancer/' + loadbalancerId + var path = 'project/load_balancer/' + loadbalancerId +
'/listeners/' + listenerId + '/listeners/' + listenerId +
'/pools/' + poolId; '/pools/' + poolId;
$location.path(path); $location.path(path);
} }
return actionResult.result;
} }
function deleteItem(id) {
return api.deleteHealthMonitor(id, true);
}
} }
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,140 +18,87 @@
'use strict'; 'use strict';
describe('LBaaS v2 Health Monitor Delete Service', function() { describe('LBaaS v2 Health Monitor Delete Service', function() {
var service, policy, modal, lbaasv2Api, $scope, $location, $q, toast, monitor; beforeEach(module('horizon.app.core'));
function allowed(item) {
spyOn(policy, 'ifAllowed').and.returnValue(makePromise());
var promise = service.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
$scope.$apply();
expect(policy.ifAllowed)
.toHaveBeenCalledWith({rules: [['neutron', 'delete_health_monitor']]});
return allowed;
}
function makePromise(reject) {
var def = $q.defer();
def[reject ? 'reject' : 'resolve']();
return def.promise;
}
function isActionable(id) {
if (id === 'active') {
return $q.when();
} else {
return $q.reject();
}
}
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module('horizon.framework'));
beforeEach(function() { var deleteModalService, service, lbaasv2API, policyAPI, $location;
monitor = { id: '1', name: 'HealthMonitor1' };
});
beforeEach(module(function($provide) { beforeEach(inject(function($injector) {
$provide.value('$uibModal', {
open: function() {
return {
result: makePromise()
};
}
});
$provide.value('horizon.app.core.openstack-service-api.lbaasv2', {
deleteHealthMonitor: function() {
return makePromise();
}
});
$provide.value('$location', {
path: function() {
return '';
}
});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
lbaasv2Api = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
modal = $injector.get('horizon.framework.widgets.modal.deleteModalService');
$scope = $injector.get('$rootScope').$new();
$location = $injector.get('$location');
$q = $injector.get('$q');
toast = $injector.get('horizon.framework.widgets.toast.service');
service = $injector.get('horizon.dashboard.project.lbaasv2.healthmonitors.actions.delete'); service = $injector.get('horizon.dashboard.project.lbaasv2.healthmonitors.actions.delete');
service.init('1', '2', '3', isActionable('active')); lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
$scope.$apply(); deleteModalService = $injector.get('horizon.framework.widgets.modal.deleteModalService');
policyAPI = $injector.get('horizon.app.core.openstack-service-api.policy');
$location = $injector.get('$location');
})); }));
it('should have the "allowed" and "perform" functions', function() { describe('perform method', function() {
expect(service.allowed).toBeDefined(); beforeEach(function () {
expect(service.perform).toBeDefined(); // just need for this to return something that looks like a promise but does nothing
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
});
it('should open the modal with correct label', function () {
service.perform({name: 'spam'});
var labels = deleteModalService.open.calls.argsFor(0)[2].labels;
expect(deleteModalService.open).toHaveBeenCalled();
angular.forEach(labels, function eachLabel(label) {
expect(label.toLowerCase()).toContain('health monitor');
});
});
it('should open the delete modal with correct entities', function () {
service.perform([{name: 'one'}, {name: 'two'}]);
var entities = deleteModalService.open.calls.argsFor(0)[1];
expect(deleteModalService.open).toHaveBeenCalled();
expect(entities.length).toEqual(2);
});
it('should pass in a function that deletes a health monitor', function () {
spyOn(lbaasv2API, 'deleteHealthMonitor').and.callFake(angular.noop);
service.perform({id: 1, name: 'one'});
var contextArg = deleteModalService.open.calls.argsFor(0)[2];
var deleteFunction = contextArg.deleteEntity;
deleteFunction(1);
expect(lbaasv2API.deleteHealthMonitor).toHaveBeenCalledWith(1, true);
});
}); });
it('should allow deleting health monitor from load balancer in ACTIVE state', function() { it('should handle the action result properly', function() {
expect(allowed()).toBe(true);
});
it('should not allow deleting health monitor from load balancer in PENDING state', function() {
service.init('1', '2', '3', isActionable('pending'));
expect(allowed()).toBe(false);
});
it('should open the delete modal', function() {
spyOn(modal, 'open');
service.perform(monitor);
$scope.$apply();
expect(modal.open.calls.count()).toBe(1);
var args = modal.open.calls.argsFor(0);
expect(args.length).toBe(3);
expect(args[0]).toEqual({ $emit: jasmine.any(Function) });
expect(args[1]).toEqual([monitor]);
expect(args[2]).toEqual(jasmine.objectContaining({
labels: jasmine.any(Object),
deleteEntity: jasmine.any(Function)
}));
expect(args[2].labels.title).toBe('Confirm Delete Health Monitor');
});
it('should pass function to modal that deletes the health monitor', function() {
spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deleteHealthMonitor').and.callThrough();
service.perform(monitor);
$scope.$apply();
expect(lbaasv2Api.deleteHealthMonitor.calls.count()).toBe(1);
expect(lbaasv2Api.deleteHealthMonitor).toHaveBeenCalledWith('1', true);
});
it('should show message if any items fail to be deleted', function() {
spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deleteHealthMonitor').and.returnValue(makePromise(true));
spyOn(toast, 'add');
service.perform(monitor);
$scope.$apply();
expect(modal.open).toHaveBeenCalled();
expect(lbaasv2Api.deleteHealthMonitor.calls.count()).toBe(1);
expect(toast.add).toHaveBeenCalledWith('error', 'The following health monitor could not ' +
'be deleted: HealthMonitor1.');
});
it('should return to pool details after delete', function() {
var path = 'project/load_balancer/1/listeners/2/pools/3';
spyOn($location, 'path'); spyOn($location, 'path');
spyOn(toast, 'add'); spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
service.perform(monitor); spyOn(lbaasv2API, 'deleteHealthMonitor').and.callFake(angular.noop);
$scope.$apply(); service.perform({loadbalancerId: 1, listenerId: 2, poolId: 3, id: 1, name: 'one'});
var result = service.deleteResult({
fail: [],
pass: [{
context: {
id: 1
}
}]
});
var path = 'project/load_balancer/1/listeners/2/pools/3';
expect($location.path).toHaveBeenCalledWith(path); expect($location.path).toHaveBeenCalledWith(path);
expect(toast.add).toHaveBeenCalledWith('success', 'Deleted health monitor: HealthMonitor1.'); expect(result.deleted[0].id).toBe(1);
result = service.deleteResult({
pass: [],
fail: [{
context: {
id: 1
}
}]
});
expect(result.failed[0].id).toBe(1);
}); });
}); describe('allow method', function() {
it('should use default policy if batch action', function () {
spyOn(policyAPI, 'ifAllowed');
service.allowed();
expect(policyAPI.ifAllowed).toHaveBeenCalled();
});
}); // end of allowed
}); // end of delete
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,8 +22,8 @@
.factory('horizon.dashboard.project.lbaasv2.healthmonitors.actions.edit', editService); .factory('horizon.dashboard.project.lbaasv2.healthmonitors.actions.edit', editService);
editService.$inject = [ editService.$inject = [
'$q', 'horizon.dashboard.project.lbaasv2.healthmonitors.resourceType',
'$route', 'horizon.framework.util.actions.action-result.service',
'horizon.dashboard.project.lbaasv2.workflow.modal', 'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext' 'horizon.framework.util.i18n.gettext'
@ -31,50 +32,36 @@
/** /**
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.healthmonitors.actions.editService * @name horizon.dashboard.project.lbaasv2.healthmonitors.actions.editService
*
* @description * @description
* Provides the service for editing a health monitor resource. * Provides the service for editing a health monitor resource.
* @param $q The angular service for promises. *
* @param $route The angular $route service. * @param resourceType The health monitor resource type.
* @param actionResultService The horizon action result service.
* @param workflowModal The LBaaS workflow modal service. * @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
*
* @returns The health monitor edit service. * @returns The health monitor edit service.
*/ */
function editService($q, $route, workflowModal, policy, gettext) { function editService(resourceType, actionResultService, workflowModal, policy, gettext) {
var statePromise;
var edit = workflowModal.init({ return workflowModal.init({
controller: 'EditHealthMonitorWizardController', controller: 'EditHealthMonitorWizardController',
message: gettext('The health monitor has been updated.'), message: gettext('The health monitor has been updated.'),
handle: handle, handle: handle,
allowed: allowed allowed: allowed
}); });
var service = {
init: init,
edit: edit
};
return service;
//////////////
function init(_statePromise_) {
statePromise = _statePromise_;
return service;
}
function allowed(/*healthmonitor*/) { function allowed(/*healthmonitor*/) {
return $q.all([ return policy.ifAllowed({ rules: [['neutron', 'update_health_monitor']] });
statePromise,
policy.ifAllowed({ rules: [['neutron', 'update_health_monitor']] })
]);
} }
function handle(/*response*/) { function handle(response) {
$route.reload(); return actionResultService.getActionResult()
.updated(resourceType, response.config.data.monitor.id)
.result;
} }
} }
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,81 +18,25 @@
'use strict'; 'use strict';
describe('LBaaS v2 Edit Health Monitor Action Service', function() { describe('LBaaS v2 Edit Health Monitor Action Service', function() {
var scope, $q, $route, policy, init, service, loadBalancerState; var policy, service;
function allowed(item) {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var promise = service.edit.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith(
{rules: [['neutron', 'update_health_monitor']]});
return allowed;
}
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {
open: function() {
return {
result: {
then: function(func) {
func({ data: { id: 'healthmonitor1' } });
}
}
};
}
});
}));
beforeEach(inject(function ($injector) { beforeEach(inject(function ($injector) {
scope = $injector.get('$rootScope').$new();
$q = $injector.get('$q');
policy = $injector.get('horizon.app.core.openstack-service-api.policy'); policy = $injector.get('horizon.app.core.openstack-service-api.policy');
$route = $injector.get('$route');
service = $injector.get('horizon.dashboard.project.lbaasv2.healthmonitors.actions.edit'); service = $injector.get('horizon.dashboard.project.lbaasv2.healthmonitors.actions.edit');
init = service.init;
loadBalancerState = $q.defer();
})); }));
it('should define the correct service properties', function() { it('should check policy to allow editing a health monitor', function() {
expect(service.init).toBeDefined(); spyOn(policy, 'ifAllowed').and.returnValue(true);
expect(service.edit).toBeDefined(); expect(service.allowed()).toBe(true);
expect(policy.ifAllowed)
.toHaveBeenCalledWith({rules: [['neutron', 'update_health_monitor']]});
}); });
it('should have the "allowed" and "perform" functions', function() { it('should handle the action result properly', function() {
expect(service.edit.allowed).toBeDefined(); var result = service.handle({config: {data: {monitor: {id: 1}}}});
expect(service.edit.perform).toBeDefined(); expect(result.updated[0].id).toBe(1);
});
it('should allow edit a health monitor under an ACTIVE load balancer', function() {
loadBalancerState.resolve();
init(loadBalancerState.promise);
expect(allowed({})).toBe(true);
});
it('should not allow editing a health monitor under a NON-ACTIVE load balancer', function() {
loadBalancerState.reject();
init(loadBalancerState.promise);
expect(allowed({})).toBe(false);
});
it('should reload page after edit', function() {
loadBalancerState.resolve();
spyOn($route, 'reload');
init(loadBalancerState.promise).edit.allowed({});
service.edit.perform();
expect($route.reload).toHaveBeenCalled();
}); });
}); });

View File

@ -1,81 +0,0 @@
/*
* 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.healthmonitors')
.factory('horizon.dashboard.project.lbaasv2.healthmonitors.actions.rowActions',
rowActions);
rowActions.$inject = [
'horizon.framework.util.i18n.gettext',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.edit',
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.delete'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.healthmonitors.actions.rowActions
*
* @description
* Provides the service for the health monitor row actions.
*
* @param gettext The horizon gettext function for translation.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param editService The LBaaS v2 health monitor edit service.
* @param deleteService The LBaaS v2 health monitor delete service.
* @returns Health monitor row actions service object.
*/
function rowActions(gettext, loadBalancersService, editService, deleteService) {
var loadBalancerIsActionable, loadbalancerId, listenerId, poolId;
var service = {
actions: actions,
init: init
};
return service;
///////////////
function init(_loadbalancerId_, _listenerId_, _poolId_) {
loadbalancerId = _loadbalancerId_;
listenerId = _listenerId_;
poolId = _poolId_;
loadBalancerIsActionable = loadBalancersService.isActionable(loadbalancerId);
return service;
}
function actions() {
return [{
service: editService.init(loadBalancerIsActionable).edit,
template: {
text: gettext('Edit')
}
},{
service: deleteService.init(loadbalancerId, listenerId, poolId, loadBalancerIsActionable),
template: {
text: gettext('Delete Health Monitor'),
type: 'delete'
}
}];
}
}
})();

View File

@ -1,51 +0,0 @@
/*
* 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';
describe('LBaaS v2 Health Monitor Row Actions Service', function() {
var actions;
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function ($injector) {
var rowActionsService = $injector.get(
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.rowActions');
actions = rowActionsService.init('1', '2', '3').actions();
var loadbalancerService = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.service');
spyOn(loadbalancerService, 'isActionable').and.returnValue(true);
}));
it('should define correct table row actions', function() {
expect(actions.length).toBe(2);
expect(actions[0].template.text).toBe('Edit');
expect(actions[1].template.text).toBe('Delete Health Monitor');
});
it('should have the "allowed" and "perform" functions', function() {
actions.forEach(function(action) {
expect(action.service.allowed).toBeDefined();
expect(action.service.perform).toBeDefined();
});
});
});
})();

View File

@ -1,100 +0,0 @@
/*
* 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.healthmonitors')
.controller('HealthMonitorDetailController', HealthMonitorDetailController);
HealthMonitorDetailController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.rowActions',
'$routeParams',
'$q'
];
/**
* @ngdoc controller
* @name HealthMonitorDetailController
*
* @description
* Controller for the LBaaS v2 health monitor detail page.
*
* @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, $q) {
var ctrl = this;
ctrl.loading = true;
ctrl.error = false;
ctrl.actions = rowActions.init($routeParams.loadbalancerId,
$routeParams.listenerId,
$routeParams.poolId).actions;
init();
////////////////////////////////
function init() {
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 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

@ -1,111 +0,0 @@
/*
* 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';
describe('LBaaS v2 Healthmonitor Detail Controller', function() {
var lbaasv2API, $controller, apiFail, qAllFail;
function fakePromise(data, reject) {
return {
then: function(success, fail) {
if (reject) {
fail();
} else {
success({ data: data });
}
return fakePromise();
}
};
}
function fakeAPI() {
return fakePromise('foo', apiFail);
}
function loadbalancerAPI() {
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'
}
});
}
///////////////////////
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.framework.conf'));
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);
$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');
expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('loadbalancerId');
expect(ctrl.loadbalancer).toEqual({ provisioning_status: 'ACTIVE' });
expect(ctrl.listener).toBe('foo');
expect(ctrl.pool).toBe('foo');
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,48 +0,0 @@
<div ng-controller="HealthMonitorDetailController as ctrl">
<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
<div ng-if="!ctrl.loading && !ctrl.error">
<div class="page-header">
<actions allowed="ctrl.actions" type="row" item="ctrl.healthmonitor"
ng-if="ctrl.healthmonitor" class="actions_column pull-right"></actions>
<ol class="breadcrumb">
<li><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li><a href="project/load_balancer/{$ ::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>
</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>Max Retries Down</dt>
<dd>{$ ::ctrl.healthmonitor.max_retries_down $}</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>Project ID</dt>
<dd>{$ ::ctrl.healthmonitor.project_id $}</dd>
<dt translate>Created At</dt>
<dd>{$ ::ctrl.healthmonitor.created_at $}</dd>
<dt translate>Updated At</dt>
<dd>{$ ::ctrl.healthmonitor.updated_at $}</dd>
</dl>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,104 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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.healthmonitors')
.controller('HealthMonitorDetailController', HealthMonitorDetailController);
HealthMonitorDetailController.$inject = [
'loadbalancer',
'listener',
'pool',
'healthmonitor',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.healthmonitors.resourceType',
'horizon.framework.conf.resource-type-registry.service',
'horizon.framework.widgets.modal-wait-spinner.service',
'$q'
];
/**
* @ngdoc controller
* @name HealthMonitorDetailController
*
* @description
* Controller for the LBaaS v2 health monitor detail page.
*
* @param loadbalancer The loadbalancer object.
* @param listener The listener object.
* @param pool The pool object.
* @param healthmonitor The health monitor object.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param resourceType The health monitor resource type.
* @param typeRegistry The horizon resource type registry service.
* @param spinnerService The horizon modal wait spinner service.
* @param $q The angular service for promises.
*
* @returns undefined
*/
function HealthMonitorDetailController(
loadbalancer, listener, pool, healthmonitor, loadBalancersService,
resourceType, typeRegistry, spinnerService, $q
) {
var ctrl = this;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
ctrl.loadbalancer = loadbalancer;
ctrl.listener = listener;
ctrl.pool = pool;
ctrl.healthmonitor = healthmonitor;
ctrl.resourceType = typeRegistry.getResourceType(resourceType);
ctrl.context = {};
ctrl.context.identifier = ctrl.resourceType.parsePath(ctrl.healthmonitor.id);
ctrl.resultHandler = actionResultHandler;
function actionResultHandler(returnValue) {
return $q.when(returnValue, actionSuccessHandler);
}
function loadData(response) {
spinnerService.hideModalSpinner();
ctrl.showDetails = true;
ctrl.resourceType.initActions();
ctrl.healthmonitor = response.data;
ctrl.healthmonitor.loadbalancerId = ctrl.loadbalancer.id;
ctrl.healthmonitor.listenerId = ctrl.listener.id;
ctrl.healthmonitor.poolId = ctrl.pool.id;
}
function actionSuccessHandler(result) {
// The action has completed (for whatever "complete" means to that
// action. Notice the view doesn't really need to know the semantics of the
// particular action because the actions return data in a standard form.
// That return includes the id and type of each created, updated, deleted
// and failed item.
// Currently just refreshes the display each time.
if (result) {
spinnerService.showModalSpinner(gettext('Please Wait'));
ctrl.showDetails = false;
ctrl.context.loadPromise = ctrl.resourceType.load(ctrl.context.identifier);
ctrl.context.loadPromise.then(loadData);
}
}
}
})();

View File

@ -0,0 +1,104 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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';
describe('LBaaS v2 HealthMonitor Detail Controller', function() {
var deferred, service, ctrl, scope, $timeout, $q, actionResultService;
///////////////////////
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($controller, $rootScope, _$q_, _$timeout_) {
$q = _$q_;
deferred = $q.defer();
service = {
getResourceType: function() {
return {
load: function() { return deferred.promise; },
parsePath: function() { return 'my-context'; },
itemName: function() { return 'A name'; },
initActions: angular.noop
};
},
getDefaultDetailsTemplateUrl: angular.noop
};
actionResultService = {
getIdsOfType: function() { return []; }
};
$timeout = _$timeout_;
scope = $rootScope.$new();
ctrl = $controller('HealthMonitorDetailController', {
$scope: scope,
loadbalancer: { id: '123' },
listener: { id: '123' },
pool: { id: '123' },
healthmonitor: { id: '123' },
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
}
});
}));
it('should create a controller', function() {
expect(ctrl).toBeDefined();
expect(ctrl.loadbalancer).toBeDefined();
expect(ctrl.listener).toBeDefined();
expect(ctrl.pool).toBeDefined();
expect(ctrl.healthmonitor).toBeDefined();
});
describe('resultHandler', function() {
it('handles empty results', function() {
var result = $q.defer();
result.resolve({});
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles falsy results', function() {
var result = $q.defer();
result.resolve(false);
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles matched results', function() {
spyOn(actionResultService, 'getIdsOfType').and.returnValue([1, 2, 3]);
var result = $q.defer();
result.resolve({some: 'thing'});
ctrl.resultHandler(result.promise);
deferred.resolve({data: {some: 'data'}});
$timeout.flush();
expect(ctrl.showDetails).toBe(true);
});
});
});
})();

View File

@ -0,0 +1,53 @@
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li><a href="project/load_balancer/{$ ::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>
</ol>
<div class="row">
<div class="col-xs-12 col-sm-9 text-left">
<ul class="list-inline">
<li>
<strong translate>Type</strong>
{$ ::ctrl.healthmonitor.type $}
</li>
<li>
<strong translate>Operating Status</strong>
{$ ctrl.healthmonitor.operating_status | decode:ctrl.operatingStatus $}
</li>
<li>
<strong translate>Provisioning Status</strong>
{$ ctrl.healthmonitor.provisioning_status | decode:ctrl.provisioningStatus $}
</li>
<li>
<strong translate>Admin State Up</strong>
{$ ctrl.healthmonitor.admin_state_up | yesno $}
</li>
</ul>
</div>
<div class="col-xs-12 col-sm-3 text-right details-item-actions">
<actions allowed="ctrl.resourceType.itemActions"
type="row"
item="ctrl.healthmonitor"
ng-if="ctrl.healthmonitor"
class="actions_column pull-right"
result-handler="ctrl.resultHandler"></actions>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 detail">
<hz-resource-property-list
resource-type-name="OS::Octavia::HealthMonitor"
cls="dl-horizontal"
item="ctrl.healthmonitor"
property-groups="[[
'id', 'name', 'project_id', 'created_at', 'updated_at',
'delay', 'timeout', 'max_retries', 'max_retries_down',
'http_method', 'url_path', 'expected_codes']]">
</hz-resource-property-list>
</div>
</div>

View File

@ -0,0 +1,10 @@
<hz-resource-property-list
resource-type-name="OS::Octavia::HealthMonitor"
item="item"
property-groups="[
['name', 'id', 'project_id'],
['created_at', 'updated_at'],
['type', 'delay', 'timeout'],
['max_retries', 'max_retries_down'],
['http_method', 'url_path', 'expected_codes']]">
</hz-resource-property-list>

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,6 +27,138 @@
*/ */
angular angular
.module('horizon.dashboard.project.lbaasv2.healthmonitors', []); .module('horizon.dashboard.project.lbaasv2.healthmonitors', [])
.constant('horizon.dashboard.project.lbaasv2.healthmonitors.resourceType',
'OS::Octavia::HealthMonitor')
.run(run);
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.create',
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.edit',
'horizon.dashboard.project.lbaasv2.healthmonitors.actions.delete',
'horizon.dashboard.project.lbaasv2.healthmonitors.resourceType'
];
function run(
registry,
basePath,
loadBalancerService,
createService,
editService,
deleteService,
resourceType
) {
var healthMonitorResourceType = registry.getResourceType(resourceType);
healthMonitorResourceType
.setNames(gettext('Health Monitor'), gettext('Health Monitors'))
.setSummaryTemplateUrl(basePath + 'healthmonitors/details/drawer.html')
.setProperties(healthMonitorProperties(loadBalancerService))
.setListFunction(loadBalancerService.getHealthMonitorsPromise)
.setLoadFunction(loadBalancerService.getHealthMonitorPromise)
.tableColumns
.append({
id: 'name',
priority: 1,
sortDefault: true,
urlFunction: loadBalancerService.getHealthMonitorDetailsPath
})
.append({
id: 'type',
priority: 1
})
.append({
id: 'operating_status',
priority: 1
})
.append({
id: 'provisioning_status',
priority: 1
})
.append({
id: 'admin_state_up',
priority: 1
});
healthMonitorResourceType.itemActions
.append({
id: 'healthMonitorEdit',
service: editService,
template: {
text: gettext('Edit Health Monitor')
}
})
.append({
id: 'healthMonitorDelete',
service: deleteService,
template: {
text: gettext('Delete Health Monitor'),
type: 'delete'
}
});
healthMonitorResourceType.globalActions
.append({
id: 'healthMonitorCreate',
service: createService,
template: {
type: 'create',
text: gettext('Create Health Monitor')
}
});
healthMonitorResourceType.batchActions
.append({
id: 'healthMonitorBatchDelete',
service: deleteService,
template: {
text: gettext('Delete Health Monitors'),
type: 'delete-selected'
}
});
}
function healthMonitorProperties(loadBalancerService) {
return {
id: gettext('ID'),
name: {
label: gettext('Name'),
filters: ['noName']
},
provisioning_status: {
label: gettext('Provisioning Status'),
values: loadBalancerService.provisioningStatus
},
operating_status: {
label: gettext('Operating Status'),
values: loadBalancerService.operatingStatus
},
admin_state_up: {
label: gettext('Admin State Up'),
filters: ['yesno']
},
type: gettext('Type'),
delay: gettext('Delay'),
timeout: gettext('Timeout'),
max_retries: gettext('Max Retries'),
max_retries_down: gettext('Max Retries Down'),
http_method: gettext('HTTP Method'),
url_path: gettext('URL Path'),
expected_codes: gettext('Expected Codes'),
project_id: gettext('Project ID'),
created_at: {
label: gettext('Created At'),
filters: ['noValue']
},
updated_at: {
label: gettext('Updated At'),
filters: ['noValue']
},
pools: gettext('Pools')
};
}
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,4 +23,46 @@
}); });
}); });
describe('LBaaS v2 Healthmonitors Registry', function () {
var registry, resourceType;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function($injector) {
resourceType = $injector.get('horizon.dashboard.project.lbaasv2.healthmonitors.resourceType');
registry = $injector.get('horizon.framework.conf.resource-type-registry.service');
}));
it('should define resourceType', function () {
expect(resourceType).toBeDefined();
});
it('should register item actions', function () {
var actions = registry.getResourceType(resourceType).itemActions;
expect(actionHasId(actions, 'healthMonitorEdit')).toBe(true);
expect(actionHasId(actions, 'healthMonitorDelete')).toBe(true);
});
it('should register global actions', function () {
var actions = registry.getResourceType(resourceType).globalActions;
expect(actionHasId(actions, 'healthMonitorCreate')).toBe(true);
});
it('should register batch actions', function () {
var actions = registry.getResourceType(resourceType).batchActions;
expect(actionHasId(actions, 'healthMonitorBatchDelete')).toBe(true);
});
function actionHasId(list, value) {
return list.filter(matchesId).length === 1;
function matchesId(action) {
if (action.id === value) {
return true;
}
}
}
});
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2015 IBM Corp. * Copyright 2015 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,12 +26,15 @@
angular angular
.module('horizon.dashboard.project.lbaasv2', [ .module('horizon.dashboard.project.lbaasv2', [
'ngRoute',
'horizon.dashboard.project.lbaasv2.loadbalancers', 'horizon.dashboard.project.lbaasv2.loadbalancers',
'horizon.dashboard.project.lbaasv2.listeners', 'horizon.dashboard.project.lbaasv2.listeners',
'horizon.dashboard.project.lbaasv2.pools', 'horizon.dashboard.project.lbaasv2.pools',
'horizon.dashboard.project.lbaasv2.members', 'horizon.dashboard.project.lbaasv2.members',
'horizon.dashboard.project.lbaasv2.healthmonitors' 'horizon.dashboard.project.lbaasv2.healthmonitors',
'horizon.framework.conf',
'horizon.framework.widgets',
'horizon.framework.util',
'horizon.app.core'
]) ])
.config(config) .config(config)
.constant('horizon.dashboard.project.lbaasv2.patterns', { .constant('horizon.dashboard.project.lbaasv2.patterns', {
@ -45,7 +49,12 @@
}) })
.constant('horizon.dashboard.project.lbaasv2.popovers', { .constant('horizon.dashboard.project.lbaasv2.popovers', {
ipAddresses: '<ul><li ng-repeat="addr in member.addresses">{$ addr.ip $}</li></ul>' ipAddresses: '<ul><li ng-repeat="addr in member.addresses">{$ addr.ip $}</li></ul>'
}); })
.run(['$rootScope', '$location', function ($rootScope, $location) {
$rootScope.$on('$routeChangeError', function() {
$location.path('project/load_balancer');
});
}]);
config.$inject = [ config.$inject = [
'$provide', '$provide',
@ -65,22 +74,212 @@
$routeProvider $routeProvider
.when(loadbalancers, { .when(loadbalancers, {
templateUrl: basePath + 'loadbalancers/table.html' templateUrl: basePath + 'loadbalancers/panel.html'
}) })
.when(loadbalancers + '/:loadbalancerId', { .when(loadbalancers + '/:loadbalancerId', {
templateUrl: basePath + 'loadbalancers/detail.html' templateUrl: basePath + 'loadbalancers/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
]
},
controller: 'LoadBalancerDetailController',
controllerAs: 'ctrl'
}) })
.when(listener, { .when(listener, {
templateUrl: basePath + 'listeners/detail.html' templateUrl: basePath + 'listeners/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
],
listener: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getListener($route.current.params.listenerId).then(
function success(response) {
response.data.loadbalancerId = $route.current.params.loadbalancerId;
return response.data;
}
);
}
]
},
controller: 'ListenerDetailController',
controllerAs: 'ctrl'
}) })
.when(pool, { .when(pool, {
templateUrl: basePath + 'pools/detail.html' templateUrl: basePath + 'pools/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
],
listener: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getListener($route.current.params.listenerId).then(
function success(response) {
return response.data;
}
);
}
],
pool: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getPool($route.current.params.poolId).then(
function success(response) {
response.data.loadbalancerId = $route.current.params.loadbalancerId;
response.data.listenerId = $route.current.params.listenerId;
return response.data;
}
);
}
]
},
controller: 'PoolDetailController',
controllerAs: 'ctrl'
}) })
.when(member, { .when(member, {
templateUrl: basePath + 'members/detail.html' templateUrl: basePath + 'members/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
],
listener: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getListener($route.current.params.listenerId).then(
function success(response) {
return response.data;
}
);
}
],
pool: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getPool($route.current.params.poolId).then(
function success(response) {
return response.data;
}
);
}
],
member: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getMember($route.current.params.poolId,
$route.current.params.memberId).then(
function success(response) {
response.data.loadbalancerId = $route.current.params.loadbalancerId;
response.data.listenerId = $route.current.params.listenerId;
response.data.poolId = $route.current.params.poolId;
return response.data;
}
);
}
]
},
controller: 'MemberDetailController',
controllerAs: 'ctrl'
}) })
.when(healthmonitor, { .when(healthmonitor, {
templateUrl: basePath + 'healthmonitors/detail.html' templateUrl: basePath + 'healthmonitors/details/detail.html',
resolve: {
loadbalancer: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getLoadBalancer($route.current.params.loadbalancerId, true).then(
function success(response) {
response.data.floating_ip_address = response.data.floating_ip.ip;
return response.data;
}
);
}
],
listener: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getListener($route.current.params.listenerId).then(
function success(response) {
return response.data;
}
);
}
],
pool: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getPool($route.current.params.poolId).then(
function success(response) {
return response.data;
}
);
}
],
healthmonitor: [
'$route',
'horizon.app.core.openstack-service-api.lbaasv2',
function($route, api) {
return api.getHealthMonitor(
$route.current.params.healthmonitorId).then(
function success(response) {
response.data.loadbalancerId = $route.current.params.loadbalancerId;
response.data.listenerId = $route.current.params.listenerId;
response.data.poolId = $route.current.params.poolId;
return response.data;
}
);
}
]
},
controller: 'HealthMonitorDetailController',
controllerAs: 'ctrl'
}); });
} }

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2015 IBM Corp. * Copyright 2015 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -104,43 +105,270 @@
inject(); inject();
}); });
it('should route URLs', function () { it('should route to loadbalancer panel', function () {
var loadbalancers = '/project/load_balancer'; var loadbalancers = '/project/load_balancer';
var listener = loadbalancers + '/:loadbalancerId/listeners/:listenerId';
var pool = listener + '/pools/:poolId';
var member = pool + '/members/:memberId';
var healthmonitor = pool + '/healthmonitors/:healthmonitorId';
var routes = [[ var routes = [[
loadbalancers, { loadbalancers, {
templateUrl: basePath + 'loadbalancers/table.html' templateUrl: basePath + 'loadbalancers/panel.html'
}
], [
loadbalancers + '/:loadbalancerId', {
templateUrl: basePath + 'loadbalancers/detail.html'
}
], [
listener, {
templateUrl: basePath + 'listeners/detail.html'
}
], [
pool, {
templateUrl: basePath + 'pools/detail.html'
}
], [
member, {
templateUrl: basePath + 'members/detail.html'
}
], [
healthmonitor, {
templateUrl: basePath + 'healthmonitors/detail.html'
} }
]]; ]];
expect($routeProvider.when.calls.count()).toBe(6); routes.forEach(function(route) {
angular.forEach($routeProvider.when.calls.all(), function(call, i) { expect($routeProvider.when).toHaveBeenCalledWith(route[0], route[1]);
expect(call.args).toEqual(routes[i]);
}); });
}); });
it('should route resolved loadbalancer detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/loadbalancers/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should route resolved listener detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
function listenerAPI() {
var listener = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(listener);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(listenerAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/listeners/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1/listeners/2');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should route resolved pool detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
function listenerAPI() {
var listener = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(listener);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function poolAPI() {
var pool = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(pool);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(listenerAPI);
spyOn(lbaasv2API, 'getPool').and.callFake(poolAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/pools/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1/listeners/2/pools/3');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should route resolved member detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
function listenerAPI() {
var listener = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(listener);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function poolAPI() {
var pool = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(pool);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function memberAPI() {
var member = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(member);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(listenerAPI);
spyOn(lbaasv2API, 'getPool').and.callFake(poolAPI);
spyOn(lbaasv2API, 'getMember').and.callFake(memberAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/members/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1/listeners/2/pools/3/members/4');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should route resolved health monitor detail', inject(function($injector) {
function loadbalancerAPI() {
var loadbalancer = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(loadbalancer);
},
then: function(callback) {
callback({ data: { id: 1, floating_ip: {}}});
}
};
}
function listenerAPI() {
var listener = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(listener);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function poolAPI() {
var pool = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(pool);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
function healthmonitorAPI() {
var healthmonitor = { provisioning_status: 'ACTIVE' };
return {
success: function(callback) {
callback(healthmonitor);
},
then: function(callback) {
callback({ data: { id: 1}});
}
};
}
var lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(listenerAPI);
spyOn(lbaasv2API, 'getPool').and.callFake(poolAPI);
spyOn(lbaasv2API, 'getHealthMonitor').and.callFake(healthmonitorAPI);
inject(function($route, $location, $rootScope, $httpBackend) {
$httpBackend.expectGET(
'/static/dashboard/project/lbaasv2/healthmonitors/details/detail.html'
).respond({});
$location.path('/project/load_balancer/1/listeners/2/pools/3/healthmonitors/4');
$rootScope.$digest();
expect($route.current).toBeDefined();
});
}));
it('should redirect to project home on route change error',
inject(function($location, $rootScope) {
spyOn($location, 'path').and.callThrough();
$rootScope.$emit('$routeChangeError', null, null, null, 'routeChangeError');
expect($location.path).toHaveBeenCalledWith('project/load_balancer');
})
);
}); });
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2015 IBM Corp. * Copyright 2015 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -74,9 +75,8 @@
} }
} }
/* Progress indicator while data is loading */ /* Progress indicator in the table while items are loading */
[table-status], [table-status] {
detail-status {
.progress { .progress {
margin: 0px auto; margin: 0px auto;
width: 25%; width: 25%;
@ -86,12 +86,9 @@ detail-status {
} }
} }
} }
detail-status {
.progress { .octavia-tabset {
margin-top: 25vh; .tab-content {
}
.error-actions {
text-align: center;
margin-top: 10px; margin-top: 10px;
} }
} }

View File

@ -1,107 +0,0 @@
/*
* 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.listeners')
.factory('horizon.dashboard.project.lbaasv2.listeners.actions.batchActions',
tableBatchActions);
tableBatchActions.$inject = [
'$q',
'$location',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.listeners.actions.delete'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.listeners.actions.batchActions
*
* @description
* Provides the service for the Listeners table batch actions.
*
* @param $q The angular service for promises.
* @param $location The angular $location service.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param deleteService The LBaaS v2 listeners delete service.
* @returns Listeners table batch actions service object.
*/
function tableBatchActions(
$q, $location, workflowModal, policy, gettext, loadBalancersService, deleteService
) {
var loadBalancerIsActionable, loadBalancerId;
var create = workflowModal.init({
controller: 'CreateListenerWizardController',
message: gettext('A new listener is being created.'),
handle: onCreate,
allowed: canCreate
});
var service = {
actions: actions,
init: init
};
return service;
///////////////
function init(_loadBalancerId_) {
loadBalancerId = _loadBalancerId_;
loadBalancerIsActionable = loadBalancersService.isActionable(loadBalancerId);
return service;
}
function actions() {
return [{
service: create,
template: {
type: 'create',
text: gettext('Create Listener')
}
},{
service: deleteService.init(loadBalancerId, loadBalancerIsActionable),
template: {
text: gettext('Delete Listeners'),
type: 'delete-selected'
}
}];
}
function canCreate() {
return $q.all([
loadBalancerIsActionable,
policy.ifAllowed({ rules: [['neutron', 'create_listener']] })
]);
}
function onCreate(response) {
var id = response.data.id;
$location.path('project/load_balancer/' + loadBalancerId + '/listeners/' + id);
}
}
})();

View File

@ -1,105 +0,0 @@
/*
* 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';
describe('LBaaS v2 Listeners Table Batch Actions Service', function() {
var $location, actions, policy, $scope, $q;
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
var response = {
data: {
id: '5678'
}
};
var modal = {
open: function() {
return {
result: {
then: function(func) {
func(response);
}
}
};
}
};
$provide.value('$uibModal', modal);
$provide.value('horizon.dashboard.project.lbaasv2.loadbalancers.service', {
isActionable: function() {
return $q.when();
}
});
$provide.value('horizon.app.core.openstack-service-api.policy', {
ifAllowed: function() {
return $q.when();
}
});
}));
beforeEach(inject(function ($injector) {
$location = $injector.get('$location');
$scope = $injector.get('$rootScope').$new();
$q = $injector.get('$q');
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
var batchActionsService = $injector.get(
'horizon.dashboard.project.lbaasv2.listeners.actions.batchActions');
actions = batchActionsService.init('1234').actions();
$scope.$apply();
}));
it('should define correct table batch actions', function() {
expect(actions.length).toBe(2);
expect(actions[0].template.text).toBe('Create Listener');
expect(actions[1].template.text).toBe('Delete Listeners');
});
it('should have the "allowed" and "perform" functions', function() {
actions.forEach(function(action) {
expect(action.service.allowed).toBeDefined();
expect(action.service.perform).toBeDefined();
});
});
it('should check policy to allow creating a listener', function() {
spyOn(policy, 'ifAllowed').and.callThrough();
var promise = actions[0].service.allowed();
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
$scope.$apply();
expect(allowed).toBe(true);
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'create_listener']]});
});
it('should redirect after create', function() {
spyOn($location, 'path').and.callThrough();
actions[0].service.perform();
expect($location.path).toHaveBeenCalledWith('project/load_balancer/1234/listeners/5678');
});
});
})();

View File

@ -0,0 +1,69 @@
/*
* Copyright 2017 Walmart.
*
* 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.listeners')
.factory('horizon.dashboard.project.lbaasv2.listeners.actions.create',
createService);
createService.$inject = [
'horizon.dashboard.project.lbaasv2.listeners.resourceType',
'horizon.framework.util.actions.action-result.service',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.listeners.actions.batchActions
*
* @description
* Provides the service for the Listeners creation.
*
* @param resourceType The listener resource type.
* @param actionResultService The horizon action result service.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
*
* @returns Listeners create service object.
*/
function createService(
resourceType, actionResultService, workflowModal, policy, gettext
) {
return workflowModal.init({
controller: 'CreateListenerWizardController',
message: gettext('A new listener is being created.'),
handle: handle,
allowed: allowed
});
function allowed() {
return policy.ifAllowed({ rules: [['neutron', 'create_listener']] });
}
function handle(response) {
return actionResultService.getActionResult()
.created(resourceType, response.data.id)
.result;
}
}
})();

View File

@ -0,0 +1,55 @@
/*
* Copyright 2017 Walmart.
*
* 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';
describe('LBaaS v2 Create Listener Action Service', function() {
var policy, service;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: {
then: function(func) {
func({ data: { id: 'listener1' } });
}
}
};
}
});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
service = $injector.get('horizon.dashboard.project.lbaasv2.listeners.actions.create');
}));
it('should check policy to allow creating a listener', function() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
expect(service.allowed()).toBe(true);
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'create_listener']]});
});
it('should handle the action result properly', function() {
var result = service.handle({data: {id: 1}});
expect(result.created[0].id).toBe(1);
});
});
})();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,9 +22,10 @@
.factory('horizon.dashboard.project.lbaasv2.listeners.actions.delete', deleteService); .factory('horizon.dashboard.project.lbaasv2.listeners.actions.delete', deleteService);
deleteService.$inject = [ deleteService.$inject = [
'horizon.dashboard.project.lbaasv2.listeners.resourceType',
'horizon.framework.util.actions.action-result.service',
'$q', '$q',
'$location', '$location',
'$route',
'horizon.framework.widgets.modal.deleteModalService', 'horizon.framework.widgets.modal.deleteModalService',
'horizon.app.core.openstack-service-api.lbaasv2', 'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
@ -35,26 +37,31 @@
/** /**
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.listeners.actions.deleteService * @name horizon.dashboard.project.lbaasv2.listeners.actions.deleteService
*
* @description * @description
* Brings up the delete listeners confirmation modal dialog. * Brings up the delete listeners confirmation modal dialog.
* On submit, deletes selected listeners. * On submit, deletes selected listeners.
* On cancel, does nothing. * On cancel, does nothing.
*
* @param resourceType The listener resource type.
* @param actionResultService The horizon action result service.
* @param $q The angular service for promises. * @param $q The angular service for promises.
* @param $location The angular $location service. * @param $location The angular $location service.
* @param $route The angular $route service.
* @param deleteModal The horizon delete modal service. * @param deleteModal The horizon delete modal service.
* @param api The LBaaS v2 API service. * @param api The LBaaS v2 API service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param toast The horizon message service. * @param toast The horizon message service.
* @param qExtensions Horizon extensions to the $q service. * @param qExtensions Horizon extensions to the $q service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
* @returns The listeners table delete service. *
* @returns The listeners delete service.
*/ */
function deleteService( function deleteService(
$q, $location, $route, deleteModal, api, policy, toast, qExtensions, gettext resourceType, actionResultService, $q, $location,
deleteModal, api, policy, toast, qExtensions, gettext
) { ) {
var loadbalancerId, statePromise; var loadbalancerId, scope;
var notAllowedMessage = gettext('The following listeners will not be deleted ' + var notAllowedMessage = gettext('The following listeners will not be deleted ' +
'due to existing pools: %s.'); 'due to existing pools: %s.');
var context = { var context = {
@ -74,54 +81,38 @@
var service = { var service = {
perform: perform, perform: perform,
allowed: allowed, allowed: allowed
init: init
}; };
return service; return service;
////////////// //////////////
function init(_loadbalancerId_, _statePromise_) { function perform(items, _scope_) {
loadbalancerId = _loadbalancerId_; scope = _scope_;
statePromise = _statePromise_; var listeners = angular.isArray(items) ? items : [items];
return service; listeners.map(function(item) {
loadbalancerId = item.loadbalancerId;
});
return qExtensions.allSettled(listeners.map(checkPermission)).then(afterCheck);
} }
function perform(items) { function deleteResult(deleteModalResult) {
if (angular.isArray(items)) { // To make the result of this action generically useful, reformat the return
qExtensions.allSettled(items.map(checkPermission)).then(afterCheck); // from the deleteModal into a standard form
} else { var actionResult = actionResultService.getActionResult();
deleteModal.open({ $emit: actionComplete }, [items], context); deleteModalResult.pass.forEach(function markDeleted(item) {
} actionResult.deleted(resourceType, item.context.id);
} });
deleteModalResult.fail.forEach(function markFailed(item) {
function allowed(item) { actionResult.failed(resourceType, item.context.id);
var promises = [policy.ifAllowed({ rules: [['neutron', 'delete_listener']] }), statePromise]; });
if (item) {
promises.push(qExtensions.booleanAsPromise(!item.default_pool_id)); if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) {
} var path = 'project/load_balancer/' + loadbalancerId;
return $q.all(promises); $location.path(path);
}
function deleteItem(id) {
return api.deleteListener(id, true);
}
function actionComplete(eventType) {
if (eventType === context.failedEvent) {
// Action failed, reload the page
$route.reload();
} else {
// If the user is on the listeners table then just reload the page, otherwise they
// are on the details page and we return to the table.
var regex = new RegExp('project\/load_balancer\/' + loadbalancerId + '(\/)?$');
if (regex.test($location.path())) {
$route.reload();
} else {
$location.path('project/load_balancer/' + loadbalancerId);
}
} }
return actionResult.result;
} }
function checkPermission(item) { function checkPermission(item) {
@ -133,7 +124,7 @@
toast.add('error', getMessage(notAllowedMessage, result.fail)); toast.add('error', getMessage(notAllowedMessage, result.fail));
} }
if (result.pass.length > 0) { if (result.pass.length > 0) {
deleteModal.open({ $emit: actionComplete }, result.pass.map(getEntity), context); return deleteModal.open(scope, result.pass.map(getEntity), context).then(deleteResult);
} }
} }
@ -153,5 +144,12 @@
return result.context; return result.context;
} }
function allowed() {
return policy.ifAllowed({ rules: [['neutron', 'delete_listener']] });
}
function deleteItem(id) {
return api.deleteListener(id, true);
}
} }
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,17 +18,11 @@
'use strict'; 'use strict';
describe('LBaaS v2 Listeners Delete Service', function() { describe('LBaaS v2 Listeners Delete Service', function() {
var service, policy, modal, lbaasv2Api, $scope, $route, $location, $q, toast, items, path; var service, policy, modal, lbaasv2Api, $scope, $location, $q, toast, items, path;
function allowed(item) { function allowed(item) {
spyOn(policy, 'ifAllowed').and.returnValue(makePromise()); spyOn(policy, 'ifAllowed').and.returnValue(true);
var promise = service.allowed(item); var allowed = service.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
$scope.$apply(); $scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'delete_listener']]}); expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'delete_listener']]});
return allowed; return allowed;
@ -46,15 +41,19 @@
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(function() { beforeEach(function() {
items = [{ id: '1', name: 'First' }, items = [{ id: '1', name: 'First', loadbalancerId: 1 },
{ id: '2', name: 'Second' }]; { id: '2', name: 'Second', loadbalancerId: 1 }];
}); });
beforeEach(module(function($provide) { beforeEach(module(function($provide) {
$provide.value('$uibModal', { $provide.value('$uibModal', {
open: function() { open: function() {
return { return {
result: makePromise() result: {
then: function(func) {
return func({ data: { id: 'listener1' } });
}
}
}; };
} }
}); });
@ -75,12 +74,10 @@
lbaasv2Api = $injector.get('horizon.app.core.openstack-service-api.lbaasv2'); lbaasv2Api = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
modal = $injector.get('horizon.framework.widgets.modal.deleteModalService'); modal = $injector.get('horizon.framework.widgets.modal.deleteModalService');
$scope = $injector.get('$rootScope').$new(); $scope = $injector.get('$rootScope').$new();
$route = $injector.get('$route');
$location = $injector.get('$location'); $location = $injector.get('$location');
$q = $injector.get('$q'); $q = $injector.get('$q');
toast = $injector.get('horizon.framework.widgets.toast.service'); toast = $injector.get('horizon.framework.widgets.toast.service');
service = $injector.get('horizon.dashboard.project.lbaasv2.listeners.actions.delete'); service = $injector.get('horizon.dashboard.project.lbaasv2.listeners.actions.delete');
service.init('1', makePromise());
})); }));
it('should have the "allowed" and "perform" functions', function() { it('should have the "allowed" and "perform" functions', function() {
@ -96,23 +93,14 @@
expect(allowed()).toBe(true); expect(allowed()).toBe(true);
}); });
it('should not allow deleting a listener from load balancer in a PENDING state', function() {
service.init('1', makePromise(true));
expect(allowed()).toBe(false);
});
it('should not allow deleting a listener that has a default pool', function() {
expect(allowed({default_pool_id: 'pool1'})).toBe(false);
});
it('should open the delete modal', function() { it('should open the delete modal', function() {
spyOn(modal, 'open'); spyOn(modal, 'open').and.callThrough();
service.perform(items[0]); service.perform(items[0], $scope);
$scope.$apply(); $scope.$apply();
expect(modal.open.calls.count()).toBe(1); expect(modal.open.calls.count()).toBe(1);
var args = modal.open.calls.argsFor(0); var args = modal.open.calls.argsFor(0);
expect(args.length).toBe(3); expect(args.length).toBe(3);
expect(args[0]).toEqual({ $emit: jasmine.any(Function) }); expect(args[0]).toEqual($scope);
expect(args[1]).toEqual([jasmine.objectContaining({ id: '1' })]); expect(args[1]).toEqual([jasmine.objectContaining({ id: '1' })]);
expect(args[2]).toEqual(jasmine.objectContaining({ expect(args[2]).toEqual(jasmine.objectContaining({
labels: jasmine.any(Object), labels: jasmine.any(Object),
@ -124,7 +112,7 @@
it('should pass function to modal that deletes listeners', function() { it('should pass function to modal that deletes listeners', function() {
spyOn(modal, 'open').and.callThrough(); spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deleteListener').and.callThrough(); spyOn(lbaasv2Api, 'deleteListener').and.callThrough();
service.perform(items[0]); service.perform(items[0], $scope);
$scope.$apply(); $scope.$apply();
expect(lbaasv2Api.deleteListener.calls.count()).toBe(1); expect(lbaasv2Api.deleteListener.calls.count()).toBe(1);
expect(lbaasv2Api.deleteListener).toHaveBeenCalledWith('1', true); expect(lbaasv2Api.deleteListener).toHaveBeenCalledWith('1', true);
@ -135,7 +123,7 @@
spyOn(lbaasv2Api, 'deleteListener').and.returnValue(makePromise(true)); spyOn(lbaasv2Api, 'deleteListener').and.returnValue(makePromise(true));
spyOn(toast, 'add'); spyOn(toast, 'add');
items.splice(1, 1); items.splice(1, 1);
service.perform(items); service.perform(items, $scope);
$scope.$apply(); $scope.$apply();
expect(modal.open).toHaveBeenCalled(); expect(modal.open).toHaveBeenCalled();
expect(lbaasv2Api.deleteListener.calls.count()).toBe(1); expect(lbaasv2Api.deleteListener.calls.count()).toBe(1);
@ -143,19 +131,11 @@
'be deleted, possibly due to existing pools: First.'); 'be deleted, possibly due to existing pools: First.');
}); });
it('should reload table after delete', function() {
path = 'project/load_balancer/1';
spyOn($route, 'reload');
service.perform(items);
$scope.$apply();
expect($route.reload).toHaveBeenCalled();
});
it('should return to table after delete if on detail page', function() { it('should return to table after delete if on detail page', function() {
path = 'project/load_balancer/1/listeners/2'; path = 'project/load_balancer/1/listeners/2';
spyOn($location, 'path'); spyOn($location, 'path');
spyOn(toast, 'add'); spyOn(toast, 'add');
service.perform(items[0]); service.perform(items[0], $scope);
$scope.$apply(); $scope.$apply();
expect($location.path).toHaveBeenCalledWith('project/load_balancer/1'); expect($location.path).toHaveBeenCalledWith('project/load_balancer/1');
expect(toast.add).toHaveBeenCalledWith('success', 'Deleted listeners: First.'); expect(toast.add).toHaveBeenCalledWith('success', 'Deleted listeners: First.');
@ -166,7 +146,7 @@
spyOn(toast, 'add'); spyOn(toast, 'add');
items[0].default_pool_id = 'pool1'; items[0].default_pool_id = 'pool1';
items[1].default_pool_id = 'pool2'; items[1].default_pool_id = 'pool2';
service.perform(items); service.perform(items, $scope);
$scope.$apply(); $scope.$apply();
expect(modal.open).not.toHaveBeenCalled(); expect(modal.open).not.toHaveBeenCalled();
expect(toast.add).toHaveBeenCalledWith('error', expect(toast.add).toHaveBeenCalledWith('error',

View File

@ -0,0 +1,69 @@
/*
* Copyright 2017 Walmart.
*
* 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.listeners')
.factory('horizon.dashboard.project.lbaasv2.listeners.actions.edit',
editService);
editService.$inject = [
'horizon.dashboard.project.lbaasv2.listeners.resourceType',
'horizon.framework.util.actions.action-result.service',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.listeners.actions.edit
*
* @description
* Provides the service for the Listener edit action.
*
* @param resourceType The listener resource type.
* @param actionResultService The horizon action result service.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
*
* @returns Listeners edit action service object.
*/
function editService(
resourceType, actionResultService, workflowModal, policy, gettext
) {
return workflowModal.init({
controller: 'EditListenerWizardController',
message: gettext('The listener has been updated.'),
handle: handle,
allowed: allowed
});
function allowed(/*item*/) {
return policy.ifAllowed({ rules: [['neutron', 'update_listener']] });
}
function handle(response) {
return actionResultService.getActionResult()
.updated(resourceType, response.config.data.listener.id)
.result;
}
}
})();

View File

@ -0,0 +1,55 @@
/*
* Copyright 2017 Walmart.
*
* 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';
describe('LBaaS v2 Edit Listener Action Service', function() {
var policy, service;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: {
then: function(func) {
func({ data: { id: 'listener1' } });
}
}
};
}
});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
service = $injector.get('horizon.dashboard.project.lbaasv2.listeners.actions.edit');
}));
it('should check policy to allow editing a listener', function() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
expect(service.allowed()).toBe(true);
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_listener']]});
});
it('should handle the action result properly', function() {
var result = service.handle({config: {data: {listener: {id: 1}}}});
expect(result.updated[0].id).toBe(1);
});
});
})();

View File

@ -1,113 +0,0 @@
/*
* 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.listeners')
.factory('horizon.dashboard.project.lbaasv2.listeners.actions.rowActions',
tableRowActions);
tableRowActions.$inject = [
'$q',
'$route',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.listeners.actions.delete',
'horizon.dashboard.project.lbaasv2.pools.actions.create'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.listeners.actions.rowActions
*
* @description
* Provides the service for the Listener table row actions.
*
* @param $q The angular service for promises.
* @param $route The angular $route service.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param deleteService The LBaaS v2 listeners delete service.
* @param createPoolService The LBaaS v2 pools create service.
* @returns Listeners row actions service object.
*/
function tableRowActions(
$q, $route, workflowModal, policy, gettext, loadBalancersService, deleteService,
createPoolService
) {
var loadbalancerId, loadBalancerIsActionable;
var edit = workflowModal.init({
controller: 'EditListenerWizardController',
message: gettext('The listener has been updated.'),
handle: onEdit,
allowed: canEdit
});
var service = {
actions: actions,
init: init
};
return service;
///////////////
function init(_loadbalancerId_) {
loadbalancerId = _loadbalancerId_;
loadBalancerIsActionable = loadBalancersService.isActionable(loadbalancerId);
return service;
}
function actions() {
return [{
service: edit,
template: {
text: gettext('Edit')
}
},{
service: createPoolService.init(loadbalancerId, loadBalancerIsActionable).create,
template: {
text: gettext('Create Pool')
}
},{
service: deleteService.init(loadbalancerId, loadBalancerIsActionable),
template: {
text: gettext('Delete Listener'),
type: 'delete'
}
}];
}
function canEdit(/*item*/) {
return $q.all([
loadBalancerIsActionable,
policy.ifAllowed({ rules: [['neutron', 'update_listener']] })
]);
}
function onEdit(/*response*/) {
$route.reload();
}
}
})();

View File

@ -1,115 +0,0 @@
/*
* 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';
describe('LBaaS v2 Listeners Table Row Actions Service', function() {
var scope, $route, $q, actions, policy, init;
function canEdit(item) {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var promise = actions[0].service.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_listener']]});
return allowed;
}
function isActionableMock(id) {
if (id === 'active') {
return $q.when();
} else {
return $q.reject();
}
}
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
var response = {
data: {
id: '1'
}
};
var modal = {
open: function() {
return {
result: {
then: function(func) {
func(response);
}
}
};
}
};
$provide.value('$uibModal', modal);
}));
beforeEach(inject(function ($injector) {
scope = $injector.get('$rootScope').$new();
$q = $injector.get('$q');
$route = $injector.get('$route');
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
var rowActionsService = $injector.get(
'horizon.dashboard.project.lbaasv2.listeners.actions.rowActions');
actions = rowActionsService.actions();
init = rowActionsService.init;
var loadbalancerService = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.service');
spyOn(loadbalancerService, 'isActionable').and.callFake(isActionableMock);
}));
it('should define correct table row actions', function() {
expect(actions.length).toBe(3);
expect(actions[0].template.text).toBe('Edit');
expect(actions[1].template.text).toBe('Create Pool');
expect(actions[2].template.text).toBe('Delete Listener');
});
it('should allow editing a listener of an ACTIVE load balancer', function() {
init('active');
expect(canEdit({listenerId: '1234'})).toBe(true);
});
it('should not allow editing a listener of a non-ACTIVE load balancer', function() {
init('non-active');
expect(canEdit({listenerId: '1234'})).toBe(false);
});
it('should have the "allowed" and "perform" functions', function() {
actions.forEach(function(action) {
expect(action.service.allowed).toBeDefined();
expect(action.service.perform).toBeDefined();
});
});
it('should reload table after edit', function() {
spyOn($route, 'reload').and.callThrough();
actions[0].service.perform();
expect($route.reload).toHaveBeenCalled();
});
});
})();

View File

@ -1,92 +0,0 @@
/*
* 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.listeners')
.controller('ListenerDetailController', ListenerDetailController);
ListenerDetailController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.listeners.actions.rowActions',
'$routeParams',
'$q'
];
/**
* @ngdoc controller
* @name ListenerDetailController
*
* @description
* Controller for the LBaaS v2 listener detail page.
*
* @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, $q) {
var ctrl = this;
ctrl.loading = true;
ctrl.error = false;
ctrl.actions = rowActions.init($routeParams.loadbalancerId).actions;
init();
////////////////////////////////
function init() {
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 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

@ -1,104 +0,0 @@
/*
* 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';
describe('LBaaS v2 Listener Detail Controller', function() {
var lbaasv2API, $controller, apiFail, qAllFail;
function fakePromise(data, reject) {
return {
then: function(success, fail) {
if (reject) {
fail();
} else {
success({ data: data });
}
return fakePromise();
}
};
}
function fakeAPI() {
return fakePromise('foo', apiFail);
}
function loadbalancerAPI() {
return fakePromise({ provisioning_status: 'ACTIVE' });
}
function qAll() {
return fakePromise(null, qAllFail);
}
function createController() {
return $controller('ListenerDetailController', {
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId'
}
});
}
///////////////////////
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.framework.conf'));
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 });
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getListener').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
$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,46 +0,0 @@
<div ng-controller="ListenerDetailController as ctrl">
<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
<div ng-if="!ctrl.loading && !ctrl.error">
<div class="page-header">
<actions allowed="ctrl.actions" type="row" item="ctrl.listener" ng-if="ctrl.listener"
class="actions_column pull-right"></actions>
<ol class="breadcrumb">
<li><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li class="active">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</li>
</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/load_balancer/{$ ::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>Project ID</dt>
<dd>{$ ::ctrl.listener.project_id $}</dd>
<dt translate>Created At</dt>
<dd>{$ ::ctrl.listener.created_at $}</dd>
<dt translate>Updated At</dt>
<dd>{$ ::ctrl.listener.updated_at $}</dd>
</dl>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,101 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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.listeners')
.controller('ListenerDetailController', ListenerDetailController);
ListenerDetailController.$inject = [
'loadbalancer',
'listener',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.listeners.resourceType',
'horizon.framework.conf.resource-type-registry.service',
'horizon.framework.widgets.modal-wait-spinner.service',
'$q'
];
/**
* @ngdoc controller
* @name ListenerDetailController
*
* @description
* Controller for the LBaaS v2 listener detail page.
*
* @param loadbalancer The loadbalancer object.
* @param listener The listener object.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param resourceType The listenr resource type.
* @param typeRegistry The horizon resource type registry service.
* @param spinnerService The horizon modal wait spinner service.
* @param $q The angular service for promises.
*
* @returns undefined
*/
function ListenerDetailController(
loadbalancer, listener, loadBalancersService, resourceType, typeRegistry,
spinnerService, $q
) {
var ctrl = this;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
ctrl.loadbalancer = loadbalancer;
ctrl.listener = listener;
ctrl.listFunctionExtraParams = {
loadbalancerId: ctrl.loadbalancer.id,
listenerId: ctrl.listener.id
};
ctrl.resourceType = typeRegistry.getResourceType(resourceType);
ctrl.context = {};
ctrl.context.identifier = listener.id;
ctrl.resultHandler = actionResultHandler;
function actionResultHandler(returnValue) {
return $q.when(returnValue, actionSuccessHandler);
}
function loadData(response) {
spinnerService.hideModalSpinner();
ctrl.showDetails = true;
ctrl.resourceType.initActions();
ctrl.listener = response.data;
ctrl.listener.loadbalancerId = ctrl.loadbalancer.id;
}
function actionSuccessHandler(result) {
// The action has completed (for whatever "complete" means to that
// action. Notice the view doesn't really need to know the semantics of the
// particular action because the actions return data in a standard form.
// That return includes the id and type of each created, updated, deleted
// and failed item.
// Currently just refreshes the display each time.
if (result) {
spinnerService.showModalSpinner(gettext('Please Wait'));
ctrl.showDetails = false;
ctrl.context.loadPromise = ctrl.resourceType.load(ctrl.context.identifier);
ctrl.context.loadPromise.then(loadData);
}
}
}
})();

View File

@ -0,0 +1,101 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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';
describe('LBaaS v2 Listener Detail Controller', function() {
var deferred, service, ctrl, scope, $timeout, $q, actionResultService;
///////////////////////
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($controller, $rootScope, _$q_, _$timeout_) {
$q = _$q_;
deferred = $q.defer();
service = {
getResourceType: function() {
return {
load: function() { return deferred.promise; },
parsePath: function() { return 'my-context'; },
itemName: function() { return 'A name'; },
initActions: angular.noop
};
},
getDefaultDetailsTemplateUrl: angular.noop
};
actionResultService = {
getIdsOfType: function() { return []; }
};
$timeout = _$timeout_;
scope = $rootScope.$new();
ctrl = $controller('ListenerDetailController', {
$scope: scope,
loadbalancer: { id: '123' },
listener: { id: '123' },
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
}
});
}));
it('should create a controller', function() {
expect(ctrl).toBeDefined();
expect(ctrl.loadbalancer).toBeDefined();
expect(ctrl.listener).toBeDefined();
});
describe('resultHandler', function() {
it('handles empty results', function() {
var result = $q.defer();
result.resolve({});
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles falsy results', function() {
var result = $q.defer();
result.resolve(false);
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles matched results', function() {
spyOn(actionResultService, 'getIdsOfType').and.returnValue([1, 2, 3]);
var result = $q.defer();
result.resolve({some: 'thing'});
ctrl.resultHandler(result.promise);
deferred.resolve({data: {some: 'data'}});
$timeout.flush();
expect(ctrl.showDetails).toBe(true);
});
});
});
})();

View File

@ -0,0 +1,62 @@
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li class="active">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</li>
</ol>
<div class="row">
<div class="col-xs-12 col-sm-9 text-left">
<ul class="list-inline">
<li>
<strong translate>Protocol</strong>
{$ ::ctrl.listener.protocol $}
</li>
<li>
<strong translate>Port</strong>
{$ ::ctrl.listener.protocol_port $}
</li>
<li>
<strong translate>Operating Status</strong>
{$ ctrl.listener.operating_status | decode:ctrl.operatingStatus $}
</li>
<li>
<strong translate>Provisioning Status</strong>
{$ ctrl.listener.provisioning_status | decode:ctrl.provisioningStatus $}
</li>
<li>
<strong translate>Admin State Up</strong>
{$ ctrl.listener.admin_state_up | yesno $}
</li>
</ul>
</div>
<div class="col-xs-12 col-sm-3 text-right details-item-actions">
<actions allowed="ctrl.resourceType.itemActions"
type="row"
item="ctrl.listener"
ng-if="ctrl.listener"
class="actions_column pull-right"
result-handler="ctrl.resultHandler"></actions>
</div>
</div>
</div>
<uib-tabset class="octavia-tabset">
<uib-tab heading="{$ 'Overview' | translate $}">
<div class="col-md-6 detail">
<hz-resource-property-list
resource-type-name="OS::Octavia::Listener"
cls="dl-horizontal"
item="ctrl.listener"
property-groups="[[
'id', 'name', 'description', 'project_id', 'created_at', 'updated_at',
'connection_limit', 'default_pool_id']]">
</hz-resource-property-list>
</div>
</uib-tab>
<uib-tab heading="{$ 'Pools' | translate $}">
<hz-resource-table resource-type-name="OS::Octavia::Pool"
track-by="trackBy"
list-function-extra-params="ctrl.listFunctionExtraParams">
</hz-resource-table>
</uib-tab>
</uib-tabset>

View File

@ -0,0 +1,9 @@
<hz-resource-property-list
resource-type-name="OS::Octavia::Listener"
item="item"
property-groups="[
['name', 'id', 'project_id'],
['created_at', 'updated_at', 'description'],
['protocol', 'protocol_port', 'connection_limit'],
['default_pool_id']]">
</hz-resource-property-list>

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,6 +27,152 @@
*/ */
angular angular
.module('horizon.dashboard.project.lbaasv2.listeners', []); .module('horizon.dashboard.project.lbaasv2.listeners', [])
.constant('horizon.dashboard.project.lbaasv2.listeners.resourceType',
'OS::Octavia::Listener')
.run(run);
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.listeners.actions.create',
'horizon.dashboard.project.lbaasv2.listeners.actions.edit',
'horizon.dashboard.project.lbaasv2.listeners.actions.delete',
'horizon.dashboard.project.lbaasv2.listeners.resourceType'
];
function run(
registry,
basePath,
loadBalancerService,
createService,
editService,
deleteService,
resourceType
) {
var listenerResourceType = registry.getResourceType(resourceType);
listenerResourceType
.setNames(gettext('Listener'), gettext('Listeners'))
.setSummaryTemplateUrl(basePath + 'listeners/details/drawer.html')
.setProperties(listenerProperties(loadBalancerService))
.setListFunction(loadBalancerService.getListenersPromise)
.setLoadFunction(loadBalancerService.getListenerPromise)
.tableColumns
.append({
id: 'name',
priority: 1,
sortDefault: true,
urlFunction: loadBalancerService.getListenerDetailsPath
})
.append({
id: 'protocol',
priority: 1
})
.append({
id: 'protocol_port',
priority: 1
})
.append({
id: 'operating_status',
priority: 1
})
.append({
id: 'provisioning_status',
priority: 1
})
.append({
id: 'admin_state_up',
priority: 1
});
listenerResourceType.itemActions
.append({
id: 'listenerEdit',
service: editService,
template: {
text: gettext('Edit Listener')
}
})
.append({
id: 'listenerDelete',
service: deleteService,
template: {
text: gettext('Delete Listener'),
type: 'delete'
}
});
listenerResourceType.globalActions
.append({
id: 'listenerCreate',
service: createService,
template: {
type: 'create',
text: gettext('Create Listener')
}
});
listenerResourceType.batchActions
.append({
id: 'listenerBatchDelete',
service: deleteService,
template: {
text: gettext('Delete Listeners'),
type: 'delete-selected'
}
});
}
function listenerProperties(loadBalancerService) {
return {
id: gettext('ID'),
name: {
label: gettext('Name'),
filters: ['noName']
},
description: {
label: gettext('Description'),
filters: ['noValue']
},
provisioning_status: {
label: gettext('Provisioning Status'),
values: loadBalancerService.provisioningStatus
},
operating_status: {
label: gettext('Operating Status'),
values: loadBalancerService.operatingStatus
},
admin_state_up: {
label: gettext('Admin State Up'),
filters: ['yesno']
},
protocol: gettext('Protocol'),
protocol_port: gettext('Port'),
project_id: gettext('Project ID'),
created_at: {
label: gettext('Created At'),
filters: ['noValue']
},
updated_at: {
label: gettext('Updated At'),
filters: ['noValue']
},
connection_limit: {
label: gettext('Connection Limit'),
filters: ['limit']
},
default_tls_container_ref: gettext('Default TLS Container Ref'),
sni_container_refs: gettext('SNI Container Refs'),
default_pool_id: {
label: gettext('Default Pool ID'),
filters: ['noName']
},
l7_policies: gettext('L7 Policies'),
insert_headers: gettext('Insert Headers'),
load_balancers: gettext('Load Balancers')
};
}
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,4 +23,46 @@
}); });
}); });
describe('LBaaS v2 Listeners Registry', function () {
var registry, resourceType;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function($injector) {
resourceType = $injector.get('horizon.dashboard.project.lbaasv2.listeners.resourceType');
registry = $injector.get('horizon.framework.conf.resource-type-registry.service');
}));
it('should define resourceType', function () {
expect(resourceType).toBeDefined();
});
it('should register item actions', function () {
var actions = registry.getResourceType(resourceType).itemActions;
expect(actionHasId(actions, 'listenerEdit')).toBe(true);
expect(actionHasId(actions, 'listenerDelete')).toBe(true);
});
it('should register global actions', function () {
var actions = registry.getResourceType(resourceType).globalActions;
expect(actionHasId(actions, 'listenerCreate')).toBe(true);
});
it('should register batch actions', function () {
var actions = registry.getResourceType(resourceType).batchActions;
expect(actionHasId(actions, 'listenerBatchDelete')).toBe(true);
});
function actionHasId(list, value) {
return list.filter(matchesId).length === 1;
function matchesId(action) {
if (action.id === value) {
return true;
}
}
}
});
})(); })();

View File

@ -1,80 +0,0 @@
/*
* 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.listeners')
.controller('ListenersTableController', ListenersTableController);
ListenersTableController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'$routeParams',
'horizon.dashboard.project.lbaasv2.listeners.actions.rowActions',
'horizon.dashboard.project.lbaasv2.listeners.actions.batchActions'
];
/**
* @ngdoc controller
* @name ListenersTableController
*
* @description
* Controller for the LBaaS v2 listeners table. Serves as the focal point for table actions.
*
* @param api The LBaaS V2 service API.
* @param $routeParams The angular $routeParams service.
* @param rowActions The listener row actions service.
* @param batchActions The listener batch actions service.
* @returns undefined
*/
function ListenersTableController(api, $routeParams, rowActions, batchActions) {
var ctrl = this;
ctrl.items = [];
ctrl.src = [];
ctrl.loading = true;
ctrl.error = false;
ctrl.checked = {};
ctrl.loadbalancerId = $routeParams.loadbalancerId;
ctrl.batchActions = batchActions.init(ctrl.loadbalancerId);
ctrl.rowActions = rowActions.init(ctrl.loadbalancerId);
init();
////////////////////////////////
function init() {
ctrl.src = [];
ctrl.loading = true;
ctrl.error = false;
api.getListeners(ctrl.loadbalancerId).then(success, fail);
}
function success(response) {
ctrl.src = response.data.items;
ctrl.loading = false;
}
function fail(/*response*/) {
ctrl.src = [];
ctrl.loading = false;
ctrl.error = true;
}
}
})();

View File

@ -1,97 +0,0 @@
/*
* 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';
describe('LBaaS v2 Listeners Table Controller', function() {
var controller, lbaasv2API, rowActions, batchActions;
var items = [{ foo: 'bar' }];
var apiFail = false;
function fakeAPI() {
return {
then: function(success, fail) {
if (apiFail && fail) {
fail();
} else {
success({ data: { items: items } });
}
}
};
}
function initMock() {
return rowActions;
}
///////////////////////
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
controller = $injector.get('$controller');
rowActions = $injector.get('horizon.dashboard.project.lbaasv2.listeners.actions.rowActions');
batchActions = $injector.get(
'horizon.dashboard.project.lbaasv2.listeners.actions.batchActions');
spyOn(rowActions, 'init').and.callFake(initMock);
spyOn(lbaasv2API, 'getListeners').and.callFake(fakeAPI);
}));
function createController() {
return controller('ListenersTableController', {
$routeParams: { loadbalancerId: '1234' }
});
}
it('should initialize correctly', function() {
var ctrl = createController();
expect(ctrl.items).toEqual([]);
expect(ctrl.src).toEqual(items);
expect(ctrl.loading).toBe(false);
expect(ctrl.error).toBe(false);
expect(ctrl.checked).toEqual({});
expect(ctrl.loadbalancerId).toEqual('1234');
expect(rowActions.init).toHaveBeenCalledWith(ctrl.loadbalancerId);
expect(ctrl.rowActions).toBeDefined();
expect(ctrl.rowActions).toEqual(rowActions);
expect(ctrl.batchActions).toBeDefined();
expect(ctrl.batchActions).toEqual(batchActions);
});
it('should invoke lbaasv2 apis', function() {
var ctrl = createController();
expect(lbaasv2API.getListeners).toHaveBeenCalled();
expect(ctrl.src.length).toBe(1);
});
it('should show error if loading fails', function() {
apiFail = true;
var ctrl = createController();
expect(ctrl.src.length).toBe(0);
expect(ctrl.error).toBe(true);
});
});
})();

View File

@ -1,124 +0,0 @@
<table ng-controller="ListenersTableController as table"
hz-table ng-cloak
st-table="table.items"
st-safe-src="table.src"
default-sort="name"
default-sort-reverse="false"
class="table table-striped table-rsp table-detail">
<!--
TODO(jpomero): This table pattern does not allow for extensibility and should be revisited
once horizon implements a better one.
-->
<thead>
<tr>
<!--
Table-batch-actions:
This is where batch actions like searching, creating, and deleting.
-->
<th colspan="7" class="search-header">
<hz-search-bar icon-classes="fa-search">
<actions allowed="table.batchActions.actions" type="batch"></actions>
</hz-search-bar>
</th>
</tr>
<tr>
<!--
Table-column-headers:
This is where we declaratively define the table column headers.
Include select-col if you want to select all.
Include expander if you want to inline details.
Include action-col if you want to perform actions.
-->
<th class="multi_select_column">
<input type="checkbox" hz-select-all="table.items">
</th>
<th class="expander"></th>
<th class="rsp-p1" st-sort="name" st-sort-default="name" translate>Name</th>
<th class="rsp-p1" st-sort="description" translate>Description</th>
<th class="rsp-p1" st-sort="protocol" translate>Protocol</th>
<th class="rsp-p1" st-sort="port" translate>Port</th>
<th class="actions_column" translate>Actions</th>
</tr>
</thead>
<tbody>
<!--
Table-rows:
This is where we declaratively define the table columns.
Include select-col if you want to select all.
Include expander if you want to inline details.
Include action-col if you want to perform actions.
rsp-p1 rsp-p2 are responsive priority as user resizes window.
-->
<tr ng-repeat-start="item in table.items track by item.id"
ng-class="{'st-selected': checked[item.id]}">
<td class="multi_select_column">
<input type="checkbox"
ng-model="tCtrl.selections[item.id].checked"
hz-select="item">
</td>
<td class="expander">
<span class="fa fa-chevron-right"
hz-expand-detail
duration="200">
</span>
</td>
<td class="rsp-p1"><a ng-href="project/load_balancer/{$ ::table.loadbalancerId $}/listeners/{$ ::item.id $}">{$ ::(item.name || item.id) $}</a></td>
<td class="rsp-p1">{$ ::item.description | noValue $}</td>
<td class="rsp-p1">{$ ::item.protocol$}</td>
<td class="rsp-p1">{$ ::item.protocol_port$}</td>
<td class="actions_column">
<!--
Table-row-action-column:
Actions taken here apply to a single item/row.
-->
<actions allowed="table.rowActions.actions" type="row" item="item"></actions>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<!--
Detail-row:
Contains detailed information on this item.
Can be toggled using the chevron button.
Ensure colspan is greater or equal to number of column-headers.
-->
<td class="detail" colspan="7">
<div class="row">
<dl class="col-sm-2">
<dt translate>ID</dt>
<dd>{$ ::item.id $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Admin State Up</dt>
<dd>{$ ::item.admin_state_up | yesno $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Connection Limit</dt>
<dd>{$ ::item.connection_limit | limit $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Default Pool ID</dt>
<dd>{$ ::item.default_pool_id | noValue $}</dd>
</dl>
</div>
</td>
</tr>
<tr table-status table="table" column-count="7"></tr>
</tbody>
<!--
Table-footer:
This is where we display number of items and pagination controls.
-->
<tfoot hz-table-footer items="table.items"></tfoot>
</table>

View File

@ -20,7 +20,6 @@
var ctrl, network, floatingIps, floatingIpPools, $controller, $uibModalInstance; var ctrl, network, floatingIps, floatingIpPools, $controller, $uibModalInstance;
var associateFail = false; var associateFail = false;
beforeEach(module('horizon.framework.util.i18n'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(function() { beforeEach(function() {

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,7 +19,7 @@
angular angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers') .module('horizon.dashboard.project.lbaasv2.loadbalancers')
.factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.associate-ip.modal.service', .factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.associate-ip',
modalService); modalService);
modalService.$inject = [ modalService.$inject = [
@ -35,7 +36,7 @@
/** /**
* @ngdoc service * @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.loadbalancers.actions.associate-ip.modal.service * @ngname horizon.dashboard.project.lbaasv2.loadbalancers.actions.associate-ip
* *
* @description * @description
* Provides the service for the Load Balancer Associate Floating IP action. * Provides the service for the Load Balancer Associate Floating IP action.

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,10 +35,6 @@
return allowed; return allowed;
} }
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(function() { beforeEach(function() {
@ -76,7 +73,7 @@
$route = $injector.get('$route'); $route = $injector.get('$route');
$uibModal = $injector.get('$uibModal'); $uibModal = $injector.get('$uibModal');
service = $injector.get( service = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.associate-ip.modal.service'); 'horizon.dashboard.project.lbaasv2.loadbalancers.actions.associate-ip');
})); }));
it('should have the "allowed" and "perform" functions', function() { it('should have the "allowed" and "perform" functions', function() {

View File

@ -1,83 +0,0 @@
/*
* Copyright 2015 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';
describe('LBaaS v2 Load Balancers Table Batch Actions Service', function() {
var $location, actions, policy;
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
var response = {
data: {
id: '1'
}
};
var modal = {
open: function() {
return {
result: {
then: function(func) {
func(response);
}
}
};
}
};
$provide.value('$uibModal', modal);
}));
beforeEach(inject(function ($injector) {
$location = $injector.get('$location');
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
var batchActionsService = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.batchActions');
actions = batchActionsService.actions();
}));
it('should define correct table batch actions', function() {
expect(actions.length).toBe(2);
expect(actions[0].template.text).toBe('Create Load Balancer');
expect(actions[1].template.text).toBe('Delete Load Balancers');
});
it('should have the "allowed" and "perform" functions', function() {
actions.forEach(function(action) {
expect(action.service.allowed).toBeDefined();
expect(action.service.perform).toBeDefined();
});
});
it('should check policy to allow creating a load balancer', function() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var allowed = actions[0].service.allowed();
expect(allowed).toBe(true);
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'create_loadbalancer']]});
});
it('should redirect after create', function() {
spyOn($location, 'path').and.callThrough();
actions[0].service.perform();
expect($location.path).toHaveBeenCalledWith('project/load_balancer/1');
});
});
})();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2015 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,76 +19,55 @@
angular angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers') .module('horizon.dashboard.project.lbaasv2.loadbalancers')
.factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.batchActions', .factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.create', createService);
tableBatchActions);
tableBatchActions.$inject = [ createService.$inject = [
'$location', 'horizon.dashboard.project.lbaasv2.loadbalancers.resourceType',
'horizon.framework.util.actions.action-result.service',
'horizon.dashboard.project.lbaasv2.workflow.modal', 'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.delete',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext' 'horizon.framework.util.i18n.gettext'
]; ];
/** /**
* @ngdoc service * @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.loadbalancers.actions.batchActions * @ngname horizon.dashboard.project.lbaasv2.loadbalancers.actions.create
* *
* @description * @description
* Provides the service for the Load Balancers table batch actions. * Provides the service for the create load balancer action.
* *
* @param $location The angular $location service. * @param resourceType The loadbalancer resource type.
* @param actionResultService The horizon action result service.
* @param workflowModal The LBaaS workflow modal service. * @param workflowModal The LBaaS workflow modal service.
* @param basePath The lbaasv2 module base path.
* @param deleteService The load balancer delete service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
* @returns Load balancers table batch actions service object. *
* @returns Create load balancer action service.
*/ */
function tableBatchActions($location, workflowModal, basePath, deleteService, policy, gettext) { function createService(
resourceType, actionResultService, workflowModal, policy, gettext
) {
var create = workflowModal.init({ return workflowModal.init({
controller: 'CreateLoadBalancerWizardController', controller: 'CreateLoadBalancerWizardController',
message: gettext('A new load balancer is being created.'), message: gettext('A new load balancer is being created.'),
handle: onCreate, handle: handle,
allowed: canCreate allowed: allowed
}); });
var service = { function allowed() {
actions: actions
};
return service;
///////////////
function actions() {
return [{
service: create,
template: {
type: 'create',
text: gettext('Create Load Balancer')
}
}, {
service: deleteService,
template: {
type: 'delete-selected',
text: gettext('Delete Load Balancers')
}
}];
}
function canCreate() {
// This rule is made up and should therefore always pass. I assume at some point there // This rule is made up and should therefore always pass. I assume at some point there
// will be a valid rule similar to this that we will want to use. // will be a valid rule similar to this that we will want to use.
return policy.ifAllowed({ rules: [['neutron', 'create_loadbalancer']] }); return policy.ifAllowed({ rules: [['neutron', 'create_loadbalancer']] });
} }
function onCreate(response) { function handle(response) {
$location.path('project/load_balancer/' + response.data.id); return actionResultService.getActionResult()
.created(resourceType, response.data.id)
.result;
} }
} }
})(); })();

View File

@ -0,0 +1,56 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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';
describe('LBaaS v2 Create Load Balancer Action Service', function() {
var policy, service;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: {
then: function(func) {
func({ data: { id: 'loadbalancer1' } });
}
}
};
}
});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
service = $injector.get('horizon.dashboard.project.lbaasv2.loadbalancers.actions.create');
}));
it('should check policy to allow creating a load balancer', function() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
expect(service.allowed()).toBe(true);
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'create_loadbalancer']]});
});
it('should handle the action result properly', function() {
var result = service.handle({data: {id: 1}});
expect(result.created[0].id).toBe(1);
});
});
})();

View File

@ -29,7 +29,6 @@
}; };
var scope = {}; var scope = {};
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function ($provide) { beforeEach(module(function ($provide) {
$provide.value('horizon.dashboard.project.lbaasv2.workflow.model', model); $provide.value('horizon.dashboard.project.lbaasv2.workflow.model', model);

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,9 +22,9 @@
.factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.delete', deleteService); .factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.delete', deleteService);
deleteService.$inject = [ deleteService.$inject = [
'$q', 'horizon.dashboard.project.lbaasv2.loadbalancers.resourceType',
'horizon.framework.util.actions.action-result.service',
'$location', '$location',
'$route',
'horizon.framework.widgets.modal.deleteModalService', 'horizon.framework.widgets.modal.deleteModalService',
'horizon.app.core.openstack-service-api.lbaasv2', 'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
@ -36,24 +37,31 @@
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.loadbalancers.actions.deleteService * @name horizon.dashboard.project.lbaasv2.loadbalancers.actions.deleteService
* @description * @description
*
* Brings up the delete load balancers confirmation modal dialog. * Brings up the delete load balancers confirmation modal dialog.
* On submit, deletes selected load balancers. * On submit, deletes selected load balancers.
* On cancel, does nothing. * On cancel, does nothing.
* @param $q The angular service for promises. *
* @param resourceType The loadbalancer resource type.
* @param actionResultService The horizon action result service.
* @param $location The angular $location service. * @param $location The angular $location service.
* @param $route The angular $route service.
* @param deleteModal The horizon delete modal service. * @param deleteModal The horizon delete modal service.
* @param api The LBaaS v2 API service. * @param api The LBaaS v2 API service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param toast The horizon message service. * @param toast The horizon message service.
* @param qExtensions Horizon extensions to the $q service. * @param qExtensions Horizon extensions to the $q service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
* @returns The load balancers table delete service. *
* @returns The load balancers delete service.
*/ */
function deleteService( function deleteService(
$q, $location, $route, deleteModal, api, policy, toast, qExtensions, gettext resourceType, actionResultService, $location, deleteModal, api,
policy, toast, qExtensions, gettext
) { ) {
var scope;
// If a batch delete, then this message is displayed for any selected load balancers not in // If a batch delete, then this message is displayed for any selected load balancers not in
// ACTIVE or ERROR state. // ACTIVE or ERROR state.
var notAllowedMessage = gettext('The following load balancers are pending and cannot be ' + var notAllowedMessage = gettext('The following load balancers are pending and cannot be ' +
@ -82,23 +90,34 @@
////////////// //////////////
function perform(items) { function perform(items, _scope_) {
if (angular.isArray(items)) { scope = _scope_;
qExtensions.allSettled(items.map(checkPermission)).then(afterCheck); var loadbalancers = angular.isArray(items) ? items : [items];
} else { return qExtensions.allSettled(loadbalancers.map(checkPermission)).then(afterCheck);
deleteModal.open({ $emit: actionComplete }, [items], context);
}
} }
function allowed(item) { function deleteResult(deleteModalResult) {
// To make the result of this action generically useful, reformat the return
// from the deleteModal into a standard form
var actionResult = actionResultService.getActionResult();
deleteModalResult.pass.forEach(function markDeleted(item) {
actionResult.deleted(resourceType, item.context.id);
});
deleteModalResult.fail.forEach(function markFailed(item) {
actionResult.failed(resourceType, item.context.id);
});
if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) {
var path = 'project/load_balancer';
$location.path(path);
}
return actionResult.result;
}
function allowed() {
// This rule is made up and should therefore always pass. I assume at some point there // This rule is made up and should therefore always pass. I assume at some point there
// will be a valid rule similar to this that we will want to use. // will be a valid rule similar to this that we will want to use.
var promises = [policy.ifAllowed({ rules: [['neutron', 'delete_loadbalancer']] })]; return policy.ifAllowed({ rules: [['neutron', 'delete_loadbalancer']] });
if (item) {
var status = item.provisioning_status;
promises.push(qExtensions.booleanAsPromise(status === 'ACTIVE' || status === 'ERROR'));
}
return $q.all(promises);
} }
function canBeDeleted(item) { function canBeDeleted(item) {
@ -115,7 +134,7 @@
toast.add('error', getMessage(notAllowedMessage, result.fail)); toast.add('error', getMessage(notAllowedMessage, result.fail));
} }
if (result.pass.length > 0) { if (result.pass.length > 0) {
deleteModal.open({ $emit: actionComplete }, result.pass.map(getEntity), context); return deleteModal.open(scope, result.pass.map(getEntity), context).then(deleteResult);
} }
} }
@ -135,20 +154,5 @@
return result.context; return result.context;
} }
function actionComplete(eventType) {
if (eventType === context.failedEvent) {
// Action failed, reload the page
$route.reload();
} else {
// If the user is on the load balancers table then just reload the page, otherwise they
// are on the details page and we return to the table.
if (/\/load_balancer(\/)?$/.test($location.path())) {
$route.reload();
} else {
$location.path('project/load_balancer');
}
}
}
} }
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,17 +18,11 @@
'use strict'; 'use strict';
describe('LBaaS v2 Load Balancers Table Row Delete Service', function() { describe('LBaaS v2 Load Balancers Table Row Delete Service', function() {
var service, policy, modal, lbaasv2Api, $scope, $route, $location, $q, toast, items, path; var service, policy, modal, lbaasv2Api, $scope, $location, $q, toast, items, path;
function allowed(item) { function allowed(item) {
spyOn(policy, 'ifAllowed').and.returnValue(true); spyOn(policy, 'ifAllowed').and.returnValue(true);
var promise = service.allowed(item); var allowed = service.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
$scope.$apply(); $scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'delete_loadbalancer']]}); expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'delete_loadbalancer']]});
return allowed; return allowed;
@ -39,10 +34,6 @@
return def.promise; return def.promise;
} }
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(function() { beforeEach(function() {
@ -75,7 +66,6 @@
lbaasv2Api = $injector.get('horizon.app.core.openstack-service-api.lbaasv2'); lbaasv2Api = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
modal = $injector.get('horizon.framework.widgets.modal.deleteModalService'); modal = $injector.get('horizon.framework.widgets.modal.deleteModalService');
$scope = $injector.get('$rootScope').$new(); $scope = $injector.get('$rootScope').$new();
$route = $injector.get('$route');
$location = $injector.get('$location'); $location = $injector.get('$location');
$q = $injector.get('$q'); $q = $injector.get('$q');
toast = $injector.get('horizon.framework.widgets.toast.service'); toast = $injector.get('horizon.framework.widgets.toast.service');
@ -95,24 +85,14 @@
expect(allowed()).toBe(true); expect(allowed()).toBe(true);
}); });
it('should not allow deleting load balancers if state check fails (single)', function() {
items[0].provisioning_status = 'PENDING_UPDATE';
expect(allowed(items[0])).toBe(false);
});
it('should allow batch delete even if state check fails (batch)', function() {
items[0].provisioning_status = 'PENDING_UPDATE';
expect(allowed()).toBe(true);
});
it('should open the delete modal', function() { it('should open the delete modal', function() {
spyOn(modal, 'open'); spyOn(modal, 'open').and.callThrough();
service.perform(items[0]); service.perform(items[0], $scope);
$scope.$apply(); $scope.$apply();
expect(modal.open.calls.count()).toBe(1); expect(modal.open.calls.count()).toBe(1);
var args = modal.open.calls.argsFor(0); var args = modal.open.calls.argsFor(0);
expect(args.length).toBe(3); expect(args.length).toBe(3);
expect(args[0]).toEqual({ $emit: jasmine.any(Function) }); expect(args[0]).toEqual($scope);
expect(args[1]).toEqual([jasmine.objectContaining({ id: '1' })]); expect(args[1]).toEqual([jasmine.objectContaining({ id: '1' })]);
expect(args[2]).toEqual(jasmine.objectContaining({ expect(args[2]).toEqual(jasmine.objectContaining({
labels: jasmine.any(Object), labels: jasmine.any(Object),
@ -124,7 +104,7 @@
it('should pass function to modal that deletes load balancers', function() { it('should pass function to modal that deletes load balancers', function() {
spyOn(modal, 'open').and.callThrough(); spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deleteLoadBalancer').and.callThrough(); spyOn(lbaasv2Api, 'deleteLoadBalancer').and.callThrough();
service.perform(items[0]); service.perform(items[0], $scope);
$scope.$apply(); $scope.$apply();
expect(lbaasv2Api.deleteLoadBalancer.calls.count()).toBe(1); expect(lbaasv2Api.deleteLoadBalancer.calls.count()).toBe(1);
expect(lbaasv2Api.deleteLoadBalancer).toHaveBeenCalledWith('1', true); expect(lbaasv2Api.deleteLoadBalancer).toHaveBeenCalledWith('1', true);
@ -135,7 +115,7 @@
spyOn(toast, 'add'); spyOn(toast, 'add');
items[0].provisioning_status = 'PENDING_UPDATE'; items[0].provisioning_status = 'PENDING_UPDATE';
items[1].provisioning_status = 'PENDING_DELETE'; items[1].provisioning_status = 'PENDING_DELETE';
service.perform(items); service.perform(items, $scope);
$scope.$apply(); $scope.$apply();
expect(modal.open).not.toHaveBeenCalled(); expect(modal.open).not.toHaveBeenCalled();
expect(toast.add).toHaveBeenCalledWith('error', expect(toast.add).toHaveBeenCalledWith('error',
@ -147,7 +127,7 @@
spyOn(lbaasv2Api, 'deleteLoadBalancer').and.returnValue(makePromise(true)); spyOn(lbaasv2Api, 'deleteLoadBalancer').and.returnValue(makePromise(true));
spyOn(toast, 'add'); spyOn(toast, 'add');
items.splice(1, 1); items.splice(1, 1);
service.perform(items); service.perform(items, $scope);
$scope.$apply(); $scope.$apply();
expect(modal.open).toHaveBeenCalled(); expect(modal.open).toHaveBeenCalled();
expect(lbaasv2Api.deleteLoadBalancer.calls.count()).toBe(1); expect(lbaasv2Api.deleteLoadBalancer.calls.count()).toBe(1);
@ -155,19 +135,11 @@
'be deleted, possibly due to existing listeners: First.'); 'be deleted, possibly due to existing listeners: First.');
}); });
it('should reload table after delete', function() { it('should return to panel after delete if on detail page', function() {
path = 'project/load_balancer';
spyOn($route, 'reload');
service.perform(items);
$scope.$apply();
expect($route.reload).toHaveBeenCalled();
});
it('should return to table after delete if on detail page', function() {
path = 'project/load_balancer/1'; path = 'project/load_balancer/1';
spyOn($location, 'path'); spyOn($location, 'path');
spyOn(toast, 'add'); spyOn(toast, 'add');
service.perform(items[0]); service.perform(items[0], $scope);
$scope.$apply(); $scope.$apply();
expect($location.path).toHaveBeenCalledWith('project/load_balancer'); expect($location.path).toHaveBeenCalledWith('project/load_balancer');
expect(toast.add).toHaveBeenCalledWith('success', 'Deleted load balancers: First.'); expect(toast.add).toHaveBeenCalledWith('success', 'Deleted load balancers: First.');

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,7 +20,7 @@
angular angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers') .module('horizon.dashboard.project.lbaasv2.loadbalancers')
.factory( .factory(
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.disassociate-ip.modal.service', 'horizon.dashboard.project.lbaasv2.loadbalancers.actions.disassociate-ip',
modalService); modalService);
modalService.$inject = [ modalService.$inject = [
@ -34,11 +35,13 @@
/** /**
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.loadbalancers.actions.disassociate-ip.modal.service * @name horizon.dashboard.project.lbaasv2.loadbalancers.actions.disassociate-ip
*
* @description * @description
* Brings up the disassociate floating IP confirmation modal dialog. * Brings up the disassociate floating IP confirmation modal dialog.
* On submit, dsiassociates the floating IP address from the load balancer. * On submit, dsiassociates the floating IP address from the load balancer.
* On cancel, does nothing. * On cancel, does nothing.
*
* @param $q The angular service for promises. * @param $q The angular service for promises.
* @param $route The angular $route service. * @param $route The angular $route service.
* @param deleteModal The horizon delete modal service. * @param deleteModal The horizon delete modal service.
@ -46,7 +49,8 @@
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param qExtensions Horizon extensions to the $q service. * @param qExtensions Horizon extensions to the $q service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
* @returns The load balancers table row delete service. *
* @returns The Disassociate Floating IP modal service.
*/ */
function modalService($q, $route, deleteModal, network, policy, qExtensions, gettext) { function modalService($q, $route, deleteModal, network, policy, qExtensions, gettext) {

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,10 +35,6 @@
return allowed; return allowed;
} }
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(function() { beforeEach(function() {
@ -71,7 +68,7 @@
$scope = $injector.get('$rootScope').$new(); $scope = $injector.get('$rootScope').$new();
$route = $injector.get('$route'); $route = $injector.get('$route');
service = $injector.get( service = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.disassociate-ip.modal.service'); 'horizon.dashboard.project.lbaasv2.loadbalancers.actions.disassociate-ip');
})); }));
it('should have the "allowed" and "perform" functions', function() { it('should have the "allowed" and "perform" functions', function() {

View File

@ -0,0 +1,77 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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.loadbalancers')
.factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.edit', editService);
editService.$inject = [
'horizon.dashboard.project.lbaasv2.loadbalancers.resourceType',
'horizon.framework.util.actions.action-result.service',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.q.extensions',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.loadbalancers.actions.edit
*
* @description
* Provides the service for the edit load balancer action.
*
* @param resourceType The loadbalancer resource type.
* @param actionResultService The horizon action result service.
* @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service.
* @param qExtensions Horizon extensions to the $q service.
* @param gettext The horizon gettext function for translation.
*
* @returns Edit load balancer action service.
*/
function editService(
resourceType, actionResultService, workflowModal, policy,
qExtensions, gettext
) {
return workflowModal.init({
controller: 'EditLoadBalancerWizardController',
message: gettext('The load balancer has been updated.'),
handle: handle,
allowed: allowed
});
///////////////
function allowed() {
// This rule is made up and should therefore always pass. At some point there will
// likely be a valid rule similar to this that we will want to use.
return policy.ifAllowed({ rules: [['neutron', 'update_loadbalancer']] });
}
function handle(response) {
return actionResultService.getActionResult()
.updated(resourceType, response.config.data.loadbalancer.id)
.result;
}
}
})();

View File

@ -0,0 +1,63 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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';
describe('LBaaS v2 Edit Load Balancer Action Service', function() {
var service, scope, policy;
function canEdit(item) {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var allowed = service.allowed(item);
scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_loadbalancer']]});
return allowed;
}
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$modal', {
open: function() {
return {
result: {
then: function(func) {
func();
}
}
};
}
});
}));
beforeEach(inject(function ($injector) {
scope = $injector.get('$rootScope').$new();
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
service = $injector.get('horizon.dashboard.project.lbaasv2.loadbalancers.actions.edit');
}));
it('should allow editing an load balancer', function() {
expect(canEdit({provisioning_status: 'ACTIVE'})).toBe(true);
});
it('should handle the action result properly', function() {
var result = service.handle({config: {data: {loadbalancer: {id: 1}}}});
expect(result.updated[0].id).toBe(1);
});
});
})();

View File

@ -29,7 +29,6 @@
launchContext: { id: '1' } launchContext: { id: '1' }
}; };
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function ($provide) { beforeEach(module(function ($provide) {
workflowSpy = jasmine.createSpy('workflow').and.returnValue(workflow); workflowSpy = jasmine.createSpy('workflow').and.returnValue(workflow);

View File

@ -1,123 +0,0 @@
/*
* 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.loadbalancers')
.factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.rowActions',
tableRowActions);
tableRowActions.$inject = [
'$q',
'$route',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.delete',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.associate-ip.modal.service',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.disassociate-ip.modal.service',
'horizon.app.core.openstack-service-api.policy',
'horizon.app.core.openstack-service-api.network',
'horizon.framework.util.q.extensions',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.loadbalancers.actions.rowActions
*
* @description
* Provides the service for the Load Balancers table row actions.
*
* @param $q The angular service for promises.
* @param $route The angular $route service.
* @param workflowModal The LBaaS workflow modal service.
* @param deleteService The load balancer delete service.
* @param associateIp The associate floating IP modal service.
* @param disassociateIp The disassociate floating IP modal service.
* @param policy The horizon policy service.
* @param network The horizon network API service.
* @param qExtensions Horizon extensions to the $q service.
* @param gettext The horizon gettext function for translation.
* @returns Load balancers table row actions service object.
*/
function tableRowActions(
$q,
$route,
workflowModal,
deleteService,
associateIp,
disassociateIp,
policy,
network,
qExtensions,
gettext
) {
var edit = workflowModal.init({
controller: 'EditLoadBalancerWizardController',
message: gettext('The load balancer has been updated.'),
handle: onEdit,
allowed: canEdit
});
var service = {
actions: actions
};
return service;
///////////////
function actions() {
return [{
service: edit,
template: {
text: gettext('Edit')
}
},{
service: associateIp,
template: {
text: gettext('Associate Floating IP')
}
},{
service: disassociateIp,
template: {
text: gettext('Disassociate Floating IP')
}
},{
service: deleteService,
template: {
text: gettext('Delete Load Balancer'),
type: 'delete'
}
}];
}
function canEdit(item) {
return $q.all([
qExtensions.booleanAsPromise(item.provisioning_status === 'ACTIVE'),
// This rule is made up and should therefore always pass. At some point there will
// likely be a valid rule similar to this that we will want to use.
policy.ifAllowed({ rules: [['neutron', 'update_loadbalancer']] })
]);
}
function onEdit(/*response*/) {
$route.reload();
}
}
})();

View File

@ -1,101 +0,0 @@
/*
* 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';
describe('LBaaS v2 Load Balancers Table Row Actions Service', function() {
var rowActionsService, scope, $route, actions, policy;
function canEdit(item) {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var promise = actions[0].service.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_loadbalancer']]});
return allowed;
}
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
var response = {
data: {
id: '1'
}
};
var modal = {
open: function() {
return {
result: {
then: function(func) {
func(response);
}
}
};
}
};
$provide.value('$uibModal', modal);
}));
beforeEach(inject(function ($injector) {
scope = $injector.get('$rootScope').$new();
$route = $injector.get('$route');
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
rowActionsService = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.rowActions');
actions = rowActionsService.actions();
}));
it('should define correct table row actions', function() {
expect(actions.length).toBe(4);
expect(actions[0].template.text).toBe('Edit');
expect(actions[1].template.text).toBe('Associate Floating IP');
expect(actions[2].template.text).toBe('Disassociate Floating IP');
expect(actions[3].template.text).toBe('Delete Load Balancer');
});
it('should allow editing an ACTIVE load balancer', function() {
expect(canEdit({provisioning_status: 'ACTIVE'})).toBe(true);
});
it('should not allow editing a non-ACTIVE load balancer', function() {
expect(canEdit({provisioning_status: 'PENDING_UPDATE'})).toBe(false);
});
it('should have the "allowed" and "perform" functions', function() {
actions.forEach(function(action) {
expect(action.service.allowed).toBeDefined();
expect(action.service.perform).toBeDefined();
});
});
it('should reload table after edit', function() {
spyOn($route, 'reload').and.callThrough();
actions[0].service.perform();
expect($route.reload).toHaveBeenCalled();
});
});
})();

View File

@ -1,92 +0,0 @@
/*
* Copyright 2015 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.loadbalancers')
.controller('LoadBalancerDetailController', LoadBalancerDetailController);
LoadBalancerDetailController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.rowActions',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'$routeParams',
'$window',
'$scope'
];
/**
* @ngdoc controller
* @name LoadBalancerDetailController
*
* @description
* Controller for the LBaaS v2 load balancers detail page.
*
* @param api The LBaaS v2 API service.
* @param rowActions The load balancer row actions service.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param $routeParams The angular $routeParams service.
* @param $window Angular's reference to the browser window object.
* @param $scope The angular scope object.
* @returns undefined
*/
function LoadBalancerDetailController(
api, rowActions, loadBalancersService, $routeParams, $window, $scope
) {
var ctrl = this;
ctrl.loading = true;
ctrl.error = false;
ctrl.actions = rowActions.actions;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
ctrl.listenersTabActive = $window.listenersTabActive;
init();
////////////////////////////////
function init() {
ctrl.loadbalancer = null;
ctrl.loading = true;
ctrl.error = false;
api.getLoadBalancer($routeParams.loadbalancerId, true).then(success, fail);
}
function success(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
// active after reloading the route following an action.
$scope.$watch(function() {
return ctrl.listenersTabActive;
}, function(active) {
$window.listenersTabActive = active;
});
}
})();

View File

@ -1,89 +0,0 @@
/*
* Copyright 2015 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';
describe('LBaaS v2 Load Balancer Detail Controller', function() {
var lbaasv2API, $scope, $window, $controller, apiFail;
function fakeAPI() {
return {
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'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
apiFail = false;
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(fakeAPI);
$scope = $injector.get('$rootScope').$new();
$window = {};
$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;
$scope.$apply();
expect($window.listenersTabActive).toBe(true);
ctrl.listenersTabActive = false;
$scope.$apply();
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,61 +0,0 @@
<div ng-controller="LoadBalancerDetailController as ctrl">
<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
<div ng-if="!ctrl.loading && !ctrl.error">
<div class="page-header">
<actions allowed="ctrl.actions" type="row" item="ctrl.loadbalancer"
ng-if="ctrl.loadbalancer" class="actions_column pull-right"></actions>
<ol class="breadcrumb">
<li><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li class="active">{$ ctrl.loadbalancer.name || ctrl.loadbalancer.id $}</li>
</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>
<dt translate>Created At</dt>
<dd>{$ ::ctrl.loadbalancer.created_at $}</dd>
<dt translate>Updated At</dt>
<dd>{$ ::ctrl.loadbalancer.updated_at $}</dd>
</dl>
</div>
</div>
</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

@ -0,0 +1,96 @@
/*
* Copyright 2015 IBM Corp.
* Copyright 2017 Walmart.
*
* 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.loadbalancers')
.controller('LoadBalancerDetailController', LoadBalancerDetailController);
LoadBalancerDetailController.$inject = [
'loadbalancer',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.loadbalancers.resourceType',
'horizon.framework.conf.resource-type-registry.service',
'horizon.framework.widgets.modal-wait-spinner.service',
'$q'
];
/**
* @ngdoc controller
* @name LoadBalancerDetailController
*
* @description
* Controller for the LBaaS v2 load balancers detail page.
*
* @param loadbalancer The loadbalancer object.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param resourceType The load balancer resource type.
* @param typeRegistry The horizon type registry service.
* @param spinnerService The horizon modal wait spinner service.
* @param $q The angular service for promises.
*
* @returns undefined
*/
function LoadBalancerDetailController(
loadbalancer, loadBalancersService, resourceType, typeRegistry,
spinnerService, $q
) {
var ctrl = this;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
ctrl.loadbalancer = loadbalancer;
ctrl.listFunctionExtraParams = {
loadbalancerId: ctrl.loadbalancer.id
};
ctrl.resourceType = typeRegistry.getResourceType(resourceType);
ctrl.context = {};
ctrl.context.identifier = loadbalancer.id;
ctrl.resultHandler = actionResultHandler;
function actionResultHandler(returnValue) {
return $q.when(returnValue, actionSuccessHandler);
}
function loadData(response) {
spinnerService.hideModalSpinner();
ctrl.showDetails = true;
ctrl.resourceType.initActions();
ctrl.loadbalancer = response.data;
ctrl.loadbalancer.floating_ip_address = response.data.floating_ip.ip;
}
function actionSuccessHandler(result) {
// The action has completed (for whatever "complete" means to that
// action. Notice the view doesn't really need to know the semantics of the
// particular action because the actions return data in a standard form.
// That return includes the id and type of each created, updated, deleted
// and failed item.
// Currently just refreshes the display each time.
if (result) {
spinnerService.showModalSpinner(gettext('Please Wait'));
ctrl.showDetails = false;
ctrl.context.loadPromise = ctrl.resourceType.load(ctrl.context.identifier);
ctrl.context.loadPromise.then(loadData);
}
}
}
})();

View File

@ -0,0 +1,99 @@
/*
* Copyright 2015 IBM Corp.
* Copyright 2017 Walmart.
*
* 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';
describe('LBaaS v2 Load Balancer Detail Controller', function() {
var deferred, service, ctrl, scope, $timeout, $q, actionResultService;
///////////////////////
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($controller, $rootScope, _$q_, _$timeout_) {
$q = _$q_;
deferred = $q.defer();
service = {
getResourceType: function() {
return {
load: function() { return deferred.promise; },
parsePath: function() { return 'my-context'; },
itemName: function() { return 'A name'; },
initActions: angular.noop
};
},
getDefaultDetailsTemplateUrl: angular.noop
};
actionResultService = {
getIdsOfType: function() { return []; }
};
$timeout = _$timeout_;
scope = $rootScope.$new();
ctrl = $controller('LoadBalancerDetailController', {
$scope: scope,
loadbalancer: { id: '123' },
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
}
});
}));
it('should create a controller', function() {
expect(ctrl).toBeDefined();
expect(ctrl.loadbalancer).toBeDefined();
});
describe('resultHandler', function() {
it('handles empty results', function() {
var result = $q.defer();
result.resolve({});
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles falsy results', function() {
var result = $q.defer();
result.resolve(false);
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles matched results', function() {
spyOn(actionResultService, 'getIdsOfType').and.returnValue([1, 2, 3]);
var result = $q.defer();
result.resolve({some: 'thing'});
ctrl.resultHandler(result.promise);
deferred.resolve({data: {some: 'data', floating_ip: {}}});
$timeout.flush();
expect(ctrl.showDetails).toBe(true);
});
});
});
})();

View File

@ -0,0 +1,66 @@
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li class="active">{$ ctrl.loadbalancer.name || ctrl.loadbalancer.id $}</li>
</ol>
<div class="row">
<div class="col-xs-12 col-sm-9 text-left">
<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>
<li>
<strong translate>Admin State Up</strong>
{$ ctrl.loadbalancer.admin_state_up | yesno $}
</li>
</ul>
</div>
<div class="col-xs-12 col-sm-3 text-right details-item-actions">
<actions allowed="ctrl.resourceType.itemActions"
type="row"
item="ctrl.loadbalancer"
ng-if="ctrl.loadbalancer"
class="actions_column pull-right"
result-handler="ctrl.resultHandler"></actions>
</div>
</div>
</div>
<uib-tabset class="octavia-tabset">
<uib-tab heading="{$ 'Overview' | translate $}">
<div class="col-md-6 detail">
<hz-resource-property-list
resource-type-name="OS::Octavia::LoadBalancer"
cls="dl-horizontal"
item="ctrl.loadbalancer"
property-groups="[[
'id', 'name', 'description', 'project_id', 'created_at', 'updated_at',
'vip_port_id', 'vip_subnet_id', 'vip_network_id', 'provider', 'flavor',
'floating_ip_address']]">
</hz-resource-property-list>
</div>
</uib-tab>
<uib-tab heading="{$ 'Listeners' | translate $}">
<hz-resource-table resource-type-name="OS::Octavia::Listener"
track-by="trackBy"
list-function-extra-params="ctrl.listFunctionExtraParams">
</hz-resource-table>
</uib-tab>
<!--
<uib-tab heading="{$ 'Pools' | translate $}">
<hz-resource-table resource-type-name="OS::Octavia::Pool"
track-by="trackBy"
list-function-extra-params="ctrl.listFunctionExtraParams">
</hz-resource-table>
</uib-tab>
-->
</uib-tabset>

View File

@ -0,0 +1,9 @@
<hz-resource-property-list
resource-type-name="OS::Octavia::LoadBalancer"
item="item"
property-groups="[
['name', 'id', 'project_id'],
['created_at', 'updated_at', 'description'],
['vip_network_id', 'vip_subnet_id', 'vip_port_id'],
['provider', 'floating_ip_address']]">
</hz-resource-property-list>

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2015 IBM Corp. * Copyright 2015 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,6 +27,166 @@
*/ */
angular angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers', []); .module('horizon.dashboard.project.lbaasv2.loadbalancers', [])
.constant('horizon.dashboard.project.lbaasv2.loadbalancers.resourceType',
'OS::Octavia::LoadBalancer')
.run(run);
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.create',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.edit',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.associate-ip',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.disassociate-ip',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.delete',
'horizon.dashboard.project.lbaasv2.loadbalancers.resourceType'
];
function run(
registry,
basePath,
loadBalancerService,
createService,
editService,
associateIpService,
disassociateIpService,
deleteService,
resourceType
) {
var loadBalancerResourceType = registry.getResourceType(resourceType);
loadBalancerResourceType
.setNames(gettext('Load Balancer'), gettext('Load Balancers'))
.setSummaryTemplateUrl(basePath + 'loadbalancers/details/drawer.html')
.setProperties(loadBalancerProperties(loadBalancerService))
.setListFunction(loadBalancerService.getLoadBalancersPromise)
.setLoadFunction(loadBalancerService.getLoadBalancerPromise)
.tableColumns
.append({
id: 'name',
priority: 1,
sortDefault: true,
urlFunction: loadBalancerService.getDetailsPath
})
.append({
id: 'vip_address',
priority: 1
})
.append({
id: 'operating_status',
priority: 1
})
.append({
id: 'provisioning_status',
priority: 1
})
.append({
id: 'admin_state_up',
priority: 1
});
loadBalancerResourceType.itemActions
.append({
id: 'loadBalancerEdit',
service: editService,
template: {
text: gettext('Edit Load Balancer')
}
})
.append({
id: 'loadBalancerAssociateFloatingIp',
service: associateIpService,
template: {
text: gettext('Associate Floating IP')
}
})
.append({
id: 'loadBalancerDisassociateFloatingIp',
service: disassociateIpService,
template: {
text: gettext('Disassociate Floating IP')
}
})
.append({
id: 'loadBalancerDelete',
service: deleteService,
template: {
text: gettext('Delete Load Balancer'),
type: 'delete'
}
});
loadBalancerResourceType.globalActions
.append({
id: 'loadBalancerCreate',
service: createService,
template: {
type: 'create',
text: gettext('Create Load Balancer')
}
});
loadBalancerResourceType.batchActions
.append({
id: 'loadBalancerBatchDelete',
service: deleteService,
template: {
text: gettext('Delete Load Balancers'),
type: 'delete-selected'
}
});
}
function loadBalancerProperties(loadBalancerService) {
return {
id: gettext('ID'),
name: {
label: gettext('Name'),
filters: ['noName']
},
description: {
label: gettext('Description'),
filters: ['noValue']
},
provisioning_status: {
label: gettext('Provisioning Status'),
values: loadBalancerService.provisioningStatus
},
operating_status: {
label: gettext('Operating Status'),
values: loadBalancerService.operatingStatus
},
admin_state_up: {
label: gettext('Admin State Up'),
filters: ['yesno']
},
project_id: gettext('Project ID'),
created_at: {
label: gettext('Created At'),
filters: ['noValue']
},
updated_at: {
label: gettext('Updated At'),
filters: ['noValue']
},
vip_address: gettext('IP Address'),
vip_port_id: gettext('Port ID'),
vip_subnet_id: gettext('Subnet ID'),
vip_network_id: gettext('Network ID'),
listeners: gettext('Listeners'),
pools: gettext('Pools'),
provider: gettext('Provider'),
flavor: {
label: gettext('Flavor'),
filters: ['noValue']
},
floating_ip_address: {
label: gettext('Floating IP'),
filters: ['noName']
}
};
}
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2015 IBM Corp. * Copyright 2015 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,4 +23,48 @@
}); });
}); });
describe('LBaaS v2 Load Balancers Registry', function () {
var registry, resourceType;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function($injector) {
resourceType = $injector.get('horizon.dashboard.project.lbaasv2.loadbalancers.resourceType');
registry = $injector.get('horizon.framework.conf.resource-type-registry.service');
}));
it('should define resourceType', function () {
expect(resourceType).toBeDefined();
});
it('should register item actions', function () {
var actions = registry.getResourceType(resourceType).itemActions;
expect(actionHasId(actions, 'loadBalancerEdit')).toBe(true);
expect(actionHasId(actions, 'loadBalancerAssociateFloatingIp')).toBe(true);
expect(actionHasId(actions, 'loadBalancerDisassociateFloatingIp')).toBe(true);
expect(actionHasId(actions, 'loadBalancerDelete')).toBe(true);
});
it('should register global actions', function () {
var actions = registry.getResourceType(resourceType).globalActions;
expect(actionHasId(actions, 'loadBalancerCreate')).toBe(true);
});
it('should register batch actions', function () {
var actions = registry.getResourceType(resourceType).batchActions;
expect(actionHasId(actions, 'loadBalancerBatchDelete')).toBe(true);
});
function actionHasId(list, value) {
return list.filter(matchesId).length === 1;
function matchesId(action) {
if (action.id === value) {
return true;
}
}
}
});
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -29,10 +30,13 @@
/** /**
* @ngdoc service * @ngdoc service
* @name horizon.dashboard.project.lbaasv2.loadbalancers.service * @name horizon.dashboard.project.lbaasv2.loadbalancers.service
*
* @description General service for LBaaS v2 load balancers. * @description General service for LBaaS v2 load balancers.
*
* @param $q The angular service for promises. * @param $q The angular service for promises.
* @param api The LBaaS V2 service API. * @param api The LBaaS V2 service API.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
*
* @returns The load balancers service. * @returns The load balancers service.
*/ */
@ -41,7 +45,8 @@
ONLINE: gettext('Online'), ONLINE: gettext('Online'),
OFFLINE: gettext('Offline'), OFFLINE: gettext('Offline'),
DEGRADED: gettext('Degraded'), DEGRADED: gettext('Degraded'),
ERROR: gettext('Error') ERROR: gettext('Error'),
NO_MONITOR: gettext('No Monitor')
}; };
var provisioningStatus = { var provisioningStatus = {
@ -53,9 +58,37 @@
ERROR: gettext('Error') ERROR: gettext('Error')
}; };
var loadBalancerAlgorithm = {
ROUND_ROBIN: gettext('Round Robin'),
LEAST_CONNECTIONS: gettext('Least Connections'),
SOURCE_IP: gettext('Source IP')
};
var none = {
null: gettext('None')
};
var service = { var service = {
operatingStatus: operatingStatus, operatingStatus: operatingStatus,
provisioningStatus: provisioningStatus, provisioningStatus: provisioningStatus,
loadBalancerAlgorithm: loadBalancerAlgorithm,
none: none,
nullFilter: nullFilter,
getLoadBalancersPromise: getLoadBalancersPromise,
getLoadBalancerPromise: getLoadBalancerPromise,
getDetailsPath: getDetailsPath,
getListenersPromise: getListenersPromise,
getListenerPromise: getListenerPromise,
getListenerDetailsPath: getListenerDetailsPath,
getPoolsPromise: getPoolsPromise,
getPoolPromise: getPoolPromise,
getPoolDetailsPath: getPoolDetailsPath,
getMembersPromise: getMembersPromise,
getMemberPromise: getMemberPromise,
getMemberDetailsPath: getMemberDetailsPath,
getHealthMonitorPromise: getHealthMonitorPromise,
getHealthMonitorsPromise: getHealthMonitorsPromise,
getHealthMonitorDetailsPath: getHealthMonitorDetailsPath,
isActionable: isActionable isActionable: isActionable
}; };
@ -63,6 +96,136 @@
//////////// ////////////
function nullFilter(input) {
if (none.hasOwnProperty(input)) {
return none[input];
}
return input;
}
function getMemberDetailsPath(item) {
return 'project/load_balancer/' + item.loadbalancerId +
'/listeners/' + item.listenerId +
'/pools/' + item.poolId +
'/members/' + item.id;
}
function getMembersPromise(params) {
return api.getMembers(params.poolId).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyItem)}};
function modifyItem(item) {
item.trackBy = item.id + item.updated_at;
item.loadbalancerId = params.loadbalancerId;
item.listenerId = params.listenerId;
item.poolId = params.poolId;
return item;
}
}
}
function getMemberPromise(poolId, memberId) {
return api.getMember(poolId, memberId);
}
function getHealthMonitorDetailsPath(item) {
return 'project/load_balancer/' + item.loadbalancerId +
'/listeners/' + item.listenerId +
'/pools/' + item.poolId +
'/healthmonitors/' + item.id;
}
function getHealthMonitorsPromise(params) {
return api.getHealthMonitors(params.poolId).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyItem)}};
function modifyItem(item) {
item.trackBy = item.id + item.updated_at;
item.loadbalancerId = params.loadbalancerId;
item.listenerId = params.listenerId;
item.poolId = params.poolId;
return item;
}
}
}
function getHealthMonitorPromise(identifier) {
return api.getHealthMonitor(identifier);
}
function getPoolsPromise(params) {
return api.getPools(params.loadbalancerId, params.listenerId).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyItem)}};
function modifyItem(item) {
item.trackBy = item.id + item.updated_at;
item.loadbalancerId = params.loadbalancerId;
item.listenerId = params.listenerId;
return item;
}
}
}
function getPoolPromise(identifier) {
return api.getPool(identifier);
}
function getPoolDetailsPath(item) {
return 'project/load_balancer/' +
item.loadbalancerId + '/listeners/' +
item.listeners[0].id + '/pools/' + item.id;
}
function getListenersPromise(params) {
return api.getListeners(params.loadbalancerId).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyItem)}};
function modifyItem(item) {
item.trackBy = item.id + item.updated_at;
item.loadbalancerId = params.loadbalancerId;
return item;
}
}
}
function getListenerPromise(identifier) {
return api.getListener(identifier);
}
function getListenerDetailsPath(item) {
return 'project/load_balancer/' + item.loadbalancerId + '/listeners/' + item.id;
}
function getLoadBalancersPromise() {
return api.getLoadBalancers(true).then(modifyResponse);
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyItem)}};
function modifyItem(item) {
item.trackBy = item.id + item.updated_at;
item.floating_ip_address = item.floating_ip.ip;
return item;
}
}
}
function getLoadBalancerPromise(identifier) {
return api.getLoadBalancer(identifier, true);
}
function getDetailsPath(item) {
return 'project/load_balancer/' + item.id;
}
/** /**
* @ngdoc method * @ngdoc method
* @name horizon.dashboard.project.lbaasv2.loadbalancers.service.isActionable * @name horizon.dashboard.project.lbaasv2.loadbalancers.service.isActionable

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,32 +18,15 @@
'use strict'; 'use strict';
describe('LBaaS v2 Load Balancers Service', function() { describe('LBaaS v2 Load Balancers Service', function() {
var service, $q, $scope; var service, $q, $scope, api;
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('horizon.app.core.openstack-service-api.lbaasv2', {
getLoadBalancer: function(index) {
var loadbalancers = [{ provisioning_status: 'ACTIVE' },
{ provisioning_status: 'PENDING_UPDATE' }];
var deferred = $q.defer();
deferred.resolve({ data: loadbalancers[index] });
return deferred.promise;
}
});
}));
beforeEach(inject(function ($injector) { beforeEach(inject(function ($injector) {
$q = $injector.get('$q'); $q = $injector.get('$q');
$scope = $injector.get('$rootScope').$new(); $scope = $injector.get('$rootScope').$new();
service = $injector.get('horizon.dashboard.project.lbaasv2.loadbalancers.service'); service = $injector.get('horizon.dashboard.project.lbaasv2.loadbalancers.service');
api = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
})); }));
it('should define value mappings', function() { it('should define value mappings', function() {
@ -50,16 +34,184 @@
expect(service.provisioningStatus).toBeDefined(); expect(service.provisioningStatus).toBeDefined();
}); });
it('should allow checking status of load balancer', function() { it('should filter null property', function() {
expect(service.nullFilter('null')).toBe(gettext('None'));
expect(service.nullFilter('something else')).toBe('something else');
});
it('getDetailsPath creates urls using the item\'s ID', function() {
var myItem = {id: '1234'};
expect(service.getDetailsPath(myItem)).toBe('project/load_balancer/1234');
});
it("getLoadBalancersPromise provides a promise", inject(function($timeout) {
var deferred = $q.defer();
spyOn(api, 'getLoadBalancers').and.returnValue(deferred.promise);
var result = service.getLoadBalancersPromise({});
deferred.resolve({data: {items: [{id: 1, updated_at: 'feb8', floating_ip: {}}]}});
$timeout.flush();
expect(result.$$state.value.data.items[0].id).toBe(1);
expect(result.$$state.value.data.items[0].updated_at).toBe('feb8');
expect(result.$$state.value.data.items[0].trackBy).toBe('1feb8');
}));
it("getLoadBalancerPromise provides a promise", inject(function() {
var deferred = $q.defer();
spyOn(api, 'getLoadBalancer').and.returnValue(deferred.promise);
var result = service.getLoadBalancerPromise({});
deferred.resolve({data: {id: 1, updated_at: 'feb8', floating_ip: {}}});
expect(result.$$state.value.data.id).toBe(1);
expect(result.$$state.value.data.updated_at).toBe('feb8');
}));
it('getListenerDetailsPath creates urls using the item\'s ID', function() {
var myItem = {loadbalancerId: '123', id: '456'};
expect(service.getListenerDetailsPath(myItem))
.toBe('project/load_balancer/123/listeners/456');
});
it("getListenersPromise provides a promise", inject(function($timeout) {
var deferred = $q.defer();
spyOn(api, 'getListeners').and.returnValue(deferred.promise);
var result = service.getListenersPromise({loadbalancerId: 3});
deferred.resolve({data: {items: [{id: 1, updated_at: 'feb8'}]}});
$timeout.flush();
expect(result.$$state.value.data.items[0].id).toBe(1);
expect(result.$$state.value.data.items[0].updated_at).toBe('feb8');
expect(result.$$state.value.data.items[0].trackBy).toBe('1feb8');
expect(result.$$state.value.data.items[0].loadbalancerId).toBe(3);
}));
it("getListenerPromise provides a promise", inject(function() {
var deferred = $q.defer();
spyOn(api, 'getListener').and.returnValue(deferred.promise);
var result = service.getListenerPromise({loadbalancerId: 3});
deferred.resolve({data: {id: 1, updated_at: 'feb8', floating_ip: {}}});
expect(result.$$state.value.data.id).toBe(1);
expect(result.$$state.value.data.updated_at).toBe('feb8');
}));
it('getPoolDetailsPath creates urls using the item\'s ID', function() {
var myItem = {loadbalancerId: '123', id: '789', listeners: [{id: '456'}]};
expect(service.getPoolDetailsPath(myItem))
.toBe('project/load_balancer/123/listeners/456/pools/789');
});
it("getPoolsPromise provides a promise", inject(function($timeout) {
var deferred = $q.defer();
spyOn(api, 'getPools').and.returnValue(deferred.promise);
var result = service.getPoolsPromise({loadbalancerId: 3});
deferred.resolve({data: {items: [{id: 1, updated_at: 'feb8'}]}});
$timeout.flush();
expect(result.$$state.value.data.items[0].id).toBe(1);
expect(result.$$state.value.data.items[0].updated_at).toBe('feb8');
expect(result.$$state.value.data.items[0].trackBy).toBe('1feb8');
expect(result.$$state.value.data.items[0].loadbalancerId).toBe(3);
}));
it("getPoolPromise provides a promise", inject(function() {
var deferred = $q.defer();
spyOn(api, 'getPool').and.returnValue(deferred.promise);
var result = service.getPoolPromise({loadbalancerId: 3});
deferred.resolve({data: {id: 1, updated_at: 'feb8'}});
expect(result.$$state.value.data.id).toBe(1);
expect(result.$$state.value.data.updated_at).toBe('feb8');
}));
it('getMemberDetailsPath creates urls using the item\'s ID', function() {
var myItem = {
loadbalancerId: '1',
listenerId: '2',
poolId: '3',
id: '4'
};
expect(service.getMemberDetailsPath(myItem))
.toBe('project/load_balancer/1/listeners/2/pools/3/members/4');
});
it("getMembersPromise provides a promise", inject(function($timeout) {
var deferred = $q.defer();
spyOn(api, 'getMembers').and.returnValue(deferred.promise);
var result = service.getMembersPromise({
loadbalancerId: 1,
listenerId: 2,
poolId: 3
});
deferred.resolve({data: {items: [{id: 4, updated_at: 'feb8'}]}});
$timeout.flush();
expect(result.$$state.value.data.items[0].id).toBe(4);
expect(result.$$state.value.data.items[0].updated_at).toBe('feb8');
expect(result.$$state.value.data.items[0].trackBy).toBe('4feb8');
expect(result.$$state.value.data.items[0].loadbalancerId).toBe(1);
expect(result.$$state.value.data.items[0].listenerId).toBe(2);
expect(result.$$state.value.data.items[0].poolId).toBe(3);
}));
it("getMemberPromise provides a promise", inject(function() {
var deferred = $q.defer();
spyOn(api, 'getMember').and.returnValue(deferred.promise);
var result = service.getMemberPromise(2, 1);
deferred.resolve({data: {id: 1, updated_at: 'feb8'}});
expect(result.$$state.value.data.id).toBe(1);
expect(result.$$state.value.data.updated_at).toBe('feb8');
}));
it('getHealthMonitorDetailsPath creates urls using the item\'s ID', function() {
var myItem = {
loadbalancerId: '1',
listenerId: '2',
poolId: '3',
id: '4'
};
expect(service.getHealthMonitorDetailsPath(myItem))
.toBe('project/load_balancer/1/listeners/2/pools/3/healthmonitors/4');
});
it("getHealthMonitorsPromise provides a promise", inject(function($timeout) {
var deferred = $q.defer();
spyOn(api, 'getHealthMonitors').and.returnValue(deferred.promise);
var result = service.getHealthMonitorsPromise({
loadbalancerId: 1,
listenerId: 2,
poolId: 3
});
deferred.resolve({data: {items: [{id: 4, updated_at: 'feb8'}]}});
$timeout.flush();
expect(result.$$state.value.data.items[0].id).toBe(4);
expect(result.$$state.value.data.items[0].updated_at).toBe('feb8');
expect(result.$$state.value.data.items[0].trackBy).toBe('4feb8');
expect(result.$$state.value.data.items[0].loadbalancerId).toBe(1);
expect(result.$$state.value.data.items[0].listenerId).toBe(2);
expect(result.$$state.value.data.items[0].poolId).toBe(3);
}));
it("getHealthMonitorPromise provides a promise", inject(function() {
var deferred = $q.defer();
spyOn(api, 'getHealthMonitor').and.returnValue(deferred.promise);
var result = service.getHealthMonitorPromise(1);
deferred.resolve({data: {id: 1, updated_at: 'feb8'}});
expect(result.$$state.value.data.id).toBe(1);
expect(result.$$state.value.data.updated_at).toBe('feb8');
}));
it('should allow checking active status of load balancer', function() {
var active = null; var active = null;
var deferred = $q.defer();
spyOn(api, 'getLoadBalancer').and.returnValue(deferred.promise);
deferred.resolve({data: { provisioning_status: 'ACTIVE'}});
service.isActionable(0).then(function() { service.isActionable(0).then(function() {
active = true; active = true;
}); });
$scope.$apply(); $scope.$apply();
expect(active).toBe(true); expect(active).toBe(true);
});
active = null; it('should allow checking transitional status of load balancer', function() {
service.isActionable(1).then(angular.noop, function() { var active = null;
var deferred = $q.defer();
spyOn(api, 'getLoadBalancer').and.returnValue(deferred.promise);
deferred.resolve({data: { provisioning_status: 'PENDING_UPDATE'}});
service.isActionable(0).then(angular.noop, function() {
active = false; active = false;
}); });
$scope.$apply(); $scope.$apply();

View File

@ -0,0 +1,4 @@
<hz-resource-panel resource-type-name="OS::Octavia::LoadBalancer">
<hz-resource-table resource-type-name="OS::Octavia::LoadBalancer"
track-by="trackBy"></hz-resource-table>
</hz-resource-panel>

View File

@ -1,80 +0,0 @@
/*
* Copyright 2015 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.loadbalancers')
.controller('LoadBalancersTableController', LoadBalancersTableController);
LoadBalancersTableController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.batchActions',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.rowActions',
'horizon.dashboard.project.lbaasv2.loadbalancers.service'
];
/**
* @ngdoc controller
* @name LoadBalancersTableController
*
* @description
* Controller for the LBaaS v2 load balancers table. Serves as the focal point for table actions.
*
* @param api The LBaaS V2 service API.
* @param batchActions The load balancer batch actions service.
* @param rowActions The load balancer row actions service.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @returns undefined
*/
function LoadBalancersTableController(api, batchActions, rowActions, loadBalancersService) {
var ctrl = this;
ctrl.items = [];
ctrl.src = [];
ctrl.loading = true;
ctrl.error = false;
ctrl.checked = {};
ctrl.batchActions = batchActions;
ctrl.rowActions = rowActions;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
init();
////////////////////////////////
function init() {
ctrl.src = [];
ctrl.loading = true;
api.getLoadBalancers(true).then(success, fail);
}
function success(response) {
ctrl.src = response.data.items;
ctrl.loading = false;
}
function fail(/*response*/) {
ctrl.src = [];
ctrl.error = true;
ctrl.loading = false;
}
}
})();

View File

@ -1,86 +0,0 @@
/*
* Copyright 2015 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';
describe('LBaaS v2 Load Balancers Table Controller', function() {
var controller, lbaasv2API, scope;
var items = [{ foo: 'bar' }];
var apiFail = false;
function fakeAPI() {
return {
then: function(success, fail) {
if (apiFail && fail) {
fail();
} else {
success({ data: { items: items } });
}
}
};
}
///////////////////////
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
controller = $injector.get('$controller');
scope = $injector.get('$rootScope').$new();
spyOn(lbaasv2API, 'getLoadBalancers').and.callFake(fakeAPI);
}));
function createController() {
return controller('LoadBalancersTableController', { $scope: scope });
}
it('should initialize correctly', function() {
var ctrl = createController();
expect(ctrl.items).toEqual([]);
expect(ctrl.src).toEqual(items);
expect(ctrl.loading).toBe(false);
expect(ctrl.error).toBe(false);
expect(ctrl.checked).toEqual({});
expect(ctrl.batchActions).toBeDefined();
expect(ctrl.rowActions).toBeDefined();
expect(ctrl.operatingStatus).toBeDefined();
expect(ctrl.provisioningStatus).toBeDefined();
});
it('should invoke lbaasv2 apis', function() {
var ctrl = createController();
expect(lbaasv2API.getLoadBalancers).toHaveBeenCalled();
expect(ctrl.src.length).toBe(1);
});
it('should show error if loading fails', function() {
apiFail = true;
var ctrl = createController();
expect(ctrl.src.length).toBe(0);
expect(ctrl.error).toBe(true);
});
});
})();

View File

@ -1,155 +0,0 @@
<hz-page-header header="{$ 'Load Balancers' | translate $}"></hz-page-header>
<table ng-controller="LoadBalancersTableController as table"
hz-table ng-cloak
st-table="table.items"
st-safe-src="table.src"
default-sort="name"
default-sort-reverse="false"
class="table table-striped table-rsp table-detail">
<!--
TODO(jpomero): This table pattern does not allow for extensibility and should be revisited
once horizon implements a better one.
-->
<thead>
<tr>
<!--
Table-batch-actions:
This is where batch actions like searching, creating, and deleting.
-->
<th colspan="9" class="search-header">
<hz-search-bar icon-classes="fa-search">
<actions allowed="table.batchActions.actions" type="batch"></actions>
</hz-search-bar>
</th>
</tr>
<tr>
<!--
Table-column-headers:
This is where we declaratively define the table column headers.
Include select-col if you want to select all.
Include expander if you want to inline details.
Include action-col if you want to perform actions.
-->
<th class="multi_select_column">
<input type="checkbox" hz-select-all="table.items">
</th>
<th class="expander"></th>
<th class="rsp-p1" st-sort="name" st-sort-default="name" translate>Name</th>
<th class="rsp-p1" st-sort="description" translate>Description</th>
<th class="rsp-p1" st-sort="operating_status" translate>Operating Status</th>
<th class="rsp-p1" st-sort="provisioning_status" translate>Provisioning Status</th>
<th class="rsp-p2" st-sort="vip_address" translate>IP Address</th>
<th class="rsp-p2" st-sort="listeners.length" translate>Listeners</th>
<th class="actions_column" translate>Actions</th>
</tr>
</thead>
<tbody>
<!--
Table-rows:
This is where we declaratively define the table columns.
Include select-col if you want to select all.
Include expander if you want to inline details.
Include action-col if you want to perform actions.
rsp-p1 rsp-p2 are responsive priority as user resizes window.
-->
<tr ng-repeat-start="item in table.items track by item.id"
ng-class="{'st-selected': checked[item.id]}">
<td class="multi_select_column">
<input type="checkbox"
ng-model="tCtrl.selections[item.id].checked"
hz-select="item">
</td>
<td class="expander">
<span class="fa fa-chevron-right"
hz-expand-detail
duration="200">
</span>
</td>
<td class="rsp-p1"><a ng-href="project/load_balancer/{$ ::item.id $}">{$ ::(item.name || item.id) $}</a></td>
<td class="rsp-p1">{$ ::item.description | noValue $}</td>
<td class="rsp-p1">{$ ::item.operating_status | decode:table.operatingStatus $}</td>
<td class="rsp-p1">{$ ::item.provisioning_status | decode:table.provisioningStatus $}</td>
<td class="rsp-p2">{$ ::item.vip_address $}</td>
<td class="rsp-p2">{$ item.listeners.length $}</td>
<td class="actions_column">
<!--
Table-row-action-column:
Actions taken here apply to a single item/row.
-->
<actions allowed="table.rowActions.actions" type="row" item="item"></actions>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<!--
Detail-row:
Contains detailed information on this item.
Can be toggled using the chevron button.
Ensure colspan is greater or equal to number of column-headers.
-->
<td class="detail" colspan="9">
<!--
The responsive columns that disappear typically should reappear here
with the same responsive priority that they disappear.
E.g. table header with rsp-p2 should be here with rsp-alt-p2
-->
<div class="row">
<span class="rsp-alt-p2">
<dl class="col-sm-2">
<dt translate>IP Address</dt>
<dd>{$ ::item.vip_address $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Listeners</dt>
<dd>{$ item.listeners.length $}</dd>
</dl>
</span>
</div>
<div class="row">
<dl class="col-sm-2">
<dt translate>Provider</dt>
<dd>{$ ::item.provider $}</dd>
</dl>
<dl class="col-sm-2" ng-if="item.floating_ip !== undefined">
<dt translate>Floating IP Address</dt>
<dd>{$ item.floating_ip.ip || 'None' | translate $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Admin State Up</dt>
<dd>{$ ::item.admin_state_up | yesno $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>ID</dt>
<dd>{$ ::item.id $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Subnet ID</dt>
<dd><a target="_self" ng-href="project/networks/subnets/{$ ::item.vip_subnet_id $}/detail">{$ ::item.vip_subnet_id $}</a></dd>
</dl>
<dl class="col-sm-2">
<dt translate>Port ID</dt>
<dd><a target="_self" ng-href="project/networks/ports/{$ ::item.vip_port_id $}/detail">{$ ::item.vip_port_id $}</a></dd>
</dl>
</div>
</td>
</tr>
<tr table-status table="table" column-count="9"></tr>
</tbody>
<!--
Table-footer:
This is where we display number of items and pagination controls.
-->
<tfoot hz-table-footer items="table.items"></tfoot>
</table>

View File

@ -1,73 +0,0 @@
/*
* 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.members')
.factory('horizon.dashboard.project.lbaasv2.members.actions.batchActions',
tableBatchActions);
tableBatchActions.$inject = [
'horizon.framework.util.i18n.gettext',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.members.actions.update-member-list'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.pools.actions.batchActions
*
* @description
* Provides the service for the Members table batch actions.
*
* @param gettext The horizon gettext function for translation.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param updateMemberListService The LBaaS v2 update member list service.
* @returns Members table batch actions service object.
*/
function tableBatchActions(
gettext, loadBalancersService, updateMemberListService
) {
var loadBalancerIsActionable, loadBalancerId;
var service = {
actions: actions,
init: init
};
return service;
///////////////
function init(_loadBalancerId_) {
loadBalancerId = _loadBalancerId_;
loadBalancerIsActionable = loadBalancersService.isActionable(loadBalancerId);
return service;
}
function actions() {
return [{
service: updateMemberListService.init(loadBalancerIsActionable).update,
template: {
text: gettext('Add/Remove Pool Members')
}
}];
}
}
})();

View File

@ -1,67 +0,0 @@
/*
* 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';
describe('LBaaS v2 Members Table Batch Actions Service', function() {
var actions;
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
var response = {
data: {
id: '1'
}
};
var modal = {
open: function() {
return {
result: {
then: function(func) {
func(response);
}
}
};
}
};
$provide.value('$uibModal', modal);
}));
beforeEach(inject(function ($injector) {
var batchActionsService = $injector.get(
'horizon.dashboard.project.lbaasv2.members.actions.batchActions');
actions = batchActionsService.actions();
}));
it('should define correct table batch actions', function() {
expect(actions.length).toBe(1);
expect(actions[0].template.text).toBe('Add/Remove Pool Members');
});
it('should have the "allowed" and "perform" functions', function() {
actions.forEach(function(action) {
expect(action.service.allowed).toBeDefined();
expect(action.service.perform).toBeDefined();
});
});
});
})();

View File

@ -21,9 +21,9 @@
.factory('horizon.dashboard.project.lbaasv2.members.actions.delete', deleteService); .factory('horizon.dashboard.project.lbaasv2.members.actions.delete', deleteService);
deleteService.$inject = [ deleteService.$inject = [
'$q', 'horizon.dashboard.project.lbaasv2.members.resourceType',
'horizon.framework.util.actions.action-result.service',
'$location', '$location',
'$route',
'horizon.framework.widgets.modal.deleteModalService', 'horizon.framework.widgets.modal.deleteModalService',
'horizon.app.core.openstack-service-api.lbaasv2', 'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
@ -33,22 +33,26 @@
/** /**
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.members.actions.deleteService * @name horizon.dashboard.project.lbaasv2.members.actions.deleteService
*
* @description * @description
* Brings up the delete member confirmation modal dialog. * Brings up the delete member confirmation modal dialog.
* On submit, deletes selected member. * On submit, deletes selected member.
* On cancel, does nothing. * On cancel, does nothing.
* @param $q The angular service for promises. *
* @param resourceType The member resource type.
* @param actionResultService The horizon action result service.
* @param $location The angular $location service. * @param $location The angular $location service.
* @param $route The angular $route service.
* @param deleteModal The horizon delete modal service. * @param deleteModal The horizon delete modal service.
* @param api The LBaaS v2 API service. * @param api The LBaaS v2 API service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
*
* @returns The load balancers table delete service. * @returns The load balancers table delete service.
*/ */
function deleteService($q, $location, $route, deleteModal, api, policy, gettext) { function deleteService(resourceType, actionResultService,
var loadbalancerId, listenerId, poolId, statePromise; $location, deleteModal, api, policy, gettext) {
var loadbalancerId, listenerId, poolId;
var context = { var context = {
labels: { labels: {
title: gettext('Confirm Delete Member'), title: gettext('Confirm Delete Member'),
@ -66,46 +70,51 @@
var service = { var service = {
perform: perform, perform: perform,
allowed: allowed, allowed: allowed,
init: init deleteResult: deleteResult // exposed just for testing
}; };
return service; return service;
////////////// //////////////
function init(_loadbalancerId_, _listenerId_, _poolId_, _statePromise_) {
loadbalancerId = _loadbalancerId_;
listenerId = _listenerId_;
poolId = _poolId_;
statePromise = _statePromise_;
return service;
}
function perform(item) {
deleteModal.open({ $emit: actionComplete }, [item], context);
}
function allowed(/*item*/) { function allowed(/*item*/) {
return $q.all([ // This rule is made up and should therefore always pass. I assume at some point there
statePromise, // will be a valid rule similar to this that we will want to use.
// This rule is made up and should therefore always pass. I assume at some point there return policy.ifAllowed({ rules: [['neutron', 'pool_member_delete']] });
// will be a valid rule similar to this that we will want to use. }
policy.ifAllowed({ rules: [['neutron', 'pool_member_delete']] })
]); function perform(items, scope) {
var members = angular.isArray(items) ? items : [items];
members.map(function(item) {
loadbalancerId = item.loadbalancerId;
listenerId = item.listenerId;
poolId = item.poolId;
});
return deleteModal.open(scope, members, context).then(deleteResult);
}
function deleteResult(deleteModalResult) {
// To make the result of this action generically useful, reformat the return
// from the deleteModal into a standard form
var actionResult = actionResultService.getActionResult();
deleteModalResult.pass.forEach(function markDeleted(item) {
actionResult.deleted(resourceType, item.context.id);
});
deleteModalResult.fail.forEach(function markFailed(item) {
actionResult.failed(resourceType, item.context.id);
});
if (actionResult.result.failed.length === 0 && actionResult.result.deleted.length > 0) {
var path = 'project/load_balancer/' + loadbalancerId +
'/listeners/' + listenerId +
'/pools/' + poolId;
$location.path(path);
}
return actionResult.result;
} }
function deleteItem(id) { function deleteItem(id) {
return api.deleteMember(poolId, id); return api.deleteMember(poolId, id);
} }
function actionComplete(eventType) {
if (eventType === context.successEvent) {
// Success, go back to pool details page
var path = 'project/load_balancer/' +
loadbalancerId + '/listeners/' + listenerId + '/pools/' + poolId;
$location.path(path);
}
$route.reload();
}
} }
})(); })();

View File

@ -17,138 +17,87 @@
'use strict'; 'use strict';
describe('LBaaS v2 Member Delete Service', function() { describe('LBaaS v2 Member Delete Service', function() {
var service, policy, modal, lbaasv2Api, $scope, $location, $q, toast, member; beforeEach(module('horizon.app.core'));
function allowed(item) {
spyOn(policy, 'ifAllowed').and.returnValue(makePromise());
var promise = service.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
$scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'pool_member_delete']]});
return allowed;
}
function makePromise(reject) {
var def = $q.defer();
def[reject ? 'reject' : 'resolve']();
return def.promise;
}
function isActionable(id) {
if (id === 'active') {
return $q.when();
} else {
return $q.reject();
}
}
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module('horizon.framework'));
beforeEach(function() { var deleteModalService, service, lbaasv2API, policyAPI, $location;
member = { id: '1', name: 'Member1' };
});
beforeEach(module(function($provide) { beforeEach(inject(function($injector) {
$provide.value('$uibModal', {
open: function() {
return {
result: makePromise()
};
}
});
$provide.value('horizon.app.core.openstack-service-api.lbaasv2', {
deleteMember: function() {
return makePromise();
}
});
$provide.value('$location', {
path: function() {
return '';
}
});
}));
beforeEach(inject(function ($injector) {
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
lbaasv2Api = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
modal = $injector.get('horizon.framework.widgets.modal.deleteModalService');
$scope = $injector.get('$rootScope').$new();
$location = $injector.get('$location');
$q = $injector.get('$q');
toast = $injector.get('horizon.framework.widgets.toast.service');
service = $injector.get('horizon.dashboard.project.lbaasv2.members.actions.delete'); service = $injector.get('horizon.dashboard.project.lbaasv2.members.actions.delete');
service.init('1', '2', '3', isActionable('active')); lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
$scope.$apply(); deleteModalService = $injector.get('horizon.framework.widgets.modal.deleteModalService');
policyAPI = $injector.get('horizon.app.core.openstack-service-api.policy');
$location = $injector.get('$location');
})); }));
it('should have the "allowed" and "perform" functions', function() { describe('perform method', function() {
expect(service.allowed).toBeDefined(); beforeEach(function () {
expect(service.perform).toBeDefined(); // just need for this to return something that looks like a promise but does nothing
spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
});
it('should open the modal with correct label', function () {
service.perform({name: 'spam'});
var labels = deleteModalService.open.calls.argsFor(0)[2].labels;
expect(deleteModalService.open).toHaveBeenCalled();
angular.forEach(labels, function eachLabel(label) {
expect(label.toLowerCase()).toContain('member');
});
});
it('should open the delete modal with correct entities', function () {
service.perform([{name: 'one'}, {name: 'two'}]);
var entities = deleteModalService.open.calls.argsFor(0)[1];
expect(deleteModalService.open).toHaveBeenCalled();
expect(entities.length).toEqual(2);
});
it('should pass in a function that deletes an member', function () {
spyOn(lbaasv2API, 'deleteMember').and.callFake(angular.noop);
service.perform({poolId: 2, id: 1, name: 'one'});
var contextArg = deleteModalService.open.calls.argsFor(0)[2];
var deleteFunction = contextArg.deleteEntity;
deleteFunction(1);
expect(lbaasv2API.deleteMember).toHaveBeenCalledWith(2, 1);
});
}); });
it('should allow deleting member from load balancer in ACTIVE state', function() { it('should handle the action result properly', function() {
expect(allowed()).toBe(true);
});
it('should not allow deleting member from load balancer in a PENDING state', function() {
service.init('1', '2', '3', isActionable('pending'));
expect(allowed()).toBe(false);
});
it('should open the delete modal', function() {
spyOn(modal, 'open');
service.perform(member);
$scope.$apply();
expect(modal.open.calls.count()).toBe(1);
var args = modal.open.calls.argsFor(0);
expect(args.length).toBe(3);
expect(args[0]).toEqual({ $emit: jasmine.any(Function) });
expect(args[1]).toEqual([member]);
expect(args[2]).toEqual(jasmine.objectContaining({
labels: jasmine.any(Object),
deleteEntity: jasmine.any(Function)
}));
expect(args[2].labels.title).toBe('Confirm Delete Member');
});
it('should pass function to modal that deletes the member', function() {
spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deleteMember').and.callThrough();
service.perform(member);
$scope.$apply();
expect(lbaasv2Api.deleteMember.calls.count()).toBe(1);
expect(lbaasv2Api.deleteMember).toHaveBeenCalledWith('3', '1');
});
it('should show message if any items fail to be deleted', function() {
spyOn(modal, 'open').and.callThrough();
spyOn(lbaasv2Api, 'deleteMember').and.returnValue(makePromise(true));
spyOn(toast, 'add');
service.perform(member);
$scope.$apply();
expect(modal.open).toHaveBeenCalled();
expect(lbaasv2Api.deleteMember.calls.count()).toBe(1);
expect(toast.add).toHaveBeenCalledWith('error', 'The following member could not ' +
'be deleted: Member1.');
});
it('should return to listener details after delete', function() {
spyOn($location, 'path'); spyOn($location, 'path');
spyOn(toast, 'add'); spyOn(deleteModalService, 'open').and.returnValue({then: angular.noop});
service.perform(member); spyOn(lbaasv2API, 'deleteMember').and.callFake(angular.noop);
$scope.$apply(); service.perform({loadbalancerId: 1, listenerId: 2, poolId: 3, id: 1, name: 'one'});
expect($location.path).toHaveBeenCalledWith('project/load_balancer/1/listeners/2/pools/3'); var result = service.deleteResult({
expect(toast.add).toHaveBeenCalledWith('success', 'Deleted member: Member1.'); fail: [],
pass: [{
context: {
id: 1
}
}]
});
var path = 'project/load_balancer/1/listeners/2/pools/3';
expect($location.path).toHaveBeenCalledWith(path);
expect(result.deleted[0].id).toBe(1);
result = service.deleteResult({
pass: [],
fail: [{
context: {
id: 1
}
}]
});
expect(result.failed[0].id).toBe(1);
}); });
}); describe('allow method', function() {
it('should use default policy if batch action', function () {
spyOn(policyAPI, 'ifAllowed');
service.allowed();
expect(policyAPI.ifAllowed).toHaveBeenCalled();
});
}); // end of allowed
}); // end of delete
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,13 +19,13 @@
angular angular
.module('horizon.dashboard.project.lbaasv2.members') .module('horizon.dashboard.project.lbaasv2.members')
.factory('horizon.dashboard.project.lbaasv2.members.actions.edit-member.modal.service', .factory('horizon.dashboard.project.lbaasv2.members.actions.edit-member',
modalService); modalService);
modalService.$inject = [ modalService.$inject = [
'$q', 'horizon.dashboard.project.lbaasv2.members.resourceType',
'horizon.framework.util.actions.action-result.service',
'$uibModal', '$uibModal',
'$route',
'horizon.dashboard.project.lbaasv2.basePath', 'horizon.dashboard.project.lbaasv2.basePath',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
'horizon.framework.widgets.toast.service', 'horizon.framework.widgets.toast.service',
@ -38,9 +39,9 @@
* @description * @description
* Provides the service for the pool member Edit Member action. * Provides the service for the pool member Edit Member action.
* *
* @param $q The angular service for promises. * @param resourceType The member resource type.
* @param actionResultService The horizon action result service.
* @param $uibModal The angular bootstrap $uibModal service. * @param $uibModal The angular bootstrap $uibModal service.
* @param $route The angular $route service.
* @param basePath The LBaaS v2 module base path. * @param basePath The LBaaS v2 module base path.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param toastService The horizon toast service. * @param toastService The horizon toast service.
@ -50,39 +51,29 @@
*/ */
function modalService( function modalService(
$q, resourceType,
actionResultService,
$uibModal, $uibModal,
$route,
basePath, basePath,
policy, policy,
toastService, toastService,
gettext gettext
) { ) {
var poolId, statePromise; var member;
var service = { var service = {
perform: open, perform: open,
allowed: allowed, allowed: allowed
init: init
}; };
return service; return service;
//////////// ////////////
function init(_poolId_, _statePromise_) {
poolId = _poolId_;
statePromise = _statePromise_;
return service;
}
function allowed(/*item*/) { function allowed(/*item*/) {
return $q.all([ // This rule is made up and should therefore always pass. At some point there will
statePromise, // likely be a valid rule similar to this that we will want to use.
// This rule is made up and should therefore always pass. At some point there will return policy.ifAllowed({ rules: [['neutron', 'pool_member_update']] });
// likely be a valid rule similar to this that we will want to use.
policy.ifAllowed({ rules: [['neutron', 'pool_member_update']] })
]);
} }
/** /**
@ -97,13 +88,14 @@
*/ */
function open(item) { function open(item) {
member = item;
var spec = { var spec = {
backdrop: 'static', backdrop: 'static',
controller: 'EditMemberModalController as modal', controller: 'EditMemberModalController as modal',
templateUrl: basePath + 'members/actions/edit-member/modal.html', templateUrl: basePath + 'members/actions/edit-member/modal.html',
resolve: { resolve: {
poolId: function() { poolId: function() {
return poolId; return item.poolId;
}, },
member: function() { member: function() {
return item; return item;
@ -115,8 +107,9 @@
function onModalClose() { function onModalClose() {
toastService.add('success', gettext('Pool member has been updated.')); toastService.add('success', gettext('Pool member has been updated.'));
$route.reload(); return actionResultService.getActionResult()
.updated(resourceType, member.id)
.result;
} }
} }
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,7 +19,7 @@
describe('LBaaS v2 Member Edit Service', function() { describe('LBaaS v2 Member Edit Service', function() {
var service, policy, $scope, $route, $uibModal, toast; var service, policy, $scope, $route, $uibModal, toast;
var member = { id: 'member1' }; var member = { poolId:'pool1', id: 'member1' };
var fakePromise = function(response) { var fakePromise = function(response) {
return { return {
@ -30,14 +31,7 @@
function allowed(item) { function allowed(item) {
spyOn(policy, 'ifAllowed').and.returnValue(true); spyOn(policy, 'ifAllowed').and.returnValue(true);
var promise = service.allowed(item); var allowed = service.allowed(item);
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
$scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'pool_member_update']]}); expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'pool_member_update']]});
return allowed; return allowed;
} }
@ -52,7 +46,7 @@
$provide.value('$uibModal', { $provide.value('$uibModal', {
open: function() { open: function() {
return { return {
result: fakePromise() result: fakePromise({config: {data: {member: {id: 1}}}})
}; };
} }
}); });
@ -65,8 +59,7 @@
$route = $injector.get('$route'); $route = $injector.get('$route');
$uibModal = $injector.get('$uibModal'); $uibModal = $injector.get('$uibModal');
service = $injector.get( service = $injector.get(
'horizon.dashboard.project.lbaasv2.members.actions.edit-member.modal.service'); 'horizon.dashboard.project.lbaasv2.members.actions.edit-member');
service.init('pool1', fakePromise());
})); }));
it('should have the "allowed" and "perform" functions', function() { it('should have the "allowed" and "perform" functions', function() {
@ -96,13 +89,11 @@
expect(resolve.member()).toBe(member); expect(resolve.member()).toBe(member);
}); });
it('should show message and reload page upon closing modal', function() { it('should show message upon closing modal', function() {
spyOn(toast, 'add'); spyOn(toast, 'add');
spyOn($route, 'reload'); spyOn($route, 'reload');
service.perform(member); service.perform(member);
$scope.$apply();
expect(toast.add).toHaveBeenCalledWith('success', 'Pool member has been updated.'); expect(toast.add).toHaveBeenCalledWith('success', 'Pool member has been updated.');
expect($route.reload).toHaveBeenCalled();
}); });
}); });

View File

@ -1,79 +0,0 @@
/*
* 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.members')
.factory('horizon.dashboard.project.lbaasv2.members.actions.rowActions', rowActions);
rowActions.$inject = [
'horizon.framework.util.i18n.gettext',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.members.actions.delete',
'horizon.dashboard.project.lbaasv2.members.actions.edit-member.modal.service'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.members.actions.rowActions
*
* @description
* Provides the service for the pool members row actions.
*
* @param gettext The horizon gettext function for translation.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param editMember The LBaaS v2 pool member edit service.
* @returns Members row actions service object.
*/
function rowActions(gettext, loadBalancersService, deleteService, editMember) {
var loadBalancerIsActionable, loadbalancerId, listenerId, poolId;
var service = {
actions: actions,
init: init
};
return service;
///////////////
function init(_loadbalancerId_, _listenerId_, _poolId_) {
loadbalancerId = _loadbalancerId_;
listenerId = _listenerId_;
poolId = _poolId_;
loadBalancerIsActionable = loadBalancersService.isActionable(loadbalancerId);
return service;
}
function actions() {
return [{
service: editMember.init(poolId, loadBalancerIsActionable),
template: {
text: gettext('Edit')
}
},{
service: deleteService.init(loadbalancerId, listenerId, poolId, loadBalancerIsActionable),
template: {
text: gettext('Delete Member'),
type: 'delete'
}
}];
}
}
})();

View File

@ -1,51 +0,0 @@
/*
* 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';
describe('LBaaS v2 Members Row Actions Service', function() {
var actions;
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function ($injector) {
var rowActionsService = $injector.get(
'horizon.dashboard.project.lbaasv2.members.actions.rowActions');
actions = rowActionsService.init('1', '2', '3').actions();
var loadbalancerService = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.service');
spyOn(loadbalancerService, 'isActionable').and.returnValue(true);
}));
it('should define correct table row actions', function() {
expect(actions.length).toBe(2);
expect(actions[0].template.text).toBe('Edit');
expect(actions[1].template.text).toBe('Delete Member');
});
it('should have the "allowed" and "perform" functions', function() {
actions.forEach(function(action) {
expect(action.service.allowed).toBeDefined();
expect(action.service.perform).toBeDefined();
});
});
});
})();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,8 +23,8 @@
updateMemberListService); updateMemberListService);
updateMemberListService.$inject = [ updateMemberListService.$inject = [
'$q', 'horizon.dashboard.project.lbaasv2.members.resourceType',
'$route', 'horizon.framework.util.actions.action-result.service',
'horizon.dashboard.project.lbaasv2.workflow.modal', 'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext' 'horizon.framework.util.i18n.gettext'
@ -31,53 +32,38 @@
/** /**
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.listeners.actions.updateMemberListService * @name horizon.dashboard.project.lbaasv2.members.actions.updateMemberListService
*
* @description * @description
* Provides the service for updating the list of pool members. * Provides the service for updating the list of pool members.
* @param $q The angular service for promises. *
* @param $route The angular $route service. * @param resourceType The member resource type.
* @param actionResultService The horizon action result service.
* @param workflowModal The LBaaS workflow modal service. * @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
*
* @returns The load balancers members update member list service. * @returns The load balancers members update member list service.
*/ */
function updateMemberListService( function updateMemberListService(
$q, $route, workflowModal, policy, gettext resourceType, actionResultService, workflowModal, policy, gettext
) { ) {
var statePromise; return workflowModal.init({
var updateList = workflowModal.init({
controller: 'UpdateMemberListWizardController', controller: 'UpdateMemberListWizardController',
message: gettext('The pool members have been updated.'), message: gettext('The pool members have been updated.'),
handle: onUpdate, handle: handle,
allowed: allowed allowed: allowed
}); });
var service = {
init: init,
update: updateList
};
return service;
//////////////
function init(_statePromise_) {
statePromise = _statePromise_;
return service;
}
function allowed(/*item*/) { function allowed(/*item*/) {
return $q.all([ return policy.ifAllowed({ rules: [['neutron', 'update_member_list']] });
statePromise,
policy.ifAllowed({ rules: [['neutron', 'update_member_list']] })
]);
} }
function onUpdate(/*response*/) { function handle(response) {
$route.reload(); return actionResultService.getActionResult()
.created(resourceType, response.data.id)
.result;
} }
} }
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,85 +18,40 @@
'use strict'; 'use strict';
describe('LBaaS v2 Update Member List Action Service', function() { describe('LBaaS v2 Update Member List Action Service', function() {
var scope, $q, $route, policy, init, updateMemberListService, defer; var policy, service;
function allowed() {
spyOn(policy, 'ifAllowed').and.returnValue(true);
var promise = updateMemberListService.update.allowed();
var allowed;
promise.then(function() {
allowed = true;
}, function() {
allowed = false;
});
scope.$apply();
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_member_list']]});
return allowed;
}
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2')); beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) { beforeEach(module(function($provide) {
var response = { $provide.value('$modal', {
data: {
id: '9012'
}
};
var modal = {
open: function() { open: function() {
return { return {
result: { result: {
then: function(func) { then: function(func) {
func(response); func({ data: { id: 'listener1' } });
} }
} }
}; };
} }
}; });
$provide.value('$uibModal', modal);
})); }));
beforeEach(inject(function ($injector) { beforeEach(inject(function ($injector) {
scope = $injector.get('$rootScope').$new();
$q = $injector.get('$q');
policy = $injector.get('horizon.app.core.openstack-service-api.policy'); policy = $injector.get('horizon.app.core.openstack-service-api.policy');
$route = $injector.get('$route'); service = $injector.get(
updateMemberListService = $injector.get( 'horizon.dashboard.project.lbaasv2.members.actions.update-member-list'
'horizon.dashboard.project.lbaasv2.members.actions.update-member-list'); );
init = updateMemberListService.init;
defer = $q.defer();
})); }));
it('should define the correct service properties', function() { it('should check policy to allow updating member list', function() {
expect(updateMemberListService.init).toBeDefined(); spyOn(policy, 'ifAllowed').and.returnValue(true);
expect(updateMemberListService.update).toBeDefined(); expect(service.allowed()).toBe(true);
expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_member_list']]});
}); });
it('should have the "allowed" and "perform" functions', function() { it('should handle the action result properly', function() {
expect(updateMemberListService.update.allowed).toBeDefined(); var result = service.handle({data: {id: 1}});
expect(updateMemberListService.update.perform).toBeDefined(); expect(result.created[0].id).toBe(1);
});
it('should allow editing a pool under an ACTIVE load balancer', function() {
defer.resolve();
init(defer.promise);
expect(allowed()).toBe(true);
});
it('should not allow editing a pool under an NON-ACTIVE load balancer', function() {
defer.reject();
init(defer.promise);
expect(allowed()).toBe(false);
});
it('should redirect after edit', function() {
spyOn($route, 'reload').and.callThrough();
updateMemberListService.update.perform();
expect($route.reload).toHaveBeenCalled();
}); });
}); });

View File

@ -1,108 +0,0 @@
/*
* 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.members')
.controller('MemberDetailController', MemberDetailController);
MemberDetailController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.members.actions.rowActions',
'$routeParams',
'$q',
'horizon.dashboard.project.lbaasv2.loadbalancers.service'
];
/**
* @ngdoc controller
* @name MemberDetailController
*
* @description
* Controller for the LBaaS v2 member detail page.
*
* @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.
* @returns undefined
*/
function MemberDetailController(
api, rowActions, $routeParams, $q, loadBalancersService
) {
var ctrl = this;
ctrl.loading = true;
ctrl.error = false;
ctrl.actions = rowActions.init($routeParams.loadbalancerId,
$routeParams.listenerId, $routeParams.poolId).actions;
ctrl.loadbalancerId = $routeParams.loadbalancerId;
ctrl.listenerId = $routeParams.listenerId;
ctrl.poolId = $routeParams.poolId;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
init();
////////////////////////////////
function init() {
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 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

@ -1,124 +0,0 @@
/*
* 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';
describe('LBaaS v2 Member Detail Controller', function() {
var $controller, lbaasv2API, apiFail, qAllFail, actions;
function fakePromise(data, reject) {
return {
then: function(success, fail) {
if (reject) {
fail();
} else {
success({ data: data });
}
return fakePromise();
}
};
}
function fakeAPI() {
return fakePromise('foo', apiFail);
}
function loadbalancerAPI() {
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'
}
});
}
///////////////////////
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.framework.conf'));
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 });
$provide.value('$uibModal', {});
$provide.value('horizon.dashboard.project.lbaasv2.members.actions.rowActions', {
init: function() {
return {
actions: 'member-actions'
};
}
});
}));
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
actions = $injector.get('horizon.dashboard.project.lbaasv2.members.actions.rowActions');
spyOn(lbaasv2API, 'getMember').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getPool').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getListener').and.callFake(fakeAPI);
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(loadbalancerAPI);
spyOn(actions, 'init').and.callThrough();
$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');
expect(ctrl.loadbalancerId).toBeDefined();
expect(ctrl.listenerId).toBeDefined();
expect(ctrl.poolId).toBeDefined();
expect(ctrl.operatingStatus).toBeDefined();
expect(ctrl.provisioningStatus).toBeDefined();
expect(ctrl.actions).toBe('member-actions');
expect(actions.init).toHaveBeenCalledWith('loadbalancerId', 'listenerId', 'poolId');
});
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,48 +0,0 @@
<div ng-controller="MemberDetailController as ctrl">
<detail-status loading="ctrl.loading" error="ctrl.error"></detail-status>
<div ng-if="!ctrl.loading && !ctrl.error">
<div class="page-header">
<actions allowed="ctrl.actions" type="row" item="ctrl.member"
ng-if="ctrl.member" class="actions_column pull-right"></actions>
<ol class="breadcrumb">
<li><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li><a href="project/load_balancer/{$ ::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>
</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>Admin State Up</dt>
<dd>{$ ctrl.member.admin_state_up | yesno $}</dd>
<dt translate>Member ID</dt>
<dd>{$ ::ctrl.member.id $}</dd>
<dt translate>Operating Status</dt>
<dd>{$ ctrl.member.operating_status | decode:ctrl.operatingStatus $}</dd>
<dt translate>Project ID</dt>
<dd>{$ ::ctrl.member.project_id $}</dd>
<dt translate>Protocol Port</dt>
<dd>{$ ::ctrl.member.protocol_port $}</dd>
<dt translate>Provisioning Status</dt>
<dd>{$ ctrl.member.provisioning_status | decode:ctrl.provisioningStatus $}</dd>
<dt translate>Subnet ID</dt>
<dd>{$ ctrl.member.subnet_id $}</dd>
<dt translate>Weight</dt>
<dd>{$ ctrl.member.weight $}</dd>
<dt translate>Monitor Address</dt>
<dd>{$ ::ctrl.member.monitor_address | noValue:('None' | translate) $}</dd>
<dt translate>Monitor Port</dt>
<dd>{$ ::ctrl.member.monitor_port | noValue:('None' | translate) $}</dd>
<dt translate>Created At</dt>
<dd>{$ ::ctrl.member.created_at $}</dd>
<dt translate>Updated At</dt>
<dd>{$ ::ctrl.member.updated_at $}</dd>
</dl>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,108 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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.members')
.controller('MemberDetailController', MemberDetailController);
MemberDetailController.$inject = [
'loadbalancer',
'listener',
'pool',
'member',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.members.resourceType',
'horizon.framework.conf.resource-type-registry.service',
'horizon.framework.widgets.modal-wait-spinner.service',
'$q'
];
/**
* @ngdoc controller
* @name MemberDetailController
*
* @description
* Controller for the LBaaS v2 member detail page.
*
* @param loadbalancer The loadbalancer object.
* @param listener The listener object.
* @param pool The pool object.
* @param member The member object.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @param resourceType The member resource type.
* @param typeRegistry The horizon resource type registry service.
* @param spinnerService The horizon modal wait spinner service.
* @param $q The angular service for promises.
*
* @returns undefined
*/
function MemberDetailController(
loadbalancer, listener, pool, member, loadBalancersService,
resourceType, typeRegistry, spinnerService, $q
) {
var ctrl = this;
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
ctrl.loadbalancer = loadbalancer;
ctrl.listener = listener;
ctrl.pool = pool;
ctrl.member = member;
ctrl.resourceType = typeRegistry.getResourceType(resourceType);
ctrl.context = {};
ctrl.context.memberId = member.id;
ctrl.context.poolId = pool.id;
ctrl.resultHandler = actionResultHandler;
function actionResultHandler(returnValue) {
return $q.when(returnValue, actionSuccessHandler);
}
function loadData(response) {
spinnerService.hideModalSpinner();
ctrl.showDetails = true;
ctrl.resourceType.initActions();
ctrl.member = response.data;
ctrl.member.loadbalancerId = ctrl.loadbalancer.id;
ctrl.member.listenerId = ctrl.listener.id;
ctrl.member.poolId = ctrl.pool.id;
}
function actionSuccessHandler(result) {
// The action has completed (for whatever "complete" means to that
// action. Notice the view doesn't really need to know the semantics of the
// particular action because the actions return data in a standard form.
// That return includes the id and type of each created, updated, deleted
// and failed item.
// Currently just refreshes the display each time.
if (result) {
spinnerService.showModalSpinner(gettext('Please Wait'));
ctrl.showDetails = false;
ctrl.context.loadPromise = ctrl.resourceType.load(
ctrl.context.poolId,
ctrl.context.memberId
);
ctrl.context.loadPromise.then(loadData);
}
}
}
})();

View File

@ -0,0 +1,105 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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';
describe('LBaaS v2 Member Detail Controller', function() {
var deferred, service, ctrl, scope, $timeout, $q, actionResultService;
///////////////////////
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(inject(function($controller, $rootScope, _$q_, _$timeout_) {
$q = _$q_;
deferred = $q.defer();
service = {
getResourceType: function() {
return {
load: function() { return deferred.promise; },
parsePath: function() { return 'my-context'; },
itemName: function() { return 'A name'; },
initActions: angular.noop
};
},
getDefaultDetailsTemplateUrl: angular.noop
};
actionResultService = {
getIdsOfType: function() { return []; }
};
$timeout = _$timeout_;
scope = $rootScope.$new();
ctrl = $controller('MemberDetailController', {
$scope: scope,
loadbalancer: { id: '123' },
listener: { id: '123' },
pool: { id: '123' },
member: { id: '123' },
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
}
});
}));
it('should create a controller', function() {
expect(ctrl).toBeDefined();
expect(ctrl.loadbalancer).toBeDefined();
expect(ctrl.listener).toBeDefined();
expect(ctrl.pool).toBeDefined();
expect(ctrl.member).toBeDefined();
});
describe('resultHandler', function() {
it('handles empty results', function() {
var result = $q.defer();
result.resolve({});
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles falsy results', function() {
var result = $q.defer();
result.resolve(false);
ctrl.resultHandler(result.promise);
$timeout.flush();
expect(ctrl.showDetails).not.toBe(true);
});
it('handles matched results', function() {
spyOn(actionResultService, 'getIdsOfType').and.returnValue([1, 2, 3]);
var result = $q.defer();
result.resolve({some: 'thing'});
ctrl.resultHandler(result.promise);
deferred.resolve({data: {some: 'data'}});
$timeout.flush();
expect(ctrl.showDetails).toBe(true);
});
});
});
})();

View File

@ -0,0 +1,55 @@
<div class="page-header">
<ol class="breadcrumb">
<li><a href="project/load_balancer/"><translate>Load Balancers</translate></a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}">{$ ::(ctrl.loadbalancer.name || ctrl.loadbalancer.id) $}</a></li>
<li><a href="project/load_balancer/{$ ::ctrl.loadbalancer.id $}/listeners/{$ ::ctrl.listener.id $}">{$ ::(ctrl.listener.name || ctrl.listener.id) $}</a></li>
<li><a href="project/load_balancer/{$ ::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>
</ol>
<div class="row">
<div class="col-xs-12 col-sm-9 text-left">
<ul class="list-inline">
<li>
<strong translate>IP Address</strong>
{$ ::ctrl.member.address $}
</li>
<li>
<strong translate>Port</strong>
{$ ::ctrl.member.protocol_port $}
</li>
<li>
<strong translate>Operating Status</strong>
{$ ctrl.member.operating_status | decode:ctrl.operatingStatus $}
</li>
<li>
<strong translate>Provisioning Status</strong>
{$ ctrl.member.provisioning_status | decode:ctrl.provisioningStatus $}
</li>
<li>
<strong translate>Admin State Up</strong>
{$ ctrl.member.admin_state_up | yesno $}
</li>
</ul>
</div>
<div class="col-xs-12 col-sm-3 text-right details-item-actions">
<actions allowed="ctrl.resourceType.itemActions"
type="row"
item="ctrl.member"
class="actions_column pull-right"
result-handler="ctrl.resultHandler"></actions>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 detail">
<hz-resource-property-list
resource-type-name="OS::Octavia::Member"
cls="dl-horizontal"
item="ctrl.member"
property-groups="[[
'id', 'name', 'project_id', 'created_at', 'updated_at',
'weight', 'monitor_address', 'monitor_port']]">
</hz-resource-property-list>
</div>
</div>

View File

@ -0,0 +1,9 @@
<hz-resource-property-list
resource-type-name="OS::Octavia::Member"
item="item"
property-groups="[
['name', 'id', 'project_id'],
['created_at', 'updated_at'],
['address', 'protocol_port', 'weight'],
['subnet_id', 'monitor_address', 'monitor_port']]">
</hz-resource-property-list>

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,6 +27,149 @@
*/ */
angular angular
.module('horizon.dashboard.project.lbaasv2.members', []); .module('horizon.dashboard.project.lbaasv2.members', [])
.constant('horizon.dashboard.project.lbaasv2.members.resourceType',
'OS::Octavia::Member')
.run(run);
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.dashboard.project.lbaasv2.loadbalancers.service',
'horizon.dashboard.project.lbaasv2.members.actions.update-member-list',
'horizon.dashboard.project.lbaasv2.members.actions.edit-member',
'horizon.dashboard.project.lbaasv2.members.actions.delete',
'horizon.dashboard.project.lbaasv2.members.resourceType'
];
function run(
registry,
basePath,
loadBalancerService,
createService,
editService,
deleteService,
resourceType
) {
var memberResourceType = registry.getResourceType(resourceType);
memberResourceType
.setNames(gettext('Member'), gettext('Members'))
.setSummaryTemplateUrl(basePath + 'members/details/drawer.html')
.setProperties(memberProperties(loadBalancerService))
.setListFunction(loadBalancerService.getMembersPromise)
.setLoadFunction(loadBalancerService.getMemberPromise)
.tableColumns
.append({
id: 'name',
priority: 1,
sortDefault: true,
urlFunction: loadBalancerService.getMemberDetailsPath
})
.append({
id: 'address',
priority: 1
})
.append({
id: 'protocol_port',
priority: 1
})
.append({
id: 'weight',
priority: 1
})
.append({
id: 'operating_status',
priority: 1
})
.append({
id: 'provisioning_status',
priority: 1
})
.append({
id: 'admin_state_up',
priority: 1
});
memberResourceType.itemActions
.append({
id: 'memberEdit',
service: editService,
template: {
text: gettext('Edit Member')
}
})
.append({
id: 'memberDelete',
service: deleteService,
template: {
text: gettext('Delete Member'),
type: 'delete'
}
});
memberResourceType.globalActions
.append({
id: 'memberCreate',
service: createService,
template: {
type: 'create',
text: gettext('Add/Remove Members')
}
});
memberResourceType.batchActions
.append({
id: 'memberBatchDelete',
service: deleteService,
template: {
text: gettext('Delete Members'),
type: 'delete-selected'
}
});
}
function memberProperties(loadBalancerService) {
return {
id: gettext('ID'),
name: {
label: gettext('Name'),
filters: ['noName']
},
provisioning_status: {
label: gettext('Provisioning Status'),
values: loadBalancerService.provisioningStatus
},
operating_status: {
label: gettext('Operating Status'),
values: loadBalancerService.operatingStatus
},
admin_state_up: {
label: gettext('Admin State Up'),
filters: ['yesno']
},
address: gettext('IP Address'),
protocol_port: gettext('Port'),
weight: gettext('Weight'),
subnet_id: gettext('Subnet ID'),
project_id: gettext('Project ID'),
created_at: {
label: gettext('Created At'),
filters: ['noValue']
},
updated_at: {
label: gettext('Updated At'),
filters: ['noValue']
},
monitor_address: {
label: gettext('Monitor Address'),
filters: ['noName']
},
monitor_port: {
label: gettext('Monitor Port'),
filters: ['noName']
}
};
}
})(); })();

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,4 +23,46 @@
}); });
}); });
describe('LBaaS v2 Members Registry', function () {
var registry, resourceType;
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function($injector) {
resourceType = $injector.get('horizon.dashboard.project.lbaasv2.members.resourceType');
registry = $injector.get('horizon.framework.conf.resource-type-registry.service');
}));
it('should define resourceType', function () {
expect(resourceType).toBeDefined();
});
it('should register item actions', function () {
var actions = registry.getResourceType(resourceType).itemActions;
expect(actionHasId(actions, 'memberEdit')).toBe(true);
expect(actionHasId(actions, 'memberDelete')).toBe(true);
});
it('should register global actions', function () {
var actions = registry.getResourceType(resourceType).globalActions;
expect(actionHasId(actions, 'memberCreate')).toBe(true);
});
it('should register batch actions', function () {
var actions = registry.getResourceType(resourceType).batchActions;
expect(actionHasId(actions, 'memberBatchDelete')).toBe(true);
});
function actionHasId(list, value) {
return list.filter(matchesId).length === 1;
function matchesId(action) {
if (action.id === value) {
return true;
}
}
}
});
})(); })();

View File

@ -1,87 +0,0 @@
/*
* 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.members')
.controller('MembersTableController', MembersTableController);
MembersTableController.$inject = [
'horizon.app.core.openstack-service-api.lbaasv2',
'horizon.dashboard.project.lbaasv2.members.actions.rowActions',
'horizon.dashboard.project.lbaasv2.members.actions.batchActions',
'$routeParams',
'horizon.dashboard.project.lbaasv2.loadbalancers.service'
];
/**
* @ngdoc controller
* @name MembersTableController
*
* @description
* Controller for the LBaaS v2 members table. Serves as the focal point for table actions.
*
* @param api The LBaaS V2 service API.
* @param rowActions The pool members row actions service.
* @param batchActions The members batch actions service.
* @param $routeParams The angular $routeParams service.
* @param loadBalancersService The LBaaS v2 load balancers service.
* @returns undefined
*/
function MembersTableController(
api, rowActions, batchActions, $routeParams, loadBalancersService
) {
var ctrl = this;
ctrl.items = [];
ctrl.src = [];
ctrl.loading = true;
ctrl.error = false;
ctrl.checked = {};
ctrl.loadbalancerId = $routeParams.loadbalancerId;
ctrl.listenerId = $routeParams.listenerId;
ctrl.poolId = $routeParams.poolId;
ctrl.rowActions = rowActions.init(ctrl.loadbalancerId, ctrl.listenerId, ctrl.poolId);
ctrl.batchActions = batchActions.init(ctrl.loadbalancerId);
ctrl.operatingStatus = loadBalancersService.operatingStatus;
ctrl.provisioningStatus = loadBalancersService.provisioningStatus;
init();
////////////////////////////////
function init() {
ctrl.src = [];
ctrl.loading = true;
ctrl.error = false;
api.getMembers(ctrl.poolId).then(success, fail);
}
function success(response) {
ctrl.src = response.data.items;
ctrl.loading = false;
}
function fail(/*response*/) {
ctrl.src = [];
ctrl.loading = false;
ctrl.error = true;
}
}
})();

View File

@ -1,103 +0,0 @@
/*
* Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
*
* 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';
describe('LBaaS v2 Members Table Controller', function() {
var controller, lbaasv2API, scope, actions;
var items = [{ foo: 'bar' }];
var apiFail = false;
function fakeAPI() {
return {
then: function(success, fail) {
if (apiFail && fail) {
fail();
} else {
success({ data: { items: items } });
}
}
};
}
///////////////////////
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
$provide.value('horizon.dashboard.project.lbaasv2.members.actions.rowActions', {
init: function() {
return {
actions: 'member-actions'
};
}
});
}));
beforeEach(inject(function($injector) {
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
actions = $injector.get('horizon.dashboard.project.lbaasv2.members.actions.rowActions');
controller = $injector.get('$controller');
spyOn(lbaasv2API, 'getMembers').and.callFake(fakeAPI);
spyOn(actions, 'init').and.callThrough();
}));
function createController() {
return controller('MembersTableController', {
$scope: scope,
$routeParams: {
loadbalancerId: 'loadbalancerId',
listenerId: 'listenerId',
poolId: 'poolId'
}});
}
it('should initialize the controller properties correctly', function() {
var ctrl = createController();
expect(ctrl.items).toEqual([]);
expect(ctrl.src).toEqual(items);
expect(ctrl.loading).toBe(false);
expect(ctrl.error).toBe(false);
expect(ctrl.checked).toEqual({});
expect(ctrl.loadbalancerId).toBeDefined();
expect(ctrl.listenerId).toBeDefined();
expect(ctrl.poolId).toBeDefined();
expect(ctrl.rowActions).toBeDefined();
expect(ctrl.batchActions).toBeDefined();
expect(ctrl.operatingStatus).toBeDefined();
expect(ctrl.provisioningStatus).toBeDefined();
});
it('should invoke lbaasv2 apis', function() {
var ctrl = createController();
expect(lbaasv2API.getMembers).toHaveBeenCalled();
expect(ctrl.src.length).toBe(1);
});
it('should show error if loading fails', function() {
apiFail = true;
var ctrl = createController();
expect(ctrl.src.length).toBe(0);
expect(ctrl.error).toBe(true);
});
});
})();

View File

@ -1,120 +0,0 @@
<table ng-controller="MembersTableController as table"
hz-table ng-cloak
st-table="table.items"
st-safe-src="table.src"
default-sort="id"
default-sort-reverse="false"
class="table table-striped table-rsp table-detail">
<!--
TODO(jpomero): This table pattern does not allow for extensibility and should be revisited
once horizon implements a better one.
-->
<thead>
<tr>
<!--
Table-batch-actions:
This is where batch actions like searching, creating, and deleting.
-->
<th colspan="6" class="search-header">
<hz-search-bar icon-classes="fa-search">
<actions allowed="table.batchActions.actions" type="batch"></actions>
</hz-search-bar>
</th>
</tr>
<tr>
<!--
Table-column-headers:
This is where we declaratively define the table column headers.
Include select-col if you want to select all.
Include expander if you want to inline details.
Include action-col if you want to perform actions.
-->
<th class="multi_select_column">
<input type="checkbox" hz-select-all="table.items">
</th>
<th class="expander"></th>
<th class="rsp-p1" st-sort="id" st-sort-default="id" translate>ID</th>
<th class="rsp-p1" st-sort="address" translate>IP Address</th>
<th class="rsp-p1" st-sort="protocol_port" translate>Protocol Port</th>
<th class="rsp-p1" st-sort="protocol_port" translate>Operating Status</th>
<th class="rsp-p1" st-sort="protocol_port" translate>Provisioning Status</th>
<th class="rsp-p1" st-sort="weight" translate>Weight</th>
<th class="actions_column" translate>Actions</th>
</tr>
</thead>
<tbody>
<!--
Table-rows:
This is where we declaratively define the table columns.
Include select-col if you want to select all.
Include expander if you want to inline details.
Include action-col if you want to perform actions.
rsp-p1 rsp-p2 are responsive priority as user resizes window.
-->
<tr ng-repeat-start="item in table.items track by item.id"
ng-class="{'st-selected': checked[item.id]}">
<td class="multi_select_column">
<input type="checkbox"
ng-model="tCtrl.selections[item.id].checked"
hz-select="item">
</td>
<td class="expander">
<span class="fa fa-chevron-right"
hz-expand-detail
duration="200">
</span>
</td>
<td class="rsp-p1"><a ng-href="project/load_balancer/{$ ::table.loadbalancerId $}/listeners/{$ ::table.listenerId $}/pools/{$ ::table.poolId $}/members/{$ ::item.id $}">{$ ::item.id $}</a></td>
<td class="rsp-p1">{$ ::item.address $}</td>
<td class="rsp-p1">{$ ::item.protocol_port $}</td>
<td class="rsp-p1">{$ ::item.operating_status | decode:table.operatingStatus $}</td>
<td class="rsp-p1">{$ ::item.provisioning_status | decode:table.provisioningStatus $}</td>
<td class="rsp-p1">{$ item.weight $}</td>
<td class="actions_column">
<!--
Table-row-action-column:
Actions taken here apply to a single item/row.
-->
<actions allowed="table.rowActions.actions" type="row" item="item"></actions>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<!--
Detail-row:
Contains detailed information on this item.
Can be toggled using the chevron button.
Ensure colspan is greater or equal to number of column-headers.
-->
<td class="detail" colspan="9">
<div class="row">
<dl class="col-sm-2">
<dt translate>Monitor Address</dt>
<dd>{$ ::item.monitor_address | noValue:('None' | translate) $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Monitor Port</dt>
<dd>{$ ::item.monitor_port | noValue:('None' | translate) $}</dd>
</dl>
</div>
</td>
</tr>
<tr table-status table="table" column-count="9"></tr>
</tbody>
<!--
Table-footer:
This is where we display number of items and pagination controls.
-->
<tfoot hz-table-footer items="table.items"></tfoot>
</table>

View File

@ -1,5 +1,6 @@
/* /*
* Copyright 2016 IBM Corp. * Copyright 2016 IBM Corp.
* Copyright 2017 Walmart.
* *
* Licensed under the Apache License, Version 2.0 (the 'License'); * Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,69 +22,59 @@
.factory('horizon.dashboard.project.lbaasv2.pools.actions.create', createService); .factory('horizon.dashboard.project.lbaasv2.pools.actions.create', createService);
createService.$inject = [ createService.$inject = [
'horizon.dashboard.project.lbaasv2.pools.resourceType',
'horizon.framework.util.actions.action-result.service',
'$q', '$q',
'$location',
'horizon.dashboard.project.lbaasv2.workflow.modal', 'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.app.core.openstack-service-api.policy', 'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext',
'horizon.framework.util.q.extensions', 'horizon.framework.util.q.extensions',
'horizon.framework.util.i18n.gettext' '$routeParams'
]; ];
/** /**
* @ngDoc factory * @ngDoc factory
* @name horizon.dashboard.project.lbaasv2.listeners.actions.createService * @name horizon.dashboard.project.lbaasv2.pools.actions.createService
*
* @description * @description
* Provides the service for creating a pool resource. * Provides the service for creating a pool resource.
*
* @param resourceType The pool resource type.
* @param actionResultService The horizon action result service.
* @param $q The angular service for promises. * @param $q The angular service for promises.
* @param $location The angular $location service.
* @param workflowModal The LBaaS workflow modal service. * @param workflowModal The LBaaS workflow modal service.
* @param policy The horizon policy service. * @param policy The horizon policy service.
* @param qExtensions Horizon extensions to the $q service.
* @param gettext The horizon gettext function for translation. * @param gettext The horizon gettext function for translation.
* @returns The load balancers pool create service. * @param qExtensions horizon extensions to the $q service.
* @param $routeParams The angular $routeParams service.
*
* @returns The pool create service.
*/ */
function createService( function createService(
$q, $location, workflowModal, policy, qExtensions, gettext resourceType, actionResultService,
$q, workflowModal, policy, gettext, qExtensions, $routeParams
) { ) {
var loadbalancerId, listenerId, statePromise; return workflowModal.init({
var create = workflowModal.init({
controller: 'CreatePoolWizardController', controller: 'CreatePoolWizardController',
message: gettext('A new pool is being created.'), message: gettext('A new pool is being created.'),
handle: onCreate, handle: handle,
allowed: allowed allowed: allowed
}); });
var service = {
init: init,
create: create
};
return service;
////////////// //////////////
function init(_loadbalancerId_, _statePromise_) { function allowed() {
loadbalancerId = _loadbalancerId_;
statePromise = _statePromise_;
return service;
}
function allowed(item) {
listenerId = item.id;
return $q.all([ return $q.all([
statePromise, qExtensions.booleanAsPromise(!!$routeParams.listenerId),
qExtensions.booleanAsPromise(!item.default_pool_id),
policy.ifAllowed({ rules: [['neutron', 'create_pool']] }) policy.ifAllowed({ rules: [['neutron', 'create_pool']] })
]); ]);
} }
function onCreate(response) { function handle(response) {
var poolId = response.data.id; return actionResultService.getActionResult()
$location.path('project/load_balancer/' + loadbalancerId + '/listeners/' + .created(resourceType, response.data.id)
listenerId + '/pools/' + poolId); .result;
} }
} }
})(); })();

Some files were not shown because too many files have changed in this diff Show More