From 882c747a8a2c3f5bfe4b1391e90e21271c1461b3 Mon Sep 17 00:00:00 2001 From: Pedro Gonzalez Gomez Date: Wed, 20 Mar 2024 18:47:10 +0100 Subject: [PATCH] mgr/dashboard: add upgrade notification - Adds upgradable component to manage upgrades from outside upgrade component. - Cleans a couple subscriptions that were not being destroyed Fixes: https://tracker.ceph.com/issues/65012 Signed-off-by: Pedro Gonzalez Gomez --- .../cluster/upgrade/upgrade.component.html | 2 +- .../cluster/upgrade/upgrade.component.spec.ts | 4 +- .../ceph/cluster/upgrade/upgrade.component.ts | 32 +++++------ .../dashboard/dashboard-v3.component.html | 5 +- .../dashboard/dashboard-v3.component.ts | 14 +++-- .../src/app/shared/api/upgrade.service.ts | 34 ++++++++++- .../shared/components/components.module.ts | 7 ++- .../upgradable/upgradable.component.html | 26 +++++++++ .../upgradable/upgradable.component.scss | 0 .../upgradable/upgradable.component.spec.ts | 24 ++++++++ .../upgradable/upgradable.component.ts | 57 +++++++++++++++++++ 11 files changed, 175 insertions(+), 30 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.ts 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 b1867c3bb0c6c..5db15a42b51a3 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 @@ -22,7 +22,7 @@
- Upgrade in progress {{executingTasks?.progress}}% + Upgrading {{executingTasks?.progress}}%
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 46b1d99204cb5..8c3e1c9eb2813 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 @@ -68,7 +68,9 @@ describe('UpgradeComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(UpgradeComponent); component = fixture.componentInstance; - upgradeInfoSpy = spyOn(TestBed.inject(UpgradeService), 'list').and.callFake(() => of(null)); + upgradeInfoSpy = spyOn(TestBed.inject(UpgradeService), 'listCached').and.callFake(() => + of(null) + ); getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth'); upgradeStatusSpy = spyOn(TestBed.inject(UpgradeService), 'status'); getHealthSpy.and.returnValue(of(healthPayload)); 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 0f1f2318a5e04..80ae0c4aadacb 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, OnDestroy, OnInit } from '@angular/core'; import { Observable, ReplaySubject, Subscription, of } from 'rxjs'; -import { catchError, publishReplay, refCount, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { catchError, shareReplay, switchMap } 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'; @@ -13,8 +13,6 @@ import { UpgradeInfoInterface } from '~/app/shared/models/upgrade.interface'; import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; 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'; @@ -43,9 +41,9 @@ export class UpgradeComponent implements OnInit, OnDestroy { upgradeStatus$: Observable; subject = new ReplaySubject(); + private subs = new Subscription(); constructor( - private modalService: ModalService, private summaryService: SummaryService, private upgradeService: UpgradeService, private healthService: HealthService, @@ -76,22 +74,21 @@ export class UpgradeComponent implements OnInit, OnDestroy { } ]; - 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.subs.add( + 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), - refCount(), + this.info$ = this.upgradeService.listCached().pipe( catchError((err) => { err.preventDefault(); this.errorMessage = $localize`Not retrieving upgrades`; @@ -110,9 +107,7 @@ export class UpgradeComponent implements OnInit, OnDestroy { } startUpgradeModal() { - this.modalRef = this.modalService.show(UpgradeStartModalComponent, { - versions: this.upgradableVersions - }); + this.modalRef = this.upgradeService.startUpgradeModal(); } fetchStatus() { @@ -141,5 +136,6 @@ export class UpgradeComponent implements OnInit, OnDestroy { ngOnDestroy() { this.interval?.unsubscribe(); + this.subs?.unsubscribe(); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html index 17df92f3f248c..46aa67721ddbf 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html @@ -17,7 +17,10 @@
Orchestrator
{{ detailsCardData.orchestrator || 'Orchestrator is not available' }}
Ceph version
-
{{ detailsCardData.cephVersion }}
+
+ {{ detailsCardData.cephVersion }} + +
Cluster API
; hardwareSubject = new BehaviorSubject([]); managedByConfig$: Observable; + private subs = new Subscription(); constructor( private summaryService: SummaryService, @@ -132,6 +133,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit ngOnDestroy() { this.interval.unsubscribe(); this.prometheusService.unsubscribe(); + this.subs?.unsubscribe(); } getHealth() { @@ -151,11 +153,13 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit this.orchestratorService.getName().subscribe((data: string) => { this.detailsCardData.orchestrator = data; }); - this.summaryService.subscribe((summary) => { - const version = summary.version.replace('ceph version ', '').split(' '); - this.detailsCardData.cephVersion = - version[0] + ' ' + version.slice(2, version.length).join(' '); - }); + this.subs.add( + this.summaryService.subscribe((summary) => { + const version = summary.version.replace('ceph version ', '').split(' '); + this.detailsCardData.cephVersion = + version[0] + ' ' + version.slice(2, version.length).join(' '); + }) + ); } getCapacityCardData() { 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 9aa25aa161478..3a2e6dc0fad3d 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 @@ -1,10 +1,15 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { ApiClient } from './api-client'; -import { map } from 'rxjs/operators'; +import { map, shareReplay, tap } from 'rxjs/operators'; import { SummaryService } from '../services/summary.service'; import { UpgradeInfoInterface, UpgradeStatusInterface } from '../models/upgrade.interface'; import { Observable } from 'rxjs'; +import { UpgradeStartModalComponent } from '~/app/ceph/cluster/upgrade/upgrade-form/upgrade-start-modal.component'; +import { ModalService } from '../services/modal.service'; +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; + +const CACHE_SIZE = 1; @Injectable({ providedIn: 'root' @@ -25,7 +30,14 @@ export class UpgradeService extends ApiClient { 'nfs' ]; - constructor(private http: HttpClient, private summaryService: SummaryService) { + _listData$: Observable; + _upgradableVersions: string[]; + + constructor( + private http: HttpClient, + private summaryService: SummaryService, + private modalService: ModalService + ) { super(); } @@ -75,4 +87,22 @@ export class UpgradeService extends ApiClient { status(): Observable { return this.http.get(`${this.baseURL}/status`); } + + listCached(): Observable { + if (!this._listData$) { + this._listData$ = this.list().pipe( + tap( + (upgradeInfo: UpgradeInfoInterface) => (this._upgradableVersions = upgradeInfo.versions) + ), + shareReplay(CACHE_SIZE) + ); + } + return this._listData$; + } + + startUpgradeModal(): NgbModalRef { + return this.modalService.show(UpgradeStartModalComponent, { + versions: this._upgradableVersions + }); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts index d70776dfd8689..d6943b0c71a7d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts @@ -56,6 +56,7 @@ import { VerticalNavigationComponent } from './vertical-navigation/vertical-navi import { CardGroupComponent } from './card-group/card-group.component'; import { HelpTextComponent } from './help-text/help-text.component'; import { FormAdvancedFieldsetComponent } from './form-advanced-fieldset/form-advanced-fieldset.component'; +import { UpgradableComponent } from './upgradable/upgradable.component'; @NgModule({ imports: [ @@ -115,7 +116,8 @@ import { FormAdvancedFieldsetComponent } from './form-advanced-fieldset/form-adv VerticalNavigationComponent, CardGroupComponent, HelpTextComponent, - FormAdvancedFieldsetComponent + FormAdvancedFieldsetComponent, + UpgradableComponent ], providers: [], exports: [ @@ -152,7 +154,8 @@ import { FormAdvancedFieldsetComponent } from './form-advanced-fieldset/form-adv VerticalNavigationComponent, CardGroupComponent, HelpTextComponent, - FormAdvancedFieldsetComponent + FormAdvancedFieldsetComponent, + UpgradableComponent ] }) export class ComponentsModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.html new file mode 100644 index 0000000000000..173df9b68714e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.html @@ -0,0 +1,26 @@ + + + +
+
+ + Upgrade available +
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.spec.ts new file mode 100644 index 0000000000000..11fa498c9b498 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UpgradableComponent } from './upgradable.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('UpgradableComponent', () => { + let component: UpgradableComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [UpgradableComponent], + imports: [HttpClientTestingModule] + }).compileComponents(); + + fixture = TestBed.createComponent(UpgradableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.ts new file mode 100644 index 0000000000000..d478df25d8a91 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { Observable, Subscription } from 'rxjs'; +import { UpgradeService } from '../../api/upgrade.service'; +import { UpgradeInfoInterface, UpgradeStatusInterface } from '../../models/upgrade.interface'; +import { OrchestratorService } from '../../api/orchestrator.service'; +import { Icons } from '~/app/shared/enum/icons.enum'; +import { SummaryService } from '../../services/summary.service'; +import { ExecutingTask } from '../../models/executing-task'; + +@Component({ + selector: 'cd-upgradable', + templateUrl: './upgradable.component.html', + styleUrls: ['./upgradable.component.scss'] +}) +export class UpgradableComponent { + orchAvailable: boolean = false; + upgradeInfo$: Observable; + upgradeStatus$: Observable; + upgradeModalRef: NgbModalRef; + executingTask: ExecutingTask; + private subs = new Subscription(); + + icons = Icons; + + constructor( + private orchestratorService: OrchestratorService, + private summaryService: SummaryService, + private upgradeService: UpgradeService + ) {} + + ngOnInit() { + this.orchestratorService.status().subscribe((status: any) => { + this.orchAvailable = status.available; + if (this.orchAvailable && status.upgrade_status?.available) { + this.upgradeInfo$ = this.upgradeService.listCached(); + this.upgradeStatus$ = this.upgradeService.status(); + } + }); + + this.subs.add( + this.summaryService.subscribe((summary) => { + this.executingTask = summary.executing_tasks.filter((tasks) => + tasks.name.includes('progress/Upgrade') + )[0]; + }) + ); + } + + ngOnDestroy() { + this.subs?.unsubscribe(); + } + + upgradeModal() { + this.upgradeModalRef = this.upgradeService.startUpgradeModal(); + } +} -- 2.39.5