]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Carbonize upgrade page
authorAfreen Misbah <afreen@ibm.com>
Sun, 17 May 2026 14:53:54 +0000 (20:23 +0530)
committerAfreen Misbah <afreen@ibm.com>
Mon, 18 May 2026 07:41:51 +0000 (13:11 +0530)
- Made cluster status clickable to navigate to overview when not HEALTH_OK
- Replaced Bootstrap classes with Carbon design tokens
- Updated upgrade.component.scss to use CSS custom properties

Assisted-by: Claude
Signed-off-by: Afreen Misbah <afreenmisbah@ibm.com>
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.scss
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

index d1a84b68f2b573fdc3386c125464d430ac1d36fe..baf6aa044d767f5d41ef0f566b0c3b48eea73344 100644 (file)
-<div class="row h-25"
-     *cdScope="'configOpt'">
+<main *cdScope="'configOpt'"
+      cdsGrid
+      [fullWidth]="true"
+      [narrow]="true"
+      class="cds-mt-5">
   <ng-container *ngIf="healthData$ | async as healthData">
-    <cd-card class="col-sm-3 px-3 d-flex"
-             cardTitle="New Version"
-             i18n-cardTitle
-             aria-label="New Version"
-             i18n-aria-label
-             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"
-             routerLink="/upgrade/progress"
-             i18n>View Details...</a>
-        </div>
+    <div cdsRow
+         class="cds-mb-5">
+      <div cdsCol
+           [columnNumbers]="{lg: 16}">
+        <cds-tile aria-label="Cluster Information"
+                  i18n-aria-label>
+          <h4 class="cds--type-heading-03 tile-title"
+              i18n>Cluster Information</h4>
+          <div class="details-grid">
+            <!-- Current Version -->
+            <div class="detail-item">
+              <span class="cds--type-label-01"
+                    i18n>Current Version</span>
+              <span class="cds--type-body-compact-01">{{ version }}</span>
+            </div>
 
-        <ng-template #inProgress>
-          <h5 i18n>
-            <i [ngClass]="[icons.spin, icons.spinner]"></i>
-              Upgrading {{executingTasks?.progress}}%
-          </h5>
-        </ng-template>
-      </ng-container>
-    </cd-card>
+            <!-- New Version / Upgrade Status -->
+            <div class="detail-item"
+                 *ngIf="upgradeStatus$ | async as status"
+                 id="newVersionAvailable">
+              <span class="cds--type-label-01"
+                    i18n>New Version</span>
+              <ng-container *ngIf="status.in_progress; else upgradeStatusTpl">
+                <div cdsStack
+                     orientation="vertical"
+                     gap="2">
+                  <span class="cds--type-body-compact-01"
+                        i18n
+                        *ngIf="status.is_paused; else inProgress">
+                    <i [ngClass]="[icons.spinner]"></i>
+                    Upgrade is paused {{executingTasks?.progress}}%</span>
+                  <a class="cds--link"
+                     routerLink="/upgrade/progress"
+                     i18n>View Details...</a>
+                </div>
 
-    <cd-card class="col-sm-3 px-3 d-flex"
-             cardTitle="Current Version"
-             i18n-cardTitle
-             aria-label="Current Version"
-             i18n-aria-label
-             id="currentVersion">
-      <div class="d-flex flex-column justify-content-center align-items-center">
-        <h5>{{ version }}</h5>
-      </div>
-    </cd-card>
+                <ng-template #inProgress>
+                  <span class="cds--type-body-compact-01"
+                        i18n>
+                    <i [ngClass]="[icons.spin, icons.spinner]"></i>
+                    Upgrading {{executingTasks?.progress}}%
+                  </span>
+                </ng-template>
+              </ng-container>
+            </div>
 
