]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix alert broken for multiple alerts 58069/head
authorNizamudeen A <nia@redhat.com>
Sun, 16 Jun 2024 12:46:52 +0000 (18:16 +0530)
committerNizamudeen A <nia@redhat.com>
Wed, 19 Jun 2024 13:32:59 +0000 (19:02 +0530)
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 <nia@redhat.com>
13 files changed:
src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.scss
src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.scss
src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.scss

index 2b3c82bfe20be0c7b1e01a05e4cd7fe944aca4f3..958ba64129a65b71eb5cd5167dd27c4371a65785 100644 (file)
@@ -2,7 +2,16 @@
   <cd-navigation>
     <div class="container-fluid h-100"
          [ngClass]="{'dashboard': (router.url == '/dashboard' || router.url == '/dashboard_3' || router.url == '/multi-cluster/overview'), 'rgw-dashboard': (router.url == '/rgw/overview')}">
-    <cd-context></cd-context>
+      <!-- ************************ -->
+      <!-- ALERTS BANNER     -->
+      <!-- ************************ -->
+      <div class="cd-alert-container"
+           [ngClass]="{'ms-4 me-4': (router.url == '/dashboard' || router.url == '/dashboard_3' || router.url == '/multi-cluster/overview'), 'm-3': (router.url == '/rgw/overview')}">
+        <cd-pwd-expiration-notification></cd-pwd-expiration-notification>
+        <cd-telemetry-notification></cd-telemetry-notification>
+        <cd-motd></cd-motd>
+      </div>
+      <cd-context></cd-context>
       <cd-breadcrumbs></cd-breadcrumbs>
       <router-outlet></router-outlet>
     </div>
index e44a6d0afb37f3feafc1703992a0ffed066e99e8..321f684da291545642187e042a660abff6a03199 100644 (file)
@@ -16,3 +16,8 @@
 .rgw-dashboard {
   background-color: vv.$body-bg-alt;
 }
+
+.cd-alert-container {
+  display: flex;
+  flex-direction: column;
+}
index faf8c9cdf9400ac19587b80a71afb230af2bd53e..22451d8206a0cee962e441b287556655e47dff73 100644 (file)
@@ -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();
+    });
+  });
 });
index 054ebf8bba1120ea569ac361d1fef8db217507c0..230e6e7ae44599880ec6d9cebf9d9f345a864bdf 100644 (file)
@@ -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();
index 8a2c698403802e702ec877cdeb724ce866989bde..a4fb9ab24166ee0ce0f8b10002805b287a0c5a26 100644 (file)
@@ -1,10 +1,7 @@
 <div class="cd-navbar-main">
 <!-- ************************ -->
-<!-- NOTIFICATIONS/ALERTS     -->
+<!-- NOTIFICATIONS     -->
 <!-- ************************ -->
-<cd-pwd-expiration-notification></cd-pwd-expiration-notification>
-<cd-telemetry-notification></cd-telemetry-notification>
-<cd-motd></cd-motd>
 <cd-notifications-sidebar></cd-notifications-sidebar>
 <!-- ************************ -->
 <!-- HEADER                   -->
index 92d9a28878a0d4bc4be731aa09244a56367a1453..c674cfdcf5c07c7d87b813c6965cfb2d21c48850 100644 (file)
@@ -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();
-    });
-  });
 });
