Add listener and pool to create load balancer workflow
This adds Listener Details and Pool Details tabs to the workflow for creating a new load balancer Partially-Implements: blueprint horizon-lbaas-v2-ui Change-Id: Id6462ddae34e53ef46d1034068ab02ebc2094bd3
This commit is contained in:
parent
3b72651253
commit
32166ee5a8
|
@ -14,8 +14,13 @@
|
|||
"""API over the neutron LBaaS v2 service.
|
||||
"""
|
||||
|
||||
from six.moves import _thread as thread
|
||||
from time import sleep
|
||||
|
||||
from django.views import generic
|
||||
|
||||
from horizon import conf
|
||||
|
||||
from openstack_dashboard.api import neutron
|
||||
from openstack_dashboard.api.rest import urls
|
||||
from openstack_dashboard.api.rest import utils as rest_utils
|
||||
|
@ -23,6 +28,79 @@ from openstack_dashboard.api.rest import utils as rest_utils
|
|||
neutronclient = neutron.neutronclient
|
||||
|
||||
|
||||
def poll_loadbalancer_status(request, loadbalancer_id, callback,
|
||||
from_state='PENDING_UPDATE', to_state='ACTIVE',
|
||||
callback_kwargs=None):
|
||||
"""Poll for the status of the load balancer.
|
||||
|
||||
Polls for the status of the load balancer and calls a function when the
|
||||
status changes to a specified state.
|
||||
|
||||
:param request: django request object
|
||||
:param loadbalancer_id: id of the load balancer to poll
|
||||
:param callback: function to call when polling is complete
|
||||
:param from_state: initial expected state of the load balancer
|
||||
:param to_state: state to check for
|
||||
:param callback_kwargs: kwargs to pass into the callback function
|
||||
"""
|
||||
interval = conf.HORIZON_CONFIG['ajax_poll_interval'] / 1000.0
|
||||
status = from_state
|
||||
while status == from_state:
|
||||
sleep(interval)
|
||||
lb = neutronclient(request).show_loadbalancer(
|
||||
loadbalancer_id).get('loadbalancer')
|
||||
status = lb['provisioning_status']
|
||||
|
||||
if status == to_state:
|
||||
kwargs = {'loadbalancer_id': loadbalancer_id}
|
||||
if callback_kwargs:
|
||||
kwargs.update(callback_kwargs)
|
||||
callback(request, **kwargs)
|
||||
|
||||
|
||||
def create_listener(request, **kwargs):
|
||||
"""Create a new listener.
|
||||
|
||||
"""
|
||||
data = request.DATA
|
||||
listenerSpec = {
|
||||
'protocol': data['listener']['protocol'],
|
||||
'protocol_port': data['listener']['port'],
|
||||
'loadbalancer_id': kwargs['loadbalancer_id']
|
||||
}
|
||||
if data['listener'].get('name'):
|
||||
listenerSpec['name'] = data['listener']['name']
|
||||
if data['listener'].get('description'):
|
||||
listenerSpec['description'] = data['listener']['description']
|
||||
listener = neutronclient(request).create_listener(
|
||||
{'listener': listenerSpec}).get('listener')
|
||||
|
||||
if data.get('pool'):
|
||||
args = (request, kwargs['loadbalancer_id'], create_pool)
|
||||
kwargs = {'callback_kwargs': {'listener_id': listener['id']}}
|
||||
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
|
||||
|
||||
return listener
|
||||
|
||||
|
||||
def create_pool(request, **kwargs):
|
||||
"""Create a new pool.
|
||||
|
||||
"""
|
||||
data = request.DATA
|
||||
poolSpec = {
|
||||
'protocol': data['pool']['protocol'],
|
||||
'lb_algorithm': data['pool']['method'],
|
||||
'listener_id': kwargs['listener_id']
|
||||
}
|
||||
if data['pool'].get('name'):
|
||||
poolSpec['name'] = data['pool']['name']
|
||||
if data['pool'].get('description'):
|
||||
poolSpec['description'] = data['pool']['description']
|
||||
return neutronclient(request).create_lbaas_pool(
|
||||
{'pool': poolSpec}).get('pool')
|
||||
|
||||
|
||||
@urls.register
|
||||
class LoadBalancers(generic.View):
|
||||
"""API for load balancers.
|
||||
|
@ -59,6 +137,15 @@ class LoadBalancers(generic.View):
|
|||
spec['vip_address'] = data['loadbalancer']['ip']
|
||||
loadbalancer = neutronclient(request).create_loadbalancer(
|
||||
{'loadbalancer': spec}).get('loadbalancer')
|
||||
if data.get('listener'):
|
||||
# There is work underway to add a new API to LBaaS v2 that will
|
||||
# allow us to pass in all information at once. Until that is
|
||||
# available we use a separate thread to poll for the load
|
||||
# balancer status and create the other resources when it becomes
|
||||
# active.
|
||||
args = (request, loadbalancer['id'], create_listener)
|
||||
kwargs = {'from_state': 'PENDING_CREATE'}
|
||||
thread.start_new_thread(poll_loadbalancer_status, args, kwargs)
|
||||
return loadbalancer
|
||||
|
||||
|
||||
|
|
|
@ -46,6 +46,8 @@ ADD_JS_FILES = [
|
|||
'workflow.service.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/details/'
|
||||
'details.controller.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/listener/'
|
||||
'listener.controller.js')
|
||||
]
|
||||
|
||||
ADD_JS_SPEC_FILES = [
|
||||
|
@ -67,6 +69,8 @@ ADD_JS_SPEC_FILES = [
|
|||
'workflow.service.spec.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/details/'
|
||||
'details.controller.spec.js'),
|
||||
('dashboard/project/lbaasv2/loadbalancers/actions/create/listener/'
|
||||
'listener.controller.spec.js')
|
||||
]
|
||||
|
||||
ADD_SCSS_FILES = [
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
return [{
|
||||
service: createModal,
|
||||
template: {
|
||||
url: basePath + 'loadbalancers/actions/create/action.template.html',
|
||||
type: 'create',
|
||||
text: gettext('Create Load Balancer')
|
||||
}
|
||||
}];
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<action action-classes="'btn btn-default btn-sm pull-right'">
|
||||
<span class="fa fa-plus">$text$</span>
|
||||
</action>
|
|
@ -21,7 +21,8 @@
|
|||
.controller('CreateLoadBalancerDetailsController', CreateLoadBalancerDetailsController);
|
||||
|
||||
CreateLoadBalancerDetailsController.$inject = [
|
||||
'horizon.dashboard.project.lbaasv2.patterns'
|
||||
'horizon.dashboard.project.lbaasv2.patterns',
|
||||
'horizon.framework.util.i18n.gettext'
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -31,10 +32,11 @@
|
|||
* The `CreateLoadBalancerDetailsController` controller provides functions for
|
||||
* configuring the details step of the Create Load Balancer Wizard.
|
||||
* @param patterns The LBaaS v2 patterns constant.
|
||||
* @param gettext The horizon gettext function for translation.
|
||||
* @returns undefined
|
||||
*/
|
||||
|
||||
function CreateLoadBalancerDetailsController(patterns) {
|
||||
function CreateLoadBalancerDetailsController(patterns, gettext) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
describe('Create Load Balancer Details Step', function() {
|
||||
|
||||
beforeEach(module('horizon.framework.util.i18n'));
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
describe('CreateLoadBalancerDetailsController', function() {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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('CreateListenerDetailsController', CreateListenerDetailsController);
|
||||
|
||||
CreateListenerDetailsController.$inject = [
|
||||
'horizon.framework.util.i18n.gettext'
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name CreateListenerDetailsController
|
||||
* @description
|
||||
* The `CreateListenerDetailsController` controller provides functions for
|
||||
* configuring the listener details step when creating a new listener.
|
||||
* @param gettext The horizon gettext function for translation.
|
||||
* @returns undefined
|
||||
*/
|
||||
|
||||
function CreateListenerDetailsController(gettext) {
|
||||
|
||||
var ctrl = this;
|
||||
|
||||
// Error text for invalid fields
|
||||
ctrl.listenerPortError = gettext('The port must be a number between 1 and 65535.');
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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('Create Listener Details Step', function() {
|
||||
|
||||
beforeEach(module('horizon.framework.util.i18n'));
|
||||
beforeEach(module('horizon.dashboard.project.lbaasv2'));
|
||||
|
||||
describe('CreateListenerDetailsController', function() {
|
||||
var ctrl;
|
||||
|
||||
beforeEach(inject(function($controller) {
|
||||
ctrl = $controller('CreateListenerDetailsController');
|
||||
}));
|
||||
|
||||
it('should define error messages for invalid fields', function() {
|
||||
expect(ctrl.listenerPortError).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,3 @@
|
|||
<h1 translate>Listener Details Help</h1>
|
||||
|
||||
<p translate>To create a listener, the port and protocol must be provided. If either of these properties are not provided, only the load balancer will be created.</p>
|
|
@ -0,0 +1,62 @@
|
|||
<div ng-controller="CreateListenerDetailsController as ctrl">
|
||||
<h1 translate>Listener Details</h1>
|
||||
|
||||
<!--content-->
|
||||
<div class="content">
|
||||
<div translate class="subtitle">Provide the details for the new load balancer listener. The listener will only be created if values are provided for all fields marked as required.</div>
|
||||
|
||||
<div class="row form-group">
|
||||
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="form-field listener-name">
|
||||
<label translate class="on-top" for="listener-name">Name</label>
|
||||
<input name="listener-name" id="listener-name"
|
||||
type="text" class="form-control input-sm"
|
||||
ng-model="model.spec.listener.name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="form-field listener-description">
|
||||
<label translate class="on-top" for="listener-description">Description</label>
|
||||
<input name="listener-description" id="listener-description"
|
||||
type="text" class="form-control input-sm"
|
||||
ng-model="model.spec.listener.description">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field required listener-protocol">
|
||||
<label translate class="on-top" for="listener-protocol">Protocol</label>
|
||||
<select class="form-control input-sm" name="listener-protocol"
|
||||
id="listener-protocol"
|
||||
ng-options="protocol for protocol in model.listenerProtocols"
|
||||
ng-model="model.spec.listener.protocol">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field required listener-port"
|
||||
ng-class="{ 'has-error': createLoadBalancerListenerForm['listener-port'].$invalid && createLoadBalancerListenerForm['listener-port'].$dirty }">
|
||||
<label translate class="on-top" for="listener-port">Port</label>
|
||||
<span class="fa fa-exclamation-triangle invalid"
|
||||
ng-show="createLoadBalancerListenerForm['listener-port'].$invalid && createLoadBalancerListenerForm.$dirty"
|
||||
popover="{$ ::ctrl.listenerPortError $}"
|
||||
popover-placement="top" popover-append-to-body="true"
|
||||
popover-trigger="hover"></span>
|
||||
<input name="listener-port" id="listener-port"
|
||||
type="number" class="form-control input-sm"
|
||||
ng-model="model.spec.listener.port" ng-pattern="/^\d+$/" min="1" max="65535">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- end content -->
|
||||
</div>
|
|
@ -24,7 +24,8 @@
|
|||
createLoadBalancerModel.$inject = [
|
||||
'$q',
|
||||
'horizon.app.core.openstack-service-api.neutron',
|
||||
'horizon.app.core.openstack-service-api.lbaasv2'
|
||||
'horizon.app.core.openstack-service-api.lbaasv2',
|
||||
'horizon.framework.util.i18n.gettext'
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -40,10 +41,11 @@
|
|||
* @param $q The angular service for promises.
|
||||
* @param neutronAPI The neutron 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) {
|
||||
function createLoadBalancerModel($q, neutronAPI, lbaasv2API, gettext) {
|
||||
var initPromise;
|
||||
|
||||
/**
|
||||
|
@ -69,6 +71,9 @@
|
|||
spec: null,
|
||||
|
||||
subnets: [],
|
||||
listenerProtocols: ['TCP', 'HTTP', 'HTTPS'],
|
||||
poolProtocols: ['TCP', 'HTTP', 'HTTPS'],
|
||||
methods: ['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP'],
|
||||
|
||||
/**
|
||||
* api methods for UI controllers
|
||||
|
@ -96,6 +101,18 @@
|
|||
description: null,
|
||||
ip: null,
|
||||
subnet: null
|
||||
},
|
||||
listener: {
|
||||
name: gettext('Listener 1'),
|
||||
description: null,
|
||||
protocol: null,
|
||||
port: null
|
||||
},
|
||||
pool: {
|
||||
name: gettext('Pool 1'),
|
||||
description: null,
|
||||
protocol: null,
|
||||
method: null
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -139,6 +156,16 @@
|
|||
function createLoadBalancer() {
|
||||
var finalSpec = angular.copy(model.spec);
|
||||
|
||||
// Listener requires protocol and port
|
||||
if (!finalSpec.listener.protocol || !finalSpec.listener.port) {
|
||||
delete finalSpec.listener;
|
||||
}
|
||||
|
||||
// Pool requires protocol and method, and also the listener
|
||||
if (!finalSpec.listener || !finalSpec.pool.protocol || !finalSpec.pool.method) {
|
||||
delete finalSpec.pool;
|
||||
}
|
||||
|
||||
// Delete null properties
|
||||
angular.forEach(finalSpec, function(group, groupName) {
|
||||
angular.forEach(group, function(value, key) {
|
||||
|
@ -160,10 +187,9 @@
|
|||
});
|
||||
var name;
|
||||
var index = 0;
|
||||
var prefix = 'Load Balancer ';
|
||||
do {
|
||||
index += 1;
|
||||
name = prefix + index;
|
||||
name = interpolate(gettext('Load Balancer %(index)s'), { index: index }, true);
|
||||
} while (name in existingNames);
|
||||
model.spec.loadbalancer.name = name;
|
||||
}
|
||||
|
|
|
@ -76,6 +76,18 @@
|
|||
expect(model.subnets).toEqual([]);
|
||||
});
|
||||
|
||||
it('has array of pool protocols', function() {
|
||||
expect(model.poolProtocols).toEqual(['TCP', 'HTTP', 'HTTPS']);
|
||||
});
|
||||
|
||||
it('has array of listener protocols', function() {
|
||||
expect(model.listenerProtocols).toEqual(['TCP', 'HTTP', 'HTTPS']);
|
||||
});
|
||||
|
||||
it('has array of methods', function() {
|
||||
expect(model.methods).toEqual(['ROUND_ROBIN', 'LEAST_CONNECTIONS', 'SOURCE_IP']);
|
||||
});
|
||||
|
||||
it('has an "initialize" function', function() {
|
||||
expect(model.initialize).toBeDefined();
|
||||
});
|
||||
|
@ -98,10 +110,14 @@
|
|||
expect(model.subnets.length).toBe(2);
|
||||
expect(model.spec).toBeDefined();
|
||||
expect(model.spec.loadbalancer).toBeDefined();
|
||||
expect(model.spec.listener).toBeDefined();
|
||||
expect(model.spec.pool).toBeDefined();
|
||||
});
|
||||
|
||||
it('should initialize names', function() {
|
||||
expect(model.spec.loadbalancer.name).toBe('Load Balancer 3');
|
||||
expect(model.spec.listener.name).toBe('Listener 1');
|
||||
expect(model.spec.pool.name).toBe('Pool 1');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -140,8 +156,10 @@
|
|||
// 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(1);
|
||||
expect(Object.keys(model.spec).length).toBe(3);
|
||||
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);
|
||||
});
|
||||
|
||||
it('sets load balancer name to null', function() {
|
||||
|
@ -159,6 +177,38 @@
|
|||
it('sets load balancer subnet to null', function() {
|
||||
expect(model.spec.loadbalancer.subnet).toBeNull();
|
||||
});
|
||||
|
||||
it('sets listener name to reasonable default', function() {
|
||||
expect(model.spec.listener.name).toBe('Listener 1');
|
||||
});
|
||||
|
||||
it('sets listener description to null', function() {
|
||||
expect(model.spec.listener.description).toBeNull();
|
||||
});
|
||||
|
||||
it('sets listener protocol to null', function() {
|
||||
expect(model.spec.listener.protocol).toBeNull();
|
||||
});
|
||||
|
||||
it('sets listener port to null', function() {
|
||||
expect(model.spec.listener.port).toBeNull();
|
||||
});
|
||||
|
||||
it('sets pool name to reasonable default', function() {
|
||||
expect(model.spec.pool.name).toBe('Pool 1');
|
||||
});
|
||||
|
||||
it('sets pool description to null', function() {
|
||||
expect(model.spec.pool.description).toBeNull();
|
||||
});
|
||||
|
||||
it('sets pool protocol to null', function() {
|
||||
expect(model.spec.pool.protocol).toBeNull();
|
||||
});
|
||||
|
||||
it('sets pool method to null', function() {
|
||||
expect(model.spec.pool.method).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Create Load Balancer', function() {
|
||||
|
@ -171,12 +221,52 @@
|
|||
it('should set final spec properties', 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.name = 'pool name';
|
||||
model.spec.pool.description = 'pool description';
|
||||
model.spec.pool.protocol = 'HTTP';
|
||||
model.spec.pool.method = 'LEAST_CONNECTIONS';
|
||||
|
||||
var finalSpec = model.createLoadBalancer();
|
||||
|
||||
expect(finalSpec.loadbalancer.name).toBe('Load Balancer 3');
|
||||
expect(finalSpec.loadbalancer.description).toBeUndefined();
|
||||
expect(finalSpec.loadbalancer.ip).toBe('1.2.3.4');
|
||||
expect(finalSpec.loadbalancer.subnet).toBe(model.subnets[0].id);
|
||||
expect(finalSpec.listener.name).toBe('Listener 1');
|
||||
expect(finalSpec.listener.description).toBeUndefined();
|
||||
expect(finalSpec.listener.protocol).toBe('HTTPS');
|
||||
expect(finalSpec.listener.port).toBe(80);
|
||||
expect(finalSpec.pool.name).toBe('pool name');
|
||||
expect(finalSpec.pool.description).toBe('pool description');
|
||||
expect(finalSpec.pool.protocol).toBe('HTTP');
|
||||
expect(finalSpec.pool.method).toBe('LEAST_CONNECTIONS');
|
||||
});
|
||||
|
||||
it('should delete listener if any required property is not set', function() {
|
||||
model.spec.loadbalancer.ip = '1.2.3.4';
|
||||
model.spec.loadbalancer.subnet = model.subnets[0];
|
||||
model.spec.listener.protocol = 'HTTPS';
|
||||
|
||||
var finalSpec = model.createLoadBalancer();
|
||||
|
||||
expect(finalSpec.loadbalancer).toBeDefined();
|
||||
expect(finalSpec.listener).toBeUndefined();
|
||||
expect(finalSpec.pool).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should delete pool if any required property is not set', 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;
|
||||
|
||||
var finalSpec = model.createLoadBalancer();
|
||||
|
||||
expect(finalSpec.loadbalancer).toBeDefined();
|
||||
expect(finalSpec.listener).toBeDefined();
|
||||
expect(finalSpec.pool).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<h1 translate>Pool Details Help</h1>
|
||||
|
||||
<p translate>To create a pool, the protocol and method must be provided. If either of these properties are not provided then the pool will not be created.</p>
|
|
@ -0,0 +1,58 @@
|
|||
<div>
|
||||
<h1 translate>Pool Details</h1>
|
||||
|
||||
<!--content-->
|
||||
<div class="content">
|
||||
<div translate class="subtitle">Provide the details for the new load balancer pool. The pool will only be created if values are provided for all fields marked as required.</div>
|
||||
|
||||
<div class="row form-group">
|
||||
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="form-field pool-name">
|
||||
<label translate class="on-top" for="pool-name">Name</label>
|
||||
<input name="pool-name" id="pool-name"
|
||||
type="text" class="form-control input-sm"
|
||||
ng-model="model.spec.pool.name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-6">
|
||||
<div class="form-field pool-description">
|
||||
<label translate class="on-top" for="pool-description">Description</label>
|
||||
<input name="pool-description" id="pool-description"
|
||||
type="text" class="form-control input-sm"
|
||||
ng-model="model.spec.pool.description">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field required pool-protocol">
|
||||
<label translate class="on-top" for="pool-protocol">Protocol</label>
|
||||
<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">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-3">
|
||||
<div class="form-field required pool-method">
|
||||
<label translate class="on-top" for="pool-method">Method</label>
|
||||
<select class="form-control input-sm" name="pool-method"
|
||||
id="pool-method"
|
||||
ng-options="method for method in model.methods"
|
||||
ng-model="model.spec.pool.method">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- end content -->
|
||||
</div>
|
|
@ -23,10 +23,11 @@
|
|||
|
||||
createLoadBalancerWorkflow.$inject = [
|
||||
'horizon.dashboard.project.lbaasv2.basePath',
|
||||
'horizon.app.core.workflow.factory'
|
||||
'horizon.app.core.workflow.factory',
|
||||
'horizon.framework.util.i18n.gettext'
|
||||
];
|
||||
|
||||
function createLoadBalancerWorkflow(basePath, dashboardWorkflow) {
|
||||
function createLoadBalancerWorkflow(basePath, dashboardWorkflow, gettext) {
|
||||
return dashboardWorkflow({
|
||||
title: gettext('Create Load Balancer'),
|
||||
|
||||
|
@ -37,6 +38,20 @@
|
|||
templateUrl: basePath + 'loadbalancers/actions/create/details/details.html',
|
||||
helpUrl: basePath + 'loadbalancers/actions/create/details/details.help.html',
|
||||
formName: 'createLoadBalancerDetailsForm'
|
||||
},
|
||||
{
|
||||
id: 'listener',
|
||||
title: gettext('Listener Details'),
|
||||
templateUrl: basePath + 'loadbalancers/actions/create/listener/listener.html',
|
||||
helpUrl: basePath + 'loadbalancers/actions/create/listener/listener.help.html',
|
||||
formName: 'createLoadBalancerListenerForm'
|
||||
},
|
||||
{
|
||||
id: 'pool',
|
||||
title: gettext('Pool Details'),
|
||||
templateUrl: basePath + 'loadbalancers/actions/create/pool/pool.html',
|
||||
helpUrl: basePath + 'loadbalancers/actions/create/pool/pool.help.html',
|
||||
formName: 'createLoadBalancerPoolForm'
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
@ -41,10 +41,12 @@
|
|||
|
||||
it('should have steps defined', function () {
|
||||
expect(createLoadBalancerWorkflow.steps).toBeDefined();
|
||||
expect(createLoadBalancerWorkflow.steps.length).toBe(1);
|
||||
expect(createLoadBalancerWorkflow.steps.length).toBe(3);
|
||||
|
||||
var forms = [
|
||||
'createLoadBalancerDetailsForm'
|
||||
'createLoadBalancerDetailsForm',
|
||||
'createLoadBalancerListenerForm',
|
||||
'createLoadBalancerPoolForm'
|
||||
];
|
||||
|
||||
forms.forEach(function(expectedForm, idx) {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<div class="content" ng-controller="LoadBalancerDetailController as ctrl">
|
||||
<div class='page-header'>
|
||||
<h1>{$ ::ctrl.loadbalancer.name $}</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="project/ngloadbalancersv2/"><translate>Load Balancers</translate></a></li>
|
||||
<li class="active">{$ ::ctrl.loadbalancer.name $}</li>
|
||||
</ol>
|
||||
<p ng-if="::ctrl.loadbalancer.description">{$ ::ctrl.loadbalancer.description $}</p>
|
||||
</div>
|
||||
<div class="detail-page">
|
||||
|
|
Loading…
Reference in New Issue