-    <cd-card class="col-sm-3 px-3 d-flex"
-             cardTitle="Cluster Status"
-             i18n-cardTitle
-             aria-label="Cluster Status"
-             i18n-aria-label
-             id="clusterStatus">
-      <div class="d-flex flex-column justify-content-center align-items-center">
-        <ng-template #healthWarningAndError>
-          <cds-toggletip  [ngStyle]="healthData.health.status | healthColor"
-                          [dropShadow]="true"
-                          [autoAlign]="true"
-                          class="info-card-content-clickable mt-1">
-            <div cdsToggletipButton>
-            {{ healthData.health.status | healthLabel | uppercase }}
-            <i  *ngIf="healthData.health?.status !== 'HEALTH_OK'"
-                class="fa fa-exclamation-triangle">
-            </i>
+            <!-- Cluster Status -->
+            <div class="detail-item"
+                 id="clusterStatus">
+              <span class="cds--type-label-01"
+                    i18n>Cluster Status</span>
+              <ng-container *ngIf="healthData.health?.status !== 'HEALTH_OK'; else healthOk">
+                <a class="cds--link cds--type-body-compact-01"
+                   routerLink="/dashboard"
+                   [ngStyle]="healthData.health.status | healthColor">
+                  {{ healthData.health.status | healthLabel | titlecase }}
+                  <i class="fa fa-exclamation-triangle"></i>
+                </a>
+              </ng-container>
+              <ng-template #healthOk>
+                <span class="cds--type-body-compact-01"
+                      [ngStyle]="healthData.health.status | healthColor">
+                  {{ healthData.health.status | healthLabel | titlecase }}
+                </span>
+              </ng-template>
             </div>
-            <div cdsToggletipContent>
-              <div class="cds--popover-scroll-container">
-                <cd-health-checks [healthData]="healthData.health.checks"
-                                  [legacyHealthChecks]="true"></cd-health-checks>
-              </div>
+
+            <!-- MGR Count -->
+            <div class="detail-item"
+                 id="mgrCount">
+              <span class="cds--type-label-01"
+                    i18n>MGR Count</span>
+              <span class="cds--type-body-compact-01">
+                <i class="text-success"
+                   [ngClass]="[icons.success]"
+                   *ngIf="(healthData.mgr_map | mgrSummary)?.total > 1; else warningIcon">
+                </i>
+                {{ (healthData.mgr_map | mgrSummary)?.total }}
+              </span>
             </div>
-          </cds-toggletip>
-        </ng-template>
 
-      <ng-container *ngIf="!healthData.health?.checks?.length; else healthWarningAndError">
-        <div [ngStyle]="healthData.health.status | healthColor">
-          {{ healthData.health.status | healthLabel | uppercase }}
-        </div>
-      </ng-container>
-      </div>
-    </cd-card>
+            <!-- Cluster FSID -->
+            <div class="detail-item"
+                 *ngIf="fsid$ | async as fsid">
+              <span class="cds--type-label-01"
+                    i18n>Cluster FSID</span>
+              <span class="cds--type-body-compact-01">{{ fsid }}</span>
+            </div>
 
-    <cd-card class="col-sm-3 px-3 d-flex"
-             cardTitle="MGR Count"
-             i18n-cardTitle
-             aria-label="MGR Count"
-             i18n-aria-label
-             id="mgrCount">
-      <div class="d-flex flex-column justify-content-center align-items-center">
-        <h5>
-          <i class="text-success"
-             [ngClass]="[icons.success]"
-             *ngIf="(healthData.mgr_map | mgrSummary)?.total > 1; else warningIcon">
-          </i>
-          {{ (healthData.mgr_map | mgrSummary)?.total }}
-        </h5>
-      </div>
-    </cd-card>
+            <!-- Release Image -->
+            <div class="detail-item">
+              <span class="cds--type-label-01"
+                    i18n>Release Image</span>
+              <ng-container *ngIf="info$ | async as info; else releaseImageError">
+                <span class="cds--type-body-compact-01">{{ info.image }}</span>
+              </ng-container>
+              <ng-template #releaseImageError>
+                <span class="cds--type-body-compact-01"
+                      *ngIf="!errorMessage; else showError">
+                  <i [ngClass]="[icons.spin, icons.spinner]"></i>
+                  <span i18n>Loading...</span>
+                </span>
+                <ng-template #showError>
+                  <span class="cds--type-body-compact-01 text-danger">
+                    <i [ngClass]="[icons.danger]"></i>
+                    <span i18n>Failed to fetch</span>
+                  </span>
+                </ng-template>
+              </ng-template>
+            </div>
 
