summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2015-04-09 23:51:25 +0000
committerGerrit Code Review <review@openstack.org>2015-04-09 23:51:26 +0000
commit2721a2f005e71810fd1fe44f81d8284a2ce8b197 (patch)
treea73d0d2473e33ac5d80a285dbac5d5006aa0d39f
parent264e9e55649127758f4ca2552f4a697728fcbb67 (diff)
parent86d121c8f20efa3789130bd02573f707d55eb7c4 (diff)
downloadhorizon-2721a2f005e71810fd1fe44f81d8284a2ce8b197.tar.gz
Merge "Launch Instance Step - Key Pair Import"
-rw-r--r--horizon/static/angular/table/table.scss2
-rw-r--r--horizon/static/angular/wizard/wizard.scss7
-rw-r--r--horizon/static/horizon/js/angular/services/hz.api.nova.js6
-rw-r--r--openstack_dashboard/static/dashboard/launch-instance/keypair/create-keypair.html34
-rw-r--r--openstack_dashboard/static/dashboard/launch-instance/keypair/import-keypair.html50
-rw-r--r--openstack_dashboard/static/dashboard/launch-instance/keypair/keypair-details.html2
-rw-r--r--openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.html138
-rw-r--r--openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.js321
-rw-r--r--openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.scss15
-rw-r--r--openstack_dashboard/static/dashboard/launch-instance/keypair/keypair.spec.js132
-rw-r--r--openstack_dashboard/static/dashboard/launch-instance/keypair/new-keypair.html31
-rw-r--r--openstack_dashboard/static/dashboard/scss/_mixins.scss2
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;
}