Merge pull request #52919 from rhcs-dashboard/cluster-upgrade-progress

mgr/dashboard: cluster upgrade progress UI

Reviewed-by: Aashish Sharma <aasharma@redhat.com>
Reviewed-by: cloudbehl <NOT@FOUND>
Reviewed-by: Nizamudeen A <nia@redhat.com>
This commit is contained in:
Nizamudeen A 2023-08-15 21:38:10 +05:30 committed by GitHub
commit 6aeeb4ac40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 495 additions and 36 deletions

View File

@ -47,6 +47,7 @@ import { ModuleStatusGuardService } from './shared/services/module-status-guard.
import { NoSsoGuardService } from './shared/services/no-sso-guard.service';
import { UpgradeComponent } from './ceph/cluster/upgrade/upgrade.component';
import { CephfsVolumeFormComponent } from './ceph/cephfs/cephfs-form/cephfs-form.component';
import { UpgradeProgressComponent } from './ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component';
@Injectable()
export class PerformanceCounterBreadcrumbsResolver extends BreadcrumbsResolver {
@ -287,7 +288,6 @@ const routes: Routes = [
{
path: 'upgrade',
canActivate: [ModuleStatusGuardService],
component: UpgradeComponent,
data: {
moduleStatusGuardConfig: {
uiApiPath: 'orchestrator',
@ -298,7 +298,18 @@ const routes: Routes = [
header: 'Orchestrator is not available'
},
breadcrumbs: 'Cluster/Upgrade'
}
},
children: [
{
path: '',
component: UpgradeComponent
},
{
path: 'progress',
component: UpgradeProgressComponent,
data: { breadcrumbs: 'Progress' }
}
]
},
{
path: 'perf_counters/:type/:id',

View File

@ -10,6 +10,7 @@ import {
NgbDropdownModule,
NgbNavModule,
NgbPopoverModule,
NgbProgressbarModule,
NgbTimepickerModule,
NgbTooltipModule,
NgbTypeaheadModule
@ -59,6 +60,7 @@ import { ServicesComponent } from './services/services.component';
import { TelemetryComponent } from './telemetry/telemetry.component';
import { UpgradeComponent } from './upgrade/upgrade.component';
import { UpgradeStartModalComponent } from './upgrade/upgrade-form/upgrade-start-modal.component';
import { UpgradeProgressComponent } from './upgrade/upgrade-progress/upgrade-progress.component';
@NgModule({
imports: [
@ -78,7 +80,8 @@ import { UpgradeStartModalComponent } from './upgrade/upgrade-form/upgrade-start
NgbDatepickerModule,
NgbPopoverModule,
NgbDropdownModule,
NgxPipeFunctionModule
NgxPipeFunctionModule,
NgbProgressbarModule
],
declarations: [
HostsComponent,
@ -120,7 +123,8 @@ import { UpgradeStartModalComponent } from './upgrade/upgrade-form/upgrade-start
CreateClusterComponent,
CreateClusterReviewComponent,
UpgradeComponent,
UpgradeStartModalComponent
UpgradeStartModalComponent,
UpgradeProgressComponent
],
providers: [NgbActiveModal]
})

View File

@ -10,8 +10,13 @@
[formGroup]="upgradeForm"
novalidate>
<div class="modal-body">
<cd-alert-panel type="warning"
spacingClass="mb-3"
*ngIf="showImageField"
i18n>Make sure to put the correct image. Passing an incorrect image can lead the cluster into an undesired state.</cd-alert-panel>
<div class="form-group row">
<label class="cd-col-form-label required"
<label class="cd-col-form-label"
[ngClass]="{'required': !showImageField}"
for="availableVersions"
i18n>New Version</label>
<div class="cd-col-form-input">
@ -36,11 +41,44 @@
i18n>This field is required!</span>
</div>
</div>
<div class="form-group row">
<div class="cd-col-form-offset">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="custom-control-input"
id="useImage"
name="useImage"
formControlName="useImage"
(click)="useImage()">
<label class="custom-control-label"
for="useImage"
i18n>Use image</label>
</div>
</div>
</div>
<!-- Custom image name input-->
<div class="form-group row"
*ngIf="showImageField">
<label class="cd-col-form-label required"
for="customImageName"
i18n>Image</label>
<div class="cd-col-form-input">
<input type="text"
class="form-control"
id="customImageName"
name="customImageName"
formControlName="customImageName">
<span class="invalid-feedback"
*ngIf="upgradeForm.showError('customImageName', formDir, 'required')"
i18n>This field is required!</span>
</div>
</div>
</div>
<div class="modal-footer">
<cd-form-button-panel *ngIf="versions"
(submitActionEvent)="startUpgrade()"
<cd-form-button-panel (submitActionEvent)="startUpgrade()"
[form]="upgradeForm"
[submitText]="actionLabels.START_UPGRADE"></cd-form-button-panel>
</div>

View File

@ -7,13 +7,14 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { SharedModule } from '~/app/shared/shared.module';
import { ToastrModule } from 'ngx-toastr';
import { RouterTestingModule } from '@angular/router/testing';
describe('UpgradeComponent', () => {
let component: UpgradeComponent;
let fixture: ComponentFixture<UpgradeComponent>;
configureTestBed({
imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot()],
imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot(), RouterTestingModule],
schemas: [NO_ERRORS_SCHEMA],
declarations: [UpgradeComponent],
providers: [UpgradeService]

View File

@ -26,6 +26,8 @@ export class UpgradeStartModalComponent implements OnInit {
icons = Icons;
versions: string[];
showImageField = false;
constructor(
public actionLabels: ActionLabelsI18n,
private authStorageService: AuthStorageService,
@ -38,12 +40,16 @@ export class UpgradeStartModalComponent implements OnInit {
ngOnInit() {
this.upgradeForm = new CdFormGroup({
availableVersions: new FormControl(null, [Validators.required])
availableVersions: new FormControl(null, [Validators.required]),
useImage: new FormControl(false),
customImageName: new FormControl(null)
});
}
startUpgrade() {
this.upgradeService.start(this.upgradeForm.getValue('availableVersions')).subscribe({
const version = this.upgradeForm.getValue('availableVersions');
const image = this.upgradeForm.getValue('customImageName');
this.upgradeService.start(version, image).subscribe({
next: () => {
this.notificationService.show(
NotificationType.success,
@ -63,4 +69,24 @@ export class UpgradeStartModalComponent implements OnInit {
}
});
}
useImage() {
this.showImageField = !this.showImageField;
const availableVersionsControl = this.upgradeForm.get('availableVersions');
const customImageNameControl = this.upgradeForm.get('customImageName');
if (this.showImageField) {
availableVersionsControl.disable();
availableVersionsControl.clearValidators();
customImageNameControl.setValidators(Validators.required);
customImageNameControl.updateValueAndValidity();
} else {
availableVersionsControl.enable();
availableVersionsControl.setValidators(Validators.required);
availableVersionsControl.updateValueAndValidity();
customImageNameControl.clearValidators();
}
}
}

View File

@ -0,0 +1,89 @@
<div class="d-flex flex-column justify-content-center align-items-center bold"
*ngIf="upgradeStatus$ | async as upgradeStatus">
<ng-container *ngIf="upgradeStatus.in_progress && !upgradeStatus.is_paused; else upgradePaused">
<h3 class="text-center"
i18n>
<i [ngClass]="[icons.large, icons.spin, icons.spinner]"></i>
</h3>
<h3 class="text-center mt-2">
{{ executingTask?.description }}
</h3>
<h5 class="text-center mt-3"
i18n>{{ upgradeStatus.which }}</h5>
</ng-container>
<div class="w-50 row h-100 d-flex justify-content-center align-items-center mt-4">
<div class="text-center w-75">
<ng-container *ngIf="upgradeStatus.services_complete.length > 0">
Finished upgrading:
<span class="text-success">
{{ upgradeStatus.services_complete }}
</span>
</ng-container>
<div class="mt-2">
<ngb-progressbar type="info"
[value]="executingTask?.progress"
[striped]="true"
[animated]="!upgradeStatus.is_paused"></ngb-progressbar>
</div>
<p class="card-text text-muted">
<span class="float-end">
{{ executingTask?.progress || 0 }} %
</span>
</p>
</div>
<h4 class="text-center m-2"
i18n>{{ upgradeStatus.progress}}</h4>
<h5 *ngIf="upgradeStatus.in_progress"
class="text-center mt-2"
i18n>
{{ upgradeStatus.message }}
</h5>
<div class="text-center mt-3">
<button class="btn btn-light"
aria-label="Go back"
routerLink="/upgrade"
i18n>Back</button>
<button *ngIf="upgradeStatus.in_progress && !upgradeStatus.is_paused"
(click)="pauseUpgrade()"
class="btn btn-light m-2"
aria-label="Pause Upgrade"
i18n>Pause</button>
<button *ngIf="upgradeStatus.in_progress && upgradeStatus.is_paused"
(click)="resumeUpgrade()"
class="btn btn-light m-2"
aria-label="Resume Upgrade"
i18n>Resume</button>
<button *ngIf="upgradeStatus.in_progress"
(click)="stopUpgradeModal()"
class="btn btn-danger"
aria-label="Stop Upgrade"
i18n>Stop</button>
</div>
</div>
</div>
<legend class="cd-header"
i18n>Cluster logs</legend>
<cd-logs [showAuditLogs]="false"
[showDaemonLogs]="false"
[showNavLinks]="false"
[showFilterTools]="false"
[showDownloadCopyButton]="false"
defaultTab="cluster-logs"
[scrollable]="true"></cd-logs>
<ng-template #upgradePaused>
<h3 class="text-center mt-3">
<i [ngClass]="[icons.large, icons.spinner]"></i>
</h3>
<h3 class="text-center mt-3 mb-4">
{{ executingTask?.description }}
</h3>
</ng-template>

View File

@ -0,0 +1,30 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UpgradeProgressComponent } from './upgrade-progress.component';
import { ToastrModule } from 'ngx-toastr';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { SharedModule } from '~/app/shared/shared.module';
import { RouterTestingModule } from '@angular/router/testing';
import { LogsComponent } from '../../logs/logs.component';
describe('UpgradeProgressComponent', () => {
let component: UpgradeProgressComponent;
let fixture: ComponentFixture<UpgradeProgressComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [UpgradeProgressComponent, LogsComponent],
imports: [ToastrModule.forRoot(), HttpClientTestingModule, SharedModule, RouterTestingModule]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(UpgradeProgressComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,140 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, ReplaySubject, Subscription } from 'rxjs';
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { Icons } from '~/app/shared/enum/icons.enum';
import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { ModalService } from '~/app/shared/services/modal.service';
import { Permission } from '~/app/shared/models/permissions';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { UpgradeService } from '~/app/shared/api/upgrade.service';
import { NotificationService } from '~/app/shared/services/notification.service';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { SummaryService } from '~/app/shared/services/summary.service';
import { ExecutingTask } from '~/app/shared/models/executing-task';
import { shareReplay, switchMap, tap } from 'rxjs/operators';
import { Router } from '@angular/router';
import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service';
import { UpgradeStatusInterface } from '~/app/shared/models/upgrade.interface';
@Component({
selector: 'cd-upgrade-progress',
templateUrl: './upgrade-progress.component.html',
styleUrls: ['./upgrade-progress.component.scss']
})
export class UpgradeProgressComponent implements OnInit, OnDestroy {
permission: Permission;
icons = Icons;
modalRef: NgbModalRef;
interval = new Subscription();
executingTask: ExecutingTask;
upgradeStatus$: Observable<UpgradeStatusInterface>;
subject = new ReplaySubject<UpgradeStatusInterface>();
constructor(
private authStorageService: AuthStorageService,
private upgradeService: UpgradeService,
private notificationService: NotificationService,
private modalService: ModalService,
private summaryService: SummaryService,
private router: Router,
private refreshIntervalService: RefreshIntervalService
) {
this.permission = this.authStorageService.getPermissions().configOpt;
}
ngOnInit() {
this.upgradeStatus$ = this.subject.pipe(
switchMap(() => this.upgradeService.status()),
tap((status: UpgradeStatusInterface) => {
if (!status.in_progress) {
this.router.navigate(['/upgrade']);
}
}),
shareReplay(1)
);
this.interval = this.refreshIntervalService.intervalData$.subscribe(() => {
this.fetchStatus();
});
this.summaryService.subscribe((summary) => {
this.executingTask = summary.executing_tasks.filter((tasks) =>
tasks.name.includes('progress/Upgrade')
)[0];
});
}
pauseUpgrade() {
this.upgradeService.pause().subscribe({
error: (error) => {
this.notificationService.show(
NotificationType.error,
$localize`Failed to pause the upgrade`,
error
);
},
complete: () => {
this.notificationService.show(NotificationType.success, $localize`The upgrade is paused`);
this.fetchStatus();
}
});
}
fetchStatus() {
this.subject.next();
}
resumeUpgrade(modal = false) {
this.upgradeService.resume().subscribe({
error: (error) => {
this.notificationService.show(
NotificationType.error,
$localize`Failed to resume the upgrade`,
error
);
},
complete: () => {
this.fetchStatus();
this.notificationService.show(NotificationType.success, $localize`Upgrade is resumed`);
if (modal) {
this.modalRef.close();
}
}
});
}
stopUpgradeModal() {
// pause the upgrade meanwhile we get stop confirmation from user
this.pauseUpgrade();
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
itemDescription: 'Upgrade',
actionDescription: 'stop',
submitAction: () => this.stopUpgrade(),
callBackAtionObservable: () => this.resumeUpgrade(true)
});
}
stopUpgrade() {
this.modalRef.close();
this.upgradeService.stop().subscribe({
error: (error) => {
this.notificationService.show(
NotificationType.error,
$localize`Failed to stop the upgrade`,
error
);
},
complete: () => {
this.notificationService.show(NotificationType.success, $localize`The upgrade is stopped`);
this.router.navigate(['/upgrade']);
}
});
}
ngOnDestroy() {
this.interval?.unsubscribe();
}
}

View File

@ -6,24 +6,26 @@
i18n-cardTitle
aria-label="New Version"
i18n-aria-label
id="newVersionAvailable">
<div class="d-flex flex-column justify-content-center align-items-center"
*ngIf="info$ | async as info; else checkingForUpgradeStatus">
<ng-container *ngIf="info.versions.length > 0; else noUpgradesAvailable">
<div i18n-ngbTooltip
[ngbTooltip]="(healthData.mgr_map | mgrSummary).total <= 1 ? 'To upgrade, you need minimum 2 mgr daemons.' : ''">
<button class="btn btn-accent mt-2"
id="upgrade"
aria-label="Upgrade now"
(click)="upgradeNow(info.versions[info.versions.length - 1])"
[disabled]="(healthData.mgr_map | mgrSummary).total <= 1"
i18n>Upgrade to {{ info.versions[info.versions.length - 1] }}</button>
</div>
id="newVersionAvailable"
*ngIf="upgradeStatus$ | async as status">
<ng-container *ngIf="status.in_progress; else upgradeStatusTpl">
<div class="d-flex flex-column justify-content-center align-items-center mt-2">
<h5 i18n
*ngIf="status.is_paused; else inProgress">
<i [ngClass]="[icons.spinner]"></i>
Upgrade is paused {{executingTasks?.progress}}%</h5>
<a class="mt-2 link-primary mb-2"
(click)="startUpgradeModal()"
i18n>Select another version...</a>
</ng-container>
</div>
routerLink="/upgrade/progress"
i18n>View Details...</a>
</div>
<ng-template #inProgress>
<h5 i18n>
<i [ngClass]="[icons.spin, icons.spinner]"></i>
Upgrade in progress {{executingTasks?.progress}}%
</h5>
</ng-template>
</ng-container>
</cd-card>
<cd-card class="col-sm-3 px-3 d-flex"
@ -123,6 +125,27 @@
[showDownloadCopyButton]="false"
defaultTab="cluster-logs"
[scrollable]="true"></cd-logs>
<ng-template #upgradeStatusTpl>
<div class="d-flex flex-column justify-content-center align-items-center"
*ngIf="info$ | async as info; else checkingForUpgradeStatus">
<ng-container *ngIf="info.versions.length > 0; else noUpgradesAvailable">
<div i18n-ngbTooltip
[ngbTooltip]="(healthData.mgr_map | mgrSummary).total <= 1 ? 'To upgrade, you need minimum 2 mgr daemons.' : ''">
<button class="btn btn-accent mt-2"
id="upgrade"
aria-label="Upgrade now"
(click)="upgradeNow(info.versions[info.versions.length - 1])"
[disabled]="(healthData.mgr_map | mgrSummary).total <= 1"
i18n>Upgrade to {{ info.versions[info.versions.length - 1] }}</button>
</div>
<a class="mt-2 link-primary mb-2"
(click)="startUpgradeModal()"
i18n>Select another version...</a>
</ng-container>
</div>
</ng-template>
</ng-container>
</div>
@ -184,3 +207,14 @@
Failed to fetch registry informations
</span>
</ng-template>
<ng-template #upgradeProgress>
<div class="d-flex flex-column justify-content-center align-items-center mt-2">
<h5 i18n>
<i [ngClass]="[icons.spin, icons.spinner]"></i>
Upgrade in progress {{executingTasks?.progress}}%</h5>
<a class="mt-2 link-primary mb-2"
routerLink="/upgrade/progress"
i18n>View Details...</a>
</div>
</ng-template>

View File

@ -14,6 +14,7 @@ import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ToastrModule } from 'ngx-toastr';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { RouterTestingModule } from '@angular/router/testing';
export class SummaryServiceMock {
summaryDataSource = new BehaviorSubject({
@ -33,6 +34,7 @@ describe('UpgradeComponent', () => {
let fixture: ComponentFixture<UpgradeComponent>;
let upgradeInfoSpy: jasmine.Spy;
let getHealthSpy: jasmine.Spy;
let upgradeStatusSpy: jasmine.Spy;
const healthPayload: Record<string, any> = {
health: { status: 'HEALTH_OK' },
@ -51,7 +53,13 @@ describe('UpgradeComponent', () => {
};
configureTestBed({
imports: [HttpClientTestingModule, SharedModule, NgbNavModule, ToastrModule.forRoot()],
imports: [
HttpClientTestingModule,
SharedModule,
NgbNavModule,
ToastrModule.forRoot(),
RouterTestingModule
],
declarations: [UpgradeComponent, LogsComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [UpgradeService, { provide: SummaryService, useClass: SummaryServiceMock }]
@ -62,6 +70,7 @@ describe('UpgradeComponent', () => {
component = fixture.componentInstance;
upgradeInfoSpy = spyOn(TestBed.inject(UpgradeService), 'list').and.callFake(() => of(null));
getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth');
upgradeStatusSpy = spyOn(TestBed.inject(UpgradeService), 'status');
getHealthSpy.and.returnValue(of(healthPayload));
const upgradeInfoPayload = {
image: 'quay.io/ceph-test/ceph',
@ -69,6 +78,8 @@ describe('UpgradeComponent', () => {
versions: ['18.1.0', '18.1.1', '18.1.2']
};
upgradeInfoSpy.and.returnValue(of(upgradeInfoPayload));
upgradeStatusSpy.and.returnValue(of({}));
component.fetchStatus();
spyOn(TestBed.inject(AuthStorageService), 'getPermissions').and.callFake(() => ({
configOpt: { read: true }
}));

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Observable, of } from 'rxjs';
import { catchError, publishReplay, refCount, tap } from 'rxjs/operators';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, ReplaySubject, Subscription, of } from 'rxjs';
import { catchError, publishReplay, refCount, shareReplay, switchMap, tap } from 'rxjs/operators';
import { DaemonService } from '~/app/shared/api/daemon.service';
import { HealthService } from '~/app/shared/api/health.service';
import { UpgradeService } from '~/app/shared/api/upgrade.service';
@ -15,13 +15,16 @@ import { NotificationService } from '~/app/shared/services/notification.service'
import { SummaryService } from '~/app/shared/services/summary.service';
import { ModalService } from '~/app/shared/services/modal.service';
import { UpgradeStartModalComponent } from './upgrade-form/upgrade-start-modal.component';
import { ExecutingTask } from '~/app/shared/models/executing-task';
import { Router } from '@angular/router';
import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service';
@Component({
selector: 'cd-upgrade',
templateUrl: './upgrade.component.html',
styleUrls: ['./upgrade.component.scss']
})
export class UpgradeComponent implements OnInit {
export class UpgradeComponent implements OnInit, OnDestroy {
version: string;
info$: Observable<UpgradeInfoInterface>;
permission: Permission;
@ -31,21 +34,33 @@ export class UpgradeComponent implements OnInit {
modalRef: NgbModalRef;
upgradableVersions: string[];
errorMessage: string;
executingTasks: ExecutingTask;
interval = new Subscription();
columns: CdTableColumn[] = [];
icons = Icons;
upgradeStatus$: Observable<any>;
subject = new ReplaySubject<any>();
constructor(
private modalService: ModalService,
private summaryService: SummaryService,
private upgradeService: UpgradeService,
private healthService: HealthService,
private daemonService: DaemonService,
private notificationService: NotificationService
private notificationService: NotificationService,
private router: Router,
private refreshIntervalService: RefreshIntervalService
) {}
ngOnInit(): void {
this.upgradeStatus$ = this.subject.pipe(
switchMap(() => this.upgradeService.status()),
shareReplay(1)
);
this.columns = [
{
name: $localize`Daemon name`,
@ -64,7 +79,15 @@ export class UpgradeComponent implements OnInit {
this.summaryService.subscribe((summary) => {
const version = summary.version.replace('ceph version ', '').split('-');
this.version = version[0];
this.executingTasks = summary.executing_tasks.filter((tasks) =>
tasks.name.includes('progress/Upgrade')
)[0];
});
this.interval = this.refreshIntervalService.intervalData$.subscribe(() => {
this.fetchStatus();
});
this.info$ = this.upgradeService.list().pipe(
tap((upgradeInfo: UpgradeInfoInterface) => (this.upgradableVersions = upgradeInfo.versions)),
publishReplay(1),
@ -80,6 +103,7 @@ export class UpgradeComponent implements OnInit {
return of(null);
})
);
this.healthData$ = this.healthService.getMinimalHealth();
this.daemons$ = this.daemonService.list(this.upgradeService.upgradableServiceTypes);
this.fsid$ = this.healthService.getClusterFsid();
@ -91,6 +115,10 @@ export class UpgradeComponent implements OnInit {
});
}
fetchStatus() {
this.subject.next();
}
upgradeNow(version: string) {
this.upgradeService.start(version).subscribe({
error: (error) => {
@ -105,7 +133,13 @@ export class UpgradeComponent implements OnInit {
NotificationType.success,
$localize`Started upgrading the cluster`
);
this.fetchStatus();
this.router.navigate(['/upgrade/progress']);
}
});
}
ngOnDestroy() {
this.interval?.unsubscribe();
}
}

View File

@ -3,7 +3,8 @@ import { Injectable } from '@angular/core';
import { ApiClient } from './api-client';
import { map } from 'rxjs/operators';
import { SummaryService } from '../services/summary.service';
import { UpgradeInfoInterface } from '../models/upgrade.interface';
import { UpgradeInfoInterface, UpgradeStatusInterface } from '../models/upgrade.interface';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
@ -55,7 +56,23 @@ export class UpgradeService extends ApiClient {
return upgradeInfo;
}
start(version: string) {
return this.http.post(`${this.baseURL}/start`, { version: version });
start(version?: string, image?: string) {
return this.http.post(`${this.baseURL}/start`, { image: image, version: version });
}
pause() {
return this.http.put(`${this.baseURL}/pause`, null);
}
resume() {
return this.http.put(`${this.baseURL}/resume`, null);
}
stop() {
return this.http.put(`${this.baseURL}/stop`, null);
}
status(): Observable<UpgradeStatusInterface> {
return this.http.get<UpgradeStatusInterface>(`${this.baseURL}/status`);
}
}

View File

@ -43,6 +43,7 @@
</div>
<div class="modal-footer">
<cd-form-button-panel (submitActionEvent)="callSubmitAction()"
(backActionEvent)="callBackAction()"
[form]="deletionForm"
[submitText]="(actionDescription | titlecase) + ' ' + itemDescription"></cd-form-button-panel>
</div>

View File

@ -18,7 +18,9 @@ export class CriticalConfirmationModalComponent implements OnInit {
bodyTemplate: TemplateRef<any>;
bodyContext: object;
submitActionObservable: () => Observable<any>;
callBackAtionObservable: () => Observable<any>;
submitAction: Function;
backAction: Function;
deletionForm: CdFormGroup;
itemDescription: 'entry';
itemNames: string[];
@ -53,6 +55,17 @@ export class CriticalConfirmationModalComponent implements OnInit {
}
}
callBackAction() {
if (this.callBackAtionObservable) {
this.callBackAtionObservable().subscribe({
error: this.stopLoadingSpinner.bind(this),
complete: this.hideModal.bind(this)
});
} else {
this.backAction();
}
}
hideModal() {
this.activeModal.close();
}

View File

@ -3,3 +3,13 @@ export interface UpgradeInfoInterface {
registry: string;
versions: string[];
}
export interface UpgradeStatusInterface {
target_image: string;
in_progress: boolean;
which: string;
services_complete: string;
progress: string;
message: string;
is_paused: boolean;
}