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 @@
+ 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 @@ +