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

366 lines
19 KiB
HTML
Raw Normal View History

2021-04-07 14:26:28 +03:00
<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>
2021-04-07 14:26:28 +03:00
<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 }}">
2021-05-06 19:21:09 +03:00
<small class="badge badge-discreet float-end" *ngIf="network.get('public').value">public</small>
2021-04-07 14:26:28 +03:00
{{ 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">
2021-05-06 19:21:09 +03:00
<button class="btn btn-outline-info text-start mb-2" (click)="showVolumes = !showVolumes">
Volumes
2021-04-07 14:26:28 +03:00
<fa-icon icon="angle-right" [fixedWidth]="true" [rotate]="showVolumes ? 90 : 0" class="float-end"></fa-icon>
</button>
<div [collapse]="!showVolumes">
2021-05-06 19:21:09 +03:00
<h5>Choose the <b>volumes</b> you wish to mount</h5>
2021-04-07 14:26:28 +03:00
<div class="select-list flex-grow-1" formArrayName="volumes" tabindex="0" *ngIf="!kvmRequired">
<table class="table mb-0">
<thead>
2021-05-06 19:21:09 +03:00
<tr>
<th>Volume name</th>
<th>Mount point</th>
<th class="text-end">Read only</th>
</tr>
2021-04-07 14:26:28 +03:00
</thead>
<tbody>
2021-05-06 19:21:09 +03:00
<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>
2021-04-07 14:26:28 +03:00
</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">
2021-05-06 19:21:09 +03:00
<button class="btn btn-outline-info text-start w-100 mb-2" (click)="showAffinity = !showAffinity">
2021-04-07 14:26:28 +03:00
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">
2021-05-06 19:21:09 +03:00
<div class="form-check ms-0">
2021-04-07 14:26:28 +03:00
<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">
2021-04-07 14:26:28 +03:00
<!--<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">
2021-05-06 19:21:09 +03:00
<div class="select-list list-group select-list p-0 mb-2 py-2" tabindex="0">
2021-04-07 14:26:28 +03:00
<div class="list-group-item list-group-item-action" *ngFor="let tag of editorForm.get('tags')['controls']; let index = index">
2021-05-06 19:21:09 +03:00
<div class="d-flex">
2021-04-07 14:26:28 +03:00
<b>{{ tag.value.key }}</b>:
2021-05-06 19:21:09 +03:00
<span class="text-truncate flex-grow-1 ps-2 tag-value"
[tooltip]="tag.value.value" container="body" placement="top left" [adaptivePosition]="false">
2021-04-07 14:26:28 +03:00
{{ tag.value.value }}
</span>
2021-05-06 19:21:09 +03:00
<button class="btn btn-sm text-danger p-0" (click)="removeTag(index)"
tooltip="Remove this tag" container="body" placement="top left" [adaptivePosition]="false">
2021-04-07 14:26:28 +03:00
<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">
2021-05-06 19:21:09 +03:00
<div class="d-flex">
2021-04-07 14:26:28 +03:00
<b>{{ meta.value.key }}</b>:
2021-05-06 19:21:09 +03:00
<span class="text-truncate flex-grow-1 ps-2 tag-value"
[tooltip]="meta.value.value" container="body" containerClass="tooltip-wrap"
placement="top left" [adaptivePosition]="false">
2021-04-07 14:26:28 +03:00
{{ meta.value.value }}
</span>
2021-05-06 19:21:09 +03:00
<button class="btn btn-sm text-danger p-0" (click)="removeMetadata(index)"
tooltip="Remove this metadata" container="body" placement="top left" [adaptivePosition]="false">
2021-04-07 14:26:28 +03:00
<fa-icon [fixedWidth]="true" icon="times"></fa-icon>
</button>
</div>
</div>
</div>
2021-05-06 19:21:09 +03:00
<app-inline-editor buttonTitle="Add metadata" (saved)="addMetadata($event)" keyPattern="^[A-Za-z0-9_-]+$"></app-inline-editor>
2021-04-07 14:26:28 +03:00
</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>
2021-04-07 14:26:28 +03:00
<span class="flex-grow-1"></span>
2021-04-07 14:26:28 +03:00
<p class="my-3 lead text-success" [innerHtml]="readyText"></p>
2021-04-07 14:26:28 +03:00
</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>