From 1f1a31e6dd0a3529eaafae07b7153f2f069917d0 Mon Sep 17 00:00:00 2001 From: Afreen Misbah Date: Tue, 19 May 2026 05:39:47 +0530 Subject: [PATCH] mgr/dashboard: carbonize RGW overview dashboard Fixes: https://tracker.ceph.com/issues/76684 Signed-off-by: Afreen Misbah Assisted-by: Claude --- .../rgw-overview-dashboard.component.html | 375 ++++++++++-------- .../rgw-overview-dashboard.component.scss | 102 ++++- .../rgw-overview-dashboard.component.spec.ts | 62 ++- .../rgw-overview-dashboard.component.ts | 20 +- .../rgw-sync-data-info.component.html | 117 +++--- .../rgw-sync-data-info.component.scss | 35 +- .../rgw-sync-data-info.component.spec.ts | 14 +- .../rgw-sync-metadata-info.component.html | 124 +++--- .../rgw-sync-metadata-info.component.scss | 35 +- .../rgw-sync-metadata-info.component.spec.ts | 14 +- .../frontend/src/app/ceph/rgw/rgw.module.ts | 12 +- .../workbench-layout.component.html | 2 +- .../workbench-layout.component.scss | 7 +- .../shared/api/performance-card.service.ts | 9 +- 14 files changed, 591 insertions(+), 337 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.html index 69ab46002049..60d6331a7d48 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.html @@ -1,182 +1,219 @@ -
-
- - - - - - - - - - - - - +
+
+
+ + +

Overview

+
+
+
+ Gateway + {{ rgwDaemonCount }} +
+
+ Realm + {{ rgwRealmCount }} +
+
+ Zonegroup + {{ rgwZonegroupCount }} +
+
+ Zone + {{ rgwZoneCount }} +
+
+ Bucket + {{ rgwBucketCount }} +
+
+ User + {{ UserCount }} +
+
+ Object + {{ objectCount }} +
+
+ Used Capacity + {{ totalPoolUsedBytes | dimlessBinary }} +
+
+ Average Object Size + {{ averageObjectSize | dimlessBinary }} +
+
+
+
+
- - - -
- - - - - - - - -
-
-
- - -

{{ totalPoolUsedBytes | dimlessBinary}}

-
-
- - -

{{ averageObjectSize | dimlessBinary}}

-
-
+
+
+ + +

Performance Statistics

+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
-
- - - +
+
+ + +

Multi-Site Sync Status

