fixed some issues when adding/deleting instance networks
This commit is contained in:
parent
861ada1aa7
commit
96c76c845b
@ -61,6 +61,40 @@ export class InstancesService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
|
getInstanceUntilNicRemoved(instance: any, networkName: string, callbackFn?: InstanceCallbackFunction, maxRetries = 30): Observable<Instance>
|
||||||
|
{
|
||||||
|
networkName = networkName.toLocaleLowerCase();
|
||||||
|
|
||||||
|
// Keep polling the instance until it reaches the expected state
|
||||||
|
return this.httpClient.get<Instance>(`/api/my/machines/${instance.id}`)
|
||||||
|
.pipe(
|
||||||
|
tap(instance => callbackFn && callbackFn(instance)),
|
||||||
|
repeatWhen(x =>
|
||||||
|
{
|
||||||
|
let retries = 0;
|
||||||
|
|
||||||
|
return x.pipe(
|
||||||
|
delay(3000),
|
||||||
|
map(() =>
|
||||||
|
{
|
||||||
|
if (retries++ === maxRetries)
|
||||||
|
throw { error: `Failed to retrieve the current status for machine "${instance.name}"` };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
filter(x => x.state === 'running' && !x.dns_names.some(d => d.toLocaleLowerCase().indexOf(networkName) >= 0)),
|
||||||
|
take(1), // needed to stop the repeatWhen loop
|
||||||
|
map(x =>
|
||||||
|
{
|
||||||
|
if (callbackFn)
|
||||||
|
callbackFn(x);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
add(instance: InstanceRequest): Observable<Instance>
|
add(instance: InstanceRequest): Observable<Instance>
|
||||||
{
|
{
|
||||||
@ -230,6 +264,12 @@ export class InstancesService
|
|||||||
{
|
{
|
||||||
return this.httpClient.get(`./assets/data/packages.json`);
|
return this.httpClient.get(`./assets/data/packages.json`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
|
clearCache()
|
||||||
|
{
|
||||||
|
instancesCacheBuster$.next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InstanceCallbackFunction = ((instance: Instance) => void);
|
export type InstanceCallbackFunction = ((instance: Instance) => void);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<b>{{ instance.id }}</b>
|
<b>{{ instance.id }}</b>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<ng-container *ngIf="instance.dnsList">
|
<ng-container *ngIf="dnsCount">
|
||||||
<li class="dropdown-header">DNS list</li>
|
<li class="dropdown-header">DNS list</li>
|
||||||
<li class="list-group-item text-uppercase px-0 dns d-flex justify-content-between align-items-center"
|
<li class="list-group-item text-uppercase px-0 dns d-flex justify-content-between align-items-center"
|
||||||
*ngFor="let keyValue of instance.dnsList | keyvalue; let index = index">
|
*ngFor="let keyValue of instance.dnsList | keyvalue; let index = index">
|
||||||
|
@ -20,6 +20,9 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
@Input()
|
@Input()
|
||||||
loadInfo: boolean;
|
loadInfo: boolean;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
refresh: boolean;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
processing = new EventEmitter();
|
processing = new EventEmitter();
|
||||||
|
|
||||||
@ -30,6 +33,7 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
load = new EventEmitter();
|
load = new EventEmitter();
|
||||||
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
dnsCount: number;
|
||||||
|
|
||||||
private finishedLoading: boolean;
|
private finishedLoading: boolean;
|
||||||
private destroy$ = new Subject();
|
private destroy$ = new Subject();
|
||||||
@ -70,10 +74,13 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private getInfo()
|
private getInfo()
|
||||||
{
|
{
|
||||||
if (this.finishedLoading) return;
|
if (this.finishedLoading || this.instance.state === 'provisioning') return;
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
|
if (this.refresh)
|
||||||
|
this.instancesService.clearCache();
|
||||||
|
|
||||||
this.instancesService.getById(this.instance.id)
|
this.instancesService.getById(this.instance.id)
|
||||||
.subscribe(x =>
|
.subscribe(x =>
|
||||||
{
|
{
|
||||||
@ -81,6 +88,8 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
for (const dns of x.dns_names.sort((a, b) => b.localeCompare(a)))
|
for (const dns of x.dns_names.sort((a, b) => b.localeCompare(a)))
|
||||||
dnsList[dns] = this.getParsedDns(dns);
|
dnsList[dns] = this.getParsedDns(dns);
|
||||||
|
|
||||||
|
this.dnsCount = Object.keys(dnsList).length;
|
||||||
|
|
||||||
this.instance.dnsList = dnsList;
|
this.instance.dnsList = dnsList;
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@ -112,7 +121,13 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
{
|
{
|
||||||
this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() =>
|
this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() =>
|
||||||
{
|
{
|
||||||
if (!this.finishedLoading && this.loadInfo && !this.instance?.infoLoaded)
|
if (this.refresh)
|
||||||
|
{
|
||||||
|
this.finishedLoading = false;
|
||||||
|
this.loadInfo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.finishedLoading && this.loadInfo && !this.instance?.infoLoaded || this.refresh)
|
||||||
this.getInfo();
|
this.getInfo();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -124,7 +139,6 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
this.onChanges$.next(changes);
|
this.onChanges$.next(changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
ngOnDestroy()
|
ngOnDestroy()
|
||||||
{
|
{
|
||||||
|
@ -39,13 +39,14 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
instanceStateUpdate = new EventEmitter();
|
instanceStateUpdate = new EventEmitter();
|
||||||
|
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
nics: Nic[];
|
nics: Nic[] = [];
|
||||||
publicNetworks: Network[] = [];
|
publicNetworks: Network[] = [];
|
||||||
fabricNetworks: Network[] = [];
|
fabricNetworks: Network[] = [];
|
||||||
finishedLoading: boolean;
|
finishedLoading: boolean;
|
||||||
|
|
||||||
private destroy$ = new Subject();
|
private destroy$ = new Subject();
|
||||||
private onChanges$ = new ReplaySubject();
|
private onChanges$ = new ReplaySubject();
|
||||||
|
private networks: Network[];
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly networkingService: NetworkingService,
|
constructor(private readonly networkingService: NetworkingService,
|
||||||
@ -71,17 +72,19 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private getNetworks()
|
private getNetworks(force = false)
|
||||||
{
|
{
|
||||||
if (this.finishedLoading) return;
|
if ((this.finishedLoading || this.instance.state === 'provisioning') && !force) return;
|
||||||
|
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
const observables = this.nics.map(x => this.networkingService.getNetwork(x.network));
|
const observables = this.nics.map(x => this.networkingService.getNetwork(x.network));
|
||||||
|
|
||||||
|
this.loading = observables.length > 0;
|
||||||
|
|
||||||
forkJoin(observables)
|
forkJoin(observables)
|
||||||
.subscribe(networks =>
|
.subscribe(networks =>
|
||||||
{
|
{
|
||||||
|
this.networks = networks;
|
||||||
|
|
||||||
for (const nic of this.nics)
|
for (const nic of this.nics)
|
||||||
{
|
{
|
||||||
nic.networkDetails = networks.find(x => x.id === nic.network);
|
nic.networkDetails = networks.find(x => x.id === nic.network);
|
||||||
@ -147,7 +150,7 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
{
|
{
|
||||||
// Keep polling the newly created NIC until it reaches its "running"/"stopped" state
|
// Keep polling the newly created NIC until it reaches its "running"/"stopped" state
|
||||||
return this.networkingService
|
return this.networkingService
|
||||||
.getNicUntilExpectedState(this.instance, response.nic, ['running', 'stopped'], n => this.nics[0].state = n.state)
|
.getNicUntilAvailable(this.instance, response.nic, network.name, n => this.nics[0].state = n.state)
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(this.destroy$),
|
takeUntil(this.destroy$),
|
||||||
map(y => ({ network: response.network, nic: y }))
|
map(y => ({ network: response.network, nic: y }))
|
||||||
@ -224,8 +227,8 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
// If the machine is currently running, keep polling until it finishes restarting
|
// If the machine is currently running, keep polling until it finishes restarting
|
||||||
return this.instance.state === 'running'
|
return this.instance.state === 'running'
|
||||||
? this.instancesService
|
? this.instancesService
|
||||||
.getInstanceUntilExpectedState(this.instance, ['running'], x => this.instanceStateUpdate.emit(x))
|
.getInstanceUntilNicRemoved(this.instance, nic.networkName, x => this.instanceStateUpdate.emit(x))
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(delay(1000), takeUntil(this.destroy$))
|
||||||
: of(nic);
|
: of(nic);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -285,15 +288,17 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
ngOnInit(): void
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
this.nics = this.instance?.nics || [];
|
if (!this.instance.nics?.length || this.instance.networksLoaded)
|
||||||
|
|
||||||
if (this.instance.networksLoaded)
|
|
||||||
this.finishedLoading = true;
|
this.finishedLoading = true;
|
||||||
|
|
||||||
this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() =>
|
this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() =>
|
||||||
{
|
{
|
||||||
if (!this.finishedLoading && this.loadNetworks && !this.instance?.networksLoaded)
|
if (!this.finishedLoading && this.loadNetworks && !this.instance?.networksLoaded)
|
||||||
|
{
|
||||||
|
this.nics = this.instance?.nics || [];
|
||||||
|
|
||||||
this.getNetworks();
|
this.getNetworks();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Component, OnInit, OnDestroy, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
|
import { Component, OnInit, OnDestroy, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
|
||||||
import { InstancesService } from '../helpers/instances.service';
|
import { InstancesService } from '../helpers/instances.service';
|
||||||
import { ReplaySubject, Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
import { delay, first, switchMap, takeUntil, tap } from 'rxjs/operators';
|
import { delay, first, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||||
@ -27,7 +26,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
processing = new EventEmitter();
|
processing = new EventEmitter();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
processingFinished = new EventEmitter();
|
finishedProcessing = new EventEmitter();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
load = new EventEmitter();
|
load = new EventEmitter();
|
||||||
@ -51,7 +50,6 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
constructor(private readonly instancesService: InstancesService,
|
constructor(private readonly instancesService: InstancesService,
|
||||||
private readonly snapshotsService: SnapshotsService,
|
private readonly snapshotsService: SnapshotsService,
|
||||||
private readonly catalogService: CatalogService,
|
|
||||||
private readonly modalService: BsModalService,
|
private readonly modalService: BsModalService,
|
||||||
private readonly toastr: ToastrService)
|
private readonly toastr: ToastrService)
|
||||||
{
|
{
|
||||||
@ -97,7 +95,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
this.snapshots[index] = x;
|
this.snapshots[index] = x;
|
||||||
|
|
||||||
this.processingFinished.emit();
|
this.finishedProcessing.emit();
|
||||||
this.toastr.info(`A new snapshot "${snapshotName}" has been created for machine "${this.instance.name}"`);
|
this.toastr.info(`A new snapshot "${snapshotName}" has been created for machine "${this.instance.name}"`);
|
||||||
},
|
},
|
||||||
err =>
|
err =>
|
||||||
@ -108,7 +106,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
this.snapshots.splice(index, 1);
|
this.snapshots.splice(index, 1);
|
||||||
|
|
||||||
this.processingFinished.emit();
|
this.finishedProcessing.emit();
|
||||||
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -133,11 +131,15 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
switchMap(() => this.instancesService.getInstanceUntilExpectedState(this.instance, ['stopped'], x => this.instanceStateUpdate.emit(x))
|
switchMap(() => this.instancesService.getInstanceUntilExpectedState(this.instance, ['stopped'], x => this.instanceStateUpdate.emit(x))
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
)
|
)
|
||||||
).subscribe(() => this.startMachineFromSnapshot(snapshot),
|
).subscribe(() =>
|
||||||
|
{
|
||||||
|
snapshot.working = false;
|
||||||
|
this.startMachineFromSnapshot(snapshot);
|
||||||
|
},
|
||||||
err =>
|
err =>
|
||||||
{
|
{
|
||||||
snapshot.working = false;
|
snapshot.working = false;
|
||||||
this.processingFinished.emit();
|
this.finishedProcessing.emit();
|
||||||
|
|
||||||
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
@ -184,14 +186,14 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
{
|
{
|
||||||
snapshot.working = false;
|
snapshot.working = false;
|
||||||
|
|
||||||
this.processingFinished.emit();
|
this.finishedProcessing.emit();
|
||||||
|
|
||||||
this.toastr.info(`The machine "${this.instance.name}" has been started from the "${snapshot.name}" snapshot`);
|
this.toastr.info(`The machine "${this.instance.name}" has been started from the "${snapshot.name}" snapshot`);
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
snapshot.working = false;
|
snapshot.working = false;
|
||||||
|
|
||||||
this.processingFinished.emit();
|
this.finishedProcessing.emit();
|
||||||
|
|
||||||
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
this.toastr.error(`Machine "${this.instance.name}" error: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
@ -225,12 +227,12 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
this.snapshots.splice(index, 1);
|
this.snapshots.splice(index, 1);
|
||||||
|
|
||||||
this.processingFinished.emit();
|
this.finishedProcessing.emit();
|
||||||
|
|
||||||
this.toastr.info(`The "${snapshot.name}" snapshot has been deleted`);
|
this.toastr.info(`The "${snapshot.name}" snapshot has been deleted`);
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
this.processingFinished.emit();
|
this.finishedProcessing.emit();
|
||||||
|
|
||||||
this.toastr.error(`The "${snapshot.name}" snapshot couldn't be deleted: ${err.error.message}`);
|
this.toastr.error(`The "${snapshot.name}" snapshot couldn't be deleted: ${err.error.message}`);
|
||||||
});
|
});
|
||||||
@ -240,7 +242,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private getSnapshots()
|
private getSnapshots()
|
||||||
{
|
{
|
||||||
if (this.snapshotsLoaded) return
|
if (this.snapshotsLoaded || this.instance.state === 'provisioning') return
|
||||||
|
|
||||||
this.loadingSnapshots = true;
|
this.loadingSnapshots = true;
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
|
|
||||||
<div *ngIf="!instance.loading && instance.imageDetails"
|
<div *ngIf="!instance.loading && instance.imageDetails"
|
||||||
class="text-truncate small text-info text-faded mb-1" [tooltip]="instance.imageDetails.description"
|
class="text-truncate small text-info text-faded mb-1" [tooltip]="instance.imageDetails.description"
|
||||||
container="body" placement="top" [adaptivePosition]="false">
|
container="body" placement="top left" [adaptivePosition]="false">
|
||||||
{{ instance.imageDetails.name }}, v{{ instance.imageDetails.version }}
|
{{ instance.imageDetails.name }}, v{{ instance.imageDetails.version }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -149,14 +149,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex flex-nowrap justify-content-between align-items-center">
|
<div class="d-flex flex-nowrap justify-content-between align-items-center">
|
||||||
<a href="javascript:void(0)" class="badge text-uppercase"
|
<button class="badge text-uppercase" [disabled]="instance.state !== 'running' && instance.state !== 'stopped'"
|
||||||
[class.bg-light]="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'"
|
[class.bg-danger]="instance.state === 'stopped'" [class.bg-success]="instance.state === 'running'"
|
||||||
(click)="showMachineHistory(instance)" tooltip="{{ 'dashboard.listItem.history' | translate }}"
|
(click)="showMachineHistory(instance)" tooltip="{{ 'dashboard.listItem.history' | translate }}"
|
||||||
container="body" placement="top" [adaptivePosition]="false">
|
container="body" placement="top" [adaptivePosition]="false">
|
||||||
<fa-icon icon="history" [fixedWidth]="true"></fa-icon>
|
<fa-icon icon="history" [fixedWidth]="true"></fa-icon>
|
||||||
{{ instance.state }}
|
{{ instance.state }}
|
||||||
</a>
|
</button>
|
||||||
|
|
||||||
<div class="btn-group btn-group-sm" dropdown placement="bottom right" *ngIf="!instance.loading">
|
<div class="btn-group btn-group-sm" dropdown placement="bottom right" *ngIf="!instance.loading">
|
||||||
<button class="btn btn-link text-success" (click)="startMachine(instance)"
|
<button class="btn btn-link text-success" (click)="startMachine(instance)"
|
||||||
@ -178,20 +178,20 @@
|
|||||||
<div class="col mt-sm-0 mt-3 no-overflow-sm" *ngIf="showMachineDetails">
|
<div class="col mt-sm-0 mt-3 no-overflow-sm" *ngIf="showMachineDetails">
|
||||||
<div class="card-header p-0 h-100">
|
<div class="card-header p-0 h-100">
|
||||||
<tabset class="dashboard-tabs" *ngIf="!instance.loading">
|
<tabset class="dashboard-tabs" *ngIf="!instance.loading">
|
||||||
<tab customClass="dashboard-tab" (selectTab)="tabChanged($event, instance)"
|
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||||
id="{{ instance.id }}-info">
|
id="{{ instance.id }}-info">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="info-circle" class="d-sm-none"></fa-icon>
|
<fa-icon icon="info-circle" class="d-sm-none"></fa-icon>
|
||||||
<span class="d-none d-sm-inline-block ms-1">Info</span>
|
<span class="d-none d-sm-inline-block ms-1">Info</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="card-body p-2 h-100">
|
<div class="card-body p-2 h-100">
|
||||||
<app-instance-info [instance]="instance" [loadInfo]="instance.shouldLoadInfo"
|
<app-instance-info [instance]="instance" [loadInfo]="instance.shouldLoadInfo" [refresh]="instance.refreshInfo"
|
||||||
(load)="setInstanceInfo(instance, $event)" (processing)="instance.working = true"
|
(load)="setInstanceInfo(instance, $event)" (processing)="instance.working = true"
|
||||||
(finishedProcessing)="instance.working = false">
|
(finishedProcessing)="instance.working = false">
|
||||||
</app-instance-info>
|
</app-instance-info>
|
||||||
</div>
|
</div>
|
||||||
</tab>
|
</tab>
|
||||||
<tab customClass="dashboard-tab" (selectTab)="tabChanged($event, instance)"
|
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||||
id="{{ instance.id }}-networks">
|
id="{{ instance.id }}-networks">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="network-wired" class="d-sm-none"></fa-icon>
|
<fa-icon icon="network-wired" class="d-sm-none"></fa-icon>
|
||||||
@ -200,13 +200,13 @@
|
|||||||
<div class="card-body p-2 h-100">
|
<div class="card-body p-2 h-100">
|
||||||
<app-instance-networks [instance]="instance" [loadNetworks]="instance.shouldLoadNetworks"
|
<app-instance-networks [instance]="instance" [loadNetworks]="instance.shouldLoadNetworks"
|
||||||
(load)="setInstanceNetworks(instance, $event)" (processing)="instance.working = true"
|
(load)="setInstanceNetworks(instance, $event)" (processing)="instance.working = true"
|
||||||
(finishedProcessing)="instance.working = false"
|
(finishedProcessing)="refreshInstanceDnsList(instance)"
|
||||||
(instanceReboot)="watchInstanceState(instance)"
|
(instanceReboot)="watchInstanceState(instance)"
|
||||||
(instanceStateUpdate)="updateInstance(instance, $event)">
|
(instanceStateUpdate)="updateInstance(instance, $event)">
|
||||||
</app-instance-networks>
|
</app-instance-networks>
|
||||||
</div>
|
</div>
|
||||||
</tab>
|
</tab>
|
||||||
<tab customClass="dashboard-tab" (selectTab)="tabChanged($event, instance)"
|
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||||
id="{{ instance.id }}-snapshots" *ngIf="instance.brand !== 'kvm'">
|
id="{{ instance.id }}-snapshots" *ngIf="instance.brand !== 'kvm'">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="history" class="d-sm-none"></fa-icon>
|
<fa-icon icon="history" class="d-sm-none"></fa-icon>
|
||||||
@ -220,7 +220,7 @@
|
|||||||
</app-instance-snapshots>
|
</app-instance-snapshots>
|
||||||
</div>
|
</div>
|
||||||
</tab>
|
</tab>
|
||||||
<tab *ngIf="false" customClass="dashboard-tab" (selectTab)="tabChanged($event, instance)"
|
<tab *ngIf="false" customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||||
id="{{ instance.id }}-migrations">
|
id="{{ instance.id }}-migrations">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="coins" class="d-sm-none"></fa-icon>
|
<fa-icon icon="coins" class="d-sm-none"></fa-icon>
|
||||||
@ -230,7 +230,7 @@
|
|||||||
<button class="btn btn-outline-info w-100">Move to another node</button>
|
<button class="btn btn-outline-info w-100">Move to another node</button>
|
||||||
</div>
|
</div>
|
||||||
</tab>
|
</tab>
|
||||||
<tab customClass="dashboard-tab" (selectTab)="tabChanged($event, instance)"
|
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||||
id="{{ instance.id }}-volumes" *ngIf="instance.volumes && instance.volumes.length">
|
id="{{ instance.id }}-volumes" *ngIf="instance.volumes && instance.volumes.length">
|
||||||
<ng-template tabHeading>
|
<ng-template tabHeading>
|
||||||
<fa-icon icon="database" class="d-sm-none"></fa-icon>
|
<fa-icon icon="database" class="d-sm-none"></fa-icon>
|
||||||
|
@ -209,7 +209,7 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.badge
|
button.badge
|
||||||
{
|
{
|
||||||
text-decoration: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import { ConfirmationDialogComponent } from '../components/confirmation-dialog/c
|
|||||||
import { InstanceHistoryComponent } from './instance-history/instance-history.component';
|
import { InstanceHistoryComponent } from './instance-history/instance-history.component';
|
||||||
import { CustomImageEditorComponent } from '../catalog/custom-image-editor/custom-image-editor.component';
|
import { CustomImageEditorComponent } from '../catalog/custom-image-editor/custom-image-editor.component';
|
||||||
import { VirtualScrollerComponent } from 'ngx-virtual-scroller';
|
import { VirtualScrollerComponent } from 'ngx-virtual-scroller';
|
||||||
import { FormGroup, FormBuilder, Validators, AbstractControl, FormArray } from '@angular/forms';
|
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import { LabelType, Options } from '@angular-slider/ngx-slider';
|
import { LabelType, Options } from '@angular-slider/ngx-slider';
|
||||||
import { FileSizePipe } from '../pipes/file-size.pipe';
|
import { FileSizePipe } from '../pipes/file-size.pipe';
|
||||||
@ -660,6 +660,8 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
{
|
{
|
||||||
if (!x) return;
|
if (!x) return;
|
||||||
|
|
||||||
|
x.working = true;
|
||||||
|
|
||||||
this.fillInInstanceDetails(x);
|
this.fillInInstanceDetails(x);
|
||||||
|
|
||||||
this.instances.push(x);
|
this.instances.push(x);
|
||||||
@ -757,6 +759,9 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
{
|
{
|
||||||
instance.state = x.state;
|
instance.state = x.state;
|
||||||
|
|
||||||
|
// This allows us to trigger later on when the state changes to a something stable
|
||||||
|
instance.shouldLoadInfo = false;
|
||||||
|
|
||||||
this.computeFiltersOptions(true);
|
this.computeFiltersOptions(true);
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
@ -765,13 +770,11 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
instance.working = false;
|
instance.working = false;
|
||||||
|
|
||||||
// Update the instance with what we got from the server
|
// Update the instance with what we got from the server
|
||||||
const index = this.instances.findIndex(i => i.id === instance.id);
|
Object.assign(instance, x);
|
||||||
if (index >= 0)
|
|
||||||
{
|
|
||||||
this.instances.splice(index, 1, x);
|
|
||||||
|
|
||||||
this.computeFiltersOptions();
|
instance.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
||||||
}
|
|
||||||
|
this.computeFiltersOptions();
|
||||||
}, err =>
|
}, err =>
|
||||||
{
|
{
|
||||||
if (err.status === 410)
|
if (err.status === 410)
|
||||||
@ -792,7 +795,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
instance.working = false;
|
instance.working = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.shouldLoadInfo = true;
|
instance.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
@ -818,6 +821,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
updateInstance(instance: Instance, updates: Instance)
|
updateInstance(instance: Instance, updates: Instance)
|
||||||
{
|
{
|
||||||
|
instance.refreshInfo = instance.state !== updates.state;
|
||||||
instance.state = updates.state;
|
instance.state = updates.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -840,7 +844,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
setInstanceSnapshot(instance: Instance, snapshots)
|
setInstanceSnapshots(instance: Instance, 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 instance as a result of the snapshots panel's "load" event. We do this because the intances are (un)loaded
|
||||||
// from the viewport as the user scrolls through the page, to optimize memory consumption.
|
// from the viewport as the user scrolls through the page, to optimize memory consumption.
|
||||||
@ -848,6 +852,13 @@ export class InstancesComponent implements OnInit, OnDestroy
|
|||||||
instance.snapshotsLoaded = true;
|
instance.snapshotsLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
|
refreshInstanceDnsList(instance: Instance)
|
||||||
|
{
|
||||||
|
instance.working = false;
|
||||||
|
instance.refreshInfo = true;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
private fillInInstanceDetails(instance: Instance)
|
private fillInInstanceDetails(instance: Instance)
|
||||||
{
|
{
|
||||||
|
@ -42,8 +42,10 @@ export class Instance extends InstanceRequest
|
|||||||
working: boolean;
|
working: boolean;
|
||||||
shouldLoadInfo: boolean;
|
shouldLoadInfo: boolean;
|
||||||
infoLoaded: boolean;
|
infoLoaded: boolean;
|
||||||
|
refreshInfo: boolean;
|
||||||
shouldLoadNetworks: boolean;
|
shouldLoadNetworks: boolean;
|
||||||
networksLoaded: boolean;
|
networksLoaded: boolean;
|
||||||
|
refreshNetworks: boolean;
|
||||||
shouldLoadSnapshots: boolean;
|
shouldLoadSnapshots: boolean;
|
||||||
snapshotsLoaded: boolean;
|
snapshotsLoaded: boolean;
|
||||||
volumesEnabled: boolean;
|
volumesEnabled: boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { delay, filter, first, flatMap, map, mergeMapTo, repeatWhen, switchMap, switchMapTo, take, takeUntil, tap } from 'rxjs/operators';
|
import { concatMap, delay, filter, first, flatMap, map, mergeMapTo, repeatWhen, switchMap, switchMapTo, take, takeUntil, tap } from 'rxjs/operators';
|
||||||
import { concat, empty, of, range, throwError, zip } from 'rxjs';
|
import { concat, empty, of, range, throwError, zip } from 'rxjs';
|
||||||
import { Cacheable } from 'ts-cacheable';
|
import { Cacheable } from 'ts-cacheable';
|
||||||
import { Network } from '../models/network';
|
import { Network } from '../models/network';
|
||||||
@ -10,6 +10,8 @@ import { VirtualAreaNetwork } from '../models/vlan';
|
|||||||
import { VirtualAreaNetworkRequest } from '../models/vlan';
|
import { VirtualAreaNetworkRequest } from '../models/vlan';
|
||||||
import { EditNetworkRequest } from '../models/network';
|
import { EditNetworkRequest } from '../models/network';
|
||||||
import { AddNetworkRequest } from '../models/network';
|
import { AddNetworkRequest } from '../models/network';
|
||||||
|
import { Instance } from 'src/app/instances/models/instance';
|
||||||
|
import { InstanceCallbackFunction } from 'src/app/instances/helpers/instances.service';
|
||||||
|
|
||||||
const networksCacheBuster$ = new Subject<void>();
|
const networksCacheBuster$ = new Subject<void>();
|
||||||
|
|
||||||
@ -145,12 +147,22 @@ export class NetworkingService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------------------------------------
|
||||||
getNicUntilExpectedState(instance: any, nic: Nic, expectedStates: string[], callbackFn?: NicCallbackFunction, maxRetries = 30): Observable<Nic>
|
getNicUntilAvailable(instance: any, nic: Nic, networkName: string, callbackFn?: NicCallbackFunction, maxRetries = 30): Observable<Nic>
|
||||||
{
|
{
|
||||||
// Keep polling the snapshot until it reaches the expected state
|
networkName = networkName.toLocaleLowerCase();
|
||||||
|
|
||||||
|
// Keep polling the instance until it reaches the expected state
|
||||||
return this.getNic(instance.id, nic.mac)
|
return this.getNic(instance.id, nic.mac)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(x => callbackFn && callbackFn(x)),
|
tap(x =>
|
||||||
|
{
|
||||||
|
// We create our own state while the instance reboots
|
||||||
|
if (x.state === 'running')
|
||||||
|
x.state = 'starting';
|
||||||
|
|
||||||
|
if (callbackFn)
|
||||||
|
callbackFn(x);
|
||||||
|
}),
|
||||||
repeatWhen(x =>
|
repeatWhen(x =>
|
||||||
{
|
{
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
@ -164,8 +176,46 @@ export class NetworkingService
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
filter(x => expectedStates.includes(x.state)),
|
filter(x => x.state === 'running' || x.state === 'starting'),
|
||||||
take(1) // needed to stop the repeatWhen loop
|
take(1), // needed to stop the repeatWhen loop
|
||||||
|
concatMap(nic =>
|
||||||
|
this.httpClient.get<Instance>(`/api/my/machines/${instance.id}`)
|
||||||
|
.pipe(
|
||||||
|
tap(() =>
|
||||||
|
{
|
||||||
|
// We create our own state while the instance reboots
|
||||||
|
nic.state = 'starting';
|
||||||
|
|
||||||
|
if (callbackFn)
|
||||||
|
callbackFn(nic);
|
||||||
|
}),
|
||||||
|
repeatWhen(x =>
|
||||||
|
{
|
||||||
|
let retries = 0;
|
||||||
|
|
||||||
|
return x.pipe(
|
||||||
|
delay(3000),
|
||||||
|
map(() =>
|
||||||
|
{
|
||||||
|
if (retries++ === maxRetries)
|
||||||
|
throw { error: { message: `Failed to retrieve the current status for network interface "${nic.mac}"` } };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
filter(x => x.state === 'running' && x.dns_names.some(d => d.toLocaleLowerCase().indexOf(networkName) >= 0)),
|
||||||
|
take(1), // needed to stop the repeatWhen loop
|
||||||
|
map(() =>
|
||||||
|
{
|
||||||
|
// We manually set the state as "running" now that the instance has rebooted
|
||||||
|
nic.state = 'running';
|
||||||
|
|
||||||
|
if (callbackFn)
|
||||||
|
callbackFn(nic);
|
||||||
|
|
||||||
|
return nic;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
<ul *dropdownMenu class="dropdown-menu dropdown-menu-right" role="menu">
|
<ul *dropdownMenu class="dropdown-menu dropdown-menu-right" role="menu">
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="showPolicyEditor(policy)">
|
<button class="dropdown-item" (click)="showPolicyEditor(policy)">
|
||||||
<fa-icon [fixedWidth]="true" icon="pen-nib"></fa-icon>
|
<fa-icon [fixedWidth]="true" icon="pen"></fa-icon>
|
||||||
{{ 'security.editPolicy' | translate }}
|
{{ 'security.editPolicy' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
@ -114,7 +114,7 @@
|
|||||||
<ul *dropdownMenu class="dropdown-menu dropdown-menu-right" role="menu">
|
<ul *dropdownMenu class="dropdown-menu dropdown-menu-right" role="menu">
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="showRoleEditor(role)">
|
<button class="dropdown-item" (click)="showRoleEditor(role)">
|
||||||
<fa-icon [fixedWidth]="true" icon="pen-nib"></fa-icon>
|
<fa-icon [fixedWidth]="true" icon="pen"></fa-icon>
|
||||||
{{ 'security.editRole' | translate }}
|
{{ 'security.editRole' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
@ -195,13 +195,13 @@
|
|||||||
<ul *dropdownMenu class="dropdown-menu dropdown-menu-right" role="menu">
|
<ul *dropdownMenu class="dropdown-menu dropdown-menu-right" role="menu">
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="showUserEditor(user)">
|
<button class="dropdown-item" (click)="showUserEditor(user)">
|
||||||
<fa-icon [fixedWidth]="true" icon="pen-nib"></fa-icon>
|
<fa-icon [fixedWidth]="true" icon="pen"></fa-icon>
|
||||||
{{ 'security.editUser' | translate }}
|
{{ 'security.editUser' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li role="menuitem">
|
<li role="menuitem">
|
||||||
<button class="dropdown-item" (click)="showUserEditor(user, true)">
|
<button class="dropdown-item" (click)="showUserEditor(user, true)">
|
||||||
<fa-icon [fixedWidth]="true" icon="pen-nib"></fa-icon>
|
<fa-icon [fixedWidth]="true" icon="pen"></fa-icon>
|
||||||
{{ 'security.changePassword' | translate }}
|
{{ 'security.changePassword' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -172,24 +172,27 @@ body, div, virtual-scroller
|
|||||||
.dropdown-item
|
.dropdown-item
|
||||||
{
|
{
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:focus, &:hover
|
.dropdown-item
|
||||||
{
|
{
|
||||||
color: #1e2125;
|
&:focus, &:hover
|
||||||
background-color: rgba(255,255,255,.75);
|
{
|
||||||
}
|
color: #1e2125;
|
||||||
|
background-color: rgba(255,255,255,.75);
|
||||||
|
}
|
||||||
|
|
||||||
&.active, &:active
|
&.active, &:active
|
||||||
{
|
{
|
||||||
color: #0dcaf0;
|
color: #0dcaf0;
|
||||||
background-color: #101a30;
|
background-color: #101a30;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ng-fa-icon
|
.ng-fa-icon
|
||||||
{
|
{
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user