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\\networking"
|
||||
],
|
||||
"SelectedNode": "\\src\\app\\instances\\instances.component.html",
|
||||
"SelectedNode": "\\src\\app\\instances\\machines.component.html",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
@ -13,7 +13,7 @@ const appRoutes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'machines',
|
||||
loadChildren: () => import('./instances/instances.module').then(x => x.InstancesModule),
|
||||
loadChildren: () => import('./machines/machines.module').then(x => x.MachinesModule),
|
||||
canActivate: [AuthGuardService],
|
||||
canLoad: [AuthGuardService],
|
||||
},
|
||||
|
@ -14,7 +14,7 @@ import { SharedModule } from './shared.module';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
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 { NotFoundComponent } from './pages/not-found/not-found.component';
|
||||
import { NavMenuComponent } from './components/nav-menu/nav-menu.component';
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="content">
|
||||
<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">
|
||||
|
||||
|
@ -13,7 +13,7 @@ import { filter, takeUntil } from 'rxjs/operators';
|
||||
export class CustomImageEditorComponent implements OnInit
|
||||
{
|
||||
@Input()
|
||||
instance: any;
|
||||
machine: any;
|
||||
|
||||
save = new Subject<any>();
|
||||
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>
|
||||
{
|
||||
return this.httpClient.post<any>(`/api/my/images`,
|
||||
{
|
||||
machine: instanceId,
|
||||
machine: machineId,
|
||||
name,
|
||||
version,
|
||||
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,9 +102,8 @@ export class InlineEditorComponent implements OnInit, OnDestroy
|
||||
else
|
||||
this.saved.emit(this.editorForm.get('key').value);
|
||||
|
||||
this.editorForm.get('key').setValue(null);
|
||||
this.editorForm.get('value').setValue(null);
|
||||
}
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------
|
||||
cancelChanges()
|
||||
@ -113,6 +112,12 @@ export class InlineEditorComponent implements OnInit, OnDestroy
|
||||
|
||||
this.removeEventListeners();
|
||||
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------
|
||||
private resetForm()
|
||||
{
|
||||
this.editorForm.get('key').setValue(null);
|
||||
this.editorForm.get('value').setValue(null);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" [routerLink]="['./']" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }">
|
||||
<fa-icon [fixedWidth]="true" icon="home"></fa-icon>
|
||||
{{ 'navbar.menu.instances' | translate }}
|
||||
{{ 'navbar.menu.machines' | translate }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AlphaOnlyDirective } from './alpha-only.directive';
|
||||
|
||||
describe('AlphaOnlyDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
it('should create an machine', () => {
|
||||
const directive = new AlphaOnlyDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AutofocusDirective } from './autofocus.directive';
|
||||
|
||||
describe('AutofocusDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
it('should create an machine', () => {
|
||||
const directive = new AutofocusDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { LazyLoadDirective } from './lazy-load.directive';
|
||||
|
||||
describe('LazyLoadDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
it('should create an machine', () => {
|
||||
const directive = new LazyLoadDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
|
@ -15,11 +15,11 @@ export class HelpComponent implements OnInit
|
||||
contentUrl: './assets/help/account-info.html'
|
||||
},
|
||||
{
|
||||
title: 'Provisioning compute instance',
|
||||
title: 'Provisioning compute machine',
|
||||
contentUrl: ''
|
||||
},
|
||||
{
|
||||
title: 'Managing instances with Triton CLI',
|
||||
title: 'Managing machines with Triton CLI',
|
||||
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 { InstancesService } from './instances.service';
|
||||
import { MachinesService } from './machines.service';
|
||||
|
||||
describe('InstancesService', () => {
|
||||
let service: InstancesService;
|
||||
describe('MachinesService', () => {
|
||||
let service: MachinesService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(InstancesService);
|
||||
service = TestBed.inject(MachinesService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
@ -1,45 +1,45 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { forkJoin, from, Observable, Subject } from 'rxjs';
|
||||
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 { InstanceRequest } from '../models/instance';
|
||||
import { MachineRequest } from '../models/machine';
|
||||
import { Cacheable } from 'ts-cacheable';
|
||||
import { volumesCacheBuster$ } from '../../volumes/helpers/volumes.service';
|
||||
|
||||
const instancesCacheBuster$ = new Subject<void>();
|
||||
const machinesCacheBuster$ = new Subject<void>();
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class InstancesService
|
||||
export class MachinesService
|
||||
{
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly httpClient: HttpClient) { }
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
@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({
|
||||
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
|
||||
return this.httpClient.get<Instance>(`/api/my/machines/${instance.id}`)
|
||||
// Keep polling the machine until it reaches the expected state
|
||||
return this.httpClient.get<Machine>(`/api/my/machines/${machine.id}`)
|
||||
.pipe(
|
||||
tap(x => callbackFn && callbackFn(x)),
|
||||
repeatWhen(x =>
|
||||
@ -51,7 +51,7 @@ export class InstancesService
|
||||
map(y =>
|
||||
{
|
||||
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;
|
||||
}));
|
||||
@ -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();
|
||||
|
||||
// Keep polling the instance until it reaches the expected state
|
||||
return this.httpClient.get<Instance>(`/api/my/machines/${instance.id}`)
|
||||
// Keep polling the machine until it reaches the expected state
|
||||
return this.httpClient.get<Machine>(`/api/my/machines/${machine.id}`)
|
||||
.pipe(
|
||||
tap(instance => callbackFn && callbackFn(instance)),
|
||||
tap(machine => callbackFn && callbackFn(machine)),
|
||||
repeatWhen(x =>
|
||||
{
|
||||
let retries = 0;
|
||||
@ -79,7 +79,7 @@ export class InstancesService
|
||||
map(() =>
|
||||
{
|
||||
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)
|
||||
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(() =>
|
||||
{
|
||||
instancesCacheBuster$.next();
|
||||
machinesCacheBuster$.next();
|
||||
|
||||
if (instance.volumes?.length)
|
||||
if (machine.volumes?.length)
|
||||
volumesCacheBuster$.next();
|
||||
}));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
delete(instanceId: string): Observable<any>
|
||||
delete(machineId: string): Observable<any>
|
||||
{
|
||||
return this.httpClient.delete(`/api/my/machines/${instanceId}`)
|
||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
||||
return this.httpClient.delete(`/api/my/machines/${machineId}`)
|
||||
.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`, {})
|
||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
||||
return this.httpClient.post<Machine>(`/api/my/machines/${machineId}?action=start`, {})
|
||||
.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`, {})
|
||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
||||
return this.httpClient.post<Machine>(`/api/my/machines/${machineId}?action=stop`, {})
|
||||
.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`, {})
|
||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
||||
return this.httpClient.post<Machine>(`/api/my/machines/${machineId}?action=reboot`, {})
|
||||
.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}`, {})
|
||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
||||
return this.httpClient.post(`/api/my/machines/${machineId}?action=resize&package=${packageId}`, {})
|
||||
.pipe(tap(() => machinesCacheBuster$.next()));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
rename(instanceId: string, name: string): Observable<Instance>
|
||||
rename(machineId: string, name: string): Observable<Machine>
|
||||
{
|
||||
if (!name)
|
||||
throw 'Name cannot be empty';
|
||||
|
||||
return this.httpClient.post<Instance>(`/api/my/machines/${instanceId}?action=rename&name=${name}`, {})
|
||||
.pipe(tap(() => instancesCacheBuster$.next()));
|
||||
return this.httpClient.post<Machine>(`/api/my/machines/${machineId}?action=rename&name=${name}`, {})
|
||||
.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
|
||||
return this.httpClient.get(`/api/my/machines/${instanceId}/metadata`)
|
||||
return this.httpClient.get(`/api/my/machines/${machineId}/metadata`)
|
||||
.pipe(concatMap(existingMetadata =>
|
||||
{
|
||||
// Compute which metadata the user chose to remove
|
||||
const obsoleteMetadata: Observable<any>[] = [];
|
||||
for (const key of Object.keys(existingMetadata))
|
||||
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.
|
||||
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)
|
||||
{
|
||||
@ -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()
|
||||
{
|
||||
instancesCacheBuster$.next();
|
||||
machinesCacheBuster$.next();
|
||||
}
|
||||
}
|
||||
|
||||
export type InstanceCallbackFunction = ((instance: Instance) => void);
|
||||
export type MachineCallbackFunction = ((machine: Machine) => void);
|
@ -27,30 +27,30 @@ export class MigrationsService
|
||||
@Cacheable({
|
||||
cacheBusterObserver: cacheBuster$
|
||||
})
|
||||
getMigration(instanceId: string, migrationId: string): Observable<any>
|
||||
getMigration(machineId: string, migrationId: string): Observable<any>
|
||||
{
|
||||
return this.httpClient.get(`/api/my/migrations/${migrationId}`);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
migrate(instanceId: string): Observable<any>
|
||||
migrate(machineId: string): Observable<any>
|
||||
{
|
||||
// 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()));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
getMigrationProgress(instanceId: string): Observable<any>
|
||||
getMigrationProgress(machineId: string): Observable<any>
|
||||
{
|
||||
// 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()));
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
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 { Cacheable } from 'ts-cacheable';
|
||||
|
||||
@ -20,25 +20,25 @@ export class SnapshotsService
|
||||
@Cacheable({
|
||||
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({
|
||||
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
|
||||
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(
|
||||
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()));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
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()));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
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()));
|
||||
}
|
||||
}
|
@ -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 { Subject } from 'rxjs';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
import { Instance } from '../models/instance';
|
||||
import { InstancesService } from '../helpers/instances.service';
|
||||
import { Machine } from '../models/machine';
|
||||
import { MachinesService } from '../helpers/machines.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
|
||||
@Component({
|
||||
selector: 'app-instance-history',
|
||||
templateUrl: './instance-history.component.html',
|
||||
styleUrls: ['./instance-history.component.scss']
|
||||
selector: 'app-machine-history',
|
||||
templateUrl: './machine-history.component.html',
|
||||
styleUrls: ['./machine-history.component.scss']
|
||||
})
|
||||
export class InstanceHistoryComponent implements OnInit, OnDestroy
|
||||
export class MachineHistoryComponent implements OnInit, OnDestroy
|
||||
{
|
||||
@Input()
|
||||
instance: Instance;
|
||||
machine: Machine;
|
||||
|
||||
loading: boolean;
|
||||
history: any[];
|
||||
@ -25,7 +25,7 @@ export class InstanceHistoryComponent implements OnInit, OnDestroy
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly modalRef: BsModalRef,
|
||||
private readonly router: Router,
|
||||
private readonly instancesService: InstancesService,
|
||||
private readonly machinesService: MachinesService,
|
||||
private readonly toastr: ToastrService)
|
||||
{
|
||||
// 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.instancesService.getAudit(this.instance.id)
|
||||
this.machinesService.getAudit(this.machine.id)
|
||||
.subscribe(x =>
|
||||
{
|
||||
this.history = x;
|
@ -1,19 +1,19 @@
|
||||
<ul class="list-group list-group-flush list-info">
|
||||
<li class="dropdown-header">Machine identifier</li>
|
||||
<li class="list-group-item text-uppercase ps-0">
|
||||
<b>{{ instance.id }}</b>
|
||||
<b>{{ machine.id }}</b>
|
||||
</li>
|
||||
|
||||
<ng-container *ngIf="dnsCount">
|
||||
<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"
|
||||
*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">
|
||||
<!--<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] }}
|
||||
</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] }}
|
||||
</span>
|
||||
|
||||
@ -33,9 +33,9 @@
|
||||
<li class="dropdown-header">Deletion protection</li>
|
||||
<li class="list-group-item ps-0 pb-0 mt-2 ms-2">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input mt-0" type="checkbox" id="dp{{ instance.id }}" [(ngModel)]="instance.deletion_protection"
|
||||
(change)="toggleDeletionProtection($event, instance)">
|
||||
<label class="form-check-label" for="dp{{ instance.id }}">Prevent this machine from being deleted</label>
|
||||
<input class="form-check-input mt-0" type="checkbox" id="dp{{ machine.id }}" [(ngModel)]="machine.deletion_protection"
|
||||
(change)="toggleDeletionProtection($event, machine)">
|
||||
<label class="form-check-label" for="dp{{ machine.id }}">Prevent this machine from being deleted</label>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
@ -1,20 +1,20 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InstanceInfoComponent } from './instance-info.component';
|
||||
import { MachineInfoComponent } from './machine-info.component';
|
||||
|
||||
describe('InstanceInfoComponent', () => {
|
||||
let component: InstanceInfoComponent;
|
||||
let fixture: ComponentFixture<InstanceInfoComponent>;
|
||||
describe('MachineInfoComponent', () => {
|
||||
let component: MachineInfoComponent;
|
||||
let fixture: ComponentFixture<MachineInfoComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ InstanceInfoComponent ]
|
||||
declarations: [ MachineInfoComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InstanceInfoComponent);
|
||||
fixture = TestBed.createComponent(MachineInfoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -3,19 +3,19 @@ import { ToastrService } from 'ngx-toastr';
|
||||
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
||||
import { empty, forkJoin, Observable, of, Subject, ReplaySubject } from 'rxjs';
|
||||
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 { Instance } from '../models/instance';
|
||||
import { Machine } from '../models/machine';
|
||||
|
||||
@Component({
|
||||
selector: 'app-instance-info',
|
||||
templateUrl: './instance-info.component.html',
|
||||
styleUrls: ['./instance-info.component.scss']
|
||||
selector: 'app-machine-info',
|
||||
templateUrl: './machine-info.component.html',
|
||||
styleUrls: ['./machine-info.component.scss']
|
||||
})
|
||||
export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
export class MachineInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
{
|
||||
@Input()
|
||||
instance: Instance;
|
||||
machine: Machine;
|
||||
|
||||
@Input()
|
||||
loadInfo: boolean;
|
||||
@ -40,7 +40,7 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
private onChanges$ = new ReplaySubject();
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly instancesService: InstancesService,
|
||||
constructor(private readonly machinesService: MachinesService,
|
||||
private readonly catalogService: CatalogService,
|
||||
private readonly modalService: BsModalService,
|
||||
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.instancesService.toggleDeletionProtection(instance.id, event.target.checked)
|
||||
this.machinesService.toggleDeletionProtection(machine.id, event.target.checked)
|
||||
.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();
|
||||
},
|
||||
err =>
|
||||
{
|
||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
||||
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||
this.finishedProcessing.emit();
|
||||
});
|
||||
}
|
||||
@ -74,14 +74,14 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
private getInfo()
|
||||
{
|
||||
if (this.finishedLoading || this.instance.state === 'provisioning') return;
|
||||
if (this.finishedLoading || this.machine.state === 'provisioning') return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
if (this.refresh)
|
||||
this.instancesService.clearCache();
|
||||
this.machinesService.clearCache();
|
||||
|
||||
this.instancesService.getById(this.instance.id)
|
||||
this.machinesService.getById(this.machine.id)
|
||||
.subscribe(x =>
|
||||
{
|
||||
const dnsList = {};
|
||||
@ -90,7 +90,7 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
|
||||
this.dnsCount = Object.keys(dnsList).length;
|
||||
|
||||
this.instance.dnsList = dnsList;
|
||||
this.machine.dnsList = dnsList;
|
||||
|
||||
this.loading = false;
|
||||
this.finishedLoading = true;
|
||||
@ -99,7 +99,7 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
err =>
|
||||
{
|
||||
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;
|
||||
});
|
||||
}
|
||||
@ -127,7 +127,7 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
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();
|
||||
});
|
||||
}
|
@ -22,9 +22,9 @@
|
||||
</div>
|
||||
|
||||
<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"
|
||||
(change)="toggleCloudFirewall($event, instance)">
|
||||
<label class="form-check-label" for="fw{{ instance.id }}">Toggle cloud firewall</label>
|
||||
<input class="form-check-input mt-0" type="checkbox" id="fw{{ machine.id }}" [(ngModel)]="machine.firewall_enabled"
|
||||
(change)="toggleCloudFirewall($event, machine)">
|
||||
<label class="form-check-label" for="fw{{ machine.id }}">Toggle cloud firewall</label>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
|
||||
<div class="btn-group btn-group-sm" dropdown placement="bottom right" container="body"
|
||||
*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">
|
||||
<fa-icon icon="ellipsis-v" [fixedWidth]="true" size="sm"></fa-icon>
|
||||
</button>
|
@ -1,20 +1,20 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InstanceHistoryComponent } from './instance-history.component';
|
||||
import { MachineNetworksComponent } from './machine-networks.component';
|
||||
|
||||
describe('InstanceHistoryComponent', () => {
|
||||
let component: InstanceHistoryComponent;
|
||||
let fixture: ComponentFixture<InstanceHistoryComponent>;
|
||||
describe('MachineNetworksComponent', () => {
|
||||
let component: MachineNetworksComponent;
|
||||
let fixture: ComponentFixture<MachineNetworksComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ InstanceHistoryComponent ]
|
||||
declarations: [ MachineNetworksComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InstanceHistoryComponent);
|
||||
fixture = TestBed.createComponent(MachineNetworksComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -3,22 +3,22 @@ import { ToastrService } from 'ngx-toastr';
|
||||
import { empty, forkJoin, Observable, of, Subject, ReplaySubject } from 'rxjs';
|
||||
import { delay, filter, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
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 { BsModalService } from 'ngx-bootstrap/modal';
|
||||
import { Nic } from '../models/nic';
|
||||
import { Network } from '../../networking/models/network';
|
||||
import { Instance } from '../models/instance';
|
||||
import { Machine } from '../models/machine';
|
||||
|
||||
@Component({
|
||||
selector: 'app-instance-networks',
|
||||
templateUrl: './instance-networks.component.html',
|
||||
styleUrls: ['./instance-networks.component.scss']
|
||||
selector: 'app-machine-networks',
|
||||
templateUrl: './machine-networks.component.html',
|
||||
styleUrls: ['./machine-networks.component.scss']
|
||||
})
|
||||
export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
export class MachineNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
{
|
||||
@Input()
|
||||
instance: Instance;
|
||||
machine: Machine;
|
||||
|
||||
@Input()
|
||||
loadNetworks: boolean;
|
||||
@ -33,10 +33,10 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
load = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
instanceReboot = new EventEmitter();
|
||||
machineReboot = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
instanceStateUpdate = new EventEmitter();
|
||||
machineStateUpdate = new EventEmitter();
|
||||
|
||||
loading: boolean;
|
||||
nics: Nic[] = [];
|
||||
@ -50,7 +50,7 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly networkingService: NetworkingService,
|
||||
private readonly instancesService: InstancesService,
|
||||
private readonly machinesService: MachinesService,
|
||||
private readonly modalService: BsModalService,
|
||||
private readonly toastr: ToastrService)
|
||||
{
|
||||
@ -67,14 +67,14 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
}, err =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
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));
|
||||
|
||||
@ -124,19 +124,19 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
{
|
||||
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(() =>
|
||||
{
|
||||
return this.networkingService.addNic(this.instance.id, network.id)
|
||||
return this.networkingService.addNic(this.machine.id, network.id)
|
||||
.pipe(
|
||||
tap(x =>
|
||||
{
|
||||
// Add the newly created NIC to the list, in its "provisioning" state
|
||||
this.nics.unshift(x);
|
||||
|
||||
if (this.instance.state === 'running')
|
||||
this.instanceReboot.emit();
|
||||
if (this.machine.state === 'running')
|
||||
this.machineReboot.emit();
|
||||
}),
|
||||
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
|
||||
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(
|
||||
takeUntil(this.destroy$),
|
||||
map(y => ({ network: response.network, nic: y }))
|
||||
@ -171,7 +171,7 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
|
||||
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();
|
||||
},
|
||||
err =>
|
||||
@ -181,7 +181,7 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
this.nics.shift();
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
@ -209,31 +209,31 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
{
|
||||
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(() =>
|
||||
{
|
||||
return this.networkingService.deleteNic(this.instance.id, nic.mac)
|
||||
return this.networkingService.deleteNic(this.machine.id, nic.mac)
|
||||
.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
tap(() =>
|
||||
{
|
||||
if (this.instance.state === 'running')
|
||||
this.instanceReboot.emit();
|
||||
if (this.machine.state === 'running')
|
||||
this.machineReboot.emit();
|
||||
}),
|
||||
switchMap(() =>
|
||||
{
|
||||
// If the machine is currently running, keep polling until it finishes restarting
|
||||
return this.instance.state === 'running'
|
||||
? this.instancesService
|
||||
.getInstanceUntilNicRemoved(this.instance, nic.networkName, x => this.instanceStateUpdate.emit(x))
|
||||
return this.machine.state === 'running'
|
||||
? this.machinesService
|
||||
.getMachineUntilNicRemoved(this.machine, nic.networkName, x => this.machineStateUpdate.emit(x))
|
||||
.pipe(delay(1000), takeUntil(this.destroy$))
|
||||
: of(nic);
|
||||
})
|
||||
);
|
||||
}),
|
||||
switchMap(() => this.networkingService.getNics(this.instance.id))
|
||||
switchMap(() => this.networkingService.getNics(this.machine.id))
|
||||
).subscribe(nics =>
|
||||
{
|
||||
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.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 =>
|
||||
{
|
||||
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();
|
||||
});
|
||||
}
|
||||
@ -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(() =>
|
||||
{
|
||||
this.toastr.info(`The cloud firewall for machine "${instance.name}" is now ${event.target.checked ? 'enabled' : 'disabled'}`);
|
||||
instance.working = false;
|
||||
this.toastr.info(`The cloud firewall for machine "${machine.name}" is now ${event.target.checked ? 'enabled' : 'disabled'}`);
|
||||
machine.working = false;
|
||||
},
|
||||
err =>
|
||||
{
|
||||
instance.working = false;
|
||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
||||
machine.working = false;
|
||||
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
ngOnInit(): void
|
||||
{
|
||||
if (!this.instance.nics?.length || this.instance.networksLoaded)
|
||||
if (!this.machine.nics?.length || this.machine.networksLoaded)
|
||||
this.finishedLoading = true;
|
||||
|
||||
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();
|
||||
}
|
@ -7,9 +7,9 @@
|
||||
|
||||
<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">
|
||||
<input class="form-check-input pe-4" type="checkbox" id="{{ instance.id }}-role-{{ role.id }}" [(ngModel)]="role.selected"
|
||||
(change)="setInstanceRole($event, role)">
|
||||
<label class="form-check-label ms-2 text-truncate" for="{{ instance.id }}-role-{{ role.id }}">
|
||||
<input class="form-check-input pe-4" type="checkbox" id="{{ machine.id }}-role-{{ role.id }}" [(ngModel)]="role.selected"
|
||||
(change)="setMachineRole($event, role)">
|
||||
<label class="form-check-label ms-2 text-truncate" for="{{ machine.id }}-role-{{ role.id }}">
|
||||
<b>{{ role.name }}</b>
|
||||
<span class="small ps-1" *ngFor="let policy of role.policies">{{ policy.name }}</span>
|
||||
</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 { ToastrService } from 'ngx-toastr';
|
||||
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
||||
import { InstancesService } from '../helpers/instances.service';
|
||||
import { MachinesService } from '../helpers/machines.service';
|
||||
import { Subject } from 'rxjs';
|
||||
import { delay, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import Fuse from 'fuse.js';
|
||||
import { SecurityService } from '../../security/helpers/security.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-instance-security',
|
||||
templateUrl: './instance-security.component.html',
|
||||
styleUrls: ['./instance-security.component.scss']
|
||||
selector: 'app-machine-security',
|
||||
templateUrl: './machine-security.component.html',
|
||||
styleUrls: ['./machine-security.component.scss']
|
||||
})
|
||||
export class InstanceSecurityComponent implements OnInit, OnDestroy
|
||||
export class MachineSecurityComponent implements OnInit, OnDestroy
|
||||
{
|
||||
@Input()
|
||||
instance: any;
|
||||
machine: any;
|
||||
|
||||
@Input()
|
||||
set loadRoles(value: boolean)
|
||||
{
|
||||
if (value && this.instance && !this.roles)
|
||||
if (value && this.machine && !this.roles)
|
||||
this.getRoles();
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ export class InstanceSecurityComponent implements OnInit, OnDestroy
|
||||
private readonly fuseJsOptions: {};
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly instancesService: InstancesService,
|
||||
constructor(private readonly machinesService: MachinesService,
|
||||
private readonly securityService: SecurityService,
|
||||
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
|
||||
{
|
||||
// TODO: Find a way to retrieve the list of RoleTags
|
||||
//this.instancesService.getRoleTags(this.instance.id)
|
||||
//this.machinesService.getRoleTags(this.machine.id)
|
||||
// .subscribe();
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InstanceSecurityComponent } from './instance-security.component';
|
||||
import { MachineSnapshotsComponent } from './machine-snapshots.component';
|
||||
|
||||
describe('InstanceSecurityComponent', () => {
|
||||
let component: InstanceSecurityComponent;
|
||||
let fixture: ComponentFixture<InstanceSecurityComponent>;
|
||||
describe('MachineSnapshotsComponent', () => {
|
||||
let component: MachineSnapshotsComponent;
|
||||
let fixture: ComponentFixture<MachineSnapshotsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ InstanceSecurityComponent ]
|
||||
declarations: [ MachineSnapshotsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InstanceSecurityComponent);
|
||||
fixture = TestBed.createComponent(MachineSnapshotsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, OnDestroy, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { InstancesService } from '../helpers/instances.service';
|
||||
import { MachinesService } from '../helpers/machines.service';
|
||||
import { ReplaySubject, Subject } from 'rxjs';
|
||||
import { delay, first, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import Fuse from 'fuse.js';
|
||||
@ -10,14 +10,14 @@ import { Snapshot } from '../models/snapshot';
|
||||
import { SnapshotsService } from '../helpers/snapshots.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-instance-snapshots',
|
||||
templateUrl: './instance-snapshots.component.html',
|
||||
styleUrls: ['./instance-snapshots.component.scss']
|
||||
selector: 'app-machine-snapshots',
|
||||
templateUrl: './machine-snapshots.component.html',
|
||||
styleUrls: ['./machine-snapshots.component.scss']
|
||||
})
|
||||
export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
export class MachineSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
{
|
||||
@Input()
|
||||
instance: any;
|
||||
machine: any;
|
||||
|
||||
@Input()
|
||||
loadSnapshots: boolean;
|
||||
@ -32,7 +32,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
load = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
instanceStateUpdate = new EventEmitter();
|
||||
machineStateUpdate = new EventEmitter();
|
||||
|
||||
loadingSnapshots: boolean;
|
||||
snapshotsLoaded: boolean;
|
||||
@ -48,7 +48,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
private readonly fuseJsOptions: {};
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly instancesService: InstancesService,
|
||||
constructor(private readonly machinesService: MachinesService,
|
||||
private readonly snapshotsService: SnapshotsService,
|
||||
private readonly modalService: BsModalService,
|
||||
private readonly toastr: ToastrService)
|
||||
@ -79,12 +79,12 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
// Clear this field
|
||||
this.snapshotName = null;
|
||||
|
||||
this.snapshotsService.createSnapshot(this.instance.id, snapshotName)
|
||||
this.snapshotsService.createSnapshot(this.machine.id, snapshotName)
|
||||
.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
delay(1000),
|
||||
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$))
|
||||
)
|
||||
)
|
||||
@ -96,7 +96,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
this.snapshots[index] = x;
|
||||
|
||||
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 =>
|
||||
{
|
||||
@ -107,7 +107,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
this.snapshots.splice(index, 1);
|
||||
|
||||
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;
|
||||
|
||||
// First we need to make sure the instance is stopped
|
||||
if (this.instance.state !== 'stopped')
|
||||
this.instancesService.stop(this.instance.id)
|
||||
// First we need to make sure the machine is stopped
|
||||
if (this.machine.state !== 'stopped')
|
||||
this.machinesService.stop(this.machine.id)
|
||||
.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
tap(() => this.toastr.info(`Restarting machine "${this.instance.name}"`)),
|
||||
tap(() => this.toastr.info(`Restarting machine "${this.machine.name}"`)),
|
||||
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$))
|
||||
)
|
||||
).subscribe(() =>
|
||||
@ -141,7 +141,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
snapshot.working = false;
|
||||
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
|
||||
this.startMachineFromSnapshot(snapshot);
|
||||
@ -172,13 +172,13 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
{
|
||||
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(
|
||||
takeUntil(this.destroy$),
|
||||
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$))
|
||||
)
|
||||
)
|
||||
@ -188,14 +188,14 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
|
||||
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 =>
|
||||
{
|
||||
snapshot.working = false;
|
||||
|
||||
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.snapshotsService.deleteSnapshot(this.instance.id, snapshot.name)
|
||||
this.snapshotsService.deleteSnapshot(this.machine.id, snapshot.name)
|
||||
.subscribe(() =>
|
||||
{
|
||||
const index = this.snapshots.findIndex(s => s.name === snapshot.name);
|
||||
@ -242,12 +242,12 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
private getSnapshots()
|
||||
{
|
||||
if (this.snapshotsLoaded || this.instance.state === 'provisioning') return
|
||||
if (this.snapshotsLoaded || this.machine.state === 'provisioning') return
|
||||
|
||||
this.loadingSnapshots = true;
|
||||
|
||||
// Get the list of snapshots
|
||||
this.snapshotsService.getSnapshots(this.instance.id)
|
||||
this.snapshotsService.getSnapshots(this.machine.id)
|
||||
.subscribe(x =>
|
||||
{
|
||||
this.snapshots = x;
|
||||
@ -297,12 +297,12 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
ngOnInit(): void
|
||||
{
|
||||
this.snapshots = this.instance?.snapshots;
|
||||
this.snapshots = this.machine?.snapshots;
|
||||
this.filteredSnapshots = this.snapshots;
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InstanceNetworksComponent } from './instance-networks.component';
|
||||
import { MachineTagEditorComponent } from './machine-tag-editor.component';
|
||||
|
||||
describe('InstanceNetworksComponent', () => {
|
||||
let component: InstanceNetworksComponent;
|
||||
let fixture: ComponentFixture<InstanceNetworksComponent>;
|
||||
describe('MachineTagEditorComponent', () => {
|
||||
let component: MachineTagEditorComponent;
|
||||
let fixture: ComponentFixture<MachineTagEditorComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ InstanceNetworksComponent ]
|
||||
declarations: [ MachineTagEditorComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InstanceNetworksComponent);
|
||||
fixture = TestBed.createComponent(MachineTagEditorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -4,19 +4,19 @@ import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '
|
||||
import { NavigationStart, Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
import { InstancesService } from '../helpers/instances.service';
|
||||
import { MachinesService } from '../helpers/machines.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { Instance } from '../models/instance';
|
||||
import { Machine } from '../models/machine';
|
||||
|
||||
@Component({
|
||||
selector: 'app-instance-tag-editor',
|
||||
templateUrl: './instance-tag-editor.component.html',
|
||||
styleUrls: ['./instance-tag-editor.component.scss']
|
||||
selector: 'app-machine-tag-editor',
|
||||
templateUrl: './machine-tag-editor.component.html',
|
||||
styleUrls: ['./machine-tag-editor.component.scss']
|
||||
})
|
||||
export class InstanceTagEditorComponent implements OnInit
|
||||
export class MachineTagEditorComponent implements OnInit
|
||||
{
|
||||
@Input()
|
||||
instance: Instance;
|
||||
machine: Machine;
|
||||
|
||||
@Input()
|
||||
showMetadata: boolean;
|
||||
@ -30,7 +30,7 @@ export class InstanceTagEditorComponent implements OnInit
|
||||
private destroy$ = new Subject();
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly instancesService: InstancesService,
|
||||
constructor(private readonly machinesService: MachinesService,
|
||||
private readonly modalRef: BsModalRef,
|
||||
private readonly router: Router,
|
||||
private readonly fb: FormBuilder,
|
||||
@ -49,13 +49,13 @@ export class InstanceTagEditorComponent implements OnInit
|
||||
private createForm()
|
||||
{
|
||||
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],
|
||||
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],
|
||||
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
|
||||
? this.instancesService.replaceMetadata(this.instance.id, items)
|
||||
: this.instancesService.replaceTags(this.instance.id, items);
|
||||
? this.machinesService.replaceMetadata(this.machine.id, items)
|
||||
: this.machinesService.replaceTags(this.machine.id, items);
|
||||
|
||||
observable.subscribe(response =>
|
||||
{
|
||||
@ -131,7 +131,7 @@ export class InstanceTagEditorComponent implements OnInit
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
ngOnInit(): void
|
||||
{
|
||||
//this.instancesService.getTags(this.instance.id).subscribe();
|
||||
//this.machinesService.getTags(this.machine.id).subscribe();
|
||||
|
||||
this.createForm();
|
||||
}
|
@ -192,7 +192,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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"
|
||||
(click)="showAffinity = !showAffinity">
|
||||
Affinity rules
|
||||
@ -200,32 +200,35 @@
|
||||
</button>
|
||||
|
||||
<div [collapse]="!showAffinity">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<select class="form-select" name="operator">
|
||||
<option></option>
|
||||
<option value="==">Must be close to instances</option>
|
||||
<option value="==~">Should be close to instances</option>
|
||||
<option value="!=">Must be far from instances</option>
|
||||
<option value="!=~">Should be far from instances</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-select" name="target">
|
||||
<option></option>
|
||||
<option value="instance">Named like</option>
|
||||
<option value="tagName">Tagged with</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" class="form-control" placeholder="Value">
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<button class="btn btn-outline-info">
|
||||
<fa-icon icon="plus"></fa-icon>
|
||||
</button>
|
||||
<div class="select-list list-group select-list p-0 mb-2 py-2" tabindex="0">
|
||||
<div class="list-group-item list-group-item-action" *ngFor="let affinityRule of editorForm.get('affinityRules')['controls']; let index = index">
|
||||
<div class="d-flex">
|
||||
<span class="flex-grow-1 text-truncate">
|
||||
<span class="me-1">
|
||||
{{ (affinityRule.value.description.strict ? 'affinityRuleEditor.strict' : 'affinityRuleEditor.optional') | translate }}
|
||||
</span>
|
||||
<span class="me-1 text-lowercase" [ngClass]="affinityRule.value.description.closeTo ? 'text-success' : 'text-danger'">
|
||||
{{ (affinityRule.value.description.closeTo ? 'affinityRuleEditor.closeTo' : 'affinityRuleEditor.farFrom') | translate }}
|
||||
</span>
|
||||
<span class="me-1 text-lowercase">
|
||||
{{ (affinityRule.value.description.targetMachine ? 'affinityRuleEditor.namedLike' : 'affinityRuleEditor.taggedWith') | translate }}
|
||||
</span>
|
||||
<span class="me-1 text-warning" *ngIf="affinityRule.value.description.tagName">
|
||||
{{ affinityRule.value.description.tagName }}={{ affinityRule.value.description.value }}
|
||||
</span>
|
||||
<span class="me-1 text-warning" *ngIf="!affinityRule.value.description.tagName">
|
||||
{{ affinityRule.value.description.value }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<button class="btn btn-sm text-danger p-0" (click)="removeAffinityRule(index)"
|
||||
tooltip="Remove this affinity rule" container="body" placement="top" [adaptivePosition]="false">
|
||||
<fa-icon [fixedWidth]="true" icon="times"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<app-affinity-rule-editor (saved)="addAffinityRule($event)"></app-affinity-rule-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,20 +1,20 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InstanceWizardComponent } from './instance-wizard.component';
|
||||
import { MachineWizardComponent } from './machine-wizard.component';
|
||||
|
||||
describe('InstanceWizardComponent', () => {
|
||||
let component: InstanceWizardComponent;
|
||||
let fixture: ComponentFixture<InstanceWizardComponent>;
|
||||
describe('MachineWizardComponent', () => {
|
||||
let component: MachineWizardComponent;
|
||||
let fixture: ComponentFixture<MachineWizardComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ InstanceWizardComponent ]
|
||||
declarations: [ MachineWizardComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InstanceWizardComponent);
|
||||
fixture = TestBed.createComponent(MachineWizardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -2,11 +2,11 @@ import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angu
|
||||
import { BsModalRef } from 'ngx-bootstrap/modal';
|
||||
import { combineLatest, forkJoin, Subject } from 'rxjs';
|
||||
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 { NavigationStart, Router } from '@angular/router';
|
||||
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 { NetworkingService } from '../../networking/helpers/networking.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
@ -16,11 +16,11 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { CatalogImageType } from '../../catalog/models/image';
|
||||
|
||||
@Component({
|
||||
selector: 'app-instance-wizard',
|
||||
templateUrl: './instance-wizard.component.html',
|
||||
styleUrls: ['./instance-wizard.component.scss']
|
||||
selector: 'app-machine-wizard',
|
||||
templateUrl: './machine-wizard.component.html',
|
||||
styleUrls: ['./machine-wizard.component.scss']
|
||||
})
|
||||
export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
export class MachineWizardComponent implements OnInit, OnDestroy
|
||||
{
|
||||
@Input()
|
||||
add: boolean;
|
||||
@ -29,7 +29,7 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
imageType = 1;
|
||||
|
||||
@Input()
|
||||
instance: Instance;
|
||||
machine: Machine;
|
||||
|
||||
private images: any[];
|
||||
imageList: any[];
|
||||
@ -39,12 +39,12 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
packageList: any[];
|
||||
packageGroups: any[];
|
||||
|
||||
instances: Instance[];
|
||||
machines: Machine[];
|
||||
dataCenters: any[];
|
||||
|
||||
loadingIndicator: boolean;
|
||||
loadingPackages: boolean;
|
||||
save = new Subject<Instance>();
|
||||
save = new Subject<Machine>();
|
||||
working: boolean;
|
||||
editorForm: FormGroup;
|
||||
currentStep = 1;
|
||||
@ -63,7 +63,7 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
private readonly fb: FormBuilder,
|
||||
private readonly fileSizePipe: FileSizePipe,
|
||||
private readonly authService: AuthService,
|
||||
private readonly instancesService: InstancesService,
|
||||
private readonly machinesService: MachinesService,
|
||||
private readonly catalogService: CatalogService,
|
||||
private readonly networkingService: NetworkingService,
|
||||
private readonly volumesService: VolumesService,
|
||||
@ -109,15 +109,15 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
private createForm()
|
||||
{
|
||||
const tags = this.fb.array(this.instance
|
||||
? Object.keys(this.instance.tags)
|
||||
.map(key => this.fb.group({ key, value: this.instance.tags[key] }))
|
||||
const tags = this.fb.array(this.machine
|
||||
? Object.keys(this.machine.tags)
|
||||
.map(key => this.fb.group({ key, value: this.machine.tags[key] }))
|
||||
: []);
|
||||
|
||||
const metadata = this.fb.array(this.instance
|
||||
? Object.keys(this.instance.metadata)
|
||||
const metadata = this.fb.array(this.machine
|
||||
? Object.keys(this.machine.metadata)
|
||||
.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(
|
||||
@ -129,14 +129,9 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
name: [null, Validators.required],
|
||||
networks: this.fb.array([], { validators: this.atLeastOneSelectionValidator.bind(this) }),
|
||||
firewallRules: this.fb.array([]),
|
||||
cloudFirewall: [this.instance?.firewall_enabled],
|
||||
cloudFirewall: [this.machine?.firewall_enabled],
|
||||
volumes: this.fb.array([]),
|
||||
affinity: this.fb.group(
|
||||
{
|
||||
strict: [{ value: false, disabled: true }],
|
||||
closeTo: [],
|
||||
farFrom: []
|
||||
}),
|
||||
affinityRules: this.fb.array([]),
|
||||
dataCenter: [],
|
||||
tags,
|
||||
metadata,
|
||||
@ -281,14 +276,6 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
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
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(this.computeEstimatedCost.bind(this));
|
||||
@ -325,15 +312,6 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
return null;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
private setAffinity(affinity)
|
||||
{
|
||||
if (affinity)
|
||||
this.editorForm.get(['affinity', 'strict']).enable();
|
||||
else
|
||||
this.editorForm.get(['affinity', 'strict']).disable();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
close()
|
||||
{
|
||||
@ -347,22 +325,22 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
|
||||
const changes = this.editorForm.getRawValue();
|
||||
|
||||
const instance: any = {};
|
||||
instance.name = changes.name;
|
||||
instance.image = changes.image.id;
|
||||
instance.package = changes.package.id;
|
||||
//instance.brand = changes.package.brand;
|
||||
instance.networks = changes.networks.filter(x => x.selected).map(x => x.id);
|
||||
instance.firewall_enabled = !!changes.cloudFirewall;
|
||||
const machine: any = {};
|
||||
machine.name = changes.name;
|
||||
machine.image = changes.image.id;
|
||||
machine.package = changes.package.id;
|
||||
//machine.brand = changes.package.brand;
|
||||
machine.networks = changes.networks.filter(x => x.selected).map(x => x.id);
|
||||
machine.firewall_enabled = !!changes.cloudFirewall;
|
||||
|
||||
for (const tag of changes.tags)
|
||||
instance[`tag.${tag.key}`] = tag.value;
|
||||
machine[`tag.${tag.key}`] = tag.value;
|
||||
|
||||
for (const metadata of changes.metadata)
|
||||
instance[`metadata.${metadata.key}`] = metadata.value;
|
||||
machine[`metadata.${metadata.key}`] = metadata.value;
|
||||
|
||||
if (!this.kvmRequired)
|
||||
instance.volumes = changes.volumes
|
||||
machine.volumes = changes.volumes
|
||||
.filter(x => x.mount)
|
||||
.map(volume =>
|
||||
({
|
||||
@ -372,9 +350,9 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
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 =>
|
||||
{
|
||||
this.working = false;
|
||||
@ -406,10 +384,10 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
|
||||
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
|
||||
? this.translateService.instant('dashboard.wizard.readyImageTypeContainer')
|
||||
: this.translateService.instant('dashboard.wizard.readyImageTypeVm'),
|
||||
? this.translateService.instant('machines.wizard.readyImageTypeContainer')
|
||||
: this.translateService.instant('machines.wizard.readyImageTypeVm'),
|
||||
packageDescription: this.editorForm.get('package').value.description ||
|
||||
`<b>${this.editorForm.get('package').value.vcpus || 1}</b> vCPUs, ` +
|
||||
`<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);
|
||||
|
||||
if (this.instance)
|
||||
if (this.machine)
|
||||
this.nextStep();
|
||||
}
|
||||
|
||||
@ -469,6 +447,25 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
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()
|
||||
{
|
||||
@ -482,9 +479,9 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
// Set the default image type (this will trigger a series of events)
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
forkJoin(
|
||||
this.instancesService.get(),
|
||||
forkJoin([
|
||||
this.machinesService.get(),
|
||||
this.catalogService.getDataCenters()
|
||||
)
|
||||
])
|
||||
.subscribe(response =>
|
||||
{
|
||||
this.instances = response[0];
|
||||
this.machines = response[0];
|
||||
|
||||
this.dataCenters = Object.keys(response[1]);
|
||||
|
||||
@ -596,18 +593,18 @@ export class InstanceWizardComponent implements OnInit, OnDestroy
|
||||
|
||||
this.getNetworksAndFirewallRules();
|
||||
|
||||
this.getInstancesAndDataCenters();
|
||||
this.getMachinesAndDataCenters();
|
||||
|
||||
this.getVolumes();
|
||||
|
||||
if (this.instance)
|
||||
if (this.machine)
|
||||
{
|
||||
if (this.instance.type === 'smartmachine')
|
||||
if (this.machine.type === 'smartmachine')
|
||||
this.imageType = 1;
|
||||
else if (this.instance.type === 'virtualmachine')
|
||||
else if (this.machine.type === 'virtualmachine')
|
||||
this.imageType = 2;
|
||||
|
||||
this.preselectedPackage = this.instance.package;
|
||||
this.preselectedPackage = this.machine.package;
|
||||
|
||||
this.nextStep();
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
|
||||
<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">
|
||||
<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"
|
||||
@ -24,16 +24,16 @@
|
||||
<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"
|
||||
[outsideClick]="true" container="body" placement="bottom right" containerClass="menu-popover">
|
||||
Showing {{ listItems.length }} / {{ instances.length }}
|
||||
<ng-container *ngIf="runningInstanceCount && stoppedInstanceCount">
|
||||
<span class="badge rounded-pill bg-success text-dark">{{ runningInstanceCount }} running</span>
|
||||
<span class="badge rounded-pill bg-danger text-dark ms-1">{{ stoppedInstanceCount }} stopped</span>
|
||||
Showing {{ listItems.length }} / {{ machines.length }}
|
||||
<ng-container *ngIf="runningMachineCount && stoppedMachineCount">
|
||||
<span class="badge rounded-pill bg-success text-dark">{{ runningMachineCount }} running</span>
|
||||
<span class="badge rounded-pill bg-danger text-dark ms-1">{{ stoppedMachineCount }} stopped</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="runningInstanceCount && !stoppedInstanceCount">
|
||||
<span class="badge rounded-pill bg-success text-dark">{{ runningInstanceCount }} running</span>
|
||||
<ng-container *ngIf="runningMachineCount && !stoppedMachineCount">
|
||||
<span class="badge rounded-pill bg-success text-dark">{{ runningMachineCount }} running</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!runningInstanceCount && stoppedInstanceCount">
|
||||
<span class="badge rounded-pill bg-danger text-dark">{{ stoppedInstanceCount }} stopped</span>
|
||||
<ng-container *ngIf="!runningMachineCount && stoppedMachineCount">
|
||||
<span class="badge rounded-pill bg-danger text-dark">{{ stoppedMachineCount }} stopped</span>
|
||||
</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
@ -85,61 +85,61 @@
|
||||
|
||||
<div class="overflow-auto flex-grow-1 mt-3 d-flex flex-column" id="scrollingBlock">
|
||||
<div class="container flex-grow-1 py-2">
|
||||
<h2 *ngIf="listItems && listItems.length === 0 && instances && instances.length > 0" class="text-uppercase">
|
||||
{{ 'dashboard.list.noResults' | translate }}
|
||||
<h2 *ngIf="listItems && listItems.length === 0 && machines && machines.length > 0" class="text-uppercase">
|
||||
{{ 'machines.list.noResults' | translate }}
|
||||
</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">
|
||||
<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'"
|
||||
[class.col-lg-6]="showMachineDetails && editorForm.get('fullDetailsTwoColumns').value" lazyLoad [lazyLoadDelay]="lazyLoadDelay"
|
||||
[container]="scroller.element.nativeElement.getElementsByClassName('scrollable-content')[0]"
|
||||
(canLoad)="instance.loading = false" (unload)="instance.loading = true"
|
||||
(load)="loadInstanceDetails(instance)">
|
||||
<fieldset class="card" [disabled]="instance.working">
|
||||
(canLoad)="machine.loading = false" (unload)="machine.loading = true"
|
||||
(load)="loadMachineDetails(machine)">
|
||||
<fieldset class="card" [disabled]="machine.working">
|
||||
<div class="row g-0">
|
||||
<div class="card-info" [ngClass]="showMachineDetails ? 'col-lg-4' : 'col'">
|
||||
<div>
|
||||
<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">
|
||||
{{ instance.name }}
|
||||
{{ machine.name }}
|
||||
</h5>
|
||||
|
||||
<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()">
|
||||
<fa-icon icon="expand-alt" [fixedWidth]="true" size="sm"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!instance.loading && instance.imageDetails"
|
||||
class="text-truncate small text-info text-faded mb-1" [tooltip]="instance.imageDetails.description"
|
||||
<div *ngIf="!machine.loading && machine.imageDetails"
|
||||
class="text-truncate small text-info text-faded mb-1" [tooltip]="machine.imageDetails.description"
|
||||
container="body" placement="top left" [adaptivePosition]="false">
|
||||
{{ instance.imageDetails.name }}, v{{ instance.imageDetails.version }}
|
||||
{{ machine.imageDetails.name }}, v{{ machine.imageDetails.version }}
|
||||
</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"
|
||||
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">
|
||||
<fa-icon icon="microchip"></fa-icon>
|
||||
{{ instance.memory * 1024 * 1024 | fileSize }}
|
||||
{{ machine.memory * 1024 * 1024 | fileSize }}
|
||||
</span>
|
||||
<span>
|
||||
<fa-icon icon="server"></fa-icon>
|
||||
|
||||
{{ instance.disk * 1024 * 1024 | fileSize }}
|
||||
{{ machine.disk * 1024 * 1024 | fileSize }}
|
||||
</span>
|
||||
</button>
|
||||
</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">
|
||||
<span class="visually-hidden">Working...</span>
|
||||
</div>
|
||||
@ -147,35 +147,35 @@
|
||||
|
||||
<div>
|
||||
<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>
|
||||
<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 *ngIf="instance.type === 'virtualmachine'">
|
||||
<ng-container *ngIf="machine.type === 'virtualmachine'">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-nowrap justify-content-between align-items-center">
|
||||
<button class="badge text-uppercase" [disabled]="instance.state !== 'running' && instance.state !== 'stopped'"
|
||||
[class.bg-light]="instance.state !== 'running' && instance.state !== 'stopped'"
|
||||
[class.bg-danger]="instance.state === 'stopped'" [class.bg-success]="instance.state === 'running'"
|
||||
(click)="showMachineHistory(instance)" tooltip="{{ 'dashboard.listItem.history' | translate }}"
|
||||
<button class="badge text-uppercase" [disabled]="machine.state !== 'running' && machine.state !== 'stopped'"
|
||||
[class.bg-light]="machine.state !== 'running' && machine.state !== 'stopped'"
|
||||
[class.bg-danger]="machine.state === 'stopped'" [class.bg-success]="machine.state === 'running'"
|
||||
(click)="showMachineHistory(machine)" tooltip="{{ 'machines.listItem.history' | translate }}"
|
||||
container="body" placement="top" [adaptivePosition]="false">
|
||||
<fa-icon icon="history" [fixedWidth]="true"></fa-icon>
|
||||
{{ instance.state }}
|
||||
{{ machine.state }}
|
||||
</button>
|
||||
|
||||
<div class="btn-group btn-group-sm" dropdown placement="bottom right" *ngIf="!instance.loading">
|
||||
<button class="btn btn-link text-success" (click)="startMachine(instance)"
|
||||
*ngIf="instance.state === 'stopped'">
|
||||
<div class="btn-group btn-group-sm" dropdown placement="bottom right" *ngIf="!machine.loading">
|
||||
<button class="btn btn-link text-success" (click)="startMachine(machine)"
|
||||
*ngIf="machine.state === 'stopped'">
|
||||
<fa-icon icon="power-off" [fixedWidth]="true" size="sm" tooltip="Start this machine" container="body"
|
||||
placement="top" [adaptivePosition]="false"></fa-icon>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-link text-info" [popover]="instanceContextMenu" container="body"
|
||||
[popoverContext]="{ instance: instance }" placement="bottom left" containerClass="menu-dropdown"
|
||||
<button class="btn btn-link text-info" [popover]="machineContextMenu" container="body"
|
||||
[popoverContext]="{ machine: machine }" placement="bottom left" containerClass="menu-dropdown"
|
||||
[outsideClick]="true">
|
||||
<fa-icon icon="ellipsis-v" [fixedWidth]="true" size="sm"></fa-icon>
|
||||
</button>
|
||||
@ -186,51 +186,51 @@
|
||||
|
||||
<div class="col mt-sm-0 mt-3 no-overflow-sm" *ngIf="showMachineDetails">
|
||||
<div class="card-header p-0 h-100">
|
||||
<tabset class="dashboard-tabs" *ngIf="!instance.loading">
|
||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||
id="{{ instance.id }}-info">
|
||||
<tabset class="dashboard-tabs" *ngIf="!machine.loading">
|
||||
<tab customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||
id="{{ machine.id }}-info">
|
||||
<ng-template tabHeading>
|
||||
<fa-icon icon="info-circle" class="d-sm-none"></fa-icon>
|
||||
<span class="d-none d-sm-inline-block ms-1">Info</span>
|
||||
</ng-template>
|
||||
<div class="card-body p-2 h-100">
|
||||
<app-instance-info [instance]="instance" [loadInfo]="instance.shouldLoadInfo" [refresh]="instance.refreshInfo"
|
||||
(load)="setInstanceInfo(instance, $event)" (processing)="instance.working = true"
|
||||
(finishedProcessing)="instance.working = false">
|
||||
</app-instance-info>
|
||||
<app-machine-info [machine]="machine" [loadInfo]="machine.shouldLoadInfo" [refresh]="machine.refreshInfo"
|
||||
(load)="setMachineInfo(machine, $event)" (processing)="machine.working = true"
|
||||
(finishedProcessing)="machine.working = false">
|
||||
</app-machine-info>
|
||||
</div>
|
||||
</tab>
|
||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||
id="{{ instance.id }}-networks">
|
||||
<tab customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||
id="{{ machine.id }}-networks">
|
||||
<ng-template tabHeading>
|
||||
<fa-icon icon="network-wired" class="d-sm-none"></fa-icon>
|
||||
<span class="d-none d-sm-inline-block ms-1">Network</span>
|
||||
</ng-template>
|
||||
<div class="card-body p-2 h-100">
|
||||
<app-instance-networks [instance]="instance" [loadNetworks]="instance.shouldLoadNetworks"
|
||||
(load)="setInstanceNetworks(instance, $event)" (processing)="instance.working = true"
|
||||
(finishedProcessing)="refreshInstanceDnsList(instance)"
|
||||
(instanceReboot)="watchInstanceState(instance)"
|
||||
(instanceStateUpdate)="updateInstance(instance, $event)">
|
||||
</app-instance-networks>
|
||||
<app-machine-networks [machine]="machine" [loadNetworks]="machine.shouldLoadNetworks"
|
||||
(load)="setMachineNetworks(machine, $event)" (processing)="machine.working = true"
|
||||
(finishedProcessing)="refreshMachineDnsList(machine)"
|
||||
(machineReboot)="watchMachineState(machine)"
|
||||
(machineStateUpdate)="updateMachine(machine, $event)">
|
||||
</app-machine-networks>
|
||||
</div>
|
||||
</tab>
|
||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||
id="{{ instance.id }}-snapshots" *ngIf="instance.brand !== 'kvm'">
|
||||
<tab customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||
id="{{ machine.id }}-snapshots" *ngIf="machine.brand !== 'kvm'">
|
||||
<ng-template tabHeading>
|
||||
<fa-icon icon="history" class="d-sm-none"></fa-icon>
|
||||
<span class="d-none d-sm-inline-block ms-1">Snapshots</span>
|
||||
</ng-template>
|
||||
<div class="card-body p-2 h-100">
|
||||
<app-instance-snapshots [instance]="instance" [loadSnapshots]="instance.shouldLoadSnapshots"
|
||||
(load)="setInstanceSnapshots(instance, $event)" (processing)="instance.working = true"
|
||||
(finishedProcessing)="instance.working = false"
|
||||
(instanceStateUpdate)="updateInstance(instance, $event)">
|
||||
</app-instance-snapshots>
|
||||
<app-machine-snapshots [machine]="machine" [loadSnapshots]="machine.shouldLoadSnapshots"
|
||||
(load)="setMachineSnapshots(machine, $event)" (processing)="machine.working = true"
|
||||
(finishedProcessing)="machine.working = false"
|
||||
(machineStateUpdate)="updateMachine(machine, $event)">
|
||||
</app-machine-snapshots>
|
||||
</div>
|
||||
</tab>
|
||||
<tab *ngIf="false" customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||
id="{{ instance.id }}-migrations">
|
||||
<tab *ngIf="false" customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||
id="{{ machine.id }}-migrations">
|
||||
<ng-template tabHeading>
|
||||
<fa-icon icon="coins" class="d-sm-none"></fa-icon>
|
||||
<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>
|
||||
</div>
|
||||
</tab>
|
||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||
id="{{ instance.id }}-volumes" *ngIf="instance.volumes && instance.volumes.length">
|
||||
<tab customClass="dashboard-tab" [disabled]="machine.working" (selectTab)="tabChanged($event, machine)"
|
||||
id="{{ machine.id }}-volumes" *ngIf="machine.volumes && machine.volumes.length">
|
||||
<ng-template tabHeading>
|
||||
<fa-icon icon="database" class="d-sm-none"></fa-icon>
|
||||
<span class="d-none d-sm-inline-block ms-1">Volumes</span>
|
||||
@ -248,7 +248,7 @@
|
||||
<div class="card-body p-2 h-100">
|
||||
<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"
|
||||
*ngFor="let volume of instance.volumes">
|
||||
*ngFor="let volume of machine.volumes">
|
||||
<div class="text-truncate">
|
||||
<fa-icon icon="database" [fixedWidth]="true" size="sm"></fa-icon>
|
||||
<span class="ms-1">
|
||||
@ -275,57 +275,57 @@
|
||||
<ng-template #filtersTemplate [formGroup]="editorForm">
|
||||
<fieldset class="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>
|
||||
<button class="btn btn-state-filter dropdown-toggle d-flex justify-content-between align-items-center"
|
||||
dropdownToggle>
|
||||
<span *ngIf="!editorForm.get(['filters', 'stateFilter']).value">
|
||||
{{ 'dashboard.list.anyState' | translate }}
|
||||
{{ 'machines.list.anyState' | translate }}
|
||||
</span>
|
||||
<span *ngIf="editorForm.get(['filters', 'stateFilter']).value === 'running'">
|
||||
{{ 'dashboard.listItem.stateRunning' | translate }}
|
||||
{{ 'machines.listItem.stateRunning' | translate }}
|
||||
</span>
|
||||
<span *ngIf="editorForm.get(['filters', 'stateFilter']).value === 'stopped'">
|
||||
{{ 'dashboard.listItem.stateStopped' | translate }}
|
||||
{{ 'machines.listItem.stateStopped' | translate }}
|
||||
</span>
|
||||
</button>
|
||||
<ul *dropdownMenu class="dropdown-menu dropdown-menu-state-filter" role="menu">
|
||||
<li role="menuitem">
|
||||
<button class="dropdown-item" [class.active]="!editorForm.get(['filters', 'stateFilter']).value"
|
||||
(click)="setStateFilter()">
|
||||
{{ 'dashboard.list.anyState' | translate }}
|
||||
{{ 'machines.list.anyState' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<button class="dropdown-item"
|
||||
[class.active]="editorForm.get(['filters', 'stateFilter']).value === 'running'"
|
||||
(click)="setStateFilter('running')">
|
||||
{{ 'dashboard.listItem.stateRunning' | translate }}
|
||||
{{ 'machines.listItem.stateRunning' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<button class="dropdown-item"
|
||||
[class.active]="editorForm.get(['filters', 'stateFilter']).value === 'stopped'"
|
||||
(click)="setStateFilter('stopped')">
|
||||
{{ 'dashboard.listItem.stateStopped' | translate }}
|
||||
{{ 'machines.listItem.stateStopped' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</ng-container>
|
||||
|
||||
<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>
|
||||
</ng-container>
|
||||
|
||||
<button *ngIf="memoryFilterOptions.stepsArray.length > 1 && diskFilterOptions.stepsArray.length > 1"
|
||||
class="btn btn-outline-dark w-100 mt-3" (click)="clearFilters()">
|
||||
{{ 'dashboard.list.resetFilters' | translate }}
|
||||
{{ 'machines.list.resetFilters' | translate }}
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
@ -333,7 +333,7 @@
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input mt-0" type="checkbox" id="showMachineDetails" formControlName="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"
|
||||
*ngIf="editorForm.get('showMachineDetails').disabled"></fa-icon>
|
||||
</label>
|
||||
@ -344,66 +344,66 @@
|
||||
<input class="form-check-input mt-0" type="checkbox" id="fullDetailsTwoColumns"
|
||||
formControlName="fullDetailsTwoColumns">
|
||||
<label class="form-check-label" for="fullDetailsTwoColumns">
|
||||
{{ 'dashboard.list.dualColumns' | translate }}
|
||||
{{ 'machines.list.dualColumns' | translate }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #instanceContextMenu let-instance="instance">
|
||||
<ng-template #machineContextMenu let-machine="machine">
|
||||
<ul class="list-group list-group-flush" role="menu">
|
||||
<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>
|
||||
{{ 'dashboard.listItem.rename' | translate }}
|
||||
{{ 'machines.listItem.rename' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<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>
|
||||
{{ 'dashboard.listItem.editTags' | translate }}
|
||||
{{ 'machines.listItem.editTags' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<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>
|
||||
{{ 'dashboard.listItem.editMetadata' | translate }}
|
||||
{{ 'machines.listItem.editMetadata' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="dropdown-divider"></li>
|
||||
<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>
|
||||
{{ 'dashboard.listItem.clone' | translate }}
|
||||
{{ 'machines.listItem.clone' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<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>
|
||||
{{ 'dashboard.listItem.createImage' | translate }}
|
||||
{{ 'machines.listItem.createImage' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="dropdown-divider"></li>
|
||||
<ng-container *ngIf="instance.state === 'running'">
|
||||
<ng-container *ngIf="machine.state === 'running'">
|
||||
<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>
|
||||
{{ 'dashboard.listItem.restart' | translate }}
|
||||
{{ 'machines.listItem.restart' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<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>
|
||||
{{ 'dashboard.listItem.stop' | translate }}
|
||||
{{ 'machines.listItem.stop' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="dropdown-divider"></li>
|
||||
</ng-container>
|
||||
<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>
|
||||
{{ 'dashboard.listItem.delete' | translate }}
|
||||
{{ 'machines.listItem.delete' | translate }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
@ -1,20 +1,20 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InstancesComponent } from './instances.component';
|
||||
import { MachinesComponent } from './machines.component';
|
||||
|
||||
describe('InstancesComponent', () => {
|
||||
let component: InstancesComponent;
|
||||
let fixture: ComponentFixture<InstancesComponent>;
|
||||
describe('MachinesComponent', () => {
|
||||
let component: MachinesComponent;
|
||||
let fixture: ComponentFixture<MachinesComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ InstancesComponent ]
|
||||
declarations: [ MachinesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InstancesComponent);
|
||||
fixture = TestBed.createComponent(MachinesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -1,17 +1,17 @@
|
||||
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 { debounceTime, delay, distinctUntilChanged, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
import { InstanceWizardComponent } from './instance-wizard/instance-wizard.component';
|
||||
import { Instance } from './models/instance';
|
||||
import { MachineWizardComponent } from './machine-wizard/machine-wizard.component';
|
||||
import { Machine } from './models/machine';
|
||||
import { forkJoin, Subject } from 'rxjs';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { CatalogService } from '../catalog/helpers/catalog.service';
|
||||
import { PackageSelectorComponent } from './package-selector/package-selector.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 { 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 { VirtualScrollerComponent } from 'ngx-virtual-scroller';
|
||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
@ -24,18 +24,18 @@ import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-instances',
|
||||
templateUrl: './instances.component.html',
|
||||
styleUrls: ['./instances.component.scss']
|
||||
selector: 'app-machines',
|
||||
templateUrl: './machines.component.html',
|
||||
styleUrls: ['./machines.component.scss']
|
||||
})
|
||||
export class InstancesComponent implements OnInit, OnDestroy
|
||||
export class MachinesComponent implements OnInit, OnDestroy
|
||||
{
|
||||
@ViewChild(VirtualScrollerComponent)
|
||||
private virtualScroller: VirtualScrollerComponent;
|
||||
|
||||
loadingIndicator = true;
|
||||
instances: Instance[] = [];
|
||||
listItems: Instance[];
|
||||
machines: Machine[] = [];
|
||||
listItems: Machine[];
|
||||
images = [];
|
||||
packages = [];
|
||||
volumes = [];
|
||||
@ -44,9 +44,9 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
editorForm: FormGroup;
|
||||
showMachineDetails: boolean;
|
||||
fullDetailsTwoColumns: boolean;
|
||||
runningInstanceCount = 0;
|
||||
stoppedInstanceCount = 0;
|
||||
instanceStateArray: string[] = [];
|
||||
runningMachineCount = 0;
|
||||
stoppedMachineCount = 0;
|
||||
machineStateArray: string[] = [];
|
||||
memoryFilterOptions: Options = {
|
||||
animate: false,
|
||||
stepsArray: [],
|
||||
@ -68,7 +68,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
private readonly fuseJsOptions: {};
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly instancesService: InstancesService,
|
||||
constructor(private readonly machinesService: MachinesService,
|
||||
private readonly catalogService: CatalogService,
|
||||
private readonly volumesService: VolumesService,
|
||||
private readonly modalService: BsModalService,
|
||||
@ -78,7 +78,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
private readonly titleService: Title,
|
||||
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;
|
||||
|
||||
@ -109,7 +109,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
{
|
||||
const formattedValue = this.fileSizePipe.transform(value * 1024 * 1024);
|
||||
|
||||
if (this.instances.length === 1)
|
||||
if (this.machines.length === 1)
|
||||
return formattedValue;
|
||||
|
||||
switch (label)
|
||||
@ -124,28 +124,28 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
private getInstances()
|
||||
private getMachines()
|
||||
{
|
||||
this.instancesService.get()
|
||||
.subscribe(instances =>
|
||||
this.machinesService.get()
|
||||
.subscribe(machines =>
|
||||
{
|
||||
//// DEMO ONLY !!!!!
|
||||
//const arr = new Array(200);
|
||||
//for (let j = 0; j < 200; j++)
|
||||
//{
|
||||
// const el = { ...instances[0] };
|
||||
// const el = { ...machines[0] };
|
||||
// el.name = this.dummyNames[j];
|
||||
// arr[j] = el;
|
||||
//}/**/
|
||||
//// DEMO ONLY !!!!!
|
||||
|
||||
this.instances = instances.map(instance =>
|
||||
this.machines = machines.map(machine =>
|
||||
{
|
||||
instance.metadataKeys = Object.keys(instance.metadata);
|
||||
instance.tagKeys = Object.keys(instance.tags);
|
||||
machine.metadataKeys = Object.keys(machine.metadata);
|
||||
machine.tagKeys = Object.keys(machine.tags);
|
||||
|
||||
instance.loading = true; // Required for improved scrolling experience
|
||||
return instance;
|
||||
machine.loading = true; // Required for improved scrolling experience
|
||||
return machine;
|
||||
});
|
||||
|
||||
this.getImagesPackagesAndVolumes();
|
||||
@ -172,8 +172,8 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
this.packages = response.packages;
|
||||
this.volumes = response.volumes;
|
||||
|
||||
for (const instance of this.instances)
|
||||
this.fillInInstanceDetails(instance);
|
||||
for (const machine of this.machines)
|
||||
this.fillInMachineDetails(machine);
|
||||
});
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
typeFilter: [],
|
||||
memoryFilter: [[0, 0]],
|
||||
diskFilter: [[0, 0]],
|
||||
imageFilter: [], // instances provisioned with a certain image
|
||||
imageFilter: [], // machines provisioned with a certain image
|
||||
}),
|
||||
filtersActive: [false],
|
||||
showMachineDetails: [this.showMachineDetails],
|
||||
@ -255,18 +255,18 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
private applyFiltersAndSort()
|
||||
{
|
||||
let listItems: Instance[] = null;
|
||||
let listItems: Machine[] = null;
|
||||
|
||||
const searchTerm = this.editorForm.get('searchTerm').value;
|
||||
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);
|
||||
listItems = fuseResults.map(x => x.item);
|
||||
}
|
||||
|
||||
if (!listItems)
|
||||
listItems = [...this.instances];
|
||||
listItems = [...this.machines];
|
||||
|
||||
const stateFilter = this.editorForm.get(['filters', 'stateFilter']).value;
|
||||
if (stateFilter)
|
||||
@ -326,43 +326,43 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
prepareForLoading(instances: Instance[])
|
||||
prepareForLoading(machines: Machine[])
|
||||
{
|
||||
for (const instance of instances)
|
||||
instance.loading = true;
|
||||
for (const machine of machines)
|
||||
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)
|
||||
{
|
||||
this.runningInstanceCount = 0;
|
||||
this.stoppedInstanceCount = 0;
|
||||
this.instanceStateArray = [];
|
||||
this.runningMachineCount = 0;
|
||||
this.stoppedMachineCount = 0;
|
||||
this.machineStateArray = [];
|
||||
|
||||
const memoryValues = {};
|
||||
const diskValues = {};
|
||||
|
||||
for (const instance of this.instances)
|
||||
for (const machine of this.machines)
|
||||
{
|
||||
if (instance.state === 'running')
|
||||
this.runningInstanceCount++;
|
||||
if (machine.state === 'running')
|
||||
this.runningMachineCount++;
|
||||
|
||||
if (instance.state === 'stopped')
|
||||
this.stoppedInstanceCount++;
|
||||
if (machine.state === 'stopped')
|
||||
this.stoppedMachineCount++;
|
||||
|
||||
if (!~this.instanceStateArray.indexOf(instance.state))
|
||||
this.instanceStateArray.push(instance.state);
|
||||
if (!~this.machineStateArray.indexOf(machine.state))
|
||||
this.machineStateArray.push(machine.state);
|
||||
|
||||
if (!computeOnlyState && !memoryValues[instance.memory])
|
||||
memoryValues[instance.memory] = true;
|
||||
if (!computeOnlyState && !memoryValues[machine.memory])
|
||||
memoryValues[machine.memory] = true;
|
||||
|
||||
if (!computeOnlyState && !diskValues[instance.disk])
|
||||
diskValues[instance.disk] = true;
|
||||
if (!computeOnlyState && !diskValues[machine.disk])
|
||||
diskValues[machine.disk] = true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
instance.working = true;
|
||||
this.toastr.info(`Starting machine "${instance.name}"...`);
|
||||
machine.working = true;
|
||||
this.toastr.info(`Starting machine "${machine.name}"...`);
|
||||
|
||||
this.instancesService.start(instance.id)
|
||||
this.machinesService.start(machine.id)
|
||||
.pipe(
|
||||
delay(1000),
|
||||
switchMap(() =>
|
||||
this.instancesService.getInstanceUntilExpectedState(instance, ['running'], x =>
|
||||
this.machinesService.getMachineUntilExpectedState(machine, ['running'], x =>
|
||||
{
|
||||
instance.state = x.state;
|
||||
machine.state = x.state;
|
||||
|
||||
this.computeFiltersOptions();
|
||||
})
|
||||
@ -411,32 +411,32 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
{
|
||||
this.computeFiltersOptions();
|
||||
|
||||
instance.working = false;
|
||||
this.toastr.info(`The machine "${instance.name}" has been started`);
|
||||
machine.working = false;
|
||||
this.toastr.info(`The machine "${machine.name}" has been started`);
|
||||
}, err =>
|
||||
{
|
||||
this.computeFiltersOptions();
|
||||
|
||||
instance.working = false;
|
||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
||||
machine.working = false;
|
||||
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;
|
||||
|
||||
instance.working = true;
|
||||
this.toastr.info(`Restarting machine "${instance.name}"...`);
|
||||
machine.working = true;
|
||||
this.toastr.info(`Restarting machine "${machine.name}"...`);
|
||||
|
||||
this.instancesService.reboot(instance.id)
|
||||
this.machinesService.reboot(machine.id)
|
||||
.pipe(
|
||||
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();
|
||||
})
|
||||
@ -447,33 +447,33 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
{
|
||||
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 =>
|
||||
{
|
||||
this.computeFiltersOptions();
|
||||
|
||||
instance.working = false;
|
||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
||||
machine.working = false;
|
||||
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;
|
||||
|
||||
instance.working = true;
|
||||
this.toastr.info(`Stopping machine "${instance.name}"`);
|
||||
machine.working = true;
|
||||
this.toastr.info(`Stopping machine "${machine.name}"`);
|
||||
|
||||
this.instancesService.stop(instance.id)
|
||||
this.machinesService.stop(machine.id)
|
||||
.pipe(
|
||||
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();
|
||||
})
|
||||
@ -484,25 +484,25 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
{
|
||||
this.computeFiltersOptions();
|
||||
|
||||
instance.working = false;
|
||||
this.toastr.info(`The machine "${instance.name}" has been stopped`);
|
||||
machine.working = false;
|
||||
this.toastr.info(`The machine "${machine.name}" has been stopped`);
|
||||
}, err =>
|
||||
{
|
||||
this.computeFiltersOptions();
|
||||
|
||||
instance.working = false;
|
||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
||||
machine.working = false;
|
||||
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
resizeMachine(instance: Instance)
|
||||
resizeMachine(machine: Machine)
|
||||
{
|
||||
const modalConfig = {
|
||||
ignoreBackdropClick: true,
|
||||
keyboard: false,
|
||||
animated: true,
|
||||
initialState: { instance }
|
||||
initialState: { machine }
|
||||
};
|
||||
|
||||
const modalRef = this.modalService.show(PackageSelectorComponent, modalConfig);
|
||||
@ -512,43 +512,43 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
.pipe(
|
||||
tap(() =>
|
||||
{
|
||||
this.toastr.info(`Changing specifications for machine "${instance.name}"...`);
|
||||
instance.working = true;
|
||||
this.toastr.info(`Changing specifications for machine "${machine.name}"...`);
|
||||
machine.working = true;
|
||||
}),
|
||||
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 =>
|
||||
{
|
||||
instance.package = pkg.name;
|
||||
instance.memory = pkg.memory;
|
||||
instance.disk = pkg.disk;
|
||||
machine.package = pkg.name;
|
||||
machine.memory = pkg.memory;
|
||||
machine.disk = pkg.disk;
|
||||
|
||||
this.fillInInstanceDetails(instance);
|
||||
this.fillInMachineDetails(machine);
|
||||
|
||||
this.computeFiltersOptions();
|
||||
|
||||
instance.working = false;
|
||||
this.toastr.info(`The specifications for machine "${instance.name}" have been changed`);
|
||||
machine.working = false;
|
||||
this.toastr.info(`The specifications for machine "${machine.name}" have been changed`);
|
||||
}, err =>
|
||||
{
|
||||
instance.working = false;
|
||||
machine.working = false;
|
||||
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 = {
|
||||
ignoreBackdropClick: true,
|
||||
keyboard: false,
|
||||
animated: true,
|
||||
initialState: {
|
||||
value: instanceName,
|
||||
value: machineName,
|
||||
required: true,
|
||||
title: 'Rename 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 =>
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
instance.working = true;
|
||||
machine.working = true;
|
||||
|
||||
this.instancesService.rename(instance.id, name)
|
||||
this.machinesService.rename(machine.id, name)
|
||||
.subscribe(() =>
|
||||
{
|
||||
instance.name = name;
|
||||
machine.name = name;
|
||||
|
||||
this.applyFiltersAndSort();
|
||||
|
||||
this.toastr.info(`The "${instanceName}" machine has been renamed to "${instance.name}"`);
|
||||
instance.working = false;
|
||||
this.toastr.info(`The "${machineName}" machine has been renamed to "${machine.name}"`);
|
||||
machine.working = false;
|
||||
}, err =>
|
||||
{
|
||||
const errorDetails = err.error?.message ? `(${err.error.message})` : '';
|
||||
this.toastr.error(`Couldn't rename the "${instanceName}" machine ${errorDetails}`);
|
||||
instance.working = false;
|
||||
this.toastr.error(`Couldn't rename the "${machineName}" machine ${errorDetails}`);
|
||||
machine.working = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
showTagEditor(instance: Instance, showMetadata = false)
|
||||
showTagEditor(machine: Machine, showMetadata = false)
|
||||
{
|
||||
const modalConfig = {
|
||||
ignoreBackdropClick: true,
|
||||
keyboard: false,
|
||||
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.content.save.pipe(first()).subscribe(x =>
|
||||
{
|
||||
instance[showMetadata ? 'metadata' : 'tags'] = x;
|
||||
machine[showMetadata ? 'metadata' : 'tags'] = x;
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
createImageFromMachine(instance: Instance)
|
||||
createImageFromMachine(machine: Machine)
|
||||
{
|
||||
const modalConfig = {
|
||||
ignoreBackdropClick: true,
|
||||
keyboard: false,
|
||||
animated: true,
|
||||
initialState: { instance }
|
||||
initialState: { machine }
|
||||
};
|
||||
|
||||
const modalRef = this.modalService.show(CustomImageEditorComponent, modalConfig);
|
||||
|
||||
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(
|
||||
delay(1000),
|
||||
switchMap(image => this.catalogService.getImageUntilExpectedState(image, ['active', 'failed'])
|
||||
@ -632,28 +632,28 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
.subscribe(image =>
|
||||
{
|
||||
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
|
||||
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 =>
|
||||
{
|
||||
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 = {
|
||||
ignoreBackdropClick: true,
|
||||
keyboard: false,
|
||||
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.content.save.pipe(first()).subscribe(x =>
|
||||
@ -662,23 +662,23 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
|
||||
x.working = true;
|
||||
|
||||
this.fillInInstanceDetails(x);
|
||||
this.fillInMachineDetails(x);
|
||||
|
||||
this.instances.push(x);
|
||||
this.machines.push(x);
|
||||
|
||||
this.computeFiltersOptions();
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
deleteMachine(instance: Instance)
|
||||
deleteMachine(machine: Machine)
|
||||
{
|
||||
const modalConfig = {
|
||||
ignoreBackdropClick: true,
|
||||
keyboard: false,
|
||||
animated: true,
|
||||
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',
|
||||
declineButtonText: 'No, keep it',
|
||||
confirmByDefault: false
|
||||
@ -689,123 +689,123 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
|
||||
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(() =>
|
||||
{
|
||||
const index = this.instances.findIndex(i => i.id === instance.id);
|
||||
const index = this.machines.findIndex(i => i.id === machine.id);
|
||||
if (index < 0) return;
|
||||
|
||||
this.instances.splice(index, 1);
|
||||
this.machines.splice(index, 1);
|
||||
|
||||
this.computeFiltersOptions();
|
||||
|
||||
this.toastr.info(`The machine "${instance.name}" has been removed`);
|
||||
this.toastr.info(`The machine "${machine.name}" has been removed`);
|
||||
},
|
||||
err =>
|
||||
{
|
||||
instance.working = false;
|
||||
this.toastr.error(`Machine "${instance.name}" error: ${err.error.message}`);
|
||||
machine.working = false;
|
||||
this.toastr.error(`Machine "${machine.name}" error: ${err.error.message}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
showMachineHistory(instance: Instance)
|
||||
showMachineHistory(machine: Machine)
|
||||
{
|
||||
const modalConfig = {
|
||||
ignoreBackdropClick: true,
|
||||
keyboard: false,
|
||||
animated: true,
|
||||
initialState: { instance }
|
||||
initialState: { machine }
|
||||
};
|
||||
|
||||
const modalRef = this.modalService.show(InstanceHistoryComponent, modalConfig);
|
||||
const modalRef = this.modalService.show(MachineHistoryComponent, modalConfig);
|
||||
modalRef.setClass('modal-lg');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
tabChanged(event, instance: Instance)
|
||||
tabChanged(event, machine: Machine)
|
||||
{
|
||||
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'))
|
||||
instance.shouldLoadSnapshots = this.editorForm.get('showMachineDetails').value;
|
||||
machine.shouldLoadSnapshots = this.editorForm.get('showMachineDetails').value;
|
||||
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'))
|
||||
{
|
||||
//instance.shouldLoadVolumes = this.editorForm.get('showMachineDetails').value;
|
||||
//machine.shouldLoadVolumes = this.editorForm.get('showMachineDetails').value;
|
||||
}
|
||||
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
|
||||
if (instance.working)
|
||||
this.instancesService.getInstanceUntilExpectedState(instance, this.stableStates, x =>
|
||||
// Keep polling the machines that are not in a "stable" state
|
||||
if (machine.working)
|
||||
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
|
||||
instance.shouldLoadInfo = false;
|
||||
machine.shouldLoadInfo = false;
|
||||
|
||||
this.computeFiltersOptions(true);
|
||||
})
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(x =>
|
||||
{
|
||||
instance.working = false;
|
||||
machine.working = false;
|
||||
|
||||
// Update the instance with what we got from the server
|
||||
Object.assign(instance, x);
|
||||
// Update the machine with what we got from the server
|
||||
Object.assign(machine, x);
|
||||
|
||||
instance.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
||||
machine.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
||||
|
||||
this.computeFiltersOptions();
|
||||
}, err =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.instances.splice(index, 1);
|
||||
this.machines.splice(index, 1);
|
||||
|
||||
this.computeFiltersOptions();
|
||||
|
||||
this.toastr.error(`The machine "${instance.name}" has been removed`);
|
||||
this.toastr.error(`The machine "${machine.name}" has been removed`);
|
||||
}
|
||||
}
|
||||
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);
|
||||
})
|
||||
@ -814,49 +814,49 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => { }, err =>
|
||||
{
|
||||
instance.working = false;
|
||||
machine.working = false;
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
updateInstance(instance: Instance, updates: Instance)
|
||||
updateMachine(machine: Machine, updates: Machine)
|
||||
{
|
||||
instance.refreshInfo = instance.state !== updates.state;
|
||||
instance.state = updates.state;
|
||||
machine.refreshInfo = machine.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.
|
||||
instance.dnsList = dnsList;
|
||||
instance.infoLoaded = true;
|
||||
machine.dnsList = dnsList;
|
||||
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.
|
||||
instance.nics = nics;
|
||||
instance.networksLoaded = true;
|
||||
machine.nics = nics;
|
||||
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.
|
||||
instance.snapshots = snapshots;
|
||||
instance.snapshotsLoaded = true;
|
||||
machine.snapshots = snapshots;
|
||||
machine.snapshotsLoaded = true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
refreshInstanceDnsList(instance: Instance)
|
||||
refreshMachineDnsList(machine: Machine)
|
||||
{
|
||||
instance.working = false;
|
||||
instance.refreshInfo = true;
|
||||
machine.working = false;
|
||||
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)
|
||||
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)
|
||||
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
|
||||
{
|
||||
this.getInstances();
|
||||
this.getMachines();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
@ -9,49 +9,49 @@ import { WebpackTranslateLoader } from '../helpers/webpack-translate-loader.serv
|
||||
import { TranslateCompiler } from '@ngx-translate/core';
|
||||
import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler';
|
||||
|
||||
import { InstancesComponent } from './instances.component';
|
||||
import { InstanceWizardComponent } from './instance-wizard/instance-wizard.component';
|
||||
import { MachinesComponent } from './machines.component';
|
||||
import { MachineWizardComponent } from './machine-wizard/machine-wizard.component';
|
||||
import { PackageSelectorComponent } from './package-selector/package-selector.component';
|
||||
import { InstanceSnapshotsComponent } from './instance-snapshots/instance-snapshots.component';
|
||||
import { InstanceNetworksComponent } from './instance-networks/instance-networks.component';
|
||||
import { InstanceSecurityComponent } from './instance-security/instance-security.component';
|
||||
import { InstanceTagEditorComponent } from './instance-tag-editor/instance-tag-editor.component';
|
||||
import { InstanceHistoryComponent } from './instance-history/instance-history.component';
|
||||
import { MachineSnapshotsComponent } from './machine-snapshots/machine-snapshots.component';
|
||||
import { MachineNetworksComponent } from './machine-networks/machine-networks.component';
|
||||
import { MachineSecurityComponent } from './machine-security/machine-security.component';
|
||||
import { MachineTagEditorComponent } from './machine-tag-editor/machine-tag-editor.component';
|
||||
import { MachineHistoryComponent } from './machine-history/machine-history.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({
|
||||
declarations: [
|
||||
InstancesComponent,
|
||||
InstanceWizardComponent,
|
||||
MachinesComponent,
|
||||
MachineWizardComponent,
|
||||
PackageSelectorComponent,
|
||||
InstanceSnapshotsComponent,
|
||||
InstanceNetworksComponent,
|
||||
InstanceSecurityComponent,
|
||||
InstanceTagEditorComponent,
|
||||
InstanceHistoryComponent,
|
||||
InstanceInfoComponent,
|
||||
MachineSnapshotsComponent,
|
||||
MachineNetworksComponent,
|
||||
MachineSecurityComponent,
|
||||
MachineTagEditorComponent,
|
||||
MachineHistoryComponent,
|
||||
MachineInfoComponent,
|
||||
],
|
||||
imports: [
|
||||
SharedModule,
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: InstancesComponent,
|
||||
component: MachinesComponent,
|
||||
data:
|
||||
{
|
||||
title: 'instances.title',
|
||||
subTitle: 'instances.subTitle',
|
||||
title: 'machines.title',
|
||||
subTitle: 'machines.subTitle',
|
||||
icon: 'server'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'wizard',
|
||||
component: InstanceWizardComponent,
|
||||
component: MachineWizardComponent,
|
||||
data:
|
||||
{
|
||||
title: 'instances.wizard.title',
|
||||
subTitle: 'instances.wizard.subTitle',
|
||||
title: 'machines.wizard.title',
|
||||
subTitle: 'machines.wizard.subTitle',
|
||||
icon: 'hat-wizard'
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,7 @@ import { InstanceInfoComponent } from './instance-info/instance-info.component';
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
//useClass: WebpackTranslateLoader
|
||||
useFactory: () => new WebpackTranslateLoader('dashboard')
|
||||
useFactory: () => new WebpackTranslateLoader('machines')
|
||||
},
|
||||
compiler: {
|
||||
provide: TranslateCompiler,
|
||||
@ -72,15 +72,15 @@ import { InstanceInfoComponent } from './instance-info/instance-info.component';
|
||||
})
|
||||
],
|
||||
entryComponents: [
|
||||
InstanceWizardComponent,
|
||||
MachineWizardComponent,
|
||||
PackageSelectorComponent,
|
||||
InstanceTagEditorComponent,
|
||||
InstanceHistoryComponent,
|
||||
MachineTagEditorComponent,
|
||||
MachineHistoryComponent,
|
||||
CustomImageEditorComponent
|
||||
|
||||
]
|
||||
})
|
||||
export class InstancesModule
|
||||
export class MachinesModule
|
||||
{
|
||||
constructor(private readonly translate: TranslateService)
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
export class InstanceDisk
|
||||
export class MachineDisk
|
||||
{
|
||||
id: string;
|
||||
boot: boolean;
|
@ -1,4 +1,4 @@
|
||||
export class InstanceVolume
|
||||
export class MachineVolume
|
||||
{
|
||||
name: string;
|
||||
type: string; // "tritonnfs"
|
@ -1,11 +1,11 @@
|
||||
import { Network } from '../../networking/models/network';
|
||||
import { InstanceDisk } from './instance-disk';
|
||||
import { MachineDisk } from './machine-disk';
|
||||
import { Nic } from './nic';
|
||||
import { InstanceVolume } from './instance-volume';
|
||||
import { MachineVolume } from './machine-volume';
|
||||
import { CatalogImage } from '../../catalog/models/image';
|
||||
import { CatalogPackage } from '../../catalog/models/package';
|
||||
|
||||
export class InstanceRequest
|
||||
export class MachineRequest
|
||||
{
|
||||
id: string;
|
||||
name: string;
|
||||
@ -19,14 +19,14 @@ export class InstanceRequest
|
||||
firewall_enabled: boolean;
|
||||
deletion_protection: boolean;
|
||||
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
|
||||
disks: InstanceDisk[]; // An array of disk objects to be created (bhyve)
|
||||
volumes: MachineVolume[]; // list of objects representing volumes to mount when the newly created machine boots
|
||||
disks: MachineDisk[]; // An array of disk objects to be created (bhyve)
|
||||
disk: number;
|
||||
encrypted: boolean; // Place this instance into an encrypted server. Optional.
|
||||
encrypted: boolean; // Place this machine into an encrypted server. Optional.
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export class Instance extends InstanceRequest
|
||||
export class Machine extends MachineRequest
|
||||
{
|
||||
nics: Nic[];
|
||||
imageDetails: CatalogImage;
|
@ -9,10 +9,10 @@
|
||||
|
||||
<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">
|
||||
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>
|
||||
|
||||
<p class="selected-package" *ngIf="editorForm.get('package').value">
|
@ -14,7 +14,7 @@ import { CatalogPackage } from '../../catalog/models/package';
|
||||
export class PackageSelectorComponent implements OnInit
|
||||
{
|
||||
@Input()
|
||||
instance: any;
|
||||
machine: any;
|
||||
|
||||
save = new Subject<CatalogPackage>();
|
||||
imageType: number;
|
||||
@ -67,7 +67,7 @@ export class PackageSelectorComponent implements OnInit
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
ngOnInit(): void
|
||||
{
|
||||
switch (this.instance.type)
|
||||
switch (this.machine.type)
|
||||
{
|
||||
case 'virtualmachine':
|
||||
this.imageType = 1;
|
@ -64,7 +64,7 @@
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="rule">
|
||||
{{ 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>
|
||||
|
||||
<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="rule">
|
||||
{{ 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>
|
||||
|
||||
<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 { FirewallService } from '../helpers/firewall.service';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { InstancesService } from '../../instances/helpers/instances.service';
|
||||
import { MachinesService } from '../../machines/helpers/machines.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-firewall-editor',
|
||||
@ -28,7 +28,7 @@ export class FirewallEditorComponent implements OnInit, OnDestroy
|
||||
canAddFromRule: boolean;
|
||||
canAddToRule: boolean;
|
||||
protocolConfigRegex: string;
|
||||
instances = {};
|
||||
machines = {};
|
||||
|
||||
private destroy$ = new Subject();
|
||||
|
||||
@ -37,7 +37,7 @@ export class FirewallEditorComponent implements OnInit, OnDestroy
|
||||
private readonly router: Router,
|
||||
private readonly fb: FormBuilder,
|
||||
private readonly toastr: ToastrService,
|
||||
private readonly instancesService: InstancesService,
|
||||
private readonly machinesService: MachinesService,
|
||||
private readonly firewallService: FirewallService)
|
||||
{ // When the user navigates away from this route, hide the modal
|
||||
router.events
|
||||
@ -47,10 +47,10 @@ export class FirewallEditorComponent implements OnInit, OnDestroy
|
||||
)
|
||||
.subscribe(() => this.modalRef.hide());
|
||||
|
||||
this.instancesService.get()
|
||||
this.machinesService.get()
|
||||
.subscribe(x =>
|
||||
{
|
||||
this.instances = x.reduce((a, b) =>
|
||||
this.machines = x.reduce((a, b) =>
|
||||
{
|
||||
a[b.id] = b.name;
|
||||
return a;
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
<div class="col-sm" *ngIf="editorForm.get('type').value === 'vm'">
|
||||
<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>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, HostListener } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms';
|
||||
import { Instance } from '../../instances/models/instance';
|
||||
import { InstancesService } from '../../instances/helpers/instances.service';
|
||||
import { Machine } from '../../machines/models/machine';
|
||||
import { MachinesService } from '../../machines/helpers/machines.service';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -18,7 +18,7 @@ export class FirewallRuleEditorComponent implements OnInit, OnDestroy
|
||||
@Output()
|
||||
saved = new EventEmitter();
|
||||
|
||||
instances: Instance[];
|
||||
machines: Machine[];
|
||||
editorVisible: boolean;
|
||||
editorForm: FormGroup;
|
||||
keyRegex = '^[A-Za-z0-9-_]+$';
|
||||
@ -29,9 +29,9 @@ export class FirewallRuleEditorComponent implements OnInit, OnDestroy
|
||||
// --------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly elementRef: ElementRef,
|
||||
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
|
||||
<span *ngFor="let from of fw.fromArray" class="inline-list-item highlight" text="OR">
|
||||
{{ from.type }}
|
||||
<b *ngIf="from.config">{{ instances[from.config] || from.config }}</b>
|
||||
<b *ngIf="from.config">{{ machines[from.config] || from.config }}</b>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
To
|
||||
<span *ngFor="let to of fw.toArray" class="inline-list-item highlight" text="OR">
|
||||
{{ to.type }}
|
||||
<span *ngIf="to.type === 'tag'" class="badge badge-discreet">{{ instances[to.config] || to.config }}</span>
|
||||
<b *ngIf="to.type !== 'tag'">{{ instances[to.config] || to.config }}</b>
|
||||
<span *ngIf="to.type === 'tag'" class="badge badge-discreet">{{ machines[to.config] || to.config }}</span>
|
||||
<b *ngIf="to.type !== 'tag'">{{ machines[to.config] || to.config }}</b>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
|
@ -8,7 +8,7 @@ import { Subject, ReplaySubject } from 'rxjs';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { FirewallRule } from '../models/firewall-rule';
|
||||
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 { sortArray } from '../../helpers/utils.service';
|
||||
import { Title } from "@angular/platform-browser";
|
||||
@ -25,14 +25,14 @@ export class FirewallRulesComponent implements OnInit, OnDestroy
|
||||
listItems: FirewallRule[];
|
||||
loadingIndicator = true;
|
||||
editorForm: FormGroup;
|
||||
instances = {};
|
||||
machines = {};
|
||||
|
||||
private readonly fuseJsOptions: {};
|
||||
private destroy$ = new Subject();
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly firewallService: FirewallService,
|
||||
private readonly instancesService: InstancesService,
|
||||
private readonly machinesService: MachinesService,
|
||||
private readonly modalService: BsModalService,
|
||||
private readonly toastr: ToastrService,
|
||||
private readonly fb: FormBuilder,
|
||||
@ -53,10 +53,10 @@ export class FirewallRulesComponent implements OnInit, OnDestroy
|
||||
]
|
||||
};
|
||||
|
||||
this.instancesService.get()
|
||||
this.machinesService.get()
|
||||
.subscribe(x =>
|
||||
{
|
||||
this.instances = x.reduce((a, b) =>
|
||||
this.machines = x.reduce((a, b) =>
|
||||
{
|
||||
a[b.id] = b.name;
|
||||
return a;
|
||||
|
@ -31,9 +31,9 @@ export class FirewallService
|
||||
@Cacheable({
|
||||
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 { Cacheable } from 'ts-cacheable';
|
||||
import { Network } from '../models/network';
|
||||
import { Nic } from '../../instances/models/nic';
|
||||
import { Nic } from '../../machines/models/nic';
|
||||
import { VirtualAreaNetwork } from '../models/vlan';
|
||||
import { VirtualAreaNetworkRequest } from '../models/vlan';
|
||||
import { EditNetworkRequest } from '../models/network';
|
||||
import { AddNetworkRequest } from '../models/network';
|
||||
import { Instance } from 'src/app/instances/models/instance';
|
||||
import { InstanceCallbackFunction } from 'src/app/instances/helpers/instances.service';
|
||||
import { Machine } from 'src/app/machines/models/machine';
|
||||
import { MachineCallbackFunction } from 'src/app/machines/helpers/machines.service';
|
||||
|
||||
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();
|
||||
|
||||
// Keep polling the instance until it reaches the expected state
|
||||
return this.getNic(instance.id, nic.mac)
|
||||
// Keep polling the machine until it reaches the expected state
|
||||
return this.getNic(machine.id, nic.mac)
|
||||
.pipe(
|
||||
tap(x =>
|
||||
{
|
||||
// We create our own state while the instance reboots
|
||||
// We create our own state while the machine reboots
|
||||
if (x.state === 'running')
|
||||
x.state = 'starting';
|
||||
|
||||
@ -179,11 +179,11 @@ export class NetworkingService
|
||||
filter(x => x.state === 'running' || x.state === 'starting'),
|
||||
take(1), // needed to stop the repeatWhen loop
|
||||
concatMap(nic =>
|
||||
this.httpClient.get<Instance>(`/api/my/machines/${instance.id}`)
|
||||
this.httpClient.get<Machine>(`/api/my/machines/${machine.id}`)
|
||||
.pipe(
|
||||
tap(() =>
|
||||
{
|
||||
// We create our own state while the instance reboots
|
||||
// We create our own state while the machine reboots
|
||||
nic.state = 'starting';
|
||||
|
||||
if (callbackFn)
|
||||
@ -206,7 +206,7 @@ export class NetworkingService
|
||||
take(1), // needed to stop the repeatWhen loop
|
||||
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';
|
||||
|
||||
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 { DashboardComponent } from './dashboard.component';
|
||||
import { DashboardComponent } from './machines.component';
|
||||
|
||||
describe('DashboardComponent', () => {
|
||||
let component: DashboardComponent;
|
@ -2,8 +2,8 @@ import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrls: ['./dashboard.component.scss']
|
||||
templateUrl: './machines.component.html',
|
||||
styleUrls: ['./machines.component.scss']
|
||||
})
|
||||
export class DashboardComponent implements OnInit {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FileSizePipe } from './file-size.pipe';
|
||||
|
||||
describe('FileSizePipe', () => {
|
||||
it('create an instance', () => {
|
||||
it('create an machine', () => {
|
||||
const pipe = new FileSizePipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
|
@ -33,6 +33,7 @@ import { ConfirmationDialogComponent } from './components/confirmation-dialog/co
|
||||
import { PromptDialogComponent } from './components/prompt-dialog/prompt-dialog.component';
|
||||
import { CustomImageEditorComponent } from './catalog/custom-image-editor/custom-image-editor.component';
|
||||
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 { library } from '@fortawesome/fontawesome-svg-core';
|
||||
@ -59,6 +60,7 @@ import { faDocker } from '@fortawesome/free-brands-svg-icons';
|
||||
PromptDialogComponent,
|
||||
CustomImageEditorComponent,
|
||||
LazyLoadDirective,
|
||||
AffinityRuleEditorComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -127,6 +129,7 @@ import { faDocker } from '@fortawesome/free-brands-svg-icons';
|
||||
VirtualScrollerModule,
|
||||
NgxSliderModule,
|
||||
ClipboardModule,
|
||||
AffinityRuleEditorComponent
|
||||
//HasPermissionDirective
|
||||
],
|
||||
providers: [
|
||||
|
@ -39,9 +39,9 @@ export class VolumesService
|
||||
//@Cacheable({
|
||||
// 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 { Volume } from './models/volume';
|
||||
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 { BsModalService } from 'ngx-bootstrap/modal';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
@ -28,7 +28,7 @@ export class VolumesComponent implements OnInit, OnDestroy
|
||||
networks = {};
|
||||
loadingIndicator = true;
|
||||
editorForm: FormGroup;
|
||||
instances = {};
|
||||
machines = {};
|
||||
|
||||
private destroy$ = new Subject();
|
||||
private readonly fuseJsOptions: {};
|
||||
@ -36,7 +36,7 @@ export class VolumesComponent implements OnInit, OnDestroy
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly volumesService: VolumesService,
|
||||
private readonly networkingService: NetworkingService,
|
||||
private readonly instancesService: InstancesService,
|
||||
private readonly machinesService: MachinesService,
|
||||
private readonly modalService: BsModalService,
|
||||
private readonly toastr: ToastrService,
|
||||
private readonly fb: FormBuilder,
|
||||
@ -60,10 +60,10 @@ export class VolumesComponent implements OnInit, OnDestroy
|
||||
|
||||
this.createForm();
|
||||
|
||||
this.instancesService.get()
|
||||
this.machinesService.get()
|
||||
.subscribe(x =>
|
||||
{
|
||||
this.instances = x.reduce((a, b) =>
|
||||
this.machines = x.reduce((a, b) =>
|
||||
{
|
||||
a[b.id] = b.name;
|
||||
return a;
|
||||
|
@ -4,7 +4,7 @@
|
||||
"menu":
|
||||
{
|
||||
"dashboard": "Dashboard",
|
||||
"instances": "Machines",
|
||||
"machines": "Machines",
|
||||
"volumes": "Volumes",
|
||||
"images": "Images",
|
||||
"networks": "Networks",
|
||||
@ -33,7 +33,7 @@
|
||||
"title": "Dashboard",
|
||||
"subTitle": ""
|
||||
},
|
||||
"instances":
|
||||
"machines":
|
||||
{
|
||||
"title": "Machines",
|
||||
"subTitle": ""
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"dashboard":
|
||||
"machines":
|
||||
{
|
||||
"title": "Dashboard",
|
||||
"general":
|
||||
@ -216,8 +216,6 @@
|
||||
"addMetadata": "Add metadata",
|
||||
"removeMetadata": "Remove this metadata",
|
||||
"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",
|
||||
"createButtonText": "Create machine",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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;
|
||||
|
||||
&.instances .scrollable-content
|
||||
&.machines .scrollable-content
|
||||
{
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
@ -29,7 +29,7 @@ virtual-scroller
|
||||
|
||||
@media (min-width: 576px)
|
||||
{
|
||||
virtual-scroller.instances .scrollable-content
|
||||
virtual-scroller.machines .scrollable-content
|
||||
{
|
||||
--bs-gutter-x: 1.5rem;
|
||||
}
|
||||
@ -37,7 +37,7 @@ virtual-scroller
|
||||
|
||||
@media (max-width: 576px)
|
||||
{
|
||||
virtual-scroller.instances .scrollable-content
|
||||
virtual-scroller.machines .scrollable-content
|
||||
{
|
||||
--bs-gutter-y: 1.5rem;
|
||||
}
|
||||
|
Reference in New Issue
Block a user