-    <div class="d-flex mt-3">
-      <dl class="w-50"
-          *ngIf="fsid$ | async as fsid">
-        <dt class="bold mt-5"
-            i18n>Cluster FSID</dt>
-        <dd class="mt-2">{{ fsid }}</dd>
+            <!-- Registry -->
+            <div class="detail-item">
+              <span class="cds--type-label-01"
+                    i18n>Registry</span>
+              <ng-container *ngIf="info$ | async as info; else registryError">
+                <span class="cds--type-body-compact-01">{{ info.registry }}</span>
+              </ng-container>
+              <ng-template #registryError>
+                <span class="cds--type-body-compact-01"
+                      *ngIf="!errorMessage; else showRegistryError">
+                  <i [ngClass]="[icons.spin, icons.spinner]"></i>
+                  <span i18n>Loading...</span>
+                </span>
+                <ng-template #showRegistryError>
+                  <span class="cds--type-body-compact-01 text-danger">
+                    <i [ngClass]="[icons.danger]"></i>
+                    <span i18n>Failed to fetch</span>
+                  </span>
+                </ng-template>
+              </ng-template>
+            </div>
+          </div>
+        </cds-tile>
+      </div>
+    </div>
 
-        <ng-container *ngIf="info$ | async as info; else loadingDetails">
-          <dt class="bold mt-5"
-              i18n>Release Image</dt>
-          <dd class="mt-2">{{ info.image }}</dd>
-          <dt class="bold mt-5"
-              i18n>Registry</dt>
-          <dd class="mt-2">{{ info.registry }}</dd>
-        </ng-container>
-      </dl>
-      <div class="w-50">
-        <ng-container *ngIf="daemons$ | async as daemons">
-          <legend class="cd-header"
-                  i18n>Daemon versions</legend>
+    <div cdsRow
+         class="cds-mb-5">
+      <div cdsCol
+           [columnNumbers]="{lg: 16}">
+        <cds-tile *ngIf="daemons$ | async as daemons">
+          <h4 class="cds--type-heading-03 tile-title"
+              i18n>Daemon Versions</h4>
           <div>
             <cd-table #daemonsTable
                       [data]="daemons"
                       [limit]="5">
             </cd-table>
           </div>
-        </ng-container>
+        </cds-tile>
       </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"></cd-logs>
-
+    <div cdsRow
+         class="cds-mb-5">
+      <div cdsCol
+           [columnNumbers]="{lg: 16}">
+        <cds-tile>
+          <h4 class="cds--type-heading-03 tile-title"
+              i18n>Cluster Logs</h4>
+          <cd-logs [showAuditLogs]="false"
+                   [showDaemonLogs]="false"
+                   [showNavLinks]="false"
+                   [showFilterTools]="false"
+                   [showDownloadCopyButton]="false"
+                   defaultTab="cluster-logs"></cd-logs>
+        </cds-tile>
+      </div>
+    </div>
 
     <ng-template #upgradeStatusTpl>
-      <div class="d-flex flex-column justify-content-center align-items-center"
+      <div cdsStack
+           orientation="vertical"
+           gap="4"
+           class="cds-mt-3"
            *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"
+          <div [cdsTip]="(healthData.mgr_map | mgrSummary)?.total <= 1 ? 'To upgrade, you need minimum 2 mgr daemons.' : ''"
+               i18n-cdsTip>
+            <button cdsButton="primary"
+                    class="cds-mt-3"
                     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"
