Fix theming in angular launch instance

This patch makes the new angular Launch Instance workflow mostly
themeable. Most of the existing CSS has been deleted, and this now
follows bootstraps markup. This is not intended to solve all edge cases
given the size of the work, but is a big step in the right direction.

Changes:
- Use stacked nav tabs for navigation. Move base nav tabs toward
  bootstrap default. Style primary side nav as before.
- Use bootstraps form markup for modal
- Use bootstraps form markup for form fields and their errors
- Make pie charts and tables inherit any missing theme variables. A more
  thorough pass will be done on this next release cycle.

Closes-Bug: 1538491
Change-Id: Ic20b7f4341a2853ca334824c6a811125b04e88cc
This commit is contained in:
Rob Cresswell 2016-02-24 13:45:17 -08:00 committed by Diana Whitten
parent dcc838128e
commit be9023d86e
64 changed files with 846 additions and 1563 deletions

View File

@ -6,7 +6,7 @@ action-list.btn-group {
.dropdown-menu > li {
&.disabled {
opacity: 0.65;
color: $dropdown-link-disabled-color;
}
> a.text-danger {
@ -16,50 +16,17 @@ action-list.btn-group {
notifications {
bottom: -0.5em;
font-size: 1.1em;
opacity: 1;
position: absolute;
right: -0.35em;
z-index: 3;
& + .btn:last-child:not(:first-child):not(.dropdown-toggle),
& + .btn:not(:last-child):not(:first-child):not(.dropdown-toggle),
& + .btn.single-button:not(:first-child),
& + .btn.split-button:not(:first-child):not(:last-child) {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
}
&.btn-group-sm {
notifications {
& + .btn:last-child:not(:first-child):not(.dropdown-toggle),
& + .btn:not(:last-child):not(:first-child):not(.dropdown-toggle),
& + .btn.single-button:not(:first-child),
& + .btn.split-button:not(:first-child):not(:last-child) {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
}
}
&.btn-group-lg {
notifications {
& + .btn:last-child:not(:first-child):not(.dropdown-toggle),
& + .btn:not(:last-child):not(:first-child):not(.dropdown-toggle),
& + .btn.single-button:not(:first-child),
& + .btn.split-button:not(:first-child):not(:last-child) {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
}
}
.invalid {
color: $invalid-color;
color: $brand-warning;
}
& + .popover a {
cursor: pointer;
}
}
}

View File

@ -1,44 +1,40 @@
.chart-tooltip {
background-color: $tooltip-bg-color;
border: $tooltip-border;
@include box-shadow($tooltip-box-shadow);
background-color: $tooltip-bg;
display: none;
padding: $tooltip-padding;
position: absolute;
white-space: nowrap;
z-index: 12000;
z-index: $zindex-popover;
&.tooltip-enabled {
display: inline-block;
}
.tooltip-key {
color: $tooltip-key-color;
font-weight: $tooltip-key-weight;
padding: $tooltip-key-padding;
color: $tooltip-color;
}
.tooltip-value {
color: $tooltip-value-color;
color: $tooltip-color;
}
span.fa {
background-color: inherit;
fill: none;
&.usage {
color: $chart-quota-usage-color;
color: $brand-primary;
}
&.added {
color: $chart-quota-added-color;
color: lighten($brand-primary, 20%);
}
&.remaining {
color: $chart-quota-remaining-color;
color: $gray-lighter;
}
&.danger {
color: $chart-quota-danger-color;
color: $brand-danger;
}
}
}
@ -48,8 +44,8 @@
.chart-tooltip {
span.fa {
&.added {
color: $chart-quota-danger-color;
color: $brand-danger;
}
}
}
}
}

View File

@ -59,7 +59,7 @@
*/
.constant('horizon.framework.widgets.charts.donutChartSettings', {
innerRadius: 24,
outerRadius: 30,
outerRadius: 36,
titleClass: 'pie-chart-title-medium',
showTitle: true,
showLabel: true,

View File

@ -1,8 +1,7 @@
<div class="pie-chart" ng-class="{ 'danger': chartData.overMax }">
<div class="pie-chart text-center" ng-class="{ 'danger': chartData.overMax }">
<chart-tooltip tooltip-data="model.tooltipData"></chart-tooltip>
<div ng-if="::model.settings.showTitle && chartData.title"
class="pie-chart-title {$ ::model.settings.titleClass $}">
<div class="pie-chart-title" ng-if="::model.settings.showTitle && chartData.title">
{$ ::chartData.title $} ({$ model.total ? model.total + ' ' : '' $}{$ model.totalLabel $})
</div>
@ -28,4 +27,4 @@
<div>{$ slice.label $}</div>
</div>
</div>
</div>
</div>

View File

@ -1,65 +1,44 @@
.pie-chart {
display: inline-block;
position: relative;
margin-left: 10px;
.svg-pie-chart {
float: left;
margin: $padding-small-horizontal 0;
.slice {
cursor: pointer;
&.usage {
fill: $chart-quota-usage-color;
fill: $brand-primary;
}
&.added {
fill: $chart-quota-added-color;
fill: lighten($brand-primary, 20%);
}
&.remaining {
fill: $chart-quota-remaining-color;
fill: $gray-lighter;
}
}
}
.pie-chart-title {
color: $chart-title-font-color;
font-weight: $chart-title-weight;
padding: $chart-title-padding;
}
.pie-chart-title-medium {
font-size: $chart-title-font-size;
}
.pie-chart-title-large {
font-size: $chart-title-font-size-large;
}
.pie-chart-label {
font-size: 1.2em;
font-size: $font-size-large;
text-anchor: middle;
text {
font-size: $chart-label-font-size;
fill: $chart-label-color;
font-size: $font-size-large;
fill: $text-color;
}
}
.pie-chart-legend {
float: left;
font-size: $chart-legend-font-size;
line-height: 1em;
padding: $chart-legend-padding;
display: table;
text-align: left;
.slice-legend {
padding: $chart-slice-legend-padding;
display: table-row;
& > :last-child {
padding-left: 5px;
padding-left: $padding-xs-horizontal;
}
div {
@ -69,41 +48,40 @@
.slice-key {
color: transparent;
display: inline-block;
height: 1.1em;
line-height: 1.1em;
height: 1em;
position: relative;
top: 0.12em;
width: 0.45em;
margin-right: 3px;
width: 0.7em;
margin-right: $padding-xs-horizontal;
&.usage {
background-color: $chart-quota-usage-color;
background-color: $brand-primary;
}
&.added {
background-color: $chart-quota-added-color;
background-color: lighten($brand-primary, 20%);
}
&.remaining {
background-color: $chart-quota-remaining-color;
background-color: $gray-lighter;
}
}
.chartless {
font-size: x-large;
font-size: $font-size-large;
text-align: right;
padding-top: 10px;
padding-top: $padding-large-vertical;
font-weight: bold;
&.usage {
color: $chart-quota-usage-color;
color: $brand-primary;
}
&.added {
color: $chart-quota-added-color;
color: lighten($brand-primary, 20%);
}
&.remaining {
color: $chart-quota-remaining-color;
color: $gray-lighter;
}
}
}
@ -117,7 +95,7 @@
&.added,
&.usage,
&.remaining {
fill: $chart-quota-danger-color;
fill: $brand-danger;
}
}
}
@ -132,7 +110,7 @@
.slice-legend {
.slice-key {
&.added {
background-color: $chart-quota-danger-color;
background-color: $brand-danger;
}
}
}

View File

@ -35,21 +35,21 @@
});
it('should be closed by default', function () {
expect(element[0].querySelector('.help-panel').className).toBe('help-panel');
expect(element[0].querySelector('#help-panel').className).toBe('collapse width');
});
it('should add "open" to class name if $scope.openHelp is true', function () {
it('should add "in" to class name if $scope.openHelp is true', function () {
$scope.openHelp = true;
$scope.$apply();
expect(element[0].querySelector('.help-panel').className).toBe('help-panel open');
expect(element[0].querySelector('#help-panel').className).toBe('collapse width in');
});
it('should remove "open" from class name if $scope.openHelp is false', function () {
it('should remove "in" from class name if $scope.openHelp is false', function () {
$scope.openHelp = true;
$scope.$apply();
$scope.openHelp = false;
$scope.$apply();
expect(element[0].querySelector('.help-panel').className).toBe('help-panel');
expect(element[0].querySelector('#help-panel').className).toBe('collapse width');
});
});

View File

@ -1,5 +1,9 @@
<div class="help-panel" ng-class="{'open': openHelp}">
<button class="open" ng-click="openHelp=true"><span class="fa fa-question-circle"></span></button>
<button class="close" ng-click="openHelp=false"><span class="fa fa-times-circle"></span></button>
<div class="content" ng-transclude></div>
<button class="btn btn-default help-toggle collapsed" data-target="#help-panel"
data-toggle="collapse" role="button" aria-expanded="false"
aria-controls="help-panel">
<span class="fa"></span>
</button>
<div id="help-panel" class="collapse width" ng-class="{'in': openHelp}">
<div class="well" ng-transclude></div>
</div>

View File

@ -1,85 +0,0 @@
.help-panel {
position: absolute;
width: $helpPanelWidthDefault;
right: -$helpPanelWidthDefault;
top: 0;
bottom: 0;
color: $helpPanelColor;
background: $helpPanelBg;
@include transition(right linear 0.1s);
z-index: 10;
.content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 10px 20px;
overflow-y: auto;
h1 {
font-size: 20px;
line-height: 1.8;
margin: 0;
}
li {
list-style-position: inside;
}
p {
line-height: 1.4;
margin: 1em 0;
}
}
&.open {
right: 0;
border: 1px solid $helpPanelBorderColor;
border-right: none;
margin-top: -1px;
margin-bottom: -1px;
& > button.open {
display: none;
}
& > button.close {
display: block;
opacity: 1; // override bootstrap
font-size: 14px; // override bootstrap
}
}
& > button {
position: absolute;
top: 0;
left: -$helpPanelBtnSize;
width: $helpPanelBtnSize;
height: $helpPanelBtnSize;
line-height: $helpPanelBtnSize;
padding: 0;
border: none;
text-align: center;
vertical-align: middle;
background: $helpPanelBtnBg;
border: 1px solid $helpPanelBorderColor;
border-right: none;
margin-top: -1px;
// button icon
& > * {
display: inline-block;
vertical-align: middle;
background: $helpPanelBtnIconBg;
color: $helpPanelBtnIconColor;
font-size: $helpPanelBtnIconSize;
}
&.close {
display: none;
}
}
}

View File

@ -1,35 +1,31 @@
<div class="form-group customization-script">
<label>
<div class="form-group" ng-class="{ 'has-error': scriptLength >= config.MAX_SCRIPT_SIZE }">
<label for="customization-script" class="control-label">
<span translate>Customization Script</span>
<span class="script-modified"
ng-show="scriptModified"
<span ng-show="scriptModified"
translate>
(Modified)</span>
</label>
<span class="size-indicator pull-right clearfix"
ng-class="{warning: scriptLength >= config.MAX_SCRIPT_SIZE}">
<span class="invalid fa fa-exclamation-triangle"
popover="{$ ::config.label.scriptSizeHoverWarningMsg $}"
popover-placement="top"
popover-append-to-body="true"
popover-trigger="hover">
</span>
<span translate>Script size</span>
{$ (scriptLength || 0) | number $}
<span translate>bytes</span>
<span translate>(Max: 16Kb)</span>
<span class="pull-right" ng-class="{ 'text-danger': scriptLength >= config.MAX_SCRIPT_SIZE }">
<span translate>Script size:</span>
{$ (scriptLength || 0) | bytes $}
<span translate>of</span>
{$ config.MAX_SCRIPT_SIZE | bytes $}
</span>
<textarea class="form-control"
rows="8"
id="customization-script"
name="customization-script"
ng-maxlength="config.MAX_SCRIPT_SIZE"
ng-model="textContent">
</textarea>
</div>
<div class="form-group script-file" ng-show="config.fileApiSupported">
<span class="file-input btn btn-primary btn-file">
<span class="fa fa-upload"></span>
<span translate>Load script from a file</span>
<input type="file">
<span class="help-block"
ng-show="scriptLength >= config.MAX_SCRIPT_SIZE"
translate>
The script is larger than the maximum size
</span>
</div>
<div class="form-group" ng-show="config.fileApiSupported">
<label for="load-script" translate>Load script from a file</label>
<input id="load-script" type="file">
</div>

View File

@ -6,28 +6,11 @@
overflow: auto;
}
/* Header */
.panel-heading {
.v-align {
display: table;
min-height: 2.5em;
}
.v-align > * {
display: table-cell;
vertical-align: middle;
}
}
/* Item lists */
:not(.active) {
&.dark-stripe {
background-color: $table-bg-odd;
}
&.light-stripe {
background-color: white;
background-color: $table-bg-accent;
}
}
@ -37,15 +20,15 @@
}
&.level-1>* {
padding-left: 15px;
padding-left: $padding-large-horizontal;
}
&.level-2>* {
padding-left: 30px;
padding-left: $padding-large-horizontal * 2;
}
.leaf {
padding-left: 10px;
padding-left: $padding-small-horizontal;
}
}
@ -66,7 +49,6 @@
color: $text-color;
.panel-heading {
padding: 4px;
&>* {
display: table;
@ -79,17 +61,8 @@
}
}
.panel-body {
padding: 3px 5px 5px;
}
.panel-footer {
padding: 4px;
}
.values .label {
display: inline-block;
text-transform: uppercase;
}
.name {

View File

@ -62,6 +62,7 @@
function modal(params) {
if (params && params.scope && params.workflow && params.submit) {
var options = {
size: 'lg',
controller: 'WizardModalController as modalCtrl',
scope: params.scope,
template: '<wizard></wizard>',

View File

@ -64,11 +64,11 @@ $em-per-priority: floor($table-col-avg-width / $font-size-base) * 3;
tbody tr {
&.lr-drop-target-before td {
border-top: $reorder-border !important;
border-top: $reorder-border;
}
&.lr-drop-target-after td {
border-bottom: $reorder-border !important;
border-bottom: $reorder-border;
}
}
@ -81,10 +81,9 @@ $em-per-priority: floor($table-col-avg-width / $font-size-base) * 3;
cursor: pointer;
&:after {
color: #d4d4d4;
content: '\f0dc';
content: $fa-var-sort;
font-family: 'FontAwesome';
margin-left: 0.5em;
margin-left: $padding-small-vertical;
opacity: 0;
}
@ -94,21 +93,16 @@ $em-per-priority: floor($table-col-avg-width / $font-size-base) * 3;
}
.st-sort-ascent:after {
color: #000000;
content: '\f0dd';
font-family: 'FontAwesome';
margin-left: 0.5em;
content: $fa-var-sort-asc;
opacity: 1;
}
.st-sort-descent:after {
color: #000000;
content: '\f0de';
font-family: 'FontAwesome';
margin-left: 0.5em;
content: $fa-var-sort-desc;
opacity: 1;
}
&.table-detail {
border-spacing: 0;

View File

@ -2,7 +2,7 @@
<!-- Allocation -->
<div class="transfer-section">
<!-- Allocated section heading -->
<h5 class="transfer-heading">
<span class="transfer-heading">
<span title="{$ ::trCtrl.helpText.sectionToggleText $}" class="fa"
ng-if="::(trCtrl.limits.maxAllocation !== 1)"
ng-class="trCtrl.views.allocated ? 'fa-chevron-down' : 'fa-chevron-right'"
@ -14,9 +14,9 @@
<span class="pull-right help-text" ng-if="::trCtrl.helpText.allocHelpText">
{$ ::trCtrl.helpText.allocHelpText $}
</span>
</h5>
</span>
<!-- Help text when allocated section collapsed -->
<div class="collapsed-help" ng-hide="trCtrl.views.allocated">
<div class="text-muted" ng-hide="trCtrl.views.allocated">
{$ ::trCtrl.helpText.allocHiddenText $}
</div>
<div class="transfer-allocated" ng-show="trCtrl.views.allocated">
@ -27,7 +27,7 @@
<!-- Available -->
<div class="transfer-section">
<!-- Available section heading -->
<h5 class="transfer-heading">
<span class="transfer-heading">
<span class="fa" title="{$ ::trCtrl.helpText.sectionToggleText $}"
ng-class="trCtrl.views.available ? 'fa-chevron-down' : 'fa-chevron-right'"
ng-click="trCtrl.toggleView('available')"></span>
@ -36,13 +36,13 @@
<span class="pull-right help-text" ng-if="::trCtrl.helpText.availHelpText">
{$ ::trCtrl.helpText.availHelpText $}
</span>
</h5>
</span>
<!-- Help text when available section collapsed -->
<div class="collapsed-help" ng-hide="trCtrl.views.available">
<div class="text-muted" ng-hide="trCtrl.views.available">
{$ ::trCtrl.helpText.availHiddenText $}
</div>
<div class="transfer-available" ng-show="trCtrl.views.available">
<!-- Available table transcluded here -->
</div>
</div>
</div>
</div>

View File

@ -1,51 +0,0 @@
.transfer-table {
.collapsed-help {
color: $transfer-help-text-color;
font-style: italic;
font-weight: 400;
margin-bottom: 3em;
}
.fa[title] {
cursor: pointer;
width: 20px;
}
.transfer-heading {
border-bottom: $transfer-header-bottom-border;
font-size: 1.2em;
margin-top: 1em;
padding-bottom: 0.5em;
.badge-info {
background-color: $badge-info-color;
}
.help-text {
font-size: 0.9em;
font-weight: 400;
}
}
.transfer-available, .transfer-allocated {
margin-bottom: 3em;
table {
.action-col {
min-width: $transfer-btn-width;
width: $transfer-btn-width;
.btn {
border-color: $transfer-btn-border-color;
padding: 2px 7px;
&.disabled {
border-color: $transfer-disabled-btn-border-color;
color: $transfer-disabled-btn-color;
}
}
}
}
}
}

View File

@ -1,8 +1,5 @@
@import "headers/headers";
@import "help-panel/help-panel";
@import "wizard/wizard";
@import "table/table";
@import "transfer-table/transfer-table";
@import "charts/chart-tooltip";
@import "charts/pie-chart";
@import "action-list/action-list";

View File

@ -1,67 +1,89 @@
<div class="ng-wizard" ng-form="wizardForm">
<div class="title" ng-bind="::workflow.title"></div>
<div class="modal-header">
<button type="button" class="close" ng-click="cancel()" aria-hidden="true" aria-label="Close">
<span aria-hidden="true" class="fa fa-close"></span>
</button>
<span class="h4 modal-title" ng-bind="::workflow.title"></span>
</div>
<div class="nav">
<button class="btn nav-item"
ng-class="{'current': currentIndex===$index}"
ng-click="switchTo($index)"
ng-repeat="step in steps"
ng-show="viewModel.ready">
<span ng-bind="::step.title"></span>
<span class="status-indicator fa fa-lg fa-warning"
ng-show="wizardForm[steps[$index].formName].$invalid"></span>
<div class="modal-body">
<div class="row">
<div class="col-xs-12 col-sm-3">
<button type="button" data-toggle="collapse"
data-target="#wizard-side-nav" aria-expanded="false"
class="navbar-toggle btn btn-default collapsed wizard-nav-toggle">
<span translate class="sr-only">Toggle navigation</span>
<span class="fa fa-bars"></span>
<span>Toggle navigation</span>
</button>
<div class="collapse navbar-collapse wizard-nav" id="wizard-side-nav">
<ul class="nav nav-pills nav-stacked">
<li role="presentation"
class="nav-item"
ng-class="{'active': currentIndex===$index}"
ng-click="switchTo($index)"
ng-repeat="step in steps"
ng-show="viewModel.ready">
<a href="#">
<span ng-bind="::step.title"></span>
<span class="hz-icon-required fa fa-asterisk"
ng-show="wizardForm[steps[$index].formName].$invalid"
aria-hidden="true"></span>
</a>
</li>
</ul>
</div>
</div>
<div class="col-xs-12 col-sm-9">
<div class="step"
ng-repeat="step in steps"
ng-show="currentIndex===$index">
<ng-include
ng-form="{$ step.formName $}"
src="step.templateUrl">
</ng-include>
</div>
</div>
<help-panel>
<ng-include src="step.helpUrl"
ng-repeat="step in steps"
ng-show="currentIndex===$index"></ng-include>
</help-panel>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default pull-left"
ng-click="cancel()">
<span ng-class="::viewModel.btnIcon.cancel||'fa fa-close'"></span>
<span ng-bind="::viewModel.btnText.cancel"></span>
</button>
<button type="button" class="btn btn-default back"
ng-click="switchTo(currentIndex - 1)"
ng-disabled="currentIndex===0">
<span ng-class="::viewModel.btnIcon.back||'fa fa-angle-left'"></span>
<span ng-bind="::viewModel.btnText.back"></span>
</button>
<button type="button" class="btn btn-default next"
ng-click="switchTo(currentIndex + 1)"
ng-disabled="currentIndex===steps.length - 1">
<span ng-bind="::viewModel.btnText.next"></span>
<span ng-class="::viewModel.btnIcon.next||'fa fa-angle-right'"></span>
</button>
<button type="button" class="btn btn-primary finish"
ng-click="viewModel.onClickFinishBtn()"
ng-disabled="wizardForm.$invalid||viewModel.isSubmitting">
<span ng-class="::viewModel.btnIcon.finish||'fa fa-check'"></span>
<span ng-bind="::viewModel.btnText.finish"></span>
</button>
</div>
<div class="step"
ng-repeat="step in steps"
ng-show="currentIndex===$index">
<ng-include
ng-form="{$ step.formName $}"
src="step.templateUrl">
</ng-include>
</div>
<div class="toolbar">
<div class="secondary-btn-grp">
<button class="cancel btn btn-sm btn-default"
ng-click="cancel()">
<span ng-class="::viewModel.btnIcon.cancel||'fa fa-close'"></span>
<span ng-bind="::viewModel.btnText.cancel"></span>
</button>
</div>
<div class="primary-btn-grp">
<button class="back btn btn-sm btn-default"
ng-click="switchTo(currentIndex - 1)"
ng-hide="currentIndex===0">
<span ng-class="::viewModel.btnIcon.back||'fa fa-chevron-left'"></span>
<span ng-bind="::viewModel.btnText.back"></span>
</button>
<button class="next btn btn-sm btn-primary"
ng-click="switchTo(currentIndex + 1)"
ng-hide="currentIndex===steps.length - 1">
<span ng-bind="::viewModel.btnText.next"></span>
<span ng-class="::viewModel.btnIcon.next||'fa fa-chevron-right'"></span>
</button>
<span class="separator"></span>
<button class="finish btn btn-sm btn-success"
ng-click="viewModel.onClickFinishBtn()"
ng-disabled="wizardForm.$invalid||viewModel.isSubmitting">
<span ng-class="::viewModel.btnIcon.finish||'fa fa-check'"></span>
<span ng-bind="::viewModel.btnText.finish"></span>
</button>
</div>
</div>
<div class="error-message" ng-show="viewModel.hasError" ng-bind="viewModel.errorMessage"></div>
<help-panel>
<ng-include src="step.helpUrl"
ng-repeat="step in steps"
ng-show="currentIndex===$index"></ng-include>
</help-panel>
</div>

View File

@ -1,352 +0,0 @@
.ng-wizard {
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
font-weight: normal;
.title {
height: $wizardTitleBarHeight;
line-height: $wizardTitleBarHeight - 6px;
vertical-align: bottom;
padding: 6px $WizardSidePadding 0 $WizardSidePadding;
color: #555;
border-bottom: 1px solid #ddd;
font-size: 22px;
font-weight: normal;
}
.fa.invalid {
color: $invalid-color;
cursor: pointer;
}
& > .nav {
display: inline-block;
top: 40px;
left: 0px;
bottom: 40px;
width: $wizardNavWidth;
padding: 10px 10px 10px $WizardSidePadding;
.nav-item {
position: relative;
display: block;
height: $WizardNavItemHeight;
width: $WizardNavItemWidth;
text-align: left;
padding-left: 10px;
background: transparent;
border: none;
border-bottom: 1px solid $WizardNavItemBdColor;
margin-top: -1px;
color: $WizardNavItemColor;
font-size: 12px;
border-radius: 0;
&::after {
content: " ";
position: absolute;
top: 0;
right: -10px;
display: block;
height: inherit;
width: 0;
border-left: none;
border-top: $WizardNavItemHeight/2 solid transparent;
border-bottom: $WizardNavItemHeight/2 solid transparent;
}
&[disabled] {
color: #444;
background: transparent;
}
&.current {
width: $WizardNavItemWidth;
background: $WizardNavItemBgColor;
color: $WizardButtonColorHiLight;
border-color: $WizardNextBtnBgColor;
&::after {
border-left: $WizardNavItemBgColor 10px solid;
}
&:focus {
outline: none;
}
.status-indicator {
color: inherit;
}
}
.status-indicator {
position: absolute;
right: 0;
top: 18px;
width: $WizardStatusIndicatorSize;
height: $WizardStatusIndicatorSize;
color: orange;
}
}
}
.step {
position: absolute;
top: $wizardTitleBarHeight;
left: $wizardNavWidth;
right: 0;
bottom: $wizardToolBarHeight;
color: #888;
h1 {
position: absolute;
top: 0;
height: 45px;
left: 10px;
right: 65px;
margin: 0;
padding-top: 20px;
}
h2.section-title {
margin-bottom: 5px;
padding-bottom: 5px;
}
h1, h2.section-title {
font-size: 18px;
font-weight: normal;
color: #555;
border-bottom: 1px solid #ddd;
}
.content {
position: absolute;
top: 45px;
left: 10px;
right: 0;
bottom: 0;
padding: 5px 65px 24px 0;
overflow: auto;
.subtitle {
margin-bottom: 30px;
}
label {
font-weight: normal;
color: #555;
}
}
}
.toolbar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: $wizardToolBarHeight;
line-height: $wizardToolBarHeight;
vertical-align: middle;
background: $WizardToolbarBgColor;
border-top: 1px solid $WizardBtnBdColor;
.secondary-btn-grp {
position: absolute;
left: $WizardSidePadding;
}
.primary-btn-grp {
position: absolute;
right: $WizardSidePadding;
}
.secondary-btn-grp button {
margin-right: $WizardBtnGap - 3px; // white space character takes 3px
}
.primary-btn-grp button {
margin-left: $WizardBtnGap - 3px; // white space character takes 3px
}
.btn-wrap {
display: inline-block;
}
.btn-wrap.finish {
padding-left: 15px;
margin-left: 20px;
border-left: 2px solid #ddd;
}
.separator {
display: inline-block;
width: 0;
margin-left: 20px;
margin-right: 15px;
border-left: 1px solid $WizardToolbarVerticalSeparatorBdColor;
height: $wizardToolBarHeight;
vertical-align: top;
}
button {
height: $WizardToolbarBtnHeight;
line-height: $WizardToolbarBtnHeight - 2px;
vertical-align: baseline;
padding: 0 25px 2px 25px;
font-size: 14px;
color: $WizardBtnTextColor;
border: 1px solid $WizardBtnBdColor;
background: $WizardBtnBgColor;
&[disabled] {
color: #ccc;
border-color: #ddd;
background: #fff;
}
&.next {
color: $WizardButtonColorHiLight;
border-color: $WizardNextBtnBdColor;
background: $WizardNextBtnBgColor;
&[disabled] {
color: $WizardButtonColorHiLight;
border-color: $WizardFinishBtnDisabledBdColor;
background: $WizardFinishBtnDisabledBgColor;
}
}
&.finish {
color: $WizardButtonColorHiLight;
border-color: $WizardFinishBtnBdColor;
background: $WizardFinishBtnBgColor;
&[disabled] {
color: $WizardButtonColorHiLight;
border-color: $WizardFinishBtnDisabledBdColor;
background: $WizardFinishBtnDisabledBgColor;
}
}
}
}
.help-panel {
top: $wizardTitleBarHeight;
bottom: $wizardToolBarHeight;
}
.error-message {
display: none !important;
}
}
.modal-dialog-wizard {
.modal-dialog {
position: relative;
margin: 0 auto;
height: $wizardHeight;
width: $wizardWidth;
// $wizardMaxHeight is the max height of the modal content without
// padding, so need to add the padding here.
max-height: $wizardMaxHeight + $wizardTopPadding + $wizardBottomPadding;
// $wizardMaxWidth is the max width of the modal content without
// padding, so need to add the padding here.
max-width: $wizardMaxWidth + $wizardLeftPadding + $wizardRightPadding;
// $wizardMinHeight is the min height of the modal content without
// padding, so need to add the padding here.
min-height: $wizardMinHeight + $wizardTopPadding + $wizardBottomPadding;
// $wizardMinWidth is the min with of the modal content without
// padding, so need to add the padding here.
min-width: $wizardMinWidth + $wizardLeftPadding + $wizardRightPadding;
overflow-x: auto;
.modal-content {
position: absolute;
top: $wizardTopPadding;
left: $wizardLeftPadding;
right: $wizardRightPadding;
bottom: $wizardBottomPadding;
border-radius: 0;
@media (max-width: 1000px) {
left: 0;
right: 0;
}
@media (max-height: 600px) {
top: 0;
bottom: 0;
}
}
}
}
/* workaround for odd Bootstrap checkbox vertical alignment */
.checkbox {
input[type="checkbox"] {
margin-top: 3px;
}
}
.form-control {
@include input-placeholder {
font-weight: normal;
color: $placeholder-text-color;
}
}
.form-group .required label:after {
content: " *";
color: red;
}
.btn-toggle {
color: #333;
background-color: #fff;
border-color: #adadad;
&:hover,
&:focus,
&:active {
background-color: #ebebeb;
}
&.active {
background-color: #0077b3;
border-color: #006699;
color: #fff !important;
}
&.disabled.active,
&[disabled].active {
background-color: rgba(0, 119, 179, 0.65);
border-color: rgba(0, 102, 153, 0.65);
color: #fff;
}
&.disabled,
&.disabled:hover,
&.disabled:focus,
&.disabled:active,
&[disabled]:hover,
&[disabled]:focus,
&[disabled]:active,
fieldset[disabled] &:hover,
fieldset[disabled] &:focus,
fieldset[disabled] &:active,
fieldset[disabled] &.active {
background-color: #fafafa;
border-color: #ccc;
color: #999;
}
}

View File

@ -46,7 +46,7 @@
it('should have empty title by default', function () {
$scope.workflow = {};
$scope.$apply();
expect(element[0].querySelector('.title').textContent).toBe('');
expect(element[0].querySelector('.h4').textContent).toBe('');
});
it('should have title if it is specified by workflow', function () {
@ -54,14 +54,14 @@
$scope.workflow = {};
$scope.workflow.title = titleText;
$scope.$apply();
expect(element[0].querySelector('.title').textContent).toBe(titleText);
expect(element[0].querySelector('.h4').textContent).toBe(titleText);
});
it('should contain one help-panel', function () {
$scope.workflow = {};
$scope.workflow.title = "doesn't matter";
$scope.$apply();
expect(element[0].querySelectorAll('help-panel').length).toBe(1);
expect(element[0].querySelectorAll('#help-panel').length).toBe(1);
});
it('should have no steps if no steps defined', function () {
@ -102,9 +102,9 @@
expect(angular.element(element).find('.step').eq(0).hasClass('ng-hide')).toBe(false);
expect(angular.element(element).find('.step').eq(1).hasClass('ng-hide')).toBe(true);
expect(angular.element(element).find('.step').eq(2).hasClass('ng-hide')).toBe(true);
expect(angular.element(element).find('.nav-item').eq(0).hasClass('current')).toBe(true);
expect(angular.element(element).find('.nav-item').eq(1).hasClass('current')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(2).hasClass('current')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(0).hasClass('active')).toBe(true);
expect(angular.element(element).find('.nav-item').eq(1).hasClass('active')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(2).hasClass('active')).toBe(false);
$scope.switchTo(1);
$scope.$apply();
@ -112,9 +112,9 @@
expect(angular.element(element).find('.step').eq(0).hasClass('ng-hide')).toBe(true);
expect(angular.element(element).find('.step').eq(1).hasClass('ng-hide')).toBe(false);
expect(angular.element(element).find('.step').eq(2).hasClass('ng-hide')).toBe(true);
expect(angular.element(element).find('.nav-item').eq(0).hasClass('current')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(1).hasClass('current')).toBe(true);
expect(angular.element(element).find('.nav-item').eq(2).hasClass('current')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(0).hasClass('active')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(1).hasClass('active')).toBe(true);
expect(angular.element(element).find('.nav-item').eq(2).hasClass('active')).toBe(false);
$scope.switchTo(2);
$scope.$apply();
@ -122,18 +122,18 @@
expect(angular.element(element).find('.step').eq(0).hasClass('ng-hide')).toBe(true);
expect(angular.element(element).find('.step').eq(1).hasClass('ng-hide')).toBe(true);
expect(angular.element(element).find('.step').eq(2).hasClass('ng-hide')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(0).hasClass('current')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(1).hasClass('current')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(2).hasClass('current')).toBe(true);
expect(angular.element(element).find('.nav-item').eq(0).hasClass('active')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(1).hasClass('active')).toBe(false);
expect(angular.element(element).find('.nav-item').eq(2).hasClass('active')).toBe(true);
});
it('should not show back button in step 1/3', function () {
it('should disable back button in step 1/3', function () {
$scope.workflow = {
steps: [{}, {}, {}]
};
$scope.$apply();
expect(angular.element(element).find('button.back').hasClass('ng-hide')).toBe(true);
expect(angular.element(element).find('button.next').hasClass('ng-hide')).toBe(false);
expect(element[0].querySelector('button.back').hasAttribute('disabled')).toBe(true);
expect(element[0].querySelector('button.next').hasAttribute('disabled')).toBe(false);
});
it('should show both back and next button in step 2/3', function () {
@ -143,19 +143,19 @@
$scope.$apply();
$scope.switchTo(1);
$scope.$apply();
expect(angular.element(element).find('button.back').hasClass('ng-hide')).toBe(false);
expect(angular.element(element).find('button.next').hasClass('ng-hide')).toBe(false);
expect(element[0].querySelector('button.back').hasAttribute('disabled')).toBe(false);
expect(element[0].querySelector('button.next').hasAttribute('disabled')).toBe(false);
});
it('should not show next button in step 3/3', function () {
it('should disable next button in step 3/3', function () {
$scope.workflow = {
steps: [{}, {}, {}]
};
$scope.$apply();
$scope.switchTo(2);
$scope.$apply();
expect(angular.element(element).find('button.back').hasClass('ng-hide')).toBe(false);
expect(angular.element(element).find('button.next').hasClass('ng-hide')).toBe(true);
expect(element[0].querySelector('button.back').hasAttribute('disabled')).toBe(false);
expect(element[0].querySelector('button.next').hasAttribute('disabled')).toBe(true);
});
it('should have finish button disabled if wizardForm is invalid', function () {

View File

@ -1 +0,0 @@
@import "workflow/workflow";

View File

@ -1,5 +1,4 @@
<div>
<h1 translate>Configuration Help</h1>
<p translate>Custom scripts are attached to instances to perform specific actions when the instance is launched. For example, if you are unable to install <samp>cloud-init</samp> inside a guest operating system, you can use a custom script to get a public key and add it to the user account.</p>
<p translate>Type your script directly into the Customization Script field. If your browser supports the HTML5 File API, you may choose to load your script from a file. The size of your script should not exceed 16 Kb.</p>
<p translate>An advanced option available when launching an instance is disk partitioning. There are two disk partition options. Selecting <b>Automatic</b> resizes the disk and sets it to a single partition. Selecting <b>Manual</b> allows you to create multiple partitions on the disk.</p>

View File

@ -1,36 +1,35 @@
<div ng-controller="LaunchInstanceConfigurationController as config">
<h1 translate>Configuration</h1>
<p translate>
You can customize your instance after it has launched using the options available here.
"Customization Script" is analogous to "User Data" in other systems.
</p>
<div class="content">
<div class="subtitle"></div>
<load-edit config="config"
user-input="model.newInstanceSpec"
key="user_data">
</load-edit>
<load-edit config="config"
user-input="model.newInstanceSpec"
key="user_data">
</load-edit>
<div hz-if-nova-extensions='["DiskConfig"]'>
<div class="form-group disk-partition">
<label for="launch-instance-disk-partition" translate>
Disk Partition
</label>
<select class="form-control"
id="launch-instance-disk-partition"
ng-model="model.newInstanceSpec.disk_config"
ng-options="option.value as option.text for option in config.diskConfigOptions">
</select>
</div>
<div hz-if-nova-extensions='["DiskConfig"]'>
<div class="form-group">
<label for="disk-partition" translate>
Disk Partition
</label>
<select class="form-control"
id="disk-partition"
ng-model="model.newInstanceSpec.disk_config"
ng-options="option.value as option.text for option in config.diskConfigOptions">
</select>
</div>
</div>
<div hz-if-nova-extensions='["ConfigDrive"]'>
<div class="checkbox customization-script-source">
<label>
<input type="checkbox"
ng-model="model.newInstanceSpec.config_drive">
<span translate>Configuration Drive</span>
</label>
</div>
<div hz-if-nova-extensions='["ConfigDrive"]'>
<div class="checkbox">
<label for="config-drive" translate>
<input type="checkbox"
id="config-drive"
ng-model="model.newInstanceSpec.config_drive">
Configuration Drive
</label>
</div>
</div>
</div>

View File

@ -1,65 +0,0 @@
[ng-controller="LaunchInstanceConfigurationController as config"] {
select {
width: 250px;
}
textarea {
width: 100%;
height: 20em;
font-family: $font-family-monospace;
}
.btn-file {
position: relative;
overflow: hidden;
input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
}
.script-modified {
font-width: normal;
font-style: italic;
color: #888;
}
.fa.invalid {
display: none;
}
.size-indicator.warning {
color: $WizardValidationErrorColor;
border: none;
padding: 0;
margin: 0;
border: none;
.fa.invalid {
display: inline;
color: $invalid-color;
cursor: pointer;
}
}
.script-file:after,
.disk-partition:after {
content: ' ';
display: block;
clear: both;
margin-bottom: 2.5em;
}
}

View File

@ -1,5 +1,3 @@
<h1 translate>Instance Details Help</h1>
<p translate>An instance name is required and used to help you uniquely identify your instance in the dashboard.</p>
<p translate>If you select an availability zone and plan to use the 'boot from volume' option in the Source step, make sure that the availability zone you select for the instance is the same availability zone where your bootable volume resides.</p>

View File

@ -1,71 +1,50 @@
<div ng-controller="LaunchInstanceDetailsController as ctrl">
<h1 translate>Instance Details</h1>
<p translate>Please provide the initial hostname for the instance, the availability zone where it will be deployed, and the instance count.
Increase the Count to create multiple instances with the same settings.</p>
<!--content-->
<div class="content">
<div translate class="subtitle">Please provide the initial hostname for the instance, the availability zone where it will be deployed, and the instance count.
Increase the Count to create multiple instances with the same settings.</div>
<div class="row">
<div class="col-xs-12 col-sm-8">
<div class="form-group required"
ng-class="{ 'has-error': launchInstanceDetailsForm.name.$invalid && launchInstanceDetailsForm.name.$dirty }">
<label translate for="name" class="control-label">
Instance Name
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<input id="name" name="name" type="text" class="form-control"
ng-model="model.newInstanceSpec.name" ng-required="true">
<!--selected-source form-->
<div class="selected-source clearfix">
<div class="row">
<div class="col-xs-12 col-sm-8">
<div class="row form-group">
<div class="col-sm-12 col-md-5">
<div class="form-field required instance-name"
ng-class="{ 'has-error': launchInstanceDetailsForm['instance-name'].$invalid && launchInstanceDetailsForm['instance-name'].$dirty }">
<label translate class="on-top">Instance Name</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="launchInstanceDetailsForm['instance-name'].$invalid && launchInstanceDetailsForm.$dirty"
popover="{$ ctrl.instanceNameError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
<input name="instance-name" type="text" class="form-control input-sm"
ng-model="model.newInstanceSpec.name" ng-required="true">
</div>
</div>
<span class="help-block"
ng-show="launchInstanceDetailsForm.name.$invalid && launchInstanceDetailsForm.name.$dirty">
{$ ctrl.instanceNameError $}
</span>
</div>
<div class="col-sm-12 col-md-5">
<div class="form-field availability-zone">
<label translate class="on-top">Availability Zone</label>
<select class="form-control input-sm"
ng-options="zone for zone in model.availabilityZones"
ng-model="model.newInstanceSpec.availability_zone">
</select>
</div>
</div>
<div class="form-group">
<label class="control-label" translate for="availability-zone">Availability Zone</label>
<select class="form-control" id="availability-zone"
ng-options="zone for zone in model.availabilityZones"
ng-model="model.newInstanceSpec.availability_zone">
</select>
</div>
<div class="col-sm-6 col-md-2">
<div class="form-field instance_count"
ng-class="{ 'has-error': launchInstanceDetailsForm['instance-count'].$invalid }">
<label translate class="on-top">Count</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="launchInstanceDetailsForm['instance-count'].$invalid"
popover="{$ launchInstanceDetailsForm['instance-count'].$error.validateNumberMax ? ctrl.instanceCountMaxError : ctrl.instanceCountError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
<input name="instance-count" type="number" class="form-control input-sm"
ng-model="model.newInstanceSpec.instance_count"
ng-required="true" ng-pattern="/^[0-9]+$/"
validate-number-min="1"
validate-number-max="{$ ctrl.maxInstanceCount $}">
</div>
</div>
</div>
</div>
<!--instance chart-->
<div class="col-xs-12 col-sm-4">
<div class="chart">
<pie-chart chart-data="ctrl.instanceStats"
chart-settings="ctrl.chartSettings"></pie-chart>
</div>
</div>
<!--end instance chart-->
<div class="form-group" ng-class="{ 'has-error': launchInstanceDetailsForm.count.$invalid }">
<label class="control-label" for="instance-count" translate>
Count
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<input id="count" name="count" type="number" class="form-control" min="1"
ng-model="model.newInstanceSpec.instance_count"
ng-required="true" ng-pattern="/^[0-9]+$/"
validate-number-min="1"
validate-number-max="{$ ctrl.maxInstanceCount $}">
<span class="help-block" ng-show="launchInstanceDetailsForm.count.$invalid">
{$ launchInstanceDetailsForm['instance-count'].$error.validateNumberMax ? ctrl.instanceCountMaxError : ctrl.instanceCountError $}
</span>
</div>
</div>
<!--end selected-source form-->
<div class="col-xs-12 col-sm-4">
<pie-chart chart-data="ctrl.instanceStats" chart-settings="ctrl.chartSettings"></pie-chart>
</div>
</div>
<!-- end content -->
</div>

View File

@ -1,9 +1,8 @@
<div>
<h1 translate>Flavor Help</h1>
<p translate>The flavor you select for an instance determines the amount of compute, storage and memory resources that will be carved out for the instance.
</p>
<p translate>The flavor you select must have enough resources allocated to support the type of instance you are trying to create. Flavors that do not provide enough resources for your instance are identified on the <b>Available</b> table with a yellow warning icon.
</p>
<p translate>Administrators are responsible for creating and managing flavors. A custom flavor can be created for you or for a specific project where it is shared with the users assigned to that project. If you need a custom flavor, contact your administrator.
</p>
</div>
</div>

View File

@ -13,28 +13,27 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<div ng-controller="LaunchInstanceFlavorController as selectFlavorCtrl">
<h1 translate>Flavor</h1>
<div class="content">
<div class="subtitle" translate>Flavors manage the sizing for the compute, memory and storage capacity of the instance.</div>
<transfer-table
tr-model="selectFlavorCtrl.transferTableModel"
limits="selectFlavorCtrl.allocationLimits">
<allocated ng-model="selectFlavorCtrl.allocatedFlavorFacades.length"
validate-number-min="1" name="allocated-flavor">
<select-flavor-table
items="selectFlavorCtrl.allocatedFlavorFacades"
displayed-items="selectFlavorCtrl.displayedAllocatedFlavorFacades"
metadata-defs="selectFlavorCtrl.metadataDefs">
</select-flavor-table>
</allocated>
<available>
<select-flavor-table
is-available-table="true"
items="selectFlavorCtrl.availableFlavorFacades"
displayed-items="selectFlavorCtrl.displayedAvailableFlavorFacades"
metadata-defs="selectFlavorCtrl.metadataDefs">
</select-flavor-table>
</available>
</transfer-table>
</div>
<p translate>
Flavors manage the sizing for the compute, memory and storage capacity of the instance.
</p>
<transfer-table
tr-model="selectFlavorCtrl.transferTableModel"
limits="selectFlavorCtrl.allocationLimits">
<allocated ng-model="selectFlavorCtrl.allocatedFlavorFacades.length"
validate-number-min="1" name="allocated-flavor">
<select-flavor-table
items="selectFlavorCtrl.allocatedFlavorFacades"
displayed-items="selectFlavorCtrl.displayedAllocatedFlavorFacades"
metadata-defs="selectFlavorCtrl.metadataDefs">
</select-flavor-table>
</allocated>
<available>
<select-flavor-table
is-available-table="true"
items="selectFlavorCtrl.availableFlavorFacades"
displayed-items="selectFlavorCtrl.displayedAvailableFlavorFacades"
metadata-defs="selectFlavorCtrl.metadataDefs">
</select-flavor-table>
</available>
</transfer-table>
</div>

View File

@ -32,7 +32,7 @@ limitations under the License.
<th st-sort="totalDisk" class="rsp-p1" translate>Total Disk</th>
<th st-sort="rootDisk" class="rsp-p2" translate>Root Disk</th>
<th st-sort="ephemeralDisk" class="rsp-p2" translate>Ephemeral Disk</th>
<th st-sort="isPublic" class="rsp-p2" translate>Public</th>
<th st-sort="isPublic" class="rsp-p1" translate>Public</th>
<th></th>
</tr>
</thead>
@ -77,7 +77,7 @@ limitations under the License.
{$ ::item.rootDisk | gb $}
</td>
<td class="rsp-p2">{$ ::item.ephemeralDisk | gb $}</td>
<td class="rsp-p2">{$ ::item.isPublic | yesno $}</td>
<td class="rsp-p1">{$ ::item.isPublic | yesno $}</td>
<td class="action-col">
<action-list button-tooltip="item.disabledMessage"
bt-model="tooltipModel"
@ -99,15 +99,22 @@ limitations under the License.
<tr ng-repeat-end class="detail-row" ng-if="showItemFunc(item)">
<td></td>
<td colspan="7" class="detail">
<h5 translate>Impact on your quota</h5>
<pie-chart chart-data="item.instancesChartData"
chart-settings="chartSettings"></pie-chart>
<pie-chart chart-data="item.vcpusChartData"
chart-settings="chartSettings"></pie-chart>
<pie-chart chart-data="item.ramChartData"
chart-settings="chartSettings"></pie-chart>
<span class="h5" translate>Impact on your quota</span>
<div class="row">
<div class="col-xs-4">
<pie-chart chart-data="item.instancesChartData"
chart-settings="chartSettings"></pie-chart>
</div>
<div class="col-xs-4">
<pie-chart chart-data="item.vcpusChartData"
chart-settings="chartSettings"></pie-chart>
</div>
<div class="col-xs-4">
<pie-chart chart-data="item.ramChartData"
chart-settings="chartSettings"></pie-chart>
</div>
</div>
<div ng-if="metadataDefs.flavor">
<hr>
<div class="row" ng-if="item.extras">
<div class="col-sm-12">
<metadata-display

View File

@ -1,52 +1,45 @@
<div class="ng-wizard no-navigation" ng-form="wizardForm">
<div class="download-iframes" style="display: none;"></div>
<div class="title" translate>Launch Instance</div>
<div class="ng-wizard" ng-form="wizardForm">
<div class="step">
<h1 translate>Create Key Pair</h1>
<div class="modal-header">
<button type="button" class="close" ng-click="ctrl.cancel()" aria-hidden="true" aria-label="Close">
<span aria-hidden="true" class="fa fa-close"></span>
</button>
<span class="h4 modal-title">Create Key Pair</span>
</div>
<div class="content">
<div class="subtitle" translate>
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.
</div>
<div class="form-group">
<div class="row">
<div class="col-md-4 required">
<label translate>Key Pair Name</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="ctrl.doesKeypairExist(ctrl.keypair) || wizardForm.$invalid"
popover="{$ ctrl.keypairExistsError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
</div>
</div>
<div class="form-field required row">
<div class="col-md-4" ng-class="{'has-error': ctrl.doesKeypairExist()}">
<input class="form-control" name="name"
ng-model="ctrl.keypair"
ng-required="true"
ng-pattern="/^[A-Za-z0-9 -]+$/"
placeholder="{$ 'Required' | translate $}">
</div>
</div>
</div>
<div class="modal-body">
<p translate>
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.
</p>
<div class="form-group" ng-class="{'has-error': (ctrl.doesKeypairExist() || wizardForm.$invalid) && wizardForm.$dirty }">
<label class="control-label" for="keypair-name" translate>
Key Pair Name
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<input class="form-control" name="name"
id="keypair-name"
ng-model="ctrl.keypair"
ng-required="true"
ng-pattern="/^[A-Za-z0-9 -]+$/">
<span class="help-block"
ng-show="(ctrl.doesKeypairExist() || wizardForm.$invalid) && wizardForm.$dirty">
{$ ctrl.keypairExistsError $}
</span>
</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>
<translate>Cancel</translate>
</button>
</div>
<div class="primary-btn-grp">
<button class="finish btn btn-sm btn-success"
ng-click="ctrl.submit()" ng-disabled="wizardForm.$invalid || ctrl.doesKeypairExist()">
<translate>Create Keypair</translate>
</button>
</div>
<div class="modal-footer">
<button class="btn btn-default pull-left" ng-click="ctrl.cancel()">
<span class="fa fa-close"></span>
<translate>Cancel</translate>
</button>
<button class="btn btn-primary"
ng-click="ctrl.submit()" ng-disabled="wizardForm.$invalid || ctrl.doesKeypairExist()">
<translate>Create Keypair</translate>
</button>
</div>
</div>

View File

@ -1,50 +1,55 @@
<div class="ng-wizard no-navigation" ng-form="wizardForm">
<div class="title" translate>Launch Instance</div>
<div class="ng-wizard" ng-form="wizardForm">
<div class="step">
<h1 translate>Import Key Pair</h1>
<div class="content">
<div class="subtitle" translate>
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.
</div>
<div class="form-group">
<div class="form-field required name">
<label translate>Key Pair Name</label>
<input class="form-control" name="name"
ng-model="ctrl.model.name"
ng-required="true" placeholder="{$ 'Required' | translate $}"/>
</div>
<div class="form-field required key">
<label translate>Public Key</label>
<textarea class="form-control" name="key" rows="15"
ng-model="ctrl.model.public_key"
ng-required="true" placeholder="{$ 'Required' | translate $}">
</textarea>
</div>
</div>
</div>
<div class="modal-header">
<button type="button" class="close" ng-click="ctrl.cancel()" aria-hidden="true" aria-label="Close">
<span aria-hidden="true" class="fa fa-close"></span>
</button>
<span class="h4 modal-title">Import Key Pair</span>
</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>
<translate>Cancel</translate>
</button>
<div class="modal-body">
<help-panel>
<ng-include src="ctrl.path + 'keypair.help.html'"></ng-include>
</help-panel>
<p translate>
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.
</p>
<div class="form-group">
<label for="keypair-name"translate>
Key Pair Name
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<input class="form-control" name="name" id="keypair-name"
ng-model="ctrl.model.name"
ng-required="true"/>
</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>
<translate>Import Key Pair</translate>
</button>
<div class="form-group">
<label for="public-key" translate>
Public Key
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<textarea class="form-control" id="public-key" name="key" rows="15"
ng-model="ctrl.model.public_key"
ng-required="true">
</textarea>
</div>
</div>
<help-panel>
<ng-include src="ctrl.path + 'keypair.help.html'"></ng-include>
</help-panel>
<div class="modal-footer">
<button class="btn btn-default pull-left" ng-click="ctrl.cancel()">
<span class="fa fa-close"></span>
<translate>Cancel</translate>
</button>
<button class="btn btn-primary"
ng-click="ctrl.submit()" ng-disabled="wizardForm.$invalid">
<span class="fa fa-upload"></span>
<translate>Import Key Pair</translate>
</button>
</div>
</div>

View File

@ -1,4 +1,3 @@
<h1 translate>Key Pair Help</h1>
<p translate>
There are two ways to generate a key pair. From a Linux system,
generate the key pair with the <samp>ssh-keygen</samp> command:

View File

@ -1,147 +1,134 @@
<div ng-controller="LaunchInstanceKeypairController as ctrl">
<h1 translate>Key Pair</h1>
<p translate>
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.
</p>
<div class="content">
<div ng-if="ctrl.isKeypairCreated" class="alert alert-info" role="alert">
<p translate>A key pair named '{$ctrl.createdKeypair.name$}' was successfully created. This key pair should automatically download.</p>
<p translate>If not, you can manually dowload this keypair at the following link:</p>
<a class="btn btn-default" role="button" href="{$ ctrl.createdKeypair.regenerateUrl $}">
<span class="fa fa-download"></span>
{$ctrl.createdKeypair.name$}
</a>
<p translate>
Note: you will not be able to download this later.
</p>
</div>
<div class="subtitle" translate>
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.
</div>
<div ng-if="ctrl.isKeypairCreated" class="alert alert-info">
<div translate>A key pair named '{$ctrl.createdKeypair.name$}' was successfully created. This key pair should automatically download.</div>
<div>
<translate>If not, you can manually dowload this keypair at the following link:<translate>
<a href="{$ ctrl.createdKeypair.regenerateUrl $}">
<span class="fa fa-download"></span>
{$ctrl.createdKeypair.name$}
</a>
</div>
<div translate>
Note: you will not be able to download this later.
</div>
</div>
<button type="button" class="btn btn-default"
ng-click="ctrl.createKeyPair()">
<span class="fa fa-plus"></span>
<translate>Create Key Pair</translate>
</button>
<button type="button" class="btn btn-default"
ng-click="ctrl.importKeyPair()">
<span class="fa fa-upload"></span>
<translate>Import Key Pair</translate>
</button>
<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>
<translate>Create Key Pair</translate>
</button>
<button type="button" class="btn btn-sm btn-primary pull-right"
ng-click="ctrl.importKeyPair()">
<span class="fa fa-fw fa-upload"></span>
<translate>Import Key Pair</translate>
</button>
</div>
</div>
<transfer-table tr-model="ctrl.tableData"
limits="ctrl.tableLimits">
<transfer-table tr-model="ctrl.tableData"
limits="ctrl.tableLimits">
<!-- Key Pairs Allocated-->
<allocated>
<table st-table="ctrl.tableData.displayedAllocated"
st-safe-src="ctrl.tableData.allocated" hz-table
class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="expander"></th>
<th class="rsp-p1" translate>Name</th>
<th class="rsp-p2" translate>Fingerprint</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-if="ctrl.tableData.allocated.length === 0">
<td colspan="8">
<div class="no-rows-help" translate>
Select a key pair from the available key pairs below.
</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 Allocated-->
<allocated>
<table st-table="ctrl.tableData.displayedAllocated"
st-safe-src="ctrl.tableData.allocated" hz-table
class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="expander"></th>
<th class="rsp-p1" translate>Name</th>
<th class="rsp-p2" translate>Fingerprint</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-if="ctrl.tableData.allocated.length === 0">
<!-- Key Pairs Available -->
<available>
<table st-table="ctrl.tableData.displayedAvailable"
st-safe-src="ctrl.tableData.available"
hz-table class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="search-header" colspan="7">
<hz-search-bar icon-classes="fa-search"></hz-search-bar>
</th>
</tr>
<tr>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
<th st-sort="fingerprint" class="rsp-p1" translate>Fingerprint</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-if="trCtrl.numAvailable() === 0">
<td colspan="8">
<div class="no-rows-help" translate>
Select a key pair from the available key pairs below.
<div class="no-rows-help">
{$ ::trCtrl.helpText.noneAvailText $}
</div>
</td>
</tr>
<tr ng-repeat-start="row in ctrl.tableData.displayedAllocated track by row.id">
<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>
</td>
<td class="rsp-p1">{$ row.name $}</td>
<td class="rsp-p2">{$ row.fingerprint $}</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.deallocate" item="row">
<span class="fa fa-minus"></span>
callback="trCtrl.allocate" item="row">
<span class="fa fa-plus"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<tr ng-repeat-end class="detail-row" ng-if="!trCtrl.allocatedIds[row.id]">
<td></td>
<td class="detail" colspan="3">
<dl class="dl-horizontal" ng-include="ctrl.tableDetails">
</dl>
<td class="detail" colspan="3" ng-include="ctrl.tableDetails">
</td>
</tr>
</tbody>
</table>
</allocated>
</tbody>
</table>
</available>
<!-- Key Pairs Available -->
<available>
<table st-table="ctrl.tableData.displayedAvailable"
st-safe-src="ctrl.tableData.available"
hz-table class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="search-header" colspan="7">
<hz-search-bar group-classes="input-group-sm" icon-classes="fa-search">
</hz-search-bar>
</th>
</tr>
<tr>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
<th st-sort="fingerprint" class="rsp-p1" translate>Fingerprint</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-if="trCtrl.numAvailable() === 0">
<td colspan="8">
<div class="no-rows-help">
{$ ::trCtrl.helpText.noneAvailText $}
</div>
</td>
</tr>
<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>
</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">
<span class="fa fa-plus"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row" ng-if="!trCtrl.allocatedIds[row.id]">
<td></td>
<td class="detail" colspan="3" ng-include="ctrl.tableDetails">
</td>
</tr>
</tbody>
</table>
</available>
</transfer-table> <!-- End Key Pairs Table -->
</div> <!-- End Content -->
</transfer-table> <!-- End Key Pairs Table -->
</div> <!-- End Controller -->

View File

@ -1,33 +0,0 @@
[ng-controller="LaunchInstanceKeypairCtrl as ctrl"] {
dl.key-pair-details {
dt {
width: 15%;
}
dd {
width: 85%;
margin-left: 15%;
padding-right: 25px;
pre {
background: none;
}
}
}
textarea {
font-family: $font-family-monospace;
}
}
.no-navigation {
.step {
left: 25px;
}
.form-field {
margin-bottom: 1em;
}
}

View File

@ -93,7 +93,7 @@
},
btnIcon: {
finish: 'fa fa-cloud-download'
finish: 'fa fa-cloud-upload'
}
});
}

View File

@ -21,9 +21,9 @@
.config(config)
.constant('horizon.dashboard.project.workflow.launch-instance.modal-spec', {
backdrop: 'static',
size: 'lg',
controller: 'ModalContainerController',
template: '<wizard ng-controller="LaunchInstanceWizardController"></wizard>',
windowClass: 'modal-dialog-wizard'
template: '<wizard class="wizard" ng-controller="LaunchInstanceWizardController"></wizard>'
})
/**

View File

@ -1,6 +0,0 @@
@import "source/source";
@import "flavor/flavor";
@import "network/network";
@import "keypair/keypair";
@import "security-groups/security-groups";
@import "configuration/configuration";

View File

@ -1,5 +1,4 @@
<div>
<h1 translate>Metadata Help</h1>
<p translate>
You can add arbitrary metadata to your instance so that you can more easily identify it among other running instances. Metadata is a collection of key-value pairs associated with an instance. The maximum length for each metadata key and value is 255 characters.
</p>

View File

@ -1,14 +1,13 @@
<div>
<h1 translate>Metadata</h1>
<div class="content">
<metadata-tree
ng-if="model.metadataDefs.instance && model.novaLimits"
available="::model.metadataDefs.instance"
existing="{}"
max-key-length="255"
max-value-length="255"
max-item-count="::model.novaLimits.maxServerMeta"
model="::model.metadataTree">
</metadata-tree>
</div>
</div>
<p translate>
This step allows you to add Metadata items to your instance.
</p>
<metadata-tree
ng-if="model.metadataDefs.instance && model.novaLimits"
available="::model.metadataDefs.instance"
existing="{}"
max-key-length="255"
max-value-length="255"
max-item-count="::model.novaLimits.maxServerMeta"
model="::model.metadataTree">
</metadata-tree>

View File

@ -28,7 +28,7 @@
var $compile = $injector.get('$compile');
var $templateCache = $injector.get('$templateCache');
var basePath = $injector.get('horizon.dashboard.project.workflow.launch-instance.basePath');
var markup = $templateCache.get(basePath + 'metadata/metadata.html');
var markup = '<div>' + $templateCache.get(basePath + 'metadata/metadata.html') + '</div>';
model = {
metadataDefs: { instance: false },
novaLimits: false

View File

@ -1,5 +1,4 @@
<div>
<h1 translate>Network Help</h1>
<p translate>
Provider networks are created by administrators.
These networks map to an existing physical network in the data center.

View File

@ -1,9 +1,7 @@
<div ng-controller="LaunchInstanceNetworkController as ctrl">
<h1 translate>Networks</h1>
<div class="content">
<div class="subtitle" translate>
Networks provide the communication channels for instances in the cloud.
</div>
<p translate>
Networks provide the communication channels for instances in the cloud.
</p>
<div class="form-group" ng-if="model.arePortProfilesSupported">
<label class="control-label required" for="profile" translate>Profile</label>
@ -105,7 +103,6 @@
</th>
</tr>
<tr>
<th class="reorder"></th>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Network</th>
<th class="rsp-p2" translate>Subnets Associated</th>
@ -117,14 +114,13 @@
</thead>
<tbody>
<tr ng-if="trCtrl.numAvailable() === 0">
<td colspan="8">
<td colspan="7">
<div class="no-rows-help" translate>
No available items
</div>
</td>
</tr>
<tr ng-repeat-start="row in ctrl.tableDataMulti.displayedAvailable track by row.id" ng-if="!trCtrl.allocatedIds[row.id]">
<td class="reorder"></td>
<td class="expander">
<span class="fa fa-chevron-right" hz-expand-detail
title="{$ 'Click to see more details'|translate $}"></span>
@ -151,7 +147,7 @@
</td>
</tr>
<tr ng-repeat-end class="detail-row" ng-if="!trCtrl.allocatedIds[row.id]">
<td colspan="2"></td>
<td></td>
<td colspan="7" class="detail">
<dl class="dl-horizontal">
<dt translate>ID</dt>
@ -182,6 +178,4 @@
</table>
</available>
</transfer-table>
</div>
</div>

View File

@ -9,12 +9,14 @@
<th st-sort="remote_ip_prefix" translate>Remote</th>
</tr>
</thead>
<tr ng-repeat="d in row.security_group_rules">
<td>{$ d.direction | noValue $}</td>
<td>{$ d.ethertype | noValue $}</td>
<td>{$ d.protocol | noValue $}</td>
<td>{$ d.port_range_min | noValue $}</td>
<td>{$ d.port_range_max | noValue $}</td>
<td>{$ d.remote_ip_prefix | noValue $}</td>
</tr>
<tbody>
<tr ng-repeat="d in row.security_group_rules">
<td>{$ d.direction | noValue $}</td>
<td>{$ d.ethertype | noValue $}</td>
<td>{$ d.protocol | noValue $}</td>
<td>{$ d.port_range_min | noValue $}</td>
<td>{$ d.port_range_max | noValue $}</td>
<td>{$ d.remote_ip_prefix | noValue $}</td>
</tr>
</tbody>
</table>

View File

@ -1,5 +1,4 @@
<div>
<h1 translate>Security Groups Help</h1>
<p translate>Security groups define a set of IP filter rules that determine how network traffic flows to and from an instance. Users can add additional rules to an existing security group to further define the access options for an instance. To create additional rules, go to the <b>Compute | Access & Security</b> view, then find the security group and click <b>Manage Rules</b>.</p>
<p translate>Security groups are project-specific and cannot be shared across projects.</p>
<p translate>If a security group is not associated with an instance before it is launched, then you will have very limited access to the instance after it is deployed. You will only be able to access the instance from a VNC console.</p>

View File

@ -1,114 +1,108 @@
<div ng-controller="LaunchInstanceSecurityGroupsController as ctrl">
<h1 translate>Security Groups</h1>
<p translate>Select the security groups to launch the instance in.</p>
<div class="content">
<div class="subtitle" translate>Select the security groups.</div>
<transfer-table tr-model="ctrl.tableData"
help-text="ctrl.tableHelp"
limits="ctrl.tableLimits">
<transfer-table tr-model="ctrl.tableData"
help-text="ctrl.tableHelp"
limits="ctrl.tableLimits">
<!-- Security Groups Allocated -->
<allocated>
<table st-table="ctrl.tableData.displayedAllocated"
st-safe-src="ctrl.tableData.allocated" hz-table
class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
<th st-sort="description" class="rsp-p2" translate>Description</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.description $}</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" ng-include="ctrl.tableDetails">
</td>
</tr>
</tbody>
</table>
</allocated>
<!-- Security Groups Allocated -->
<allocated>
<table st-table="ctrl.tableData.displayedAllocated"
st-safe-src="ctrl.tableData.allocated" hz-table
class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
<th st-sort="description" class="rsp-p2" translate>Description</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.description $}</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" ng-include="ctrl.tableDetails">
</td>
</tr>
</tbody>
</table>
</allocated>
<!-- Security Groups Available -->
<available>
<table st-table="ctrl.tableData.displayedAvailable"
st-safe-src="ctrl.tableData.available"
hz-table class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="search-header" colspan="7">
<hz-search-bar icon-classes="fa-search"></hz-search-bar>
</th>
</tr>
<tr>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
<th st-sort="description" class="rsp-p1" translate>Description</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-if="trCtrl.numAvailable() === 0">
<td colspan="8">
<div class="no-rows-help">
{$ ::trCtrl.helpText.noneAvailText $}
</div>
</td>
</tr>
<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>
</td>
<td class="rsp-p1">{$ row.name$}</td>
<td class="rsp-p1">{$ row.description $}</td>
<td class="action-col">
<action-list>
<action action-classes="'btn btn-sm btn-default'"
callback="trCtrl.allocate" item="row">
<span class="fa fa-plus"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row" ng-if="!trCtrl.allocatedIds[row.id]">
<td></td>
<td class="detail" colspan="3" ng-include="ctrl.tableDetails">
</td>
</tr>
</tbody>
</table>
</available>
<!-- Security Groups Available -->
<available>
<table st-table="ctrl.tableData.displayedAvailable"
st-safe-src="ctrl.tableData.available"
hz-table class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th class="search-header" colspan="7">
<hz-search-bar group-classes="input-group-sm"
icon-classes="fa-search">
</hz-search-bar>
</th>
</tr>
<tr>
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
<th st-sort="description" class="rsp-p1" translate>Description</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-if="trCtrl.numAvailable() === 0">
<td colspan="8">
<div class="no-rows-help">
{$ ::trCtrl.helpText.noneAvailText $}
</div>
</td>
</tr>
<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>
</td>
<td class="rsp-p1">{$ row.name$}</td>
<td class="rsp-p1">{$ row.description $}</td>
<td class="action-col">
<action-list>
<action action-classes="'btn btn-sm btn-default'"
callback="trCtrl.allocate" item="row">
<span class="fa fa-plus"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row" ng-if="!trCtrl.allocatedIds[row.id]">
<td></td>
<td class="detail" colspan="3" ng-include="ctrl.tableDetails">
</td>
</tr>
</tbody>
</table>
</available>
</transfer-table> <!-- End Security Groups Transfer Table -->
</transfer-table> <!-- End Security Groups Transfer Table -->
</div> <!-- End Content -->
</div> <!-- End Controller -->

View File

@ -1,11 +0,0 @@
[ng-controller="LaunchInstanceSecurityGroupsController as ctrl"] {
.table-rsp.security-group-details {
background: none;
td {
background: none !important;
padding: 15px !important;
}
}
}

View File

@ -1,7 +1,4 @@
<div>
<h1 translate>Instance Source Help</h1>
<p translate>If you want to create an instance that uses ephemeral storage, meaning the instance data is lost when the instance is deleted, then choose one of the following boot sources:</p>
<p translate><li><b>Image</b>: This option uses an image to boot the instance.</li></p>
<p translate><li><b>Instance Snapshot</b>: This option uses an instance snapshot to boot the instance.</li></p>
@ -9,5 +6,4 @@
<p translate><li><b>Image (with Create New Volume checked)</b>: This options uses an image to boot the instance, and creates a new volume to persist instance data. You can specify volume size and whether to delete the volume on deletion of the instance.</li></p>
<p translate><li><b>Volume</b>: This option uses a volume that already exists. It does not create a new volume. You can choose to delete the volume on deletion of the instance. <em>Note: when selecting Volume, you can only launch one instance.</em></li></p>
<p translate><li><b>Volume Snapshot</b>: This option uses a volume snapshot to boot the instance, and creates a new volume to persist instance data. You can choose to delete the volume on deletion of the instance.</li></p>
</div>

View File

@ -1,124 +1,105 @@
<div ng-controller="LaunchInstanceSourceController as ctrl">
<!--content-->
<h1 translate>Instance Source</h1>
<div class="content">
<div translate class="subtitle">Instance source is the template used to create an instance. You can use a snapshot of an existing instance, an image, or a volume (if enabled).
You can also choose to use persistent storage by creating a new volume.</div>
<p translate>
Instance source is the template used to create an instance. You can use a snapshot of an existing instance, an image, or a volume (if enabled).
You can also choose to use persistent storage by creating a new volume.
</p>
<!--instance-source form-->
<div class="instance-source clearfix">
<div class="form-horizontal">
<div class="row">
<div class="col-xs-12 col-sm-3">
<div class="form-field image"
ng-class="{ 'has-error': launchInstanceSourceForm['boot-source-type'].$invalid }">
<label translate class="on-top">Select Boot Source</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="launchInstanceSourceForm['boot-source-type'].$invalid"
popover="{$ ctrl.bootSourceTypeError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
<select name="boot-source-type" class="form-control input-sm"
ng-options="src.label for src in ctrl.bootSourcesOptions track by src.type"
ng-change="ctrl.updateBootSourceSelection(model.newInstanceSpec.source_type.type)"
ng-model="model.newInstanceSpec.source_type">
</select>
<div class="row">
<div class="col-xs-6">
<div class="form-group" ng-class="{ 'has-error': launchInstanceSourceForm.boot-source-type.$invalid }">
<label for="boot-source-type" class="control-label" translate>Select Boot Source</label>
<select name="boot-source-type" class="form-control" id="boot-source-type"
ng-options="src.label for src in ctrl.bootSourcesOptions track by src.type"
ng-change="ctrl.updateBootSourceSelection(model.newInstanceSpec.source_type.type)"
ng-model="model.newInstanceSpec.source_type">
</select>
<span class="help-block" ng-show="launchInstanceSourceForm.boot-source-type.$invalid">
{$ ctrl.bootSourceTypeError $}
</span>
</div>
</div>
<div ng-if="model.newInstanceSpec.source_type.type === 'image' &&
model.allowCreateVolumeFromImage">
<div class="col-xs-6">
<div class="form-group">
<label for="vol-create" translate>Create New Volume</label><br/>
<div class="btn-group">
<label class="btn btn-default" id="vol-create"
ng-repeat="option in ctrl.toggleButtonOptions"
ng-model="model.newInstanceSpec.vol_create"
btn-radio="option.value">{$ ::option.label $}</label>
</div>
</div>
</div>
</div>
<!-- start image select options -->
<div class="col-xs-12 col-sm-9"
ng-if="model.newInstanceSpec.source_type.type === 'image' &&
model.allowCreateVolumeFromImage">
<div class="col-xs-12 col-sm-3">
<div class="form-group create-volume">
<label translate class="on-top">Create New Volume</label>
<div class="form-field">
<div class="btn-group">
<label class="btn btn-toggle"
ng-repeat="option in ctrl.toggleButtonOptions"
ng-model="model.newInstanceSpec.vol_create"
btn-radio="option.value">{$ ::option.label $}</label>
</div>
</div>
</div>
<div ng-if="model.newInstanceSpec.source_type.type == 'volume' || model.newInstanceSpec.source_type.type == 'volume_snapshot'">
<div class="col-xs-6">
<div class="form-group">
<label translate>Delete Volume on Instance Delete</label><br/>
<div class="btn-group">
<label class="btn btn-default"
ng-repeat="option in ctrl.toggleButtonOptions"
ng-model="model.newInstanceSpec.vol_delete_on_instance_delete"
btn-radio="option.value">{$ ::option.label $}</label>
</div>
<div hz-if-settings='["OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point"]'
ng-if="model.newInstanceSpec.vol_create === true">
<div class="col-xs-12 col-sm-3">
<div class="form-field">
<label translate>Device Name</label>
<input class="form-control input-sm"
ng-model="model.newInstanceSpec.vol_device_name"
type="text">
</div>
</div>
</div>
<div class="col-xs-12 col-sm-2 volume-size-wrapper" ng-if="model.newInstanceSpec.vol_create == true">
<div class="form-field volume-size"
ng-class="{ 'has-error': launchInstanceSourceForm['volume-size'].$invalid }">
<label translate class="on-top">Size (GB)</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="launchInstanceSourceForm['volume-size'].$invalid"
popover="{$ launchInstanceSourceForm['volume-size'].$error.validateNumberMin ? ctrl.minVolumeSizeError :
ctrl.volumeSizeError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
<input name="volume-size" type="number"
class="form-control input-sm volume-size"
ng-model="model.newInstanceSpec.vol_size"
ng-pattern="/^[0-9]+$/" ng-required="true"
validate-number-min="{$ ctrl.minVolumeSize $}">
</div>
</div>
<div class="col-xs-12 col-sm-4" ng-if="model.newInstanceSpec.vol_create == true">
<div class="form-group delete-volume">
<label translate class="on-top">Delete Volume on Instance Delete</label>
<div class="form-field">
<div class="btn-group">
<label class="btn btn-toggle"
ng-repeat="option in ctrl.toggleButtonOptions"
ng-model="model.newInstanceSpec.vol_delete_on_instance_delete"
btn-radio="option.value">{$ ::option.label $}</label>
</div>
</div>
</div>
</div>
</div><!-- end image select options -->
<!-- start volume select options -->
<div class="col-xs-12 col-sm-9"
ng-if="model.newInstanceSpec.source_type.type == 'volume' || model.newInstanceSpec.source_type.type == 'volume_snapshot'">
<div class="col-xs-12 col-sm-6">
<div class="form-group delete-volume">
<label translate class="on-top">Delete Volume on Instance Delete</label>
<div class="form-field">
<div class="btn-group">
<label class="btn btn-toggle"
ng-repeat="option in ctrl.toggleButtonOptions"
ng-model="model.newInstanceSpec.vol_delete_on_instance_delete"
btn-radio="option.value">{$ ::option.label $}</label>
</div>
</div>
</div>
</div>
</div><!-- end volume select options -->
</div>
</div>
</div>
</div>
<!--end instance-source form-->
<div class="row">
<div class="col-xs-6">
<div ng-if="model.newInstanceSpec.vol_create == true">
<div class="form-group" ng-class="{ 'has-error': launchInstanceSourceForm['volume-size'].$invalid }">
<label for="volume-size" class="control-label" translate>
Volume Size (GB)
<span class="hz-icon-required fa fa-asterisk"></span>
</label>
<input name="volume-size"
min="0"
id="volume-size"
type="number"
class="form-control"
ng-model="model.newInstanceSpec.vol_size"
ng-pattern="/^[0-9]+$/"
ng-required="true"
validate-number-min="{$ ctrl.minVolumeSize $}">
<span class="help-block" ng-show="launchInstanceSourceForm['volume-size'].$invalid">
{$ launchInstanceSourceForm['volume-size'].$error.validateNumberMin ? ctrl.minVolumeSizeError : ctrl.volumeSizeError $}
</span>
</div>
</div>
</div>
<div class="col-xs-6">
<div ng-if="model.newInstanceSpec.vol_create == true">
<div class="form-group">
<label translate class="control-label">Delete Volume on Instance Delete</label><br/>
<div class="btn-group">
<label class="btn btn-default"
ng-repeat="option in ctrl.toggleButtonOptions"
ng-model="model.newInstanceSpec.vol_delete_on_instance_delete"
btn-radio="option.value">{$ ::option.label $}</label>
</div>
</div>
</div>
</div>
</div>
<div hz-if-settings='["OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point"]'
ng-if="model.newInstanceSpec.vol_create === true">
<label translate>Device Name</label>
<input class="form-control"
ng-model="model.newInstanceSpec.vol_device_name"
type="text">
</div>
<transfer-table help-text="ctrl.helpText"
tr-model="ctrl.tableData">
<allocated validate-number-min="1" ng-model="ctrl.tableData.allocated.length">
<table class="table table-striped table-rsp table-detail"
<table class="table table-striped table-rsp table-detail modern"
hz-table
st-safe-src="ctrl.tableData.allocated"
st-table="ctrl.tableData.displayAllocated">
@ -216,7 +197,7 @@
<table st-table="ctrl.tableData.displayedAvailable"
st-safe-src="ctrl.tableData.available"
hz-table
class="table table-striped table-rsp table-detail">
class="table table-striped table-rsp table-detail modern">
<!-- transfer table, available table head -->
<thead>
@ -327,5 +308,3 @@
</transfer-table>
</div>
<!-- end content -->
</div>

View File

@ -1,44 +0,0 @@
[ng-controller="LaunchInstanceSourceController"] {
td.hi-light {
color: #0084d1;
}
th.number,
td.number {
text-align: right;
padding-right: 30px;
}
.selected-source {
background: #eee;
padding: 12px 18px;
margin-top: 20px;
margin-bottom: 20px;
.chart {
width: 99%;
margin-bottom: 0;
padding: 0 10px 10px 10px;
@media (min-width: 768px) {
border-left: 1px solid #ccc;
padding-left: 20px;
}
}
}
.instance-source {
margin-top: 18px;
margin-bottom: 40px;
.image select {
width: 99%;
}
.volume-size input[type="number"]{
width: 90%;
}
}
}

View File

@ -1 +0,0 @@
@import "launch-instance/launch-instance";

View File

@ -30,7 +30,3 @@ AUTO_DISCOVER_STATIC_FILES = True
ADD_JS_FILES = []
ADD_JS_SPEC_FILES = []
ADD_SCSS_FILES = [
'dashboard/project/project.scss'
]

View File

@ -46,11 +46,6 @@ dt {
top: 0 !important;
}
// Note (hurgleburgler) Whatever is using these styles should be using alerts
.has-error .help-block, .dynamic-error {
padding: 10px;
}
.dynamic-error {
background: $body-bg;
border: 1px solid $border-color;
@ -266,4 +261,4 @@ td .btn-group {
.tooltip {
z-index: 12000;
word-wrap: break-word;
}
}

View File

@ -1,4 +1,5 @@
/* Some utility classes useful everywhere */
@import "/bootstrap/scss/bootstrap/mixins/vendor-prefixes";
.row .horizontal-center,
.horizontal-center {
@ -58,3 +59,10 @@ input::-ms-clear, input::-ms-reveal {
text-overflow: ellipsis;
display: block;
}
// Add functionality for a horizontal Bootstrap toggle element
.collapsing.width {
@include transition-property(width, visibility);
width: 0;
height: auto;
}

View File

@ -143,7 +143,6 @@ $tooltip-key-weight: 600 !default;
$tooltip-padding: 0.3em 0.8em !default;
/* Transfer Tables */
$badge-info-color: #0084d1 !default;
$invalid-color: #f0ad4e !default;
$transfer-btn-width: 3em !default;
$transfer-help-text-color: #999999 !default;

View File

@ -0,0 +1,40 @@
$help-panel-width: 400px;
.help-toggle,
.wizard-help,
#help-panel {
position: absolute;
top: $padding-xs-horizontal;
right: 0;
z-index: 2; // TODO(robcresswell) untangle the need for this sorcery
}
#help-panel > div {
width: $help-panel-width;
}
// Controls the size of the "?" icon on the right of the wizards
.help-toggle {
@extend .btn-xs;
font-size: $font-size-h3;
.fa {
@extend .fa-question-circle;
}
&:not(.collapsed) {
z-index: 3;
&,
&:hover,
&:active,
&:focus {
@extend .close;
margin: $padding-xs-horizontal;
}
.fa {
@extend .fa-times;
}
}
}

View File

@ -0,0 +1,10 @@
.hz-icon-required {
font-size: 50%;
vertical-align: top;
color: $brand-primary;
}
// Make sure the color is correct on selected workflow steps
.active > a > .hz-icon-required {
color: $component-active-color;
}

View File

@ -0,0 +1,18 @@
.transfer-table {
.fa[title] {
cursor: pointer;
}
.transfer-heading{
@extend h5;
font-size: $font-size-h5;
}
.transfer-section {
margin-top: $padding-large-vertical;
}
.magic-search-bar, .basic-search-bar {
margin: $padding-small-vertical 0;
}
}

View File

@ -0,0 +1,11 @@
// Reapply border colour and prevent float right
.wizard-nav-toggle {
border-color: $btn-default-border;
float: none;
margin-bottom: $padding-small-vertical;
}
// Prevent extra padding on the side nav
.wizard-nav {
padding: 0;
}

View File

@ -85,11 +85,5 @@
color: $workflow-color-label-error;
}
}
.hz-icon-required {
font-size: 50%;
vertical-align: top;
color: $brand-primary;
}
}

View File

@ -19,6 +19,8 @@
@import "components/charts";
@import "components/datepicker";
@import "components/forms";
@import "components/help_panel";
@import "components/icons";
@import "components/inline_edit";
@import "components/login";
@import "components/membership";
@ -35,6 +37,8 @@
@import "components/sidebar";
@import "components/table_actions";
@import "components/tables";
@import "components/transfer_tables";
@import "components/wizard";
@import "components/workflow";
// Framework

View File

@ -405,8 +405,8 @@ $navbar-inverse-toggle-border-color: #333 !default;
//##
//=== Shared nav styles
$nav-link-padding: 0.5em 1.2em !default;
$nav-link-hover-bg: #dbdcdf !default;
$nav-link-padding: 0.8em 1.2em !default;
$nav-link-hover-bg: $gray-lighter !default;
$nav-disabled-link-color: $gray-light !default;
$nav-disabled-link-hover-color: $gray-light !default;
@ -426,9 +426,9 @@ $nav-tabs-justified-link-border-color: #ddd !default;
$nav-tabs-justified-active-link-border-color: $body-bg !default;
//== Pills
$nav-pills-border-radius: 0 !default;
$nav-pills-active-link-hover-bg: #e3e4e6 !default;
$nav-pills-active-link-hover-color: $gray !default;
$nav-pills-border-radius: $border-radius-base !default;
$nav-pills-active-link-hover-bg: $component-active-bg !default;
$nav-pills-active-link-hover-color: $component-active-color !default;
//== Pagination
@ -598,7 +598,7 @@ $modal-header-border-color: #e5e5e5 !default;
//** Modal footer border color
$modal-footer-border-color: $modal-header-border-color !default;
$modal-lg: 900px !default;
$modal-lg: 960px !default;
$modal-md: 732px !default;
$modal-sm: 300px !default;

View File

@ -2,27 +2,6 @@
a {
color: $gray;
}
& > li {
& > a {
border-top: 1px solid $gray-light;
border-bottom: 1px solid $gray-light;
border-radius: 0;
font-weight: bold;
}
}
& > li + li {
margin-top: 0;
& > a {
border-top: none;
}
}
& > li > .in {
border-bottom: 1px solid $gray-light;
}
}
.nav-tabs {

View File

@ -4,7 +4,6 @@
$sidebar-active-color: $brand-primary !default;
$sidebar-box-shadow: -3px 2px 6px -2px $gray-light inset, -1px 0 0 0 $gray-light inset !default;
#sidebar {
background-color: $sidebar-background-color;
@include box-shadow($sidebar-box-shadow);
@ -20,6 +19,31 @@ $sidebar-box-shadow: -3px 2px 6px -2px $gray-light inset, -1px 0 0 0 $gray-ligh
border-bottom: $padding-base-vertical/2 solid $gray-light;
}
// Overrides specific to the navigation sidebar
.nav-pills.nav-stacked {
& > li {
& > a {
border-top: 1px solid $gray-light;
border-bottom: 1px solid $gray-light;
border-radius: 0;
color: $gray;
font-weight: bold;
}
}
& > li + li {
margin-top: 0;
& > a {
border-top: none;
}
}
& > li > .in {
border-bottom: 1px solid $gray-light;
}
}
.panel {
background-color: transparent;
}
@ -33,7 +57,6 @@ $sidebar-box-shadow: -3px 2px 6px -2px $gray-light inset, -1px 0 0 0 $gray-ligh
.openstack-panel {
& > a {
color: $gray;
padding: $padding-large-vertical $padding-large-horizontal;
}
&.active > a {
@ -56,7 +79,7 @@ $sidebar-box-shadow: -3px 2px 6px -2px $gray-light inset, -1px 0 0 0 $gray-ligh
.openstack-dashboard > a:hover,
.openstack-dashboard > a:focus,
li > a {
background-color: $nav-pills-active-link-hover-bg;
background-color: #e3e4e6;
}
li > a {

View File

@ -13,7 +13,10 @@ $mdi-font-path: $static_url + "/horizon/lib/mdi/fonts";
$icon-swap: (
asterisk: 'star',
angle-right: 'arrow-right',
angle-left: 'arrow-left',
ban: 'block-helper',
bars: 'menu',
caret-down: 'menu-down',
check: 'check',
chevron-down: 'chevron-down',
@ -75,4 +78,4 @@ $icon-swap: (
.fa-caret-down {
@include vertical-center();
}
}
}