From 69a1edc27a1927631dd04fecda088d303264e00e Mon Sep 17 00:00:00 2001 From: avanthakkar Date: Wed, 9 Aug 2023 13:53:09 +0530 Subject: [PATCH] mgr/dashboard: cluster upgrade progress UI Fixes: https://tracker.ceph.com/issues/62343 Signed-off-by: avanthakkar Co-Authored-By: Nizamudeen A (cherry picked from commit 3157728eee1023a6444c4633a71ad71ea3f07e25) Conflicts: src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts - Removed the CephfsSubvolumeComponent import --- .../frontend/src/app/app-routing.module.ts | 15 +- .../src/app/ceph/cluster/cluster.module.ts | 8 +- .../upgrade-start-modal.component.html | 44 +++++- .../upgrade-start-modal.component.spec.ts | 3 +- .../upgrade-start-modal.component.ts | 30 +++- .../upgrade-progress.component.html | 89 +++++++++++ .../upgrade-progress.component.scss | 0 .../upgrade-progress.component.spec.ts | 30 ++++ .../upgrade-progress.component.ts | 140 ++++++++++++++++++ .../cluster/upgrade/upgrade.component.html | 68 ++++++--- .../cluster/upgrade/upgrade.component.spec.ts | 13 +- .../ceph/cluster/upgrade/upgrade.component.ts | 44 +++++- .../src/app/shared/api/upgrade.service.ts | 23 ++- ...critical-confirmation-modal.component.html | 1 + .../critical-confirmation-modal.component.ts | 13 ++ .../app/shared/models/upgrade.interface.ts | 10 ++ 16 files changed, 495 insertions(+), 36 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts index 2a2e0e00cbf..8db1a333ffb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts @@ -47,6 +47,7 @@ import { ModuleStatusGuardService } from './shared/services/module-status-guard. import { NoSsoGuardService } from './shared/services/no-sso-guard.service'; import { CephfsVolumeFormComponent } from './ceph/cephfs/cephfs-form/cephfs-form.component'; import { UpgradeComponent } from './ceph/cluster/upgrade/upgrade.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', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts index c94244e0682..74657ec4010 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts @@ -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] }) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.html index be12716f0e6..27b7bc2e816 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.html @@ -10,8 +10,13 @@ [formGroup]="upgradeForm" novalidate> diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.spec.ts index a6232ee082a..1fe7ffbf809 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.spec.ts @@ -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; configureTestBed({ - imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot()], + imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot(), RouterTestingModule], schemas: [NO_ERRORS_SCHEMA], declarations: [UpgradeComponent], providers: [UpgradeService] diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.ts index c601b776f56..6ea38bfc323 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component.ts @@ -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(); + } + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.html new file mode 100644 index 00000000000..c683eee7d13 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.html @@ -0,0 +1,89 @@ +
+ +

+ +

+ +

+ {{ executingTask?.description }} +

+ +
{{ upgradeStatus.which }}
+
+ +
+
+ + Finished upgrading: + + {{ upgradeStatus.services_complete }} + + +
+ +
+ +

+ + {{ executingTask?.progress || 0 }} % + +

+
+

{{ upgradeStatus.progress}}

+ +
+ {{ upgradeStatus.message }} +
+ +
+ + + + +
+
+
+ +Cluster logs + + + +

+ +

+ +

+ {{ executingTask?.description }} +

+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.spec.ts new file mode 100644 index 00000000000..c4ec8823fda --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.spec.ts @@ -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; + + 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(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.ts new file mode 100644 index 00000000000..03bb6ed084d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.ts @@ -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; + subject = new ReplaySubject(); + + 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(); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html index c91af1cdcce..e5252cbe4df 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html @@ -6,24 +6,26 @@ i18n-cardTitle aria-label="New Version" i18n-aria-label - id="newVersionAvailable"> -
- -
- -
+ id="newVersionAvailable" + *ngIf="upgradeStatus$ | async as status"> + +
+
+ + Upgrade is paused {{executingTasks?.progress}}%
Select another version... - -
+ routerLink="/upgrade/progress" + i18n>View Details... +
+ + +
+ + Upgrade in progress {{executingTasks?.progress}}% +
+
+ + + + +
+ +
+ +
+ Select another version... +
+
+
@@ -184,3 +207,14 @@ Failed to fetch registry informations + + +
+
+ + Upgrade in progress {{executingTasks?.progress}}%
+ View Details... +
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.spec.ts index 407a7da58e1..574d3c8bb4c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.spec.ts @@ -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; let upgradeInfoSpy: jasmine.Spy; let getHealthSpy: jasmine.Spy; + let upgradeStatusSpy: jasmine.Spy; const healthPayload: Record = { 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 } })); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.ts index a271a24202d..0f1f2318a5e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.ts @@ -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; 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; + subject = new ReplaySubject(); + 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(); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/upgrade.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/upgrade.service.ts index 8421fc57f37..9aa25aa1614 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/upgrade.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/upgrade.service.ts @@ -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 { + return this.http.get(`${this.baseURL}/status`); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html index 29b669b141f..01c0e2ca5cc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html @@ -43,6 +43,7 @@ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts index 4c634f8ca25..b2f482db062 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts @@ -18,7 +18,9 @@ export class CriticalConfirmationModalComponent implements OnInit { bodyTemplate: TemplateRef; bodyContext: object; submitActionObservable: () => Observable; + callBackAtionObservable: () => Observable; 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(); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/upgrade.interface.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/upgrade.interface.ts index ada46bcd6b8..2a853d59a94 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/upgrade.interface.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/upgrade.interface.ts @@ -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; +} -- 2.39.5