diff --git a/neutron_lbaas_dashboard/api/rest/lbaasv2.py b/neutron_lbaas_dashboard/api/rest/lbaasv2.py index e61a526..f617e4a 100644 --- a/neutron_lbaas_dashboard/api/rest/lbaasv2.py +++ b/neutron_lbaas_dashboard/api/rest/lbaasv2.py @@ -230,3 +230,17 @@ class LoadBalancer(generic.View): """ lb = neutronclient(request).show_loadbalancer(loadbalancer_id) return lb.get('loadbalancer') + + @rest_utils.ajax() + def put(self, request, loadbalancer_id): + """Edit a load balancer. + + """ + data = request.DATA + spec = {} + if data['loadbalancer'].get('name'): + spec['name'] = data['loadbalancer']['name'] + if data['loadbalancer'].get('description'): + spec['description'] = data['loadbalancer']['description'] + return neutronclient(request).update_loadbalancer( + loadbalancer_id, {'loadbalancer': spec}).get('loadbalancer') diff --git a/neutron_lbaas_dashboard/enabled/_1481_project_ng_loadbalancersv2_panel.py b/neutron_lbaas_dashboard/enabled/_1481_project_ng_loadbalancersv2_panel.py index 8d59498..7e2f8d4 100644 --- a/neutron_lbaas_dashboard/enabled/_1481_project_ng_loadbalancersv2_panel.py +++ b/neutron_lbaas_dashboard/enabled/_1481_project_ng_loadbalancersv2_panel.py @@ -38,8 +38,11 @@ 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/row-actions.service.js', ('dashboard/project/lbaasv2/loadbalancers/actions/create/' 'wizard.controller.js'), + ('dashboard/project/lbaasv2/loadbalancers/actions/edit/' + 'wizard.controller.js'), 'dashboard/project/lbaasv2/workflow/modal.service.js', 'dashboard/project/lbaasv2/workflow/model.service.js', 'dashboard/project/lbaasv2/workflow/workflow.service.js', @@ -60,8 +63,12 @@ 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/' + 'row-actions.service.spec.js'), ('dashboard/project/lbaasv2/loadbalancers/actions/create/' 'wizard.controller.spec.js'), + ('dashboard/project/lbaasv2/loadbalancers/actions/edit/' + 'wizard.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', diff --git a/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.js b/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.js index 59dad1e..7b062ce 100644 --- a/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.js +++ b/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.js @@ -38,7 +38,8 @@ var service = { getLoadBalancers: getLoadBalancers, getLoadBalancer: getLoadBalancer, - createLoadBalancer: createLoadBalancer + createLoadBalancer: createLoadBalancer, + editLoadBalancer: editLoadBalancer }; return service; @@ -93,5 +94,21 @@ }); } + /** + * @name horizon.app.core.openstack-service-api.lbaasv2.editLoadBalancer + * @description + * Edit a load balancer + * @param {string} id + * @param {object} spec + * Specifies the data used to update the load balancer. + */ + + function editLoadBalancer(id, spec) { + return apiService.put('/api/lbaas/loadbalancers/' + id + '/', spec) + .error(function () { + toastService.add('error', gettext('Unable to update load balancer.')); + }); + } + } }()); diff --git a/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.spec.js b/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.spec.js index b917ed2..720d289 100644 --- a/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.spec.js +++ b/neutron_lbaas_dashboard/static/app/core/openstack-service-api/lbaasv2.service.spec.js @@ -60,6 +60,16 @@ "testInput": [ { name: 'loadbalancer-1' } ] + }, + { + "func": "editLoadBalancer", + "method": "put", + "path": "/api/lbaas/loadbalancers/1234/", + "error": "Unable to update load balancer.", + "data": { name: 'loadbalancer-1' }, + "testInput": [ + '1234', { name: 'loadbalancer-1' } + ] } ]; diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/edit/wizard.controller.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/edit/wizard.controller.js new file mode 100644 index 0000000..114df56 --- /dev/null +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/edit/wizard.controller.js @@ -0,0 +1,42 @@ +/* + * 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('EditLoadBalancerWizardController', EditLoadBalancerWizardController); + + EditLoadBalancerWizardController.$inject = [ + '$scope', + 'horizon.dashboard.project.lbaasv2.workflow.model', + 'horizon.dashboard.project.lbaasv2.workflow.workflow', + 'horizon.framework.util.i18n.gettext' + ]; + + function EditLoadBalancerWizardController($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('Update Load Balancer'), + 'fa fa-pencil', + ['loadbalancer']); + scope.model.initialize('loadbalancer', scope.launchContext.id); + } + +})(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/edit/wizard.controller.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/edit/wizard.controller.spec.js new file mode 100644 index 0000000..231dcf4 --- /dev/null +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/edit/wizard.controller.spec.js @@ -0,0 +1,67 @@ +/* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function () { + 'use strict'; + + describe('LBaaS v2 Edit Load Balancer Wizard Controller', function() { + var ctrl, workflowSpy; + var model = { + submit: function() { + return 'updated'; + }, + initialize: angular.noop + }; + var workflow = 'foo'; + var scope = { + launchContext: { id: '1' } + }; + + beforeEach(module('horizon.framework.util')); + beforeEach(module('horizon.dashboard.project.lbaasv2')); + beforeEach(module(function ($provide) { + workflowSpy = jasmine.createSpy('workflow').and.returnValue(workflow); + $provide.value('horizon.dashboard.project.lbaasv2.workflow.model', model); + $provide.value('horizon.dashboard.project.lbaasv2.workflow.workflow', workflowSpy); + })); + beforeEach(inject(function ($controller) { + spyOn(model, 'initialize'); + ctrl = $controller('EditLoadBalancerWizardController', { $scope: scope }); + })); + + it('defines the controller', function() { + expect(ctrl).toBeDefined(); + }); + + it('calls initialize on the given model', function() { + expect(model.initialize).toHaveBeenCalledWith('loadbalancer', '1'); + }); + + it('sets scope.workflow to the given workflow', function() { + expect(scope.workflow).toBe(workflow); + }); + + it('initializes workflow with correct properties', function() { + expect(workflowSpy).toHaveBeenCalledWith('Update Load Balancer', + 'fa fa-pencil', ['loadbalancer']); + }); + + it('defines scope.submit', function() { + expect(scope.submit).toBe(model.submit); + expect(scope.submit()).toBe('updated'); + }); + }); + +})(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.js new file mode 100644 index 0000000..4af4e8e --- /dev/null +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.js @@ -0,0 +1,89 @@ +/* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + 'use strict'; + + angular + .module('horizon.dashboard.project.lbaasv2.loadbalancers') + .factory('horizon.dashboard.project.lbaasv2.loadbalancers.actions.rowActions', + tableRowActions); + + tableRowActions.$inject = [ + '$q', + '$route', + 'horizon.dashboard.project.lbaasv2.workflow.modal', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.q.extensions', + 'horizon.framework.util.i18n.gettext' + ]; + + /** + * @ngdoc service + * @ngname horizon.dashboard.project.lbaasv2.loadbalancers.actions.rowActions + * + * @description + * Provides the service for the Load Balancers table row actions. + * + * @param $q The angular service for promises. + * @param $route The angular $route service. + * @param workflowModal The LBaaS workflow modal service. + * @param policy The horizon policy service. + * @param qExtensions Horizon extensions to the $q service. + * @param gettext The horizon gettext function for translation. + * @returns Load balancers table batch actions service object. + */ + + function tableRowActions($q, $route, workflowModal, policy, qExtensions, gettext) { + + var edit = workflowModal.init({ + controller: 'EditLoadBalancerWizardController', + message: gettext('The load balancer has been updated.'), + handle: onEdit, + allowed: canEdit + }); + + var service = { + actions: actions + }; + + return service; + + /////////////// + + function actions() { + return [{ + service: edit, + template: { + text: gettext('Edit') + } + }]; + } + + function canEdit(item) { + return $q.all([ + qExtensions.booleanAsPromise(item.provisioning_status === 'ACTIVE'), + // This rule is made up and should therefore always pass. At some point there will + // likely be a valid rule similar to this that we will want to use. + policy.ifAllowed({ rules: [['neutron', 'update_loadbalancer']] }) + ]); + } + + function onEdit(/*response*/) { + $route.reload(); + } + } + +})(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.spec.js new file mode 100644 index 0000000..9e62333 --- /dev/null +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/actions/row-actions.service.spec.js @@ -0,0 +1,98 @@ +/* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + 'use strict'; + + describe('LBaaS v2 Load Balancers Table Row Actions Service', function() { + var rowActionsService, scope, $route, actions, policy; + + function canEdit(item) { + spyOn(policy, 'ifAllowed').and.returnValue(true); + var promise = actions[0].service.allowed(item); + var allowed; + promise.then(function() { + allowed = true; + }, function() { + allowed = false; + }); + scope.$apply(); + expect(policy.ifAllowed).toHaveBeenCalledWith({rules: [['neutron', 'update_loadbalancer']]}); + return allowed; + } + + beforeEach(module('horizon.framework.util')); + beforeEach(module('horizon.framework.conf')); + beforeEach(module('horizon.framework.widgets.toast')); + beforeEach(module('horizon.app.core.openstack-service-api')); + beforeEach(module('horizon.dashboard.project.lbaasv2')); + + beforeEach(module(function($provide) { + var response = { + data: { + id: '1' + } + }; + var modal = { + open: function() { + return { + result: { + then: function(func) { + func(response); + } + } + }; + } + }; + $provide.value('$modal', modal); + })); + + beforeEach(inject(function ($injector) { + scope = $injector.get('$rootScope').$new(); + $route = $injector.get('$route'); + policy = $injector.get('horizon.app.core.openstack-service-api.policy'); + rowActionsService = $injector.get( + 'horizon.dashboard.project.lbaasv2.loadbalancers.actions.rowActions'); + actions = rowActionsService.actions(); + })); + + it('should define correct table row actions', function() { + expect(actions.length).toBe(1); + expect(actions[0].template.text).toBe('Edit'); + }); + + it('should allow editing an ACTIVE load balancer', function() { + expect(canEdit({provisioning_status: 'ACTIVE'})).toBe(true); + }); + + it('should not allow editing a non-ACTIVE load balancer', function() { + expect(canEdit({provisioning_status: 'PENDING_UPDATE'})).toBe(false); + }); + + it('should have the "allowed" and "perform" functions', function() { + actions.forEach(function(action) { + expect(action.service.allowed).toBeDefined(); + expect(action.service.perform).toBeDefined(); + }); + }); + + it('should reload table after edit', function() { + spyOn($route, 'reload').and.callThrough(); + actions[0].service.perform(); + expect($route.reload).toHaveBeenCalled(); + }); + + }); +})(); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.js index e495048..7cca2d6 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.js @@ -22,6 +22,7 @@ LoadBalancerDetailController.$inject = [ 'horizon.app.core.openstack-service-api.lbaasv2', + 'horizon.dashboard.project.lbaasv2.loadbalancers.actions.rowActions', '$routeParams' ]; @@ -33,14 +34,16 @@ * Controller for the LBaaS v2 load balancers detail page. * * @param api The LBaaS v2 API service. + * @param rowActions The load balancer row actions service. * @param $routeParams The angular $routeParams service. * @returns undefined */ - function LoadBalancerDetailController(api, $routeParams) { + function LoadBalancerDetailController(api, rowActions, $routeParams) { var ctrl = this; - ctrl.loadbalancer = {}; + ctrl.loadbalancer = null; + ctrl.actions = rowActions.actions; var loadbalancerId = $routeParams.loadbalancerId; diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.spec.js b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.spec.js index aeed16f..d789a90 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.spec.js +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.controller.spec.js @@ -29,12 +29,16 @@ /////////////////////// - beforeEach(module('horizon.framework.util.http')); beforeEach(module('horizon.framework.widgets.toast')); beforeEach(module('horizon.framework.conf')); + beforeEach(module('horizon.framework.util')); beforeEach(module('horizon.app.core.openstack-service-api')); beforeEach(module('horizon.dashboard.project.lbaasv2')); + beforeEach(module(function($provide) { + $provide.value('$modal', {}); + })); + beforeEach(inject(function($injector) { loadbalancer = { id: '1234' }; lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2'); diff --git a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.html b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.html index 866e1af..ad0898f 100644 --- a/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.html +++ b/neutron_lbaas_dashboard/static/dashboard/project/lbaasv2/loadbalancers/detail.html @@ -7,6 +7,8 @@
{$ ::ctrl.loadbalancer.description $}