From b9bc49f6a904e4e9faf31e5e86da2b6a04530330 Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Sun, 16 Jun 2024 18:16:52 +0530 Subject: [PATCH] mgr/dashboard: fix alert broken for multiple alerts After carbon the alert panel was broken when there are multiple alerts present (telemetry, motd, password expiration). Applying carbon banner to the existing alert banner Fixes: https://tracker.ceph.com/issues/66512 Signed-off-by: Nizamudeen A --- .../workbench-layout.component.html | 11 ++- .../workbench-layout.component.scss | 5 ++ .../workbench-layout.component.spec.ts | 52 ++++++++++++ .../workbench-layout.component.ts | 41 ++++++++- .../navigation/navigation.component.html | 5 +- .../navigation/navigation.component.spec.ts | 52 ------------ .../navigation/navigation.component.ts | 43 +--------- .../alert-panel/alert-panel.component.html | 85 ++++++++++--------- .../alert-panel/alert-panel.component.scss | 4 + .../alert-panel/alert-panel.component.ts | 27 +++++- .../shared/components/components.module.ts | 10 ++- .../telemetry-notification.component.html | 2 +- .../telemetry-notification.component.scss | 8 -- 13 files changed, 193 insertions(+), 152 deletions(-) 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 2b3c82bfe20..958ba64129a 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 @@ -2,7 +2,16 @@
- + + + +
+ + + +
+
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 e44a6d0afb3..321f684da29 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 @@ -16,3 +16,8 @@ .rgw-dashboard { background-color: vv.$body-bg-alt; } + +.cd-alert-container { + display: flex; + flex-direction: column; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts index faf8c9cdf94..22451d8206a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts @@ -32,4 +32,56 @@ describe('WorkbenchLayoutComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + describe('showTopNotification', () => { + const notification1 = 'notificationName1'; + const notification2 = 'notificationName2'; + + beforeEach(() => { + component.notifications = []; + }); + + it('should show notification', () => { + component.showTopNotification(notification1, true); + expect(component.notifications.includes(notification1)).toBeTruthy(); + expect(component.notifications.length).toBe(1); + }); + + it('should not add a second notification if it is already shown', () => { + component.showTopNotification(notification1, true); + component.showTopNotification(notification1, true); + expect(component.notifications.includes(notification1)).toBeTruthy(); + expect(component.notifications.length).toBe(1); + }); + + it('should add a second notification if the first one is different', () => { + component.showTopNotification(notification1, true); + component.showTopNotification(notification2, true); + expect(component.notifications.includes(notification1)).toBeTruthy(); + expect(component.notifications.includes(notification2)).toBeTruthy(); + expect(component.notifications.length).toBe(2); + }); + + it('should hide an active notification', () => { + component.showTopNotification(notification1, true); + expect(component.notifications.includes(notification1)).toBeTruthy(); + expect(component.notifications.length).toBe(1); + component.showTopNotification(notification1, false); + expect(component.notifications.length).toBe(0); + }); + + it('should not fail if it tries to hide an inactive notification', () => { + expect(() => component.showTopNotification(notification1, false)).not.toThrow(); + expect(component.notifications.length).toBe(0); + }); + + it('should keep other notifications if it hides one', () => { + component.showTopNotification(notification1, true); + component.showTopNotification(notification2, true); + expect(component.notifications.length).toBe(2); + component.showTopNotification(notification2, false); + expect(component.notifications.length).toBe(1); + expect(component.notifications.includes(notification1)).toBeTruthy(); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts index 054ebf8bba1..230e6e7ae44 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, HostBinding, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Subscription } from 'rxjs'; @@ -9,6 +9,9 @@ import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; import { FaviconService } from '~/app/shared/services/favicon.service'; import { SummaryService } from '~/app/shared/services/summary.service'; import { TaskManagerService } from '~/app/shared/services/task-manager.service'; +import { TelemetryNotificationService } from '../../../shared/services/telemetry-notification.service'; +import { MotdNotificationService } from '~/app/shared/services/motd-notification.service'; +import _ from 'lodash'; @Component({ selector: 'cd-workbench-layout', @@ -17,8 +20,12 @@ import { TaskManagerService } from '~/app/shared/services/task-manager.service'; providers: [FaviconService] }) export class WorkbenchLayoutComponent implements OnInit, OnDestroy { + notifications: string[] = []; private subs = new Subscription(); permissions: Permissions; + @HostBinding('class') get class(): string { + return 'top-notification-' + this.notifications.length; + } constructor( public router: Router, @@ -26,7 +33,9 @@ export class WorkbenchLayoutComponent implements OnInit, OnDestroy { private taskManagerService: TaskManagerService, private multiClusterService: MultiClusterService, private faviconService: FaviconService, - private authStorageService: AuthStorageService + private authStorageService: AuthStorageService, + private telemetryNotificationService: TelemetryNotificationService, + private motdNotificationService: MotdNotificationService ) { this.permissions = this.authStorageService.getPermissions(); } @@ -38,8 +47,36 @@ export class WorkbenchLayoutComponent implements OnInit, OnDestroy { } this.subs.add(this.summaryService.startPolling()); this.subs.add(this.taskManagerService.init(this.summaryService)); + + this.subs.add( + this.authStorageService.isPwdDisplayed$.subscribe((isDisplayed) => { + this.showTopNotification('isPwdDisplayed', isDisplayed); + }) + ); + this.subs.add( + this.telemetryNotificationService.update.subscribe((visible: boolean) => { + this.showTopNotification('telemetryNotificationEnabled', visible); + }) + ); + this.subs.add( + this.motdNotificationService.motd$.subscribe((motd: any) => { + this.showTopNotification('motdNotificationEnabled', _.isPlainObject(motd)); + }) + ); this.faviconService.init(); } + showTopNotification(name: string, isDisplayed: boolean) { + if (isDisplayed) { + if (!this.notifications.includes(name)) { + this.notifications.push(name); + } + } else { + const index = this.notifications.indexOf(name); + if (index >= 0) { + this.notifications.splice(index, 1); + } + } + } ngOnDestroy() { this.subs.unsubscribe(); 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 8a2c6984038..a4fb9ab2416 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 @@ -1,10 +1,7 @@
- + - - - diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts index 92d9a28878a..c674cfdcf5c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts @@ -214,56 +214,4 @@ describe('NavigationComponent', () => { }); } }); - - describe('showTopNotification', () => { - const notification1 = 'notificationName1'; - const notification2 = 'notificationName2'; - - beforeEach(() => { - component.notifications = []; - }); - - it('should show notification', () => { - component.showTopNotification(notification1, true); - expect(component.notifications.includes(notification1)).toBeTruthy(); - expect(component.notifications.length).toBe(1); - }); - - it('should not add a second notification if it is already shown', () => { - component.showTopNotification(notification1, true); - component.showTopNotification(notification1, true); - expect(component.notifications.includes(notification1)).toBeTruthy(); - expect(component.notifications.length).toBe(1); - }); - - it('should add a second notification if the first one is different', () => { - component.showTopNotification(notification1, true); - component.showTopNotification(notification2, true); - expect(component.notifications.includes(notification1)).toBeTruthy(); - expect(component.notifications.includes(notification2)).toBeTruthy(); - expect(component.notifications.length).toBe(2); - }); - - it('should hide an active notification', () => { - component.showTopNotification(notification1, true); - expect(component.notifications.includes(notification1)).toBeTruthy(); - expect(component.notifications.length).toBe(1); - component.showTopNotification(notification1, false); - expect(component.notifications.length).toBe(0); - }); - - it('should not fail if it tries to hide an inactive notification', () => { - expect(() => component.showTopNotification(notification1, false)).not.toThrow(); - expect(component.notifications.length).toBe(0); - }); - - it('should keep other notifications if it hides one', () => { - component.showTopNotification(notification1, true); - component.showTopNotification(notification2, true); - expect(component.notifications.length).toBe(2); - component.showTopNotification(notification2, false); - expect(component.notifications.length).toBe(1); - expect(component.notifications.includes(notification1)).toBeTruthy(); - }); - }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts index d0b5fcf8106..fefe1d8dab5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts @@ -1,4 +1,4 @@ -import { Component, HostBinding, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import * as _ from 'lodash'; @@ -14,10 +14,8 @@ import { FeatureTogglesMap$, FeatureTogglesService } from '~/app/shared/services/feature-toggles.service'; -import { MotdNotificationService } from '~/app/shared/services/motd-notification.service'; import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service'; import { SummaryService } from '~/app/shared/services/summary.service'; -import { TelemetryNotificationService } from '~/app/shared/services/telemetry-notification.service'; @Component({ selector: 'cd-navigation', @@ -25,11 +23,7 @@ import { TelemetryNotificationService } from '~/app/shared/services/telemetry-no styleUrls: ['./navigation.component.scss'] }) export class NavigationComponent implements OnInit, OnDestroy { - notifications: string[] = []; clusterDetails: any[] = []; - @HostBinding('class') get class(): string { - return 'top-notification-' + this.notifications.length; - } permissions: Permissions; enabledFeature$: FeatureTogglesMap$; @@ -54,9 +48,7 @@ export class NavigationComponent implements OnInit, OnDestroy { private router: Router, private summaryService: SummaryService, private featureToggles: FeatureTogglesService, - private telemetryNotificationService: TelemetryNotificationService, public prometheusAlertService: PrometheusAlertService, - private motdNotificationService: MotdNotificationService, private cookieService: CookiesService, private settingsService: SettingsService ) { @@ -91,26 +83,6 @@ export class NavigationComponent implements OnInit, OnDestroy { this.summaryData = summary; }) ); - /* - Note: If you're going to add more top notifications please do not forget to increase - the number of generated css-classes in section topNotification settings in the scss - file. - */ - this.subs.add( - this.authStorageService.isPwdDisplayed$.subscribe((isDisplayed) => { - this.showTopNotification('isPwdDisplayed', isDisplayed); - }) - ); - this.subs.add( - this.telemetryNotificationService.update.subscribe((visible: boolean) => { - this.showTopNotification('telemetryNotificationEnabled', visible); - }) - ); - this.subs.add( - this.motdNotificationService.motd$.subscribe((motd: any) => { - this.showTopNotification('motdNotificationEnabled', _.isPlainObject(motd)); - }) - ); this.subs.add( this.multiClusterService.subscribeClusterTokenStatus((resp: object) => { this.clusterTokenStatus = resp; @@ -165,19 +137,6 @@ export class NavigationComponent implements OnInit, OnDestroy { this.rightSidebarOpen = !this.rightSidebarOpen; } - showTopNotification(name: string, isDisplayed: boolean) { - if (isDisplayed) { - if (!this.notifications.includes(name)) { - this.notifications.push(name); - } - } else { - const index = this.notifications.indexOf(name); - if (index >= 0) { - this.notifications.splice(index, 1); - } - } - } - onClusterSelection(value: object) { this.multiClusterService.setCluster(value).subscribe( (resp: any) => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html index 30f8b530a59..58761eead54 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html @@ -1,43 +1,50 @@ - - - - - - - - - - - - - - - - - - -
- - {{ title }}
- -
- - {{ title }} - -
-
+ + + + + + + + + + + + + + + +
+ + {{ title }}
+ +
+
+
+ + + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.scss index 6b89d6d3e38..98541e9bfda 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.scss @@ -10,3 +10,7 @@ .alert { margin-bottom: 0; } + +cds-actionable-notification { + max-width: 100%; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts index cc2024baa23..3402eea5742 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts @@ -1,4 +1,13 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + OnInit, + Output, + TemplateRef, + ViewChild +} from '@angular/core'; +import { NotificationContent, NotificationType } from 'carbon-components-angular'; import { Icons } from '~/app/shared/enum/icons.enum'; @@ -8,6 +17,11 @@ import { Icons } from '~/app/shared/enum/icons.enum'; styleUrls: ['./alert-panel.component.scss'] }) export class AlertPanelComponent implements OnInit { + @ViewChild('content', { static: true }) + alertContent: TemplateRef; + @ViewChild('closeTpl', { static: true }) + closeTpl: TemplateRef; + @Input() title = ''; @Input() @@ -36,7 +50,18 @@ export class AlertPanelComponent implements OnInit { icons = Icons; + notificationContent: NotificationContent; + ngOnInit() { + const type: NotificationType = this.type === 'danger' ? 'error' : this.type; + this.notificationContent = { + type: type, + template: this.alertContent, + actionsTemplate: this.closeTpl, + showClose: false, + title: this.title + }; + switch (this.type) { case 'warning': this.title = this.title || $localize`Warning`; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts index 21d333cc0f9..e6ccde0a362 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts @@ -15,7 +15,12 @@ import { import { ClickOutsideModule } from 'ng-click-outside'; import { NgChartsModule } from 'ng2-charts'; import { SimplebarAngularModule } from 'simplebar-angular'; -import { UIShellModule, ButtonModule, NotificationModule } from 'carbon-components-angular'; +import { + UIShellModule, + ButtonModule, + NotificationModule, + IconModule +} from 'carbon-components-angular'; import { MotdComponent } from '~/app/shared/components/motd/motd.component'; import { DirectivesModule } from '../directives/directives.module'; @@ -80,7 +85,8 @@ import { UpgradableComponent } from './upgradable/upgradable.component'; NgbTimepickerModule, UIShellModule, ButtonModule, - NotificationModule + NotificationModule, + IconModule ], declarations: [ SparklineComponent, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.html index 98f473e3bbf..9af7958370a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.html @@ -1,5 +1,5 @@