TRIX-26 affinity rule editor
also renamed all occurrences of "instance" to "machine" because there were many inconsistencies
This commit is contained in:
parent
4a3f2a0aeb
commit
069afd02b1
@ -11,6 +11,6 @@
|
|||||||
"\\src\\assets\\i18n",
|
"\\src\\assets\\i18n",
|
||||||
"\\src\\assets\\i18n\\networking"
|
"\\src\\assets\\i18n\\networking"
|
||||||
],
|
],
|
||||||
"SelectedNode": "\\src\\app\\instances\\instances.component.html",
|
"SelectedNode": "\\src\\app\\instances\\machines.component.html",
|
||||||
"PreviewInSolutionExplorer": false
|
"PreviewInSolutionExplorer": false
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ const appRoutes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'machines',
|
path: 'machines',
|
||||||
loadChildren: () => import('./instances/instances.module').then(x => x.InstancesModule),
|
loadChildren: () => import('./machines/machines.module').then(x => x.MachinesModule),
|
||||||
canActivate: [AuthGuardService],
|
canActivate: [AuthGuardService],
|
||||||
canLoad: [AuthGuardService],
|
canLoad: [AuthGuardService],
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,7 @@ import { SharedModule } from './shared.module';
|
|||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { DashboardComponent } from './pages/dashboard/dashboard.component';
|
import { DashboardComponent } from './pages/machines/machines.component';
|
||||||
import { UnauthorizedComponent } from './pages/unauthorized/unauthorized.component';
|
import { UnauthorizedComponent } from './pages/unauthorized/unauthorized.component';
|
||||||
import { NotFoundComponent } from './pages/not-found/not-found.component';
|
import { NotFoundComponent } from './pages/not-found/not-found.component';
|
||||||
import { NavMenuComponent } from './components/nav-menu/nav-menu.component';
|
import { NavMenuComponent } from './components/nav-menu/nav-menu.component';
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<h4 class="mb-3">Create image from machine</h4>
|
<h4 class="mb-3">Create image from machine</h4>
|
||||||
|
|
||||||
<p class="my-2">Fill in the name and version for a new image based on the "{{ instance.name }}" machine</p>
|
<p class="my-2">Fill in the name and version for a new image based on the "{{ machine.name }}" machine</p>
|
||||||
|
|
||||||
<input type="text" class="form-control mb-3" formControlName="name" placeholder="Image name" [appAutofocus]="true" [appAutofocusDelay]="600">
|
<input type="text" class="form-control mb-3" formControlName="name" placeholder="Image name" [appAutofocus]="true" [appAutofocusDelay]="600">
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import { filter, takeUntil } from 'rxjs/operators';
|
|||||||
export class CustomImageEditorComponent implements OnInit
|
export class CustomImageEditorComponent implements OnInit
|
||||||
{
|
{
|
||||||
@Input()
|
@Input()
|
||||||
instance: any;
|
machine: any;
|
||||||
|
|
||||||
save = new Subject<any>();
|
save = new Subject<any>();
|
||||||
editorForm: FormGroup;
|
editorForm: FormGroup;
|
||||||
|
@ -129,12 +129,12 @@ export class CatalogService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
createImage(instanceId: string, name: string, version: string,
|
createImage(machineId: string, name: string, version: string,
|
||||||
description?: string, homepage?: string, eula?: string, acl?: string, tags?: string): Observable<CatalogImage>
|
description?: string, homepage?: string, eula?: string, acl?: string, tags?: string): Observable<CatalogImage>
|
||||||
{
|
{
|
||||||
return this.httpClient.post<any>(`/api/my/images`,
|
return this.httpClient.post<any>(`/api/my/images`,
|
||||||
{
|
{
|
||||||
machine: instanceId,
|
machine: machineId,
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
description,
|
description,
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
<button class="btn btn-link text-info" (click)="showEditor()" [collapse]="editorVisible" [disabled]="disabled">Add affinity rule</button>
|
||||||
|
|
||||||
|
<div [collapse]="!editorVisible" [formGroup]="editorForm">
|
||||||
|
<div class="row gx-1 my-1 align-items-center">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<select class="form-select" name="strict" formControlName="strict" [appAutofocus]="editorVisible">
|
||||||
|
<option hidden disabled selected value></option>
|
||||||
|
<option value="=">{{ 'affinityRuleEditor.strict' | translate }}</option>
|
||||||
|
<option value="=~">{{ 'affinityRuleEditor.optional' | translate }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<select class="form-select" name="operator" formControlName="operator" [appAutofocus]="editorForm.get('strict').value">
|
||||||
|
<option hidden disabled selected value></option>
|
||||||
|
<option value="=">{{ 'affinityRuleEditor.closeTo' | translate }}</option>
|
||||||
|
<option value="!">{{ 'affinityRuleEditor.farFrom' | translate }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" name="target" formControlName="target" [appAutofocus]="editorForm.get('operator').value">
|
||||||
|
<option hidden disabled selected value></option>
|
||||||
|
<option value="machine">{{ 'affinityRuleEditor.namedLike' | translate }}</option>
|
||||||
|
<option value="tagName">{{ 'affinityRuleEditor.taggedWith' | translate }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="editorForm.get('target').value === 'tagName'" class="col-sm">
|
||||||
|
<input type="text" class="form-control" formControlName="tagName" placeholder="Tag name" [appAutofocus]="editorForm.get('target').value" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<input type="text" class="form-control" formControlName="value" placeholder="Value" [appAutofocus]="editorForm.get('target').value"
|
||||||
|
[tooltip]="(editorForm.get('target').value === 'machine' ? 'affinityRuleEditor.valueHint' : 'affinityRuleEditor.tagHint') | translate"
|
||||||
|
placement="top" container="body" [adaptivePosition]="false" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1 d-flex flex-nowrap justify-content-between align-items-start">
|
||||||
|
<button class="btn px-1 text-success" (click)="saveChanges()" [disabled]="editorForm.invalid">
|
||||||
|
<fa-icon [fixedWidth]="true" icon="check"></fa-icon>
|
||||||
|
</button>
|
||||||
|
<button class="btn px-1 text-danger" (click)="cancelChanges()">
|
||||||
|
<fa-icon [fixedWidth]="true" icon="times"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,7 @@
|
|||||||
|
.form-control
|
||||||
|
{
|
||||||
|
background: #0c1321;
|
||||||
|
border-color: #00e7ff;
|
||||||
|
border-radius: 3rem;
|
||||||
|
color: #ff9c07;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AffinityRuleEditorComponent } from './affinity-rule-editor.component';
|
||||||
|
|
||||||
|
describe('AffinityRuleEditorComponent', () => {
|
||||||
|
let component: AffinityRuleEditorComponent;
|
||||||
|
let fixture: ComponentFixture<AffinityRuleEditorComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ AffinityRuleEditorComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AffinityRuleEditorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,144 @@
|
|||||||
|
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, HostListener } from '@angular/core';
|
||||||
|
import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-affinity-rule-editor',
|
||||||
|
templateUrl: './affinity-rule-editor.component.html',
|
||||||
|
styleUrls: ['./affinity-rule-editor.component.scss']
|
||||||
|
})
|
||||||
|
export class AffinityRuleEditorComponent implements OnInit, OnDestroy
|
||||||
|
{
|
||||||
|
@Input()
|
||||||
|
disabled: boolean;
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
saved = new EventEmitter();
|
||||||
|
|
||||||
|
editorVisible: boolean;
|
||||||
|
editorForm: FormGroup;
|
||||||
|
|
||||||
|
private destroy$ = new Subject();
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
constructor(private readonly elementRef: ElementRef,
|
||||||
|
private readonly fb: FormBuilder) { }
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
|
private createForm()
|
||||||
|
{
|
||||||
|
this.editorForm = this.fb.group(
|
||||||
|
{
|
||||||
|
strict: [null, Validators.required],
|
||||||
|
operator: [null, Validators.required],
|
||||||
|
target: [null, Validators.required],
|
||||||
|
tagName: [null],
|
||||||
|
value: [null, Validators.required]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.editorForm.get('target').valueChanges
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe(target =>
|
||||||
|
{
|
||||||
|
if (target === 'tagName')
|
||||||
|
this.editorForm.get('tagName').setValidators(Validators.required);
|
||||||
|
else
|
||||||
|
this.editorForm.get('tagName').clearValidators();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
showEditor()
|
||||||
|
{
|
||||||
|
if (this.disabled) return;
|
||||||
|
|
||||||
|
this.editorVisible = true;
|
||||||
|
|
||||||
|
addEventListener('click', this.onDocumentClick.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
saveChanges()
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
this.editorVisible = false;
|
||||||
|
|
||||||
|
this.removeEventListeners();
|
||||||
|
|
||||||
|
let rule: string;
|
||||||
|
|
||||||
|
if (this.editorForm.get('target').value === 'machine')
|
||||||
|
rule = `machine${this.editorForm.get('operator').value}${this.editorForm.get('strict').value}${this.editorForm.get('value').value}`;
|
||||||
|
else
|
||||||
|
rule = `${this.editorForm.get('tagName').value}${this.editorForm.get('operator').value}${this.editorForm.get('strict').value}${this.editorForm.get('value').value}`;
|
||||||
|
|
||||||
|
this.saved.emit({
|
||||||
|
strict: this.editorForm.get('strict').value === '=',
|
||||||
|
closeTo: this.editorForm.get('operator').value === '=',
|
||||||
|
targetMachine: this.editorForm.get('target').value === 'machine',
|
||||||
|
tagName: this.editorForm.get('tagName').value,
|
||||||
|
value: this.editorForm.get('value').value,
|
||||||
|
rule
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
cancelChanges()
|
||||||
|
{
|
||||||
|
this.editorVisible = false;
|
||||||
|
|
||||||
|
this.removeEventListeners();
|
||||||
|
|
||||||
|
this.resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
private resetForm()
|
||||||
|
{
|
||||||
|
this.editorForm.get('strict').setValue(null);
|
||||||
|
this.editorForm.get('operator').setValue(null);
|
||||||
|
this.editorForm.get('target').setValue(null);
|
||||||
|
this.editorForm.get('tagName').setValue(null);
|
||||||
|
this.editorForm.get('value').setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
@HostListener('document:keydown.escape', ['$event'])
|
||||||
|
escapePressed(event)
|
||||||
|
{
|
||||||
|
this.cancelChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
protected onDocumentClick(event: MouseEvent)
|
||||||
|
{
|
||||||
|
if (!this.elementRef.nativeElement.contains(event.target))
|
||||||
|
this.cancelChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
private removeEventListeners()
|
||||||
|
{
|
||||||
|
removeEventListener('click', this.onDocumentClick);
|
||||||
|
removeEventListener('document:keydown.escape', this.escapePressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
ngOnInit(): void
|
||||||
|
{
|
||||||
|
this.createForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
ngOnDestroy()
|
||||||
|
{
|
||||||
|
this.removeEventListeners();
|
||||||
|
|
||||||
|
this.destroy$.next();
|
||||||
|
}
|
||||||
|
}
|
@ -102,8 +102,7 @@ export class InlineEditorComponent implements OnInit, OnDestroy
|
|||||||
else
|
else
|
||||||
this.saved.emit(this.editorForm.get('key').value);
|
this.saved.emit(this.editorForm.get('key').value);
|
||||||
|
|
||||||
this.editorForm.get('key').setValue(null);
|
this.resetForm();
|
||||||
this.editorForm.get('value').setValue(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------------
|
||||||
@ -113,6 +112,12 @@ export class InlineEditorComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
this.removeEventListeners();
|
this.removeEventListeners();
|
||||||
|
|
||||||
|
this.resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------------
|
||||||
|
private resetForm()
|
||||||
|
{
|
||||||
this.editorForm.get('key').setValue(null);
|
this.editorForm.get('key').setValue(null);
|
||||||
this.editorForm.get('value').setValue(null);
|
this.editorForm.get('value').setValue(null);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" [routerLink]="['./']" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }">
|
<a class="nav-link" [routerLink]="['./']" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }">
|
||||||
<fa-icon [fixedWidth]="true" icon="home"></fa-icon>
|
<fa-icon [fixedWidth]="true" icon="home"></fa-icon>
|
||||||
{{ 'navbar.menu.instances' | translate }}
|
{{ 'navbar.menu.machines' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AlphaOnlyDirective } from './alpha-only.directive';
|
import { AlphaOnlyDirective } from './alpha-only.directive';
|
||||||
|
|
||||||
describe('AlphaOnlyDirective', () => {
|
describe('AlphaOnlyDirective', () => {
|
||||||
it('should create an instance', () => {
|
it('should create an machine', () => {
|
||||||
const directive = new AlphaOnlyDirective();
|
const directive = new AlphaOnlyDirective();
|
||||||
expect(directive).toBeTruthy();
|
expect(directive).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AutofocusDirective } from './autofocus.directive';
|
import { AutofocusDirective } from './autofocus.directive';
|
||||||
|
|
||||||
describe('AutofocusDirective', () => {
|
describe('AutofocusDirective', () => {
|
||||||
it('should create an instance', () => {
|
it('should create an machine', () => {
|
||||||
const directive = new AutofocusDirective();
|
const directive = new AutofocusDirective();
|
||||||
expect(directive).toBeTruthy();
|
expect(directive).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { LazyLoadDirective } from './lazy-load.directive';
|
import { LazyLoadDirective } from './lazy-load.directive';
|
||||||
|
|
||||||
describe('LazyLoadDirective', () => {
|
describe('LazyLoadDirective', () => {
|
||||||
it('should create an instance', () => {
|
it('should create an machine', () => {
|
||||||
const directive = new LazyLoadDirective();
|
const directive = new LazyLoadDirective();
|
||||||
expect(directive).toBeTruthy();
|
expect(directive).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -15,11 +15,11 @@ export class HelpComponent implements OnInit
|
|||||||
contentUrl: './assets/help/account-info.html'
|
contentUrl: './assets/help/account-info.html'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Provisioning compute instance',
|
title: 'Provisioning compute machine',
|
||||||
contentUrl: ''
|
contentUrl: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Managing instances with Triton CLI',
|
title: 'Managing machines with Triton CLI',
|
||||||
contentUrl: ''
|
contentUrl: ''
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { InstanceSnapshotsComponent } from './instance-snapshots.component';
|
|
||||||
|
|
||||||
describe('InstanceSnapshotsComponent', () => {
|
|
||||||
let component: InstanceSnapshotsComponent;
|
|
||||||
let fixture: ComponentFixture<InstanceSnapshotsComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [ InstanceSnapshotsComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(InstanceSnapshotsComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,25 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { InstanceTagEditorComponent } from './instance-tag-editor.component';
|
|
||||||
|
|
||||||
describe('InstanceTagEditorComponent', () => {
|
|
||||||
let component: InstanceTagEditorComponent;
|
|
||||||
let fixture: ComponentFixture<InstanceTagEditorComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [ InstanceTagEditorComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(InstanceTagEditorComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,13 +1,13 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { InstancesService } from './instances.service';
|
import { MachinesService } from './machines.service';
|
||||||
|
|
||||||
describe('InstancesService', () => {
|
describe('MachinesService', () => {
|
||||||
let service: InstancesService;
|
let service: MachinesService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({});
|
TestBed.configureTestingModule({});
|
||||||
service = TestBed.inject(InstancesService);
|
service = TestBed.inject(MachinesService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
@ -1,45 +1,45 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { forkJoin, from, Observable, Subject } from 'rxjs';
|
import { forkJoin, from, Observable, Subject } from 'rxjs';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Instance } from '../models/instance';
|
import { Machine } from '../models/machine';
|
||||||
import { concatMap, delay, filter, map, repeatWhen, take, tap } from 'rxjs/operators';
|
import { concatMap, delay, filter, map, repeatWhen, take, tap } from 'rxjs/operators';
|
||||||
import { InstanceRequest } from '../models/instance';
|
import { MachineRequest } from '../models/machine';
|
||||||
import { Cacheable } from 'ts-cacheable';
|
import { Cacheable } from 'ts-cacheable';
|
||||||
import { volumesCacheBuster$ } from '../../volumes/helpers/volumes.service';
|
import { volumesCacheBuster$ } from '../../volumes/helpers/volumes.service';
|
||||||
|
|
||||||
const instancesCacheBuster$ = new Subject<void>();
|
const machinesCacheBuster$ = new Subject<void>();
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class InstancesService
|
export class MachinesService
|
||||||
{
|
{
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly httpClient: HttpClient) { }
|
constructor(private readonly httpClient: HttpClient) { }
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
@Cacheable({
|
@Cacheable({
|
||||||
cacheBusterObserver: instancesCacheBuster$
|
cacheBusterObserver: machinesCacheBuster$
|
||||||
})
|
})
|
||||||
get(): Observable<Instance[]>
|
get(): Observable<Machine[]>
|
||||||
{
|
{
|
||||||
return this.httpClient.get<Instance[]>(`/api/my/machines`);
|
return this.httpClient.get<Machine[]>(`/api/my/machines`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
@Cacheable({
|
@Cacheable({
|
||||||
cacheBusterObserver: instancesCacheBuster$
|
cacheBusterObserver: machinesCacheBuster$
|
||||||
})
|
})
|
||||||
getById(instanceId: string): Observable<Instance>
|
getById(machineId: string): Observable<Machine>
|
||||||
{
|
{
|
||||||
return this.httpClient.get<Instance>(`/api/my/machines/${instanceId}`);
|
return this.httpClient.get<Machine>(`/api/my/machines/${machineId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getInstanceUntilExpectedState(instance: Instance, expectedStates: string[], callbackFn?: InstanceCallbackFunction, maxRetries = 30): Observable<Instance>
|
getMachineUntilExpectedState(machine: Machine, expectedStates: string[], callbackFn?: MachineCallbackFunction, maxRetries = 30): Observable<Machine>
|
||||||
{
|
{
|
||||||
// Keep polling the instance until it reaches the expected state
|
// Keep polling the machine until it reaches the expected state
|
||||||
return this.httpClient.get<Instance>(`/api/my/machines/${instance.id}`)
|
return this.httpClient.get<Machine>(`/api/my/machines/${machine.id}`)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(x => callbackFn && callbackFn(x)),
|
tap(x => callbackFn && callbackFn(x)),
|
||||||
repeatWhen(x =>
|
repeatWhen(x =>
|
||||||
@ -51,7 +51,7 @@ export class InstancesService
|
|||||||
map(y =>
|
map(y =>
|
||||||
{
|
{
|
||||||
if (retries++ === maxRetries)
|
if (retries++ === maxRetries)
|
||||||
throw { error: `Failed to retrieve the current status for machine "${instance.name}"` };
|
throw { error: `Failed to retrieve the current status for machine "${machine.name}"` };
|
||||||
|
|
||||||
return y;
|
return y;
|
||||||
}));
|
}));
|
||||||
@ -62,14 +62,14 @@ export class InstancesService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getInstanceUntilNicRemoved(instance: any, networkName: string, callbackFn?: InstanceCallbackFunction, maxRetries = 30): Observable<Instance>
|
getMachineUntilNicRemoved(machine: any, networkName: string, callbackFn?: MachineCallbackFunction, maxRetries = 30): Observable<Machine>
|
||||||
{
|
{
|
||||||
networkName = networkName.toLocaleLowerCase();
|
networkName = networkName.toLocaleLowerCase();
|
||||||
|
|
||||||
// Keep polling the instance until it reaches the expected state
|
// Keep polling the machine until it reaches the expected state
|
||||||
return this.httpClient.get<Instance>(`/api/my/machines/${instance.id}`)
|
return this.httpClient.get<Machine>(`/api/my/machines/${machine.id}`)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(instance => callbackFn && callbackFn(instance)),
|
tap(machine => callbackFn && callbackFn(machine)),
|
||||||
repeatWhen(x =>
|
repeatWhen(x =>
|
||||||
{
|
{
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
@ -79,7 +79,7 @@ export class InstancesService
|
|||||||
map(() =>
|
map(() =>
|
||||||
{
|
{
|
||||||
if (retries++ === maxRetries)
|
if (retries++ === maxRetries)
|
||||||
throw { error: `Failed to retrieve the current status for machine "${instance.name}"` };
|
throw { error: `Failed to retrieve the current status for machine "${machine.name}"` };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -90,144 +90,144 @@ export class InstancesService
|
|||||||
if (callbackFn)
|
if (callbackFn)
|
||||||
callbackFn(x);
|
callbackFn(x);
|
||||||
|
|
||||||
return instance;
|
return machine;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
add(instance: InstanceRequest): Observable<Instance>
|
add(machine: MachineRequest): Observable<Machine>
|
||||||
{
|
{
|
||||||
return this.httpClient.post<Instance>(`/api/my/machines`, instance)
|
return this.httpClient.post<Machine>(`/api/my/machines`, machine)
|
||||||
.pipe(tap(() =>
|
.pipe(tap(() =>
|
||||||
{
|
{
|
||||||
instancesCacheBuster$.next();
|
machinesCacheBuster$.next();
|
||||||
|
|
||||||
if (instance.volumes?.length)
|
if (machine.volumes?.length)
|
||||||
volumesCacheBuster$.next();
|
volumesCacheBuster$.next();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
delete(instanceId: string): Observable<any>
|
delete(machineId: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.delete(`/api/my/machines/${instanceId}`)
|
return this.httpClient.delete(`/api/my/machines/${machineId}`)
|
||||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
.pipe(tap(() => machinesCacheBuster$.next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
start(instanceId: string): Observable<Instance>
|
start(machineId: string): Observable<Machine>
|
||||||
{
|
{
|
||||||
return this.httpClient.post<Instance>(`/api/my/machines/${instanceId}?action=start`, {})
|
return this.httpClient.post<Machine>(`/api/my/machines/${machineId}?action=start`, {})
|
||||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
.pipe(tap(() => machinesCacheBuster$.next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
stop(instanceId: string): Observable<Instance>
|
stop(machineId: string): Observable<Machine>
|
||||||
{
|
{
|
||||||
return this.httpClient.post<Instance>(`/api/my/machines/${instanceId}?action=stop`, {})
|
return this.httpClient.post<Machine>(`/api/my/machines/${machineId}?action=stop`, {})
|
||||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
.pipe(tap(() => machinesCacheBuster$.next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
reboot(instanceId: string): Observable<Instance>
|
reboot(machineId: string): Observable<Machine>
|
||||||
{
|
{
|
||||||
return this.httpClient.post<Instance>(`/api/my/machines/${instanceId}?action=reboot`, {})
|
return this.httpClient.post<Machine>(`/api/my/machines/${machineId}?action=reboot`, {})
|
||||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
.pipe(tap(() => machinesCacheBuster$.next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
resize(instanceId: string, packageId: string): Observable<any>
|
resize(machineId: string, packageId: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.post(`/api/my/machines/${instanceId}?action=resize&package=${packageId}`, {})
|
return this.httpClient.post(`/api/my/machines/${machineId}?action=resize&package=${packageId}`, {})
|
||||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
.pipe(tap(() => machinesCacheBuster$.next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
rename(instanceId: string, name: string): Observable<Instance>
|
rename(machineId: string, name: string): Observable<Machine>
|
||||||
{
|
{
|
||||||
if (!name)
|
if (!name)
|
||||||
throw 'Name cannot be empty';
|
throw 'Name cannot be empty';
|
||||||
|
|
||||||
return this.httpClient.post<Instance>(`/api/my/machines/${instanceId}?action=rename&name=${name}`, {})
|
return this.httpClient.post<Machine>(`/api/my/machines/${machineId}?action=rename&name=${name}`, {})
|
||||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
.pipe(tap(() => machinesCacheBuster$.next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
toggleFirewall(instanceId: string, enable: boolean): Observable<any>
|
toggleFirewall(machineId: string, enable: boolean): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.post(`/api/my/machines/${instanceId}?action=${enable ? 'enable' : 'disable'}_firewall`, {});
|
return this.httpClient.post(`/api/my/machines/${machineId}?action=${enable ? 'enable' : 'disable'}_firewall`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
toggleDeletionProtection(instanceId: string, enable: boolean): Observable<any>
|
toggleDeletionProtection(machineId: string, enable: boolean): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.post(`/api/my/machines/${instanceId}?action=${enable ? 'enable' : 'disable'}_deletion_protection`, {});
|
return this.httpClient.post(`/api/my/machines/${machineId}?action=${enable ? 'enable' : 'disable'}_deletion_protection`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getTags(instanceId: string): Observable<any>
|
getTags(machineId: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.get(`/api/my/machines/${instanceId}/tags`);
|
return this.httpClient.get(`/api/my/machines/${machineId}/tags`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getTag(instanceId: string, key: string): Observable<any>
|
getTag(machineId: string, key: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.get(`/api/my/machines/${instanceId}/tags/${key}`);
|
return this.httpClient.get(`/api/my/machines/${machineId}/tags/${key}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
addTags(instanceId: string, tags: any): Observable<any>
|
addTags(machineId: string, tags: any): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.post(`/api/my/machines/${instanceId}/tags`, tags);
|
return this.httpClient.post(`/api/my/machines/${machineId}/tags`, tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
replaceTags(instanceId: string, tags: any): Observable<any>
|
replaceTags(machineId: string, tags: any): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.put(`/api/my/machines/${instanceId}/tags`, tags);
|
return this.httpClient.put(`/api/my/machines/${machineId}/tags`, tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
deleteAllTags(instanceId: string): Observable<any>
|
deleteAllTags(machineId: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.delete(`/api/my/machines/${instanceId}/tags`);
|
return this.httpClient.delete(`/api/my/machines/${machineId}/tags`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
deleteTag(instanceId: string, key: string): Observable<any>
|
deleteTag(machineId: string, key: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.delete(`/api/my/machines/${instanceId}/tags/${key}`);
|
return this.httpClient.delete(`/api/my/machines/${machineId}/tags/${key}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getMetadata(instanceId: string): Observable<any>
|
getMetadata(machineId: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.get(`/api/my/machines/${instanceId}/metadata`);
|
return this.httpClient.get(`/api/my/machines/${machineId}/metadata`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getMetadataValue(instanceId: string, key: string): Observable<any>
|
getMetadataValue(machineId: string, key: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.get(`/api/my/machines/${instanceId}/metadata/${key}`);
|
return this.httpClient.get(`/api/my/machines/${machineId}/metadata/${key}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
replaceMetadata(instanceId: string, metadata: any): Observable<any>
|
replaceMetadata(machineId: string, metadata: any): Observable<any>
|
||||||
{
|
{
|
||||||
// First retrieve current metadata
|
// First retrieve current metadata
|
||||||
return this.httpClient.get(`/api/my/machines/${instanceId}/metadata`)
|
return this.httpClient.get(`/api/my/machines/${machineId}/metadata`)
|
||||||
.pipe(concatMap(existingMetadata =>
|
.pipe(concatMap(existingMetadata =>
|
||||||
{
|
{
|
||||||
// Compute which metadata the user chose to remove
|
// Compute which metadata the user chose to remove
|
||||||
const obsoleteMetadata: Observable<any>[] = [];
|
const obsoleteMetadata: Observable<any>[] = [];
|
||||||
for (const key of Object.keys(existingMetadata))
|
for (const key of Object.keys(existingMetadata))
|
||||||
if (!metadata.hasOwnProperty(key) && key !== 'root_authorized_keys') // root_authorized_keys is readonly
|
if (!metadata.hasOwnProperty(key) && key !== 'root_authorized_keys') // root_authorized_keys is readonly
|
||||||
obsoleteMetadata.push(this.httpClient.delete(`/api/my/machines/${instanceId}/metadata/${key}`));
|
obsoleteMetadata.push(this.httpClient.delete(`/api/my/machines/${machineId}/metadata/${key}`));
|
||||||
|
|
||||||
// Any metadata keys passed in here are created if they do not exist, and overwritten if they do.
|
// Any metadata keys passed in here are created if they do not exist, and overwritten if they do.
|
||||||
const metadataToUpsert = this.httpClient.post(`/api/my/machines/${instanceId}/metadata`, metadata);
|
const metadataToUpsert = this.httpClient.post(`/api/my/machines/${machineId}/metadata`, metadata);
|
||||||
|
|
||||||
if (obsoleteMetadata.length)
|
if (obsoleteMetadata.length)
|
||||||
{
|
{
|
||||||
@ -240,15 +240,15 @@ export class InstancesService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
deleteAllMetadata(instanceId: string): Observable<any>
|
deleteAllMetadata(machineId: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.delete(`/api/my/machines/${instanceId}/metadata`);
|
return this.httpClient.delete(`/api/my/machines/${machineId}/metadata`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getAudit(instanceId: string): Observable<any>
|
getAudit(machineId: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.get(`/api/my/machines/${instanceId}/audit`);
|
return this.httpClient.get(`/api/my/machines/${machineId}/audit`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
@ -268,8 +268,8 @@ export class InstancesService
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
clearCache()
|
clearCache()
|
||||||
{
|
{
|
||||||
instancesCacheBuster$.next();
|
machinesCacheBuster$.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InstanceCallbackFunction = ((instance: Instance) => void);
|
export type MachineCallbackFunction = ((machine: Machine) => void);
|
@ -27,30 +27,30 @@ export class MigrationsService
|
|||||||
@Cacheable({
|
@Cacheable({
|
||||||
cacheBusterObserver: cacheBuster$
|
cacheBusterObserver: cacheBuster$
|
||||||
})
|
})
|
||||||
getMigration(instanceId: string, migrationId: string): Observable<any>
|
getMigration(machineId: string, migrationId: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.get(`/api/my/migrations/${migrationId}`);
|
return this.httpClient.get(`/api/my/migrations/${migrationId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
migrate(instanceId: string): Observable<any>
|
migrate(machineId: string): Observable<any>
|
||||||
{
|
{
|
||||||
// https://apidocs.Spearhead.com/cloudapi/#Migrate
|
// https://apidocs.Spearhead.com/cloudapi/#Migrate
|
||||||
return this.httpClient.post(`/api/my/machines/${instanceId}/migrate`, { action: 'begin | sync | switch | automatic | pause | abort | watch', affinity: [] })
|
return this.httpClient.post(`/api/my/machines/${machineId}/migrate`, { action: 'begin | sync | switch | automatic | pause | abort | watch', affinity: [] })
|
||||||
.pipe(tap(() => cacheBuster$.next()));
|
.pipe(tap(() => cacheBuster$.next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getMigrationProgress(instanceId: string): Observable<any>
|
getMigrationProgress(machineId: string): Observable<any>
|
||||||
{
|
{
|
||||||
// https://apidocs.Spearhead.com/cloudapi/#Migrate
|
// https://apidocs.Spearhead.com/cloudapi/#Migrate
|
||||||
return this.httpClient.get(`/api/my/machines/${instanceId}/migrate?action=watch`);
|
return this.httpClient.get(`/api/my/machines/${machineId}/migrate?action=watch`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
finalizeMigration(instanceId: string): Observable<any>
|
finalizeMigration(machineId: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.post(`/api/my/machines/${instanceId}/migrate?action=finalize`, {})
|
return this.httpClient.post(`/api/my/machines/${machineId}/migrate?action=finalize`, {})
|
||||||
.pipe(tap(() => cacheBuster$.next()));
|
.pipe(tap(() => cacheBuster$.next()));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Snapshot } from '../models/snapshot';
|
import { Snapshot } from '../models/snapshot';
|
||||||
import { Instance } from '../models/instance';
|
import { Machine } from '../models/machine';
|
||||||
import { delay, filter, map, repeatWhen, take, tap } from 'rxjs/operators';
|
import { delay, filter, map, repeatWhen, take, tap } from 'rxjs/operators';
|
||||||
import { Cacheable } from 'ts-cacheable';
|
import { Cacheable } from 'ts-cacheable';
|
||||||
|
|
||||||
@ -20,25 +20,25 @@ export class SnapshotsService
|
|||||||
@Cacheable({
|
@Cacheable({
|
||||||
cacheBusterObserver: cacheBuster$
|
cacheBusterObserver: cacheBuster$
|
||||||
})
|
})
|
||||||
getSnapshots(instanceId: string): Observable<Snapshot[]>
|
getSnapshots(machineId: string): Observable<Snapshot[]>
|
||||||
{
|
{
|
||||||
return this.httpClient.get<Snapshot[]>(`/api/my/machines/${instanceId}/snapshots`);
|
return this.httpClient.get<Snapshot[]>(`/api/my/machines/${machineId}/snapshots`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
@Cacheable({
|
@Cacheable({
|
||||||
cacheBusterObserver: cacheBuster$
|
cacheBusterObserver: cacheBuster$
|
||||||
})
|
})
|
||||||
getSnapshot(instanceId: string, snapshotName: string): Observable<Snapshot>
|
getSnapshot(machineId: string, snapshotName: string): Observable<Snapshot>
|
||||||
{
|
{
|
||||||
return this.httpClient.get<Snapshot>(`/api/my/machines/${instanceId}/snapshots/${encodeURIComponent(snapshotName)}`);
|
return this.httpClient.get<Snapshot>(`/api/my/machines/${machineId}/snapshots/${encodeURIComponent(snapshotName)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getSnapshotUntilExpectedState(instance: Instance, snapshot: Snapshot, expectedStates: string[], maxRetries = 10): Observable<Snapshot>
|
getSnapshotUntilExpectedState(machine: Machine, snapshot: Snapshot, expectedStates: string[], maxRetries = 10): Observable<Snapshot>
|
||||||
{
|
{
|
||||||
// Keep polling the snapshot until it reaches the expected state
|
// Keep polling the snapshot until it reaches the expected state
|
||||||
return this.httpClient.get<Snapshot>(`/api/my/machines/${instance.id}/snapshots/${encodeURIComponent(snapshot.name)}`)
|
return this.httpClient.get<Snapshot>(`/api/my/machines/${machine.id}/snapshots/${encodeURIComponent(snapshot.name)}`)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(x =>
|
tap(x =>
|
||||||
{
|
{
|
||||||
@ -65,23 +65,23 @@ export class SnapshotsService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
createSnapshot(instanceId: string, snapshotName: string): Observable<Snapshot>
|
createSnapshot(machineId: string, snapshotName: string): Observable<Snapshot>
|
||||||
{
|
{
|
||||||
return this.httpClient.post<Snapshot>(`/api/my/machines/${instanceId}/snapshots?name=${encodeURIComponent(snapshotName)}`, {})
|
return this.httpClient.post<Snapshot>(`/api/my/machines/${machineId}/snapshots?name=${encodeURIComponent(snapshotName)}`, {})
|
||||||
.pipe(tap(() => cacheBuster$.next()));
|
.pipe(tap(() => cacheBuster$.next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
deleteSnapshot(instanceId: string, snapshotName: string): Observable<any>
|
deleteSnapshot(machineId: string, snapshotName: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.delete(`/api/my/machines/${instanceId}/snapshots/${encodeURIComponent(snapshotName)}`)
|
return this.httpClient.delete(`/api/my/machines/${machineId}/snapshots/${encodeURIComponent(snapshotName)}`)
|
||||||
.pipe(tap(() => cacheBuster$.next()));
|
.pipe(tap(() => cacheBuster$.next()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
startFromSnapshot(instanceId: string, snapshotName: string): Observable<any>
|
startFromSnapshot(machineId: string, snapshotName: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.post(`/api/my/machines/${instanceId}/snapshots/${encodeURIComponent(snapshotName)}`, {})
|
return this.httpClient.post(`/api/my/machines/${machineId}/snapshots/${encodeURIComponent(snapshotName)}`, {})
|
||||||
.pipe(tap(() => cacheBuster$.next()));
|
.pipe(tap(() => cacheBuster$.next()));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MachineHistoryComponent } from './machine-history.component';
|
||||||
|
|
||||||
|
describe('MachineHistoryComponent', () => {
|
||||||
|
let component: MachineHistoryComponent;
|
||||||
|
let fixture: ComponentFixture<MachineHistoryComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ MachineHistoryComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MachineHistoryComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -3,19 +3,19 @@ import { BsModalRef } from 'ngx-bootstrap/modal';
|
|||||||
import { NavigationStart, Router } from '@angular/router';
|
import { NavigationStart, Router } from '@angular/router';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { filter, takeUntil } from 'rxjs/operators';
|
import { filter, takeUntil } from 'rxjs/operators';
|
||||||
import { Instance } from '../models/instance';
|
import { Machine } from '../models/machine';
|
||||||
import { InstancesService } from '../helpers/instances.service';
|
import { MachinesService } from '../helpers/machines.service';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-instance-history',
|
selector: 'app-machine-history',
|
||||||
templateUrl: './instance-history.component.html',
|
templateUrl: './machine-history.component.html',
|
||||||
styleUrls: ['./instance-history.component.scss']
|
styleUrls: ['./machine-history.component.scss']
|
||||||
})
|
})
|
||||||
export class InstanceHistoryComponent implements OnInit, OnDestroy
|
export class MachineHistoryComponent implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
@Input()
|
@Input()
|
||||||
instance: Instance;
|
machine: Machine;
|
||||||
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
history: any[];
|
history: any[];
|
||||||
@ -25,7 +25,7 @@ export class InstanceHistoryComponent implements OnInit, OnDestroy
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly modalRef: BsModalRef,
|
constructor(private readonly modalRef: BsModalRef,
|
||||||
private readonly router: Router,
|
private readonly router: Router,
|
||||||
private readonly instancesService: InstancesService,
|
private readonly machinesService: MachinesService,
|
||||||
private readonly toastr: ToastrService)
|
private readonly toastr: ToastrService)
|
||||||
{
|
{
|
||||||
// When the user navigates away from this route, hide the modal
|
// When the user navigates away from this route, hide the modal
|
||||||
@ -48,7 +48,7 @@ export class InstanceHistoryComponent implements OnInit, OnDestroy
|
|||||||
{
|
{
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
this.instancesService.getAudit(this.instance.id)
|
this.machinesService.getAudit(this.machine.id)
|
||||||
.subscribe(x =>
|
.subscribe(x =>
|
||||||
{
|
{
|
||||||
this.history = x;
|
this.history = x;
|
@ -1,19 +1,19 @@
|
|||||||
<ul class="list-group list-group-flush list-info">
|
<ul class="list-group list-group-flush list-info">
|
||||||
<li class="dropdown-header">Machine identifier</li>
|
<li class="dropdown-header">Machine identifier</li>
|
||||||
<li class="list-group-item text-uppercase ps-0">
|
<li class="list-group-item text-uppercase ps-0">
|
||||||
<b>{{ instance.id }}</b>
|
<b>{{ machine.id }}</b>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<ng-container *ngIf="dnsCount">
|
<ng-container *ngIf="dnsCount">
|
||||||
<li class="dropdown-header">DNS list</li>
|
<li class="dropdown-header">DNS list</li>
|
||||||
<li class="list-group-item text-uppercase px-0 dns d-flex justify-content-between align-items-center"
|
<li class="list-group-item text-uppercase px-0 dns d-flex justify-content-between align-items-center"
|
||||||
*ngFor="let keyValue of instance.dnsList | keyvalue; let index = index">
|
*ngFor="let keyValue of machine.dnsList | keyvalue; let index = index">
|
||||||
<div class="text-truncate text-info text-faded" [tooltip]="keyValue.key" container="body" placement="top" [adaptivePosition]="false">
|
<div class="text-truncate text-info text-faded" [tooltip]="keyValue.key" container="body" placement="top" [adaptivePosition]="false">
|
||||||
<!--<span class="ms-1" [ngClass]="keyValue.value[0] === instance.id || keyValue.value[0] === instance.name.toLowerCase() ? 'highlight' : 'text-info text-faded'">
|
<!--<span class="ms-1" [ngClass]="keyValue.value[0] === machine.id || keyValue.value[0] === machine.name.toLowerCase() ? 'highlight' : 'text-info text-faded'">
|
||||||
{{ keyValue.value[0] }}
|
{{ keyValue.value[0] }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span *ngIf="keyValue.value[1]" [ngClass]="keyValue.value[1] === instance.id || keyValue.value[1] === instance.name.toLowerCase() ? 'highlight' : 'text-info text-faded'">
|
<span *ngIf="keyValue.value[1]" [ngClass]="keyValue.value[1] === machine.id || keyValue.value[1] === machine.name.toLowerCase() ? 'highlight' : 'text-info text-faded'">
|
||||||
{{ keyValue.value[1] }}
|
{{ keyValue.value[1] }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@ -33,9 +33,9 @@
|
|||||||
<li class="dropdown-header">Deletion protection</li>
|
<li class="dropdown-header">Deletion protection</li>
|
||||||
<li class="list-group-item ps-0 pb-0 mt-2 ms-2">
|
<li class="list-group-item ps-0 pb-0 mt-2 ms-2">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input mt-0" type="checkbox" id="dp{{ instance.id }}" [(ngModel)]="instance.deletion_protection"
|
<input class="form-check-input mt-0" type="checkbox" id="dp{{ machine.id }}" [(ngModel)]="machine.deletion_protection"
|
||||||
(change)="toggleDeletionProtection($event, instance)">
|
(change)="toggleDeletionProtection($event, machine)">
|
||||||
<label class="form-check-label" for="dp{{ instance.id }}">Prevent this machine from being deleted</label>
|
<label class="form-check-label" for="dp{{ machine.id }}">Prevent this machine from being deleted</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
@ -1,20 +1,20 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { InstanceInfoComponent } from './instance-info.component';
|
import { MachineInfoComponent } from './machine-info.component';
|
||||||
|
|
||||||
describe('InstanceInfoComponent', () => {
|
describe('MachineInfoComponent', () => {
|
||||||
let component: InstanceInfoComponent;
|
let component: MachineInfoComponent;
|
||||||
let fixture: ComponentFixture<InstanceInfoComponent>;
|
let fixture: ComponentFixture<MachineInfoComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ InstanceInfoComponent ]
|
declarations: [ MachineInfoComponent ]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(InstanceInfoComponent);
|
fixture = TestBed.createComponent(MachineInfoComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@ -3,19 +3,19 @@ import { ToastrService } from 'ngx-toastr';
|
|||||||
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
||||||
import { empty, forkJoin, Observable, of, Subject, ReplaySubject } from 'rxjs';
|
import { empty, forkJoin, Observable, of, Subject, ReplaySubject } from 'rxjs';
|
||||||
import { delay, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
import { delay, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||||
import { InstancesService } from '../helpers/instances.service';
|
import { MachinesService } from '../helpers/machines.service';
|
||||||
import { BsModalService } from 'ngx-bootstrap/modal';
|
import { BsModalService } from 'ngx-bootstrap/modal';
|
||||||
import { Instance } from '../models/instance';
|
import { Machine } from '../models/machine';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-instance-info',
|
selector: 'app-machine-info',
|
||||||
templateUrl: './instance-info.component.html',
|
templateUrl: './machine-info.component.html',
|
||||||
styleUrls: ['./instance-info.component.scss']
|
styleUrls: ['./machine-info.component.scss']
|
||||||
})
|
})
|
||||||
export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
export class MachineInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||||
{
|
{
|
||||||
@Input()
|
@Input()
|
||||||
instance: Instance;
|
machine: Machine;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
loadInfo: boolean;
|
loadInfo: boolean;
|
||||||
@ -40,7 +40,7 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
private onChanges$ = new ReplaySubject();
|
private onChanges$ = new ReplaySubject();
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly instancesService: InstancesService,
|
constructor(private readonly machinesService: MachinesService,
|
||||||
private readonly catalogService: CatalogService,
|
private readonly catalogService: CatalogService,
|
||||||
private readonly modalService: BsModalService,
|
private readonly modalService: BsModalService,
|
||||||
private readonly toastr: ToastrService)
|
private readonly toastr: ToastrService)
|
||||||
@ -48,19 +48,19 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
toggleDeletionProtection(event, instance: Instance)
|
toggleDeletionProtection(event, machine: Machine)
|
||||||
{
|
{
|
||||||
this.processing.emit();
|
this.processing.emit();
|
||||||
|
|
||||||
this.instancesService.toggleDeletionProtection(instance.id, event.target.checked)
|
this.machinesService.toggleDeletionProtection(machine.id, event.target.checked)
|
||||||
.subscribe(() =>
|
.subscribe(() =>
|
||||||
{
|
{
|
||||||
this.toastr.info(`The deletion protection for machine "${instance.name}" is now ${event.target.checked ? 'enabled' : 'disabled'}`);
|
this.toastr.info(`The deletion protection for machine "${machine.name}" is now ${event.target.checked ? 'enabled' : 'disabled'}`);
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
},
|
},
|
||||||
err =>
|
err =>
|
||||||
{
|
{
|
||||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -74,14 +74,14 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private getInfo()
|
private getInfo()
|
||||||
{
|
{
|
||||||
if (this.finishedLoading || this.instance.state === 'provisioning') return;
|
if (this.finishedLoading || this.machine.state === 'provisioning') return;
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
if (this.refresh)
|
if (this.refresh)
|
||||||
this.instancesService.clearCache();
|
this.machinesService.clearCache();
|
||||||
|
|
||||||
this.instancesService.getById(this.instance.id)
|
this.machinesService.getById(this.machine.id)
|
||||||
.subscribe(x =>
|
.subscribe(x =>
|
||||||
{
|
{
|
||||||
const dnsList = {};
|
const dnsList = {};
|
||||||
@ -90,7 +90,7 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
this.dnsCount = Object.keys(dnsList).length;
|
this.dnsCount = Object.keys(dnsList).length;
|
||||||
|
|
||||||
this.instance.dnsList = dnsList;
|
this.machine.dnsList = dnsList;
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.finishedLoading = true;
|
this.finishedLoading = true;
|
||||||
@ -99,7 +99,7 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
err =>
|
err =>
|
||||||
{
|
{
|
||||||
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
||||||
this.toastr.error(`Couldn't load details for machine "${this.instance.name}" ${errorDetails}`);
|
this.toastr.error(`Couldn't load details for machine "${this.machine.name}" ${errorDetails}`);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -127,7 +127,7 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
this.loadInfo = true;
|
this.loadInfo = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.finishedLoading && this.loadInfo && !this.instance?.infoLoaded || this.refresh)
|
if (!this.finishedLoading && this.loadInfo && !this.machine?.infoLoaded || this.refresh)
|
||||||
this.getInfo();
|
this.getInfo();
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -22,9 +22,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check form-switch mb-0">
|
<div class="form-check form-switch mb-0">
|
||||||
<input class="form-check-input mt-0" type="checkbox" id="fw{{ instance.id }}" [(ngModel)]="instance.firewall_enabled"
|
<input class="form-check-input mt-0" type="checkbox" id="fw{{ machine.id }}" [(ngModel)]="machine.firewall_enabled"
|
||||||
(change)="toggleCloudFirewall($event, instance)">
|
(change)="toggleCloudFirewall($event, machine)">
|
||||||
<label class="form-check-label" for="fw{{ instance.id }}">Toggle cloud firewall</label>
|
<label class="form-check-label" for="fw{{ machine.id }}">Toggle cloud firewall</label>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
<div class="btn-group btn-group-sm" dropdown placement="bottom right" container="body"
|
<div class="btn-group btn-group-sm" dropdown placement="bottom right" container="body"
|
||||||
*ngIf="!nic.state || nic.state === 'running' || nic.state === 'stopped'">
|
*ngIf="!nic.state || nic.state === 'running' || nic.state === 'stopped'">
|
||||||
<button class="btn btn-link text-info" dropdownToggle [isDisabled]="instance.working"
|
<button class="btn btn-link text-info" dropdownToggle [isDisabled]="machine.working"
|
||||||
tooltip="More options" container="body" placement="top" [adaptivePosition]="false">
|
tooltip="More options" container="body" placement="top" [adaptivePosition]="false">
|
||||||
<fa-icon icon="ellipsis-v" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="ellipsis-v" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
</button>
|
</button>
|
@ -1,20 +1,20 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { InstanceHistoryComponent } from './instance-history.component';
|
import { MachineNetworksComponent } from './machine-networks.component';
|
||||||
|
|
||||||
describe('InstanceHistoryComponent', () => {
|
describe('MachineNetworksComponent', () => {
|
||||||
let component: InstanceHistoryComponent;
|
let component: MachineNetworksComponent;
|
||||||
let fixture: ComponentFixture<InstanceHistoryComponent>;
|
let fixture: ComponentFixture<MachineNetworksComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ InstanceHistoryComponent ]
|
declarations: [ MachineNetworksComponent ]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(InstanceHistoryComponent);
|
fixture = TestBed.createComponent(MachineNetworksComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@ -3,22 +3,22 @@ import { ToastrService } from 'ngx-toastr';
|
|||||||
import { empty, forkJoin, Observable, of, Subject, ReplaySubject } from 'rxjs';
|
import { empty, forkJoin, Observable, of, Subject, ReplaySubject } from 'rxjs';
|
||||||
import { delay, filter, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
import { delay, filter, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||||
import { NetworkingService } from '../../networking/helpers/networking.service';
|
import { NetworkingService } from '../../networking/helpers/networking.service';
|
||||||
import { InstancesService } from '../helpers/instances.service';
|
import { MachinesService } from '../helpers/machines.service';
|
||||||
import { ConfirmationDialogComponent } from '../../components/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from '../../components/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { BsModalService } from 'ngx-bootstrap/modal';
|
import { BsModalService } from 'ngx-bootstrap/modal';
|
||||||
import { Nic } from '../models/nic';
|
import { Nic } from '../models/nic';
|
||||||
import { Network } from '../../networking/models/network';
|
import { Network } from '../../networking/models/network';
|
||||||
import { Instance } from '../models/instance';
|
import { Machine } from '../models/machine';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-instance-networks',
|
selector: 'app-machine-networks',
|
||||||
templateUrl: './instance-networks.component.html',
|
templateUrl: './machine-networks.component.html',
|
||||||
styleUrls: ['./instance-networks.component.scss']
|
styleUrls: ['./machine-networks.component.scss']
|
||||||
})
|
})
|
||||||
export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
export class MachineNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||||
{
|
{
|
||||||
@Input()
|
@Input()
|
||||||
instance: Instance;
|
machine: Machine;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
loadNetworks: boolean;
|
loadNetworks: boolean;
|
||||||
@ -33,10 +33,10 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
load = new EventEmitter();
|
load = new EventEmitter();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
instanceReboot = new EventEmitter();
|
machineReboot = new EventEmitter();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
instanceStateUpdate = new EventEmitter();
|
machineStateUpdate = new EventEmitter();
|
||||||
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
nics: Nic[] = [];
|
nics: Nic[] = [];
|
||||||
@ -50,7 +50,7 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly networkingService: NetworkingService,
|
constructor(private readonly networkingService: NetworkingService,
|
||||||
private readonly instancesService: InstancesService,
|
private readonly machinesService: MachinesService,
|
||||||
private readonly modalService: BsModalService,
|
private readonly modalService: BsModalService,
|
||||||
private readonly toastr: ToastrService)
|
private readonly toastr: ToastrService)
|
||||||
{
|
{
|
||||||
@ -67,14 +67,14 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
||||||
this.toastr.error(`Failed to load the list of available networks for machine "${this.instance.name}" ${errorDetails}`);
|
this.toastr.error(`Failed to load the list of available networks for machine "${this.machine.name}" ${errorDetails}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private getNetworks(force = false)
|
private getNetworks(force = false)
|
||||||
{
|
{
|
||||||
if ((this.finishedLoading || this.instance.state === 'provisioning') && !force) return;
|
if ((this.finishedLoading || this.machine.state === 'provisioning') && !force) return;
|
||||||
|
|
||||||
const observables = this.nics.map(x => this.networkingService.getNetwork(x.network));
|
const observables = this.nics.map(x => this.networkingService.getNetwork(x.network));
|
||||||
|
|
||||||
@ -124,19 +124,19 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
{
|
{
|
||||||
this.processing.emit();
|
this.processing.emit();
|
||||||
|
|
||||||
this.toastr.info(`Connecting machine "${this.instance.name}" to the "${network.name}" network...`);
|
this.toastr.info(`Connecting machine "${this.machine.name}" to the "${network.name}" network...`);
|
||||||
}),
|
}),
|
||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
{
|
{
|
||||||
return this.networkingService.addNic(this.instance.id, network.id)
|
return this.networkingService.addNic(this.machine.id, network.id)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(x =>
|
tap(x =>
|
||||||
{
|
{
|
||||||
// Add the newly created NIC to the list, in its "provisioning" state
|
// Add the newly created NIC to the list, in its "provisioning" state
|
||||||
this.nics.unshift(x);
|
this.nics.unshift(x);
|
||||||
|
|
||||||
if (this.instance.state === 'running')
|
if (this.machine.state === 'running')
|
||||||
this.instanceReboot.emit();
|
this.machineReboot.emit();
|
||||||
}),
|
}),
|
||||||
switchMap(x =>
|
switchMap(x =>
|
||||||
{
|
{
|
||||||
@ -150,7 +150,7 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
{
|
{
|
||||||
// Keep polling the newly created NIC until it reaches its "running"/"stopped" state
|
// Keep polling the newly created NIC until it reaches its "running"/"stopped" state
|
||||||
return this.networkingService
|
return this.networkingService
|
||||||
.getNicUntilAvailable(this.instance, response.nic, network.name, n => this.nics[0].state = n.state)
|
.getNicUntilAvailable(this.machine, response.nic, network.name, n => this.nics[0].state = n.state)
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(this.destroy$),
|
takeUntil(this.destroy$),
|
||||||
map(y => ({ network: response.network, nic: y }))
|
map(y => ({ network: response.network, nic: y }))
|
||||||
@ -171,7 +171,7 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
this.load.emit(this.nics);
|
this.load.emit(this.nics);
|
||||||
|
|
||||||
this.toastr.info(`The machine "${this.instance.name}" has been connected to the "${network.name}" network`);
|
this.toastr.info(`The machine "${this.machine.name}" has been connected to the "${network.name}" network`);
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
},
|
},
|
||||||
err =>
|
err =>
|
||||||
@ -181,7 +181,7 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
this.nics.shift();
|
this.nics.shift();
|
||||||
|
|
||||||
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
||||||
this.toastr.error(`Failed to connect machine "${this.instance.name}" to the "${network.name}" network ${errorDetails}`);
|
this.toastr.error(`Failed to connect machine "${this.machine.name}" to the "${network.name}" network ${errorDetails}`);
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -209,31 +209,31 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
{
|
{
|
||||||
this.processing.emit();
|
this.processing.emit();
|
||||||
|
|
||||||
this.toastr.info(`Removing network interface "${nic.mac.toUpperCase()}" from machine "${this.instance.name}"...`);
|
this.toastr.info(`Removing network interface "${nic.mac.toUpperCase()}" from machine "${this.machine.name}"...`);
|
||||||
}),
|
}),
|
||||||
//filter(() => this.instance.state === 'running' || this.instance.state === 'stopped'),
|
//filter(() => this.machine.state === 'running' || this.machine.state === 'stopped'),
|
||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
{
|
{
|
||||||
return this.networkingService.deleteNic(this.instance.id, nic.mac)
|
return this.networkingService.deleteNic(this.machine.id, nic.mac)
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(this.destroy$),
|
takeUntil(this.destroy$),
|
||||||
tap(() =>
|
tap(() =>
|
||||||
{
|
{
|
||||||
if (this.instance.state === 'running')
|
if (this.machine.state === 'running')
|
||||||
this.instanceReboot.emit();
|
this.machineReboot.emit();
|
||||||
}),
|
}),
|
||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
{
|
{
|
||||||
// If the machine is currently running, keep polling until it finishes restarting
|
// If the machine is currently running, keep polling until it finishes restarting
|
||||||
return this.instance.state === 'running'
|
return this.machine.state === 'running'
|
||||||
? this.instancesService
|
? this.machinesService
|
||||||
.getInstanceUntilNicRemoved(this.instance, nic.networkName, x => this.instanceStateUpdate.emit(x))
|
.getMachineUntilNicRemoved(this.machine, nic.networkName, x => this.machineStateUpdate.emit(x))
|
||||||
.pipe(delay(1000), takeUntil(this.destroy$))
|
.pipe(delay(1000), takeUntil(this.destroy$))
|
||||||
: of(nic);
|
: of(nic);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
switchMap(() => this.networkingService.getNics(this.instance.id))
|
switchMap(() => this.networkingService.getNics(this.machine.id))
|
||||||
).subscribe(nics =>
|
).subscribe(nics =>
|
||||||
{
|
{
|
||||||
const index = this.nics.findIndex(x => x.network === nic.network);
|
const index = this.nics.findIndex(x => x.network === nic.network);
|
||||||
@ -253,10 +253,10 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
this.load.emit(this.nics);
|
this.load.emit(this.nics);
|
||||||
|
|
||||||
this.toastr.info(`The network interface has been removed from machine "${this.instance.name}"`);
|
this.toastr.info(`The network interface has been removed from machine "${this.machine.name}"`);
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${this.machine.name}" error: ${err.error.message}`);
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -268,34 +268,34 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
toggleCloudFirewall(event, instance: Instance)
|
toggleCloudFirewall(event, machine: Machine)
|
||||||
{
|
{
|
||||||
instance.working = true;
|
machine.working = true;
|
||||||
|
|
||||||
this.instancesService.toggleFirewall(instance.id, event.target.checked)
|
this.machinesService.toggleFirewall(machine.id, event.target.checked)
|
||||||
.subscribe(() =>
|
.subscribe(() =>
|
||||||
{
|
{
|
||||||
this.toastr.info(`The cloud firewall for machine "${instance.name}" is now ${event.target.checked ? 'enabled' : 'disabled'}`);
|
this.toastr.info(`The cloud firewall for machine "${machine.name}" is now ${event.target.checked ? 'enabled' : 'disabled'}`);
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
},
|
},
|
||||||
err =>
|
err =>
|
||||||
{
|
{
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
ngOnInit(): void
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
if (!this.instance.nics?.length || this.instance.networksLoaded)
|
if (!this.machine.nics?.length || this.machine.networksLoaded)
|
||||||
this.finishedLoading = true;
|
this.finishedLoading = true;
|
||||||
|
|
||||||
this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() =>
|
this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() =>
|
||||||
{
|
{
|
||||||
if (!this.finishedLoading && this.loadNetworks && !this.instance?.networksLoaded)
|
if (!this.finishedLoading && this.loadNetworks && !this.machine?.networksLoaded)
|
||||||
{
|
{
|
||||||
this.nics = this.instance?.nics || [];
|
this.nics = this.machine?.nics || [];
|
||||||
|
|
||||||
this.getNetworks();
|
this.getNetworks();
|
||||||
}
|
}
|
@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
<li class="list-group-item ps-0 pe-0" *ngFor="let role of filteredRoles">
|
<li class="list-group-item ps-0 pe-0" *ngFor="let role of filteredRoles">
|
||||||
<div class="form-check form-switch align-items-center d-flex">
|
<div class="form-check form-switch align-items-center d-flex">
|
||||||
<input class="form-check-input pe-4" type="checkbox" id="{{ instance.id }}-role-{{ role.id }}" [(ngModel)]="role.selected"
|
<input class="form-check-input pe-4" type="checkbox" id="{{ machine.id }}-role-{{ role.id }}" [(ngModel)]="role.selected"
|
||||||
(change)="setInstanceRole($event, role)">
|
(change)="setMachineRole($event, role)">
|
||||||
<label class="form-check-label ms-2 text-truncate" for="{{ instance.id }}-role-{{ role.id }}">
|
<label class="form-check-label ms-2 text-truncate" for="{{ machine.id }}-role-{{ role.id }}">
|
||||||
<b>{{ role.name }}</b>
|
<b>{{ role.name }}</b>
|
||||||
<span class="small ps-1" *ngFor="let policy of role.policies">{{ policy.name }}</span>
|
<span class="small ps-1" *ngFor="let policy of role.policies">{{ policy.name }}</span>
|
||||||
</label>
|
</label>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MachineSecurityComponent } from './machine-security.component';
|
||||||
|
|
||||||
|
describe('MachineSecurityComponent', () => {
|
||||||
|
let component: MachineSecurityComponent;
|
||||||
|
let fixture: ComponentFixture<MachineSecurityComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ MachineSecurityComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MachineSecurityComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -1,26 +1,26 @@
|
|||||||
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
|
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
||||||
import { InstancesService } from '../helpers/instances.service';
|
import { MachinesService } from '../helpers/machines.service';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { delay, switchMap, takeUntil, tap } from 'rxjs/operators';
|
import { delay, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import { SecurityService } from '../../security/helpers/security.service';
|
import { SecurityService } from '../../security/helpers/security.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-instance-security',
|
selector: 'app-machine-security',
|
||||||
templateUrl: './instance-security.component.html',
|
templateUrl: './machine-security.component.html',
|
||||||
styleUrls: ['./instance-security.component.scss']
|
styleUrls: ['./machine-security.component.scss']
|
||||||
})
|
})
|
||||||
export class InstanceSecurityComponent implements OnInit, OnDestroy
|
export class MachineSecurityComponent implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
@Input()
|
@Input()
|
||||||
instance: any;
|
machine: any;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set loadRoles(value: boolean)
|
set loadRoles(value: boolean)
|
||||||
{
|
{
|
||||||
if (value && this.instance && !this.roles)
|
if (value && this.machine && !this.roles)
|
||||||
this.getRoles();
|
this.getRoles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ export class InstanceSecurityComponent implements OnInit, OnDestroy
|
|||||||
private readonly fuseJsOptions: {};
|
private readonly fuseJsOptions: {};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly instancesService: InstancesService,
|
constructor(private readonly machinesService: MachinesService,
|
||||||
private readonly securityService: SecurityService,
|
private readonly securityService: SecurityService,
|
||||||
private readonly toastr: ToastrService)
|
private readonly toastr: ToastrService)
|
||||||
{
|
{
|
||||||
@ -83,7 +83,7 @@ export class InstanceSecurityComponent implements OnInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
setInstanceRole(event, role)
|
setMachineRole(event, role)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ export class InstanceSecurityComponent implements OnInit, OnDestroy
|
|||||||
ngOnInit(): void
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
// TODO: Find a way to retrieve the list of RoleTags
|
// TODO: Find a way to retrieve the list of RoleTags
|
||||||
//this.instancesService.getRoleTags(this.instance.id)
|
//this.machinesService.getRoleTags(this.machine.id)
|
||||||
// .subscribe();
|
// .subscribe();
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,20 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { InstanceSecurityComponent } from './instance-security.component';
|
import { MachineSnapshotsComponent } from './machine-snapshots.component';
|
||||||
|
|
||||||
describe('InstanceSecurityComponent', () => {
|
describe('MachineSnapshotsComponent', () => {
|
||||||
let component: InstanceSecurityComponent;
|
let component: MachineSnapshotsComponent;
|
||||||
let fixture: ComponentFixture<InstanceSecurityComponent>;
|
let fixture: ComponentFixture<MachineSnapshotsComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ InstanceSecurityComponent ]
|
declarations: [ MachineSnapshotsComponent ]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(InstanceSecurityComponent);
|
fixture = TestBed.createComponent(MachineSnapshotsComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@ -1,6 +1,6 @@
|
|||||||
import { Component, OnInit, OnDestroy, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
|
import { Component, OnInit, OnDestroy, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { InstancesService } from '../helpers/instances.service';
|
import { MachinesService } from '../helpers/machines.service';
|
||||||
import { ReplaySubject, Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
import { delay, first, switchMap, takeUntil, tap } from 'rxjs/operators';
|
import { delay, first, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
@ -10,14 +10,14 @@ import { Snapshot } from '../models/snapshot';
|
|||||||
import { SnapshotsService } from '../helpers/snapshots.service';
|
import { SnapshotsService } from '../helpers/snapshots.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-instance-snapshots',
|
selector: 'app-machine-snapshots',
|
||||||
templateUrl: './instance-snapshots.component.html',
|
templateUrl: './machine-snapshots.component.html',
|
||||||
styleUrls: ['./instance-snapshots.component.scss']
|
styleUrls: ['./machine-snapshots.component.scss']
|
||||||
})
|
})
|
||||||
export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
export class MachineSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||||
{
|
{
|
||||||
@Input()
|
@Input()
|
||||||
instance: any;
|
machine: any;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
loadSnapshots: boolean;
|
loadSnapshots: boolean;
|
||||||
@ -32,7 +32,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
load = new EventEmitter();
|
load = new EventEmitter();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
instanceStateUpdate = new EventEmitter();
|
machineStateUpdate = new EventEmitter();
|
||||||
|
|
||||||
loadingSnapshots: boolean;
|
loadingSnapshots: boolean;
|
||||||
snapshotsLoaded: boolean;
|
snapshotsLoaded: boolean;
|
||||||
@ -48,7 +48,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
private readonly fuseJsOptions: {};
|
private readonly fuseJsOptions: {};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly instancesService: InstancesService,
|
constructor(private readonly machinesService: MachinesService,
|
||||||
private readonly snapshotsService: SnapshotsService,
|
private readonly snapshotsService: SnapshotsService,
|
||||||
private readonly modalService: BsModalService,
|
private readonly modalService: BsModalService,
|
||||||
private readonly toastr: ToastrService)
|
private readonly toastr: ToastrService)
|
||||||
@ -79,12 +79,12 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
// Clear this field
|
// Clear this field
|
||||||
this.snapshotName = null;
|
this.snapshotName = null;
|
||||||
|
|
||||||
this.snapshotsService.createSnapshot(this.instance.id, snapshotName)
|
this.snapshotsService.createSnapshot(this.machine.id, snapshotName)
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(this.destroy$),
|
takeUntil(this.destroy$),
|
||||||
delay(1000),
|
delay(1000),
|
||||||
tap(x => this.snapshots.unshift(x)),
|
tap(x => this.snapshots.unshift(x)),
|
||||||
switchMap((x: Snapshot) => this.snapshotsService.getSnapshotUntilExpectedState(this.instance, x, ['created'])
|
switchMap((x: Snapshot) => this.snapshotsService.getSnapshotUntilExpectedState(this.machine, x, ['created'])
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -96,7 +96,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
this.snapshots[index] = x;
|
this.snapshots[index] = x;
|
||||||
|
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
this.toastr.info(`A new snapshot "${snapshotName}" has been created for machine "${this.instance.name}"`);
|
this.toastr.info(`A new snapshot "${snapshotName}" has been created for machine "${this.machine.name}"`);
|
||||||
},
|
},
|
||||||
err =>
|
err =>
|
||||||
{
|
{
|
||||||
@ -107,7 +107,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
this.snapshots.splice(index, 1);
|
this.snapshots.splice(index, 1);
|
||||||
|
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${this.machine.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,14 +121,14 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
snapshot.working = true;
|
snapshot.working = true;
|
||||||
|
|
||||||
// First we need to make sure the instance is stopped
|
// First we need to make sure the machine is stopped
|
||||||
if (this.instance.state !== 'stopped')
|
if (this.machine.state !== 'stopped')
|
||||||
this.instancesService.stop(this.instance.id)
|
this.machinesService.stop(this.machine.id)
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(this.destroy$),
|
takeUntil(this.destroy$),
|
||||||
tap(() => this.toastr.info(`Restarting machine "${this.instance.name}"`)),
|
tap(() => this.toastr.info(`Restarting machine "${this.machine.name}"`)),
|
||||||
delay(1000),
|
delay(1000),
|
||||||
switchMap(() => this.instancesService.getInstanceUntilExpectedState(this.instance, ['stopped'], x => this.instanceStateUpdate.emit(x))
|
switchMap(() => this.machinesService.getMachineUntilExpectedState(this.machine, ['stopped'], x => this.machineStateUpdate.emit(x))
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
)
|
)
|
||||||
).subscribe(() =>
|
).subscribe(() =>
|
||||||
@ -141,7 +141,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
snapshot.working = false;
|
snapshot.working = false;
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
|
|
||||||
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${this.machine.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
this.startMachineFromSnapshot(snapshot);
|
this.startMachineFromSnapshot(snapshot);
|
||||||
@ -172,13 +172,13 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
{
|
{
|
||||||
this.processing.emit();
|
this.processing.emit();
|
||||||
|
|
||||||
this.toastr.info(`Restoring machine "${this.instance.name}" from "${snapshot.name}" snapshot`);
|
this.toastr.info(`Restoring machine "${this.machine.name}" from "${snapshot.name}" snapshot`);
|
||||||
|
|
||||||
this.snapshotsService.startFromSnapshot(this.instance.id, snapshot.name)
|
this.snapshotsService.startFromSnapshot(this.machine.id, snapshot.name)
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(this.destroy$),
|
takeUntil(this.destroy$),
|
||||||
delay(1000),
|
delay(1000),
|
||||||
switchMap(() => this.instancesService.getInstanceUntilExpectedState(this.instance, ['running'], x => this.instanceStateUpdate.emit(x), 20)
|
switchMap(() => this.machinesService.getMachineUntilExpectedState(this.machine, ['running'], x => this.machineStateUpdate.emit(x), 20)
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -188,14 +188,14 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
|
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
|
|
||||||
this.toastr.info(`The machine "${this.instance.name}" has been started from the "${snapshot.name}" snapshot`);
|
this.toastr.info(`The machine "${this.machine.name}" has been started from the "${snapshot.name}" snapshot`);
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
snapshot.working = false;
|
snapshot.working = false;
|
||||||
|
|
||||||
this.finishedProcessing.emit();
|
this.finishedProcessing.emit();
|
||||||
|
|
||||||
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${this.machine.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
{
|
{
|
||||||
this.processing.emit();
|
this.processing.emit();
|
||||||
|
|
||||||
this.snapshotsService.deleteSnapshot(this.instance.id, snapshot.name)
|
this.snapshotsService.deleteSnapshot(this.machine.id, snapshot.name)
|
||||||
.subscribe(() =>
|
.subscribe(() =>
|
||||||
{
|
{
|
||||||
const index = this.snapshots.findIndex(s => s.name === snapshot.name);
|
const index = this.snapshots.findIndex(s => s.name === snapshot.name);
|
||||||
@ -242,12 +242,12 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private getSnapshots()
|
private getSnapshots()
|
||||||
{
|
{
|
||||||
if (this.snapshotsLoaded || this.instance.state === 'provisioning') return
|
if (this.snapshotsLoaded || this.machine.state === 'provisioning') return
|
||||||
|
|
||||||
this.loadingSnapshots = true;
|
this.loadingSnapshots = true;
|
||||||
|
|
||||||
// Get the list of snapshots
|
// Get the list of snapshots
|
||||||
this.snapshotsService.getSnapshots(this.instance.id)
|
this.snapshotsService.getSnapshots(this.machine.id)
|
||||||
.subscribe(x =>
|
.subscribe(x =>
|
||||||
{
|
{
|
||||||
this.snapshots = x;
|
this.snapshots = x;
|
||||||
@ -297,12 +297,12 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
ngOnInit(): void
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
this.snapshots = this.instance?.snapshots;
|
this.snapshots = this.machine?.snapshots;
|
||||||
this.filteredSnapshots = this.snapshots;
|
this.filteredSnapshots = this.snapshots;
|
||||||
|
|
||||||
this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() =>
|
this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() =>
|
||||||
{
|
{
|
||||||
if (!this.finishedLoading && this.loadSnapshots && !this.instance?.snapshotsLoaded)
|
if (!this.finishedLoading && this.loadSnapshots && !this.machine?.snapshotsLoaded)
|
||||||
this.getSnapshots();
|
this.getSnapshots();
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -1,20 +1,20 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { InstanceNetworksComponent } from './instance-networks.component';
|
import { MachineTagEditorComponent } from './machine-tag-editor.component';
|
||||||
|
|
||||||
describe('InstanceNetworksComponent', () => {
|
describe('MachineTagEditorComponent', () => {
|
||||||
let component: InstanceNetworksComponent;
|
let component: MachineTagEditorComponent;
|
||||||
let fixture: ComponentFixture<InstanceNetworksComponent>;
|
let fixture: ComponentFixture<MachineTagEditorComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ InstanceNetworksComponent ]
|
declarations: [ MachineTagEditorComponent ]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(InstanceNetworksComponent);
|
fixture = TestBed.createComponent(MachineTagEditorComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@ -4,19 +4,19 @@ import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '
|
|||||||
import { NavigationStart, Router } from '@angular/router';
|
import { NavigationStart, Router } from '@angular/router';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { filter, takeUntil } from 'rxjs/operators';
|
import { filter, takeUntil } from 'rxjs/operators';
|
||||||
import { InstancesService } from '../helpers/instances.service';
|
import { MachinesService } from '../helpers/machines.service';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { Instance } from '../models/instance';
|
import { Machine } from '../models/machine';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-instance-tag-editor',
|
selector: 'app-machine-tag-editor',
|
||||||
templateUrl: './instance-tag-editor.component.html',
|
templateUrl: './machine-tag-editor.component.html',
|
||||||
styleUrls: ['./instance-tag-editor.component.scss']
|
styleUrls: ['./machine-tag-editor.component.scss']
|
||||||
})
|
})
|
||||||
export class InstanceTagEditorComponent implements OnInit
|
export class MachineTagEditorComponent implements OnInit
|
||||||
{
|
{
|
||||||
@Input()
|
@Input()
|
||||||
instance: Instance;
|
machine: Machine;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
showMetadata: boolean;
|
showMetadata: boolean;
|
||||||
@ -30,7 +30,7 @@ export class InstanceTagEditorComponent implements OnInit
|
|||||||
private destroy$ = new Subject();
|
private destroy$ = new Subject();
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly instancesService: InstancesService,
|
constructor(private readonly machinesService: MachinesService,
|
||||||
private readonly modalRef: BsModalRef,
|
private readonly modalRef: BsModalRef,
|
||||||
private readonly router: Router,
|
private readonly router: Router,
|
||||||
private readonly fb: FormBuilder,
|
private readonly fb: FormBuilder,
|
||||||
@ -49,13 +49,13 @@ export class InstanceTagEditorComponent implements OnInit
|
|||||||
private createForm()
|
private createForm()
|
||||||
{
|
{
|
||||||
const items = this.fb.array(this.showMetadata
|
const items = this.fb.array(this.showMetadata
|
||||||
? Object.keys(this.instance.metadata).map(key => this.fb.group({
|
? Object.keys(this.machine.metadata).map(key => this.fb.group({
|
||||||
key: [key, Validators.required],
|
key: [key, Validators.required],
|
||||||
value: [this.instance.metadata[key], Validators.required]
|
value: [this.machine.metadata[key], Validators.required]
|
||||||
}))
|
}))
|
||||||
: Object.keys(this.instance.tags).map(key => this.fb.group({
|
: Object.keys(this.machine.tags).map(key => this.fb.group({
|
||||||
key: [key, Validators.required],
|
key: [key, Validators.required],
|
||||||
value: [this.instance.tags[key], Validators.required]
|
value: [this.machine.tags[key], Validators.required]
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -113,8 +113,8 @@ export class InstanceTagEditorComponent implements OnInit
|
|||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const observable = this.showMetadata
|
const observable = this.showMetadata
|
||||||
? this.instancesService.replaceMetadata(this.instance.id, items)
|
? this.machinesService.replaceMetadata(this.machine.id, items)
|
||||||
: this.instancesService.replaceTags(this.instance.id, items);
|
: this.machinesService.replaceTags(this.machine.id, items);
|
||||||
|
|
||||||
observable.subscribe(response =>
|
observable.subscribe(response =>
|
||||||
{
|
{
|
||||||
@ -131,7 +131,7 @@ export class InstanceTagEditorComponent implements OnInit
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
ngOnInit(): void
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
//this.instancesService.getTags(this.instance.id).subscribe();
|
//this.machinesService.getTags(this.machine.id).subscribe();
|
||||||
|
|
||||||
this.createForm();
|
this.createForm();
|
||||||
}
|
}
|
@ -192,7 +192,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Affinity settings -->
|
<!-- Affinity settings -->
|
||||||
<div class="mt-3 d-flex flex-column" *ngIf="instances && instances.length">
|
<div class="mt-3 d-flex flex-column" *ngIf="machines && machines.length">
|
||||||
<button class="btn text-start w-100 mb-2" [class.btn-outline-info]="!showAffinity" [class.btn-info]="showAffinity"
|
<button class="btn text-start w-100 mb-2" [class.btn-outline-info]="!showAffinity" [class.btn-info]="showAffinity"
|
||||||
(click)="showAffinity = !showAffinity">
|
(click)="showAffinity = !showAffinity">
|
||||||
Affinity rules
|
Affinity rules
|
||||||
@ -200,33 +200,36 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div [collapse]="!showAffinity">
|
<div [collapse]="!showAffinity">
|
||||||
<div class="row">
|
<div class="select-list list-group select-list p-0 mb-2 py-2" tabindex="0">
|
||||||
<div class="col-sm-4">
|
<div class="list-group-item list-group-item-action" *ngFor="let affinityRule of editorForm.get('affinityRules')['controls']; let index = index">
|
||||||
<select class="form-select" name="operator">
|
<div class="d-flex">
|
||||||
<option></option>
|
<span class="flex-grow-1 text-truncate">
|
||||||
<option value="==">Must be close to instances</option>
|
<span class="me-1">
|
||||||
<option value="==~">Should be close to instances</option>
|
{{ (affinityRule.value.description.strict ? 'affinityRuleEditor.strict' : 'affinityRuleEditor.optional') | translate }}
|
||||||
<option value="!=">Must be far from instances</option>
|
</span>
|
||||||
<option value="!=~">Should be far from instances</option>
|
<span class="me-1 text-lowercase" [ngClass]="affinityRule.value.description.closeTo ? 'text-success' : 'text-danger'">
|
||||||
</select>
|
{{ (affinityRule.value.description.closeTo ? 'affinityRuleEditor.closeTo' : 'affinityRuleEditor.farFrom') | translate }}
|
||||||
</div>
|
</span>
|
||||||
<div class="col-sm-3">
|
<span class="me-1 text-lowercase">
|
||||||
<select class="form-select" name="target">
|
{{ (affinityRule.value.description.targetMachine ? 'affinityRuleEditor.namedLike' : 'affinityRuleEditor.taggedWith') | translate }}
|
||||||
<option></option>
|
</span>
|
||||||
<option value="instance">Named like</option>
|
<span class="me-1 text-warning" *ngIf="affinityRule.value.description.tagName">
|
||||||
<option value="tagName">Tagged with</option>
|
{{ affinityRule.value.description.tagName }}={{ affinityRule.value.description.value }}
|
||||||
</select>
|
</span>
|
||||||
</div>
|
<span class="me-1 text-warning" *ngIf="!affinityRule.value.description.tagName">
|
||||||
<div class="col-sm-4">
|
{{ affinityRule.value.description.value }}
|
||||||
<input type="text" class="form-control" placeholder="Value">
|
</span>
|
||||||
</div>
|
</span>
|
||||||
<div class="col-sm-1">
|
|
||||||
<button class="btn btn-outline-info">
|
<button class="btn btn-sm text-danger p-0" (click)="removeAffinityRule(index)"
|
||||||
<fa-icon icon="plus"></fa-icon>
|
tooltip="Remove this affinity rule" container="body" placement="top" [adaptivePosition]="false">
|
||||||
|
<fa-icon [fixedWidth]="true" icon="times"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<app-affinity-rule-editor (saved)="addAffinityRule($event)"></app-affinity-rule-editor>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,20 +1,20 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { InstanceWizardComponent } from './instance-wizard.component';
|
import { MachineWizardComponent } from './machine-wizard.component';
|
||||||
|
|
||||||
describe('InstanceWizardComponent', () => {
|
describe('MachineWizardComponent', () => {
|
||||||
let component: InstanceWizardComponent;
|
let component: MachineWizardComponent;
|
||||||
let fixture: ComponentFixture<InstanceWizardComponent>;
|
let fixture: ComponentFixture<MachineWizardComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ InstanceWizardComponent ]
|
declarations: [ MachineWizardComponent ]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(InstanceWizardComponent);
|
fixture = TestBed.createComponent(MachineWizardComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@ -2,11 +2,11 @@ import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angu
|
|||||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||||
import { combineLatest, forkJoin, Subject } from 'rxjs';
|
import { combineLatest, forkJoin, Subject } from 'rxjs';
|
||||||
import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray, ValidatorFn, ValidationErrors } from '@angular/forms';
|
import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray, ValidatorFn, ValidationErrors } from '@angular/forms';
|
||||||
import { Instance } from '../models/instance';
|
import { Machine } from '../models/machine';
|
||||||
import { filter, takeUntil, startWith, distinctUntilChanged } from 'rxjs/operators';
|
import { filter, takeUntil, startWith, distinctUntilChanged } from 'rxjs/operators';
|
||||||
import { NavigationStart, Router } from '@angular/router';
|
import { NavigationStart, Router } from '@angular/router';
|
||||||
import { FileSizePipe } from '../../pipes/file-size.pipe';
|
import { FileSizePipe } from '../../pipes/file-size.pipe';
|
||||||
import { InstancesService } from '../helpers/instances.service';
|
import { MachinesService } from '../helpers/machines.service';
|
||||||
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
||||||
import { NetworkingService } from '../../networking/helpers/networking.service';
|
import { NetworkingService } from '../../networking/helpers/networking.service';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
@ -16,11 +16,11 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { CatalogImageType } from '../../catalog/models/image';
|
import { CatalogImageType } from '../../catalog/models/image';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-instance-wizard',
|
selector: 'app-machine-wizard',
|
||||||
templateUrl: './instance-wizard.component.html',
|
templateUrl: './machine-wizard.component.html',
|
||||||
styleUrls: ['./instance-wizard.component.scss']
|
styleUrls: ['./machine-wizard.component.scss']
|
||||||
})
|
})
|
||||||
export class InstanceWizardComponent implements OnInit, OnDestroy
|
export class MachineWizardComponent implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
@Input()
|
@Input()
|
||||||
add: boolean;
|
add: boolean;
|
||||||
@ -29,7 +29,7 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
imageType = 1;
|
imageType = 1;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
instance: Instance;
|
machine: Machine;
|
||||||
|
|
||||||
private images: any[];
|
private images: any[];
|
||||||
imageList: any[];
|
imageList: any[];
|
||||||
@ -39,12 +39,12 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
packageList: any[];
|
packageList: any[];
|
||||||
packageGroups: any[];
|
packageGroups: any[];
|
||||||
|
|
||||||
instances: Instance[];
|
machines: Machine[];
|
||||||
dataCenters: any[];
|
dataCenters: any[];
|
||||||
|
|
||||||
loadingIndicator: boolean;
|
loadingIndicator: boolean;
|
||||||
loadingPackages: boolean;
|
loadingPackages: boolean;
|
||||||
save = new Subject<Instance>();
|
save = new Subject<Machine>();
|
||||||
working: boolean;
|
working: boolean;
|
||||||
editorForm: FormGroup;
|
editorForm: FormGroup;
|
||||||
currentStep = 1;
|
currentStep = 1;
|
||||||
@ -63,7 +63,7 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
private readonly fb: FormBuilder,
|
private readonly fb: FormBuilder,
|
||||||
private readonly fileSizePipe: FileSizePipe,
|
private readonly fileSizePipe: FileSizePipe,
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
private readonly instancesService: InstancesService,
|
private readonly machinesService: MachinesService,
|
||||||
private readonly catalogService: CatalogService,
|
private readonly catalogService: CatalogService,
|
||||||
private readonly networkingService: NetworkingService,
|
private readonly networkingService: NetworkingService,
|
||||||
private readonly volumesService: VolumesService,
|
private readonly volumesService: VolumesService,
|
||||||
@ -109,15 +109,15 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private createForm()
|
private createForm()
|
||||||
{
|
{
|
||||||
const tags = this.fb.array(this.instance
|
const tags = this.fb.array(this.machine
|
||||||
? Object.keys(this.instance.tags)
|
? Object.keys(this.machine.tags)
|
||||||
.map(key => this.fb.group({ key, value: this.instance.tags[key] }))
|
.map(key => this.fb.group({ key, value: this.machine.tags[key] }))
|
||||||
: []);
|
: []);
|
||||||
|
|
||||||
const metadata = this.fb.array(this.instance
|
const metadata = this.fb.array(this.machine
|
||||||
? Object.keys(this.instance.metadata)
|
? Object.keys(this.machine.metadata)
|
||||||
.filter(key => key !== 'root_authorized_keys') // This shouldn't be cloned
|
.filter(key => key !== 'root_authorized_keys') // This shouldn't be cloned
|
||||||
.map(key => this.fb.group({ key, value: this.instance.metadata[key] }))
|
.map(key => this.fb.group({ key, value: this.machine.metadata[key] }))
|
||||||
: []);
|
: []);
|
||||||
|
|
||||||
this.editorForm = this.fb.group(
|
this.editorForm = this.fb.group(
|
||||||
@ -129,14 +129,9 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
name: [null, Validators.required],
|
name: [null, Validators.required],
|
||||||
networks: this.fb.array([], { validators: this.atLeastOneSelectionValidator.bind(this) }),
|
networks: this.fb.array([], { validators: this.atLeastOneSelectionValidator.bind(this) }),
|
||||||
firewallRules: this.fb.array([]),
|
firewallRules: this.fb.array([]),
|
||||||
cloudFirewall: [this.instance?.firewall_enabled],
|
cloudFirewall: [this.machine?.firewall_enabled],
|
||||||
volumes: this.fb.array([]),
|
volumes: this.fb.array([]),
|
||||||
affinity: this.fb.group(
|
affinityRules: this.fb.array([]),
|
||||||
{
|
|
||||||
strict: [{ value: false, disabled: true }],
|
|
||||||
closeTo: [],
|
|
||||||
farFrom: []
|
|
||||||
}),
|
|
||||||
dataCenter: [],
|
dataCenter: [],
|
||||||
tags,
|
tags,
|
||||||
metadata,
|
metadata,
|
||||||
@ -281,14 +276,6 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
this.computeEstimatedCost();
|
this.computeEstimatedCost();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.editorForm.get(['affinity', 'farFrom']).valueChanges.pipe(startWith(null))
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe(this.setAffinity.bind(this));
|
|
||||||
|
|
||||||
this.editorForm.get(['affinity', 'closeTo']).valueChanges.pipe(startWith(null))
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe(this.setAffinity.bind(this));
|
|
||||||
|
|
||||||
this.editorForm.get('estimatedMinutesRan').valueChanges
|
this.editorForm.get('estimatedMinutesRan').valueChanges
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe(this.computeEstimatedCost.bind(this));
|
.subscribe(this.computeEstimatedCost.bind(this));
|
||||||
@ -325,15 +312,6 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
|
||||||
private setAffinity(affinity)
|
|
||||||
{
|
|
||||||
if (affinity)
|
|
||||||
this.editorForm.get(['affinity', 'strict']).enable();
|
|
||||||
else
|
|
||||||
this.editorForm.get(['affinity', 'strict']).disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
close()
|
close()
|
||||||
{
|
{
|
||||||
@ -347,22 +325,22 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
const changes = this.editorForm.getRawValue();
|
const changes = this.editorForm.getRawValue();
|
||||||
|
|
||||||
const instance: any = {};
|
const machine: any = {};
|
||||||
instance.name = changes.name;
|
machine.name = changes.name;
|
||||||
instance.image = changes.image.id;
|
machine.image = changes.image.id;
|
||||||
instance.package = changes.package.id;
|
machine.package = changes.package.id;
|
||||||
//instance.brand = changes.package.brand;
|
//machine.brand = changes.package.brand;
|
||||||
instance.networks = changes.networks.filter(x => x.selected).map(x => x.id);
|
machine.networks = changes.networks.filter(x => x.selected).map(x => x.id);
|
||||||
instance.firewall_enabled = !!changes.cloudFirewall;
|
machine.firewall_enabled = !!changes.cloudFirewall;
|
||||||
|
|
||||||
for (const tag of changes.tags)
|
for (const tag of changes.tags)
|
||||||
instance[`tag.${tag.key}`] = tag.value;
|
machine[`tag.${tag.key}`] = tag.value;
|
||||||
|
|
||||||
for (const metadata of changes.metadata)
|
for (const metadata of changes.metadata)
|
||||||
instance[`metadata.${metadata.key}`] = metadata.value;
|
machine[`metadata.${metadata.key}`] = metadata.value;
|
||||||
|
|
||||||
if (!this.kvmRequired)
|
if (!this.kvmRequired)
|
||||||
instance.volumes = changes.volumes
|
machine.volumes = changes.volumes
|
||||||
.filter(x => x.mount)
|
.filter(x => x.mount)
|
||||||
.map(volume =>
|
.map(volume =>
|
||||||
({
|
({
|
||||||
@ -372,9 +350,9 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
mountpoint: volume.mountpoint
|
mountpoint: volume.mountpoint
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(this.editorForm.get('affinity'));
|
machine.affinity = changes.affinityRules.map(x => x.rule);
|
||||||
|
|
||||||
this.instancesService.add(instance)
|
this.machinesService.add(machine)
|
||||||
.subscribe(x =>
|
.subscribe(x =>
|
||||||
{
|
{
|
||||||
this.working = false;
|
this.working = false;
|
||||||
@ -406,10 +384,10 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
if (this.currentStep < this.steps.length) return;
|
if (this.currentStep < this.steps.length) return;
|
||||||
|
|
||||||
this.readyText = this.translateService.instant('dashboard.wizard.ready', {
|
this.readyText = this.translateService.instant('machines.wizard.ready', {
|
||||||
imageType: this.editorForm.get('imageType').value == 1
|
imageType: this.editorForm.get('imageType').value == 1
|
||||||
? this.translateService.instant('dashboard.wizard.readyImageTypeContainer')
|
? this.translateService.instant('machines.wizard.readyImageTypeContainer')
|
||||||
: this.translateService.instant('dashboard.wizard.readyImageTypeVm'),
|
: this.translateService.instant('machines.wizard.readyImageTypeVm'),
|
||||||
packageDescription: this.editorForm.get('package').value.description ||
|
packageDescription: this.editorForm.get('package').value.description ||
|
||||||
`<b>${this.editorForm.get('package').value.vcpus || 1}</b> vCPUs, ` +
|
`<b>${this.editorForm.get('package').value.vcpus || 1}</b> vCPUs, ` +
|
||||||
`<b>${this.fileSizePipe.transform(this.editorForm.get('package').value.memory * 1024 * 1024)}</b> RAM, ` +
|
`<b>${this.fileSizePipe.transform(this.editorForm.get('package').value.memory * 1024 * 1024)}</b> RAM, ` +
|
||||||
@ -427,7 +405,7 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
this.editorForm.get('package').setValue(selection);
|
this.editorForm.get('package').setValue(selection);
|
||||||
|
|
||||||
if (this.instance)
|
if (this.machine)
|
||||||
this.nextStep();
|
this.nextStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,6 +447,25 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
array.removeAt(index);
|
array.removeAt(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
|
addAffinityRule(affinityRule)
|
||||||
|
{
|
||||||
|
const array = this.editorForm.get('affinityRules') as FormArray;
|
||||||
|
|
||||||
|
array.push(this.fb.group({
|
||||||
|
description: [affinityRule],
|
||||||
|
rule: [affinityRule.rule]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
|
removeAffinityRule(index)
|
||||||
|
{
|
||||||
|
const array = this.editorForm.get('affinityRules') as FormArray;
|
||||||
|
|
||||||
|
array.removeAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private getImages()
|
private getImages()
|
||||||
{
|
{
|
||||||
@ -482,9 +479,9 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
// Set the default image type (this will trigger a series of events)
|
// Set the default image type (this will trigger a series of events)
|
||||||
this.editorForm.get('imageType').setValue(this.imageType);
|
this.editorForm.get('imageType').setValue(this.imageType);
|
||||||
|
|
||||||
if (this.instance)
|
if (this.machine)
|
||||||
{
|
{
|
||||||
const image = this.images.find(x => x.id === this.instance.image);
|
const image = this.images.find(x => x.id === this.machine.image);
|
||||||
this.editorForm.get('image').setValue(image);
|
this.editorForm.get('image').setValue(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,18 +525,18 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private getInstancesAndDataCenters()
|
private getMachinesAndDataCenters()
|
||||||
{
|
{
|
||||||
if (this.instances || this.dataCenters)
|
if (this.machines || this.dataCenters)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
forkJoin(
|
forkJoin([
|
||||||
this.instancesService.get(),
|
this.machinesService.get(),
|
||||||
this.catalogService.getDataCenters()
|
this.catalogService.getDataCenters()
|
||||||
)
|
])
|
||||||
.subscribe(response =>
|
.subscribe(response =>
|
||||||
{
|
{
|
||||||
this.instances = response[0];
|
this.machines = response[0];
|
||||||
|
|
||||||
this.dataCenters = Object.keys(response[1]);
|
this.dataCenters = Object.keys(response[1]);
|
||||||
|
|
||||||
@ -596,18 +593,18 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
this.getNetworksAndFirewallRules();
|
this.getNetworksAndFirewallRules();
|
||||||
|
|
||||||
this.getInstancesAndDataCenters();
|
this.getMachinesAndDataCenters();
|
||||||
|
|
||||||
this.getVolumes();
|
this.getVolumes();
|
||||||
|
|
||||||
if (this.instance)
|
if (this.machine)
|
||||||
{
|
{
|
||||||
if (this.instance.type === 'smartmachine')
|
if (this.machine.type === 'smartmachine')
|
||||||
this.imageType = 1;
|
this.imageType = 1;
|
||||||
else if (this.instance.type === 'virtualmachine')
|
else if (this.machine.type === 'virtualmachine')
|
||||||
this.imageType = 2;
|
this.imageType = 2;
|
||||||
|
|
||||||
this.preselectedPackage = this.instance.package;
|
this.preselectedPackage = this.machine.package;
|
||||||
|
|
||||||
this.nextStep();
|
this.nextStep();
|
||||||
}
|
}
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<span class="d-none d-lg-block flex-grow-1"></span>
|
<span class="d-none d-lg-block flex-grow-1"></span>
|
||||||
|
|
||||||
<ng-container *ngIf="instances && instances.length">
|
<ng-container *ngIf="machines && machines.length">
|
||||||
<div class="input-group input-group-pill me-lg-3 mb-3 mb-lg-0 w-lg-auto w-100">
|
<div class="input-group input-group-pill me-lg-3 mb-3 mb-lg-0 w-lg-auto w-100">
|
||||||
<input type="text" class="form-control" placeholder="Search..." formControlName="searchTerm"
|
<input type="text" class="form-control" placeholder="Search..." formControlName="searchTerm"
|
||||||
appAlphaOnly="^[A-Za-z0-9_-]+$" tooltip="Search by name, tag, metadata, operating system or brand"
|
appAlphaOnly="^[A-Za-z0-9_-]+$" tooltip="Search by name, tag, metadata, operating system or brand"
|
||||||
@ -24,16 +24,16 @@
|
|||||||
<div class="btn-group me-lg-3 mb-3 mb-lg-0 w-lg-auto w-100">
|
<div class="btn-group me-lg-3 mb-3 mb-lg-0 w-lg-auto w-100">
|
||||||
<button class="btn btn-outline-info dropdown-toggle" [disabled]="loadingIndicator" [popover]="filtersTemplate"
|
<button class="btn btn-outline-info dropdown-toggle" [disabled]="loadingIndicator" [popover]="filtersTemplate"
|
||||||
[outsideClick]="true" container="body" placement="bottom right" containerClass="menu-popover">
|
[outsideClick]="true" container="body" placement="bottom right" containerClass="menu-popover">
|
||||||
Showing {{ listItems.length }} / {{ instances.length }}
|
Showing {{ listItems.length }} / {{ machines.length }}
|
||||||
<ng-container *ngIf="runningInstanceCount && stoppedInstanceCount">
|
<ng-container *ngIf="runningMachineCount && stoppedMachineCount">
|
||||||
<span class="badge rounded-pill bg-success text-dark">{{ runningInstanceCount }} running</span>
|
<span class="badge rounded-pill bg-success text-dark">{{ runningMachineCount }} running</span>
|
||||||
<span class="badge rounded-pill bg-danger text-dark ms-1">{{ stoppedInstanceCount }} stopped</span>
|
<span class="badge rounded-pill bg-danger text-dark ms-1">{{ stoppedMachineCount }} stopped</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="runningInstanceCount && !stoppedInstanceCount">
|
<ng-container *ngIf="runningMachineCount && !stoppedMachineCount">
|
||||||
<span class="badge rounded-pill bg-success text-dark">{{ runningInstanceCount }} running</span>
|
<span class="badge rounded-pill bg-success text-dark">{{ runningMachineCount }} running</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!runningInstanceCount && stoppedInstanceCount">
|
<ng-container *ngIf="!runningMachineCount && stoppedMachineCount">
|
||||||
<span class="badge rounded-pill bg-danger text-dark">{{ stoppedInstanceCount }} stopped</span>
|
<span class="badge rounded-pill bg-danger text-dark">{{ stoppedMachineCount }} stopped</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -85,61 +85,61 @@
|
|||||||
|
|
||||||
<div class="overflow-auto flex-grow-1 mt-3 d-flex flex-column" id="scrollingBlock">
|
<div class="overflow-auto flex-grow-1 mt-3 d-flex flex-column" id="scrollingBlock">
|
||||||
<div class="container flex-grow-1 py-2">
|
<div class="container flex-grow-1 py-2">
|
||||||
<h2 *ngIf="listItems && listItems.length === 0 && instances && instances.length > 0" class="text-uppercase">
|
<h2 *ngIf="listItems && listItems.length === 0 && machines && machines.length > 0" class="text-uppercase">
|
||||||
{{ 'dashboard.list.noResults' | translate }}
|
{{ 'machines.list.noResults' | translate }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<virtual-scroller #scroller [items]="listItems" bufferAmount="2" class="instances"
|
<virtual-scroller #scroller [items]="listItems" bufferAmount="2" class="machines"
|
||||||
[parentScroll]="scroller.window.document.getElementById('scrollingBlock')" [scrollThrottlingTime]="250">
|
[parentScroll]="scroller.window.document.getElementById('scrollingBlock')" [scrollThrottlingTime]="250">
|
||||||
<div *ngFor="let instance of scroller.viewPortItems; trackBy: trackByFunction; let index = index"
|
<div *ngFor="let machine of scroller.viewPortItems; trackBy: trackByFunction; let index = index"
|
||||||
[ngClass]="showMachineDetails ? 'col-12 full-details' : 'col-xl-2 col-lg-3 col-md-4 col-sm-6 col-12'"
|
[ngClass]="showMachineDetails ? 'col-12 full-details' : 'col-xl-2 col-lg-3 col-md-4 col-sm-6 col-12'"
|
||||||
[class.col-lg-6]="showMachineDetails && editorForm.get('fullDetailsTwoColumns').value" lazyLoad [lazyLoadDelay]="lazyLoadDelay"
|
[class.col-lg-6]="showMachineDetails && editorForm.get('fullDetailsTwoColumns').value" lazyLoad [lazyLoadDelay]="lazyLoadDelay"
|
||||||
[container]="scroller.element.nativeElement.getElementsByClassName('scrollable-content')[0]"
|
[container]="scroller.element.nativeElement.getElementsByClassName('scrollable-content')[0]"
|
||||||
(canLoad)="instance.loading = false" (unload)="instance.loading = true"
|
(canLoad)="machine.loading = false" (unload)="machine.loading = true"
|
||||||
(load)="loadInstanceDetails(instance)">
|
(load)="loadMachineDetails(machine)">
|
||||||
<fieldset class="card" [disabled]="instance.working">
|
<fieldset class="card" [disabled]="machine.working">
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<div class="card-info" [ngClass]="showMachineDetails ? 'col-lg-4' : 'col'">
|
<div class="card-info" [ngClass]="showMachineDetails ? 'col-lg-4' : 'col'">
|
||||||
<div>
|
<div>
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<h5 class="card-title text-truncate" [tooltip]="instance.name" container="body" placement="top left"
|
<h5 class="card-title text-truncate" [tooltip]="machine.name" container="body" placement="top left"
|
||||||
[adaptivePosition]="false">
|
[adaptivePosition]="false">
|
||||||
{{ instance.name }}
|
{{ machine.name }}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="btn-group btn-group-sm">
|
<div class="btn-group btn-group-sm">
|
||||||
<button class="btn btn-link text-info" tooltip="Toggle instances details" container="body"
|
<button class="btn btn-link text-info" tooltip="Toggle machines details" container="body"
|
||||||
placement="top" [adaptivePosition]="false" (click)="toggleMachineDetails()">
|
placement="top" [adaptivePosition]="false" (click)="toggleMachineDetails()">
|
||||||
<fa-icon icon="expand-alt" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="expand-alt" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!instance.loading && instance.imageDetails"
|
<div *ngIf="!machine.loading && machine.imageDetails"
|
||||||
class="text-truncate small text-info text-faded mb-1" [tooltip]="instance.imageDetails.description"
|
class="text-truncate small text-info text-faded mb-1" [tooltip]="machine.imageDetails.description"
|
||||||
container="body" placement="top left" [adaptivePosition]="false">
|
container="body" placement="top left" [adaptivePosition]="false">
|
||||||
{{ instance.imageDetails.name }}, v{{ instance.imageDetails.version }}
|
{{ machine.imageDetails.name }}, v{{ machine.imageDetails.version }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button *ngIf="!instance.loading"
|
<button *ngIf="!machine.loading"
|
||||||
class="btn btn-outline-info w-100 d-flex justify-content-around align-items-center text-truncate"
|
class="btn btn-outline-info w-100 d-flex justify-content-around align-items-center text-truncate"
|
||||||
tooltip="Change specifications" container="body" placement="top" [adaptivePosition]="false"
|
tooltip="Change specifications" container="body" placement="top" [adaptivePosition]="false"
|
||||||
(click)="resizeMachine(instance)" [disabled]="instance.brand === 'kvm'">
|
(click)="resizeMachine(machine)" [disabled]="machine.brand === 'kvm'">
|
||||||
|
|
||||||
<!--<span class="text-uppercase text-truncate">{{ instance.packageDetails.name }}</span>-->
|
<!--<span class="text-uppercase text-truncate">{{ machine.packageDetails.name }}</span>-->
|
||||||
<span class="px-1">
|
<span class="px-1">
|
||||||
<fa-icon icon="microchip"></fa-icon>
|
<fa-icon icon="microchip"></fa-icon>
|
||||||
{{ instance.memory * 1024 * 1024 | fileSize }}
|
{{ machine.memory * 1024 * 1024 | fileSize }}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<fa-icon icon="server"></fa-icon>
|
<fa-icon icon="server"></fa-icon>
|
||||||
|
|
||||||
{{ instance.disk * 1024 * 1024 | fileSize }}
|
{{ machine.disk * 1024 * 1024 | fileSize }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center" *ngIf="instance.working">
|
<div class="text-center" *ngIf="machine.working">
|
||||||
<div class="spinner-border spinner-border-sm text-info text-faded" role="status">
|
<div class="spinner-border spinner-border-sm text-info text-faded" role="status">
|
||||||
<span class="visually-hidden">Working...</span>
|
<span class="visually-hidden">Working...</span>
|
||||||
</div>
|
</div>
|
||||||
@ -147,35 +147,35 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="small text-truncate my-2">
|
<div class="small text-truncate my-2">
|
||||||
<ng-container *ngIf="instance.type === 'smartmachine'">
|
<ng-container *ngIf="machine.type === 'smartmachine'">
|
||||||
<fa-icon icon="server" size="sm" class="me-1"></fa-icon>
|
<fa-icon icon="server" size="sm" class="me-1"></fa-icon>
|
||||||
<span class="machine-brand" innerHtml="{{ 'dashboard.listItem.infrastructureContainer' | translate: { brand: instance.brand } }}"></span>
|
<span class="machine-brand" innerHtml="{{ 'machines.listItem.infrastructureContainer' | translate: { brand: machine.brand } }}"></span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="instance.type === 'virtualmachine'">
|
<ng-container *ngIf="machine.type === 'virtualmachine'">
|
||||||
<fa-icon icon="desktop" size="sm" class="me-1"></fa-icon>
|
<fa-icon icon="desktop" size="sm" class="me-1"></fa-icon>
|
||||||
<span class="machine-brand" innerHtml="{{ 'dashboard.listItem.virtualMachine' | translate: { brand: instance.brand } }}"></span>
|
<span class="machine-brand" innerHtml="{{ 'machines.listItem.virtualMachine' | translate: { brand: machine.brand } }}"></span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex flex-nowrap justify-content-between align-items-center">
|
<div class="d-flex flex-nowrap justify-content-between align-items-center">
|
||||||
<button class="badge text-uppercase" [disabled]="instance.state !== 'running' && instance.state !== 'stopped'"
|
<button class="badge text-uppercase" [disabled]="machine.state !== 'running' && machine.state !== 'stopped'"
|
||||||
[class.bg-light]="instance.state !== 'running' && instance.state !== 'stopped'"
|
[class.bg-light]="machine.state !== 'running' && machine.state !== 'stopped'"
|
||||||
[class.bg-danger]="instance.state === 'stopped'" [class.bg-success]="instance.state === 'running'"
|
[class.bg-danger]="machine.state === 'stopped'" [class.bg-success]="machine.state === 'running'"
|
||||||
(click)="showMachineHistory(instance)" tooltip="{{ 'dashboard.listItem.history' | translate }}"
|
(click)="showMachineHistory(machine)" tooltip="{{ 'machines.listItem.history' | translate }}"
|
||||||
container="body" placement="top" [adaptivePosition]="false">
|
container="body" placement="top" [adaptivePosition]="false">
|
||||||
<fa-icon icon="history" [fixedWidth]="true"></fa-icon>
|
<fa-icon icon="history" [fixedWidth]="true"></fa-icon>
|
||||||
{{ instance.state }}
|
{{ machine.state }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="btn-group btn-group-sm" dropdown placement="bottom right" *ngIf="!instance.loading">
|
<div class="btn-group btn-group-sm" dropdown placement="bottom right" *ngIf="!machine.loading">
|
||||||
<button class="btn btn-link text-success" (click)="startMachine(instance)"
|
<button class="btn btn-link text-success" (click)="startMachine(machine)"
|
||||||
*ngIf="instance.state === 'stopped'">
|
*ngIf="machine.state === 'stopped'">
|
||||||
<fa-icon icon="power-off" [fixedWidth]="true" size="sm" tooltip="Start this machine" container="body"
|
<fa-icon icon="power-off" [fixedWidth]="true" size="sm" tooltip="Start this machine" container="body"
|
||||||
placement="top" [adaptivePosition]="false"></fa-icon>
|
placement="top" [adaptivePosition]="false"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn btn-link text-info" [popover]="instanceContextMenu" container="body"
|
<button class="btn btn-link text-info" [popover]="machineContextMenu" container="body"
|
||||||
[popoverContext]="{ instance: instance }" placement="bottom left" containerClass="menu-dropdown"
|
[popoverContext]="{ machine: machine }" placement="bottom left" containerClass="menu-dropdown"
|
||||||
[outsideClick]="true">
|
[outsideClick]="true">
|
||||||
<fa-icon icon="ellipsis-v" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="ellipsis-v" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -186,51 +186,51 @@
|
|||||||
|
|
||||||
<div class="col mt-sm-0 mt-3 no-overflow-sm" *ngIf="showMachineDetails">
|
<div class="col mt-sm-0 mt-3 no-overflow-sm" *ngIf="showMachineDetails">
|
||||||
<div class="card-header p-0 h-100">
|
<div class="card-header p-0 h-100">
|
||||||
<tabset class="dashboard-tabs" *ngIf="!instance.loading">
|
<tabset class="dashboard-tabs" *ngIf="!machine.loading">
|
||||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
<tab customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||||
id="{{ instance.id }}-info">
|
id="{{ machine.id }}-info">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="info-circle" class="d-sm-none"></fa-icon>
|
<fa-icon icon="info-circle" class="d-sm-none"></fa-icon>
|
||||||
<span class="d-none d-sm-inline-block ms-1">Info</span>
|
<span class="d-none d-sm-inline-block ms-1">Info</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="card-body p-2 h-100">
|
<div class="card-body p-2 h-100">
|
||||||
<app-instance-info [instance]="instance" [loadInfo]="instance.shouldLoadInfo" [refresh]="instance.refreshInfo"
|
<app-machine-info [machine]="machine" [loadInfo]="machine.shouldLoadInfo" [refresh]="machine.refreshInfo"
|
||||||
(load)="setInstanceInfo(instance, $event)" (processing)="instance.working = true"
|
(load)="setMachineInfo(machine, $event)" (processing)="machine.working = true"
|
||||||
(finishedProcessing)="instance.working = false">
|
(finishedProcessing)="machine.working = false">
|
||||||
</app-instance-info>
|
</app-machine-info>
|
||||||
</div>
|
</div>
|
||||||
</tab>
|
</tab>
|
||||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
<tab customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||||
id="{{ instance.id }}-networks">
|
id="{{ machine.id }}-networks">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="network-wired" class="d-sm-none"></fa-icon>
|
<fa-icon icon="network-wired" class="d-sm-none"></fa-icon>
|
||||||
<span class="d-none d-sm-inline-block ms-1">Network</span>
|
<span class="d-none d-sm-inline-block ms-1">Network</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="card-body p-2 h-100">
|
<div class="card-body p-2 h-100">
|
||||||
<app-instance-networks [instance]="instance" [loadNetworks]="instance.shouldLoadNetworks"
|
<app-machine-networks [machine]="machine" [loadNetworks]="machine.shouldLoadNetworks"
|
||||||
(load)="setInstanceNetworks(instance, $event)" (processing)="instance.working = true"
|
(load)="setMachineNetworks(machine, $event)" (processing)="machine.working = true"
|
||||||
(finishedProcessing)="refreshInstanceDnsList(instance)"
|
(finishedProcessing)="refreshMachineDnsList(machine)"
|
||||||
(instanceReboot)="watchInstanceState(instance)"
|
(machineReboot)="watchMachineState(machine)"
|
||||||
(instanceStateUpdate)="updateInstance(instance, $event)">
|
(machineStateUpdate)="updateMachine(machine, $event)">
|
||||||
</app-instance-networks>
|
</app-machine-networks>
|
||||||
</div>
|
</div>
|
||||||
</tab>
|
</tab>
|
||||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
<tab customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||||
id="{{ instance.id }}-snapshots" *ngIf="instance.brand !== 'kvm'">
|
id="{{ machine.id }}-snapshots" *ngIf="machine.brand !== 'kvm'">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="history" class="d-sm-none"></fa-icon>
|
<fa-icon icon="history" class="d-sm-none"></fa-icon>
|
||||||
<span class="d-none d-sm-inline-block ms-1">Snapshots</span>
|
<span class="d-none d-sm-inline-block ms-1">Snapshots</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="card-body p-2 h-100">
|
<div class="card-body p-2 h-100">
|
||||||
<app-instance-snapshots [instance]="instance" [loadSnapshots]="instance.shouldLoadSnapshots"
|
<app-machine-snapshots [machine]="machine" [loadSnapshots]="machine.shouldLoadSnapshots"
|
||||||
(load)="setInstanceSnapshots(instance, $event)" (processing)="instance.working = true"
|
(load)="setMachineSnapshots(machine, $event)" (processing)="machine.working = true"
|
||||||
(finishedProcessing)="instance.working = false"
|
(finishedProcessing)="machine.working = false"
|
||||||
(instanceStateUpdate)="updateInstance(instance, $event)">
|
(machineStateUpdate)="updateMachine(machine, $event)">
|
||||||
</app-instance-snapshots>
|
</app-machine-snapshots>
|
||||||
</div>
|
</div>
|
||||||
</tab>
|
</tab>
|
||||||
<tab *ngIf="false" customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
<tab *ngIf="false" customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||||
id="{{ instance.id }}-migrations">
|
id="{{ machine.id }}-migrations">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="coins" class="d-sm-none"></fa-icon>
|
<fa-icon icon="coins" class="d-sm-none"></fa-icon>
|
||||||
<span class="d-none d-sm-inline-block ms-1">Migrations</span>
|
<span class="d-none d-sm-inline-block ms-1">Migrations</span>
|
||||||
@ -239,8 +239,8 @@
|
|||||||
<button class="btn btn-outline-info w-100">Move to another node</button>
|
<button class="btn btn-outline-info w-100">Move to another node</button>
|
||||||
</div>
|
</div>
|
||||||
</tab>
|
</tab>
|
||||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
<tab customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||||
id="{{ instance.id }}-volumes" *ngIf="instance.volumes && instance.volumes.length">
|
id="{{ machine.id }}-volumes" *ngIf="machine.volumes && machine.volumes.length">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="database" class="d-sm-none"></fa-icon>
|
<fa-icon icon="database" class="d-sm-none"></fa-icon>
|
||||||
<span class="d-none d-sm-inline-block ms-1">Volumes</span>
|
<span class="d-none d-sm-inline-block ms-1">Volumes</span>
|
||||||
@ -248,7 +248,7 @@
|
|||||||
<div class="card-body p-2 h-100">
|
<div class="card-body p-2 h-100">
|
||||||
<ul class="list-group list-group-flush list-info">
|
<ul class="list-group list-group-flush list-info">
|
||||||
<li class="list-group-item text-uppercase px-0 dns d-flex justify-content-between align-items-center"
|
<li class="list-group-item text-uppercase px-0 dns d-flex justify-content-between align-items-center"
|
||||||
*ngFor="let volume of instance.volumes">
|
*ngFor="let volume of machine.volumes">
|
||||||
<div class="text-truncate">
|
<div class="text-truncate">
|
||||||
<fa-icon icon="database" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="database" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
<span class="ms-1">
|
<span class="ms-1">
|
||||||
@ -275,57 +275,57 @@
|
|||||||
<ng-template #filtersTemplate [formGroup]="editorForm">
|
<ng-template #filtersTemplate [formGroup]="editorForm">
|
||||||
<fieldset class="filters">
|
<fieldset class="filters">
|
||||||
<ng-container formGroupName="filters">
|
<ng-container formGroupName="filters">
|
||||||
<div class="dropdown-header">{{ 'dashboard.list.filterByState' | translate }}</div>
|
<div class="dropdown-header">{{ 'machines.list.filterByState' | translate }}</div>
|
||||||
<div class="btn-group w-100" dropdown>
|
<div class="btn-group w-100" dropdown>
|
||||||
<button class="btn btn-state-filter dropdown-toggle d-flex justify-content-between align-items-center"
|
<button class="btn btn-state-filter dropdown-toggle d-flex justify-content-between align-items-center"
|
||||||
dropdownToggle>
|
dropdownToggle>
|
||||||
<span *ngIf="!editorForm.get(['filters', 'stateFilter']).value">
|
<span *ngIf="!editorForm.get(['filters', 'stateFilter']).value">
|
||||||
{{ 'dashboard.list.anyState' | translate }}
|
{{ 'machines.list.anyState' | translate }}
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="editorForm.get(['filters', 'stateFilter']).value === 'running'">
|
<span *ngIf="editorForm.get(['filters', 'stateFilter']).value === 'running'">
|
||||||
{{ 'dashboard.listItem.stateRunning' | translate }}
|
{{ 'machines.listItem.stateRunning' | translate }}
|
||||||
</span>
|
</span>
|
||||||
<span *ngIf="editorForm.get(['filters', 'stateFilter']).value === 'stopped'">
|
<span *ngIf="editorForm.get(['filters', 'stateFilter']).value === 'stopped'">
|
||||||
{{ 'dashboard.listItem.stateStopped' | translate }}
|
{{ 'machines.listItem.stateStopped' | translate }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<ul *dropdownMenu class="dropdown-menu dropdown-menu-state-filter" role="menu">
|
<ul *dropdownMenu class="dropdown-menu dropdown-menu-state-filter" role="menu">
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" [class.active]="!editorForm.get(['filters', 'stateFilter']).value"
|
<button class="dropdown-item" [class.active]="!editorForm.get(['filters', 'stateFilter']).value"
|
||||||
(click)="setStateFilter()">
|
(click)="setStateFilter()">
|
||||||
{{ 'dashboard.list.anyState' | translate }}
|
{{ 'machines.list.anyState' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item"
|
<button class="dropdown-item"
|
||||||
[class.active]="editorForm.get(['filters', 'stateFilter']).value === 'running'"
|
[class.active]="editorForm.get(['filters', 'stateFilter']).value === 'running'"
|
||||||
(click)="setStateFilter('running')">
|
(click)="setStateFilter('running')">
|
||||||
{{ 'dashboard.listItem.stateRunning' | translate }}
|
{{ 'machines.listItem.stateRunning' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item"
|
<button class="dropdown-item"
|
||||||
[class.active]="editorForm.get(['filters', 'stateFilter']).value === 'stopped'"
|
[class.active]="editorForm.get(['filters', 'stateFilter']).value === 'stopped'"
|
||||||
(click)="setStateFilter('stopped')">
|
(click)="setStateFilter('stopped')">
|
||||||
{{ 'dashboard.listItem.stateStopped' | translate }}
|
{{ 'machines.listItem.stateStopped' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="memoryFilterOptions.stepsArray.length > 1">
|
<ng-container *ngIf="memoryFilterOptions.stepsArray.length > 1">
|
||||||
<div class="dropdown-header">{{ 'dashboard.list.filterByMemory' | translate }}</div>
|
<div class="dropdown-header">{{ 'machines.list.filterByMemory' | translate }}</div>
|
||||||
<ngx-slider class="mb-4" formControlName="memoryFilter" [options]="memoryFilterOptions"></ngx-slider>
|
<ngx-slider class="mb-4" formControlName="memoryFilter" [options]="memoryFilterOptions"></ngx-slider>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="diskFilterOptions.stepsArray.length > 1">
|
<ng-container *ngIf="diskFilterOptions.stepsArray.length > 1">
|
||||||
<div class="dropdown-header">{{ 'dashboard.list.filterByDisk' | translate }}</div>
|
<div class="dropdown-header">{{ 'machines.list.filterByDisk' | translate }}</div>
|
||||||
<ngx-slider class="mb-3" formControlName="diskFilter" [options]="diskFilterOptions"></ngx-slider>
|
<ngx-slider class="mb-3" formControlName="diskFilter" [options]="diskFilterOptions"></ngx-slider>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<button *ngIf="memoryFilterOptions.stepsArray.length > 1 && diskFilterOptions.stepsArray.length > 1"
|
<button *ngIf="memoryFilterOptions.stepsArray.length > 1 && diskFilterOptions.stepsArray.length > 1"
|
||||||
class="btn btn-outline-dark w-100 mt-3" (click)="clearFilters()">
|
class="btn btn-outline-dark w-100 mt-3" (click)="clearFilters()">
|
||||||
{{ 'dashboard.list.resetFilters' | translate }}
|
{{ 'machines.list.resetFilters' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@ -333,7 +333,7 @@
|
|||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input mt-0" type="checkbox" id="showMachineDetails" formControlName="showMachineDetails">
|
<input class="form-check-input mt-0" type="checkbox" id="showMachineDetails" formControlName="showMachineDetails">
|
||||||
<label class="form-check-label" for="showMachineDetails">
|
<label class="form-check-label" for="showMachineDetails">
|
||||||
{{ 'dashboard.list.showDetails' | translate }}
|
{{ 'machines.list.showDetails' | translate }}
|
||||||
<fa-icon icon="spinner" [pulse]="true" size="sm" class="me-1"
|
<fa-icon icon="spinner" [pulse]="true" size="sm" class="me-1"
|
||||||
*ngIf="editorForm.get('showMachineDetails').disabled"></fa-icon>
|
*ngIf="editorForm.get('showMachineDetails').disabled"></fa-icon>
|
||||||
</label>
|
</label>
|
||||||
@ -344,66 +344,66 @@
|
|||||||
<input class="form-check-input mt-0" type="checkbox" id="fullDetailsTwoColumns"
|
<input class="form-check-input mt-0" type="checkbox" id="fullDetailsTwoColumns"
|
||||||
formControlName="fullDetailsTwoColumns">
|
formControlName="fullDetailsTwoColumns">
|
||||||
<label class="form-check-label" for="fullDetailsTwoColumns">
|
<label class="form-check-label" for="fullDetailsTwoColumns">
|
||||||
{{ 'dashboard.list.dualColumns' | translate }}
|
{{ 'machines.list.dualColumns' | translate }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #instanceContextMenu let-instance="instance">
|
<ng-template #machineContextMenu let-machine="machine">
|
||||||
<ul class="list-group list-group-flush" role="menu">
|
<ul class="list-group list-group-flush" role="menu">
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="renameMachine(instance)">
|
<button class="dropdown-item" (click)="renameMachine(machine)">
|
||||||
<fa-icon icon="pen" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="pen" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
{{ 'dashboard.listItem.rename' | translate }}
|
{{ 'machines.listItem.rename' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="showTagEditor(instance)">
|
<button class="dropdown-item" (click)="showTagEditor(machine)">
|
||||||
<fa-icon icon="tags" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="tags" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
{{ 'dashboard.listItem.editTags' | translate }}
|
{{ 'machines.listItem.editTags' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="showTagEditor(instance, true)">
|
<button class="dropdown-item" (click)="showTagEditor(machine, true)">
|
||||||
<fa-icon icon="tags" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="tags" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
{{ 'dashboard.listItem.editMetadata' | translate }}
|
{{ 'machines.listItem.editMetadata' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-divider"></li>
|
<li class="dropdown-divider"></li>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="createMachine(instance)">
|
<button class="dropdown-item" (click)="createMachine(machine)">
|
||||||
<fa-icon icon="clone" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="clone" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
{{ 'dashboard.listItem.clone' | translate }}
|
{{ 'machines.listItem.clone' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="createImageFromMachine(instance)">
|
<button class="dropdown-item" (click)="createImageFromMachine(machine)">
|
||||||
<fa-icon icon="layer-group" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="layer-group" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
{{ 'dashboard.listItem.createImage' | translate }}
|
{{ 'machines.listItem.createImage' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-divider"></li>
|
<li class="dropdown-divider"></li>
|
||||||
<ng-container *ngIf="instance.state === 'running'">
|
<ng-container *ngIf="machine.state === 'running'">
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="restartMachine(instance)">
|
<button class="dropdown-item" (click)="restartMachine(machine)">
|
||||||
<fa-icon icon="undo" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="undo" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
{{ 'dashboard.listItem.restart' | translate }}
|
{{ 'machines.listItem.restart' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="stopMachine(instance)">
|
<button class="dropdown-item" (click)="stopMachine(machine)">
|
||||||
<fa-icon icon="stop" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="stop" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
{{ 'dashboard.listItem.stop' | translate }}
|
{{ 'machines.listItem.stop' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-divider"></li>
|
<li class="dropdown-divider"></li>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="deleteMachine(instance)">
|
<button class="dropdown-item" (click)="deleteMachine(machine)">
|
||||||
<fa-icon icon="trash" [fixedWidth]="true" size="sm"></fa-icon>
|
<fa-icon icon="trash" [fixedWidth]="true" size="sm"></fa-icon>
|
||||||
{{ 'dashboard.listItem.delete' | translate }}
|
{{ 'machines.listItem.delete' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
@ -1,20 +1,20 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { InstancesComponent } from './instances.component';
|
import { MachinesComponent } from './machines.component';
|
||||||
|
|
||||||
describe('InstancesComponent', () => {
|
describe('MachinesComponent', () => {
|
||||||
let component: InstancesComponent;
|
let component: MachinesComponent;
|
||||||
let fixture: ComponentFixture<InstancesComponent>;
|
let fixture: ComponentFixture<MachinesComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ InstancesComponent ]
|
declarations: [ MachinesComponent ]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(InstancesComponent);
|
fixture = TestBed.createComponent(MachinesComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
@ -1,17 +1,17 @@
|
|||||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { InstancesService } from './helpers/instances.service';
|
import { MachinesService } from './helpers/machines.service';
|
||||||
import { BsModalService } from 'ngx-bootstrap/modal';
|
import { BsModalService } from 'ngx-bootstrap/modal';
|
||||||
import { debounceTime, delay, distinctUntilChanged, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
import { debounceTime, delay, distinctUntilChanged, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||||
import { InstanceWizardComponent } from './instance-wizard/instance-wizard.component';
|
import { MachineWizardComponent } from './machine-wizard/machine-wizard.component';
|
||||||
import { Instance } from './models/instance';
|
import { Machine } from './models/machine';
|
||||||
import { forkJoin, Subject } from 'rxjs';
|
import { forkJoin, Subject } from 'rxjs';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { CatalogService } from '../catalog/helpers/catalog.service';
|
import { CatalogService } from '../catalog/helpers/catalog.service';
|
||||||
import { PackageSelectorComponent } from './package-selector/package-selector.component';
|
import { PackageSelectorComponent } from './package-selector/package-selector.component';
|
||||||
import { PromptDialogComponent } from '../components/prompt-dialog/prompt-dialog.component';
|
import { PromptDialogComponent } from '../components/prompt-dialog/prompt-dialog.component';
|
||||||
import { InstanceTagEditorComponent } from './instance-tag-editor/instance-tag-editor.component';
|
import { MachineTagEditorComponent } from './machine-tag-editor/machine-tag-editor.component';
|
||||||
import { ConfirmationDialogComponent } from '../components/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from '../components/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { InstanceHistoryComponent } from './instance-history/instance-history.component';
|
import { MachineHistoryComponent } from './machine-history/machine-history.component';
|
||||||
import { CustomImageEditorComponent } from '../catalog/custom-image-editor/custom-image-editor.component';
|
import { CustomImageEditorComponent } from '../catalog/custom-image-editor/custom-image-editor.component';
|
||||||
import { VirtualScrollerComponent } from 'ngx-virtual-scroller';
|
import { VirtualScrollerComponent } from 'ngx-virtual-scroller';
|
||||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||||
@ -24,18 +24,18 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-instances',
|
selector: 'app-machines',
|
||||||
templateUrl: './instances.component.html',
|
templateUrl: './machines.component.html',
|
||||||
styleUrls: ['./instances.component.scss']
|
styleUrls: ['./machines.component.scss']
|
||||||
})
|
})
|
||||||
export class InstancesComponent implements OnInit, OnDestroy
|
export class MachinesComponent implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
@ViewChild(VirtualScrollerComponent)
|
@ViewChild(VirtualScrollerComponent)
|
||||||
private virtualScroller: VirtualScrollerComponent;
|
private virtualScroller: VirtualScrollerComponent;
|
||||||
|
|
||||||
loadingIndicator = true;
|
loadingIndicator = true;
|
||||||
instances: Instance[] = [];
|
machines: Machine[] = [];
|
||||||
listItems: Instance[];
|
listItems: Machine[];
|
||||||
images = [];
|
images = [];
|
||||||
packages = [];
|
packages = [];
|
||||||
volumes = [];
|
volumes = [];
|
||||||
@ -44,9 +44,9 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
editorForm: FormGroup;
|
editorForm: FormGroup;
|
||||||
showMachineDetails: boolean;
|
showMachineDetails: boolean;
|
||||||
fullDetailsTwoColumns: boolean;
|
fullDetailsTwoColumns: boolean;
|
||||||
runningInstanceCount = 0;
|
runningMachineCount = 0;
|
||||||
stoppedInstanceCount = 0;
|
stoppedMachineCount = 0;
|
||||||
instanceStateArray: string[] = [];
|
machineStateArray: string[] = [];
|
||||||
memoryFilterOptions: Options = {
|
memoryFilterOptions: Options = {
|
||||||
animate: false,
|
animate: false,
|
||||||
stepsArray: [],
|
stepsArray: [],
|
||||||
@ -68,7 +68,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
private readonly fuseJsOptions: {};
|
private readonly fuseJsOptions: {};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly instancesService: InstancesService,
|
constructor(private readonly machinesService: MachinesService,
|
||||||
private readonly catalogService: CatalogService,
|
private readonly catalogService: CatalogService,
|
||||||
private readonly volumesService: VolumesService,
|
private readonly volumesService: VolumesService,
|
||||||
private readonly modalService: BsModalService,
|
private readonly modalService: BsModalService,
|
||||||
@ -78,7 +78,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
private readonly titleService: Title,
|
private readonly titleService: Title,
|
||||||
private readonly translationService: TranslateService)
|
private readonly translationService: TranslateService)
|
||||||
{
|
{
|
||||||
translationService.get('dashboard.title').pipe(first()).subscribe(x => titleService.setTitle(`Spearhead - ${x}`));
|
translationService.get('machines.title').pipe(first()).subscribe(x => titleService.setTitle(`Spearhead - ${x}`));
|
||||||
|
|
||||||
this.lazyLoadDelay = this.minimumLazyLoadDelay;
|
this.lazyLoadDelay = this.minimumLazyLoadDelay;
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
{
|
{
|
||||||
const formattedValue = this.fileSizePipe.transform(value * 1024 * 1024);
|
const formattedValue = this.fileSizePipe.transform(value * 1024 * 1024);
|
||||||
|
|
||||||
if (this.instances.length === 1)
|
if (this.machines.length === 1)
|
||||||
return formattedValue;
|
return formattedValue;
|
||||||
|
|
||||||
switch (label)
|
switch (label)
|
||||||
@ -124,28 +124,28 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private getInstances()
|
private getMachines()
|
||||||
{
|
{
|
||||||
this.instancesService.get()
|
this.machinesService.get()
|
||||||
.subscribe(instances =>
|
.subscribe(machines =>
|
||||||
{
|
{
|
||||||
//// DEMO ONLY !!!!!
|
//// DEMO ONLY !!!!!
|
||||||
//const arr = new Array(200);
|
//const arr = new Array(200);
|
||||||
//for (let j = 0; j < 200; j++)
|
//for (let j = 0; j < 200; j++)
|
||||||
//{
|
//{
|
||||||
// const el = { ...instances[0] };
|
// const el = { ...machines[0] };
|
||||||
// el.name = this.dummyNames[j];
|
// el.name = this.dummyNames[j];
|
||||||
// arr[j] = el;
|
// arr[j] = el;
|
||||||
//}/**/
|
//}/**/
|
||||||
//// DEMO ONLY !!!!!
|
//// DEMO ONLY !!!!!
|
||||||
|
|
||||||
this.instances = instances.map(instance =>
|
this.machines = machines.map(machine =>
|
||||||
{
|
{
|
||||||
instance.metadataKeys = Object.keys(instance.metadata);
|
machine.metadataKeys = Object.keys(machine.metadata);
|
||||||
instance.tagKeys = Object.keys(instance.tags);
|
machine.tagKeys = Object.keys(machine.tags);
|
||||||
|
|
||||||
instance.loading = true; // Required for improved scrolling experience
|
machine.loading = true; // Required for improved scrolling experience
|
||||||
return instance;
|
return machine;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getImagesPackagesAndVolumes();
|
this.getImagesPackagesAndVolumes();
|
||||||
@ -172,8 +172,8 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
this.packages = response.packages;
|
this.packages = response.packages;
|
||||||
this.volumes = response.volumes;
|
this.volumes = response.volumes;
|
||||||
|
|
||||||
for (const instance of this.instances)
|
for (const machine of this.machines)
|
||||||
this.fillInInstanceDetails(instance);
|
this.fillInMachineDetails(machine);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
typeFilter: [],
|
typeFilter: [],
|
||||||
memoryFilter: [[0, 0]],
|
memoryFilter: [[0, 0]],
|
||||||
diskFilter: [[0, 0]],
|
diskFilter: [[0, 0]],
|
||||||
imageFilter: [], // instances provisioned with a certain image
|
imageFilter: [], // machines provisioned with a certain image
|
||||||
}),
|
}),
|
||||||
filtersActive: [false],
|
filtersActive: [false],
|
||||||
showMachineDetails: [this.showMachineDetails],
|
showMachineDetails: [this.showMachineDetails],
|
||||||
@ -255,18 +255,18 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private applyFiltersAndSort()
|
private applyFiltersAndSort()
|
||||||
{
|
{
|
||||||
let listItems: Instance[] = null;
|
let listItems: Machine[] = null;
|
||||||
|
|
||||||
const searchTerm = this.editorForm.get('searchTerm').value;
|
const searchTerm = this.editorForm.get('searchTerm').value;
|
||||||
if (searchTerm.length >= 2)
|
if (searchTerm.length >= 2)
|
||||||
{
|
{
|
||||||
const fuse = new Fuse(this.instances, this.fuseJsOptions);
|
const fuse = new Fuse(this.machines, this.fuseJsOptions);
|
||||||
const fuseResults = fuse.search(searchTerm);
|
const fuseResults = fuse.search(searchTerm);
|
||||||
listItems = fuseResults.map(x => x.item);
|
listItems = fuseResults.map(x => x.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!listItems)
|
if (!listItems)
|
||||||
listItems = [...this.instances];
|
listItems = [...this.machines];
|
||||||
|
|
||||||
const stateFilter = this.editorForm.get(['filters', 'stateFilter']).value;
|
const stateFilter = this.editorForm.get(['filters', 'stateFilter']).value;
|
||||||
if (stateFilter)
|
if (stateFilter)
|
||||||
@ -326,43 +326,43 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
prepareForLoading(instances: Instance[])
|
prepareForLoading(machines: Machine[])
|
||||||
{
|
{
|
||||||
for (const instance of instances)
|
for (const machine of machines)
|
||||||
instance.loading = true;
|
machine.loading = true;
|
||||||
|
|
||||||
return instances;
|
return machines;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
trackByFunction = (index: number, instance: Instance) => instance.name;
|
trackByFunction = (index: number, machine: Machine) => machine.name;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private computeFiltersOptions(computeOnlyState = false)
|
private computeFiltersOptions(computeOnlyState = false)
|
||||||
{
|
{
|
||||||
this.runningInstanceCount = 0;
|
this.runningMachineCount = 0;
|
||||||
this.stoppedInstanceCount = 0;
|
this.stoppedMachineCount = 0;
|
||||||
this.instanceStateArray = [];
|
this.machineStateArray = [];
|
||||||
|
|
||||||
const memoryValues = {};
|
const memoryValues = {};
|
||||||
const diskValues = {};
|
const diskValues = {};
|
||||||
|
|
||||||
for (const instance of this.instances)
|
for (const machine of this.machines)
|
||||||
{
|
{
|
||||||
if (instance.state === 'running')
|
if (machine.state === 'running')
|
||||||
this.runningInstanceCount++;
|
this.runningMachineCount++;
|
||||||
|
|
||||||
if (instance.state === 'stopped')
|
if (machine.state === 'stopped')
|
||||||
this.stoppedInstanceCount++;
|
this.stoppedMachineCount++;
|
||||||
|
|
||||||
if (!~this.instanceStateArray.indexOf(instance.state))
|
if (!~this.machineStateArray.indexOf(machine.state))
|
||||||
this.instanceStateArray.push(instance.state);
|
this.machineStateArray.push(machine.state);
|
||||||
|
|
||||||
if (!computeOnlyState && !memoryValues[instance.memory])
|
if (!computeOnlyState && !memoryValues[machine.memory])
|
||||||
memoryValues[instance.memory] = true;
|
memoryValues[machine.memory] = true;
|
||||||
|
|
||||||
if (!computeOnlyState && !diskValues[instance.disk])
|
if (!computeOnlyState && !diskValues[machine.disk])
|
||||||
diskValues[instance.disk] = true;
|
diskValues[machine.disk] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (computeOnlyState)
|
if (computeOnlyState)
|
||||||
@ -386,21 +386,21 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
startMachine(instance: Instance)
|
startMachine(machine: Machine)
|
||||||
{
|
{
|
||||||
if (instance.state !== 'stopped')
|
if (machine.state !== 'stopped')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
instance.working = true;
|
machine.working = true;
|
||||||
this.toastr.info(`Starting machine "${instance.name}"...`);
|
this.toastr.info(`Starting machine "${machine.name}"...`);
|
||||||
|
|
||||||
this.instancesService.start(instance.id)
|
this.machinesService.start(machine.id)
|
||||||
.pipe(
|
.pipe(
|
||||||
delay(1000),
|
delay(1000),
|
||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
this.instancesService.getInstanceUntilExpectedState(instance, ['running'], x =>
|
this.machinesService.getMachineUntilExpectedState(machine, ['running'], x =>
|
||||||
{
|
{
|
||||||
instance.state = x.state;
|
machine.state = x.state;
|
||||||
|
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
})
|
})
|
||||||
@ -411,32 +411,32 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
{
|
{
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
|
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
this.toastr.info(`The machine "${instance.name}" has been started`);
|
this.toastr.info(`The machine "${machine.name}" has been started`);
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
|
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
restartMachine(instance: Instance)
|
restartMachine(machine: Machine)
|
||||||
{
|
{
|
||||||
if (instance.state !== 'running')
|
if (machine.state !== 'running')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
instance.working = true;
|
machine.working = true;
|
||||||
this.toastr.info(`Restarting machine "${instance.name}"...`);
|
this.toastr.info(`Restarting machine "${machine.name}"...`);
|
||||||
|
|
||||||
this.instancesService.reboot(instance.id)
|
this.machinesService.reboot(machine.id)
|
||||||
.pipe(
|
.pipe(
|
||||||
delay(1000),
|
delay(1000),
|
||||||
switchMap(() => this.instancesService.getInstanceUntilExpectedState(instance, ['running'], x =>
|
switchMap(() => this.machinesService.getMachineUntilExpectedState(machine, ['running'], x =>
|
||||||
{
|
{
|
||||||
instance.state = x.state;
|
machine.state = x.state;
|
||||||
|
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
})
|
})
|
||||||
@ -447,33 +447,33 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
{
|
{
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
|
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
|
|
||||||
this.toastr.info(`The machine "${instance.name}" has been restarted`);
|
this.toastr.info(`The machine "${machine.name}" has been restarted`);
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
|
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
stopMachine(instance: Instance)
|
stopMachine(machine: Machine)
|
||||||
{
|
{
|
||||||
if (instance.state !== 'running')
|
if (machine.state !== 'running')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
instance.working = true;
|
machine.working = true;
|
||||||
this.toastr.info(`Stopping machine "${instance.name}"`);
|
this.toastr.info(`Stopping machine "${machine.name}"`);
|
||||||
|
|
||||||
this.instancesService.stop(instance.id)
|
this.machinesService.stop(machine.id)
|
||||||
.pipe(
|
.pipe(
|
||||||
delay(1000),
|
delay(1000),
|
||||||
switchMap(() => this.instancesService.getInstanceUntilExpectedState(instance, ['stopped'], x =>
|
switchMap(() => this.machinesService.getMachineUntilExpectedState(machine, ['stopped'], x =>
|
||||||
{
|
{
|
||||||
instance.state = x.state;
|
machine.state = x.state;
|
||||||
|
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
})
|
})
|
||||||
@ -484,25 +484,25 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
{
|
{
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
|
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
this.toastr.info(`The machine "${instance.name}" has been stopped`);
|
this.toastr.info(`The machine "${machine.name}" has been stopped`);
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
|
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
resizeMachine(instance: Instance)
|
resizeMachine(machine: Machine)
|
||||||
{
|
{
|
||||||
const modalConfig = {
|
const modalConfig = {
|
||||||
ignoreBackdropClick: true,
|
ignoreBackdropClick: true,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
animated: true,
|
animated: true,
|
||||||
initialState: { instance }
|
initialState: { machine }
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalRef = this.modalService.show(PackageSelectorComponent, modalConfig);
|
const modalRef = this.modalService.show(PackageSelectorComponent, modalConfig);
|
||||||
@ -512,43 +512,43 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
.pipe(
|
.pipe(
|
||||||
tap(() =>
|
tap(() =>
|
||||||
{
|
{
|
||||||
this.toastr.info(`Changing specifications for machine "${instance.name}"...`);
|
this.toastr.info(`Changing specifications for machine "${machine.name}"...`);
|
||||||
instance.working = true;
|
machine.working = true;
|
||||||
}),
|
}),
|
||||||
first(),
|
first(),
|
||||||
switchMap(pkg => this.instancesService.resize(instance.id, pkg.id).pipe(map(() => pkg)))
|
switchMap(pkg => this.machinesService.resize(machine.id, pkg.id).pipe(map(() => pkg)))
|
||||||
)
|
)
|
||||||
.subscribe(pkg =>
|
.subscribe(pkg =>
|
||||||
{
|
{
|
||||||
instance.package = pkg.name;
|
machine.package = pkg.name;
|
||||||
instance.memory = pkg.memory;
|
machine.memory = pkg.memory;
|
||||||
instance.disk = pkg.disk;
|
machine.disk = pkg.disk;
|
||||||
|
|
||||||
this.fillInInstanceDetails(instance);
|
this.fillInMachineDetails(machine);
|
||||||
|
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
|
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
this.toastr.info(`The specifications for machine "${instance.name}" have been changed`);
|
this.toastr.info(`The specifications for machine "${machine.name}" have been changed`);
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
||||||
this.toastr.error(`Couldn't change the specifications for machine "${instance.name}" ${errorDetails}`);
|
this.toastr.error(`Couldn't change the specifications for machine "${machine.name}" ${errorDetails}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
renameMachine(instance: Instance)
|
renameMachine(machine: Machine)
|
||||||
{
|
{
|
||||||
const instanceName = instance.name;
|
const machineName = machine.name;
|
||||||
|
|
||||||
const modalConfig = {
|
const modalConfig = {
|
||||||
ignoreBackdropClick: true,
|
ignoreBackdropClick: true,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
animated: true,
|
animated: true,
|
||||||
initialState: {
|
initialState: {
|
||||||
value: instanceName,
|
value: machineName,
|
||||||
required: true,
|
required: true,
|
||||||
title: 'Rename machine',
|
title: 'Rename machine',
|
||||||
prompt: 'Type in the new name for your machine',
|
prompt: 'Type in the new name for your machine',
|
||||||
@ -561,68 +561,68 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
modalRef.content.save.pipe(first()).subscribe(name =>
|
modalRef.content.save.pipe(first()).subscribe(name =>
|
||||||
{
|
{
|
||||||
if (name === instanceName)
|
if (name === machineName)
|
||||||
{
|
{
|
||||||
this.toastr.warning(`You provided the same name for machine "${instanceName}"`);
|
this.toastr.warning(`You provided the same name for machine "${machineName}"`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.working = true;
|
machine.working = true;
|
||||||
|
|
||||||
this.instancesService.rename(instance.id, name)
|
this.machinesService.rename(machine.id, name)
|
||||||
.subscribe(() =>
|
.subscribe(() =>
|
||||||
{
|
{
|
||||||
instance.name = name;
|
machine.name = name;
|
||||||
|
|
||||||
this.applyFiltersAndSort();
|
this.applyFiltersAndSort();
|
||||||
|
|
||||||
this.toastr.info(`The "${instanceName}" machine has been renamed to "${instance.name}"`);
|
this.toastr.info(`The "${machineName}" machine has been renamed to "${machine.name}"`);
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
||||||
this.toastr.error(`Couldn't rename the "${instanceName}" machine ${errorDetails}`);
|
this.toastr.error(`Couldn't rename the "${machineName}" machine ${errorDetails}`);
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
showTagEditor(instance: Instance, showMetadata = false)
|
showTagEditor(machine: Machine, showMetadata = false)
|
||||||
{
|
{
|
||||||
const modalConfig = {
|
const modalConfig = {
|
||||||
ignoreBackdropClick: true,
|
ignoreBackdropClick: true,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
animated: true,
|
animated: true,
|
||||||
initialState: { instance, showMetadata }
|
initialState: { machine, showMetadata }
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalRef = this.modalService.show(InstanceTagEditorComponent, modalConfig);
|
const modalRef = this.modalService.show(MachineTagEditorComponent, modalConfig);
|
||||||
modalRef.setClass('modal-lg');
|
modalRef.setClass('modal-lg');
|
||||||
|
|
||||||
modalRef.content.save.pipe(first()).subscribe(x =>
|
modalRef.content.save.pipe(first()).subscribe(x =>
|
||||||
{
|
{
|
||||||
instance[showMetadata ? 'metadata' : 'tags'] = x;
|
machine[showMetadata ? 'metadata' : 'tags'] = x;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
createImageFromMachine(instance: Instance)
|
createImageFromMachine(machine: Machine)
|
||||||
{
|
{
|
||||||
const modalConfig = {
|
const modalConfig = {
|
||||||
ignoreBackdropClick: true,
|
ignoreBackdropClick: true,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
animated: true,
|
animated: true,
|
||||||
initialState: { instance }
|
initialState: { machine }
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalRef = this.modalService.show(CustomImageEditorComponent, modalConfig);
|
const modalRef = this.modalService.show(CustomImageEditorComponent, modalConfig);
|
||||||
|
|
||||||
modalRef.content.save.pipe(first()).subscribe(x =>
|
modalRef.content.save.pipe(first()).subscribe(x =>
|
||||||
{
|
{
|
||||||
this.toastr.info(`Creating a new image based on the "${instance.name}" machine...`);
|
this.toastr.info(`Creating a new image based on the "${machine.name}" machine...`);
|
||||||
|
|
||||||
this.catalogService.createImage(instance.id, x.name, x.version, x.description)
|
this.catalogService.createImage(machine.id, x.name, x.version, x.description)
|
||||||
.pipe(
|
.pipe(
|
||||||
delay(1000),
|
delay(1000),
|
||||||
switchMap(image => this.catalogService.getImageUntilExpectedState(image, ['active', 'failed'])
|
switchMap(image => this.catalogService.getImageUntilExpectedState(image, ['active', 'failed'])
|
||||||
@ -632,28 +632,28 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
.subscribe(image =>
|
.subscribe(image =>
|
||||||
{
|
{
|
||||||
if (image.state === 'active')
|
if (image.state === 'active')
|
||||||
this.toastr.info(`A new image "${x.name}" based on the "${instance.name}" machine has been created`);
|
this.toastr.info(`A new image "${x.name}" based on the "${machine.name}" machine has been created`);
|
||||||
else
|
else
|
||||||
this.toastr.error(`Failed to create an image based on the "${instance.name}" machine`);
|
this.toastr.error(`Failed to create an image based on the "${machine.name}" machine`);
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
||||||
this.toastr.error(`Failed to create an image based on the "${instance.name}" machine ${errorDetails}`);
|
this.toastr.error(`Failed to create an image based on the "${machine.name}" machine ${errorDetails}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
createMachine(instance?: Instance)
|
createMachine(machine?: Machine)
|
||||||
{
|
{
|
||||||
const modalConfig = {
|
const modalConfig = {
|
||||||
ignoreBackdropClick: true,
|
ignoreBackdropClick: true,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
animated: true,
|
animated: true,
|
||||||
initialState: { instance }
|
initialState: { machine }
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalRef = this.modalService.show(InstanceWizardComponent, modalConfig);
|
const modalRef = this.modalService.show(MachineWizardComponent, modalConfig);
|
||||||
modalRef.setClass('modal-xl');
|
modalRef.setClass('modal-xl');
|
||||||
|
|
||||||
modalRef.content.save.pipe(first()).subscribe(x =>
|
modalRef.content.save.pipe(first()).subscribe(x =>
|
||||||
@ -662,23 +662,23 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
x.working = true;
|
x.working = true;
|
||||||
|
|
||||||
this.fillInInstanceDetails(x);
|
this.fillInMachineDetails(x);
|
||||||
|
|
||||||
this.instances.push(x);
|
this.machines.push(x);
|
||||||
|
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
deleteMachine(instance: Instance)
|
deleteMachine(machine: Machine)
|
||||||
{
|
{
|
||||||
const modalConfig = {
|
const modalConfig = {
|
||||||
ignoreBackdropClick: true,
|
ignoreBackdropClick: true,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
animated: true,
|
animated: true,
|
||||||
initialState: {
|
initialState: {
|
||||||
prompt: `Are you sure you wish to permanently delete the "${instance.name}" machine?`,
|
prompt: `Are you sure you wish to permanently delete the "${machine.name}" machine?`,
|
||||||
confirmButtonText: 'Yes, delete this machine',
|
confirmButtonText: 'Yes, delete this machine',
|
||||||
declineButtonText: 'No, keep it',
|
declineButtonText: 'No, keep it',
|
||||||
confirmByDefault: false
|
confirmByDefault: false
|
||||||
@ -689,123 +689,123 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
modalRef.content.confirm.pipe(first()).subscribe(() =>
|
modalRef.content.confirm.pipe(first()).subscribe(() =>
|
||||||
{
|
{
|
||||||
instance.working = true;
|
machine.working = true;
|
||||||
|
|
||||||
this.toastr.info(`Removing machine "${instance.name}"...`);
|
this.toastr.info(`Removing machine "${machine.name}"...`);
|
||||||
|
|
||||||
this.instancesService.delete(instance.id)
|
this.machinesService.delete(machine.id)
|
||||||
.subscribe(() =>
|
.subscribe(() =>
|
||||||
{
|
{
|
||||||
const index = this.instances.findIndex(i => i.id === instance.id);
|
const index = this.machines.findIndex(i => i.id === machine.id);
|
||||||
if (index < 0) return;
|
if (index < 0) return;
|
||||||
|
|
||||||
this.instances.splice(index, 1);
|
this.machines.splice(index, 1);
|
||||||
|
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
|
|
||||||
this.toastr.info(`The machine "${instance.name}" has been removed`);
|
this.toastr.info(`The machine "${machine.name}" has been removed`);
|
||||||
},
|
},
|
||||||
err =>
|
err =>
|
||||||
{
|
{
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
showMachineHistory(instance: Instance)
|
showMachineHistory(machine: Machine)
|
||||||
{
|
{
|
||||||
const modalConfig = {
|
const modalConfig = {
|
||||||
ignoreBackdropClick: true,
|
ignoreBackdropClick: true,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
animated: true,
|
animated: true,
|
||||||
initialState: { instance }
|
initialState: { machine }
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalRef = this.modalService.show(InstanceHistoryComponent, modalConfig);
|
const modalRef = this.modalService.show(MachineHistoryComponent, modalConfig);
|
||||||
modalRef.setClass('modal-lg');
|
modalRef.setClass('modal-lg');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
tabChanged(event, instance: Instance)
|
tabChanged(event, machine: Machine)
|
||||||
{
|
{
|
||||||
if (event.id.endsWith('info'))
|
if (event.id.endsWith('info'))
|
||||||
instance.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
machine.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
||||||
else if (event.id.endsWith('snapshots'))
|
else if (event.id.endsWith('snapshots'))
|
||||||
instance.shouldLoadSnapshots = this.editorForm.get('showMachineDetails').value;
|
machine.shouldLoadSnapshots = this.editorForm.get('showMachineDetails').value;
|
||||||
else if (event.id.endsWith('networks'))
|
else if (event.id.endsWith('networks'))
|
||||||
instance.shouldLoadNetworks = this.editorForm.get('showMachineDetails').value;
|
machine.shouldLoadNetworks = this.editorForm.get('showMachineDetails').value;
|
||||||
else if (event.id.endsWith('volumes'))
|
else if (event.id.endsWith('volumes'))
|
||||||
{
|
{
|
||||||
//instance.shouldLoadVolumes = this.editorForm.get('showMachineDetails').value;
|
//machine.shouldLoadVolumes = this.editorForm.get('showMachineDetails').value;
|
||||||
}
|
}
|
||||||
else if (event.id.endsWith('migrations'))
|
else if (event.id.endsWith('migrations'))
|
||||||
{
|
{
|
||||||
//instance.shouldLoadMigrations = this.editorForm.get('showMachineDetails').value;
|
//machine.shouldLoadMigrations = this.editorForm.get('showMachineDetails').value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
loadInstanceDetails(instance: Instance): any
|
loadMachineDetails(machine: Machine): any
|
||||||
{
|
{
|
||||||
instance.loading = false;
|
machine.loading = false;
|
||||||
|
|
||||||
instance.working = !this.stableStates.includes(instance.state);
|
machine.working = !this.stableStates.includes(machine.state);
|
||||||
|
|
||||||
// Keep polling the instances that are not in a "stable" state
|
// Keep polling the machines that are not in a "stable" state
|
||||||
if (instance.working)
|
if (machine.working)
|
||||||
this.instancesService.getInstanceUntilExpectedState(instance, this.stableStates, x =>
|
this.machinesService.getMachineUntilExpectedState(machine, this.stableStates, x =>
|
||||||
{
|
{
|
||||||
instance.state = x.state;
|
machine.state = x.state;
|
||||||
|
|
||||||
// This allows us to trigger later on when the state changes to a something stable
|
// This allows us to trigger later on when the state changes to a something stable
|
||||||
instance.shouldLoadInfo = false;
|
machine.shouldLoadInfo = false;
|
||||||
|
|
||||||
this.computeFiltersOptions(true);
|
this.computeFiltersOptions(true);
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe(x =>
|
.subscribe(x =>
|
||||||
{
|
{
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
|
|
||||||
// Update the instance with what we got from the server
|
// Update the machine with what we got from the server
|
||||||
Object.assign(instance, x);
|
Object.assign(machine, x);
|
||||||
|
|
||||||
instance.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
machine.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
||||||
|
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
if (err.status === 410)
|
if (err.status === 410)
|
||||||
{
|
{
|
||||||
const index = this.instances.findIndex(i => i.id === instance.id);
|
const index = this.machines.findIndex(i => i.id === machine.id);
|
||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
{
|
{
|
||||||
this.instances.splice(index, 1);
|
this.machines.splice(index, 1);
|
||||||
|
|
||||||
this.computeFiltersOptions();
|
this.computeFiltersOptions();
|
||||||
|
|
||||||
this.toastr.error(`The machine "${instance.name}" has been removed`);
|
this.toastr.error(`The machine "${machine.name}" has been removed`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||||
|
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
machine.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
watchInstanceState(instance: Instance)
|
watchMachineState(machine: Machine)
|
||||||
{
|
{
|
||||||
instance.working = true;
|
machine.working = true;
|
||||||
|
|
||||||
this.instancesService.getInstanceUntilExpectedState(instance, ['running'], x =>
|
this.machinesService.getMachineUntilExpectedState(machine, ['running'], x =>
|
||||||
{
|
{
|
||||||
instance.state = x.state;
|
machine.state = x.state;
|
||||||
|
|
||||||
this.computeFiltersOptions(true);
|
this.computeFiltersOptions(true);
|
||||||
})
|
})
|
||||||
@ -814,49 +814,49 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
takeUntil(this.destroy$)
|
takeUntil(this.destroy$)
|
||||||
).subscribe(() => { }, err =>
|
).subscribe(() => { }, err =>
|
||||||
{
|
{
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
updateInstance(instance: Instance, updates: Instance)
|
updateMachine(machine: Machine, updates: Machine)
|
||||||
{
|
{
|
||||||
instance.refreshInfo = instance.state !== updates.state;
|
machine.refreshInfo = machine.state !== updates.state;
|
||||||
instance.state = updates.state;
|
machine.state = updates.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
setInstanceInfo(instance: Instance, dnsList)
|
setMachineInfo(machine: Machine, dnsList)
|
||||||
{
|
{
|
||||||
// Update the instance as a result of the info panel's "load" event. We do this because the intances are (un)loaded
|
// Update the machine as a result of the info panel's "load" event. We do this because the intances are (un)loaded
|
||||||
// from the viewport as the user scrolls through the page, to optimize memory consumption.
|
// from the viewport as the user scrolls through the page, to optimize memory consumption.
|
||||||
instance.dnsList = dnsList;
|
machine.dnsList = dnsList;
|
||||||
instance.infoLoaded = true;
|
machine.infoLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
setInstanceNetworks(instance: Instance, nics)
|
setMachineNetworks(machine: Machine, nics)
|
||||||
{
|
{
|
||||||
// Update the instance as a result of the networks panel's "load" event. We do this because the intances are (un)loaded
|
// Update the machine as a result of the networks panel's "load" event. We do this because the intances are (un)loaded
|
||||||
// from the viewport as the user scrolls through the page, to optimize memory consumption.
|
// from the viewport as the user scrolls through the page, to optimize memory consumption.
|
||||||
instance.nics = nics;
|
machine.nics = nics;
|
||||||
instance.networksLoaded = true;
|
machine.networksLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
setInstanceSnapshots(instance: Instance, snapshots)
|
setMachineSnapshots(machine: Machine, snapshots)
|
||||||
{
|
{
|
||||||
// Update the instance as a result of the snapshots panel's "load" event. We do this because the intances are (un)loaded
|
// Update the machine as a result of the snapshots panel's "load" event. We do this because the intances are (un)loaded
|
||||||
// from the viewport as the user scrolls through the page, to optimize memory consumption.
|
// from the viewport as the user scrolls through the page, to optimize memory consumption.
|
||||||
instance.snapshots = snapshots;
|
machine.snapshots = snapshots;
|
||||||
instance.snapshotsLoaded = true;
|
machine.snapshotsLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
refreshInstanceDnsList(instance: Instance)
|
refreshMachineDnsList(machine: Machine)
|
||||||
{
|
{
|
||||||
instance.working = false;
|
machine.working = false;
|
||||||
instance.refreshInfo = true;
|
machine.refreshInfo = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
@ -867,17 +867,17 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private fillInInstanceDetails(instance: Instance)
|
private fillInMachineDetails(machine: Machine)
|
||||||
{
|
{
|
||||||
const imageDetails = this.images.find(i => i.id === instance.image);
|
const imageDetails = this.images.find(i => i.id === machine.image);
|
||||||
if (imageDetails)
|
if (imageDetails)
|
||||||
instance.imageDetails = imageDetails;
|
machine.imageDetails = imageDetails;
|
||||||
|
|
||||||
const packageDetails = this.packages.find(p => p.name === instance.package);
|
const packageDetails = this.packages.find(p => p.name === machine.package);
|
||||||
if (packageDetails)
|
if (packageDetails)
|
||||||
instance.packageDetails = packageDetails;
|
machine.packageDetails = packageDetails;
|
||||||
|
|
||||||
instance.volumes = this.volumes.filter(i => i.refs && i.refs.includes(instance.id));
|
machine.volumes = this.volumes.filter(i => i.refs && i.refs.includes(machine.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
@ -890,7 +890,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
ngOnInit(): void
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
this.getInstances();
|
this.getMachines();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
@ -9,49 +9,49 @@ import { WebpackTranslateLoader } from '../helpers/webpack-translate-loader.serv
|
|||||||
import { TranslateCompiler } from '@ngx-translate/core';
|
import { TranslateCompiler } from '@ngx-translate/core';
|
||||||
import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler';
|
import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler';
|
||||||
|
|
||||||
import { InstancesComponent } from './instances.component';
|
import { MachinesComponent } from './machines.component';
|
||||||
import { InstanceWizardComponent } from './instance-wizard/instance-wizard.component';
|
import { MachineWizardComponent } from './machine-wizard/machine-wizard.component';
|
||||||
import { PackageSelectorComponent } from './package-selector/package-selector.component';
|
import { PackageSelectorComponent } from './package-selector/package-selector.component';
|
||||||
import { InstanceSnapshotsComponent } from './instance-snapshots/instance-snapshots.component';
|
import { MachineSnapshotsComponent } from './machine-snapshots/machine-snapshots.component';
|
||||||
import { InstanceNetworksComponent } from './instance-networks/instance-networks.component';
|
import { MachineNetworksComponent } from './machine-networks/machine-networks.component';
|
||||||
import { InstanceSecurityComponent } from './instance-security/instance-security.component';
|
import { MachineSecurityComponent } from './machine-security/machine-security.component';
|
||||||
import { InstanceTagEditorComponent } from './instance-tag-editor/instance-tag-editor.component';
|
import { MachineTagEditorComponent } from './machine-tag-editor/machine-tag-editor.component';
|
||||||
import { InstanceHistoryComponent } from './instance-history/instance-history.component';
|
import { MachineHistoryComponent } from './machine-history/machine-history.component';
|
||||||
import { CustomImageEditorComponent } from '../catalog/custom-image-editor/custom-image-editor.component';
|
import { CustomImageEditorComponent } from '../catalog/custom-image-editor/custom-image-editor.component';
|
||||||
import { InstanceInfoComponent } from './instance-info/instance-info.component';
|
import { MachineInfoComponent } from './machine-info/machine-info.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
InstancesComponent,
|
MachinesComponent,
|
||||||
InstanceWizardComponent,
|
MachineWizardComponent,
|
||||||
PackageSelectorComponent,
|
PackageSelectorComponent,
|
||||||
InstanceSnapshotsComponent,
|
MachineSnapshotsComponent,
|
||||||
InstanceNetworksComponent,
|
MachineNetworksComponent,
|
||||||
InstanceSecurityComponent,
|
MachineSecurityComponent,
|
||||||
InstanceTagEditorComponent,
|
MachineTagEditorComponent,
|
||||||
InstanceHistoryComponent,
|
MachineHistoryComponent,
|
||||||
InstanceInfoComponent,
|
MachineInfoComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
SharedModule,
|
SharedModule,
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: InstancesComponent,
|
component: MachinesComponent,
|
||||||
data:
|
data:
|
||||||
{
|
{
|
||||||
title: 'instances.title',
|
title: 'machines.title',
|
||||||
subTitle: 'instances.subTitle',
|
subTitle: 'machines.subTitle',
|
||||||
icon: 'server'
|
icon: 'server'
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'wizard',
|
path: 'wizard',
|
||||||
component: InstanceWizardComponent,
|
component: MachineWizardComponent,
|
||||||
data:
|
data:
|
||||||
{
|
{
|
||||||
title: 'instances.wizard.title',
|
title: 'machines.wizard.title',
|
||||||
subTitle: 'instances.wizard.subTitle',
|
subTitle: 'machines.wizard.subTitle',
|
||||||
icon: 'hat-wizard'
|
icon: 'hat-wizard'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ import { InstanceInfoComponent } from './instance-info/instance-info.component';
|
|||||||
loader: {
|
loader: {
|
||||||
provide: TranslateLoader,
|
provide: TranslateLoader,
|
||||||
//useClass: WebpackTranslateLoader
|
//useClass: WebpackTranslateLoader
|
||||||
useFactory: () => new WebpackTranslateLoader('dashboard')
|
useFactory: () => new WebpackTranslateLoader('machines')
|
||||||
},
|
},
|
||||||
compiler: {
|
compiler: {
|
||||||
provide: TranslateCompiler,
|
provide: TranslateCompiler,
|
||||||
@ -72,15 +72,15 @@ import { InstanceInfoComponent } from './instance-info/instance-info.component';
|
|||||||
})
|
})
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
InstanceWizardComponent,
|
MachineWizardComponent,
|
||||||
PackageSelectorComponent,
|
PackageSelectorComponent,
|
||||||
InstanceTagEditorComponent,
|
MachineTagEditorComponent,
|
||||||
InstanceHistoryComponent,
|
MachineHistoryComponent,
|
||||||
CustomImageEditorComponent
|
CustomImageEditorComponent
|
||||||
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class InstancesModule
|
export class MachinesModule
|
||||||
{
|
{
|
||||||
constructor(private readonly translate: TranslateService)
|
constructor(private readonly translate: TranslateService)
|
||||||
{
|
{
|
@ -1,4 +1,4 @@
|
|||||||
export class InstanceDisk
|
export class MachineDisk
|
||||||
{
|
{
|
||||||
id: string;
|
id: string;
|
||||||
boot: boolean;
|
boot: boolean;
|
@ -1,4 +1,4 @@
|
|||||||
export class InstanceVolume
|
export class MachineVolume
|
||||||
{
|
{
|
||||||
name: string;
|
name: string;
|
||||||
type: string; // "tritonnfs"
|
type: string; // "tritonnfs"
|
@ -1,11 +1,11 @@
|
|||||||
import { Network } from '../../networking/models/network';
|
import { Network } from '../../networking/models/network';
|
||||||
import { InstanceDisk } from './instance-disk';
|
import { MachineDisk } from './machine-disk';
|
||||||
import { Nic } from './nic';
|
import { Nic } from './nic';
|
||||||
import { InstanceVolume } from './instance-volume';
|
import { MachineVolume } from './machine-volume';
|
||||||
import { CatalogImage } from '../../catalog/models/image';
|
import { CatalogImage } from '../../catalog/models/image';
|
||||||
import { CatalogPackage } from '../../catalog/models/package';
|
import { CatalogPackage } from '../../catalog/models/package';
|
||||||
|
|
||||||
export class InstanceRequest
|
export class MachineRequest
|
||||||
{
|
{
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -19,14 +19,14 @@ export class InstanceRequest
|
|||||||
firewall_enabled: boolean;
|
firewall_enabled: boolean;
|
||||||
deletion_protection: boolean;
|
deletion_protection: boolean;
|
||||||
allow_shared_images: boolean; // Whether to allow provisioning from a shared image.
|
allow_shared_images: boolean; // Whether to allow provisioning from a shared image.
|
||||||
volumes: InstanceVolume[]; // list of objects representing volumes to mount when the newly created machine boots
|
volumes: MachineVolume[]; // list of objects representing volumes to mount when the newly created machine boots
|
||||||
disks: InstanceDisk[]; // An array of disk objects to be created (bhyve)
|
disks: MachineDisk[]; // An array of disk objects to be created (bhyve)
|
||||||
disk: number;
|
disk: number;
|
||||||
encrypted: boolean; // Place this instance into an encrypted server. Optional.
|
encrypted: boolean; // Place this machine into an encrypted server. Optional.
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Instance extends InstanceRequest
|
export class Machine extends MachineRequest
|
||||||
{
|
{
|
||||||
nics: Nic[];
|
nics: Nic[];
|
||||||
imageDetails: CatalogImage;
|
imageDetails: CatalogImage;
|
@ -9,10 +9,10 @@
|
|||||||
|
|
||||||
<p class="my-2">Pick the package that best suits your requirements</p>
|
<p class="my-2">Pick the package that best suits your requirements</p>
|
||||||
|
|
||||||
<app-packages [image]="instance.imageDetails" [imageType]="imageType" [package]="instance.package" (select)="packageSelected($event)"></app-packages>
|
<app-packages [image]="machine.imageDetails" [imageType]="imageType" [package]="machine.package" (select)="packageSelected($event)"></app-packages>
|
||||||
|
|
||||||
<div class="current-package">
|
<div class="current-package">
|
||||||
This machine's current package is: <b class="text-uppercase">{{ instance.package }}</b>
|
This machine's current package is: <b class="text-uppercase">{{ machine.package }}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="selected-package" *ngIf="editorForm.get('package').value">
|
<p class="selected-package" *ngIf="editorForm.get('package').value">
|
@ -14,7 +14,7 @@ import { CatalogPackage } from '../../catalog/models/package';
|
|||||||
export class PackageSelectorComponent implements OnInit
|
export class PackageSelectorComponent implements OnInit
|
||||||
{
|
{
|
||||||
@Input()
|
@Input()
|
||||||
instance: any;
|
machine: any;
|
||||||
|
|
||||||
save = new Subject<CatalogPackage>();
|
save = new Subject<CatalogPackage>();
|
||||||
imageType: number;
|
imageType: number;
|
||||||
@ -67,7 +67,7 @@ export class PackageSelectorComponent implements OnInit
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
ngOnInit(): void
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
switch (this.instance.type)
|
switch (this.machine.type)
|
||||||
{
|
{
|
||||||
case 'virtualmachine':
|
case 'virtualmachine':
|
||||||
this.imageType = 1;
|
this.imageType = 1;
|
@ -64,7 +64,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="rule">
|
<div class="rule">
|
||||||
{{ control.value.type }}
|
{{ control.value.type }}
|
||||||
<b *ngIf="control.value.config">{{ instances[control.value.config] || control.value.config }}</b>
|
<b *ngIf="control.value.config">{{ machines[control.value.config] || control.value.config }}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-sm text-danger p-0" (click)="removeFromRule(index)"
|
<button class="btn btn-sm text-danger p-0" (click)="removeFromRule(index)"
|
||||||
@ -89,7 +89,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="rule">
|
<div class="rule">
|
||||||
{{ control.value.type }}
|
{{ control.value.type }}
|
||||||
<b *ngIf="control.value.config">{{ instances[control.value.config] || control.value.config }}</b>
|
<b *ngIf="control.value.config">{{ machines[control.value.config] || control.value.config }}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn btn-sm text-danger p-0" (click)="removeToRule(index)"
|
<button class="btn btn-sm text-danger p-0" (click)="removeToRule(index)"
|
||||||
|
@ -8,7 +8,7 @@ import { FirewallRule } from '../models/firewall-rule';
|
|||||||
import { FirewallRuleRequest } from '../models/firewall-rule';
|
import { FirewallRuleRequest } from '../models/firewall-rule';
|
||||||
import { FirewallService } from '../helpers/firewall.service';
|
import { FirewallService } from '../helpers/firewall.service';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { InstancesService } from '../../instances/helpers/instances.service';
|
import { MachinesService } from '../../machines/helpers/machines.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-firewall-editor',
|
selector: 'app-firewall-editor',
|
||||||
@ -28,7 +28,7 @@ export class FirewallEditorComponent implements OnInit, OnDestroy
|
|||||||
canAddFromRule: boolean;
|
canAddFromRule: boolean;
|
||||||
canAddToRule: boolean;
|
canAddToRule: boolean;
|
||||||
protocolConfigRegex: string;
|
protocolConfigRegex: string;
|
||||||
instances = {};
|
machines = {};
|
||||||
|
|
||||||
private destroy$ = new Subject();
|
private destroy$ = new Subject();
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ export class FirewallEditorComponent implements OnInit, OnDestroy
|
|||||||
private readonly router: Router,
|
private readonly router: Router,
|
||||||
private readonly fb: FormBuilder,
|
private readonly fb: FormBuilder,
|
||||||
private readonly toastr: ToastrService,
|
private readonly toastr: ToastrService,
|
||||||
private readonly instancesService: InstancesService,
|
private readonly machinesService: MachinesService,
|
||||||
private readonly firewallService: FirewallService)
|
private readonly firewallService: FirewallService)
|
||||||
{ // When the user navigates away from this route, hide the modal
|
{ // When the user navigates away from this route, hide the modal
|
||||||
router.events
|
router.events
|
||||||
@ -47,10 +47,10 @@ export class FirewallEditorComponent implements OnInit, OnDestroy
|
|||||||
)
|
)
|
||||||
.subscribe(() => this.modalRef.hide());
|
.subscribe(() => this.modalRef.hide());
|
||||||
|
|
||||||
this.instancesService.get()
|
this.machinesService.get()
|
||||||
.subscribe(x =>
|
.subscribe(x =>
|
||||||
{
|
{
|
||||||
this.instances = x.reduce((a, b) =>
|
this.machines = x.reduce((a, b) =>
|
||||||
{
|
{
|
||||||
a[b.id] = b.name;
|
a[b.id] = b.name;
|
||||||
return a;
|
return a;
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<div class="col-sm" *ngIf="editorForm.get('type').value === 'vm'">
|
<div class="col-sm" *ngIf="editorForm.get('type').value === 'vm'">
|
||||||
<select class="form-select" formControlName="key" [appAutofocus]="editorForm.get('type').value" [appAutofocusDelay]="250">
|
<select class="form-select" formControlName="key" [appAutofocus]="editorForm.get('type').value" [appAutofocusDelay]="250">
|
||||||
<option *ngFor="let instance of instances" [value]="instance.id">{{ instance.name }}</option>
|
<option *ngFor="let machine of machines" [value]="machine.id">{{ machine.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, HostListener } from '@angular/core';
|
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, HostListener } from '@angular/core';
|
||||||
import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms';
|
import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms';
|
||||||
import { Instance } from '../../instances/models/instance';
|
import { Machine } from '../../machines/models/machine';
|
||||||
import { InstancesService } from '../../instances/helpers/instances.service';
|
import { MachinesService } from '../../machines/helpers/machines.service';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ export class FirewallRuleEditorComponent implements OnInit, OnDestroy
|
|||||||
@Output()
|
@Output()
|
||||||
saved = new EventEmitter();
|
saved = new EventEmitter();
|
||||||
|
|
||||||
instances: Instance[];
|
machines: Machine[];
|
||||||
editorVisible: boolean;
|
editorVisible: boolean;
|
||||||
editorForm: FormGroup;
|
editorForm: FormGroup;
|
||||||
keyRegex = '^[A-Za-z0-9-_]+$';
|
keyRegex = '^[A-Za-z0-9-_]+$';
|
||||||
@ -29,9 +29,9 @@ export class FirewallRuleEditorComponent implements OnInit, OnDestroy
|
|||||||
// --------------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly elementRef: ElementRef,
|
constructor(private readonly elementRef: ElementRef,
|
||||||
private readonly fb: FormBuilder,
|
private readonly fb: FormBuilder,
|
||||||
private readonly instancesService: InstancesService)
|
private readonly machinesService: MachinesService)
|
||||||
{
|
{
|
||||||
this.instancesService.get().subscribe(x => this.instances = x);
|
this.machinesService.get().subscribe(x => this.machines = x);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -82,15 +82,15 @@
|
|||||||
From
|
From
|
||||||
<span *ngFor="let from of fw.fromArray" class="inline-list-item highlight" text="OR">
|
<span *ngFor="let from of fw.fromArray" class="inline-list-item highlight" text="OR">
|
||||||
{{ from.type }}
|
{{ from.type }}
|
||||||
<b *ngIf="from.config">{{ instances[from.config] || from.config }}</b>
|
<b *ngIf="from.config">{{ machines[from.config] || from.config }}</b>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
To
|
To
|
||||||
<span *ngFor="let to of fw.toArray" class="inline-list-item highlight" text="OR">
|
<span *ngFor="let to of fw.toArray" class="inline-list-item highlight" text="OR">
|
||||||
{{ to.type }}
|
{{ to.type }}
|
||||||
<span *ngIf="to.type === 'tag'" class="badge badge-discreet">{{ instances[to.config] || to.config }}</span>
|
<span *ngIf="to.type === 'tag'" class="badge badge-discreet">{{ machines[to.config] || to.config }}</span>
|
||||||
<b *ngIf="to.type !== 'tag'">{{ instances[to.config] || to.config }}</b>
|
<b *ngIf="to.type !== 'tag'">{{ machines[to.config] || to.config }}</b>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
@ -8,7 +8,7 @@ import { Subject, ReplaySubject } from 'rxjs';
|
|||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { FirewallRule } from '../models/firewall-rule';
|
import { FirewallRule } from '../models/firewall-rule';
|
||||||
import { ConfirmationDialogComponent } from '../../components/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from '../../components/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { InstancesService } from '../../instances/helpers/instances.service';
|
import { MachinesService } from '../../machines/helpers/machines.service';
|
||||||
import { FirewallService } from '../helpers/firewall.service';
|
import { FirewallService } from '../helpers/firewall.service';
|
||||||
import { sortArray } from '../../helpers/utils.service';
|
import { sortArray } from '../../helpers/utils.service';
|
||||||
import { Title } from "@angular/platform-browser";
|
import { Title } from "@angular/platform-browser";
|
||||||
@ -25,14 +25,14 @@ export class FirewallRulesComponent implements OnInit, OnDestroy
|
|||||||
listItems: FirewallRule[];
|
listItems: FirewallRule[];
|
||||||
loadingIndicator = true;
|
loadingIndicator = true;
|
||||||
editorForm: FormGroup;
|
editorForm: FormGroup;
|
||||||
instances = {};
|
machines = {};
|
||||||
|
|
||||||
private readonly fuseJsOptions: {};
|
private readonly fuseJsOptions: {};
|
||||||
private destroy$ = new Subject();
|
private destroy$ = new Subject();
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly firewallService: FirewallService,
|
constructor(private readonly firewallService: FirewallService,
|
||||||
private readonly instancesService: InstancesService,
|
private readonly machinesService: MachinesService,
|
||||||
private readonly modalService: BsModalService,
|
private readonly modalService: BsModalService,
|
||||||
private readonly toastr: ToastrService,
|
private readonly toastr: ToastrService,
|
||||||
private readonly fb: FormBuilder,
|
private readonly fb: FormBuilder,
|
||||||
@ -53,10 +53,10 @@ export class FirewallRulesComponent implements OnInit, OnDestroy
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
this.instancesService.get()
|
this.machinesService.get()
|
||||||
.subscribe(x =>
|
.subscribe(x =>
|
||||||
{
|
{
|
||||||
this.instances = x.reduce((a, b) =>
|
this.machines = x.reduce((a, b) =>
|
||||||
{
|
{
|
||||||
a[b.id] = b.name;
|
a[b.id] = b.name;
|
||||||
return a;
|
return a;
|
||||||
|
@ -31,9 +31,9 @@ export class FirewallService
|
|||||||
@Cacheable({
|
@Cacheable({
|
||||||
cacheBusterObserver: cacheBuster$
|
cacheBusterObserver: cacheBuster$
|
||||||
})
|
})
|
||||||
getInstanceFirewallRules(instanceId: string): Observable<FirewallRuleResponse[]>
|
getMachineFirewallRules(machineId: string): Observable<FirewallRuleResponse[]>
|
||||||
{
|
{
|
||||||
return this.httpClient.get<FirewallRuleResponse[]>(`/api/my/machines/${instanceId}/fwrules`);
|
return this.httpClient.get<FirewallRuleResponse[]>(`/api/my/machines/${machineId}/fwrules`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -5,13 +5,13 @@ import { concatMap, delay, filter, first, flatMap, map, mergeMapTo, repeatWhen,
|
|||||||
import { concat, empty, of, range, throwError, zip } from 'rxjs';
|
import { concat, empty, of, range, throwError, zip } from 'rxjs';
|
||||||
import { Cacheable } from 'ts-cacheable';
|
import { Cacheable } from 'ts-cacheable';
|
||||||
import { Network } from '../models/network';
|
import { Network } from '../models/network';
|
||||||
import { Nic } from '../../instances/models/nic';
|
import { Nic } from '../../machines/models/nic';
|
||||||
import { VirtualAreaNetwork } from '../models/vlan';
|
import { VirtualAreaNetwork } from '../models/vlan';
|
||||||
import { VirtualAreaNetworkRequest } from '../models/vlan';
|
import { VirtualAreaNetworkRequest } from '../models/vlan';
|
||||||
import { EditNetworkRequest } from '../models/network';
|
import { EditNetworkRequest } from '../models/network';
|
||||||
import { AddNetworkRequest } from '../models/network';
|
import { AddNetworkRequest } from '../models/network';
|
||||||
import { Instance } from 'src/app/instances/models/instance';
|
import { Machine } from 'src/app/machines/models/machine';
|
||||||
import { InstanceCallbackFunction } from 'src/app/instances/helpers/instances.service';
|
import { MachineCallbackFunction } from 'src/app/machines/helpers/machines.service';
|
||||||
|
|
||||||
const networksCacheBuster$ = new Subject<void>();
|
const networksCacheBuster$ = new Subject<void>();
|
||||||
|
|
||||||
@ -135,28 +135,28 @@ export class NetworkingService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getNics(instanceId: string): Observable<Nic[]>
|
getNics(machineId: string): Observable<Nic[]>
|
||||||
{
|
{
|
||||||
return this.httpClient.get<Nic[]>(`/api/my/machines/${instanceId}/nics`);
|
return this.httpClient.get<Nic[]>(`/api/my/machines/${machineId}/nics`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getNic(instanceId: string, macAddress: string): Observable<Nic>
|
getNic(machineId: string, macAddress: string): Observable<Nic>
|
||||||
{
|
{
|
||||||
return this.httpClient.get<Nic>(`/api/my/machines/${instanceId}/nics/${macAddress.replace(/:/g, '')}`);
|
return this.httpClient.get<Nic>(`/api/my/machines/${machineId}/nics/${macAddress.replace(/:/g, '')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getNicUntilAvailable(instance: any, nic: Nic, networkName: string, callbackFn?: NicCallbackFunction, maxRetries = 30): Observable<Nic>
|
getNicUntilAvailable(machine: any, nic: Nic, networkName: string, callbackFn?: NicCallbackFunction, maxRetries = 30): Observable<Nic>
|
||||||
{
|
{
|
||||||
networkName = networkName.toLocaleLowerCase();
|
networkName = networkName.toLocaleLowerCase();
|
||||||
|
|
||||||
// Keep polling the instance until it reaches the expected state
|
// Keep polling the machine until it reaches the expected state
|
||||||
return this.getNic(instance.id, nic.mac)
|
return this.getNic(machine.id, nic.mac)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(x =>
|
tap(x =>
|
||||||
{
|
{
|
||||||
// We create our own state while the instance reboots
|
// We create our own state while the machine reboots
|
||||||
if (x.state === 'running')
|
if (x.state === 'running')
|
||||||
x.state = 'starting';
|
x.state = 'starting';
|
||||||
|
|
||||||
@ -179,11 +179,11 @@ export class NetworkingService
|
|||||||
filter(x => x.state === 'running' || x.state === 'starting'),
|
filter(x => x.state === 'running' || x.state === 'starting'),
|
||||||
take(1), // needed to stop the repeatWhen loop
|
take(1), // needed to stop the repeatWhen loop
|
||||||
concatMap(nic =>
|
concatMap(nic =>
|
||||||
this.httpClient.get<Instance>(`/api/my/machines/${instance.id}`)
|
this.httpClient.get<Machine>(`/api/my/machines/${machine.id}`)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(() =>
|
tap(() =>
|
||||||
{
|
{
|
||||||
// We create our own state while the instance reboots
|
// We create our own state while the machine reboots
|
||||||
nic.state = 'starting';
|
nic.state = 'starting';
|
||||||
|
|
||||||
if (callbackFn)
|
if (callbackFn)
|
||||||
@ -206,7 +206,7 @@ export class NetworkingService
|
|||||||
take(1), // needed to stop the repeatWhen loop
|
take(1), // needed to stop the repeatWhen loop
|
||||||
map(() =>
|
map(() =>
|
||||||
{
|
{
|
||||||
// We manually set the state as "running" now that the instance has rebooted
|
// We manually set the state as "running" now that the machine has rebooted
|
||||||
nic.state = 'running';
|
nic.state = 'running';
|
||||||
|
|
||||||
if (callbackFn)
|
if (callbackFn)
|
||||||
@ -220,15 +220,15 @@ export class NetworkingService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
addNic(instanceId: string, networkId: string): Observable<Nic>
|
addNic(machineId: string, networkId: string): Observable<Nic>
|
||||||
{
|
{
|
||||||
return this.httpClient.post<Nic>(`/api/my/machines/${instanceId}/nics`, { network: networkId });
|
return this.httpClient.post<Nic>(`/api/my/machines/${machineId}/nics`, { network: networkId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
deleteNic(instanceId: string, macAddress: string): Observable<any>
|
deleteNic(machineId: string, macAddress: string): Observable<any>
|
||||||
{
|
{
|
||||||
return this.httpClient.delete(`/api/my/machines/${instanceId}/nics/${macAddress.replace(/:/g, '')}`);
|
return this.httpClient.delete(`/api/my/machines/${machineId}/nics/${macAddress.replace(/:/g, '')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { DashboardComponent } from './dashboard.component';
|
import { DashboardComponent } from './machines.component';
|
||||||
|
|
||||||
describe('DashboardComponent', () => {
|
describe('DashboardComponent', () => {
|
||||||
let component: DashboardComponent;
|
let component: DashboardComponent;
|
@ -2,8 +2,8 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
templateUrl: './dashboard.component.html',
|
templateUrl: './machines.component.html',
|
||||||
styleUrls: ['./dashboard.component.scss']
|
styleUrls: ['./machines.component.scss']
|
||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit {
|
export class DashboardComponent implements OnInit {
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
import { FileSizePipe } from './file-size.pipe';
|
import { FileSizePipe } from './file-size.pipe';
|
||||||
|
|
||||||
describe('FileSizePipe', () => {
|
describe('FileSizePipe', () => {
|
||||||
it('create an instance', () => {
|
it('create an machine', () => {
|
||||||
const pipe = new FileSizePipe();
|
const pipe = new FileSizePipe();
|
||||||
expect(pipe).toBeTruthy();
|
expect(pipe).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -33,6 +33,7 @@ import { ConfirmationDialogComponent } from './components/confirmation-dialog/co
|
|||||||
import { PromptDialogComponent } from './components/prompt-dialog/prompt-dialog.component';
|
import { PromptDialogComponent } from './components/prompt-dialog/prompt-dialog.component';
|
||||||
import { CustomImageEditorComponent } from './catalog/custom-image-editor/custom-image-editor.component';
|
import { CustomImageEditorComponent } from './catalog/custom-image-editor/custom-image-editor.component';
|
||||||
import { LazyLoadDirective } from './directives/lazy-load.directive';
|
import { LazyLoadDirective } from './directives/lazy-load.directive';
|
||||||
|
import { AffinityRuleEditorComponent } from './components/affinity-rule-editor/affinity-rule-editor.component';
|
||||||
|
|
||||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||||
@ -59,6 +60,7 @@ import { faDocker } from '@fortawesome/free-brands-svg-icons';
|
|||||||
PromptDialogComponent,
|
PromptDialogComponent,
|
||||||
CustomImageEditorComponent,
|
CustomImageEditorComponent,
|
||||||
LazyLoadDirective,
|
LazyLoadDirective,
|
||||||
|
AffinityRuleEditorComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -127,6 +129,7 @@ import { faDocker } from '@fortawesome/free-brands-svg-icons';
|
|||||||
VirtualScrollerModule,
|
VirtualScrollerModule,
|
||||||
NgxSliderModule,
|
NgxSliderModule,
|
||||||
ClipboardModule,
|
ClipboardModule,
|
||||||
|
AffinityRuleEditorComponent
|
||||||
//HasPermissionDirective
|
//HasPermissionDirective
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -39,9 +39,9 @@ export class VolumesService
|
|||||||
//@Cacheable({
|
//@Cacheable({
|
||||||
// cacheBusterObserver: volumesCacheBuster$
|
// cacheBusterObserver: volumesCacheBuster$
|
||||||
//})
|
//})
|
||||||
//getInstanceVolumes(instanceId: string): Observable<VolumeResponse[]>
|
//getMachineVolumes(machineId: string): Observable<VolumeResponse[]>
|
||||||
//{
|
//{
|
||||||
// return this.httpClient.get<VolumeResponse[]>(`/api/my/machines/${instanceId}/volumes`);
|
// return this.httpClient.get<VolumeResponse[]>(`/api/my/machines/${machineId}/volumes`);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { Volume } from './models/volume';
|
import { Volume } from './models/volume';
|
||||||
import { VolumesService } from './helpers/volumes.service';
|
import { VolumesService } from './helpers/volumes.service';
|
||||||
import { InstancesService } from '../instances/helpers/instances.service';
|
import { MachinesService } from '../machines/helpers/machines.service';
|
||||||
import { ConfirmationDialogComponent } from '../components/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from '../components/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { BsModalService } from 'ngx-bootstrap/modal';
|
import { BsModalService } from 'ngx-bootstrap/modal';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
@ -28,7 +28,7 @@ export class VolumesComponent implements OnInit, OnDestroy
|
|||||||
networks = {};
|
networks = {};
|
||||||
loadingIndicator = true;
|
loadingIndicator = true;
|
||||||
editorForm: FormGroup;
|
editorForm: FormGroup;
|
||||||
instances = {};
|
machines = {};
|
||||||
|
|
||||||
private destroy$ = new Subject();
|
private destroy$ = new Subject();
|
||||||
private readonly fuseJsOptions: {};
|
private readonly fuseJsOptions: {};
|
||||||
@ -36,7 +36,7 @@ export class VolumesComponent implements OnInit, OnDestroy
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly volumesService: VolumesService,
|
constructor(private readonly volumesService: VolumesService,
|
||||||
private readonly networkingService: NetworkingService,
|
private readonly networkingService: NetworkingService,
|
||||||
private readonly instancesService: InstancesService,
|
private readonly machinesService: MachinesService,
|
||||||
private readonly modalService: BsModalService,
|
private readonly modalService: BsModalService,
|
||||||
private readonly toastr: ToastrService,
|
private readonly toastr: ToastrService,
|
||||||
private readonly fb: FormBuilder,
|
private readonly fb: FormBuilder,
|
||||||
@ -60,10 +60,10 @@ export class VolumesComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
this.createForm();
|
this.createForm();
|
||||||
|
|
||||||
this.instancesService.get()
|
this.machinesService.get()
|
||||||
.subscribe(x =>
|
.subscribe(x =>
|
||||||
{
|
{
|
||||||
this.instances = x.reduce((a, b) =>
|
this.machines = x.reduce((a, b) =>
|
||||||
{
|
{
|
||||||
a[b.id] = b.name;
|
a[b.id] = b.name;
|
||||||
return a;
|
return a;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"menu":
|
"menu":
|
||||||
{
|
{
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"instances": "Machines",
|
"machines": "Machines",
|
||||||
"volumes": "Volumes",
|
"volumes": "Volumes",
|
||||||
"images": "Images",
|
"images": "Images",
|
||||||
"networks": "Networks",
|
"networks": "Networks",
|
||||||
@ -33,7 +33,7 @@
|
|||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
"subTitle": ""
|
"subTitle": ""
|
||||||
},
|
},
|
||||||
"instances":
|
"machines":
|
||||||
{
|
{
|
||||||
"title": "Machines",
|
"title": "Machines",
|
||||||
"subTitle": ""
|
"subTitle": ""
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dashboard":
|
"machines":
|
||||||
{
|
{
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
"general":
|
"general":
|
||||||
@ -216,8 +216,6 @@
|
|||||||
"addMetadata": "Add metadata",
|
"addMetadata": "Add metadata",
|
||||||
"removeMetadata": "Remove this metadata",
|
"removeMetadata": "Remove this metadata",
|
||||||
"affinityHint": "Affinity",
|
"affinityHint": "Affinity",
|
||||||
"affinityTip": "Affinity (aka 'locality') controls whether the machine should be placed close to or away from other machines.",
|
|
||||||
"affinityRequired": "Provision only when the affinity criteria are met",
|
|
||||||
"previousStep": "Previous step",
|
"previousStep": "Previous step",
|
||||||
"createButtonText": "Create machine",
|
"createButtonText": "Create machine",
|
||||||
"ready": "You're about to create {imageType} having {packageDescription}, named <b>{machineName}</b>, based on the <b>{imageDescription}</b>",
|
"ready": "You're about to create {imageType} having {packageDescription}, named <b>{machineName}</b>, based on the <b>{imageDescription}</b>",
|
||||||
@ -249,5 +247,16 @@
|
|||||||
"loadFailed": "Failed to retrieve the audit log for \"{machineName}\" machine"
|
"loadFailed": "Failed to retrieve the audit log for \"{machineName}\" machine"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"affinityRuleEditor":
|
||||||
|
{
|
||||||
|
"strict": "Place strictly",
|
||||||
|
"optional": "Place optionally",
|
||||||
|
"closeTo": "Close to",
|
||||||
|
"farFrom": "Far from",
|
||||||
|
"namedLike": "Machines named like",
|
||||||
|
"taggedWith": "Machines tagged with",
|
||||||
|
"valueHint": "Can be an exact string, simple *-glob, or regular expression to match against machine names or IDs",
|
||||||
|
"tagHint": "The exact tag's value"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ virtual-scroller
|
|||||||
{
|
{
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
&.instances .scrollable-content
|
&.machines .scrollable-content
|
||||||
{
|
{
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
width: auto;
|
width: auto;
|
||||||
@ -29,7 +29,7 @@ virtual-scroller
|
|||||||
|
|
||||||
@media (min-width: 576px)
|
@media (min-width: 576px)
|
||||||
{
|
{
|
||||||
virtual-scroller.instances .scrollable-content
|
virtual-scroller.machines .scrollable-content
|
||||||
{
|
{
|
||||||
--bs-gutter-x: 1.5rem;
|
--bs-gutter-x: 1.5rem;
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ virtual-scroller
|
|||||||
|
|
||||||
@media (max-width: 576px)
|
@media (max-width: 576px)
|
||||||
{
|
{
|
||||||
virtual-scroller.instances .scrollable-content
|
virtual-scroller.machines .scrollable-content
|
||||||
{
|
{
|
||||||
--bs-gutter-y: 1.5rem;
|
--bs-gutter-y: 1.5rem;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user