Make create load balancer workflow generic

This patch turns the create load balancer workflow into a generic
LBaaS workflow that can be used for many actions such as creating or
editing any of the LBaaS resources like listeners, pools, etc. This
way a single workflow, modal, model, and wizard can be used for all
the actions.

Partially-Implements: blueprint horizon-lbaas-v2-ui
Change-Id: I0b0cff415f5afb4dccf66b7acca8260dbf124015
This commit is contained in:
Justin Pomeroy 2015-12-15 15:50:15 -06:00
parent 729c223f77
commit 39c2a18699
35 changed files with 616 additions and 449 deletions

View File

@ -38,22 +38,17 @@ ADD_JS_FILES = [
'dashboard/project/lbaasv2/loadbalancers/detail.controller.js',
'dashboard/project/lbaasv2/loadbalancers/filters.js',
'dashboard/project/lbaasv2/loadbalancers/actions/batch-actions.service.js',
'dashboard/project/lbaasv2/loadbalancers/actions/create/modal.service.js',
'dashboard/project/lbaasv2/loadbalancers/actions/create/model.service.js',
('dashboard/project/lbaasv2/loadbalancers/actions/create/'
'wizard.controller.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/'
'workflow.service.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/details/'
'details.controller.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/listener/'
'listener.controller.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/pool/'
'pool.controller.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/members/'
'members.controller.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/monitor/'
'monitor.controller.js')
'dashboard/project/lbaasv2/workflow/modal.service.js',
'dashboard/project/lbaasv2/workflow/model.service.js',
'dashboard/project/lbaasv2/workflow/workflow.service.js',
('dashboard/project/lbaasv2/workflow/loadbalancer/'
'loadbalancer.controller.js'),
'dashboard/project/lbaasv2/workflow/listener/listener.controller.js',
'dashboard/project/lbaasv2/workflow/pool/pool.controller.js',
'dashboard/project/lbaasv2/workflow/members/members.controller.js',
'dashboard/project/lbaasv2/workflow/monitor/monitor.controller.js'
]
ADD_JS_SPEC_FILES = [
@ -65,24 +60,17 @@ ADD_JS_SPEC_FILES = [
'dashboard/project/lbaasv2/loadbalancers/filters.spec.js',
('dashboard/project/lbaasv2/loadbalancers/actions/'
'batch-actions.service.spec.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/'
'modal.service.spec.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/'
'model.service.spec.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/'
'wizard.controller.spec.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/'
'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'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/pool/'
'pool.controller.spec.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/members/'
'members.controller.spec.js'),
('dashboard/project/lbaasv2/loadbalancers/actions/create/monitor/'
'monitor.controller.spec.js')
'dashboard/project/lbaasv2/workflow/modal.service.spec.js',
'dashboard/project/lbaasv2/workflow/model.service.spec.js',
'dashboard/project/lbaasv2/workflow/workflow.service.spec.js',
('dashboard/project/lbaasv2/workflow/loadbalancer/'
'loadbalancer.controller.spec.js'),
'dashboard/project/lbaasv2/workflow/listener/listener.controller.spec.js',
'dashboard/project/lbaasv2/workflow/pool/pool.controller.spec.js',
'dashboard/project/lbaasv2/workflow/members/members.controller.spec.js',
'dashboard/project/lbaasv2/workflow/monitor/monitor.controller.spec.js'
]
ADD_SCSS_FILES = [

View File

@ -22,8 +22,10 @@
tableBatchActions);
tableBatchActions.$inject = [
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.modal',
'$location',
'horizon.dashboard.project.lbaasv2.workflow.modal',
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.i18n.gettext'
];
@ -34,13 +36,22 @@
* @description
* Provides the service for the Load Balancers table batch actions.
*
* @param createModal The create action modal service.
* @param $location The angular $location service.
* @param workflowModal The LBaaS workflow modal service.
* @param basePath The lbaasv2 module base path.
* @param policy The horizon policy service.
* @param gettext The horizon gettext function for translation.
* @returns Load balancers table batch actions service object.
*/
function tableBatchActions(createModal, basePath, gettext) {
function tableBatchActions($location, workflowModal, basePath, policy, gettext) {
var create = workflowModal.init({
controller: 'CreateLoadBalancerWizardController',
message: gettext('A new load balancer is being created.'),
handle: onCreate,
allowed: canCreate
});
var service = {
actions: actions
@ -52,13 +63,23 @@
function actions() {
return [{
service: createModal,
service: create,
template: {
type: 'create',
text: gettext('Create Load Balancer')
}
}];
}
function canCreate() {
// 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.
return policy.ifAllowed({ rules: [['neutron', 'create_loadbalancer']] });
}
function onCreate(response) {
$location.path('project/ngloadbalancersv2/detail/' + response.data.id);
}
}
})();

View File

@ -17,7 +17,7 @@
'use strict';
describe('LBaaS v2 Load Balancers Table Batch Actions Service', function() {
var batchActionsService;
var $location, actions, policy;
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
@ -26,19 +26,57 @@
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
$provide.value('$modal', {});
var response = {
data: {
id: '1'
}
};
var modal = {
open: function() {
return {
result: {
then: function(func) {
func(response);
}
}
};
}
};
$provide.value('$modal', modal);
}));
beforeEach(inject(function ($injector) {
batchActionsService = $injector.get(
$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() {
var actions = batchActionsService.actions();
expect(actions.length).toBe(1);
expect(actions[0].template.text).toBe('Create Load Balancer');
});
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/ngloadbalancersv2/detail/1');
});
});
})();

View File

