]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add upgrade notification 57861/head
authorPedro Gonzalez Gomez <pegonzal@redhat.com>
Wed, 20 Mar 2024 17:47:10 +0000 (18:47 +0100)
committerPedro Gonzalez Gomez <pegonzal@redhat.com>
Tue, 4 Jun 2024 07:23:14 +0000 (09:23 +0200)
Conflicts:
-mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts
No hardware status in squid

- 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 <pegonzal@redhat.com>
(cherry picked from commit 882c747a8a2c3f5bfe4b1391e90e21271c1461b3)

src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/upgrade.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/upgradable/upgradable.component.ts [new file with mode: 0644]

index b1867c3bb0c6c4866ce9ba44a31bc904d66f3744..5db15a42b51a348d82d1bd7c6d19e97be8460d5f 100644 (file)
@@ -22,7 +22,7 @@
         <ng-template #inProgress>
           <h5 i18n>
             <i [ngClass]="[icons.spin, icons.spinner]"></i>
-              Upgrade in progress {{executingTasks?.progress}}%
+              Upgrading {{executingTasks?.progress}}%
           </h5>
         </ng-template>
       </ng-container>
index 46b1d99204cb55693450e11d2d9d9138860f2efa..8c3e1c9eb28134a96886f0ab3ff98f7a5bead3c0 100644 (file)
@@ -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));
index 0f1f2318a5e0423abc7612b4efb29c62353b0666..80ae0c4aadacbd1a1d0c8cac01b560145efc1a77 100644 (file)
@@ -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<any>;
   subject = new ReplaySubject<any>();
+  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();
   }
 }
index 4c290746b45b1cdbdad8e9b5cc0d9cd2d1957f86..ee01993703a0d6a835a8262647b643143d390fdf 100644 (file)
           <dt>Orchestrator</dt>
           <dd i18n>{{ detailsCardData.orchestrator || 'Orchestrator is not available' }}</dd>
           <dt>Ceph version</dt>
-          <dd>{{ detailsCardData.cephVersion }}</dd>
+          <dd>
+            {{ detailsCardData.cephVersion }}
+            <cd-upgradable></cd-upgradable>
+          </dd>
           <dt>Cluster API</dt>
           <dd>
             <a routerLink="/api-docs"
index 3c44bd36a8905d7b90db9716d550dc9beaab260f..11cc7177faf754577b3acc42df69abeda071ce1e 100644 (file)
@@ -69,6 +69,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit
   telemetryEnabled: boolean;
   telemetryURL = 'https://telemetry-public.ceph.com/';
   origin = window.location.origin;
+  private subs = new Subscription();
 
   constructor(
     private summaryService: SummaryService,
@@ -108,6 +109,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit
   ngOnDestroy() {
     this.interval.unsubscribe();
     this.prometheusService.unsubscribe();
+    this.subs?.unsubscribe();
   }
 
   getHealth() {
@@ -127,11 +129,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() {
index 9aa25aa1614787c4e96e1db36bb89276d9adb92b..3a2e6dc0fad3dda2f5699598e298b4663f4c8c02 100644 (file)
@@ -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<UpgradeInfoInterface>;
+  _upgradableVersions: string[];
+
+  constructor(
+    private http: HttpClient,
+    private summaryService: SummaryService,
+    private modalService: ModalService
+  ) {
     super();
   }
 
@@ -75,4 +87,22 @@ export class UpgradeService extends ApiClient {
   status(): Observable<UpgradeStatusInterface> {
     return this.http.get<UpgradeStatusInterface>(`${this.baseURL}/status`);
   }
+
+  listCached(): Observable<UpgradeInfoInterface> {
+    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
+    });
+  }
 }
index c3baa9c13f59c1d8fef350c9324a74bc1a2869b2..1b5119b096315e72f68c42d2ed2108ac6317938a 100644 (file)
@@ -55,6 +55,7 @@ import { CodeBlockComponent } from './code-block/code-block.component';
 import { VerticalNavigationComponent } from './vertical-navigation/vertical-navigation.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: [
@@ -113,7 +114,8 @@ import { FormAdvancedFieldsetComponent } from './form-advanced-fieldset/form-adv
     CodeBlockComponent,
     VerticalNavigationComponent,
     HelpTextComponent,
-    FormAdvancedFieldsetComponent
+    FormAdvancedFieldsetComponent,
+    UpgradableComponent
   ],
   providers: [],
   exports: [
@@ -149,7 +151,8 @@ import { FormAdvancedFieldsetComponent } from './form-advanced-fieldset/form-adv
     CodeBlockComponent,
     VerticalNavigationComponent,
     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 (file)
index 0000000..173df9b
--- /dev/null
@@ -0,0 +1,26 @@
+<div *ngIf="upgradeStatus$ | async as status; else isUpgradable">
+  <ng-container *ngIf="status.is_paused || status.in_progress; else isUpgradable">
+    <h5 *ngIf="status.is_paused; else inProgress"
+        i18n>
+      Upgrade is paused
+    </h5>
+    <ng-template #inProgress>
+      <a href="#/upgrade/progress"
+         i18n>
+        <i [ngClass]="[icons.spin, icons.spinner]"></i>
+          Upgrading {{executingTask?.progress}}%
+      </a>
+    </ng-template>
+  </ng-container>
+</div>
+
+<ng-template #isUpgradable>
+  <div *ngIf="upgradeInfo$ | async as info"
+       i18n>
+    <h5 *ngIf="info.versions.length > 0"
+        (click)="upgradeModal()">
+      <i [ngClass]="icons.up"></i>
+      Upgrade available
+    </h5>
+  </div>
+</ng-template>
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 (file)
index 0000000..e69de29
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 (file)
index 0000000..11fa498
--- /dev/null
@@ -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<UpgradableComponent>;
+
+  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 (file)
index 0000000..d478df2
--- /dev/null
@@ -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<UpgradeInfoInterface>;
+  upgradeStatus$: Observable<UpgradeStatusInterface>;
+  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();
+  }
+}