+          <a class="cds--link cds-mb-3"
              (click)="startUpgradeModal()"
              i18n>Select another version...</a>
         </ng-container>
       </div>
     </ng-template>
   </ng-container>
-</div>
+</main>
 
 <ng-template #noUpgradesAvailable>
-  <span class="mt-1"
+  <span class="cds--type-body-compact-01"
         id="no-upgrades-available"
         i18n>
     <i [ngClass]="[icons.success]"
        class="text-success"></i>
     Cluster is up-to-date
   </span>
-  <a class="link-primary mb-2"
+  <a class="cds--link cds-mb-3"
      (click)="startUpgradeModal()"
      i18n>Upgrade using custom image...</a>
 </ng-template>
 </ng-template>
 
 <ng-template #checkingForUpgradeStatus>
-  <div class="d-flex flex-column justify-content-center align-items-center"
+  <div cdsStack
+       orientation="vertical"
+       gap="4"
+       class="cds-mt-3"
        *ngIf="!errorMessage; else upgradeStatusError">
-    <button class="btn btn-accent mt-2 mb-4"
+    <button cdsButton="primary"
+            class="cds-mt-3 cds-mb-5"
             id="upgrade"
             aria-label="Upgrade now"
             [disabled]="true"
   </div>
 </ng-template>
 
-<ng-template #loadingDetails>
-  <div class="w-50"
-       *ngIf="!errorMessage; else upgradeInfoError">
-    <span class="text-info justify-content-center align-items-center"
-          i18n>Fetching registry information
-      <i [ngClass]="[icons.spin, icons.spinner]"></i>
-    </span>
-  </div>
-</ng-template>
-
 <ng-template #upgradeStatusError>
-  <div class="d-flex flex-column justify-content-center align-items-center">
-    <span class="text-danger mt-2 mb-4"
+  <div cdsStack
+       orientation="vertical"
+       gap="4"
+       class="cds-mt-3 cds-mb-5">
+    <span class="cds--type-body-compact-01"
           id="upgrade-status-error"
           i18n>
       <i [ngClass]="[icons.danger]"></i>
       {{ errorMessage }}
     </span>
-    <a class="link-primary mb-2"
+    <a class="cds--link cds-mb-3"
        (click)="startUpgradeModal()"
        i18n>Upgrade using custom image...</a>
   </div>
 </ng-template>
 
-<ng-template #upgradeInfoError>
-  <span class="text-danger justify-content-center align-items-center"
-        i18n>
-    <i [ngClass]="[icons.danger]"></i>
-    Failed to fetch registry information
-  </span>
-</ng-template>
-
 <ng-template #upgradeProgress>
-  <div class="d-flex flex-column justify-content-center align-items-center mt-2">
-    <h5 i18n>
+  <div cdsStack
+       orientation="vertical"
+       gap="4"
+       class="cds-mt-3">
+    <h5 class="cds--type-heading-compact-01"
+        i18n>
       <i [ngClass]="[icons.spin, icons.spinner]"></i>
       Upgrade in progress {{executingTasks?.progress}}%</h5>
-    <a class="mt-2 link-primary mb-2"
+    <a class="cds--link cds-mb-3"
        routerLink="/upgrade/progress"
        i18n>View Details...</a>
   </div>
index 3a9a1506c33bc064f438467b6ab73847088cacd1..f2e90ffffd23c93480231040e270ae66a4e1f26e 100644 (file)
@@ -1,14 +1,16 @@
-@use './src/styles/vendor/variables' as vv;
+.tile-title {
+  margin-bottom: var(--cds-spacing-06);
+}
 
-.info-card-content-clickable {
-  border: 1px solid vv.$gray-200;
-  border-radius: 3px;
-  cursor: pointer;
-  font-size: 1.25em;
-  padding: 7px;
+.details-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+  row-gap: var(--cds-spacing-06);
+  column-gap: var(--cds-spacing-07);
 }
 