@ -1,3 +0,0 @@
<h1 translate>Load Balancer Details Help</h1>
<p translate>Provide the details for the new load balancer. A subnet is required. The subnet is the network on which to allocate the load balancer's IP address. If an IP address is provided it must be a well-formed IPv4 or IPv6 address. The system will attempt to assign the provided IP address to the load balancer.</p>

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';
angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers')
.factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.modal',
modalService);
modalService.$inject = [
'$modal',
'$location',
'horizon.framework.widgets.toast.service',
'horizon.framework.util.i18n.gettext',
'horizon.app.core.openstack-service-api.policy'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.modal
*
* @description
* Provides the service for opening the create load balancer modal.
*
* @param $modal The angular bootstrap $modal service.
* @param $location The angular $location service.
* @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, toastService, gettext, policy) {
var service = {
allowed: allowed,
perform: perform
};
return service;
//////////////
function allowed() {
// 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.
return policy.ifAllowed({ rules: [['neutron', 'create_loadbalancer']] });
}
function perform() {
var spec = {
backdrop: 'static',
controller: 'ModalContainerController',
template: '<wizard ng-controller="CreateLoadBalancerWizardController"></wizard>',
windowClass: 'modal-dialog-wizard',
// ModalContainerController requires a launchContext parameter...
resolve: {
launchContext: null
}
};
var modal = $modal.open(spec);
modal.result.then(function(response) {
toastService.add('success', gettext('A new load balancer is being created.'));
$location.path('project/ngloadbalancersv2/detail/' + response.data.id);
});
}
}
})();

View File

@ -22,21 +22,19 @@
CreateLoadBalancerWizardController.$inject = [
'$scope',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.model',
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.workflow'
'horizon.dashboard.project.lbaasv2.workflow.model',
'horizon.dashboard.project.lbaasv2.workflow.workflow',
'horizon.framework.util.i18n.gettext'
];
function CreateLoadBalancerWizardController(
$scope,
createLoadBalancerModel,
createLoadBalancerWorkflow
) {
// Note: we set these attributes on the $scope so that the scope inheritance used all
// through the wizard continues to work.
$scope.workflow = createLoadBalancerWorkflow; // eslint-disable-line angular/controller-as
$scope.model = createLoadBalancerModel; // eslint-disable-line angular/controller-as
$scope.model.initialize();
$scope.submit = $scope.model.createLoadBalancer; // eslint-disable-line angular/controller-as
function CreateLoadBalancerWizardController($scope, model, workflowService, gettext) {
var scope = $scope;
// Note: We set these attributes on the $scope so that the scope inheritance used all through
// the wizard continues to work. Using local var to appease eslint angular/ng_controller_as.
scope.model = model;
scope.submit = scope.model.submit;
scope.workflow = workflowService(gettext('Create Load Balancer'), 'fa fa-cloud-download');
scope.model.initialize('loadbalancer');
}
})();

View File

@ -19,23 +19,21 @@
describe('LBaaS v2 Create Load Balancer Wizard Controller', function() {
var ctrl;
var model = {
createLoadBalancer: function() {
submit: function() {
return 'created';
},
initialize: angular.noop
};
var workflow = function() {
return 'foo';
};
var scope = {};
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function ($provide) {
$provide.value('horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.model',
model);
$provide.value('horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.workflow',
{ thing: true });
$provide.value('horizon.dashboard.project.lbaasv2.workflow.model', model);
$provide.value('horizon.dashboard.project.lbaasv2.workflow.workflow', workflow);
}));
beforeEach(inject(function ($controller) {
spyOn(model, 'initialize');
@ -51,7 +49,7 @@
});
it('sets scope.workflow to the given workflow', function() {
expect(scope.workflow).toEqual({ thing: true });
expect(scope.workflow).toBe('foo');
});
it('defines scope.submit', function() {

View File

@ -1,82 +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')
.factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.workflow',
createLoadBalancerWorkflow);
createLoadBalancerWorkflow.$inject = [
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.app.core.workflow.factory',
'horizon.framework.util.i18n.gettext'
];
function createLoadBalancerWorkflow(basePath, dashboardWorkflow, gettext) {
return dashboardWorkflow({
title: gettext('Create Load Balancer'),
steps: [
{
id: 'loadbalancer',
title: gettext('Load Balancer Details'),
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'
},
{
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'
},
{
id: 'monitor',
title: gettext('Monitor'),
templateUrl: basePath + 'loadbalancers/actions/create/monitor/monitor.html',
helpUrl: basePath + 'loadbalancers/actions/create/monitor/monitor.help.html',
formName: 'createLoadBalancerMonitorForm'
}
],
btnText: {
finish: gettext('Create Load Balancer')
},
btnIcon: {
finish: 'fa fa-cloud-download'
}
});
}
})();

View File

@ -1,69 +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 Create Load Balancer Workflow Service', function() {
var createLoadBalancerWorkflow;
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function ($injector) {
createLoadBalancerWorkflow = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.workflow'
);
}));
it('should be defined', function () {
expect(createLoadBalancerWorkflow).toBeDefined();
});
it('should have a title property', function () {
expect(createLoadBalancerWorkflow.title).toBeDefined();
});
it('should have steps defined', function () {
expect(createLoadBalancerWorkflow.steps).toBeDefined();
expect(createLoadBalancerWorkflow.steps.length).toBe(5);
var forms = [
'createLoadBalancerDetailsForm',
'createLoadBalancerListenerForm',
'createLoadBalancerPoolForm',
'createLoadBalancerMembersForm',
'createLoadBalancerMonitorForm'
];
forms.forEach(function(expectedForm, idx) {
expect(createLoadBalancerWorkflow.steps[idx].formName).toBe(expectedForm);
});
});
it('can be extended', function () {
expect(createLoadBalancerWorkflow.append).toBeDefined();
expect(createLoadBalancerWorkflow.prepend).toBeDefined();
expect(createLoadBalancerWorkflow.after).toBeDefined();
expect(createLoadBalancerWorkflow.replace).toBeDefined();
expect(createLoadBalancerWorkflow.remove).toBeDefined();
expect(createLoadBalancerWorkflow.addController).toBeDefined();
});
});
})();

