From 5424c6306a468c5fc067e626fe78f30f734941a1 Mon Sep 17 00:00:00 2001 From: Shu Muto Date: Mon, 10 Jul 2017 20:52:16 +0900 Subject: [PATCH] Add create and import key pair actions This patch adds create and import keypair actions into Angularized key pair panel as global action. Also, to load public key from file, this patch uses load-edit directive. To use load-edit directive from angular-schema-form, i.e. from separated scope, this patch enables to configure callback function to get content of textarea in load-edit. Change-Id: Ie38bff8fba90de99095b589d70da45dcb202fa56 Imprements: blueprint ng-keypair Needed-By: I3d6ae0b513120cf50c89b40234b602b816adfd48 Needed-By: I9200baa585c18095656d0459c649391b61b553a2 --- doc/source/configuration/settings.rst | 2 +- .../action-list/actions-batch.template.html | 1 + .../action-list/actions-row.template.html | 1 + .../widgets/action-list/actions.service.js | 1 + .../widgets/load-edit/load-edit.directive.js | 4 +- openstack_dashboard/settings.py | 2 +- .../static/app/core/keypairs/_keypairs.scss | 4 + .../core/keypairs/actions/actions.module.js | 21 +++ .../keypairs/actions/create.description.html | 5 + .../core/keypairs/actions/create.service.js | 159 ++++++++++++++++++ .../keypairs/actions/create.service.spec.js | 74 ++++++++ .../keypairs/actions/import.description.html | 23 +++ .../actions/import.public-key.controller.js | 43 +++++ .../import.public-key.controller.spec.js | 54 ++++++ .../keypairs/actions/import.public-key.html | 9 + .../core/keypairs/actions/import.service.js | 157 +++++++++++++++++ .../keypairs/actions/import.service.spec.js | 75 +++++++++ .../bp-ng-keypairs-876c38a1a8aed60f.yaml | 11 +- 18 files changed, 639 insertions(+), 7 deletions(-) create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/create.description.html create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/create.service.js create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/create.service.spec.js create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/import.description.html create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.js create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.spec.js create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/import.public-key.html create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/import.service.js create mode 100644 openstack_dashboard/static/app/core/keypairs/actions/import.service.spec.js diff --git a/doc/source/configuration/settings.rst b/doc/source/configuration/settings.rst index 814bee020b..66ec835446 100644 --- a/doc/source/configuration/settings.rst +++ b/doc/source/configuration/settings.rst @@ -48,7 +48,7 @@ Default: { 'images_panel': True, - 'key_pairs_panel': False, + 'key_pairs_panel': True, 'flavors_panel': False, 'domains_panel': False, 'users_panel': False, diff --git a/horizon/static/framework/widgets/action-list/actions-batch.template.html b/horizon/static/framework/widgets/action-list/actions-batch.template.html index 7fa8e20c1e..5920809d95 100644 --- a/horizon/static/framework/widgets/action-list/actions-batch.template.html +++ b/horizon/static/framework/widgets/action-list/actions-batch.template.html @@ -1,3 +1,4 @@ + $text$ diff --git a/horizon/static/framework/widgets/action-list/actions-row.template.html b/horizon/static/framework/widgets/action-list/actions-row.template.html index b6828c8d48..33b59bee31 100644 --- a/horizon/static/framework/widgets/action-list/actions-row.template.html +++ b/horizon/static/framework/widgets/action-list/actions-row.template.html @@ -1,3 +1,4 @@ + $text$ diff --git a/horizon/static/framework/widgets/action-list/actions.service.js b/horizon/static/framework/widgets/action-list/actions.service.js index da5d7ca3e4..6b9cadf215 100644 --- a/horizon/static/framework/widgets/action-list/actions.service.js +++ b/horizon/static/framework/widgets/action-list/actions.service.js @@ -208,6 +208,7 @@ .replace( '$action-classes$', getActionClasses(action, index, permittedActions.length) ) + .replace('$icon$', action.template.icon) .replace('$text$', action.template.text) .replace('$title$', action.template.title) .replace('$description$', action.template.description) diff --git a/horizon/static/framework/widgets/load-edit/load-edit.directive.js b/horizon/static/framework/widgets/load-edit/load-edit.directive.js index 902c11f1e9..7b5e94babc 100644 --- a/horizon/static/framework/widgets/load-edit/load-edit.directive.js +++ b/horizon/static/framework/widgets/load-edit/load-edit.directive.js @@ -52,7 +52,8 @@ maxBytes: '@', key: '@', required: '=', - rows: '@' + rows: '@', + onTextareaChange: '&' }, link: link, templateUrl: basePath + 'load-edit.html' @@ -113,6 +114,7 @@ } else { $scope.textModified = false; } + $scope.onTextareaChange({textContent: $scope.textContent}); }); } diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index e4f4d3718a..3d6810ac6e 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -325,7 +325,7 @@ COMPRESS_OFFLINE_CONTEXT = 'horizon.themes.offline_context' # Dictionary of currently available angular features ANGULAR_FEATURES = { 'images_panel': True, - 'key_pairs_panel': False, + 'key_pairs_panel': True, 'flavors_panel': False, 'domains_panel': False, 'users_panel': False, diff --git a/openstack_dashboard/static/app/core/keypairs/_keypairs.scss b/openstack_dashboard/static/app/core/keypairs/_keypairs.scss index 57b15c80f9..d8012004c0 100644 --- a/openstack_dashboard/static/app/core/keypairs/_keypairs.scss +++ b/openstack_dashboard/static/app/core/keypairs/_keypairs.scss @@ -21,3 +21,7 @@ hz-details { } } } + +textarea#public_key { + height: 22em; +} \ No newline at end of file diff --git a/openstack_dashboard/static/app/core/keypairs/actions/actions.module.js b/openstack_dashboard/static/app/core/keypairs/actions/actions.module.js index e37262932c..c3e7ce8cff 100644 --- a/openstack_dashboard/static/app/core/keypairs/actions/actions.module.js +++ b/openstack_dashboard/static/app/core/keypairs/actions/actions.module.js @@ -30,16 +30,37 @@ registerKeypairActions.$inject = [ 'horizon.framework.conf.resource-type-registry.service', + 'horizon.app.core.keypairs.actions.create.service', + 'horizon.app.core.keypairs.actions.import.service', 'horizon.app.core.keypairs.actions.delete.service', 'horizon.app.core.keypairs.resourceType' ]; function registerKeypairActions( registry, + createKeypairService, + importKeypairService, deleteKeypairService, resourceType ) { var keypairResourceType = registry.getResourceType(resourceType); + keypairResourceType.globalActions + .append({ + id: 'createKeypairService', + service: createKeypairService, + template: { + type: 'create', + text: gettext('Create Key Pair') + } + }) + .append({ + id: 'importKeypairService', + service: importKeypairService, + template: { + text: gettext('Import Public Key'), + icon: 'upload' + } + }); keypairResourceType.batchActions .append({ id: 'batchDeleteKeypairAction', diff --git a/openstack_dashboard/static/app/core/keypairs/actions/create.description.html b/openstack_dashboard/static/app/core/keypairs/actions/create.description.html new file mode 100644 index 0000000000..28b8ad924b --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/actions/create.description.html @@ -0,0 +1,5 @@ +