index d0b5fcf81069cf0f7eacaf61a1f8faede4037a33..fefe1d8dab5bc8e9943334e99dab5d902d004892 100644 (file)
@@ -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) => {
index 30f8b530a59c5fc170688e0462f15f58dbbaf74f..58761eead54bb2e4f1bfc18762a522b0b15b79a4 100644 (file)
@@ -1,43 +1,50 @@
-<ngb-alert type="{{ bootstrapClass }}"
-           [dismissible]="dismissible"
-           (closed)="onClose()"
-           [ngClass]="spacingClass">
-  <table>
-    <ng-container *ngIf="size === 'normal'; else slim">
-      <tr>
-        <td *ngIf="showIcon"
-            rowspan="2"
-            class="alert-panel-icon">
-          <i [ngClass]="[icons.large3x]"
-             class="alert-{{ bootstrapClass }} {{ typeIcon }}"
-             aria-hidden="true"></i>
-        </td>
-        <td *ngIf="showTitle"
-            class="alert-panel-title">{{ title }}</td>
-      </tr>
-      <tr>
-        <td class="alert-panel-text">
-          <ng-container *ngTemplateOutlet="content"></ng-container>
-        </td>
-      </tr>
-    </ng-container>
-    <ng-template #slim>
-      <tr>
-        <td *ngIf="showIcon"
-            class="alert-panel-icon">
-          <i class="alert-{{ bootstrapClass }} {{ typeIcon }}"
-             aria-hidden="true"></i>
-        </td>
-        <td *ngIf="showTitle"
-            class="alert-panel-title">{{ title }}</td>
-        <td class="alert-panel-text">
-          <ng-container *ngTemplateOutlet="content"></ng-container>
-        </td>
-      </tr>
-    </ng-template>
-  </table>
-</ngb-alert>
+<cds-actionable-notification
+      class="mb-1"
+      [notificationObj]="notificationContent"
+      *ngIf="size === 'slim'; else normal">
+</cds-actionable-notification>
+
+<ng-template #normal>
+  <ngb-alert type="{{ bootstrapClass }}"
+             [dismissible]="dismissible"
+             (closed)="onClose()"
+             [ngClass]="spacingClass">
+    <table>
+      <ng-container *ngIf="size === 'normal'">
+        <tr>
+          <td *ngIf="showIcon"
+              rowspan="2"
+              class="alert-panel-icon">
+            <i [ngClass]="[icons.large3x]"
+               class="alert-{{ bootstrapClass }} {{ typeIcon }}"
+               aria-hidden="true"></i>
+          </td>
+          <td *ngIf="showTitle"
+              class="alert-panel-title">{{ title }}</td>
+        </tr>
+        <tr>
+          <td class="alert-panel-text">
+            <ng-container *ngTemplateOutlet="content"></ng-container>
+          </td>
+        </tr>
+      </ng-container>
+    </table>
+  </ngb-alert>
+</ng-template>
 
 <ng-template #content>
   <ng-content></ng-content>
 </ng-template>
+
+<ng-template #closeTpl>
+  <button cdsActionableButton
+          cdsButton="ghost"
+          size="md"
+          title="Close"
+          (click)="onClose()"
+          *ngIf="dismissible">
+    <svg class="cds--btn__icon"
+         cdsIcon="close"
+         size="16"></svg>
+  </button>
+</ng-template>
index cc2024baa23359f5a83e88c7bec5e0dd40891738..3402eea5742779596c761a73cc7af1da2836cec1 100644 (file)
@@ -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<any>;
+  @ViewChild('closeTpl', { static: true })
+  closeTpl: TemplateRef<any>;
+
   @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`;
index 21d333cc0f90a331fe8dd5c5e7e80b1d0e2af8a3..e6ccde0a36293f6d8410ac109f735b45279977cc 100644 (file)
@@ -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,
index 98f473e3bbf79fbaaa92325165986a1793d3a0d9..9af7958370a79d6f167dc362b1720b8f7bcd06f0 100644 (file)
@@ -1,5 +1,5 @@
 <cd-alert-panel *ngIf="displayNotification"
-                class="no-margin-bottom telemetry-notification"
+                class="no-margin-bottom"
                 [showTitle]="false"
                 size="slim"
                 [type]="notificationSeverity"
index 53efbe18980ff1feb99d17b0762b1e7d5e9f87f0..1e1606724ca4cf08192af846711a5a1f0a9b4bc4 100644 (file)
@@ -21,11 +21,3 @@ a {
   color: darken(vv.$primary, 10);
   font-weight: bold;
 }
-
-.telemetry-notification {
-  bottom: 25px;
-  position: absolute;
-  right: 50%;
-  transform: translateX(50%);
-  z-index: 99999;
-}