View File

@ -17,28 +17,28 @@
'use strict';
angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers')
.controller('CreateListenerDetailsController', CreateListenerDetailsController);
.module('horizon.dashboard.project.lbaasv2')
.controller('ListenerDetailsController', ListenerDetailsController);
CreateListenerDetailsController.$inject = [
ListenerDetailsController.$inject = [
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc controller
* @name CreateListenerDetailsController
* @name ListenerDetailsController
* @description
* The `CreateListenerDetailsController` controller provides functions for
* configuring the listener details step when creating a new listener.
* The `ListenerDetailsController` controller provides functions for
* configuring the listener details step of the LBaaS wizard.
* @param gettext The horizon gettext function for translation.
* @returns undefined
*/
function CreateListenerDetailsController(gettext) {
function ListenerDetailsController(gettext) {
var ctrl = this;
// Error text for invalid fields
ctrl.listenerPortError = gettext('The port must be a number between 1 and 65535.');
ctrl.portError = gettext('The port must be a number between 1 and 65535.');
}
})();

View File

@ -16,20 +16,20 @@
(function() {
'use strict';
describe('Create Listener Details Step', function() {
describe('Listener Details Step', function() {
beforeEach(module('horizon.framework.util.i18n'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
describe('CreateListenerDetailsController', function() {
describe('ListenerDetailsController', function() {
var ctrl;
beforeEach(inject(function($controller) {
ctrl = $controller('CreateListenerDetailsController');
ctrl = $controller('ListenerDetailsController');
}));
it('should define error messages for invalid fields', function() {
expect(ctrl.listenerPortError).toBeDefined();
expect(ctrl.portError).toBeDefined();
});
});

View File

@ -1,9 +1,9 @@
<div ng-controller="CreateListenerDetailsController as ctrl">
<div ng-controller="ListenerDetailsController 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 translate class="subtitle">Provide the details for the listener.</div>
<div class="row form-group">
@ -35,23 +35,24 @@
<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">
ng-model="model.spec.listener.protocol" ng-required="model.context.type === 'listener'">
</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 }">
ng-class="{ 'has-error': listenerDetailForm['listener-port'].$invalid && listenerDetailForm['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 $}"
ng-show="listenerDetailForm['listener-port'].$invalid && listenerDetailForm.$dirty"
popover="{$ ::ctrl.portError $}"
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">
ng-model="model.spec.listener.port" ng-pattern="/^\d+$/" min="1" max="65535"
ng-required="model.context.type === 'listener'">
</div>
</div>

View File

@ -17,31 +17,31 @@
'use strict';
angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers')
.controller('CreateLoadBalancerDetailsController', CreateLoadBalancerDetailsController);
.module('horizon.dashboard.project.lbaasv2')
.controller('LoadBalancerDetailsController', LoadBalancerDetailsController);
CreateLoadBalancerDetailsController.$inject = [
LoadBalancerDetailsController.$inject = [
'horizon.dashboard.project.lbaasv2.patterns',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc controller
* @name CreateLoadBalancerDetailsController
* @name LoadBalancerDetailsController
* @description
* The `CreateLoadBalancerDetailsController` controller provides functions for
* configuring the details step of the Create Load Balancer Wizard.
* The `LoadBalancerDetailsController` controller provides functions for
* configuring the load balancers step of the LBaaS wizard.
* @param patterns The LBaaS v2 patterns constant.
* @param gettext The horizon gettext function for translation.
* @returns undefined
*/
function CreateLoadBalancerDetailsController(patterns, gettext) {
function LoadBalancerDetailsController(patterns, gettext) {
var ctrl = this;
// Error text for invalid fields
ctrl.loadbalancerIPError = gettext('The IP address is not valid.');
ctrl.ipError = gettext('The IP address is not valid.');
// IP address validation pattern
ctrl.ipPattern = [patterns.ipv4, patterns.ipv6].join('|');

View File

@ -16,20 +16,20 @@
(function() {
'use strict';
describe('Create Load Balancer Details Step', function() {
describe('Load Balancer Details Step', function() {
beforeEach(module('horizon.framework.util.i18n'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
describe('CreateLoadBalancerDetailsController', function() {
describe('LoadBalancerDetailsController', function() {
var ctrl;
beforeEach(inject(function($controller) {
ctrl = $controller('CreateLoadBalancerDetailsController');
ctrl = $controller('LoadBalancerDetailsController');
}));
it('should define error messages for invalid fields', function() {
expect(ctrl.loadbalancerIPError).toBeDefined();
expect(ctrl.ipError).toBeDefined();
});
it('should define patterns for field validation', function() {

View File

@ -0,0 +1,3 @@
<h1 translate>Load Balancer Details Help</h1>
<p translate>Provide the details for the load balancer. A subnet is required. The subnet is the network on which to allocate the load balancer's IP address. If an IP address is provided it must be a well-formed IPv4 or IPv6 address. The system will attempt to assign the provided IP address to the load balancer.</p>

View File

@ -1,14 +1,14 @@
<div ng-controller="CreateLoadBalancerDetailsController as ctrl">
<div ng-controller="LoadBalancerDetailsController as ctrl">
<h1 translate>Load Balancer Details</h1>
<!--content-->
<div class="content">
<div translate class="subtitle">Provide the details for the new load balancer.</div>
<div translate class="subtitle">Provide the details for the load balancer.</div>
<div class="row form-group">
<div class="col-sm-12 col-md-6">
<div class="form-field loadbalancer-name"
ng-class="{ 'has-error': createLoadBalancerDetailsForm['loadbalancer-name'].$invalid && createLoadBalancerDetailsForm['loadbalancer-name'].$dirty }">
ng-class="{ 'has-error': loadBalancerDetailsForm['loadbalancer-name'].$invalid && loadBalancerDetailsForm['loadbalancer-name'].$dirty }">
<label translate class="on-top" for="loadbalancer-name">Name</label>
<input name="loadbalancer-name" id="loadbalancer-name"
type="text" class="form-control input-sm"
@ -30,11 +30,11 @@
<div class="row form-group">
<div class="col-sm-6 col-md-3">
<div class="form-field loadbalancer-ip"
ng-class="{ 'has-error': createLoadBalancerDetailsForm['loadbalancer-ip'].$invalid && createLoadBalancerDetailsForm['loadbalancer-ip'].$dirty }">
ng-class="{ 'has-error': loadBalancerDetailsForm['loadbalancer-ip'].$invalid && loadBalancerDetailsForm['loadbalancer-ip'].$dirty }">
<label translate class="on-top" for="loadbalancer-ip">IP Address</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="createLoadBalancerDetailsForm['loadbalancer-ip'].$invalid && createLoadBalancerDetailsForm.$dirty"
popover="{$ ::ctrl.loadbalancerIPError $}"
ng-show="loadBalancerDetailsForm['loadbalancer-ip'].$invalid && loadBalancerDetailsForm.$dirty"
popover="{$ ::ctrl.ipError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
<input name="loadbalancer-ip" id="loadbalancer-ip"

View File

@ -17,10 +17,10 @@
'use strict';
angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers')
.controller('AddMembersController', AddMembersController);
.module('horizon.dashboard.project.lbaasv2')
.controller('MemberDetailsController', MemberDetailsController);
AddMembersController.$inject = [
MemberDetailsController.$inject = [
'$scope',
'$compile',
'horizon.dashboard.project.lbaasv2.popovers',
@ -29,18 +29,17 @@
/**
* @ngdoc controller
* @name AddMembersController
* @name MemberDetailsController
* @description
* The `AddMembersController` controller provides functions for adding members to a pool.
* The `MemberDetailsController` 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 popoverTemplates LBaaS v2 popover templates constant.
* @param gettext The horizon gettext function for translation.
* @returns undefined
*/
function AddMembersController($scope, $compile, popoverTemplates, gettext) {
function MemberDetailsController($scope, $compile, popoverTemplates, gettext) {
var ctrl = this;
// Error text for invalid fields

View File

@ -16,7 +16,7 @@
(function() {
'use strict';
describe('Create Load Balancer Add Members Step', function() {
describe('Member Details Step', function() {
var members = [{
id: '1',
name: 'foo',
@ -31,7 +31,7 @@
beforeEach(module('horizon.framework.util.i18n'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
describe('AddMembersController', function() {
describe('MemberDetailsController', function() {
var ctrl, scope;
beforeEach(inject(function($controller) {
@ -43,7 +43,7 @@
members: members
}
};
ctrl = $controller('AddMembersController', { $scope: scope });
ctrl = $controller('MemberDetailsController', { $scope: scope });
}));
it('should define error messages for invalid fields', function() {
@ -77,7 +77,7 @@
});
});
describe('Add Members Template', function() {
describe('Member Details Step Template', function() {
var $scope, $element, popoverContent;
beforeEach(module('templates'));
@ -88,8 +88,7 @@
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');
var markup = $templateCache.get(basePath + 'workflow/members/members.html');
$scope = $injector.get('$rootScope').$new();
$scope.model = {
spec: {

View File

@ -1,9 +1,9 @@
<div ng-controller="AddMembersController as ctrl">
<div ng-controller="MemberDetailsController 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>
<div translate class="subtitle">Add members to the load balancer pool.</div>
<transfer-table tr-model="ctrl.tableData"
limits="::ctrl.tableLimits"
@ -60,25 +60,25 @@
</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 }">
ng-class="{ 'has-error': memberDetailsForm['{$ ::row.id $}-weight'].$invalid && memberDetailsForm['{$ ::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"
ng-show="memberDetailsForm['{$ ::row.id $}-weight'].$invalid && memberDetailsForm.$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 }">
ng-class="{ 'has-error': memberDetailsForm['{$ ::row.id $}-port'].$invalid && memberDetailsForm['{$ ::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"
ng-show="memberDetailsForm['{$ ::row.id $}-port'].$invalid && memberDetailsForm.$dirty"
popover="{$ ::ctrl.portError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>

View File

@ -0,0 +1,106 @@
/*
* 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')
.factory('horizon.dashboard.project.lbaasv2.workflow.modal', modalService);
modalService.$inject = [
'$modal',
'horizon.framework.widgets.toast.service'
];
/**
* @ngdoc service
* @ngname horizon.dashboard.project.lbaasv2.workflow.modal
*
* @description
* Provides the service for opening the LBaaS create / edit modal.
*
* @param $modal The angular bootstrap $modal service.
* @param toastService The horizon toast service.
* @returns The modal service for the LBaaS workflow.
*/
function modalService($modal, toastService) {
var service = {
init: init
};
return service;
//////////////
/**
* @ngdoc method
* @name init
*
* @description
* Initialize a new scope for an LBaaS workflow modal.
*
* @param args An object containing the following properties:
* controller*: Controller to use for the wizard instance.
* message*: String to display using the toast service when wizard completes.
* allowed*: Function used to determine if the workflow action is allowed.
* handle: Function to call after the modal closes, receives the result of wizard submit.
* @returns An object with a single function 'open', used to open the modal.
*/
function init(args) {
return {
allowed: args.allowed,
perform: open
};
/**
* @ngdoc method
* @name open
*
* @description
* Open the modal.
*
* @param item The row item from the table action.
* @returns undefined
*/
function open(item) {
var spec = {
backdrop: 'static',
controller: 'ModalContainerController',
template: '<wizard ng-controller="' + args.controller + '"></wizard>',
windowClass: 'modal-dialog-wizard',
resolve: {
launchContext: function() {
return item;
}
}
};
$modal.open(spec).result.then(onModalClose);
}
function onModalClose(response) {
toastService.add('success', args.message);
if (args.handle) {
args.handle(response);
}
}
}
}
})();

View File

@ -16,17 +16,16 @@
(function() {
'use strict';
describe('LBaaS v2 Load Balancers Table Create Action Modal Service', function() {
var modalService, modal;
describe('LBaaS v2 Workflow Modal Service', function() {
var modalService, modal, response;
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module(function($provide) {
var response = {
response = {
data: {
id: '1'
}
@ -43,62 +42,64 @@
}
};
var policyAPI = {
ifAllowed: function() {
return true;
}
};
$provide.value('$modal', modal);
$provide.value('horizon.app.core.openstack-service-api.policy', policyAPI);
}));
beforeEach(inject(function ($injector) {
modalService = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.modal');
modalService = $injector.get('horizon.dashboard.project.lbaasv2.workflow.modal');
}));
it('should define function for opening a modal', function() {
expect(modalService.perform).toBeDefined();
});
it('should be allowed based on policy', function() {
expect(modalService.allowed()).toBe(true);
it('should define an init function', function() {
expect(modalService.init).toBeDefined();
});
describe('modalService "perform" function tests', function() {
var toastService, $location;
var toastService;
beforeEach(inject(function ($injector) {
toastService = $injector.get('horizon.framework.widgets.toast.service');
$location = $injector.get('$location');
}));
it('calls modal.open', function() {
spyOn(modal, 'open').and.callThrough();
modalService.perform();
modalService.init({}).perform();
expect(modal.open).toHaveBeenCalled();
});
it('calls modal.open with expected values', function() {
spyOn(modal, 'open').and.callThrough();
modalService.perform();
modalService.init({}).perform();
var args = modal.open.calls.argsFor(0)[0];
expect(args.backdrop).toBe('static');
expect(args.controller).toBeDefined();
expect(args.resolve).toBeDefined();
expect(args.resolve.launchContext).toBeNull();
expect(args.resolve.launchContext).toBeDefined();
});
it('redirects upon success', function() {
spyOn(toastService, 'add').and.callThrough();
spyOn($location, 'path').and.callThrough();
modalService.perform();
it('launchContext function returns argument passed to open function', function() {
spyOn(modal, 'open').and.callThrough();
modalService.init({}).perform('foo');
expect(toastService.add).toHaveBeenCalledWith('success', jasmine.any(String));
expect($location.path).toHaveBeenCalledWith('project/ngloadbalancersv2/detail/1');
var args = modal.open.calls.argsFor(0)[0];
expect(args.resolve.launchContext()).toBe('foo');
});
it('shows message upon success', function() {
spyOn(toastService, 'add').and.callThrough();
modalService.init({ message: 'foo' }).perform();
expect(toastService.add).toHaveBeenCalledWith('success', 'foo');
});
it('handles response upon success', function() {
spyOn(toastService, 'add').and.callThrough();
var args = { handle: angular.noop };
spyOn(args, 'handle');
modalService.init(args).perform();
expect(args.handle).toHaveBeenCalledWith(response);
});
});

View File

@ -19,11 +19,10 @@
var push = Array.prototype.push;
angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers')
.factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.model',
createLoadBalancerModel);
.module('horizon.dashboard.project.lbaasv2')
.factory('horizon.dashboard.project.lbaasv2.workflow.model', workflowModel);
createLoadBalancerModel.$inject = [
workflowModel.$inject = [
'$q',
'horizon.app.core.openstack-service-api.neutron',
'horizon.app.core.openstack-service-api.nova',
@ -33,13 +32,12 @@
/**
* @ngdoc service
* @name horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.model
* @name horizon.dashboard.project.lbaasv2.workflow.model
*
* @description
* This is the M part of the MVC design pattern for the create load balancer wizard workflow. It
* is responsible for providing data to the view of each step in the workflow and collecting the
* user's input from the view for creation of the new load balancer. It is also the center point
* of communication between the UI and services API.
* This is the M part of the MVC design pattern for the LBaaS wizard workflow. It is responsible
* for providing data to the view of each step in the workflow and collecting the user's input
* from the view. It is also the center point of communication between the UI and services API.
*
* @param $q The angular service for promises.
* @param neutronAPI The neutron service API.
@ -49,7 +47,7 @@
* @returns The model service for the create load balancer workflow.
*/
function createLoadBalancerModel($q, neutronAPI, novaAPI, lbaasv2API, gettext) {
function workflowModel($q, neutronAPI, novaAPI, lbaasv2API, gettext) {
var initPromise, ports;
/**
@ -60,16 +58,14 @@
initializing: false,
initialized: false,
context: null,
/**
* @name spec
*
* @description
* A dictionary like object containing specification collected from user
* input. Required properties include:
*
* @property {String} name: The new load balancer name.
* @property {String} subnet: The subnet for the load balancer.
* input.
*/
spec: null,
@ -87,21 +83,31 @@
*/
initialize: initialize,
createLoadBalancer: createLoadBalancer
submit: submit
};
/**
* @ngdoc method
* @name createLoadBalancerModel.initialize
* @name workflowModel.initialize
* @returns {promise}
*
* @description
* Send request to get all data to initialize the model.
*
* @param resource Resource type being created / edited, one of 'loadbalancer', 'listener',
* 'pool', 'monitor', or 'members'.
* @param id ID of the resource being edited.
*/
function initialize() {
function initialize(resource, id) {
var promise;
model.context = {
resource: resource,
id: id,
submit: null
};
model.spec = {
loadbalancer: {
name: null,
@ -138,13 +144,56 @@
} else {
model.initializing = true;
promise = $q.all([
lbaasv2API.getLoadBalancers().then(onGetLoadBalancers),
neutronAPI.getSubnets().then(onGetSubnets),
neutronAPI.getPorts().then(onGetPorts),
novaAPI.getServers().then(onGetServers)
]);
var promises = [];
promises.push(lbaasv2API.getLoadBalancers().then(onGetLoadBalancers));
promises.push(neutronAPI.getSubnets().then(onGetSubnets));
promises.push(neutronAPI.getPorts().then(onGetPorts));
promises.push(novaAPI.getServers().then(onGetServers));
model.context.submit = createLoadBalancer;
// // Create load balancer (default)
// if (resource === 'loadbalancer' && !id) {
// promises.push(lbaasv2API.getLoadBalancers().then(onGetLoadBalancers));
// promises.push(neutronAPI.getSubnets().then(onGetSubnets));
// promises.push(neutronAPI.getPorts().then(onGetPorts));
// promises.push(novaAPI.getServers().then(onGetServers));
// model.context.submit = createLoadBalancer;
// }
// // Edit load balancer
// else if (resource === 'loadbalancer' && id) {
// // Get load balancer
// model.context.submit = editLoadBalancer;
// }
// // Create listener
// else if (resource === 'listener' && !id) {
// promises.push(neutronAPI.getSubnets().then(onGetSubnets));
// promises.push(neutronAPI.getPorts().then(onGetPorts));
// promises.push(novaAPI.getServers().then(onGetServers));
// model.context.submit = createListener;
// }
// // Edit listener
// else if (resource === 'listener' && id) {
// // Get listener, pool, members, monitor
// model.context.submit = editListener;
// }
// // Edit pool
// else if (resource === 'pool' && id) {
// // Get pool, members, monitor
// model.context.submit = editPool;
// }
// // Edit monitor
// else if (resource === 'monitor' && id) {
// // Get monitor
// model.context.submit = editMonitor;
// }
// // Edit members
// else if (resource === 'members' && id) {
// // Get pool members by pool id
// model.context.submit = editMembers;
// }
promise = $q.all(promises);
promise.then(onInitSuccess, onInitFail);
}
@ -164,17 +213,25 @@
/**
* @ngdoc method
* @name createLoadBalancerModel.createLoadBalancer
* @name workflowModel.submit
* @returns {promise}
*
* @description
* Send request for creating the load balancer.
* Send request for completing the wizard.
*
* @returns Response from the LBaaS V2 API for creating a load balancer.
* @returns Response from the LBaaS V2 API.
*/
function createLoadBalancer() {
function submit() {
var finalSpec = angular.copy(model.spec);
var resource = model.context.resource;
// Load balancer requires subnet
if (!finalSpec.loadbalancer.subnet) {
delete finalSpec.loadbalancer;
} else {
finalSpec.loadbalancer.subnet = finalSpec.loadbalancer.subnet.id;
}
// Listener requires protocol and port
if (!finalSpec.listener.protocol || !finalSpec.listener.port) {
@ -182,7 +239,9 @@
}
// Pool requires protocol and method, and also the listener
if (!finalSpec.listener || !finalSpec.pool.protocol || !finalSpec.pool.method) {
if (resource !== 'pool' && !finalSpec.listener ||
!finalSpec.pool.protocol ||
!finalSpec.pool.method) {
delete finalSpec.pool;
}
@ -190,9 +249,11 @@
cleanFinalSpecMonitor(finalSpec);
removeNulls(finalSpec);
finalSpec.loadbalancer.subnet = finalSpec.loadbalancer.subnet.id;
return model.context.submit(finalSpec);
}
return lbaasv2API.createLoadBalancer(finalSpec);
function createLoadBalancer(spec) {
return lbaasv2API.createLoadBalancer(spec);
}
function cleanFinalSpecMembers(finalSpec) {

View File

@ -16,7 +16,7 @@
(function() {
'use strict';
describe('LBaaS v2 Create Load Balancer Workflow Model Service', function() {
describe('LBaaS v2 Workflow Model Service', function() {
var model, $q, scope;
beforeEach(module('horizon.framework.util.i18n'));
@ -77,7 +77,7 @@
beforeEach(inject(function ($injector) {
model = $injector.get(
'horizon.dashboard.project.lbaasv2.loadbalancers.actions.create.model'
'horizon.dashboard.project.lbaasv2.workflow.model'
);
$q = $injector.get('$q');
scope = $injector.get('$rootScope').$new();
@ -130,15 +130,19 @@
expect(model.initialize).toBeDefined();
});
it('has a "createLoadBalancer" function', function() {
expect(model.createLoadBalancer).toBeDefined();
it('has a "submit" function', function() {
expect(model.submit).toBeDefined();
});
it('has a "context" object', function() {
expect(model.context).toBeDefined();
});
});
describe('Post initialize model', function() {
beforeEach(function() {
model.initialize();
model.initialize('loadbalancer');
scope.$apply();
});
@ -166,6 +170,12 @@
expect(model.spec.monitor.status).toBe('200');
expect(model.spec.monitor.path).toBe('/');
});
it('should initialize context', function() {
expect(model.context.resource).toBe('loadbalancer');
expect(model.context.id).toBeUndefined();
expect(model.context.submit).toBeDefined();
});
});
describe('Initialization failure', function() {
@ -287,7 +297,7 @@
});
});
describe('Create Load Balancer', function() {
describe('Model submit function', function() {
beforeEach(function() {
model.initialize();
@ -318,7 +328,7 @@
model.spec.monitor.retry = 1;
model.spec.monitor.timeout = 1;
var finalSpec = model.createLoadBalancer();
var finalSpec = model.submit();
expect(finalSpec.loadbalancer.name).toBe('Load Balancer 3');
expect(finalSpec.loadbalancer.description).toBeUndefined();
@ -347,12 +357,20 @@
expect(finalSpec.monitor.timeout).toBe(1);
});
it('should delete load balancer if any required property is not set', function() {
model.spec.loadbalancer.ip = '1.2.3.4';
var finalSpec = model.submit();
expect(finalSpec.loadbalancer).toBeUndefined();
});
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();
var finalSpec = model.submit();
expect(finalSpec.loadbalancer).toBeDefined();
expect(finalSpec.listener).toBeUndefined();
@ -365,7 +383,7 @@
model.spec.listener.protocol = 'HTTPS';
model.spec.listener.port = 80;
var finalSpec = model.createLoadBalancer();
var finalSpec = model.submit();
expect(finalSpec.loadbalancer).toBeDefined();
expect(finalSpec.listener).toBeDefined();
@ -380,7 +398,7 @@
model.spec.pool.protocol = 'HTTP';
model.spec.pool.method = 'LEAST_CONNECTIONS';
var finalSpec = model.createLoadBalancer();
var finalSpec = model.submit();
expect(finalSpec.loadbalancer).toBeDefined();
expect(finalSpec.listener).toBeDefined();
@ -399,7 +417,7 @@
model.spec.monitor.interval = 1;
model.spec.monitor.retry = 1;
var finalSpec = model.createLoadBalancer();
var finalSpec = model.submit();
expect(finalSpec.loadbalancer).toBeDefined();
expect(finalSpec.listener).toBeDefined();

View File

@ -17,26 +17,26 @@
'use strict';
angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers')
.controller('CreateMonitorController', CreateMonitorController);
.module('horizon.dashboard.project.lbaasv2')
.controller('MonitorDetailsController', MonitorDetailsController);
CreateMonitorController.$inject = [
MonitorDetailsController.$inject = [
'horizon.dashboard.project.lbaasv2.patterns',
'horizon.framework.util.i18n.gettext'
];
/**
* @ngdoc controller
* @name CreateMonitorController
* @name MonitorDetailsController
* @description
* The `CreateMonitorController` controller provides functions for
* configuring the health monitor step when creating a new health monitor.
* The `MonitorDetailsController` controller provides functions for
* configuring the health monitor step of the LBaaS wizard.
* @param patterns The LBaaS v2 patterns constant.
* @param gettext The horizon gettext function for translation.
* @returns undefined
*/
function CreateMonitorController(patterns, gettext) {
function MonitorDetailsController(patterns, gettext) {
var ctrl = this;

View File

@ -16,16 +16,16 @@
(function() {
'use strict';
describe('Create Monitor Step', function() {
describe('Monitor Details Step', function() {
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
describe('CreateMonitorController', function() {
describe('MonitorDetailsController', function() {
var ctrl;
beforeEach(inject(function($controller) {
ctrl = $controller('CreateMonitorController');
ctrl = $controller('MonitorDetailsController');
}));
it('should define error messages for invalid fields', function() {

View File

@ -1,9 +1,9 @@
<div ng-controller="CreateMonitorController as ctrl">
<div ng-controller="MonitorDetailsController as ctrl">
<h1 translate>Monitor</h1>
<!--content-->
<div class="content">
<div translate class="subtitle">Provide the details for the new health monitor. The monitor will only be created if values are provided for all fields marked as required. A listener and pool are also required.</div>
<div translate class="subtitle">Provide the details for the health monitor.</div>
<div class="row form-group">
@ -24,10 +24,10 @@
<div class="col-sm-6 col-md-3">
<div class="form-field required monitor-interval"
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-interval'].$invalid && createLoadBalancerMonitorForm['monitor-interval'].$dirty }">
ng-class="{ 'has-error': monitorDetailsForm['monitor-interval'].$invalid && monitorDetailsForm['monitor-interval'].$dirty }">
<label translate class="on-top" for="monitor-interval">Health check interval (sec)</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="createLoadBalancerMonitorForm['monitor-interval'].$invalid && createLoadBalancerMonitorForm.$dirty"
ng-show="monitorDetailsForm['monitor-interval'].$invalid && monitorDetailsForm.$dirty"
popover="{$ ::ctrl.intervalError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
@ -43,10 +43,10 @@
<div class="col-sm-6 col-md-3">
<div class="form-field required monitor-retry"
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-retry'].$invalid && createLoadBalancerMonitorForm['monitor-retry'].$dirty }">
ng-class="{ 'has-error': monitorDetailsForm['monitor-retry'].$invalid && monitorDetailsForm['monitor-retry'].$dirty }">
<label translate class="on-top" for="monitor-retry">Retry count before markdown</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="createLoadBalancerMonitorForm['monitor-retry'].$invalid && createLoadBalancerMonitorForm.$dirty"
ng-show="monitorDetailsForm['monitor-retry'].$invalid && monitorDetailsForm.$dirty"
popover="{$ ::ctrl.retryError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
@ -62,10 +62,10 @@
<div class="col-sm-6 col-md-3">
<div class="form-field required monitor-timeout"
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-timeout'].$invalid && createLoadBalancerMonitorForm['monitor-timeout'].$dirty }">
ng-class="{ 'has-error': monitorDetailsForm['monitor-timeout'].$invalid && monitorDetailsForm['monitor-timeout'].$dirty }">
<label translate class="on-top" for="monitor-timeout">Timeout (sec)</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="createLoadBalancerMonitorForm['monitor-timeout'].$invalid && createLoadBalancerMonitorForm.$dirty"
ng-show="monitorDetailsForm['monitor-timeout'].$invalid && monitorDetailsForm.$dirty"
popover="{$ ::ctrl.timeoutError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
@ -92,10 +92,10 @@
<div class="col-sm-6 col-md-3">
<div class="form-field monitor-status"
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-status'].$invalid && createLoadBalancerMonitorForm['monitor-status'].$dirty }">
ng-class="{ 'has-error': monitorDetailsForm['monitor-status'].$invalid && monitorDetailsForm['monitor-status'].$dirty }">
<label translate class="on-top" for="monitor-status">Expected HTTP status code</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="createLoadBalancerMonitorForm['monitor-status'].$invalid && createLoadBalancerMonitorForm.$dirty"
ng-show="monitorDetailsForm['monitor-status'].$invalid && monitorDetailsForm.$dirty"
popover="{$ ::ctrl.statusError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
@ -111,10 +111,10 @@
<div class="col-sm-6 col-md-3">
<div class="form-field monitor-path"
ng-class="{ 'has-error': createLoadBalancerMonitorForm['monitor-path'].$invalid && createLoadBalancerMonitorForm['monitor-path'].$dirty }">
ng-class="{ 'has-error': monitorDetailsForm['monitor-path'].$invalid && monitorDetailsForm['monitor-path'].$dirty }">
<label translate class="on-top" for="monitor-path">URL path</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="createLoadBalancerMonitorForm['monitor-path'].$invalid && createLoadBalancerMonitorForm.$dirty"
ng-show="monitorDetailsForm['monitor-path'].$invalid && monitorDetailsForm.$dirty"
popover="{$ ::ctrl.pathError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>

View File

@ -17,7 +17,7 @@
'use strict';
angular
.module('horizon.dashboard.project.lbaasv2.loadbalancers')
.module('horizon.dashboard.project.lbaasv2')
.controller('CreatePoolDetailsController', CreatePoolDetailsController);
CreatePoolDetailsController.$inject = [

View File

@ -3,7 +3,7 @@
<!--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 translate class="subtitle">Provide the details for the pool.</div>
<div class="row form-group">

View File

@ -0,0 +1,86 @@
/*
* 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')
.factory('horizon.dashboard.project.lbaasv2.workflow.workflow', lbaasWorkflow);
lbaasWorkflow.$inject = [
'horizon.dashboard.project.lbaasv2.basePath',
'horizon.app.core.workflow.factory',
'horizon.framework.util.i18n.gettext'
];
function lbaasWorkflow(basePath, dashboardWorkflow, gettext) {
var workflowSteps = [
{
id: 'loadbalancer',
title: gettext('Load Balancer Details'),
templateUrl: basePath + 'workflow/loadbalancer/loadbalancer.html',
helpUrl: basePath + 'workflow/loadbalancer/loadbalancer.help.html',
formName: 'loadBalancerDetailsForm'
},
{
id: 'listener',
title: gettext('Listener Details'),
templateUrl: basePath + 'workflow/listener/listener.html',
helpUrl: basePath + 'workflow/listener/listener.help.html',
formName: 'listenerDetailsForm'
},
{
id: 'pool',
title: gettext('Pool Details'),
templateUrl: basePath + 'workflow/pool/pool.html',
helpUrl: basePath + 'workflow/pool/pool.help.html',
formName: 'poolDetailsForm'
},
{
id: 'members',
title: gettext('Pool Members'),
templateUrl: basePath + 'workflow/members/members.html',
helpUrl: basePath + 'workflow/members/members.help.html',
formName: 'memberDetailsForm'
},
{
id: 'monitor',
title: gettext('Monitor Details'),
templateUrl: basePath + 'workflow/monitor/monitor.html',
helpUrl: basePath + 'workflow/monitor/monitor.help.html',
formName: 'monitorDetailsForm'
}
];
return initWorkflow;
function initWorkflow(title, icon, steps) {
return dashboardWorkflow({
title: title,
btnText: {
finish: title
},
btnIcon: {
finish: icon
},
steps: steps ? workflowSteps.filter(function(step) {
return steps.indexOf(step.id) > -1;
}) : workflowSteps
});
}
}
})();

View File

@ -0,0 +1,87 @@
/*
* 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 Workflow Service', function() {
var workflowService;
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.framework.util'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(inject(function ($injector) {
workflowService = $injector.get(
'horizon.dashboard.project.lbaasv2.workflow.workflow'
);
}));
it('should be defined', function () {
expect(workflowService).toBeDefined();
});
it('should have a title property', function () {
var workflow = workflowService('My Workflow');
expect(workflow.title).toBe('My Workflow');
});
it('should have default steps defined', function () {
var workflow = workflowService('My Workflow');
expect(workflow.steps).toBeDefined();
expect(workflow.steps.length).toBe(5);
var forms = [
'loadBalancerDetailsForm',
'listenerDetailsForm',
'poolDetailsForm',
'memberDetailsForm',
'monitorDetailsForm'
];
forms.forEach(function(expectedForm, idx) {
expect(workflow.steps[idx].formName).toBe(expectedForm);
});
});
it('can filter steps', function () {
var workflow = workflowService('My Workflow', 'foo', ['listener', 'pool']);
expect(workflow.steps).toBeDefined();
expect(workflow.steps.length).toBe(2);
var forms = [
'listenerDetailsForm',
'poolDetailsForm'
];
forms.forEach(function(expectedForm, idx) {
expect(workflow.steps[idx].formName).toBe(expectedForm);
});
});
it('can be extended', function () {
var workflow = workflowService('My Workflow');
expect(workflow.append).toBeDefined();
expect(workflow.prepend).toBeDefined();
expect(workflow.after).toBeDefined();
expect(workflow.replace).toBeDefined();
expect(workflow.remove).toBeDefined();
expect(workflow.addController).toBeDefined();
});
});
})();