+ Key Pairs are how you login to your instance after it is launched. + Choose a key pair name you will recognize. + Names may only include alphanumeric characters, spaces, or dashes. +

\ No newline at end of file diff --git a/openstack_dashboard/static/app/core/keypairs/actions/create.service.js b/openstack_dashboard/static/app/core/keypairs/actions/create.service.js new file mode 100644 index 0000000000..c65c6dd414 --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/actions/create.service.js @@ -0,0 +1,159 @@ +/** + * 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'; + + /** + * @ngdoc overview + * @name horizon.app.core.keypairs.create.service + * @description Service for the key pair create modal + */ + angular + .module('horizon.app.core.keypairs.actions') + .factory('horizon.app.core.keypairs.actions.create.service', createService); + + createService.$inject = [ + 'horizon.app.core.keypairs.basePath', + 'horizon.app.core.keypairs.resourceType', + 'horizon.app.core.openstack-service-api.nova', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.file.text-download', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service' + ]; + + function createService( + basePath, resourceType, nova, policy, actionResult, download, modal, toast + ) { + + var keypairs = []; + var caption = gettext("Create Key Pair"); + var invalidMsg = gettext("Key pair already exists."); + + // schema + var schema = { + type: "object", + properties: { + "name": { + title: gettext("Key Pair Name"), + type: "string", + pattern: "^[A-Za-z0-9 -]+$" + } + } + }; + + // form + var form = [ + { + type: "section", + htmlClass: "row", + items: [ + { + type: "section", + htmlClass: "col-sm-6", + items: [ + { + key: "name", + validationMessage: { + keypairExists: invalidMsg + }, + $validators: { + keypairExists: function (name) { + return (keypairs.indexOf(name) === -1); + } + }, + required: true + } + ] + }, + { + type: "section", + htmlClass: "col-sm-6", + items: [ + { + type: "template", + templateUrl: basePath + "actions/create.description.html" + } + ] + } + ] + } + ]; + + // model + var model; + + var service = { + perform: perform, + allowed: allowed, + getKeypairs: getKeypairs + }; + + return service; + + ////////////// + + function allowed() { + return policy.ifAllowed({ rules: [['compute', 'os_compute_api:os-keypairs:create']] }); + } + + function perform() { + getKeypairs(); + model = { + name: "" + }; + var config = { + "title": caption, + "submitText": caption, + "schema": schema, + "form": form, + "model": model, + "submitIcon": "plus" + }; + return modal.open(config).then(submit); + } + + function submit(context) { + return nova.createKeypair(context.model).then(success); + } + + /** + * @ngdoc function + * @name success + * @description + * Informs the user about the created key pair. + * @param {Object} keypair The new key pair object + * @returns {undefined} No return value + */ + function success(response) { + var successMsg = gettext('Key pair %(name)s was successfully created.'); + toast.add('success', interpolate(successMsg, { name: response.data.name }, true)); + download.downloadTextFile(response.data.private_key, response.data.name + '.pem'); + var result = actionResult.getActionResult().created(resourceType, response.data.name); + return result.result; + } + + function getKeypairs() { + nova.getKeypairs().then(function(response) { + keypairs = response.data.items.map(getName); + }); + } + + function getName(item) { + return item.keypair.name; + } + } +})(); diff --git a/openstack_dashboard/static/app/core/keypairs/actions/create.service.spec.js b/openstack_dashboard/static/app/core/keypairs/actions/create.service.spec.js new file mode 100644 index 0000000000..afbbf39ce8 --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/actions/create.service.spec.js @@ -0,0 +1,74 @@ +/** + * 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('horizon.app.core.keypairs.actions.create.service', function() { + + var service, nova, $q, $scope, deferred, deferredKeypairs, deferredNewKeypair, toast; + var model = { + name: "Hiroshige" + }; + var modal = { + open: function (config) { + config.model = model; + deferred = $q.defer(); + deferred.resolve(config); + return deferred.promise; + } + }; + + /////////////////// + + beforeEach(module('horizon.app.core')); + beforeEach(module('horizon.framework')); + beforeEach(module('horizon.app.core.keypairs.actions')); + + beforeEach(module(function($provide) { + $provide.value('horizon.framework.widgets.form.ModalFormService', modal); + })); + + beforeEach(inject(function($injector, _$rootScope_, _$q_) { + $scope = _$rootScope_.$new(); + $q = _$q_; + service = $injector.get('horizon.app.core.keypairs.actions.create.service'); + nova = $injector.get('horizon.app.core.openstack-service-api.nova'); + toast = $injector.get('horizon.framework.widgets.toast.service'); + deferredKeypairs = $q.defer(); + deferredKeypairs.resolve({data: {items: [{keypair: {name: "Hokusai"}}]}}); + spyOn(nova, 'getKeypairs').and.returnValue(deferredKeypairs.promise); + deferredNewKeypair = $q.defer(); + deferredNewKeypair.resolve({data: {items: [{keypair: {name: "Hiroshige"}}]}}); + spyOn(nova, 'createKeypair').and.returnValue(deferredNewKeypair.promise); + spyOn(modal, 'open').and.callThrough(); + spyOn(toast, 'add').and.callFake(angular.noop); + })); + + it('should check the policy if the user is allowed to create key pair', function() { + var allowed = service.allowed(); + expect(allowed).toBeTruthy(); + }); + + it('should open the modal and submit', inject(function() { + service.perform(); + expect(nova.getKeypairs).toHaveBeenCalled(); + expect(modal.open).toHaveBeenCalled(); + + $scope.$apply(); + expect(nova.createKeypair).toHaveBeenCalled(); + expect(toast.add).toHaveBeenCalled(); + })); + }); +})(); diff --git a/openstack_dashboard/static/app/core/keypairs/actions/import.description.html b/openstack_dashboard/static/app/core/keypairs/actions/import.description.html new file mode 100644 index 0000000000..cd114d7d32 --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/actions/import.description.html @@ -0,0 +1,23 @@ +

+ Key Pairs are how you login to your instance after it is launched. + Choose a key pair name you will recognize and paste your SSH public key into the + space provided. +

+ +

+ There are two ways to generate a key pair. From a Linux system, + generate the key pair with the ssh-keygen command: +

+

+ ssh-keygen -t rsa -f cloud.key +

+

+ This command generates a pair of keys: a private key (cloud.key) + and a public key (cloud.key.pub). +

+

+ From a Windows system, you can use PuTTYGen to create private/public keys. + Use the PuTTY Key Generator to create and save the keys, then copy + the public key in the red highlighted box to your .ssh/authorized_keys + file. +

diff --git a/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.js b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.js new file mode 100644 index 0000000000..71ef415dde --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.js @@ -0,0 +1,43 @@ +/** + * 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'; + + /** + * @ngdoc controller + * @name horizon.app.core.keypairs.actions.ImportPublicKeyController + * @ngController + * + * @description + * Controller for the loading public key file + */ + angular + .module('horizon.app.core.keypairs.actions') + .controller('horizon.app.core.keypairs.actions.ImportPublicKeyController', + importPublicKeyController); + + importPublicKeyController.$inject = [ + '$scope' + ]; + + function importPublicKeyController($scope) { + var ctrl = this; + ctrl.title = $scope.schema.properties.public_key.title; + ctrl.public_key = ""; + ctrl.onPublicKeyChange = function (publicKey) { + $scope.model.public_key = publicKey; + }; + } +})(); diff --git a/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.spec.js b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.spec.js new file mode 100644 index 0000000000..08e98ef4c2 --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.spec.js @@ -0,0 +1,54 @@ +/** + * 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('horizon.app.core.keypairs.actions.ImportPublicKeyController', function() { + var ctrl, scope; + + beforeEach(module('horizon.app.core.keypairs')); + beforeEach(inject(function ($injector, _$rootScope_) { + scope = _$rootScope_.$new(); + scope.schema = { + properties: { + public_key: { + title: 'Public Key' + } + } + }; + scope.model = { + public_key: '' + }; + + var controller = $injector.get('$controller'); + ctrl = controller( + 'horizon.app.core.keypairs.actions.ImportPublicKeyController', + { $scope: scope } + ); + })); + + it('gets title from scope.schema.properties.public_key.title', function() { + expect(ctrl.title).toBe('Public Key'); + }); + + it('sets public key into scope.model.public_key', function() { + var key = 'public key string'; + ctrl.onPublicKeyChange(key); + expect(scope.model.public_key).toBeDefined(key); + }); + + }); + +})(); diff --git a/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.html b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.html new file mode 100644 index 0000000000..5134c40222 --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/actions/import.public-key.html @@ -0,0 +1,9 @@ +
+ + +
\ No newline at end of file diff --git a/openstack_dashboard/static/app/core/keypairs/actions/import.service.js b/openstack_dashboard/static/app/core/keypairs/actions/import.service.js new file mode 100644 index 0000000000..bece383bfa --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/actions/import.service.js @@ -0,0 +1,157 @@ +/** + * 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'; + + /** + * @ngdoc overview + * @name horizon.app.core.keypairs.import.service + * @description Service for the key pair import modal + */ + angular + .module('horizon.app.core.keypairs.actions') + .factory('horizon.app.core.keypairs.actions.import.service', importService); + + importService.$inject = [ + 'horizon.app.core.keypairs.basePath', + 'horizon.app.core.keypairs.resourceType', + 'horizon.app.core.openstack-service-api.nova', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service' + ]; + + function importService( + basePath, resourceType, nova, policy, actionResult, modal, toast + ) { + + var keypairs = []; + var caption = gettext("Import Public Key"); + var invalidMsg = gettext("Key pair already exists."); + + // schema + var schema = { + type: "object", + properties: { + "name": { + title: gettext("Key Pair Name"), + type: "string", + pattern: "^[A-Za-z0-9 -]+$" + }, + "public_key": { + title: gettext("Public Key"), + type: "string" + } + } + }; + + // form + var form = [ + { + type: "section", + htmlClass: "row", + items: [ + { + type: "section", + htmlClass: "col-sm-6", + items: [ + { + key: "name", + validationMessage: { + keypairExists: invalidMsg + }, + $validators: { + keypairExists: function (name) { + return (keypairs.indexOf(name) === -1); + } + }, + required: true + }, + { + type: "template", + templateUrl: basePath + "actions/import.public-key.html" + } + ] + }, + { + type: "section", + htmlClass: "col-sm-6", + items: [ + { + type: "template", + templateUrl: basePath + "actions/import.description.html" + } + ] + } + ] + } + ]; + + // model + var model; + + var service = { + perform: perform, + allowed: allowed + }; + + return service; + + ////////////// + + function allowed() { + return policy.ifAllowed({ rules: [['compute', 'os_compute_api:os-keypairs:create']] }); + } + + function perform() { + getKeypairs(); + model = { + name: "", + public_key: "" + }; + var config = { + "title": caption, + "submitText": caption, + "schema": schema, + "form": form, + "model": model, + "submitIcon": "upload" + }; + return modal.open(config).then(submit); + } + + function submit(context) { + return nova.createKeypair(context.model).then(success); + } + + function success(response) { + var successMsg = gettext('Successfully imported key pair %(name)s.'); + toast.add('success', interpolate(successMsg, { name: response.data.name }, true)); + var result = actionResult.getActionResult().created(resourceType, response.data.name); + return result.result; + } + + function getKeypairs() { + nova.getKeypairs().then(function(response) { + keypairs = response.data.items.map(getName); + }); + } + + function getName(item) { + return item.keypair.name; + } + } +})(); diff --git a/openstack_dashboard/static/app/core/keypairs/actions/import.service.spec.js b/openstack_dashboard/static/app/core/keypairs/actions/import.service.spec.js new file mode 100644 index 0000000000..0af015375c --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/actions/import.service.spec.js @@ -0,0 +1,75 @@ +/** + * 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('horizon.app.core.keypairs.actions.import.service', function() { + + var service, nova, $q, $scope, deferred, deferredKeypairs, deferredNewKeypair, toast; + var model = { + name: "Hiroshige", + public_key: "secret" + }; + var modal = { + open: function (config) { + config.model = model; + deferred = $q.defer(); + deferred.resolve(config); + return deferred.promise; + } + }; + + /////////////////// + + beforeEach(module('horizon.app.core')); + beforeEach(module('horizon.framework')); + beforeEach(module('horizon.app.core.keypairs.actions')); + + beforeEach(module(function($provide) { + $provide.value('horizon.framework.widgets.form.ModalFormService', modal); + })); + + beforeEach(inject(function($injector, _$rootScope_, _$q_) { + $scope = _$rootScope_.$new(); + $q = _$q_; + service = $injector.get('horizon.app.core.keypairs.actions.import.service'); + nova = $injector.get('horizon.app.core.openstack-service-api.nova'); + toast = $injector.get('horizon.framework.widgets.toast.service'); + deferredKeypairs = $q.defer(); + deferredKeypairs.resolve({data: {items: [{keypair: {name: "Hokusai"}}]}}); + spyOn(nova, 'getKeypairs').and.returnValue(deferredKeypairs.promise); + deferredNewKeypair = $q.defer(); + deferredNewKeypair.resolve({data: {items: [{keypair: {name: "Hiroshige"}}]}}); + spyOn(nova, 'createKeypair').and.returnValue(deferredNewKeypair.promise); + spyOn(modal, 'open').and.callThrough(); + spyOn(toast, 'add').and.callFake(angular.noop); + })); + + it('should check the policy if the user is allowed to import key pair', function() { + var allowed = service.allowed(); + expect(allowed).toBeTruthy(); + }); + + it('should open the modal and submit', inject(function() { + service.perform(); + expect(nova.getKeypairs).toHaveBeenCalled(); + expect(modal.open).toHaveBeenCalled(); + + $scope.$apply(); + expect(nova.createKeypair).toHaveBeenCalled(); + expect(toast.add).toHaveBeenCalled(); + })); + }); +})(); diff --git a/releasenotes/notes/bp-ng-keypairs-876c38a1a8aed60f.yaml b/releasenotes/notes/bp-ng-keypairs-876c38a1a8aed60f.yaml index 840bd7e7fd..d6372b01bc 100644 --- a/releasenotes/notes/bp-ng-keypairs-876c38a1a8aed60f.yaml +++ b/releasenotes/notes/bp-ng-keypairs-876c38a1a8aed60f.yaml @@ -2,7 +2,10 @@ features: - | [`blueprint ng-keypairs `_] - Add Angular Key Pairs panel. The Key Pairs panel allows users to view - a list of created or imported key pairs. This panel displays a table - view of the name and fingerprint of each key pair. Also, public key - is shown in expanded row. + AngularJS-based Key Pairs panel is added. The features in the legacy + panel are fully implemented. The Key Pairs panel now may be configured + to use either the legacy or AngularJS-based codes. + The ANGULAR_FEATURES setting now allows for a `key_pairs_panel`. + If set to True, then the AngularJS-Based Key Pairs panel will be used, + while the Django version will be used if set to False. Default value + for key_pairs_panel is True.