diff options
author | Jenkins <jenkins@review.openstack.org> | 2015-04-09 23:51:25 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2015-04-09 23:51:26 +0000 |
commit | 2721a2f005e71810fd1fe44f81d8284a2ce8b197 (patch) | |
tree | a73d0d2473e33ac5d80a285dbac5d5006aa0d39f | |
parent | 264e9e55649127758f4ca2552f4a697728fcbb67 (diff) | |
parent | 86d121c8f20efa3789130bd02573f707d55eb7c4 (diff) | |
download | horizon-2721a2f005e71810fd1fe44f81d8284a2ce8b197.tar.gz |
Merge "Launch Instance Step - Key Pair Import"
12 files changed, 613 insertions, 127 deletions
diff --git a/horizon/static/angular/table/table.scss b/horizon/static/angular/table/table.scss index 3fef60536..61f2c9df3 100644 --- a/horizon/static/angular/table/table.scss +++ b/horizon/static/angular/table/table.scss @@ -59,7 +59,7 @@ $em-per-priority: floor($table-col-avg-width / $font-size-base) * 3; } input[type="text"] { - @include search-placeholder { + @include input-placeholder { font-weight: normal; color: $placeholder-text-color; } diff --git a/horizon/static/angular/wizard/wizard.scss b/horizon/static/angular/wizard/wizard.scss index 67df2de4b..8b4183802 100644 --- a/horizon/static/angular/wizard/wizard.scss +++ b/horizon/static/angular/wizard/wizard.scss @@ -297,3 +297,10 @@ margin-top: 3px; } } + +.form-control { + @include input-placeholder { + font-weight: normal; + color: $placeholder-text-color; + } +} diff --git a/horizon/static/horizon/js/angular/services/hz.api.nova.js b/horizon/static/horizon/js/angular/services/hz.api.nova.js index 66ef95803..47d9c7608 100644 --- a/horizon/static/horizon/js/angular/services/hz.api.nova.js +++ b/horizon/static/horizon/js/angular/services/hz.api.nova.js @@ -58,7 +58,11 @@ limitations under the License. this.createKeypair = function(newKeypair) { return apiService.post('/api/nova/keypairs/', newKeypair) .error(function () { - horizon.alert('error', gettext('Unable to create the keypair.')); + if (angular.isDefined(newKeypair.public_key)) { + horizon.alert('error', gettext('Unable to import the keypair.')); + } else { + horizon.alert('error', gettext('Unable to create the keypair.')); + } }); }; diff --git a/openstack_dashboard/static/dashboard/launch-instance/keypair/create-keypair.html b/openstack_dashboard/static/dashboard/launch-instance/keypair/create-keypair.html new file mode 100644 index 000000000..bacdd6012 --- /dev/null +++ b/openstack_dashboard/static/dashboard/launch-instance/keypair/create-keypair.html @@ -0,0 +1,34 @@ +<div class="ng-wizard no-navigation" ng-form="wizardForm"> + <div class="title" ng-bind="::ctrl.labels.wizardTitle"></div> + + <div class="step"> + <h1>{$ ::ctrl.labels.title $}</h1> + <div class="content"> + <div class="subtitle">{$ ::ctrl.labels.help $}</div> + <div class="form-group"> + <div class="form-field required"> + <label>{$ ::ctrl.labels.keyPairName $}</label> + <input class="form-control" name="name" + ng-model="ctrl.model.name" + ng-required="true" placeholder="{$ ::ctrl.labels.required $}"> + </div> + </div> + </div> + </div> + + <div class="toolbar"> + <div class="secondary-btn-grp"> + <button class="cancel btn btn-sm btn-default" ng-click="ctrl.cancel()"> + <span class="fa fa-close"></span> + {$ ::ctrl.labels.cancel $} + </button> + </div> + <div class="primary-btn-grp"> + <button class="finish btn btn-sm btn-success" + ng-click="ctrl.submit()" ng-disabled="wizardForm.$invalid"> + <span class="fa fa-plus"></span> + {$ ::ctrl.labels.ok $} + </button> + </div> + </div> +</div> diff --git a/openstack_dashboard/static/dashboard/launch-instance/keypair/import-keypair.html b/openstack_dashboard/static/dashboard/launch-instance/keypair/import-keypair.html new file mode 100644 index 000000000..636dedefc --- /dev/null +++ b/openstack_dashboard/static/dashboard/launch-instance/keypair/import-keypair.html @@ -0,0 +1,50 @@ +<div class="ng-wizard no-navigation" ng-form="wizardForm"> + <div class="title" ng-bind="::ctrl.labels.wizardTitle"></div> + + <div class="step"> + <h1>{$ ::ctrl.labels.title $}</h1> + <div class="content"> + <div class="subtitle">{$ ::ctrl.labels.help $}</div> + + <div class="form-group"> + <div class="form-field required name"> + <label>{$ ::ctrl.labels.keyPairName $}</label> + <input class="form-control" name="name" + ng-model="ctrl.model.name" + ng-required="true" placeholder="{$ ::ctrl.labels.required $}"/> + </div> + <div class="form-field required key"> + <label>{$ ::ctrl.labels.publicKey $}</label> + <textarea class="form-control" name="key" rows="15" + ng-model="ctrl.model.public_key" + ng-required="true" placeholder="{$ ::ctrl.labels.required $}"> + </textarea> + </div> + </div> + </div> + </div> + + <div class="toolbar"> + <div class="secondary-btn-grp"> + <button class="cancel btn btn-sm btn-default" ng-click="ctrl.cancel()"> + <span class="fa fa-close"></span> + {$ ::ctrl.labels.cancel $} + </button> + </div> + <div class="primary-btn-grp"> + <button class="finish btn btn-sm btn-success" + ng-click="ctrl.submit()" ng-disabled="wizardForm.$invalid"> + <span class="fa fa-upload"></span> + {$ ::ctrl.labels.ok $} + </button> + </div> + </div> + + <help-panel> + <div ng-controller="LaunchInstanceImportKeyPairHelpCtrl as importHelpCtrl"> + <h1>{$ ::importHelpCtrl.title $}</h1> + <p ng-repeat="paragraph in ::importHelpCtrl.paragraphs" + ng-bind-html="::paragraph"></p> + </div> + </help-panel> +</div> diff --git a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair-details.html b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair-details.html index 9b66b037e..624896334 100644 --- a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair-details.html +++ b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair-details.html @@ -1,6 +1,6 @@ <dl class="dl-horizontal key-pair-details"> <dt>{$ ctrl.tableLabels.public_key $}</dt> <dd> - <pre><code>{$ row.public_key $}</code></pre> + <pre><code>{$ row.public_key $}</code></pre> </dd> </dl> diff --git a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.html b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.html index e100c3cef..432cefb47 100644 --- a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.html +++ b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.html @@ -4,68 +4,82 @@ <div class="content"> <div class="subtitle">{$ ::ctrl.label.subtitle $}</div> - <transfer-table tr-model="ctrl.tableData" - help-text="ctrl.tableHelp" - limits="ctrl.tableLimits"> + <div class="row form-group"> + <div class="col-sm-12 form-inline"> + <button type="button" class="btn btn-sm btn-primary pull-right" + ng-click="ctrl.createKeyPair()"> + <span class="fa fa-fw fa-plus"></span> + {$ ::ctrl.label.createKeyPair $} + </button> + <button type="button" class="btn btn-sm btn-primary pull-right" + ng-click="ctrl.importKeyPair()"> + <span class="fa fa-fw fa-upload"></span> + {$ ::ctrl.label.importKeyPair $} + </button> + </div> + </div> - <!-- Key Pairs Allocated--> - <allocated> - <table st-table="ctrl.tableData.displayedAllocated" - st-safe-src="ctrl.tableData.allocated" hz-table - class="table-striped table-rsp table-detail modern"> - <thead> + <transfer-table tr-model="ctrl.tableData" + help-text="ctrl.tableHelp" + limits="ctrl.tableLimits"> + + <!-- Key Pairs Allocated--> + <allocated> + <table st-table="ctrl.tableData.displayedAllocated" + st-safe-src="ctrl.tableData.allocated" hz-table + class="table-striped table-rsp table-detail modern"> + <thead> <tr> <th class="expander"></th> <th class="rsp-p1">{$ ::ctrl.label.name $}</th> <th class="rsp-p2">{$ ::ctrl.tableLabels.fingerprint $}</th> <th></th> </tr> - </thead> - <tbody> - <tr ng-if="ctrl.tableData.allocated.length === 0"> - <td colspan="8"> - <div class="no-rows-help"> - {$ ::trCtrl.helpText.noneAllocText $} - </div> - </td> - </tr> - <tr ng-repeat-start="row in ctrl.tableData.displayedAllocated track by row.id"> - <td class="expander"> - <span class="fa fa-chevron-right" hz-expand-detail - title="{$ ::trCtrl.helpText.expandDetailsText $}"></span> - </td> - <td class="rsp-p1">{$ row.name $}</td> - <td class="rsp-p2">{$ row.fingerprint $}</td> - <td class="action-col"> - <action-list> - <action action-classes="'btn btn-sm btn-default'" - callback="trCtrl.deallocate" item="row"> - <span class="fa fa-minus"></span> - </action> - </action-list> - </td> - </tr> - <tr ng-repeat-end class="detail-row"> - <td></td> - <td class="detail" colspan="3"> - <dl class="dl-horizontal" ng-include="ctrl.tableDetails"> - </dl> - </td> - </tr> - </tbody> - </table> - </allocated> + </thead> + <tbody> + <tr ng-if="ctrl.tableData.allocated.length === 0"> + <td colspan="8"> + <div class="no-rows-help"> + {$ ::trCtrl.helpText.noneAllocText $} + </div> + </td> + </tr> + <tr ng-repeat-start="row in ctrl.tableData.displayedAllocated track by row.id"> + <td class="expander"> + <span class="fa fa-chevron-right" hz-expand-detail + title="{$ ::trCtrl.helpText.expandDetailsText $}"></span> + </td> + <td class="rsp-p1">{$ row.name $}</td> + <td class="rsp-p2">{$ row.fingerprint $}</td> + <td class="action-col"> + <action-list> + <action action-classes="'btn btn-sm btn-default'" + callback="trCtrl.deallocate" item="row"> + <span class="fa fa-minus"></span> + </action> + </action-list> + </td> + </tr> + <tr ng-repeat-end class="detail-row"> + <td></td> + <td class="detail" colspan="3"> + <dl class="dl-horizontal" ng-include="ctrl.tableDetails"> + </dl> + </td> + </tr> + </tbody> + </table> + </allocated> - <!-- Key Pairs Available --> - <available> - <table st-table="ctrl.tableData.displayedAvailable" - st-safe-src="ctrl.tableData.available" - hz-table class="table-striped table-rsp table-detail modern"> - <thead> + <!-- Key Pairs Available --> + <available> + <table st-table="ctrl.tableData.displayedAvailable" + st-safe-src="ctrl.tableData.available" + hz-table class="table-striped table-rsp table-detail modern"> + <thead> <tr> <th class="search-header" colspan="7"> - <search-bar group-classes="input-group-sm" - icon-classes="fa-search"> + <search-bar group-classes="input-group-sm" icon-classes="fa-search"> </search-bar> </th> </tr> @@ -75,8 +89,8 @@ <th st-sort="fingerprint" class="rsp-p1">{$ ::ctrl.tableLabels.fingerprint $}</th> <th></th> </tr> - </thead> - <tbody> + </thead> + <tbody> <tr ng-if="trCtrl.numDisplayedAvailable() === 0"> <td colspan="8"> <div class="no-rows-help"> @@ -87,15 +101,15 @@ <tr ng-repeat-start="row in ctrl.tableData.displayedAvailable track by row.id" ng-if="!trCtrl.allocatedIds[row.id]"> <td class="expander"> - <span class="fa fa-chevron-right" hz-expand-detail - title="{$ ::trCtrl.helpText.expandDetailsText $}"></span> + <span class="fa fa-chevron-right" hz-expand-detail + title="{$ ::trCtrl.helpText.expandDetailsText $}"></span> </td> <td class="rsp-p1">{$ row.name$}</td> <td class="rsp-p1">{$ row.fingerprint $}</td> <td class="action-col"> <action-list> <action action-classes="'btn btn-sm btn-default'" - callback="trCtrl.allocate" item="row"> + callback="trCtrl.allocate" item="row"> <span class="fa fa-plus"></span> </action> </action-list> @@ -106,11 +120,11 @@ <td class="detail" colspan="3" ng-include="ctrl.tableDetails"> </td> </tr> - </tbody> - </table> - </available> + </tbody> + </table> + </available> - </transfer-table> <!-- End Key Pairs Table --> + </transfer-table> <!-- End Key Pairs Table --> </div> <!-- End Content --> -</div> <!-- End Controller --> +</div> <!-- End Controller -->
\ No newline at end of file diff --git a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.js b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.js index a10849867..ca388eb6d 100644 --- a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.js +++ b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.js @@ -3,7 +3,7 @@ var module = angular.module('hz.dashboard.launch-instance'); - /** + /** * @ngdoc controller * @name hz.dashboard.launch-instance.LaunchInstanceKeypairCtrl * @description @@ -11,70 +11,283 @@ */ module.controller('LaunchInstanceKeypairCtrl', [ 'launchInstanceModel', - function (launchInstanceModel) { - var ctrl = this; - - ctrl.label = { - title: gettext('Key Pair'), - subtitle: gettext('Select a key pair.'), - name: gettext('Name'), - description: gettext('Description') - }; - - ctrl.tableLabels = { - fingerprint: gettext('Fingerprint'), - public_key: gettext('Public Key') - }; - - ctrl.tableData = { - available: launchInstanceModel.keypairs, - allocated: launchInstanceModel.newInstanceSpec.key_pair, - displayedAvailable: [], - displayedAllocated: [] - }; - - ctrl.tableDetails = - '/static/dashboard/launch-instance/keypair/keypair-details.html'; - - ctrl.tableHelp = { - noneAllocText: gettext('Select a key pair from the available key pairs below.') - }; - - ctrl.tableLimits = { - maxAllocation: 1 - }; - - } + '$modal', + 'dashboardBasePath', + LaunchInstanceKeypairCtrl ]); + function LaunchInstanceKeypairCtrl(launchInstanceModel, + $modal, + dashboardBasePath) { + var ctrl = this; - /** + ctrl.label = { + title: gettext('Key Pair'), + subtitle: gettext('A key pair allows you to SSH into your newly created instance. You may select an existing key pair, import a key pair, or generate a new key pair.'), + name: gettext('Name'), + description: gettext('Description'), + createKeyPair: gettext('Create Key Pair'), + importKeyPair: gettext('Import Key Pair') + }; + + ctrl.tableLabels = { + fingerprint: gettext('Fingerprint'), + public_key: gettext('Public Key') + }; + + ctrl.tableData = { + available: launchInstanceModel.keypairs, + allocated: launchInstanceModel.newInstanceSpec.key_pair, + displayedAvailable: [], + displayedAllocated: [] + }; + + ctrl.tableDetails = dashboardBasePath + 'launch-instance/keypair/keypair-details.html'; + + ctrl.tableHelp = { + noneAllocText: gettext('Select a key pair from the available key pairs below.') + }; + + ctrl.tableLimits = { + maxAllocation: 1 + }; + + // Allocate the new key pair (after import or create) + // if nothing is already allocated + ctrl.allocateNewKeyPair = function(newKeyPair) { + if (ctrl.tableData.allocated.length === 0) { + ctrl.tableData.allocated.push(newKeyPair); + } + }; + + ctrl.createKeyPair = function () { + $modal.open({ + templateUrl: dashboardBasePath + 'launch-instance/keypair/create-keypair.html', + controller: 'LaunchInstanceCreateKeyPairCtrl as ctrl', + windowClass: 'modal-dialog-wizard', + }).result.then( + function (result) { + // Nova doesn't set the id in the response so we will use + // the name as the id. Name is the key used in URLs, etc. + result.id = result.name; + + $modal.open({ + templateUrl: dashboardBasePath + 'launch-instance/keypair/new-keypair.html', + controller: 'LaunchInstanceNewKeyPairCtrl as ctrl', + windowClass: 'modal-dialog-wizard', + resolve: { + keypair: function () { + return result; + } + } + }); + + launchInstanceModel.keypairs.push(result); + ctrl.allocateNewKeyPair(result); + } + ); + }; + + ctrl.importKeyPair = function () { + $modal.open({ + templateUrl: dashboardBasePath + 'launch-instance/keypair/import-keypair.html', + controller: 'LaunchInstanceImportKeyPairCtrl as ctrl', + windowClass: 'modal-dialog-wizard' + }).result.then( + function (result) { + // Nova doesn't set the id in the response so we will use + // the name as the id. Name is the key used in URLs, etc. + result.id = result.name; + + launchInstanceModel.keypairs.push(result); + ctrl.allocateNewKeyPair(result); + } + ); + }; + } + + /** * @ngdoc controller * @name hz.dashboard.launch-instance.LaunchInstanceKeypairHelpCtrl * @description - * Provide help for selection of security groups and key pairs. + * Provide help for selection of a key pair. */ - module.controller('LaunchInstanceKeypairHelpCtrl', [function () { - var ctrl = this; + module.controller('LaunchInstanceKeypairHelpCtrl', [ + LaunchInstanceKeypairHelpCtrl + ]); - ctrl.title = gettext('Key Pair Help'); + function LaunchInstanceKeypairHelpCtrl() { + var ctrl = this; - var genKeyPairsMap = { genKeyPairCmd: 'ssh-keygen' }; - var genKeyPairsText = gettext('There are two ways to generate a key pair. From a Linux system, generate the key pair with the <samp>%(genKeyPairCmd)s</samp> command:'); + ctrl.title = gettext('Key Pair Help'); - var keyPathsMap = { privateKeyPath: 'cloud.key', publicKeyPath: 'cloud.key.pub' }; - var keyPathText = gettext('This command generates a pair of keys: a private key (%(privateKeyPath)s) and a public key (%(publicKeyPath)s).'); + var genKeyPairsMap = {genKeyPairCmd: 'ssh-keygen'}; + var genKeyPairsText = gettext('There are two ways to generate a key pair. From a Linux system, generate the key pair with the <samp>%(genKeyPairCmd)s</samp> command:'); - var windowsCmdMap = { authorizeKeysFile: '.ssh/authorized_keys' }; - var windowsCmd = gettext('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 <samp>%(authorizeKeysFile)s</samp> file.'); + var keyPathsMap = { + privateKeyPath: 'cloud.key', + publicKeyPath: 'cloud.key.pub' + }; + var keyPathText = gettext('This command generates a pair of keys: a private key (%(privateKeyPath)s) and a public key (%(publicKeyPath)s).'); - ctrl.paragraphs = [ - gettext('The key pair allows you to SSH into the instance.'), - interpolate(genKeyPairsText, genKeyPairsMap, true), - '<samp>ssh-keygen -t rsa -f cloud.key</samp>', - interpolate(keyPathText, keyPathsMap, true), - interpolate(windowsCmd, windowsCmdMap, true) - ]; - } + var windowsCmdMap = {authorizeKeysFile: '.ssh/authorized_keys'}; + var windowsCmd = gettext('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 <samp>%(authorizeKeysFile)s</samp> file.'); + + ctrl.paragraphs = [ + gettext('The key pair allows you to SSH into the instance.'), + interpolate(genKeyPairsText, genKeyPairsMap, true), + '<samp>ssh-keygen -t rsa -f cloud.key</samp>', + interpolate(keyPathText, keyPathsMap, true), + interpolate(windowsCmd, windowsCmdMap, true) + ]; + } + + /** + * @ngdoc controller + * @name hz.dashboard.launch-instance.LaunchInstanceCreateKeyPairCtrl + * @description + * Provide a dialog for creation of a new key pair. + */ + module.controller('LaunchInstanceCreateKeyPairCtrl', [ + '$modalInstance', + 'novaAPI', + LaunchInstanceCreateKeyPairCtrl ]); + + function LaunchInstanceCreateKeyPairCtrl($modalInstance, novaAPI) { + var ctrl = this; + + ctrl.labels = { + wizardTitle: gettext('Launch Instance'), + title: gettext('Create Key Pair'), + help: gettext('Key Pairs are how you login to your instance after it is launched. Choose a key pair name you will recognize.'), + keyPairName: gettext('Key Pair Name'), + cancel: gettext('Cancel'), + ok: gettext('Create Key Pair'), + required: gettext('Required') + }; + + ctrl.model = { name: '' }; + ctrl.submit = function () { + novaAPI.createKeypair(ctrl.model) + .success(function (data) { + $modalInstance.close(data); + + var successMsg = gettext('Successfully created key pair %(name)s'); + horizon.alert('success', interpolate(successMsg, { name: data.name }, true)); + }); + }; + ctrl.cancel = function () { + $modalInstance.dismiss(); + }; + } + + /** + * @ngdoc controller + * @name hz.dashboard.launch-instance.LaunchInstanceNewKeyPairCtrl + * @description + * Provide a dialog for display of the information about a new + * public/private key pair. + */ + module.controller('LaunchInstanceNewKeyPairCtrl', [ + '$modalInstance', + 'keypair', + LaunchInstanceNewKeyPairCtrl + ]); + + function LaunchInstanceNewKeyPairCtrl($modalInstance, keypair) { + var ctrl = this; + + ctrl.labels = { + wizardTitle: gettext('Launch Instance'), + title: gettext('Private Key'), + help: gettext('This is your new key pair. Copy this information and keep it secure.'), + keyPairName: gettext('Key Pair Name'), + fingerprint: gettext('Fingerprint'), + privateKey: gettext('Private Key'), + publicKey: gettext('Public Key'), + ok: gettext('OK') + }; + + ctrl.keypair = keypair; + ctrl.ok = function () { + $modalInstance.dismiss(); + }; + } + + /** + * @ngdoc controller + * @name hz.dashboard.launch-instance.LaunchInstanceImportKeyPairCtrl + * @description + * Provide a dialog for import of an existing ssh public key. + */ + module.controller('LaunchInstanceImportKeyPairCtrl', [ + '$modalInstance', + 'novaAPI', + LaunchInstanceImportKeyPairCtrl + ]); + + function LaunchInstanceImportKeyPairCtrl($modalInstance, novaAPI) { + var ctrl = this; + + ctrl.labels = { + wizardTitle: gettext('Launch Instance'), + title: gettext('Import Key Pair'), + help: gettext('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.'), + keyPairName: gettext('Key Pair Name'), + publicKey: gettext('Public Key'), + cancel: gettext('Cancel'), + ok: gettext('Import Key Pair'), + required: gettext('Required') + }; + + ctrl.model = { name: '', public_key: '' }; + ctrl.submit = function () { + novaAPI.createKeypair(ctrl.model) + .success(function (data) { + $modalInstance.close(data); + + var successMsg = gettext('Successfully imported key pair %(name)s'); + horizon.alert('success', interpolate(successMsg, { name: data.name }, true)); + }); + }; + ctrl.cancel = function () { + $modalInstance.dismiss(); + }; + } + + /** + * @ngdoc controller + * @name LaunchInstanceImportKeyPairHelpCtrl + * @description + * The `LaunchInstanceImportKeyPairHelpCtrl` controller provides help text + * for the import key pair function within the Launch Instance Wizard. + * + */ + module.controller('LaunchInstanceImportKeyPairHelpCtrl', [ + LaunchInstanceImportKeyPairHelpCtrl + ]); + + function LaunchInstanceImportKeyPairHelpCtrl() { + var ctrl = this; + + ctrl.title = gettext('Import Key Pair Help'); + + var genKeyPairsMap = { genKeyPairCmd: 'ssh-keygen' }; + var genKeyPairsText = gettext('There are two ways to generate a key pair. From a Linux system, generate the key pair with the <samp>%(genKeyPairCmd)s</samp> command:'); + + var keyPathsMap = { privateKeyPath: 'cloud.key', publicKeyPath: 'cloud.key.pub' }; + var keyPathText = gettext('This command generates a pair of keys: a private key (%(privateKeyPath)s) and a public key (%(publicKeyPath)s).'); + + var windowsCmdMap = { authorizeKeysFile: '.ssh/authorized_keys' }; + var windowsCmd = gettext('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 <samp>%(authorizeKeysFile)s</samp> file.'); + + ctrl.paragraphs = [ + interpolate(genKeyPairsText, genKeyPairsMap, true), + '<samp>ssh-keygen -t rsa -f cloud.key</samp>', + interpolate(keyPathText, keyPathsMap, true), + interpolate(windowsCmd, windowsCmdMap, true) + ]; + } + })(); diff --git a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.scss b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.scss index 947fa2e15..cd5b3db84 100644 --- a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.scss +++ b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.scss @@ -15,4 +15,19 @@ } } } + + textarea { + font-family: $code-font-family; + } +} + +.no-navigation { + + .step { + left: 25px; + } + + .form-field { + margin-bottom: 1em; + } } diff --git a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.spec.js b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.spec.js index 8f1a131aa..a8d5b7cdf 100644 --- a/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.spec.js +++ b/openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.spec.js @@ -17,16 +17,26 @@ 'use strict'; describe('Launch Instance Keypair Step', function() { + describe('LaunchInstanceKeypairCtrl', function() { var ctrl; - beforeEach(module('hz.dashboard.launch-instance')); + beforeEach(module(function ($provide) { + $provide.value('$modal', {}); + })); + + beforeEach(module('hz.dashboard')); beforeEach(inject(function($controller) { - var model = { newInstanceSpec: { key_pair: ['key1'] }, - keypairs: ['key1', 'key2'] }; + var model = { + newInstanceSpec: { + key_pair: ['key1'] + }, + keypairs: ['key1', 'key2'] + }; + ctrl = $controller('LaunchInstanceKeypairCtrl', - {launchInstanceModel: model}); + { launchInstanceModel: model }); })); it('contains its general labels', function() { @@ -67,13 +77,12 @@ }); describe('LaunchInstanceKeypairHelpCtrl', function() { - var scope, ctrl; + var ctrl; beforeEach(module('hz.dashboard.launch-instance')); beforeEach(inject(function($controller) { - scope = {}; - ctrl = $controller('LaunchInstanceKeypairHelpCtrl', {$scope:scope}); + ctrl = $controller('LaunchInstanceKeypairHelpCtrl'); })); it('defines the title', function() { @@ -86,6 +95,115 @@ }); }); + describe('LaunchInstanceCreateKeyPairCtrl', function() { + var ctrl; + + beforeEach(module(function ($provide) { + $provide.value('$modalInstance', {}); + $provide.value('novaAPI', {}); + })); + + beforeEach(module('hz.dashboard')); + + beforeEach(inject(function($controller) { + ctrl = $controller('LaunchInstanceCreateKeyPairCtrl'); + })); + + it('contains its general labels', function() { + expect(ctrl.labels).toBeDefined(); + }); + + it('defines a model with a empty name', function() { + expect(ctrl.model).toBeDefined(); + expect(ctrl.model.name).toBe(''); + }); + + it('defines a submit function', function() { + expect(ctrl.submit).toBeDefined(); + }); + + it('defines a cancel function', function() { + expect(ctrl.cancel).toBeDefined(); + }); + }); + + describe('LaunchInstanceNewKeyPairCtrl', function() { + var ctrl; + + beforeEach(module(function ($provide) { + $provide.value('$modalInstance', {}); + })); + + beforeEach(module('hz.dashboard')); + + beforeEach(inject(function($controller) { + ctrl = $controller('LaunchInstanceNewKeyPairCtrl', { keypair: {} }); + })); + + it('contains its general labels', function() { + expect(ctrl.labels).toBeDefined(); + }); + + it('defines an empty keypair', function() { + expect(ctrl.keypair).toBeDefined(); + }); + + it('defines an OK function', function() { + expect(ctrl.ok).toBeDefined(); + }); + }); + + describe('LaunchInstanceImportKeyPairCtrl', function() { + var ctrl; + + beforeEach(module(function ($provide) { + $provide.value('$modalInstance', {}); + $provide.value('novaAPI', {}); + })); + + beforeEach(module('hz.dashboard')); + + beforeEach(inject(function($controller) { + ctrl = $controller('LaunchInstanceImportKeyPairCtrl'); + })); + + it('contains its general labels', function() { + expect(ctrl.labels).toBeDefined(); + }); + + it('defines a model with a empty name and public key', function() { + expect(ctrl.model).toBeDefined(); + expect(ctrl.model.name).toBe(''); + expect(ctrl.model.public_key).toBe(''); + }); + + it('defines a submit function', function() { + expect(ctrl.submit).toBeDefined(); + }); + + it('defines a cancel function', function() { + expect(ctrl.cancel).toBeDefined(); + }); + }); + + describe('LaunchInstanceImportKeyPairHelpCtrl', function() { + var ctrl; + + beforeEach(module('hz.dashboard.launch-instance')); + + beforeEach(inject(function($controller) { + ctrl = $controller('LaunchInstanceImportKeyPairHelpCtrl'); + })); + + it('defines the title', function() { + expect(ctrl.title).toBeDefined(); + }); + + it('has paragraphs', function() { + expect(ctrl.paragraphs).toBeDefined(); + expect(ctrl.paragraphs.length).toBeGreaterThan(0); + }); + }); }); diff --git a/openstack_dashboard/static/dashboard/launch-instance/keypair/new-keypair.html b/openstack_dashboard/static/dashboard/launch-instance/keypair/new-keypair.html new file mode 100644 index 000000000..cd845f7e1 --- /dev/null +++ b/openstack_dashboard/static/dashboard/launch-instance/keypair/new-keypair.html @@ -0,0 +1,31 @@ +<div class="ng-wizard no-navigation"> + <div class="title" ng-bind="::ctrl.labels.wizardTitle"></div> + + <div class="step"> + <h1>{$ ::ctrl.labels.title $}</h1> + <div class="content"> + <div class="subtitle">{$ ::ctrl.labels.help $}</div> + + <dl class="dl-horizontal key-pair-details"> + <dt>{$ ::ctrl.labels.keyPairName $}</dt> + <dd><pre><code>{$ ctrl.keypair.name $}</code></pre></dd> + <dt>{$ ::ctrl.labels.fingerprint $}</dt> + <dd><pre><code>{$ ctrl.keypair.fingerprint $}</code></pre></dd> + <dt>{$ ::ctrl.labels.publicKey $}</dt> + <dd><pre><code>{$ ctrl.keypair.public_key $}</code></pre></dd> + <dt>{$ ::ctrl.labels.privateKey $}</dt> + <dd><pre><code>{$ ctrl.keypair.private_key $}</code></pre></dd> + </dl> + </div> + </div> + + <div class="toolbar"> + <div class="primary-btn-grp"> + <button class="finish btn btn-sm btn-success" + ng-click="ctrl.ok()"> + <span class="fa fa-check"></span> + {$ ::ctrl.labels.ok $} + </button> + </div> + </div> +</div> diff --git a/openstack_dashboard/static/dashboard/scss/_mixins.scss b/openstack_dashboard/static/dashboard/scss/_mixins.scss index 390122cfa..ba3b1533d 100644 --- a/openstack_dashboard/static/dashboard/scss/_mixins.scss +++ b/openstack_dashboard/static/dashboard/scss/_mixins.scss @@ -3,7 +3,7 @@ * check out http://www.sass-lang.com/guide#topic-6 */ -@mixin search-placeholder { +@mixin input-placeholder { &::-webkit-input-placeholder { @content; } |