From: Afreen Misbah Date: Mon, 27 Apr 2026 19:50:41 +0000 (+0530) Subject: mgr/dashboard: Update cypress dashboard e2e tests X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=c259c6f3fcf822adaac37b2ed871db6519b73434;p=ceph.git mgr/dashboard: Update cypress dashboard e2e tests - removed dashboard v3 tests -fixed login, navigation, mirroring, language, osd, page header e2e tests Signed-off-by: Afreen Misbah --- diff --git a/PendingReleaseNotes b/PendingReleaseNotes index f7f8d7b136f5..a5aa832074d4 100644 --- a/PendingReleaseNotes +++ b/PendingReleaseNotes @@ -21,7 +21,7 @@ it must be explicitly loaded in the configuration file or code (see https://github.com/openssl/openssl/blob/master/README-PROVIDERS.md). * RGW: Fixed bucket notification events so the 'x_amz_request_id' in NotificationEvent now matches the 'x_amz_request_id' returned by the corresponding S3 operation. - +* DASHBOARD: Introduces a new landing page - "Overview". This revamps UX and adds more information in the landing page - overall cluster health, health checks, resilency panel (showing active/clean Pgs status), total and used raw capacity, alerts, capacity breakdown by object, file and block and performance charts - throughput, latency and IOPS. This renames teh landing page from "Dashboard" to "Overview" * DASHBOARD: Removed the older landing page which was deprecated in Quincy. Admins can no longer enable the older, deprecated landing page layout by adjusting FEATURE_TOGGLE_DASHBOARD. diff --git a/src/pybind/mgr/dashboard/frontend/cypress.config.ts b/src/pybind/mgr/dashboard/frontend/cypress.config.ts index 3f8f743efaf3..63b236078c3c 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress.config.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress.config.ts @@ -23,7 +23,7 @@ export default defineConfig({ env: { LOGIN_USER: 'admin', LOGIN_PWD: 'admin', - CEPH2_URL: 'https://localhost:11002/' + CEPH2_URL: 'https://localhost:4202/' }, chromeWebSecurity: false, @@ -55,7 +55,7 @@ export default defineConfig({ ) return require('./cypress/plugins/index.js')(on, config); }, - baseUrl: 'https://localhost:11000/', + baseUrl: 'https://localhost:4200/', excludeSpecPattern: ['*.po.ts', '**/orchestrator/**'], experimentalSessionAndOrigin: true, specPattern: 'cypress/e2e/**/*-spec.{js,jsx,ts,tsx,feature}' diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/overview.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/overview.e2e-spec.ts index 720718f0d6c6..04732f07412c 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/overview.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/a11y/overview.e2e-spec.ts @@ -1,7 +1,7 @@ -import { OvevriewPagehelper } from '../ui/dashboard-v3.po'; +import { OverviewPagehelper } from '../ui/overview.po'; describe('Overview Page', { retries: 0 }, () => { - const overview = new OvevriewPagehelper(); + const overview = new OverviewPagehelper(); beforeEach(() => { cy.intercept('GET', '**/api/prometheus/data*', { diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.e2e-spec.ts index 5302e4e3ce9e..306c3e078900 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/block/mirroring.e2e-spec.ts @@ -61,8 +61,19 @@ describe('Mirroring page', () => { cy.get('[type=submit]').click(); cy.get('[data-testid="pool-name"]').clear().type(name); - cy.get('[data-testid="pool-type-select"]').select('replicated'); - cy.get('[data-testid="pool-type-select"] option:checked').contains('replicated'); + + cy.get( + '[data-testid="pool-type-select"] cds-radio input[type="radio"][value="replicated"]' + ).check({ force: true }); + + cy.get('cds-combo-box[id="applications"] input.cds--text-input').click({ force: true }); + cy.get('.cds--list-box__menu.cds--multi-select').should('be.visible'); + cy.get('.cds--list-box__menu.cds--multi-select .cds--checkbox-label') + .contains('.cds--checkbox-label-text', 'rbd', { matchCase: false }) + .parent() + .click({ force: true }); + cy.get('body').type('{esc}'); + cy.get('cd-submit-button').click(); // Wait for form submission navigation to complete cy.url().should('include', '/pool'); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/04-osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/04-osds.e2e-spec.ts index e526fd615743..183cbaf441ce 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/04-osds.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/orchestrator/04-osds.e2e-spec.ts @@ -1,9 +1,9 @@ import { OSDsPageHelper } from '../cluster/osds.po'; -import { OvevriewPagehelper } from '../ui/dashboard-v3.po'; +import { OverviewPagehelper } from '../ui/overview.po'; describe('OSDs page', () => { const osds = new OSDsPageHelper(); - const overview = new OvevriewPagehelper(); + const overview = new OverviewPagehelper(); before(() => { cy.login(); @@ -29,7 +29,11 @@ describe('OSDs page', () => { // landing page is easier to check OSD status overview.navigateTo(); - overview.cardRow('OSD').should('contain.text', `${expectedCount} OSDs`); + overview.clickSystemsTab(); + cy.get(`[data-test-id="OSD-value"]`).should( + 'contain.text', + `${expectedCount}/${expectedCount} in/up` + ); cy.wait(30000); expect(Number(newCount)).to.be.gte(2); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.e2e-spec.ts deleted file mode 100644 index 0a4666d685e1..000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.e2e-spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { OvevriewPagehelper } from './dashboard-v3.po'; - -describe('Dashboard-v3 Main Page', () => { - const overview = new OvevriewPagehelper(); - - before(() => { - cy.login(); - }); - - beforeEach(() => { - cy.login(); - overview.navigateTo(); - }); - - describe('Check that all hyperlinks on inventory card lead to the correct page and fields exist', () => { - it('should ensure that all linked pages in the inventory card lead to correct page', () => { - const expectationMap = { - Host: 'Hosts', - Monitor: 'Monitors', - OSDs: 'OSDs', - Pool: 'Pools', - 'Object Gateway': 'Gateways' - }; - - for (const [linkText, breadcrumbText] of Object.entries(expectationMap)) { - cy.location('hash').should('eq', '#/overview'); - overview.clickInventoryCardLink(linkText); - overview.expectBreadcrumbText(breadcrumbText); - overview.navigateBack(); - } - }); - - it('should verify that cards exist on overview in proper order', () => { - // Ensures that cards are all displayed on the overview tab while being in the proper - // order, checks for card title and position via indexing into a list of all cards. - const order = ['Details', 'Inventory', 'Status', 'Capacity', 'Cluster Utilization']; - - for (let i = 0; i < order.length; i++) { - overview.card(i).should('contain.text', order[i]); - } - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.po.ts deleted file mode 100644 index 059938b86e9d..000000000000 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/dashboard-v3.po.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { PageHelper } from '../page-helper.po'; - -export class OvevriewPagehelper extends PageHelper { - pages = { index: { url: '#/overview', id: 'cd-overview' } }; - - cardTitle(index: number) { - return cy.get('.card-title').its(index).text(); - } - - clickInventoryCardLink(link: string) { - console.log(link); - cy.get(`cd-card[cardTitle="Inventory"]`).contains('a', link).click(); - } - - card(indexOrTitle: number) { - cy.get('cd-card').as('cards'); - - return cy.get('@cards').its(indexOrTitle); - } - - cardRow(rowName: string) { - return cy.get(`[data-testid=${rowName}]`); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.po.ts index 72987b699b71..2e92c8e4e7e4 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/language.po.ts @@ -2,7 +2,7 @@ import { PageHelper } from '../page-helper.po'; export class LanguagePageHelper extends PageHelper { pages = { - index: { url: '#/overview', id: 'cd-dashboard' } + index: { url: '#/overview', id: 'cd-overview' } }; getLanguageBtn() { diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.po.ts index 7746ac1a0c31..b9b4d3064516 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/login.po.ts @@ -3,14 +3,14 @@ import { PageHelper } from '../page-helper.po'; export class LoginPageHelper extends PageHelper { pages = { index: { url: '#/login', id: 'cd-login' }, - overview: { url: '#/overview', id: 'cd-dashboard' } + overview: { url: '#/overview', id: 'cd-overview' } }; doLogin() { cy.get('[name=username]').type('admin'); cy.get('#password').type('admin'); cy.get('[type=submit]').click(); - cy.get('cd-dashboard').should('exist'); + cy.get('cd-overview').should('exist'); } doLogout() { diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.po.ts index 70ba694efa91..90da5fdca7c2 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/navigation.po.ts @@ -88,7 +88,7 @@ export class NavigationPageHelper extends PageHelper { navs.forEach((nav: any) => { cy.get('cds-sidenav-item').each(($link) => { if ($link.text().trim() === nav.menu.trim()) { - cy.wrap($link).click(); + cy.wrap($link).click({ force: true }); } }); if (nav.submenus) { diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.po.ts index 6775d682d7a2..897e11153f57 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/notification.po.ts @@ -2,7 +2,7 @@ import { PageHelper } from '../page-helper.po'; export class NotificationSidebarPageHelper extends PageHelper { getNotificationIcon() { - return cy.get('cd-notifications a'); + return cy.get(`[data-testid='header-notification-icon']`); } getPanel() { @@ -32,7 +32,7 @@ export class NotificationSidebarPageHelper extends PageHelper { } open() { - this.getNotificationIcon().click(); + this.getNotificationIcon().click({ force: true }); this.getPanel().should('exist'); this.getSidebar().should('exist'); } diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/overview.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/overview.po.ts new file mode 100644 index 000000000000..947b032a05e0 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/overview.po.ts @@ -0,0 +1,28 @@ +import { PageHelper } from '../page-helper.po'; + +export class OverviewPagehelper extends PageHelper { + pages = { index: { url: '#/overview', id: 'cd-overview' } }; + + cardTitle(index: number) { + return cy.get('.card-title').its(index).text(); + } + + clickInventoryCardLink(link: string) { + console.log(link); + cy.get(`cd-card[cardTitle="Inventory"]`).contains('a', link).click(); + } + + card(indexOrTitle: number) { + cy.get('cd-card').as('cards'); + + return cy.get('@cards').its(indexOrTitle); + } + + clickSystemsTab() { + cy.get(`[data-test-id="systems-tab"]`).click(); + } + + cardRow(rowName: string) { + return cy.get(`[data-testid=${rowName}]`); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.po.ts index b04b98f1ec78..41f63bb437a7 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.po.ts @@ -1,7 +1,7 @@ import { PageHelper } from '../page-helper.po'; const pages = { - cephfsMirroring: { url: '#/cephfs/mirroring', id: 'cd-cephfs-mirroring-list' } + cephfsMirroring: { url: '#/cephfs/mirroring', id: 'cd-cephfs-mirroring-error' } }; export class PageHeaderPageHelper extends PageHelper { diff --git a/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts b/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts index 2f54487ab0a8..1fe3476c72cb 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts @@ -23,7 +23,7 @@ import { LocalStorage } from '../../src/app/shared/enum/local-storage-enum'; let auth: any; const fillAuth = () => { - window.localStorage.setItem(LocalStorage.DASHBOARD_USRENAME, auth.username); + window.localStorage.setItem(LocalStorage.DASHBOARD_USERNAME, auth.username); window.localStorage.setItem('dashboard_permissions', auth.permissions); window.localStorage.setItem('user_pwd_expiration_date', auth.pwdExpirationDate); window.localStorage.setItem('user_pwd_update_required', auth.pwdUpdateRequired); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/health-card/overview-health-card.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/health-card/overview-health-card.component.html index b7ea0637cccd..a3ea1f11c9ff 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/health-card/overview-health-card.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/health-card/overview-health-card.component.html @@ -121,6 +121,7 @@ class="cds-ml-2" [caret]="true" (click)="toggleSection('system')" + data-test-id="systems-tab" description="" i18n-description> - + {{ item.label }} -

+

{{ vm?.[item.key]?.value }}

diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.spec.ts index ce95fb1f5753..992f68855d75 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.spec.ts @@ -1,4 +1,10 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + ComponentFixture, + discardPeriodicTasks, + fakeAsync, + TestBed, + tick +} from '@angular/core/testing'; import { of, Subject, throwError } from 'rxjs'; import { OverviewComponent } from './overview.component'; @@ -47,12 +53,12 @@ describe('OverviewComponent', () => { }; let mockPrometheusService: { - isPrometheusUsable: jest.Mock; + refreshPrometheusUsable: jest.Mock; }; beforeEach(async () => { mockPrometheusService = { - isPrometheusUsable: jest.fn().mockReturnValue(of(true)) + refreshPrometheusUsable: jest.fn().mockReturnValue(of(true)) }; mockHealthService = { getHealthSnapshot: jest.fn() }; @@ -247,7 +253,7 @@ describe('OverviewComponent', () => { mockRefreshIntervalService.intervalData$.complete(); }); - it('storageCardVm$ should emit storage view model with mapped fields', (done) => { + it('storageCardVm$ should emit storage view model with mapped fields', fakeAsync((done) => { const mockData: HealthSnapshotMap = { fsid: 'fsid-storage', health: { status: 'HEALTH_OK', checks: {} }, @@ -302,8 +308,10 @@ describe('OverviewComponent', () => { done(); }); + tick(0); mockRefreshIntervalService.intervalData$.next(); - }); + discardPeriodicTasks(); + })); it('storageCardVm$ should emit safe defaults before storage side streams resolve', (done) => { const mockData: HealthSnapshotMap = { @@ -327,7 +335,7 @@ describe('OverviewComponent', () => { } as any; mockHealthService.getHealthSnapshot.mockReturnValue(of(mockData)); - mockOverviewStorageService.getStorageBreakdown.mockReturnValue(of(null)); + mockOverviewStorageService.getStorageBreakdown.mockReturnValue(of({ result: [] })); const sub = component.storageCardVm$.subscribe((vm) => { expect(vm.totalCapacity).toBe(1000); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.ts index 9b61a099522b..42a2f2a5b683 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.ts @@ -7,8 +7,16 @@ import { ViewEncapsulation } from '@angular/core'; import { GridModule, LayoutModule, TilesModule } from 'carbon-components-angular'; -import { combineLatest, EMPTY, Observable } from 'rxjs'; -import { catchError, exhaustMap, map, shareReplay, startWith, switchMap } from 'rxjs/operators'; +import { combineLatest, EMPTY, Observable, timer } from 'rxjs'; +import { + catchError, + distinctUntilChanged, + exhaustMap, + map, + shareReplay, + startWith, + switchMap +} from 'rxjs/operators'; import { HealthService } from '~/app/shared/api/health.service'; import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service'; @@ -34,6 +42,7 @@ import { PrometheusService } from '~/app/shared/api/prometheus.service'; const SECONDS_PER_HOUR = 3600; const SECONDS_PER_DAY = 86400; const TREND_DAYS = 7; +const PROMETHUES_CONFIG_POLL_INTERVAL = 60000; @Component({ selector: 'cd-overview', @@ -83,9 +92,11 @@ export class OverviewComponent { ); /* EMPTY STATE DATA */ - readonly isPrometheusUsable$ = this.prometheusService - .isPrometheusUsable() - .pipe(shareReplay({ bufferSize: 1, refCount: true })); + readonly isPromethuesConfigured$ = timer(0, PROMETHUES_CONFIG_POLL_INTERVAL).pipe( + switchMap(() => this.prometheusService.refreshPrometheusUsable()), + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: true }) + ); readonly hasNoOSDs$ = this.healthData$.pipe( map((data: HealthSnapshotMap) => (data?.osdmap?.num_osds ?? 0) === 0), @@ -95,16 +106,16 @@ export class OverviewComponent { readonly storageEmptyState$ = this.hasNoOSDs$.pipe(startWith(false)).pipe( map((hasNoOSDs) => { if (hasNoOSDs) { - return $localize`You must have storage configured to access this capability.`; + return $localize`You can view capacity usage and related metrics here once you add storage.`; } return ''; }), shareReplay({ bufferSize: 1, refCount: true }) ); - readonly prometheusEmptyState$ = this.isPrometheusUsable$.pipe( - map((isPrometheusUsable) => - isPrometheusUsable + readonly prometheusEmptyState$ = this.isPromethuesConfigured$.pipe( + map((isPromethuesConfigured) => + isPromethuesConfigured ? '' : $localize`You must have Prometheus configured to access this capability.` ), @@ -120,34 +131,36 @@ export class OverviewComponent { shareReplay({ bufferSize: 1, refCount: true }) ); - readonly averageConsumption$ = this.isPrometheusUsable$.pipe( - switchMap((usable) => - usable + readonly averageConsumption$ = this.isPromethuesConfigured$.pipe( + switchMap((isConfigured) => + isConfigured ? this.refreshIntervalObs(() => this.overviewStorageService.getAverageConsumption()) : EMPTY ), shareReplay({ bufferSize: 1, refCount: true }) ); - readonly timeUntilFull$ = this.isPrometheusUsable$.pipe( - switchMap((usable) => - usable ? this.refreshIntervalObs(() => this.overviewStorageService.getTimeUntilFull()) : EMPTY + readonly timeUntilFull$ = this.isPromethuesConfigured$.pipe( + switchMap((isConfigured) => + isConfigured + ? this.refreshIntervalObs(() => this.overviewStorageService.getTimeUntilFull()) + : EMPTY ), shareReplay({ bufferSize: 1, refCount: true }) ); - readonly breakdownRawData$ = this.isPrometheusUsable$.pipe( - switchMap((usable) => - usable + readonly breakdownRawData$ = this.isPromethuesConfigured$.pipe( + switchMap((isConfigured) => + isConfigured ? this.refreshIntervalObs(() => this.overviewStorageService.getStorageBreakdown()) : EMPTY ), shareReplay({ bufferSize: 1, refCount: true }) ); - readonly capacityThresholds$ = this.isPrometheusUsable$.pipe( - switchMap((usable) => - usable + readonly capacityThresholds$ = this.isPromethuesConfigured$.pipe( + switchMap((isConfigured) => + isConfigured ? this.refreshIntervalObs(() => this.overviewStorageService.getRawCapacityThresholds()) : EMPTY ), @@ -156,9 +169,9 @@ export class OverviewComponent { // getTrendData() is already a polling stream through getRangeQueriesData() // hence no refresh needed. - readonly trendData$ = this.isPrometheusUsable$.pipe( - switchMap((usable) => - usable + readonly trendData$ = this.isPromethuesConfigured$.pipe( + switchMap((isConfigured) => + isConfigured ? this.overviewStorageService.getTrendData( Math.floor(Date.now() / 1000) - TREND_DAYS * SECONDS_PER_DAY, Math.floor(Date.now() / 1000), diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.ts index 9e5017ca7bd1..0be836e56ff1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/about/about.component.ts @@ -55,7 +55,7 @@ export class AboutComponent extends BaseModal implements OnInit, OnDestroy { setVariables() { const NOT_AVAILABLE = $localize`Not available`; const project = {} as any; - project.user = localStorage.getItem(LocalStorage.DASHBOARD_USRENAME); + project.user = localStorage.getItem(LocalStorage.DASHBOARD_USERNAME); project.role = USER; if (this.userPermission.read) { this.userService.get(project.user).subscribe((data: any) => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html index 9074d0c129a2..ba995dc16c31 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html @@ -35,6 +35,7 @@
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts index 5204064a8f2f..2acb2f27bcbc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/auth.service.spec.ts @@ -41,7 +41,7 @@ describe('AuthService', () => { expect(req.request.body).toEqual(fakeCredentials); req.flush(fakeResponse); tick(); - expect(localStorage.getItem(LocalStorage.DASHBOARD_USRENAME)).toBe('foo'); + expect(localStorage.getItem(LocalStorage.DASHBOARD_USERNAME)).toBe('foo'); })); it('should logout and remove the user', () => { @@ -52,7 +52,7 @@ describe('AuthService', () => { const req = httpTesting.expectOne('api/auth/logout'); expect(req.request.method).toBe('POST'); req.flush({ redirect_url: '#/login' }); - expect(localStorage.getItem(LocalStorage.DASHBOARD_USRENAME)).toBe(null); + expect(localStorage.getItem(LocalStorage.DASHBOARD_USERNAME)).toBe(null); expect(router.navigate).toBeCalledTimes(1); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts index 0b93d171696a..ae5e5db06548 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts @@ -91,14 +91,17 @@ export class PrometheusService { } isPrometheusUsable(): Observable { - return this.isPrometheusModuleEnabled().pipe( - switchMap((enabled) => - enabled ? this.isSettingConfigured(this.settingsKey.prometheus) : of(false) - ), + return this.isSettingConfigured(this.settingsKey.prometheus).pipe( + map((isConfigured) => isConfigured), catchError(() => of(false)) ); } + refreshPrometheusUsable(): Observable { + delete this.settings[this.settingsKey.prometheus]; + return this.isPrometheusUsable(); + } + isAlertmanagerUsable(): Observable { return this.isPrometheusModuleEnabled().pipe( switchMap((enabled) => diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/storage-overview.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/storage-overview.service.spec.ts index 3a256176f216..10a4bbd3271a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/storage-overview.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/storage-overview.service.spec.ts @@ -275,17 +275,16 @@ describe('OverviewStorageService', () => { }); describe('getStorageBreakdown', () => { - it('should call getPrometheusQueryData with storage breakdown query', () => { + it('should call getGaugeQueryData with storage breakdown query', () => { const promSpy = jest - .spyOn(service['prom'], 'getPrometheusQueryData') + .spyOn(service['prom'], 'getGaugeQueryData') .mockReturnValue(of({}) as any); service.getStorageBreakdown().subscribe(); - expect(promSpy).toHaveBeenCalledWith({ - params: - 'sum by (application) (ceph_pool_bytes_used * on(pool_id) group_left(instance, name, application) ceph_pool_metadata{application=~"(.*Block.*)|(.*Filesystem.*)|(.*Object.*)|(..*)"})' - }); + expect(promSpy).toHaveBeenCalledWith( + 'sum by (application) (ceph_pool_bytes_used * on(pool_id) group_left(instance, name, application) ceph_pool_metadata{application=~"(.*Block.*)|(.*Filesystem.*)|(.*Object.*)|(..*)"})' + ); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/storage-overview.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/storage-overview.service.ts index a5afd9452708..a8ed8e319bc9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/storage-overview.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/storage-overview.service.ts @@ -121,7 +121,7 @@ export class OverviewStorageService { } getStorageBreakdown(): Observable { - return this.prom.getPrometheusQueryData({ params: this.RAW_USED_BY_STORAGE_TYPE_QUERY }); + return this.prom.getGaugeQueryData(this.RAW_USED_BY_STORAGE_TYPE_QUERY); } getThresholdStatus(total, used, nearfull, full): CapacityThreshold { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/empty-state/empty-state.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/empty-state/empty-state.component.scss index 28079273da46..0d8ceda8d69c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/empty-state/empty-state.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/empty-state/empty-state.component.scss @@ -1,18 +1,21 @@ @use '@carbon/colors'; -empty-state { +.empty-state { display: flex; flex-direction: column; justify-content: flex-end; gap: var(--cds-spacing-05); - height: 350px; + margin-top: 283px; + padding: var(--cds-spacing-05) var(--cds-spacing-05) var(--cds-spacing-07) var(--cds-spacing-05); + width: 264px; + margin-left: var(--cds-spacing-05); - p { - font-size: 12px !important; + img { + width: 80px !important; + height: 80px !important; } - img { - width: 100px !important; - height: 100px !important; + span { + color: var(--cds-text-secondary); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/local-storage-enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/local-storage-enum.ts index 611a8bcffdb6..b8c17b18de39 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/local-storage-enum.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/enum/local-storage-enum.ts @@ -1,3 +1,3 @@ export enum LocalStorage { - DASHBOARD_USRENAME = 'dashboard_username' + DASHBOARD_USERNAME = 'dashboard_username' } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts index 76e3290ed908..2b2965441309 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.spec.ts @@ -15,13 +15,13 @@ describe('AuthStorageService', () => { it('should store username', () => { service.set(username, ''); - expect(localStorage.getItem(LocalStorage.DASHBOARD_USRENAME)).toBe(username); + expect(localStorage.getItem(LocalStorage.DASHBOARD_USERNAME)).toBe(username); }); it('should remove username', () => { service.set(username, ''); service.remove(); - expect(localStorage.getItem(LocalStorage.DASHBOARD_USRENAME)).toBe(null); + expect(localStorage.getItem(LocalStorage.DASHBOARD_USERNAME)).toBe(null); }); it('should be loggedIn', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts index 02c4feede3bc..d6dbedf110af 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/auth-storage.service.ts @@ -16,7 +16,7 @@ export class AuthStorageService { pwdExpirationDate: number = null, pwdUpdateRequired: boolean = false ) { - localStorage.setItem(LocalStorage.DASHBOARD_USRENAME, username); + localStorage.setItem(LocalStorage.DASHBOARD_USERNAME, username); localStorage.setItem('dashboard_permissions', JSON.stringify(new Permissions(permissions))); localStorage.setItem('user_pwd_expiration_date', String(pwdExpirationDate)); localStorage.setItem('user_pwd_update_required', String(pwdUpdateRequired)); @@ -24,17 +24,17 @@ export class AuthStorageService { } remove() { - localStorage.removeItem(LocalStorage.DASHBOARD_USRENAME); + localStorage.removeItem(LocalStorage.DASHBOARD_USERNAME); localStorage.removeItem('user_pwd_expiration_data'); localStorage.removeItem('user_pwd_update_required'); } isLoggedIn() { - return localStorage.getItem(LocalStorage.DASHBOARD_USRENAME) !== null; + return localStorage.getItem(LocalStorage.DASHBOARD_USERNAME) !== null; } getUsername() { - return localStorage.getItem(LocalStorage.DASHBOARD_USRENAME); + return localStorage.getItem(LocalStorage.DASHBOARD_USERNAME); } getPermissions(): Permissions {