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>
|
||||
{
|
||||
@ -230,6 +264,12 @@ export class InstancesService
|
||||
{
|
||||
return this.httpClient.get(`./assets/data/packages.json`);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
clearCache()
|
||||
{
|
||||
instancesCacheBuster$.next();
|
||||
}
|
||||
}
|
||||
|
||||
export type InstanceCallbackFunction = ((instance: Instance) => void);
|
||||
|
@ -4,7 +4,7 @@
|
||||
<b>{{ instance.id }}</b>
|
||||
</li>
|
||||
|
||||
<ng-container *ngIf="instance.dnsList">
|
||||
<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">
|
||||
|
@ -20,6 +20,9 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
@Input()
|
||||
loadInfo: boolean;
|
||||
|
||||
@Input()
|
||||
refresh: boolean;
|
||||
|
||||
@Output()
|
||||
processing = new EventEmitter();
|
||||
|
||||
@ -30,7 +33,8 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
load = new EventEmitter();
|
||||
|
||||
loading: boolean;
|
||||
|
||||
dnsCount: number;
|
||||
|
||||
private finishedLoading: boolean;
|
||||
private destroy$ = new Subject();
|
||||
private onChanges$ = new ReplaySubject();
|
||||
@ -70,10 +74,13 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
private getInfo()
|
||||
{
|
||||
if (this.finishedLoading) return;
|
||||
if (this.finishedLoading || this.instance.state === 'provisioning') return;
|
||||
|
||||
this.loading = true;
|
||||
|
||||
if (this.refresh)
|
||||
this.instancesService.clearCache();
|
||||
|
||||
this.instancesService.getById(this.instance.id)
|
||||
.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)))
|
||||
dnsList[dns] = this.getParsedDns(dns);
|
||||
|
||||
this.dnsCount = Object.keys(dnsList).length;
|
||||
|
||||
this.instance.dnsList = dnsList;
|
||||
|
||||
this.loading = false;
|
||||
@ -112,7 +121,13 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
{
|
||||
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();
|
||||
});
|
||||
}
|
||||
@ -124,7 +139,6 @@ export class InstanceInfoComponent implements OnInit, OnDestroy, OnChanges
|
||||
this.onChanges$.next(changes);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
ngOnDestroy()
|
||||
{
|
||||
|
@ -39,13 +39,14 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
instanceStateUpdate = new EventEmitter();
|
||||
|
||||
loading: boolean;
|
||||
nics: Nic[];
|
||||
nics: Nic[] = [];
|
||||
publicNetworks: Network[] = [];
|
||||
fabricNetworks: Network[] = [];
|
||||
finishedLoading: boolean;
|
||||
|
||||
private destroy$ = new Subject();
|
||||
private onChanges$ = new ReplaySubject();
|
||||
private networks: Network[];
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
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;
|
||||
|
||||
this.loading = true;
|
||||
if ((this.finishedLoading || this.instance.state === 'provisioning') && !force) return;
|
||||
|
||||
const observables = this.nics.map(x => this.networkingService.getNetwork(x.network));
|
||||
|
||||
this.loading = observables.length > 0;
|
||||
|
||||
forkJoin(observables)
|
||||
.subscribe(networks =>
|
||||
{
|
||||
this.networks = networks;
|
||||
|
||||
for (const nic of this.nics)
|
||||
{
|
||||
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
|
||||
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(
|
||||
takeUntil(this.destroy$),
|
||||
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
|
||||
return this.instance.state === 'running'
|
||||
? this.instancesService
|
||||
.getInstanceUntilExpectedState(this.instance, ['running'], x => this.instanceStateUpdate.emit(x))
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.getInstanceUntilNicRemoved(this.instance, nic.networkName, x => this.instanceStateUpdate.emit(x))
|
||||
.pipe(delay(1000), takeUntil(this.destroy$))
|
||||
: of(nic);
|
||||
})
|
||||
);
|
||||
@ -285,15 +288,17 @@ export class InstanceNetworksComponent implements OnInit, OnDestroy, OnChanges
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
ngOnInit(): void
|
||||
{
|
||||
this.nics = this.instance?.nics || [];
|
||||
|
||||
if (this.instance.networksLoaded)
|
||||
if (!this.instance.nics?.length || this.instance.networksLoaded)
|
||||
this.finishedLoading = true;
|
||||
|
||||
this.onChanges$.pipe(takeUntil(this.destroy$)).subscribe(() =>
|
||||
{
|
||||
if (!this.finishedLoading && this.loadNetworks && !this.instance?.networksLoaded)
|
||||
{
|
||||
this.nics = this.instance?.nics || [];
|
||||
|
||||
this.getNetworks();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Component, OnInit, OnDestroy, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { CatalogService } from '../../catalog/helpers/catalog.service';
|
||||
import { InstancesService } from '../helpers/instances.service';
|
||||
import { ReplaySubject, Subject } from 'rxjs';
|
||||
import { delay, first, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||
@ -27,7 +26,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
processing = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
processingFinished = new EventEmitter();
|
||||
finishedProcessing = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
load = new EventEmitter();
|
||||
@ -51,7 +50,6 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
constructor(private readonly instancesService: InstancesService,
|
||||
private readonly snapshotsService: SnapshotsService,
|
||||
private readonly catalogService: CatalogService,
|
||||
private readonly modalService: BsModalService,
|
||||
private readonly toastr: ToastrService)
|
||||
{
|
||||
@ -97,7 +95,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
if (index >= 0)
|
||||
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}"`);
|
||||
},
|
||||
err =>
|
||||
@ -108,7 +106,7 @@ export class InstanceSnapshotsComponent implements OnInit, OnDestroy, OnChanges
|
||||
if (index >= 0)
|
||||
this.snapshots.splice(index, 1);
|
||||
|
||||
this.processingFinished.emit();
|
||||
this.finishedProcessing.emit();
|
||||
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))
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
)
|
||||
).subscribe(() => this.startMachineFromSnapshot(snapshot),
|
||||
).subscribe(() =>
|
||||
{
|
||||
snapshot.working = false;
|
||||
this.startMachineFromSnapshot(snapshot);
|
||||
},
|
||||
err =>
|
||||
{
|
||||
snapshot.working = false;
|
||||
this.processingFinished.emit();
|
||||
this.finishedProcessing.emit();
|
||||
|
||||
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;
|
||||
|
||||
this.processingFinished.emit();
|
||||
this.finishedProcessing.emit();
|
||||
|
||||
this.toastr.info(`The machine "${this.instance.name}" has been started from the "${snapshot.name}" snapshot`);
|
||||
}, err =>
|
||||
{
|
||||
snapshot.working = false;
|
||||
|
||||
this.processingFinished.emit();
|
||||
this.finishedProcessing.emit();
|
||||
|
||||
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)
|
||||
this.snapshots.splice(index, 1);
|
||||
|
||||
this.processingFinished.emit();
|
||||
this.finishedProcessing.emit();
|
||||
|
||||
this.toastr.info(`The "${snapshot.name}" snapshot has been deleted`);
|
||||
}, err =>
|
||||
{
|
||||
this.processingFinished.emit();
|
||||
this.finishedProcessing.emit();
|
||||
|
||||
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()
|
||||
{
|
||||
if (this.snapshotsLoaded) return
|
||||
if (this.snapshotsLoaded || this.instance.state === 'provisioning') return
|
||||
|
||||
this.loadingSnapshots = true;
|
||||
|
||||
|
@ -108,7 +108,7 @@
|
||||
|
||||
<div *ngIf="!instance.loading && instance.imageDetails"
|
||||
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 }}
|
||||
</div>
|
||||
|
||||
@ -149,14 +149,14 @@
|
||||
</div>
|
||||
|
||||
<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-danger]="instance.state === 'stopped'" [class.bg-success]="instance.state === 'running'"
|
||||
(click)="showMachineHistory(instance)" tooltip="{{ 'dashboard.listItem.history' | translate }}"
|
||||
container="body" placement="top" [adaptivePosition]="false">
|
||||
<fa-icon icon="history" [fixedWidth]="true"></fa-icon>
|
||||
{{ instance.state }}
|
||||
</a>
|
||||
</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)"
|
||||
@ -178,20 +178,20 @@
|
||||
<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" (selectTab)="tabChanged($event, instance)"
|
||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||
id="{{ instance.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"
|
||||
<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>
|
||||
</div>
|
||||
</tab>
|
||||
<tab customClass="dashboard-tab" (selectTab)="tabChanged($event, instance)"
|
||||
<tab customClass="dashboard-tab" [disabled]="instance.working" (selectTab)="tabChanged($event, instance)"
|
||||
id="{{ instance.id }}-networks">
|
||||
<ng-template tabHeading>
|
||||
<fa-icon icon="network-wired" class="d-sm-none"></fa-icon>
|
||||
@ -200,13 +200,13 @@
|
||||
<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)="instance.working = false"
|
||||
(finishedProcessing)="refreshInstanceDnsList(instance)"
|
||||
(instanceReboot)="watchInstanceState(instance)"
|
||||
(instanceStateUpdate)="updateInstance(instance, $event)">
|
||||
</app-instance-networks>
|
||||
</div>
|
||||
</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'">
|
||||
<ng-template tabHeading>
|
||||
<fa-icon icon="history" class="d-sm-none"></fa-icon>
|
||||
@ -220,7 +220,7 @@
|
||||
</app-instance-snapshots>
|
||||
</div>
|
||||
</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">
|
||||
<ng-template tabHeading>
|
||||
<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>
|
||||
</div>
|
||||
</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">
|
||||
<ng-template tabHeading>
|
||||
<fa-icon icon="database" class="d-sm-none"></fa-icon>
|
||||
|
@ -209,7 +209,7 @@
|
||||
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 { CustomImageEditorComponent } from '../catalog/custom-image-editor/custom-image-editor.component';
|
||||
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 { LabelType, Options } from '@angular-slider/ngx-slider';
|
||||
import { FileSizePipe } from '../pipes/file-size.pipe';
|
||||
@ -660,6 +660,8 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
{
|
||||
if (!x) return;
|
||||
|
||||
x.working = true;
|
||||
|
||||
this.fillInInstanceDetails(x);
|
||||
|
||||
this.instances.push(x);
|
||||
@ -757,6 +759,9 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
{
|
||||
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);
|
||||
})
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
@ -765,13 +770,11 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
instance.working = false;
|
||||
|
||||
// Update the instance with what we got from the server
|
||||
const index = this.instances.findIndex(i => i.id === instance.id);
|
||||
if (index >= 0)
|
||||
{
|
||||
this.instances.splice(index, 1, x);
|
||||
Object.assign(instance, x);
|
||||
|
||||
this.computeFiltersOptions();
|
||||
}
|
||||
instance.shouldLoadInfo = this.editorForm.get('showMachineDetails').value;
|
||||
|
||||
this.computeFiltersOptions();
|
||||
}, err =>
|
||||
{
|
||||
if (err.status === 410)
|
||||
@ -792,7 +795,7 @@ export class InstancesComponent implements OnInit, OnDestroy
|
||||
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)
|
||||
{
|
||||
instance.refreshInfo = 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
|
||||
// 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;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
refreshInstanceDnsList(instance: Instance)
|
||||
{
|
||||
instance.working = false;
|
||||
instance.refreshInfo = true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
private fillInInstanceDetails(instance: Instance)
|
||||
{
|
||||
|
@ -42,8 +42,10 @@ export class Instance extends InstanceRequest
|
||||
working: boolean;
|
||||
shouldLoadInfo: boolean;
|
||||
infoLoaded: boolean;
|
||||
refreshInfo: boolean;
|
||||
shouldLoadNetworks: boolean;
|
||||
networksLoaded: boolean;
|
||||
refreshNetworks: boolean;
|
||||
shouldLoadSnapshots: boolean;
|
||||
snapshotsLoaded: boolean;
|
||||
volumesEnabled: boolean;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
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 { Cacheable } from 'ts-cacheable';
|
||||
import { Network } from '../models/network';
|
||||
@ -10,6 +10,8 @@ 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';
|
||||
|
||||
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)
|
||||
.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 =>
|
||||
{
|
||||
let retries = 0;
|
||||
@ -164,8 +176,46 @@ export class NetworkingService
|
||||
})
|
||||
);
|
||||
}),
|
||||
filter(x => expectedStates.includes(x.state)),
|
||||
take(1) // needed to stop the repeatWhen loop
|
||||
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}`)
|
||||
.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">
|
||||
<li role="menuitem">
|
||||
<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 }}
|
||||
</button>
|
||||
</li>
|
||||
@ -114,7 +114,7 @@
|
||||
<ul *dropdownMenu class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li role="menuitem">
|
||||
<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 }}
|
||||
</button>
|
||||
</li>
|
||||
@ -195,13 +195,13 @@
|
||||
<ul *dropdownMenu class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<li role="menuitem">
|
||||
<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 }}
|
||||
</button>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<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 }}
|
||||
</button>
|
||||
</li>
|
||||
|
@ -172,24 +172,27 @@ body, div, virtual-scroller
|
||||
.dropdown-item
|
||||
{
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus, &:hover
|
||||
{
|
||||
color: #1e2125;
|
||||
background-color: rgba(255,255,255,.75);
|
||||
}
|
||||
.dropdown-item
|
||||
{
|
||||
&:focus, &:hover
|
||||
{
|
||||
color: #1e2125;
|
||||
background-color: rgba(255,255,255,.75);
|
||||
}
|
||||
|
||||
&.active, &:active
|
||||
{
|
||||
color: #0dcaf0;
|
||||
background-color: #101a30;
|
||||
}
|
||||
&.active, &:active
|
||||
{
|
||||
color: #0dcaf0;
|
||||
background-color: #101a30;
|
||||
}
|
||||
|
||||
.ng-fa-icon
|
||||
{
|
||||
font-size: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ng-fa-icon
|
||||
{
|
||||
font-size: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user