Launch Instance Step - Key Pair Import

This adds key pair creation and importing to Launch Instance.

Closes-Bug: #1433826

Change-Id: I548bcea280cea1ebcb7445c519bfdbfe811a9235
Co-Authored-By: Neill Cox <neill.cox@ingenious.com.au>
Co-Authored-By: Richard Jones <r1chardj0n3s@gmail.com>
Co-Authored-By: Kelly Domico <kelly.domico@hp.com>
Co-Authored-By: Mike Hagedorn <mike.hagedorn@hp.com>
Co-Authored-By: Brian Tully <brian.tully@hp.com>
Co-Authored-By: Shaoquan Chen <sean.chen2@hp.com>
This commit is contained in:
Richard Jones 2015-03-26 17:07:23 +11:00 committed by Kelly Domico
parent c6b2bc7e22
commit 86d121c8f2
12 changed files with 618 additions and 132 deletions

View File

@ -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;
}

View File

@ -297,3 +297,10 @@
margin-top: 3px;
}
}
.form-control {
@include input-placeholder {
font-weight: normal;
color: $placeholder-text-color;
}
}

View File

@ -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.'));
}
});
};

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
ctrl.title = gettext('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 = [
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)
];
}
module.controller('LaunchInstanceKeypairHelpCtrl', [
LaunchInstanceKeypairHelpCtrl
]);
function LaunchInstanceKeypairHelpCtrl() {
var ctrl = this;
ctrl.title = gettext('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 = [
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)
];
}
})();

View File

@ -15,4 +15,19 @@
}
}
}
textarea {
font-family: $code-font-family;
}
}
.no-navigation {
.step {
left: 25px;
}
.form-field {
margin-bottom: 1em;
}
}

View File

@ -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);
});
});
});

View File

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

View File

@ -3,7 +3,7 @@
* check out http://www.sass-lang.com/guide#topic-6
*/
@mixin search-placeholder {
@mixin input-placeholder {
&::-webkit-input-placeholder {
@content;
}