Add members tab to create load balancer workflow
This adds the members tab to the create load balancer workflow and allows adding multiple members (nova servers) to the pool. Partially-Implements: blueprint horizon-lbaas-v2-ui Change-Id: I469dd9ff4d9aef9b91316699d1edec278957f091
This commit is contained in:
parent
32166ee5a8
commit
6c4828d8f6
|
@ -97,9 +97,45 @@ def create_pool(request, **kwargs):
|
|||
poolSpec['name'] = data['pool']['name']
|
||||
if data['pool'].get('description'):
|
||||
poolSpec['description'] = data['pool']['description']
|
||||
return neutronclient(request).create_lbaas_pool(
|
||||
pool = neutronclient(request).create_lbaas_pool(
|
||||
{'pool': poolSpec}).get('pool')
|
||||
|
||||
if data.get('members'):
|
||||
args = (request, kwargs['loadbalancer_id'], add_member)
|
||||
kwargs = {'callback_kwargs': {'pool_id': pool['id'],
|
||||
'index': 0}}
|
||||
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
|
||||
|
||||
return pool
|
||||
|
||||
|
||||
def add_member(request, **kwargs):
|
||||
"""Add a member to a pool.
|
||||
|
||||
"""
|
||||
data = request.DATA
|
||||
members = data['members']
|
||||
index = kwargs['index']
|
||||
member = members[index]
|
||||
memberSpec = {
|
||||
'address': member['address'],
|
||||
'protocol_port': member['port'],
|
||||
'subnet_id': member['subnet']
|
||||
}
|
||||
if member.get('weight'):
|
||||
memberSpec['weight'] = member['weight']
|
||||
member = neutronclient(request).create_lbaas_member(
|
||||
kwargs['pool_id'], {'member': memberSpec}).get('member')
|
||||
|
||||
index += 1
|
||||
if len(members) > index:
|
||||
args = (request, kwargs['loadbalancer_id'], add_member)
|
||||
kwargs = {'callback_kwargs': {'pool_id': kwargs['pool_id'],
|
||||
'index': index}}
|
||||
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
|
||||
|
||||
return member
|
||||
|
||||
|
||||
@urls.register
|
||||
class LoadBalancers(generic.View):
|
||||
|
|
|
@ -47,7 +47,11 @@ ADD_JS_FILES = [
|
|||
('dashboard/project/lbaasv2/loadbalancers/actions/create/details/'
|
||||
'details.controller.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/listener/'
|
||||
'listener.controller.js')
|
||||
'listener.controller.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/pool/'
|
||||
'pool.controller.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/members/'
|
||||
'members.controller.js')
|
||||
]
|
||||
|
||||
ADD_JS_SPEC_FILES = [
|
||||
|
@ -70,7 +74,11 @@ ADD_JS_SPEC_FILES = [
|
|||
('dashboard/project/lbaasv2/loadbalancers/actions/create/details/'
|
||||
'details.controller.spec.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/listener/'
|
||||
'listener.controller.spec.js')
|
||||
'listener.controller.spec.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/pool/'
|
||||
'pool.controller.spec.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/members/'
|
||||
'members.controller.spec.js')
|
||||
]
|
||||
|
||||
ADD_SCSS_FILES = [
|
||||
|
|
|
@ -50,7 +50,7 @@ module.exports = function (config) {
|
|||
|
||||
// Sets up module to process templates.
|
||||
ngHtml2JsPreprocessor: {
|
||||
prependPrefix: '/static/',
|
||||
prependPrefix: '/',
|
||||
moduleName: 'templates'
|
||||
},
|
||||
|
||||
|
|
|
@ -33,8 +33,11 @@
|
|||
.constant('horizon.dashboard.project.lbaasv2.patterns', {
|
||||
ipv4: '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$',
|
||||
ipv6: '^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$'
|
||||
});
|
||||
})
|
||||
/* eslint-enable max-len */
|
||||
.constant('horizon.dashboard.project.lbaasv2.popovers', {
|
||||
ipAddresses: '<ul><li ng-repeat="addr in member.addresses">{$ addr.ip $}</li></ul>'
|
||||
});
|
||||
|
||||
config.$inject = [
|
||||
'$provide',
|
||||
|
|
|
@ -45,3 +45,25 @@
|
|||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
transfer-table {
|
||||
.member-weight,
|
||||
.member-port {
|
||||
display: inline-block;
|
||||
width: 6em;
|
||||
}
|
||||
.member-address {
|
||||
width: 20em;
|
||||
}
|
||||
span.invalid {
|
||||
vertical-align: top;
|
||||
margin: 8px 0px 0px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.addresses-popover + .popover {
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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')
|
||||
.controller('AddMembersController', AddMembersController);
|
||||
|
||||
AddMembersController.$inject = [
|
||||
'$scope',
|
||||
'$compile',
|
||||
'horizon.dashboard.project.lbaasv2.popovers',
|
||||
'horizon.framework.util.i18n.gettext'
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name AddMembersController
|
||||
* @description
|
||||
* The `AddMembersController` controller provides functions for adding members to a pool.
|
||||
* @param $scope The angular scope object.
|
||||
* @param $compile The angular compile service.
|
||||
* @param popovers LBaaS v2 popover templates constant.
|
||||
* @param gettext The horizon gettext function for translation.
|
||||
* @returns undefined
|
||||
*/
|
||||
|
||||
function AddMembersController($scope, $compile, popoverTemplates, gettext) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
// Error text for invalid fields
|
||||
ctrl.portError = gettext('The port must be a number between 1 and 65535.');
|
||||
ctrl.weightError = gettext('The weight must be a number between 1 and 256.');
|
||||
|
||||
// Table widget properties
|
||||
ctrl.tableData = {
|
||||
available: $scope.model.members,
|
||||
allocated: $scope.model.spec.members,
|
||||
displayedAvailable: [],
|
||||
displayedAllocated: []
|
||||
};
|
||||
ctrl.tableLimits = {
|
||||
maxAllocation: -1
|
||||
};
|
||||
ctrl.tableHelp = {
|
||||
availHelpText: '',
|
||||
noneAllocText: gettext('Select members from the available members below'),
|
||||
noneAvailText: gettext('No available members')
|
||||
};
|
||||
|
||||
// Functions to control the IP address popover
|
||||
ctrl.showAddressPopover = showAddressPopover;
|
||||
ctrl.hideAddressPopover = hideAddressPopover;
|
||||
ctrl.addressPopoverTarget = addressPopoverTarget;
|
||||
|
||||
//////////
|
||||
|
||||
function showAddressPopover(event, member) {
|
||||
var element = angular.element(event.target);
|
||||
var scope = $scope.$new(true);
|
||||
scope.member = member;
|
||||
element.popover({
|
||||
content: $compile(popoverTemplates.ipAddresses)(scope),
|
||||
html: true,
|
||||
placement: 'top',
|
||||
title: interpolate(gettext('IP Addresses (%(count)s)'),
|
||||
{ count: member.addresses.length }, true)
|
||||
});
|
||||
element.popover('show');
|
||||
}
|
||||
|
||||
function hideAddressPopover(event) {
|
||||
var element = angular.element(event.target);
|
||||
element.popover('hide');
|
||||
}
|
||||
|
||||
function addressPopoverTarget(member) {
|
||||
return interpolate(gettext('%(ip)s...'), { ip: member.address.ip }, true);
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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('Create Load Balancer Add Members Step', function() {
|
||||
var members = [{
|
||||
id: '1',
|
||||
name: 'foo',
|
||||
description: 'bar',
|
||||
weight: 1,
|
||||
port: 80,
|
||||
address: { ip: '1.2.3.4', subnet: '1' },
|
||||
addresses: [{ ip: '1.2.3.4', subnet: '1' },
|
||||
{ ip: '2.3.4.5', subnet: '2' }]
|
||||
}];
|
||||
|
||||
beforeEach(module('horizon.framework.util.i18n'));
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
describe('AddMembersController', function() {
|
||||
var ctrl, scope;
|
||||
|
||||
beforeEach(inject(function($controller) {
|
||||
scope = {
|
||||
model: {
|
||||
spec: {
|
||||
members: []
|
||||
},
|
||||
members: members
|
||||
}
|
||||
};
|
||||
ctrl = $controller('AddMembersController', { $scope: scope });
|
||||
}));
|
||||
|
||||
it('should define error messages for invalid fields', function() {
|
||||
expect(ctrl.portError).toBeDefined();
|
||||
expect(ctrl.weightError).toBeDefined();
|
||||
});
|
||||
|
||||
it('should define transfer table properties', function() {
|
||||
expect(ctrl.tableData).toBeDefined();
|
||||
expect(ctrl.tableLimits).toBeDefined();
|
||||
expect(ctrl.tableHelp).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have available members', function() {
|
||||
expect(ctrl.tableData.available).toBeDefined();
|
||||
expect(ctrl.tableData.available.length).toBe(1);
|
||||
expect(ctrl.tableData.available[0].id).toBe('1');
|
||||
});
|
||||
|
||||
it('should not have allocated members', function() {
|
||||
expect(ctrl.tableData.allocated).toEqual([]);
|
||||
});
|
||||
|
||||
it('should allow adding multiple members', function() {
|
||||
expect(ctrl.tableLimits.maxAllocation).toBe(-1);
|
||||
});
|
||||
|
||||
it('should properly format address popover target', function() {
|
||||
var target = ctrl.addressPopoverTarget(members[0]);
|
||||
expect(target).toBe('1.2.3.4...');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Add Members Template', function() {
|
||||
var $scope, $element, popoverContent;
|
||||
|
||||
beforeEach(module('templates'));
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
var $compile = $injector.get('$compile');
|
||||
var $templateCache = $injector.get('$templateCache');
|
||||
var basePath = $injector.get('horizon.dashboard.project.lbaasv2.basePath');
|
||||
var popoverTemplates = $injector.get('horizon.dashboard.project.lbaasv2.popovers');
|
||||
var markup = $templateCache.get(
|
||||
basePath + 'loadbalancers/actions/create/members/members.html');
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
$scope.model = {
|
||||
spec: {
|
||||
members: []
|
||||
},
|
||||
members: members
|
||||
};
|
||||
$element = $compile(markup)($scope);
|
||||
var popoverScope = $injector.get('$rootScope').$new();
|
||||
popoverScope.member = members[0];
|
||||
popoverContent = $compile(popoverTemplates.ipAddresses)(popoverScope);
|
||||
}));
|
||||
|
||||
it('should show IP addresses popover on hover', function() {
|
||||
var ctrl = $element.scope().ctrl;
|
||||
ctrl.tableData.displayedAvailable = members;
|
||||
$scope.$apply();
|
||||
|
||||
var popoverElement = $element.find('span.addresses-popover');
|
||||
expect(popoverElement.length).toBe(1);
|
||||
|
||||
$.fn.popover = angular.noop;
|
||||
spyOn($.fn, 'popover');
|
||||
spyOn(ctrl, 'showAddressPopover').and.callThrough();
|
||||
popoverElement.trigger('mouseover');
|
||||
|
||||
expect(ctrl.showAddressPopover).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({type: 'mouseover'}), members[0]);
|
||||
expect($.fn.popover.calls.count()).toBe(2);
|
||||
expect($.fn.popover.calls.argsFor(0)[0]).toEqual({
|
||||
content: popoverContent,
|
||||
html: true,
|
||||
placement: 'top',
|
||||
title: 'IP Addresses (2)'
|
||||
});
|
||||
expect($.fn.popover.calls.argsFor(1)[0]).toBe('show');
|
||||
|
||||
spyOn(ctrl, 'hideAddressPopover').and.callThrough();
|
||||
popoverElement.trigger('mouseleave');
|
||||
|
||||
expect(ctrl.hideAddressPopover)
|
||||
.toHaveBeenCalledWith(jasmine.objectContaining({type: 'mouseleave'}));
|
||||
expect($.fn.popover.calls.count()).toBe(3);
|
||||
expect($.fn.popover.calls.argsFor(2)[0]).toBe('hide');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,3 @@
|
|||
<h1 translate>Pool Members Help</h1>
|
||||
|
||||
<p translate>Add members to the load balancer pool. The Available Members table contains existing instances that can be added to the pool.</p>
|
|
@ -0,0 +1,191 @@
|
|||
<div ng-controller="AddMembersController as ctrl">
|
||||
<h1 translate>Pool Members</h1>
|
||||
|
||||
<!--content-->
|
||||
<div class="content">
|
||||
<div translate class="subtitle">Add members to the load balancer pool. All required listener and pool details must also be provided.</div>
|
||||
|
||||
<transfer-table tr-model="ctrl.tableData"
|
||||
limits="::ctrl.tableLimits"
|
||||
help-text="::ctrl.tableHelp">
|
||||
|
||||
<!-- Allocated-->
|
||||
<allocated>
|
||||
<table st-table="ctrl.tableData.displayedAllocated"
|
||||
st-safe-src="ctrl.tableData.allocated" hz-table
|
||||
class="table-striped table-rsp table-detail modern form-group">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="expander"></th>
|
||||
<th class="rsp-p2" translate>Name</th>
|
||||
<th class="rsp-p3" translate>Description</th>
|
||||
<th class="rsp-p1"
|
||||
ng-class="{ 'required': ctrl.tableData.displayedAllocated.length > 0 }">
|
||||
<label translate>IP Address</label>
|
||||
</th>
|
||||
<th class="rsp-p1" translate>Weight</th>
|
||||
<th class="rsp-p1"
|
||||
ng-class="{ 'required': ctrl.tableData.displayedAllocated.length > 0 }">
|
||||
<label translate>Port</label>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-if="ctrl.tableData.allocated.length === 0">
|
||||
<td colspan="0">
|
||||
<div class="no-rows-help">
|
||||
{$ ::trCtrl.helpText.noneAllocText $}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-start="row in ctrl.tableData.displayedAllocated track by row.id">
|
||||
<td class="expander">
|
||||
<i class="fa fa-chevron-right"
|
||||
hz-expand-detail
|
||||
duration="200">
|
||||
</i>
|
||||
</td>
|
||||
<td class="rsp-p2">{$ ::row.name $}</td>
|
||||
<td class="rsp-p3">{$ ::row.description | noValue $}</td>
|
||||
<td class="rsp-p1">
|
||||
<span ng-if="row.addresses.length === 1">{$ row.address.ip $}</span>
|
||||
<div ng-if="row.addresses.length > 1"
|
||||
class="form-field member-address">
|
||||
<select class="form-control input-sm"
|
||||
ng-options="addr.ip for addr in row.addresses"
|
||||
ng-model="row.address" ng-required="true">
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td class="rsp-p1">
|
||||
<div class="form-field member-weight"
|
||||
ng-class="{ 'has-error': createLoadBalancerMembersForm['{$ ::row.id $}-weight'].$invalid && createLoadBalancerMembersForm['{$ ::row.id $}-weight'].$dirty }">
|
||||
<input name="{$ ::row.id $}-weight" type="number" class="form-control input-sm"
|
||||
ng-model="row.weight" ng-pattern="/^\d+$/" min="1" max="256">
|
||||
</div>
|
||||
<span class="fa fa-exclamation-triangle invalid"
|
||||
ng-show="createLoadBalancerMembersForm['{$ ::row.id $}-weight'].$invalid && createLoadBalancerMembersForm.$dirty"
|
||||
popover="{$ ::ctrl.weightError $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
</td>
|
||||
<td class="rsp-p1">
|
||||
<div class="form-field member-port"
|
||||
ng-class="{ 'has-error': createLoadBalancerMembersForm['{$ ::row.id $}-port'].$invalid && createLoadBalancerMembersForm['{$ ::row.id $}-port'].$dirty }">
|
||||
<input name="{$ ::row.id $}-port" type="number" class="form-control input-sm"
|
||||
ng-model="row.port" ng-pattern="/^\d+$/" min="1" max="65535"
|
||||
ng-required="true">
|
||||
</div>
|
||||
<span class="fa fa-exclamation-triangle invalid"
|
||||
ng-show="createLoadBalancerMembersForm['{$ ::row.id $}-port'].$invalid && createLoadBalancerMembersForm.$dirty"
|
||||
popover="{$ ::ctrl.portError $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
</td>
|
||||
<td class="action-col">
|
||||
<action-list>
|
||||
<action action-classes="'btn btn-sm btn-default'"
|
||||
callback="trCtrl.deallocate" item="row">
|
||||
<span class="fa fa-minus"></span>
|
||||
</action>
|
||||
</action-list>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-end class="detail-row">
|
||||
<td class="detail" colspan="0">
|
||||
<div class="row">
|
||||
<dl class="rsp-alt-p2 col-sm-2">
|
||||
<dt translate>Name</dt>
|
||||
<dd>{$ ::row.name $}</dd>
|
||||
</dl>
|
||||
<dl class="rsp-alt-p3 col-sm-2">
|
||||
<dt translate>Description</dt>
|
||||
<dd>{$ ::row.description | noValue $}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</allocated>
|
||||
|
||||
<!-- Available -->
|
||||
<available>
|
||||
<table st-table="ctrl.tableData.displayedAvailable"
|
||||
st-safe-src="ctrl.tableData.available"
|
||||
hz-table class="table-striped table-rsp table-detail modern">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="search-header" colspan="0">
|
||||
<hz-search-bar group-classes="input-group-sm" icon-classes="fa-search">
|
||||
</hz-search-bar>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="expander"></th>
|
||||
<th st-sort="name" st-sort-default class="rsp-p2" translate>Name</th>
|
||||
<th class="rsp-p3" translate>Description</th>
|
||||
<th class="rsp-p1" translate>IP Address</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-if="trCtrl.numAvailable() === 0">
|
||||
<td colspan="0">
|
||||
<div class="no-rows-help">
|
||||
{$ ::trCtrl.helpText.noneAvailText $}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-start="row in ctrl.tableData.displayedAvailable track by row.id"
|
||||
ng-if="!trCtrl.allocatedIds[row.id]">
|
||||
<td class="expander">
|
||||
<i class="fa fa-chevron-right"
|
||||
hz-expand-detail
|
||||
duration="200">
|
||||
</i>
|
||||
</td>
|
||||
<td class="rsp-p2">{$ ::row.name $}</td>
|
||||
<td class="rsp-p3">{$ ::row.description | noValue $}</td>
|
||||
<td class="rsp-p1">
|
||||
<span ng-if="row.addresses.length === 1">{$ row.address.ip $}</span>
|
||||
<!-- The current version of the popover directive doesn't seem to allow HTML content (0.11.0) -->
|
||||
<span ng-if="row.addresses.length > 1"
|
||||
class="addresses-popover"
|
||||
ng-mouseover="ctrl.showAddressPopover($event, row)"
|
||||
ng-mouseleave="ctrl.hideAddressPopover($event)">
|
||||
{$ ctrl.addressPopoverTarget(row) $}
|
||||
</span>
|
||||
</td>
|
||||
<td class="action-col">
|
||||
<action-list>
|
||||
<action action-classes="'btn btn-sm btn-default'"
|
||||
callback="trCtrl.allocate" item="row">
|
||||
<span class="fa fa-plus"></span>
|
||||
</action>
|
||||
</action-list>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-repeat-end class="detail-row">
|
||||
<td class="detail" colspan="0">
|
||||
<div class="row">
|
||||
<dl class="rsp-alt-p2 col-sm-2">
|
||||
<dt translate>Name</dt>
|
||||
<dd>{$ ::row.name $}</dd>
|
||||
</dl>
|
||||
<dl class="rsp-alt-p3 col-sm-2">
|
||||
<dt translate>Description</dt>
|
||||
<dd>{$ ::row.description | noValue $}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</available>
|
||||
|
||||
</transfer-table> <!-- End Transfer Table -->
|
||||
|
||||
</div> <!-- end content -->
|
||||
</div>
|
|
@ -24,7 +24,6 @@
|
|||
modalService.$inject = [
|
||||
'$modal',
|
||||
'$location',
|
||||
'$window',
|
||||
'horizon.framework.widgets.toast.service',
|
||||
'horizon.framework.util.i18n.gettext',
|
||||
'horizon.app.core.openstack-service-api.policy'
|
||||
|
@ -39,14 +38,13 @@
|
|||
*
|
||||
* @param $modal The angular bootstrap $modal service.
|
||||
* @param $location The angular $location service.
|
||||
* @param $window The angular reference to the browser window object.
|
||||
* @param toastService The horizon toast service.
|
||||
* @param gettext The horizon gettext function for translation.
|
||||
* @param policy The horizon policy service.
|
||||
* @returns The modal service for the create load balancer workflow.
|
||||
*/
|
||||
|
||||
function modalService($modal, $location, $window, toastService, gettext, policy) {
|
||||
function modalService($modal, $location, toastService, gettext, policy) {
|
||||
|
||||
var service = {
|
||||
allowed: allowed,
|
||||
|
@ -77,7 +75,7 @@
|
|||
var modal = $modal.open(spec);
|
||||
modal.result.then(function(response) {
|
||||
toastService.add('success', gettext('A new load balancer is being created.'));
|
||||
$location.path($window.WEBROOT + 'project/ngloadbalancersv2/detail/' + response.data.id);
|
||||
$location.path('project/ngloadbalancersv2/detail/' + response.data.id);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
modalService.perform();
|
||||
|
||||
expect(toastService.add).toHaveBeenCalledWith('success', jasmine.any(String));
|
||||
expect($location.path).toHaveBeenCalledWith('/project/ngloadbalancersv2/detail/1');
|
||||
expect($location.path).toHaveBeenCalledWith('project/ngloadbalancersv2/detail/1');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var push = Array.prototype.push;
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.project.lbaasv2.loadbalancers')
|
||||
.factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.model',
|
||||
|
@ -24,6 +26,7 @@
|
|||
createLoadBalancerModel.$inject = [
|
||||
'$q',
|
||||
'horizon.app.core.openstack-service-api.neutron',
|
||||
'horizon.app.core.openstack-service-api.nova',
|
||||
'horizon.app.core.openstack-service-api.lbaasv2',
|
||||
'horizon.framework.util.i18n.gettext'
|
||||
];
|
||||
|
@ -40,13 +43,14 @@
|
|||
*
|
||||
* @param $q The angular service for promises.
|
||||
* @param neutronAPI The neutron service API.
|
||||
* @param novaAPI The nova service API.
|
||||
* @param lbaasv2API The LBaaS V2 service API.
|
||||
* @param gettext The horizon gettext function for translation.
|
||||
* @returns The model service for the create load balancer workflow.
|
||||
*/
|
||||
|
||||
function createLoadBalancerModel($q, neutronAPI, lbaasv2API, gettext) {
|
||||
var initPromise;
|
||||
function createLoadBalancerModel($q, neutronAPI, novaAPI, lbaasv2API, gettext) {
|
||||
var initPromise, ports;
|
||||
|
||||
/**
|
||||
* @ngdoc model api object
|
||||
|
@ -71,6 +75,7 @@
|
|||
spec: null,
|
||||
|
||||
subnets: [],
|
||||
members: [],
|
||||
listenerProtocols: ['TCP', 'HTTP', 'HTTPS'],
|
||||
poolProtocols: ['TCP', 'HTTP', 'HTTPS'],
|
||||
methods: ['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP'],
|
||||
|
@ -113,7 +118,8 @@
|
|||
description: null,
|
||||
protocol: null,
|
||||
method: null
|
||||
}
|
||||
},
|
||||
members: []
|
||||
};
|
||||
|
||||
if (model.initializing) {
|
||||
|
@ -123,7 +129,9 @@
|
|||
|
||||
promise = $q.all([
|
||||
lbaasv2API.getLoadBalancers().then(onGetLoadBalancers),
|
||||
neutronAPI.getSubnets().then(onGetSubnets)
|
||||
neutronAPI.getSubnets().then(onGetSubnets),
|
||||
neutronAPI.getPorts().then(onGetPorts),
|
||||
novaAPI.getServers().then(onGetServers)
|
||||
]);
|
||||
|
||||
promise.then(onInitSuccess, onInitFail);
|
||||
|
@ -133,6 +141,7 @@
|
|||
}
|
||||
|
||||
function onInitSuccess() {
|
||||
initMemberAddresses();
|
||||
model.initializing = false;
|
||||
model.initialized = true;
|
||||
}
|
||||
|
@ -166,9 +175,24 @@
|
|||
delete finalSpec.pool;
|
||||
}
|
||||
|
||||
// Members require a pool, address, subnet, and port but the wizard requires the address,
|
||||
// subnet, and port so we can assume those exist here.
|
||||
if (!finalSpec.pool || finalSpec.members.length === 0) {
|
||||
delete finalSpec.members;
|
||||
}
|
||||
|
||||
angular.forEach(finalSpec.members, function cleanMember(member) {
|
||||
delete member.id;
|
||||
delete member.addresses;
|
||||
delete member.name;
|
||||
delete member.description;
|
||||
member.subnet = member.address.subnet;
|
||||
member.address = member.address.ip;
|
||||
});
|
||||
|
||||
// Delete null properties
|
||||
angular.forEach(finalSpec, function(group, groupName) {
|
||||
angular.forEach(group, function(value, key) {
|
||||
angular.forEach(finalSpec, function deleteNullsForGroup(group, groupName) {
|
||||
angular.forEach(group, function deleteNullValue(value, key) {
|
||||
if (value === null) {
|
||||
delete finalSpec[groupName][key];
|
||||
}
|
||||
|
@ -195,9 +219,43 @@
|
|||
}
|
||||
|
||||
function onGetSubnets(response) {
|
||||
model.subnets = [];
|
||||
angular.forEach(response.data.items, function(subnet) {
|
||||
model.subnets.push(subnet);
|
||||
model.subnets.length = 0;
|
||||
push.apply(model.subnets, response.data.items);
|
||||
}
|
||||
|
||||
function onGetServers(response) {
|
||||
model.members.length = 0;
|
||||
var members = [];
|
||||
angular.forEach(response.data.items, function(server) {
|
||||
members.push({
|
||||
id: server.id,
|
||||
name: server.name,
|
||||
description: server.description,
|
||||
weight: 1
|
||||
});
|
||||
});
|
||||
push.apply(model.members, members);
|
||||
}
|
||||
|
||||
function onGetPorts(response) {
|
||||
ports = response.data.items;
|
||||
}
|
||||
|
||||
function initMemberAddresses() {
|
||||
angular.forEach(model.members, function(member) {
|
||||
var memberPorts = ports.filter(function(port) {
|
||||
return port.device_id === member.id;
|
||||
});
|
||||
member.addresses = [];
|
||||
angular.forEach(memberPorts, function(port) {
|
||||
angular.forEach(port.fixed_ips, function(ip) {
|
||||
member.addresses.push({
|
||||
ip: ip.ip_address,
|
||||
subnet: ip.subnet_id
|
||||
});
|
||||
});
|
||||
});
|
||||
member.address = member.addresses[0];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
describe('LBaaS v2 Create Load Balancer Workflow Model Service', function() {
|
||||
var model, $q, scope;
|
||||
|
||||
beforeEach(module('horizon.framework.util.i18n'));
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
|
@ -44,6 +45,31 @@
|
|||
var deferred = $q.defer();
|
||||
deferred.resolve({ data: { items: subnets } });
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
getPorts: function() {
|
||||
var ports = [ { device_id: '1',
|
||||
fixed_ips: [{ ip_address: '1.2.3.4', subnet_id: '1' },
|
||||
{ ip_address: '2.3.4.5', subnet_id: '2' }] },
|
||||
{ device_id: '2',
|
||||
fixed_ips: [{ ip_address: '3.4.5.6', subnet_id: '1' },
|
||||
{ ip_address: '4.5.6.7', subnet_id: '2' }] } ];
|
||||
|
||||
var deferred = $q.defer();
|
||||
deferred.resolve({ data: { items: ports } });
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
});
|
||||
|
||||
$provide.value('horizon.app.core.openstack-service-api.nova', {
|
||||
getServers: function() {
|
||||
var servers = [ { id: '1', name: 'server-1' },
|
||||
{ id: '2', name: 'server-2' } ];
|
||||
|
||||
var deferred = $q.defer();
|
||||
deferred.resolve({ data: { items: servers } });
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
});
|
||||
|
@ -76,6 +102,10 @@
|
|||
expect(model.subnets).toEqual([]);
|
||||
});
|
||||
|
||||
it('has empty members array', function() {
|
||||
expect(model.members).toEqual([]);
|
||||
});
|
||||
|
||||
it('has array of pool protocols', function() {
|
||||
expect(model.poolProtocols).toEqual(['TCP', 'HTTP', 'HTTPS']);
|
||||
});
|
||||
|
@ -108,10 +138,12 @@
|
|||
expect(model.initializing).toBe(false);
|
||||
expect(model.initialized).toBe(true);
|
||||
expect(model.subnets.length).toBe(2);
|
||||
expect(model.members.length).toBe(2);
|
||||
expect(model.spec).toBeDefined();
|
||||
expect(model.spec.loadbalancer).toBeDefined();
|
||||
expect(model.spec.listener).toBeDefined();
|
||||
expect(model.spec.pool).toBeDefined();
|
||||
expect(model.spec.members).toEqual([]);
|
||||
});
|
||||
|
||||
it('should initialize names', function() {
|
||||
|
@ -156,7 +188,7 @@
|
|||
// This is here to ensure that as people add/change spec properties, they don't forget
|
||||
// to implement tests for them.
|
||||
it('has the right number of properties', function() {
|
||||
expect(Object.keys(model.spec).length).toBe(3);
|
||||
expect(Object.keys(model.spec).length).toBe(4);
|
||||
expect(Object.keys(model.spec.loadbalancer).length).toBe(4);
|
||||
expect(Object.keys(model.spec.listener).length).toBe(4);
|
||||
expect(Object.keys(model.spec.pool).length).toBe(4);
|
||||
|
@ -227,6 +259,16 @@
|
|||
model.spec.pool.description = 'pool description';
|
||||
model.spec.pool.protocol = 'HTTP';
|
||||
model.spec.pool.method = 'LEAST_CONNECTIONS';
|
||||
model.spec.members = [{
|
||||
address: { ip: '1.2.3.4', subnet: '1' },
|
||||
addresses: [{ ip: '1.2.3.4', subnet: '1' },
|
||||
{ ip: '2.3.4.5', subnet: '2' }],
|
||||
id: '1',
|
||||
name: 'foo',
|
||||
description: 'bar',
|
||||
port: 80,
|
||||
weight: 1
|
||||
}];
|
||||
|
||||
var finalSpec = model.createLoadBalancer();
|
||||
|
||||
|
@ -242,6 +284,15 @@
|
|||
expect(finalSpec.pool.description).toBe('pool description');
|
||||
expect(finalSpec.pool.protocol).toBe('HTTP');
|
||||
expect(finalSpec.pool.method).toBe('LEAST_CONNECTIONS');
|
||||
expect(finalSpec.members.length).toBe(1);
|
||||
expect(finalSpec.members[0].address).toBe('1.2.3.4');
|
||||
expect(finalSpec.members[0].subnet).toBe('1');
|
||||
expect(finalSpec.members[0].port).toBe(80);
|
||||
expect(finalSpec.members[0].weight).toBe(1);
|
||||
expect(finalSpec.members[0].addresses).toBeUndefined();
|
||||
expect(finalSpec.members[0].id).toBeUndefined();
|
||||
expect(finalSpec.members[0].name).toBeUndefined();
|
||||
expect(finalSpec.members[0].description).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should delete listener if any required property is not set', function() {
|
||||
|
@ -268,6 +319,22 @@
|
|||
expect(finalSpec.listener).toBeDefined();
|
||||
expect(finalSpec.pool).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should delete members if none selected', function() {
|
||||
model.spec.loadbalancer.ip = '1.2.3.4';
|
||||
model.spec.loadbalancer.subnet = model.subnets[0];
|
||||
model.spec.listener.protocol = 'HTTPS';
|
||||
model.spec.listener.port = 80;
|
||||
model.spec.pool.protocol = 'HTTP';
|
||||
model.spec.pool.method = 'LEAST_CONNECTIONS';
|
||||
|
||||
var finalSpec = model.createLoadBalancer();
|
||||
|
||||
expect(finalSpec.loadbalancer).toBeDefined();
|
||||
expect(finalSpec.listener).toBeDefined();
|
||||
expect(finalSpec.pool).toBeDefined();
|
||||
expect(finalSpec.members).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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')
|
||||
.controller('CreatePoolDetailsController', CreatePoolDetailsController);
|
||||
|
||||
CreatePoolDetailsController.$inject = [
|
||||
'$scope'
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name CreatePoolDetailsController
|
||||
* @description
|
||||
* The `CreatePoolDetailsController` controller provides functions for configuring
|
||||
* pool details.
|
||||
* @param $scope The angular scope object.
|
||||
* @returns undefined
|
||||
*/
|
||||
|
||||
function CreatePoolDetailsController($scope) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.protocolChange = protocolChange;
|
||||
|
||||
//////////
|
||||
|
||||
function protocolChange(protocol) {
|
||||
var port = '';
|
||||
if (protocol === 'HTTP') {
|
||||
port = 80;
|
||||
} else if (protocol === 'HTTPS') {
|
||||
port = 443;
|
||||
}
|
||||
$scope.model.members.forEach(function setPort(member) {
|
||||
member.port = port;
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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('Create Pool Details Step', function() {
|
||||
var ctrl;
|
||||
var members = [{port: ''}, {port: ''}];
|
||||
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
beforeEach(inject(function($controller) {
|
||||
var scope = {
|
||||
model: {
|
||||
members: members
|
||||
}
|
||||
};
|
||||
ctrl = $controller('CreatePoolDetailsController', { $scope: scope });
|
||||
}));
|
||||
|
||||
it('should define protocolChange function', function() {
|
||||
expect(ctrl.protocolChange).toBeDefined();
|
||||
});
|
||||
|
||||
it('should update member ports on protocol change to HTTP', function() {
|
||||
ctrl.protocolChange('HTTP');
|
||||
|
||||
members.forEach(function(member) {
|
||||
expect(member.port).toBe(80);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update member ports on protocol change to HTTPS', function() {
|
||||
ctrl.protocolChange('HTTPS');
|
||||
|
||||
members.forEach(function(member) {
|
||||
expect(member.port).toBe(443);
|
||||
});
|
||||
});
|
||||
|
||||
it('should update member ports on protocol change to TCP', function() {
|
||||
ctrl.protocolChange('TCP');
|
||||
|
||||
members.forEach(function(member) {
|
||||
expect(member.port).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -1,4 +1,4 @@
|
|||
<div>
|
||||
<div ng-controller="CreatePoolDetailsController as ctrl">
|
||||
<h1 translate>Pool Details</h1>
|
||||
|
||||
<!--content-->
|
||||
|
@ -35,7 +35,8 @@
|
|||
<select class="form-control input-sm" name="pool-protocol"
|
||||
id="pool-protocol"
|
||||
ng-options="protocol for protocol in model.poolProtocols"
|
||||
ng-model="model.spec.pool.protocol">
|
||||
ng-model="model.spec.pool.protocol"
|
||||
ng-change="ctrl.protocolChange(model.spec.pool.protocol)">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -52,6 +52,13 @@
|
|||
templateUrl: basePath + 'loadbalancers/actions/create/pool/pool.html',
|
||||
helpUrl: basePath + 'loadbalancers/actions/create/pool/pool.help.html',
|
||||
formName: 'createLoadBalancerPoolForm'
|
||||
},
|
||||
{
|
||||
id: 'members',
|
||||
title: gettext('Pool Members'),
|
||||
templateUrl: basePath + 'loadbalancers/actions/create/members/members.html',
|
||||
helpUrl: basePath + 'loadbalancers/actions/create/members/members.help.html',
|
||||
formName: 'createLoadBalancerMembersForm'
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
@ -41,12 +41,13 @@
|
|||
|
||||
it('should have steps defined', function () {
|
||||
expect(createLoadBalancerWorkflow.steps).toBeDefined();
|
||||
expect(createLoadBalancerWorkflow.steps.length).toBe(3);
|
||||
expect(createLoadBalancerWorkflow.steps.length).toBe(4);
|
||||
|
||||
var forms = [
|
||||
'createLoadBalancerDetailsForm',
|
||||
'createLoadBalancerListenerForm',
|
||||
'createLoadBalancerPoolForm'
|
||||
'createLoadBalancerPoolForm',
|
||||
'createLoadBalancerMembersForm'
|
||||
];
|
||||
|
||||
forms.forEach(function(expectedForm, idx) {
|
||||
|
|
|
@ -22,8 +22,7 @@
|
|||
|
||||
LoadBalancerDetailController.$inject = [
|
||||
'horizon.app.core.openstack-service-api.lbaasv2',
|
||||
'$routeParams',
|
||||
'$window'
|
||||
'$routeParams'
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -35,15 +34,13 @@
|
|||
*
|
||||
* @param api The LBaaS v2 API service.
|
||||
* @param $routeParams The angular $routeParams service.
|
||||
* @param $window The angular reference to the browser window object.
|
||||
* @returns undefined
|
||||
*/
|
||||
|
||||
function LoadBalancerDetailController(api, $routeParams, $window) {
|
||||
function LoadBalancerDetailController(api, $routeParams) {
|
||||
|
||||
var ctrl = this;
|
||||
ctrl.loadbalancer = {};
|
||||
ctrl.webroot = $window.webroot;
|
||||
|
||||
var loadbalancerId = $routeParams.loadbalancerId;
|
||||
|
||||
|
|
|
@ -39,13 +39,13 @@
|
|||
<div>
|
||||
<dt translate>Subnet ID</dt>
|
||||
<dd>
|
||||
<a target="_self" ng-href="{$ ::ctrl.webroot $}project/networks/subnets/{$ ::ctrl.loadbalancer.vip_subnet_id $}/detail">{$ ::ctrl.loadbalancer.vip_subnet_id $}</a>
|
||||
<a target="_self" ng-href="project/networks/subnets/{$ ::ctrl.loadbalancer.vip_subnet_id $}/detail">{$ ::ctrl.loadbalancer.vip_subnet_id $}</a>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt translate>Port ID</dt>
|
||||
<dd>
|
||||
<a target="_self" ng-href="{$ ::ctrl.webroot $}project/networks/ports/{$ ::ctrl.loadbalancer.vip_port_id $}/detail">{$ ::ctrl.loadbalancer.vip_port_id $}</a>
|
||||
<a target="_self" ng-href="project/networks/ports/{$ ::ctrl.loadbalancer.vip_port_id $}/detail">{$ ::ctrl.loadbalancer.vip_port_id $}</a>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
.controller('LoadBalancersTableController', LoadBalancersTableController);
|
||||
|
||||
LoadBalancersTableController.$inject = [
|
||||
'$scope',
|
||||
'$window',
|
||||
'horizon.app.core.openstack-service-api.lbaasv2',
|
||||
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.batchActions'
|
||||
];
|
||||
|
@ -34,20 +32,17 @@
|
|||
* @description
|
||||
* Controller for the LBaaS v2 load balancers table. Serves as the focal point for table actions.
|
||||
*
|
||||
* @param $scope The angular $scope object.
|
||||
* @param $window The angular reference to the browser window object.
|
||||
* @param api The LBaaS V2 service API.
|
||||
* @param batchActions The load balancer batch actions service.
|
||||
* @returns undefined
|
||||
*/
|
||||
|
||||
function LoadBalancersTableController($scope, $window, api, batchActions) {
|
||||
function LoadBalancersTableController(api, batchActions) {
|
||||
|
||||
var ctrl = this;
|
||||
ctrl.items = [];
|
||||
ctrl.src = [];
|
||||
ctrl.checked = {};
|
||||
ctrl.webroot = $window.webroot;
|
||||
ctrl.batchActions = batchActions;
|
||||
|
||||
init();
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
Table-batch-actions:
|
||||
This is where batch actions like searching, creating, and deleting.
|
||||
-->
|
||||
<th colspan="100" class="search-header">
|
||||
<th colspan="0" class="search-header">
|
||||
<hz-search-bar group-classes="input-group-sm" icon-classes="fa-search">
|
||||
<actions allowed="table.batchActions.actions" type="batch"></actions>
|
||||
</hz-search-bar>
|
||||
|
@ -71,7 +71,7 @@
|
|||
duration="200">
|
||||
</span>
|
||||
</td>
|
||||
<td class="rsp-p1"><a ng-href="{$ ::ctrl.webroot $}project/ngloadbalancersv2/detail/{$ ::item.id $}">{$ item.name $}</a></td>
|
||||
<td class="rsp-p1"><a ng-href="project/ngloadbalancersv2/detail/{$ ::item.id $}">{$ item.name $}</a></td>
|
||||
<td class="rsp-p1">{$ item.description | noValue $}</td>
|
||||
<td class="rsp-p1">{$ item.operating_status | operatingStatus $}</td>
|
||||
<td class="rsp-p1">{$ item.provisioning_status | provisioningStatus $}</td>
|
||||
|
@ -86,7 +86,7 @@
|
|||
Can be toggled using the chevron button.
|
||||
Ensure colspan is greater or equal to number of column-headers.
|
||||
-->
|
||||
<td class="detail" colspan="100">
|
||||
<td class="detail" colspan="0">
|
||||
<!--
|
||||
The responsive columns that disappear typically should reappear here
|
||||
with the same responsive priority that they disappear.
|
||||
|
|
Loading…
Reference in New Issue