]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Combining Quorum tables data on Monitors page 69040/head
authorDevika Babrekar <devika.babrekar@ibm.com>
Thu, 21 May 2026 06:33:04 +0000 (12:03 +0530)
committerDevika Babrekar <devika.babrekar@ibm.com>
Mon, 1 Jun 2026 12:36:11 +0000 (18:06 +0530)
Fixes: https://tracker.ceph.com/issues/76746
Signed-off-by: Devika Babrekar <devika.babrekar@ibm.com>
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/monitors.po.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.ts

index 4d4e53aee508bcf2a7dd7ab6c64749d0dc2c6f68..8e830fa3af0bfb20438bd3c115f8e9349b3479bb 100644 (file)
@@ -16,13 +16,10 @@ describe('Monitors page', () => {
 
   describe('fields check', () => {
     it('should check status table is present', () => {
-      // check for table header 'Status'
-      monitors.getLegends().its(0).should('have.text', 'Status');
-
-      // check for fields in table
       monitors
-        .getStatusTables()
-        .should('contain.text', 'Cluster ID')
+        .getStatusTable()
+        .should('be.visible')
+        .and('contain.text', 'Cluster ID')
         .and('contain.text', 'monmap modified')
         .and('contain.text', 'monmap epoch')
         .and('contain.text', 'quorum con')
@@ -31,30 +28,21 @@ describe('Monitors page', () => {
         .and('contain.text', 'required mon');
     });
 
-    it('should check In Quorum and Not In Quorum tables are present', () => {
-      // check for there to be two tables
-      monitors.getDataTables().should('have.length', 2);
-
-      // check for table header 'In Quorum'
-      monitors.getLegends().its(1).should('have.text', 'In Quorum');
-
-      // check for table header 'Not In Quorum'
-      monitors.getLegends().its(2).should('have.text', 'Not In Quorum');
-
-      // verify correct columns on In Quorum table
-      monitors.getDataTableHeaders().contains('Name');
-
-      monitors.getDataTableHeaders().contains('Rank');
+    it('should check monitors table is present', () => {
+      monitors.getMonitorTable().should('be.visible');
+      monitors.getDataTables().should('have.length', 1);
 
-      monitors.getDataTableHeaders().contains('Public Address');
-
-      monitors.getDataTableHeaders().contains('Open Sessions');
-      // verify correct columns on Not In Quorum table
-      monitors.getDataTableHeaders().contains('Name');
-
-      monitors.getDataTableHeaders().contains('Rank');
-
-      monitors.getDataTableHeaders().contains('Public Address');
+      monitors.getMonitorTable().find('h4').should('contain.text', 'Monitors');
+      monitors
+        .getMonitorTable()
+        .find('p')
+        .should('contain.text', 'Maintains the master copy of the cluster state');
+
+      monitors.getMonitorTableHeaders().should('contain.text', 'Name');
+      monitors.getMonitorTableHeaders().should('contain.text', 'Rank');
+      monitors.getMonitorTableHeaders().should('contain.text', 'Public address');
+      monitors.getMonitorTableHeaders().should('contain.text', 'In Quorum');
+      monitors.getMonitorTableHeaders().should('contain.text', 'Open sessions');
     });
   });
 });
index 4113b99288d1f898da90364e70aad3c7dfeee7d8..fdcfa93e77c05caf07bacff2270fc2d0ca253a24 100644 (file)
@@ -4,4 +4,18 @@ export class MonitorsPageHelper extends PageHelper {
   pages = {
     index: { url: '#/monitor', id: 'cd-monitor' }
   };
+
+  getStatusTable() {
+    cy.get('cd-monitor cd-table table[cdstable] tbody').should('exist');
+    cy.contains('Loading').should('not.exist');
+    return cy.get('cd-monitor fieldset table');
+  }
+
+  getMonitorTable() {
+    return cy.get('cd-monitor cd-table');
+  }
+
+  getMonitorTableHeaders() {
+    return this.getMonitorTable().find('th');
+  }
 }
index 9a92cfe04eeb7e3be43cab97f144a9ea767acc53..72438079fb62d48441ad83be8383a5627e46a58a 100644 (file)
@@ -1,65 +1,45 @@
-<div class="row">
-  <div class="col-lg-4">
-    <fieldset>
-      <legend class="cd-header"
-              i18n>Status</legend>
-      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
-             *ngIf="mon_status">
+<fieldset>
+    @if(mon_status) {
+    <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
         <tbody>
           <tr>
-            <td i18n
-                class="bold">Cluster ID</td>
+            <td i18n><B>Cluster ID</B></td>
             <td>{{ mon_status.monmap.fsid }}</td>
           </tr>
           <tr>
-            <td i18n
-                class="bold">monmap modified</td>
+            <td i18n><B>monmap modified</B></td>
             <td>{{ mon_status.monmap.modified | relativeDate }}</td>
           </tr>
           <tr>
-            <td i18n
-                class="bold">monmap epoch</td>
+            <td i18n><B>monmap epoch</B></td>
             <td>{{ mon_status.monmap.epoch }}</td>
           </tr>
           <tr>
-            <td i18n
-                class="bold">quorum con</td>
+            <td i18n><B>quorum con</B></td>
             <td>{{ mon_status.features.quorum_con }}</td>
           </tr>
           <tr>
-            <td i18n
-                class="bold">quorum mon</td>
+            <td i18n><B>quorum mon</B></td>
             <td>{{ mon_status.features.quorum_mon }}</td>
           </tr>
           <tr>
-            <td i18n
-                class="bold">required con</td>
+            <td i18n><B>required con</B></td>
             <td>{{ mon_status.features.required_con }}</td>
           </tr>
           <tr>
-            <td i18n
-                class="bold">required mon</td>
+            <td i18n><B>required mon</B></td>
             <td>{{ mon_status.features.required_mon }}</td>
           </tr>
         </tbody>
-      </table>
-    </fieldset>
-  </div>
+    </table>
+    }
+</fieldset>
 
-  <div class="col-lg-8">
-    <legend i18n
-            class="in-quorum cd-header">In Quorum</legend>
-    <div>
-    <cd-table [data]="inQuorum.data"
-              [columns]="inQuorum.columns">
-    </cd-table></div>
-
-    <legend i18n
-            class="in-quorum cd-header">Not In Quorum</legend>
-    <div>
-    <cd-table [data]="notInQuorum.data"
-              (fetchData)="refresh()"
-              [columns]="notInQuorum.columns">
-    </cd-table></div>
-  </div>
+<div class="cds-mt-4">
+    <cd-table [data]="quorum.data"
+            [columns]="quorum.columns"
+            [headerTitle]="title"
+            [headerDescription]="description"
+            (fetchData)="refresh()">
+    </cd-table>
 </div>
index 53673c7f4c0998d2d5b7058099f5bcffdbc091f8..44c7acaf532cc4ad5a6b04a8592677c9f0460631 100644 (file)
@@ -74,31 +74,31 @@ describe('MonitorComponent', () => {
 
     expect(getMonitorSpy).toHaveBeenCalled();
 
-    expect(component.inQuorum.columns[3].comparator(undefined, undefined)).toBe(0);
-    expect(component.inQuorum.columns[3].comparator(null, null)).toBe(0);
-    expect(component.inQuorum.columns[3].comparator([], [])).toBe(0);
+    expect(component.quorum.columns[4].comparator(undefined, undefined)).toBe(0);
+    expect(component.quorum.columns[4].comparator(null, null)).toBe(0);
+    expect(component.quorum.columns[4].comparator([], [])).toBe(0);
     expect(
-      component.inQuorum.columns[3].comparator(
-        component.inQuorum.data[0].cdOpenSessions,
-        component.inQuorum.data[3].cdOpenSessions
+      component.quorum.columns[4].comparator(
+        component.quorum.data[0].cdOpenSessions,
+        component.quorum.data[3].cdOpenSessions
       )
     ).toBe(0);
     expect(
-      component.inQuorum.columns[3].comparator(
-        component.inQuorum.data[0].cdOpenSessions,
-        component.inQuorum.data[1].cdOpenSessions
+      component.quorum.columns[4].comparator(
+        component.quorum.data[0].cdOpenSessions,
+        component.quorum.data[1].cdOpenSessions
       )
     ).toBe(1);
     expect(
-      component.inQuorum.columns[3].comparator(
-        component.inQuorum.data[1].cdOpenSessions,
-        component.inQuorum.data[0].cdOpenSessions
+      component.quorum.columns[4].comparator(
+        component.quorum.data[1].cdOpenSessions,
+        component.quorum.data[0].cdOpenSessions
       )
     ).toBe(-1);
     expect(
-      component.inQuorum.columns[3].comparator(
-        component.inQuorum.data[2].cdOpenSessions,
-        component.inQuorum.data[1].cdOpenSessions
+      component.quorum.columns[4].comparator(
+        component.quorum.data[2].cdOpenSessions,
+        component.quorum.data[1].cdOpenSessions
       )
     ).toBe(1);
   });
index fb0d13d2df10a88aa25be94068f7aebd394ae91e..6293cb0dac8bf7d0b77d1cee4810d62bd4e79701 100644 (file)
@@ -5,6 +5,11 @@ import _ from 'lodash';
 import { MonitorService } from '~/app/shared/api/monitor.service';
 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
 
+const enum QuorumPresent {
+  Yes = 'Yes',
+  No = 'No'
+}
+
 @Component({
   selector: 'cd-monitor',
   templateUrl: './monitor.component.html',
@@ -13,20 +18,31 @@ import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
 })
 export class MonitorComponent {
   mon_status: any;
-  inQuorum: any;
-  notInQuorum: any;
-
+  quorum: any;
   interval: any;
+  title: string = $localize`Monitors`;
+  description: string = $localize`Maintains the master copy of the cluster state, including the monitor map, OSD map, and CRUSH map`;
 
   constructor(private monitorService: MonitorService) {
-    this.inQuorum = {
+    this.quorum = {
       columns: [
         { prop: 'name', name: $localize`Name`, cellTransformation: CellTemplate.routerLink },
         { prop: 'rank', name: $localize`Rank` },
-        { prop: 'public_addr', name: $localize`Public Address` },
+        { prop: 'public_addr', name: $localize`Public address` },
+        {
+          prop: 'status',
+          name: $localize`In Quorum`,
+          cellTransformation: CellTemplate.tag,
+          customTemplateConfig: {
+            map: {
+              Yes: { value: $localize`Yes`, class: 'tag-success' },
+              No: { value: $localize`No`, class: 'tag-danger' }
+            }
+          }
+        },
         {
           prop: 'cdOpenSessions',
-          name: $localize`Open Sessions`,
+          name: $localize`Open sessions`,
           cellTransformation: CellTemplate.sparkline,
           comparator: (dataA: any, dataB: any) => {
             // We get the last value of time series to compare:
@@ -42,14 +58,6 @@ export class MonitorComponent {
         }
       ]
     };
-
-    this.notInQuorum = {
-      columns: [
-        { prop: 'name', name: $localize`Name`, cellTransformation: CellTemplate.routerLink },
-        { prop: 'rank', name: $localize`Rank` },
-        { prop: 'public_addr', name: $localize`Public Address` }
-      ]
-    };
   }
 
   refresh() {
@@ -58,17 +66,19 @@ export class MonitorComponent {
         row.cdOpenSessions = row.stats.num_sessions.map((i: string) => i[1]);
         row.cdLink = '/perf_counters/mon/' + row.name;
         row.cdParams = { fromLink: '/monitor' };
+        row.status = QuorumPresent.Yes;
         return row;
       });
 
       data.out_quorum.map((row: any) => {
         row.cdLink = '/perf_counters/mon/' + row.name;
         row.cdParams = { fromLink: '/monitor' };
+        row.status = QuorumPresent.No;
+        row.cdOpenSessions = [];
         return row;
       });
 
-      this.inQuorum.data = [...data.in_quorum];
-      this.notInQuorum.data = [...data.out_quorum];
+      this.quorum.data = [...data.in_quorum, ...data.out_quorum];
       this.mon_status = data.mon_status;
     });
   }