-.info-card-content-clickable:hover {
-  background-color: vv.$gray-200;
-  border-color: vv.$gray-400;
+.detail-item {
+  display: flex;
+  flex-direction: column;
+  gap: var(--cds-spacing-02);
 }
index 2ff6fe2ae69381002ad7372071d93747395c3338..d086d3c95987f02398e61326a34067884f43935a 100644 (file)
@@ -96,10 +96,10 @@ describe('UpgradeComponent', () => {
   it('should load the view once check for upgrade is done', () => {
     component.ngOnInit();
     fixture.detectChanges();
-    const firstCellSpan = fixture.debugElement.nativeElement.querySelector(
-      'cd-card[cardTitle="New Version"] .card-title'
+    const upgradeSection = fixture.debugElement.nativeElement.querySelector(
+      '#newVersionAvailable'
     );
-    expect(firstCellSpan.textContent).toContain('New Version');
+    expect(upgradeSection).not.toBeNull();
   });
 
   it('should show button to Upgrade if a new version is available', () => {
@@ -183,7 +183,7 @@ describe('UpgradeComponent', () => {
     expect(loading.textContent).toContain('Failed to retrieve');
   });
 
-  it('should show popover when health warning is present', () => {
+  it('should show warning icon when health warning is present', () => {
     const healthPayload: Record<string, any> = {
       health: {
         status: 'HEALTH_WARN',
@@ -210,13 +210,13 @@ describe('UpgradeComponent', () => {
     component.ngOnInit();
     fixture.detectChanges();
 
-    const popover = fixture.debugElement.nativeElement.querySelector(
-      '.info-card-content-clickable'
+    const warningIcon = fixture.debugElement.nativeElement.querySelector(
+      '#clusterStatus .fa-exclamation-triangle'
     );
-    expect(popover).not.toBeNull();
+    expect(warningIcon).not.toBeNull();
   });
 
-  it('should not show popover when health warning is not present', () => {
+  it('should not show warning icon when health is OK', () => {
     const healthPayload: Record<string, any> = {
       health: {
         status: 'HEALTH_OK'
@@ -225,9 +225,9 @@ describe('UpgradeComponent', () => {
     getHealthSpy.and.returnValue(of(healthPayload));
     component.ngOnInit();
     fixture.detectChanges();
-    const popover = fixture.debugElement.nativeElement.querySelector(
-      '.info-card-content-clickable'
+    const warningIcon = fixture.debugElement.nativeElement.querySelector(
+      '#clusterStatus .fa-exclamation-triangle'
     );
-    expect(popover).toBeNull();
+    expect(warningIcon).toBeNull();
   });
 });
index a60d9a237fd5610d14156fbfe93359f576cc86ad..4ba2d5e39550107da8af257e486ec99b6c15752f 100644 (file)
@@ -10,7 +10,6 @@ import { CdTableColumn } from '~/app/shared/models/cd-table-column';
 import { Daemon } from '~/app/shared/models/daemon.interface';
 import { Permission } from '~/app/shared/models/permissions';
 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 { ExecutingTask } from '~/app/shared/models/executing-task';
@@ -31,7 +30,6 @@ export class UpgradeComponent implements OnInit, OnDestroy {
   healthData$: Observable<any>;
   daemons$: Observable<Daemon[]>;
   fsid$: Observable<any>;
-  modalRef: NgbModalRef;
   upgradableVersions: string[];
   errorMessage: string;
   executingTasks: ExecutingTask;
@@ -109,7 +107,7 @@ export class UpgradeComponent implements OnInit, OnDestroy {
   }
 
   startUpgradeModal() {
-    this.modalRef = this.upgradeService.startUpgradeModal();
+    this.upgradeService.startUpgradeModal();
   }
 
   fetchStatus() {