+
+ Multi-site needs to be configured in order to see the multi-site sync status. Please consult the  on how to configure and enable the multi-site functionality. - - - - - -
-
- - - - - - - - - -
- - - - -
- -
- - - - - - - - - - + + @if (loading) { +
+ +
+ } + @if (multisiteSyncStatus$ | async) { + @if (showMultisiteCard) { +
+
+

Primary Source Zone

+
+ @if (loading) { +
+ +
+ } @else { + + + } +
+
+
+

Source Zones

+ @if (loading) { +
+ +
+ } @else { +
+ @for (zone of replicaZonesInfo; track zone) { + +
+ + {{ zone.name }} +
+
+ @for (title of chartTitles; track title) { +
+ {{ title }} + @if (title === 'Metadata Sync') { + + + } + @if (title === 'Data Sync') { + + + } +
+ } +
+
+ }
- + }
- -
-
-
-
+
+ } @else { + + } + } + +
-
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.scss index b735edde21f1..c9f980ae4b47 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.scss @@ -1,19 +1,89 @@ -@use './src/styles/vendor/variables' as vv; +.rgw-overview-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + row-gap: var(--cds-spacing-06); + column-gap: var(--cds-spacing-07); -hr { - margin-bottom: 2px; - margin-top: 2px; + &__item { + display: flex; + flex-direction: column; + gap: var(--cds-spacing-02); + } } -.list-group-item { - border: 0; -} +.multisite-sync { + &__loading { + display: flex; + justify-content: center; + padding: var(--cds-spacing-05); + } + + &__layout { + display: flex; + align-items: flex-start; + } + + &__primary { + flex: 0 0 250px; + text-align: center; + } + + &__primary-content { + border-inline-end: 1px solid var(--cds-border-subtle); + padding-inline-end: var(--cds-spacing-05); + } + + &__section-title { + margin-block-end: var(--cds-spacing-05); + + &--centered { + text-align: center; + } + } + + &__source-zones { + flex: 1; + padding-inline-start: var(--cds-spacing-05); + } + + &__zones-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--cds-spacing-05); + } + + &__zone-tile { + height: 100%; + padding-block-start: 0; + } -.align-replica-zones { - margin-left: auto; - margin-right: auto; - padding-left: 2em; - padding-right: 2em; + &__zone-header { + display: flex; + align-items: center; + justify-content: center; + gap: var(--cds-spacing-02); + margin-block-end: var(--cds-spacing-04); + } + + &__sync-columns { + display: grid; + grid-template-columns: 1fr 1fr; + } + + &__sync-item { + display: flex; + flex-direction: column; + align-items: center; + + &--bordered { + border-inline-start: 1px solid var(--cds-border-subtle); + } + } + + &__sync-title { + text-align: center; + margin-block-end: var(--cds-spacing-03); + } } ul { @@ -22,11 +92,3 @@ ul { flex-direction: column; list-style-type: none; } - -.align-primary-zone { - padding-left: 4em; -} - -.border-left { - border-left: 1px solid vv.$chart-color-border; -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts index 941eabb426f9..3fde88d9c12a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts @@ -119,8 +119,10 @@ describe('RgwOverviewDashboardComponent', () => { }); it('should render all cards', () => { - const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card'); - expect(dashboardCards.length).toBe(5); + const productiveCards = fixture.debugElement.nativeElement.querySelectorAll( + 'cd-productive-card' + ); + expect(productiveCards.length).toBe(3); }); it('should get data for Realms', () => { @@ -138,6 +140,62 @@ describe('RgwOverviewDashboardComponent', () => { expect(component.rgwZoneCount).toEqual(4); }); + it('should transform prometheus data to chart format', () => { + const mockResults: Record = { + RGW_REQUEST_PER_SECOND: [ + [1700000000, '10'], + [1700000060, '20'] + ], + AVG_GET_LATENCY: [ + [1700000000, '1.5'], + [1700000060, '2.0'] + ], + AVG_PUT_LATENCY: [ + [1700000000, '3.0'], + [1700000060, '4.0'] + ], + GET_BANDWIDTH: [ + [1700000000, '1024'], + [1700000060, '2048'] + ], + PUT_BANDWIDTH: [ + [1700000000, '512'], + [1700000060, '768'] + ] + }; + + const perfService = component['performanceCardService']; + component['getPrometheusData'] = function (_selectedTime: any) { + this.queriesResults = mockResults; + this.requestsChartData = perfService.toSeries( + mockResults.RGW_REQUEST_PER_SECOND, + 'Requests/sec' + ); + this.latencyChartData = perfService.mergeSeries( + perfService.toSeries(mockResults.AVG_GET_LATENCY, 'GET'), + perfService.toSeries(mockResults.AVG_PUT_LATENCY, 'PUT') + ); + this.bandwidthChartData = perfService.mergeSeries( + perfService.toSeries(mockResults.GET_BANDWIDTH, 'GET'), + perfService.toSeries(mockResults.PUT_BANDWIDTH, 'PUT') + ); + }; + + component['getPrometheusData']({}); + + expect(component.requestsChartData.length).toBe(2); + expect(component.requestsChartData[0].values['Requests/sec']).toBe(10); + expect(component.requestsChartData[0].timestamp).toEqual(new Date(1700000000 * 1000)); + + expect(component.latencyChartData.length).toBe(2); + expect(component.latencyChartData[0].values['GET']).toBe(1.5); + expect(component.latencyChartData[0].values['PUT']).toBe(3.0); + + expect(component.bandwidthChartData.length).toBe(2); + expect(component.bandwidthChartData[0].values['GET']).toBe(1024); + expect(component.bandwidthChartData[0].values['PUT']).toBe(512); + }); + it('should set component properties from services using combineLatest', fakeAsync(() => { component.interval = of(null).subscribe(() => { component.fetchDataSub = combineLatest([ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts index 61f93c9fda13..3ab594b37c4c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts @@ -16,9 +16,11 @@ import { PrometheusService } from '~/app/shared/api/prometheus.service'; import { RgwPromqls as queries } from '~/app/shared/enum/dashboard-promqls.enum'; import { Icons } from '~/app/shared/enum/icons.enum'; import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service'; +import { ChartPoint } from '~/app/shared/models/area-chart-point'; import { catchError, shareReplay, switchMap, takeUntil, tap } from 'rxjs/operators'; import { NotificationService } from '~/app/shared/services/notification.service'; import { NotificationType } from '~/app/shared/enum/notification-type.enum'; +import { PerformanceCardService } from '~/app/shared/api/performance-card.service'; @Component({ selector: 'cd-rgw-overview-dashboard', @@ -51,6 +53,9 @@ export class RgwOverviewDashboardComponent implements OnInit, OnDestroy { AVG_GET_LATENCY: [], AVG_PUT_LATENCY: [] }; + requestsChartData: ChartPoint[] = []; + latencyChartData: ChartPoint[] = []; + bandwidthChartData: ChartPoint[] = []; timerGetPrometheusDataSub: Subscription; chartTitles = ['Metadata Sync', 'Data Sync']; realm: string; @@ -76,7 +81,8 @@ export class RgwOverviewDashboardComponent implements OnInit, OnDestroy { private rgwBucketService: RgwBucketService, private prometheusService: PrometheusService, private rgwMultisiteService: RgwMultisiteService, - private notificationService: NotificationService + private notificationService: NotificationService, + private performanceCardService: PerformanceCardService ) { this.permissions = this.authStorageService.getPermissions(); } @@ -158,6 +164,18 @@ export class RgwOverviewDashboardComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.destroy$)) .subscribe((results) => { this.queriesResults = results; + this.requestsChartData = this.performanceCardService.toSeries( + results?.RGW_REQUEST_PER_SECOND || [], + $localize`Requests/sec` + ); + this.latencyChartData = this.performanceCardService.mergeSeries( + this.performanceCardService.toSeries(results?.AVG_GET_LATENCY || [], 'GET'), + this.performanceCardService.toSeries(results?.AVG_PUT_LATENCY || [], 'PUT') + ); + this.bandwidthChartData = this.performanceCardService.mergeSeries( + this.performanceCardService.toSeries(results?.GET_BANDWIDTH || [], 'GET'), + this.performanceCardService.toSeries(results?.PUT_BANDWIDTH || [], 'PUT') + ); }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.html index bef3e5c67b6f..2e2db07843ca 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.html @@ -1,58 +1,63 @@ -
    - -
  • - Up to Date -
  • -
    - - -
    -
    -
      -
    • Sync Status:
    • -
    • - - - {{ status.split(': ')[0] | titlecase }}:{{ status.split(': ')[1] | titlecase}} - - - {{ status | titlecase }} - - - - {{ status | titlecase }} - -
    • -
    +
    + +
    + Status: + @if (zone.syncstatus?.includes('failed') || zone.syncstatus?.includes('error')) { + + + Error + + } @else { + @if (zone.syncstatus === 'Not Syncing From Zone') { + Not Syncing + } @else { + {{ zone.syncstatus | titlecase }} + } + } +
    +
    +
    +
    + @for (status of zone.fullSyncStatus; track status) { + @if (status?.includes('failed') || status?.includes('error')) { +
    + {{ status | titlecase }} +
    + } @else if (!status?.includes(zone.name) && !status?.includes(zone.syncstatus) && status?.trim()) { + @if (status?.includes(':')) { +
    + {{ status.split(': ')[0] | titlecase }} + {{ status.split(': ')[1] | titlecase }} +
    + } @else { +
    + Summary + {{ status | titlecase }} +
    + } + } + } +
    +
    -
    -
  • - Last Synced: -
  • -
  • - - {{ zone.timestamp | relativeDate }} - -
  • -
+
+ Last Synced: + @if (zone.timestamp) { + + {{ zone.timestamp | relativeDate }} + + } @else { + Up to Date + } +
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.scss index dbb6a0cedf1a..1988b39b0bff 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.scss @@ -1,13 +1,42 @@ @use './src/styles/vendor/variables' as vv; -ul { - align-items: center; +.sync-info { display: flex; flex-direction: column; - list-style-type: none; + align-items: center; + + &__status { + display: flex; + align-items: center; + gap: var(--cds-spacing-02); + } + + &__last-synced { + display: flex; + flex-direction: column; + align-items: center; + } } .toggleTipBtn { flex-direction: column; text-align: center; } + +.toggletip-content { + display: flex; + flex-direction: column; + gap: var(--cds-spacing-04); + + &__item { + display: flex; + flex-direction: column; + gap: var(--cds-spacing-01); + } +} + +.sync-status-link { + text-decoration: underline dotted; + text-underline-offset: 3px; + cursor: pointer; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.spec.ts index 94e886f14831..0614d778446d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-data-info/rgw-sync-data-info.component.spec.ts @@ -39,10 +39,10 @@ describe('RgwSyncDataInfoComponent', () => { component.zone = { syncstatus: 'Syncing', timestamp: new Date(Date.now() - 10 * 60 * 1000) }; fixture.detectChanges(); - const statusElement = fixture.debugElement.query(By.css('li b')); + const statusElement = fixture.debugElement.query(By.css('.cds--type-label-01')); expect(statusElement.nativeElement.textContent).toContain('Status:'); - const lastSyncedElement = fixture.debugElement.query(By.css('li.mt-4.fw-bold')); + const lastSyncedElement = fixture.debugElement.query(By.css('.sync-info__last-synced')); expect(lastSyncedElement.nativeElement.textContent).toContain('Last Synced:'); const lastSyncedTimestamp = fixture.debugElement.query(By.css('cds-tag.tag-info')); expect(lastSyncedTimestamp.nativeElement.textContent.trim()).toEqual('10 minutes ago'); @@ -59,16 +59,16 @@ describe('RgwSyncDataInfoComponent', () => { ] }; fixture.detectChanges(); - const syncStatus = fixture.debugElement.query(By.css('.text-primary')); + const syncStatus = fixture.debugElement.query(By.css('.cds--link')); expect(syncStatus).toBeTruthy(); expect(syncStatus.nativeElement.textContent).toEqual('Syncing'); const syncPopover = fixture.nativeElement.querySelector('a'); syncPopover.dispatchEvent(new Event('click')); fixture.detectChanges(); expect(syncPopover).toBeTruthy(); - const syncPopoverText = fixture.debugElement.query(By.css('.text-center')); - expect(syncPopoverText.nativeElement.textContent).toEqual( - 'Sync Status:Full Sync:0/128 Shards Incremental Sync:128/128 Shards: Data Is Behind On 31 Shards' - ); + const syncPopoverText = fixture.debugElement.query(By.css('.toggletip-content')); + expect(syncPopoverText.nativeElement.textContent).toContain('Full Sync'); + expect(syncPopoverText.nativeElement.textContent).toContain('0/128 Shards'); + expect(syncPopoverText.nativeElement.textContent).toContain('Data Is Behind On 31 Shards'); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.html index 6f923e9eff06..6db4caf3b37b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.html @@ -1,69 +1,71 @@ - -
    -
  • Status:
  • -
  • No Sync
  • -
-
- -
    - -
  • - - Up to Date - -
  • -
    - +@if (metadataSyncInfo === 'no sync (zone is master)') { +
    + Status: + No Sync +
    +} @else { +
    +
    - - {{ metadataSyncInfo.syncstatus | titlecase }} - Not Syncing - -
  • Status:
  • -
  • - - Error -
  • + Status: + @if (metadataSyncInfo.syncstatus?.includes('failed') || metadataSyncInfo.syncstatus?.includes('error')) { + + + Error + + } @else { + @if (metadataSyncInfo.syncstatus === 'Not Syncing From Zone') { + Not Syncing + } @else { + {{ metadataSyncInfo.syncstatus | titlecase }} + } + }
    -
      -
    • Metadata Sync Status:
    • -
    • - - - {{ status.split(':')[0] | titlecase }}:{{ status.split(':')[1] | titlecase}} - - - {{ status | titlecase }} - - - - {{ status | titlecase }} - -
    • -
    +
    + @for (status of metadataSyncInfo.fullSyncStatus; track status; let last = $last) { + @if (status?.includes('failed') || status?.includes('error')) { +
    + {{ status | titlecase }} +
    + } @else if (!status?.includes(metadataSyncInfo.syncstatus) && status?.trim()) { + @if (status?.includes(':')) { +
    + {{ status.split(':')[0] | titlecase }} + {{ status.split(':')[1] | titlecase }} +
    + } @else { +
    + Summary + {{ status | titlecase }} +
    + } + } + } +
    -
  • - Last Synced: -
  • -
  • - - {{ metadataSyncInfo.timestamp | relativeDate }} - -
  • -
-
+
+ Last Synced: + @if (metadataSyncInfo.timestamp) { + + {{ metadataSyncInfo.timestamp | relativeDate }} + + } @else { + Up to Date + } +
+
+} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.scss index 91dc6e037394..225afd402f57 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.scss @@ -1,12 +1,41 @@ @use './src/styles/vendor/variables' as vv; -ul { - align-items: center; +.sync-info { display: flex; flex-direction: column; - list-style-type: none; + align-items: center; + + &__status { + display: flex; + align-items: center; + gap: var(--cds-spacing-02); + } + + &__last-synced { + display: flex; + flex-direction: column; + align-items: center; + } } .toggleTipBtn { flex-direction: column; } + +.toggletip-content { + display: flex; + flex-direction: column; + gap: var(--cds-spacing-04); + + &__item { + display: flex; + flex-direction: column; + gap: var(--cds-spacing-01); + } +} + +.sync-status-link { + text-decoration: underline dotted; + text-underline-offset: 3px; + cursor: pointer; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.spec.ts index 1b4155a01a05..ec6e9a38081b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-sync-metadata-info/rgw-sync-metadata-info.component.spec.ts @@ -43,10 +43,10 @@ describe('RgwSyncMetadataInfoComponent', () => { }; fixture.detectChanges(); - const statusElement = fixture.debugElement.query(By.css('li b')); + const statusElement = fixture.debugElement.query(By.css('.cds--type-label-01')); expect(statusElement.nativeElement.textContent).toContain('Status:'); - const lastSyncedElement = fixture.debugElement.query(By.css('li.mt-4.fw-bold')); + const lastSyncedElement = fixture.debugElement.query(By.css('.sync-info__last-synced')); expect(lastSyncedElement.nativeElement.textContent).toContain('Last Synced:'); const lastSyncedTimestamp = fixture.debugElement.query(By.css('cds-tag')); expect(lastSyncedTimestamp.nativeElement.classList).toContain('tag-info'); @@ -64,16 +64,16 @@ describe('RgwSyncMetadataInfoComponent', () => { ] }; fixture.detectChanges(); - const syncStatus = fixture.debugElement.query(By.css('.text-primary')); + const syncStatus = fixture.debugElement.query(By.css('.cds--link')); expect(syncStatus).toBeTruthy(); expect(syncStatus.nativeElement.textContent).toEqual('Syncing'); const syncPopover = fixture.nativeElement.querySelector('a'); syncPopover.dispatchEvent(new Event('click')); fixture.detectChanges(); expect(syncPopover).toBeTruthy(); - const syncPopoverText = fixture.debugElement.query(By.css('.text-center')); - expect(syncPopoverText.nativeElement.textContent).toEqual( - 'Metadata Sync Status:Full Sync:0/128 Shards Incremental Sync:128/128 Shards Data Is Behind On 31 Shards' - ); + const syncPopoverText = fixture.debugElement.query(By.css('.toggletip-content')); + expect(syncPopoverText.nativeElement.textContent).toContain('Full Sync'); + expect(syncPopoverText.nativeElement.textContent).toContain('0/128 Shards'); + expect(syncPopoverText.nativeElement.textContent).toContain('Data Is Behind On 31 Shards'); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts index dd1f0c9d2b2b..9a5e465d3ee8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts @@ -85,7 +85,8 @@ import { ToggletipModule, IconService, LayoutModule, - SkeletonModule + SkeletonModule, + TilesModule } from 'carbon-components-angular'; import EditIcon from '@carbon/icons/es/edit/16'; import ScalesIcon from '@carbon/icons/es/scales/20'; @@ -100,6 +101,9 @@ import ToolsIcon from '@carbon/icons/es/tools/32'; import ParentChild from '@carbon/icons/es/parent-child/20'; import UserAccessLocked from '@carbon/icons/es/user--access-locked/16'; +import { ProductiveCardComponent } from '~/app/shared/components/productive-card/productive-card.component'; +import { TimePickerComponent } from '~/app/shared/components/time-picker/time-picker.component'; +import { AreaChartComponent } from '~/app/shared/components/area-chart/area-chart.component'; import { CephSharedModule } from '../shared/ceph-shared.module'; import { RgwUserAccountsComponent } from './rgw-user-accounts/rgw-user-accounts.component'; import { RgwUserAccountsFormComponent } from './rgw-user-accounts-form/rgw-user-accounts-form.component'; @@ -154,7 +158,11 @@ import { RgwNotificationFormComponent } from './rgw-notification-form/rgw-notifi RadioModule, SelectModule, LayoutModule, - SkeletonModule + SkeletonModule, + TilesModule, + ProductiveCardComponent, + TimePickerComponent, + AreaChartComponent ], exports: [ RgwDaemonDetailsComponent, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html index aeafc41c4470..95d610d26d3a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html @@ -6,7 +6,7 @@
+ [ngClass]="{'ms-4 me-4': (router.url == '/overview' || router.url == '/dashboard_3' || router.url == '/multi-cluster/overview' || router.url == '/rgw/overview')}"> @if (enabledFeature$ | async; as features) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss index 5fe0558f2db3..7eae41be97cc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss @@ -1,8 +1,13 @@ @use './src/styles/vendor/variables' as vv; -.overview { +.overview, +.rgw-dashboard { margin: 0; padding: 0; + + .breadcrumbs--padding { + padding-left: var(--cds-spacing-07); + } } .container-fluid { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/performance-card.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/performance-card.service.ts index 41f01496b3a2..0d5e409c06a9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/performance-card.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/performance-card.service.ts @@ -4,6 +4,7 @@ import { PerformanceData } from '../models/performance-data'; import { AllStoragetypesQueries } from '../enum/dashboard-promqls.enum'; import { map } from 'rxjs/operators'; import { Observable } from 'rxjs'; +import { ChartPoint } from '../models/area-chart-point'; @Injectable({ providedIn: 'root' @@ -50,15 +51,15 @@ export class PerformanceCardService { }; } - private toSeries(metric: [number, string][], label: string) { + toSeries(metric: [number, string][], label: string): ChartPoint[] { return metric.map(([ts, val]) => ({ timestamp: new Date(ts * 1000), values: { [label]: Number(val) } })); } - private mergeSeries(...series: any[]) { - const map = new Map(); + mergeSeries(...series: ChartPoint[][]): ChartPoint[] { + const map = new Map(); for (const items of series) { for (const item of items) { @@ -75,6 +76,6 @@ export class PerformanceCardService { } } - return [...map.values()].sort((a, b) => a.timestamp - b.timestamp); + return [...map.values()].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); } } -- 2.47.3