sc-portal/app/src/app/instances/instance-wizard/instance-wizard.component.html

360 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<form novalidate>
<fieldset [formGroup]="editorForm" [disabled]="working">
<button type="button" class="close" [attr.aria-label]="'general.closeWithoutSaving' | translate" (click)="close()">
<span aria-hidden="true">&times;</span>
</button>
<div class="row flex-nowrap g-0 h-100" *ngIf="!loadingIndicator">
<div class="col-3 h-100 steps d-none d-sm-block">
<ul class="list-group list-group-flush">
<li class="list-group-item">
Create a new machine
<hr />
</li>
<li class="list-group-item" [class.active]="step.id === currentStep" *ngFor="let step of steps">
{{ step.title }}
<span class="step-summary" *ngIf="step.selection">{{ step.selection.name }}</span>
<div class="step-description">{{ step.description }}</div>
</li>
<li class="flex-grow-1"></li>
</ul>
</div>
<div class="col h-100">
<div class="content d-flex flex-column">
<div class="flex-grow-1 auto-height">
<div *ngIf="currentStep === 1" class="d-flex flex-column h-100">
<div class="d-flex justify-content-center text-primary mt-1" *ngIf="!images">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div class="row mb-4" *ngIf="images">
<div class="col-sm-12 px-4">
<h5 class="mt-1">Pick the <b>type</b> of machine you wish to create and the <b>image</b> used to provision it</h5>
<select class="form-select image-type-selector" aria-label="Choose image type" formControlName="imageType">
<option [value]="1">Infrastructure container</option>
<option [value]="2">Virtual machine</option>
<!--<option [value]="3">Docker container</option>-->
<option [value]="4">Custom images</option>
</select>
</div>
</div>
<div class="btn-group" btnRadioGroup formControlName="imageOs">
<label [btnRadio]="os" class="btn" *ngFor="let os of operatingSystems">
{{ os }}
</label>
</div>
<div class="list-group list-group-flush flex-grow-1" *ngIf="images">
<div class="list-group-item list-group-item-action p-0 pe-2" *ngFor="let image of imageList">
<div class="form-check">
<input class="form-check-input" type="radio" id="image-{{ image.id }}" [value]="image" formControlName="image">
<label class="form-check-label" for="image-{{ image.id }}">
<small class="float-end d-flex align-items-center">
<span class="text-faded me-1">
<span class="d-block text-end">{{ image.published_at | timeago }}</span>
</span>
<span class="badge rounded-pill" [class.bg-success]="image.state === 'active'">&nbsp;</span>
</small>
<span class="d-block">
<span class="h3">
{{ image.name }}
<span class="price" *ngIf="image.price">{{ image.price | currency }}</span>
</span>
<small class="text-faded pb-1 d-block">v<b>{{ image.version }}</b></small>
</span>
<span class="small">
<span class="text-faded align-middle">{{ image.description }}</span>
<a [href]="image.homepage" class="small" target="_blank">
more
<fa-icon icon="external-link-alt" size="sm"></fa-icon>
</a>
</span>
</label>
</div>
</div>
</div>
</div>
<div *ngIf="currentStep === 2" class="d-flex flex-column h-100">
<h5 class="px-3 mb-3 mt-1">Choose the <b>package</b> that matches the technical specifications this machine will have</h5>
<app-packages [image]="editorForm.get('image').value" [imageType]="editorForm.get('imageType').value" [package]="preselectedPackage"
(select)="setPackage($event)">
</app-packages>
</div>
<div *ngIf="currentStep === 3" class="px-4 h-100 d-flex flex-column">
<h5>Give this machine a <b>name</b> for easier lookup</h5>
<div class="form-row">
<div class="col-sm-12">
<div class="form-floating mb-3">
<input type="text" class="form-control" id="name" formControlName="name" placeholder="Name"
[appAutofocus]="currentStep === 3">
<label for="name">Name</label>
</div>
</div>
</div>
<div class="row my-3">
<div class="col-sm-6">
<h5>Which <b>networks</b> will this machine use?</h5>
<div class="row">
<div class="col-sm">
<div class="select-list" formArrayName="networks" tabindex="0">
<div class="form-check" *ngFor="let network of editorForm.get('networks')['controls']; let index = index" [formGroupName]="index">
<input class="form-check-input" type="checkbox" [value]="network.get('id')" id="network-{{ network.get('id').value }}" formControlName="selected">
<label class="form-check-label text-truncate" for="network-{{ network.get('id').value }}">
<small class="badge text-secondary border float-end" *ngIf="network.get('public').value">public</small>
{{ network.get('name').value }}
<small class="text-faded">{{ network.get('description').value }}</small>
</label>
</div>
<div class="form-check" *ngIf="!editorForm.get('networks')['controls'].length">
<input class="form-check-input" type="checkbox" value="null" id="network" disabled="disabled">
<label class="form-check-label" for="network">There are no networks configured</label>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<h5>Enable the <b>firewall</b> for you machine</h5>
<div class="form-row">
<div class="col-sm">
<div class="select-list" tabindex="0">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="cloud-fw" formControlName="cloudFirewall">
<label class="form-check-label" for="cloud-fw">Cloud firewall</label>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Volumes and disks -->
<div class="mt-3 d-flex flex-column">
<button class="btn btn-outline-info text-start" (click)="showVolumes = !showVolumes">
Choose the <b>volumes</b> you wish to mount
<fa-icon icon="angle-right" [fixedWidth]="true" [rotate]="showVolumes ? 90 : 0" class="float-end"></fa-icon>
</button>
<div [collapse]="!showVolumes">
<div class="select-list flex-grow-1" formArrayName="volumes" tabindex="0" *ngIf="!kvmRequired">
<table class="table mb-0">
<thead>
<tr>
<th>Volume name</th>
<th>Mount point</th>
<th class="text-end">Read only</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let volume of editorForm.get('volumes')['controls']; let index = index" [formGroupName]="index">
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="vol-mount-{{ volume.get('name').value }}" formControlName="mount">
<label class="form-check-label" for="vol-mount-{{ volume.get('name').value }}">
{{ volume.get('name').value }}
</label>
</div>
</td>
<td>
<input type="text" class="form-control" formControlName="mountpoint" placeholder="/[...]" minlength="2"
[appAutofocus]="volume.get('mount').value && !volume.get('mountpoint').value" [appAutofocusDelay]="250"
tooltip="Must begin with a '/' and be at least 2 characters long" container="body" pacement="top" [adaptivePosition]="false" />
</td>
<td class="text-end ps-1">
<div class="form-check form-switch float-end">
<input class="form-check-input" type="checkbox" formControlName="ro">
</div>
</td>
</tr>
</tbody>
</table>
</div>
<p class="alert alert-info mt-3" *ngIf="kvmRequired">
<fa-icon icon="info-circle" class="align-middle"></fa-icon>
Volumes are disabled for KVM packages
</p>
</div>
</div>
<!-- Affinity settings -->
<div class="mt-3 d-flex flex-column" *ngIf="instances && instances.length">
<button class="btn btn-outline-info text-start w-100 mb-3" (click)="showAffinity = !showAffinity">
Affinity
<fa-icon icon="angle-right" [fixedWidth]="true" [rotate]="showAffinity ? 90 : 0" class="float-end"></fa-icon>
</button>
<div [collapse]="!showAffinity">
<div class="row">
<div class="col-sm-6" formGroupName="affinity">
<h5>Place close to</h5>
<div class="select-list" tabindex="0">
<div class="form-check">
<input class="form-check-input" type="radio" id="closeTo" [value]="null" formControlName="closeTo">
<label class="form-check-label" for="closeTo">
(no preference)
</label>
</div>
<div class="form-check" *ngFor="let instance of instances">
<input class="form-check-input" type="radio" id="closeTo-{{ instance.id }}" [value]="instance.id" formControlName="closeTo">
<label class="form-check-label" for="closeTo-{{ instance.id }}">
{{ instance.name }}
</label>
</div>
</div>
</div>
<div class="col-sm-6" formGroupName="affinity">
<h5>Place far from</h5>
<div class="select-list" tabindex="0">
<div class="form-check">
<input class="form-check-input" type="radio" id="farFrom" [value]="null" formControlName="farFrom">
<label class="form-check-label" for="farFrom">
(no preference)
</label>
</div>
<div class="form-check" *ngFor="let instance of instances">
<input class="form-check-input" type="radio" id="farFrom-{{ instance.id }}" [value]="instance.id" formControlName="farFrom">
<label class="form-check-label" for="farFrom-{{ instance.id }}">
{{ instance.name }}
</label>
</div>
</div>
</div>
<div class="col-sm" formGroupName="affinity">
<div class="form-check ms-4">
<input class="form-check-input" type="checkbox" id="strict" formControlName="strict">
<label class="form-check-label" for="strict">
Provision only when the affinity criteria are met
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="px-4 d-flex flex-column h-100" *ngIf="currentStep === 4">
<!--<h5>Tell us which <b>data center</b> we should place this machine in</h5>
<select class="form-select image-type-selector" aria-label="Choose data center" formControlName="dataCenter">
<option [value]="dataCenter" *ngFor="let dataCenter of dataCenters">{{ dataCenter | uppercase }}</option>
</select>-->
<div class="row mb-3 gx-3">
<div class="col-sm-6">
<h5 class="text-truncate"><b>Tags</b> make it easier to lookup an machine</h5>
<div class="row">
<div class="col-sm">
<div class="select-list list-group select-list p-0 mb-2" tabindex="0">
<div class="list-group-item list-group-item-action" *ngFor="let tag of editorForm.get('tags')['controls']; let index = index">
<div class="d-flex" [tooltip]="tag.value.value" container="body">
<b>{{ tag.value.key }}</b>:
<span class="text-truncate flex-grow-1 ps-2 tag-value">
{{ tag.value.value }}
</span>
<button class="btn btn-sm text-danger p-0" (click)="removeTag(index)">
<fa-icon [fixedWidth]="true" icon="times"></fa-icon>
</button>
</div>
</div>
</div>
<app-inline-editor buttonTitle="Add a new tag" [singleLine]="true" (saved)="addTag($event)" keyPattern="^[A-Za-z0-9_-]+$">
</app-inline-editor>
</div>
</div>
</div>
<div class="col-sm-6">
<h5 class="text-truncate"><b>Metadata</b> makes it easier to find a machine</h5>
<div class="form-row">
<div class="col-sm">
<div class="select-list list-group select-list p-0 mb-2" tabindex="0">
<div class="list-group-item list-group-item-action" *ngFor="let meta of editorForm.get('metadata')['controls']; let index = index">
<div class="d-flex" [tooltip]="meta.value.value" container="body" containerClass="tooltip-wrap">
<b>{{ meta.value.key }}</b>:
<span class="text-truncate flex-grow-1 ps-2 tag-value">
{{ meta.value.value }}
</span>
<button class="btn btn-sm text-danger p-0" (click)="removeMetadata(index)">
<fa-icon [fixedWidth]="true" icon="times"></fa-icon>
</button>
</div>
</div>
</div>
<app-inline-editor buttonTitle="Add metadata" (saved)="addMetadata($event)" keyPattern="A-Za-z0-9-_"></app-inline-editor>
</div>
</div>
</div>
</div>
<div class="my-3">
<h5>Get an <b>estimated montly cost</b> based on the machine's running time</h5>
<div class="input-group input-group-cost">
<input type="number" min="1" max="1000000" class="form-control text-center" formControlName="estimatedMinutesRan"
placeholder="Number of hours"
tooltip="Number of hours this machine is running" />
<span class="input-group-text" tooltip="Package hourly rate">
hours
<b class="px-1">× {{ editorForm.get('package').value.price | currency: 'USD': 'symbol': '1.2-4' }}</b>
<span class="d-none d-sm-inline text-lowercase">(package hourly rate)</span>
</span>
<span class="input-group-text" *ngIf="editorForm.get('image').value.price" tooltip="Image monthly price">
<b class="px-1">+ {{ editorForm.get('image').value.price | currency: 'USD': 'symbol': '1.2-4' }}</b>
<span class="d-none d-sm-inline ps-1 text-lowercase">(Image monthly price)</span>
</span>
<span class="input-group-text" tooltip="Estimated cost per month">
<b>= {{ estimatedCost | currency: 'USD': 'symbol': '1.2-4' }}</b>
<span class="d-none d-sm-inline ps-1">(per month)</span>
</span>
</div>
</div>
<span class="flex-grow-1"></span>
<p class="my-3 lead text-success" [innerHtml]="readyText"></p>
</div>
</div>
<div class="d-flex justify-content-between mt-4 px-4 mb-2">
<button class="btn btn-link text-info" [class.hidden]="currentStep === 1" (click)="previousStep()">
<fa-icon icon="angle-left"></fa-icon>
Previous step
</button>
<button class="btn btn-lg btn-info" *ngIf="currentStep < steps.length" [disabled]="!steps[currentStep - 1].complete" (click)="nextStep()">
{{ steps[currentStep].title }}
<fa-icon icon="angle-right"></fa-icon>
</button>
<button class="btn btn-lg btn-info" *ngIf="currentStep === steps.length" (click)="saveChanges()">
<fa-icon icon="spinner" [pulse]="true" size="sm" class="me-1" *ngIf="working"></fa-icon>
Create machine
</button>
</div>
</div>
</div>
</div>
</fieldset>
</form>