]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Improve Notification sidebar 32895/head
authorTiago Melo <tmelo@suse.com>
Wed, 22 Jan 2020 09:46:30 +0000 (08:46 -0100)
committerTiago Melo <tmelo@suse.com>
Mon, 3 Feb 2020 11:50:12 +0000 (10:50 -0100)
Remove the external package and add custom scrollbar.

Fix random e2e tests.

Fixes: https://tracker.ceph.com/issues/43804
Fixes: https://tracker.ceph.com/issues/43593
Signed-off-by: Tiago Melo <tmelo@suse.com>
21 files changed:
src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts
src/pybind/mgr/dashboard/frontend/e2e/ui/notification.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/e2e/ui/notification.po.ts
src/pybind/mgr/dashboard/frontend/package-lock.json
src/pybind/mgr/dashboard/frontend/package.json
src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/login/login.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/core.module.ts
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.scss
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.scss
src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.ts
src/pybind/mgr/dashboard/frontend/src/styles/defaults.scss

index 263974f56e7b49c4ff3a069f85612bd825111301..0a33340a431e8c33cc3489e47e45d1e193150e0f 100644 (file)
@@ -249,6 +249,10 @@ export abstract class PageHelper {
     return $$('legend');
   }
 
+  getToast() {
+    return $('.ngx-toastr');
+  }
+
   async waitPresence(elem: ElementFinder, message?: string) {
     return browser.wait(EC.presenceOf(elem), TIMEOUT, message);
   }
index 7822a54b9fc7d16134d62189432c259bba65ae34..52d172a87c65d96bf95a143629b73fb8997172e7 100644 (file)
@@ -11,16 +11,11 @@ describe('Notification page', () => {
   });
 
   afterEach(async () => {
-    NotificationSidebarPageHelper.checkConsole();
-
-    if (await notification.getCloseBtn().isPresent()) {
-      await notification.waitClickableAndClick(notification.getCloseBtn());
-      await notification.waitStaleness(notification.getSidebar());
-    }
+    await NotificationSidebarPageHelper.checkConsole();
   });
 
   it('should open notification sidebar', async () => {
-    await notification.waitStaleness(notification.getSidebar());
+    await notification.waitInvisibility(notification.getSidebar());
     await notification.open();
     await notification.waitVisibility(notification.getSidebar());
   });
@@ -31,6 +26,7 @@ describe('Notification page', () => {
     await pools.navigateTo('create');
     await pools.create(poolName, 16);
     await pools.edit_pool_pg(poolName, 8, false);
+    await notification.waitStaleness(notification.getToast());
 
     await notification.open();
     await notification.waitVisibility(notification.getTasks().first());
@@ -45,8 +41,9 @@ describe('Notification page', () => {
   });
 
   it('should clear notifications', async () => {
-    await notification.open();
+    await notification.waitStaleness(notification.getToast());
     await expect((await notification.getNotifications()).length).toBeGreaterThan(0);
+    await notification.waitVisibility(notification.getClearNotficationsBtn());
     await notification.waitClickableAndClick(notification.getClearNotficationsBtn());
     await notification.waitStaleness(notification.getNotifications().first());
     await expect((await notification.getNotifications()).length).toBe(0);
index 35e493d8df62f517393fe49c23b496479a23aac0..68eeb61e919e534b2f283b94df912185919f3af4 100644 (file)
@@ -8,7 +8,7 @@ export class NotificationSidebarPageHelper extends PageHelper {
   }
 
   getSidebar() {
-    return element(by.css('aside.ng-sidebar--opened cd-notifications-sidebar'));
+    return element(by.css('cd-notifications-sidebar'));
   }
 
   getTasks() {
index 2e9506f0d58e58afade7aa6ef0678534349655df..1c911576027df76f72934cc40651041e54a5b3b5 100644 (file)
         "worker-plugin": "3.2.0"
       },
       "dependencies": {
+        "caniuse-lite": {
+          "version": "1.0.30001019",
+          "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001019.tgz",
+          "integrity": "sha512-6ljkLtF1KM5fQ+5ZN0wuyVvvebJxgJPTmScOMaFuQN2QuOzvRJnWSKfzQskQU5IOU4Gap3zasYPIinzwUjoj/g==",
+          "dev": true
+        },
         "core-js": {
           "version": "3.2.1",
           "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
         "mimic-fn": {
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
-          "dev": true
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
         },
         "os-locale": {
           "version": "3.1.0",
           }
         },
         "mem": {
-          "dev": true,
           "requires": {
             "map-age-cleaner": "^0.1.1",
             "mimic-fn": "^2.0.0",
         "mimic-fn": {
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
-          "dev": true
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
         },
         "os-locale": {
           "version": "3.1.0",
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
       "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
-      "dev": true,
       "requires": {
         "p-defer": "^1.0.0"
       }
       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
       "dev": true
     },
-    "mem": {
-      "requires": {
-        "mimic-fn": "^1.0.0"
-      },
-      "version": "4.3.0"
-    },
     "memory-fs": {
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
         "mime-db": "1.43.0"
       }
     },
-    "mimic-fn": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
-      "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
-    },
     "mini-css-extract-plugin": {
       "version": "0.8.0",
       "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz",
         "tslib": "^1.9.0"
       }
     },
-    "ng-sidebar": {
-      "version": "9.1.1",
-      "resolved": "https://registry.npmjs.org/ng-sidebar/-/ng-sidebar-9.1.1.tgz",
-      "integrity": "sha512-G8BAaV/TsfkMHyy4FbaDEjrKdu0b55aEjZM1Nrz1xG62J/jyfhgK+S0ma3nszUWK6hKMqwXXVBghoX8pl9SoVg=="
+    "ng-click-outside": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/ng-click-outside/-/ng-click-outside-5.3.0.tgz",
+      "integrity": "sha512-+WYtu2hSQy0F6VlHOqKhPtdVJimTiXXNtZPBGfLORJNX71ieYGsentke8KG+8EudR36FUB6Ya9g2GwGXM0UqdA=="
     },
     "ng2-charts": {
       "version": "2.3.0",
       "dependencies": {
         "mem": {
           "version": "4.3.0"
+        },
+        "mimic-fn": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
         }
       }
     },
     "p-defer": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
-      "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
-      "dev": true
+      "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww="
     },
     "p-each-series": {
       "version": "1.0.0",
     "p-is-promise": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
-      "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
-      "dev": true
+      "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg=="
     },
     "p-limit": {
       "version": "1.3.0",
           }
         },
         "mem": {
-          "dev": true,
           "requires": {
             "map-age-cleaner": "^0.1.1",
             "mimic-fn": "^2.0.0",
         "mimic-fn": {
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
-          "dev": true
+          "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
         },
         "os-locale": {
           "version": "3.1.0",
index 1d77fef928af136b4d292b82d9c5f5bcd7d54770..3c5ab4ff263de377f5d7f361ddc316e9af78aeb5 100644 (file)
@@ -99,7 +99,7 @@
     "moment": "2.24.0",
     "ng-block-ui": "2.1.7",
     "ng-bootstrap-form-validation": "5.0.0",
-    "ng-sidebar": "9.1.1",
+    "ng-click-outside": "5.3.0",
     "ng2-charts": "2.3.0",
     "ng2-tree": "2.0.0-rc.11",
     "ngx-bootstrap": "5.1.2",
index 03a48b86324629773f76d0aa4aadcc41803f5035..daa96f34e8bfda92edd00912fa19859a81d770cf 100644 (file)
@@ -2,10 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { ToastrModule } from 'ngx-toastr';
-
-import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
-import { NotificationService } from '../../../shared/services/notification.service';
+import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { AuthModule } from '../auth.module';
 import { LoginComponent } from './login.component';
 
@@ -14,8 +11,7 @@ describe('LoginComponent', () => {
   let fixture: ComponentFixture<LoginComponent>;
 
   configureTestBed({
-    imports: [RouterTestingModule, HttpClientTestingModule, AuthModule, ToastrModule.forRoot()],
-    providers: [i18nProviders]
+    imports: [RouterTestingModule, HttpClientTestingModule, AuthModule]
   });
 
   beforeEach(() => {
@@ -33,13 +29,4 @@ describe('LoginComponent', () => {
     component.ngOnInit();
     expect(component['bsModalService'].getModalsCount()).toBe(0);
   });
-
-  it('should call toggleSidebar if not logged in', () => {
-    const notificationService: NotificationService = TestBed.get(NotificationService);
-    spyOn(notificationService, 'toggleSidebar').and.callThrough();
-
-    component.ngOnInit();
-
-    expect(notificationService.toggleSidebar).toHaveBeenCalledWith(true);
-  });
 });
index db095550f550e2b255c5bc0a53472e51e17a74f2..9286e31d30f107ea11a64662cdd9111cbacbda0b 100644 (file)
@@ -6,7 +6,6 @@ import { BsModalService } from 'ngx-bootstrap/modal';
 import { AuthService } from '../../../shared/api/auth.service';
 import { Credentials } from '../../../shared/models/credentials';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
-import { NotificationService } from '../../../shared/services/notification.service';
 
 @Component({
   selector: 'cd-login',
@@ -21,8 +20,7 @@ export class LoginComponent implements OnInit {
     private authService: AuthService,
     private authStorageService: AuthStorageService,
     private bsModalService: BsModalService,
-    private router: Router,
-    private notificationService: NotificationService
+    private router: Router
   ) {}
 
   ngOnInit() {
@@ -37,9 +35,6 @@ export class LoginComponent implements OnInit {
         this.bsModalService.hide(i);
       }
 
-      // Make sure notification sidebar is closed.
-      this.notificationService.toggleSidebar(true);
-
       let token: string = null;
       if (window.location.hash.indexOf('access_token=') !== -1) {
         token = window.location.hash.split('access_token=')[1];
index e6194267320cbc0cdcb904a57bca5a1b094371b7..b452185fd124b5c565b04ec8e08615cef2a4b82f 100644 (file)
@@ -3,7 +3,6 @@ import { NgModule } from '@angular/core';
 import { RouterModule } from '@angular/router';
 
 import { BlockUIModule } from 'ng-block-ui';
-import { SidebarModule } from 'ng-sidebar';
 
 import { SharedModule } from '../shared/shared.module';
 import { ForbiddenComponent } from './forbidden/forbidden.component';
@@ -13,14 +12,7 @@ import { NavigationModule } from './navigation/navigation.module';
 import { NotFoundComponent } from './not-found/not-found.component';
 
 @NgModule({
-  imports: [
-    BlockUIModule.forRoot(),
-    CommonModule,
-    NavigationModule,
-    RouterModule,
-    SharedModule,
-    SidebarModule.forRoot()
-  ],
+  imports: [BlockUIModule.forRoot(), CommonModule, NavigationModule, RouterModule, SharedModule],
   exports: [NavigationModule],
   declarations: [
     NotFoundComponent,
index 1b32a4dfea5f12d6353b5227b0629c608ee11670..ae81cdee71d42020f36189228ca0ff7deb3d17b4 100644 (file)
@@ -1,26 +1,9 @@
-<!-- Container for sidebar(s) + page content -->
-<ng-sidebar-container>
-  <!-- A sidebar -->
-  <ng-sidebar #sidebar
-              [(opened)]="sidebarOpened"
-              [animate]="sidebarAnimate"
-              position="end"
-              mode="over"
-              autoFocus="false"
-              closeOnClickOutside="true">
-    <cd-notifications-sidebar></cd-notifications-sidebar>
-  </ng-sidebar>
-
-  <!-- Page content -->
-  <div ng-sidebar-content>
-    <block-ui>
-      <cd-navigation>
-        <div class="container-fluid"
-             [ngClass]="{'dashboard':isDashboardPage()} ">
-          <cd-breadcrumbs></cd-breadcrumbs>
-          <router-outlet></router-outlet>
-        </div>
-      </cd-navigation>
-    </block-ui>
-  </div>
-</ng-sidebar-container>
+<block-ui>
+  <cd-navigation>
+    <div class="container-fluid h-100"
+         [ngClass]="{'dashboard':isDashboardPage()} ">
+      <cd-breadcrumbs></cd-breadcrumbs>
+      <router-outlet></router-outlet>
+    </div>
+  </cd-navigation>
+</block-ui>
index 4bf4a0494188f3e7baba10c03a2abf99ad535cc4..3d1d440824f86503960effb5c7c670f3879e4506 100644 (file)
@@ -3,14 +3,12 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { SidebarModule } from 'ng-sidebar';
 import { ToastrModule } from 'ngx-toastr';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
 import { RbdService } from '../../../shared/api/rbd.service';
 import { PipesModule } from '../../../shared/pipes/pipes.module';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
-import { NotificationService } from '../../../shared/services/notification.service';
 import { WorkbenchLayoutComponent } from './workbench-layout.component';
 
 describe('WorkbenchLayoutComponent', () => {
@@ -18,13 +16,7 @@ describe('WorkbenchLayoutComponent', () => {
   let fixture: ComponentFixture<WorkbenchLayoutComponent>;
 
   configureTestBed({
-    imports: [
-      RouterTestingModule,
-      ToastrModule.forRoot(),
-      PipesModule,
-      HttpClientTestingModule,
-      SidebarModule.forRoot()
-    ],
+    imports: [RouterTestingModule, ToastrModule.forRoot(), PipesModule, HttpClientTestingModule],
     declarations: [WorkbenchLayoutComponent],
     schemas: [NO_ERRORS_SCHEMA],
     providers: [AuthStorageService, i18nProviders, RbdService]
@@ -39,38 +31,4 @@ describe('WorkbenchLayoutComponent', () => {
   it('should create', () => {
     expect(component).toBeTruthy();
   });
-
-  describe('Sidebar', () => {
-    let notificationService: NotificationService;
-
-    beforeEach(() => {
-      notificationService = TestBed.get(NotificationService);
-    });
-
-    it('should always close if sidebarSubject value is true', () => {
-      // Closed before next value
-      expect(component.sidebarOpened).toBeFalsy();
-      notificationService.sidebarSubject.next(true);
-      expect(component.sidebarOpened).toBeFalsy();
-
-      // Opened before next value
-      component.sidebarOpened = true;
-      expect(component.sidebarOpened).toBeTruthy();
-      notificationService.sidebarSubject.next(true);
-      expect(component.sidebarOpened).toBeFalsy();
-    });
-
-    it('should toggle sidebar visibility if sidebarSubject value is false', () => {
-      // Closed before next value
-      expect(component.sidebarOpened).toBeFalsy();
-      notificationService.sidebarSubject.next(false);
-      expect(component.sidebarOpened).toBeTruthy();
-
-      // Opened before next value
-      component.sidebarOpened = true;
-      expect(component.sidebarOpened).toBeTruthy();
-      notificationService.sidebarSubject.next(false);
-      expect(component.sidebarOpened).toBeFalsy();
-    });
-  });
 });
index c913918fd3490e0025393b2fc95ec256926626bd..64d3f4c38edbe86b0d64af5d0626bceb92cdabe6 100644 (file)
@@ -1,9 +1,7 @@
-import { Component, OnDestroy, ViewChild } from '@angular/core';
+import { Component } from '@angular/core';
 import { Router } from '@angular/router';
 
-import { Sidebar } from 'ng-sidebar';
 import { TooltipConfig } from 'ngx-bootstrap/tooltip';
-import { Subscription } from 'rxjs';
 
 import { NotificationService } from '../../../shared/services/notification.service';
 
@@ -21,33 +19,8 @@ import { NotificationService } from '../../../shared/services/notification.servi
     }
   ]
 })
-export class WorkbenchLayoutComponent implements OnDestroy {
-  @ViewChild(Sidebar, { static: true })
-  sidebar: Sidebar;
-
-  sidebarOpened = false;
-  // There is a bug in ng-sidebar that will show the sidebar closing animation
-  // when the page is first loaded. This prevents that.
-  sidebarAnimate = false;
-
-  private readonly sidebarSubscription: Subscription;
-
-  constructor(private router: Router, public notificationService: NotificationService) {
-    this.sidebarSubscription = this.notificationService.sidebarSubject.subscribe((forcedClose) => {
-      if (forcedClose) {
-        this.sidebar.close();
-      } else {
-        this.sidebarAnimate = true;
-        this.sidebarOpened = !this.sidebarOpened;
-      }
-    });
-  }
-
-  ngOnDestroy() {
-    if (this.sidebarSubscription) {
-      this.sidebarSubscription.unsubscribe();
-    }
-  }
+export class WorkbenchLayoutComponent {
+  constructor(private router: Router, public notificationService: NotificationService) {}
 
   isDashboardPage() {
     return this.router.url === '/dashboard';
index 7ef562d4f8576526dcf201c7f6fc8b07b8510f18..1b1b90511613f226edcf4b0f64f43e072cf34b44 100644 (file)
@@ -1,7 +1,8 @@
 <cd-pwd-expiration-notification></cd-pwd-expiration-notification>
+<cd-notifications-sidebar></cd-notifications-sidebar>
+
 <div class="cd-navbar-top">
-  <nav class="navbar fixed-top navbar-expand-md navbar-dark cd-navbar-brand"
-       [ngClass]="{'isPwdDisplayed': isPwdDisplayed}">
+  <nav class="navbar fixed-top navbar-expand-md navbar-dark cd-navbar-brand">
     <button class="btn btn-link py-0"
             (click)="showMenuSidebar = !showMenuSidebar">
       <i class="fa fa-bars fa-2x"
@@ -36,7 +37,7 @@
 <div class="wrapper">
   <!-- Content -->
   <nav id="sidebar"
-       [ngClass]="{'active': !showMenuSidebar, 'isPwdDisplayed': isPwdDisplayed}">
+       [ngClass]="{'active': !showMenuSidebar}">
     <ngx-simplebar [options]="simplebar">
       <ul class="list-unstyled components cd-navbar-primary">
         <ng-container *ngTemplateOutlet="cd_menu"> </ng-container>
@@ -46,7 +47,7 @@
 
   <!-- Page Content -->
   <div id="content"
-       [ngClass]="{'active': !showMenuSidebar, 'isPwdDisplayed': isPwdDisplayed}">
+       [ngClass]="{'active': !showMenuSidebar}">
     <ng-content></ng-content>
   </div>
 </div>
index 7357b3ee49ff8bfeb41bce3f7030af83c92c21ac..b1d8e0e25d8de95111d547af1fce3db07e495c10 100644 (file)
@@ -1,7 +1,5 @@
 @import 'defaults';
 
-$pwd-exp-height: 37.6px;
-
 /* ---------------------------------------------------
     NAVBAR STYLE
 --------------------------------------------------- */
@@ -141,7 +139,6 @@ $pwd-exp-height: 37.6px;
 --------------------------------------------------- */
 
 $sidebar-width: 200px;
-$navbar-height: 43px;
 
 .cd-navbar-primary .active > a,
 .cd-navbar-primary > .active > a:focus,
@@ -171,10 +168,6 @@ $navbar-height: 43px;
       margin-left: -$sidebar-width;
     }
 
-    &.isPwdDisplayed {
-      top: $navbar-height + $pwd-exp-height;
-    }
-
     ul {
       &.component {
         padding: 20px 0;
@@ -251,13 +244,29 @@ $navbar-height: 43px;
   top: $navbar-height;
   bottom: 0;
   right: 0;
-  overflow: auto;
 
   &.active {
     width: 100vw;
   }
+}
 
-  &.isPwdDisplayed {
+/* ---------------------------------------------------
+    isPwdDisplayed
+--------------------------------------------------- */
+:host.isPwdDisplayed {
+  .cd-navbar-top .cd-navbar-brand {
+    top: $pwd-exp-height;
+  }
+
+  #sidebar {
     top: $navbar-height + $pwd-exp-height;
   }
+
+  #content {
+    top: $navbar-height + $pwd-exp-height;
+  }
+
+  cd-notifications-sidebar {
+    top: $navbar-height + $pwd-exp-height + 10px;
+  }
 }
index d13c4b1d3f65cf78a660a4061dee81cf22bbe05c..b7cd7dc08c34283b43c7d33baa2a1b3aebbd1ae2 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, HostBinding, OnInit } from '@angular/core';
 
 import { PrometheusService } from '../../../shared/api/prometheus.service';
 import { Icons } from '../../../shared/enum/icons.enum';
@@ -16,6 +16,8 @@ import { SummaryService } from '../../../shared/services/summary.service';
   styleUrls: ['./navigation.component.scss']
 })
 export class NavigationComponent implements OnInit {
+  @HostBinding('class.isPwdDisplayed') isPwdDisplayed = false;
+
   permissions: Permissions;
   summaryData: any;
   icons = Icons;
@@ -27,7 +29,6 @@ export class NavigationComponent implements OnInit {
   isCollapsed = true;
   showMenuSidebar = true;
   displayedSubMenu = '';
-  isPwdDisplayed = false;
 
   simplebar = {
     autoHide: false
index 8d1d1ae644ea11953cc906f3611319849c0098ca..6c729a85858df81782857c21098092b216a9402c 100644 (file)
@@ -4,6 +4,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
+import { ClickOutsideModule } from 'ng-click-outside';
 import { ChartsModule } from 'ng2-charts';
 import { AlertModule } from 'ngx-bootstrap/alert';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
@@ -11,6 +12,7 @@ import { ModalModule } from 'ngx-bootstrap/modal';
 import { PopoverModule } from 'ngx-bootstrap/popover';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
 import { TooltipModule } from 'ngx-bootstrap/tooltip';
+import { SimplebarAngularModule } from 'simplebar-angular';
 
 import { DirectivesModule } from '../directives/directives.module';
 import { PipesModule } from '../pipes/pipes.module';
@@ -51,6 +53,8 @@ import { ViewCacheComponent } from './view-cache/view-cache.component';
     DirectivesModule,
     BsDropdownModule,
     NgBootstrapFormValidationModule,
+    ClickOutsideModule,
+    SimplebarAngularModule,
     RouterModule
   ],
   declarations: [
index 8397b8b2590b2822e28d239826cf83010b86ca91..16771427f3dcdc1742e4e51b5cbbfe3ca5fd968b 100644 (file)
   </div>
 </ng-template>
 
-<div class="card">
+<div class="card"
+     (clickOutside)="closeSidebar()"
+     [clickOutsideEnabled]="isSidebarOpened">
   <div class="card-header">
     <ng-container i18n>Tasks and Notifications</ng-container>
 
     </button>
   </div>
 
-  <div class="card-body">
-    <ng-container *ngTemplateOutlet="tasksTpl"></ng-container>
-    <ng-container *ngTemplateOutlet="notificationsTpl"></ng-container>
-    <ng-container *ngTemplateOutlet="emptyTpl"></ng-container>
-  </div>
+  <ngx-simplebar [options]="simplebar">
+    <div class="card-body">
+      <ng-container *ngTemplateOutlet="tasksTpl"></ng-container>
+      <ng-container *ngTemplateOutlet="notificationsTpl"></ng-container>
+      <ng-container *ngTemplateOutlet="emptyTpl"></ng-container>
+    </div>
+  </ngx-simplebar>
 </div>
index a22a6e4d6cb6356829480b1ffe6b8bfc32f7c1e1..8f5d577e8b4e61023ed0161c29aeb3a1777cb422 100644 (file)
@@ -1,44 +1,40 @@
 @import 'defaults';
 
-::ng-deep .ng-sidebar__content {
-  overflow: hidden !important;
-}
-
-::ng-deep .isPwdDisplayed .ng-sidebar {
-  top: 92px !important;
-}
-
-// sidebar
-::ng-deep .ng-sidebar {
-  &.ng-sidebar--opened {
-    margin-right: 20px;
-  }
+:host {
+  position: fixed;
+  top: $navbar-height + 10px;
+  bottom: 10px;
+  right: -350px;
 
   width: 350px;
   max-width: 90vw;
-  z-index: 9 !important;
 
-  top: 54px !important;
-  bottom: 10px !important;
+  z-index: 9;
 
-  .card {
-    height: 100%;
+  transition: all 0.6s;
+}
 
-    .card-body {
-      overflow: auto;
-    }
-  }
+:host.active {
+  right: 20px;
+}
 
-  .separator {
-    padding: 5px 12px;
-    color: $color-popover-seperator-text;
-    background-color: $color-popover-seperator-bg;
-    font-size: 12px;
-  }
+.card {
+  height: 100%;
+}
+
+ngx-simplebar {
+  height: calc(100% - 42.2px);
+}
+
+.separator {
+  padding: 5px 12px;
+  color: $color-popover-seperator-text;
+  background-color: $color-popover-seperator-bg;
+  font-size: 12px;
+}
 
-  .btn-link .fa-trash-o {
-    color: $color-black;
-  }
+.btn-link .fa-trash-o {
+  color: $color-black;
 }
 
 table {
index 229c136ad91275b0f4283f56a871685ff98ef0ab..a4b8aff265123b9325aa07adf4d2b3e20cdf6d32 100644 (file)
@@ -2,9 +2,11 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { ClickOutsideModule } from 'ng-click-outside';
 import { PopoverModule } from 'ngx-bootstrap/popover';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
 import { ToastrModule } from 'ngx-toastr';
+import { SimplebarAngularModule } from 'simplebar-angular';
 
 import { NoopAnimationsModule } from '@angular/platform-browser/animations';
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
@@ -34,7 +36,9 @@ describe('NotificationsSidebarComponent', () => {
       ProgressbarModule.forRoot(),
       RouterTestingModule,
       ToastrModule.forRoot(),
-      NoopAnimationsModule
+      NoopAnimationsModule,
+      SimplebarAngularModule,
+      ClickOutsideModule
     ],
     declarations: [NotificationsSidebarComponent],
     providers: [
@@ -101,6 +105,7 @@ describe('NotificationsSidebarComponent', () => {
 
       expectPrometheusServicesToBeCalledTimes(0);
     });
+
     it('should first refresh prometheus notifications and alerts during init', () => {
       fixture.detectChanges();
 
@@ -156,4 +161,41 @@ describe('NotificationsSidebarComponent', () => {
       expect(component.notifications[0].title).toBe('Sample title');
     }));
   });
+
+  describe('Sidebar', () => {
+    let notificationService: NotificationService;
+
+    beforeEach(() => {
+      notificationService = TestBed.get(NotificationService);
+      fixture.detectChanges();
+    });
+
+    it('should always close if sidebarSubject value is true', fakeAsync(() => {
+      // Closed before next value
+      expect(component.isSidebarOpened).toBeFalsy();
+      notificationService.sidebarSubject.next(true);
+      tick();
+      expect(component.isSidebarOpened).toBeFalsy();
+
+      // Opened before next value
+      component.isSidebarOpened = true;
+      expect(component.isSidebarOpened).toBeTruthy();
+      notificationService.sidebarSubject.next(true);
+      tick();
+      expect(component.isSidebarOpened).toBeFalsy();
+    }));
+
+    it('should toggle sidebar visibility if sidebarSubject value is false', () => {
+      // Closed before next value
+      expect(component.isSidebarOpened).toBeFalsy();
+      notificationService.sidebarSubject.next(false);
+      expect(component.isSidebarOpened).toBeTruthy();
+
+      // Opened before next value
+      component.isSidebarOpened = true;
+      expect(component.isSidebarOpened).toBeTruthy();
+      notificationService.sidebarSubject.next(false);
+      expect(component.isSidebarOpened).toBeFalsy();
+    });
+  });
 });
index 7bb1624b3219ac2b1d1ffd12e8dbc99a25a3e022..c395e4650a56fae998a2aef56e2c1a407a19f990 100644 (file)
@@ -2,6 +2,7 @@ import {
   ChangeDetectionStrategy,
   ChangeDetectorRef,
   Component,
+  HostBinding,
   NgZone,
   OnDestroy,
   OnInit
@@ -11,6 +12,7 @@ import { Mutex } from 'async-mutex';
 import * as _ from 'lodash';
 import * as moment from 'moment';
 import { LocalStorage } from 'ngx-store';
+import { Subscription } from 'rxjs';
 
 import { ExecutingTask } from '../../../shared/models/executing-task';
 import { SummaryService } from '../../../shared/services/summary.service';
@@ -30,17 +32,27 @@ import { PrometheusNotificationService } from '../../services/prometheus-notific
   changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class NotificationsSidebarComponent implements OnInit, OnDestroy {
+  @HostBinding('class.active') isSidebarOpened = false;
+
   notifications: CdNotification[];
   private interval: number;
+  private timeout: number;
 
   executingTasks: ExecutingTask[] = [];
 
+  private sidebarSubscription: Subscription;
+  private notificationDataSubscription: Subscription;
+
   icons = Icons;
 
   // Tasks
   @LocalStorage() last_task = '';
   mutex = new Mutex();
 
+  simplebar = {
+    autoHide: false
+  };
+
   constructor(
     public notificationService: NotificationService,
     private summaryService: SummaryService,
@@ -56,6 +68,13 @@ export class NotificationsSidebarComponent implements OnInit, OnDestroy {
 
   ngOnDestroy() {
     window.clearInterval(this.interval);
+    window.clearTimeout(this.timeout);
+    if (this.sidebarSubscription) {
+      this.sidebarSubscription.unsubscribe();
+    }
+    if (this.notificationDataSubscription) {
+      this.notificationDataSubscription.unsubscribe();
+    }
   }
 
   ngOnInit() {
@@ -71,9 +90,24 @@ export class NotificationsSidebarComponent implements OnInit, OnDestroy {
       });
     }
 
-    this.notificationService.data$.subscribe((notifications: CdNotification[]) => {
-      this.notifications = _.orderBy(notifications, ['timestamp'], ['desc']);
-      this.cdRef.detectChanges();
+    this.notificationDataSubscription = this.notificationService.data$.subscribe(
+      (notifications: CdNotification[]) => {
+        this.notifications = _.orderBy(notifications, ['timestamp'], ['desc']);
+        this.cdRef.detectChanges();
+      }
+    );
+
+    this.sidebarSubscription = this.notificationService.sidebarSubject.subscribe((forceClose) => {
+      if (forceClose) {
+        this.isSidebarOpened = false;
+      } else {
+        this.isSidebarOpened = !this.isSidebarOpened;
+      }
+
+      window.clearTimeout(this.timeout);
+      this.timeout = window.setTimeout(() => {
+        this.cdRef.detectChanges();
+      }, 0);
     });
 
     this.summaryService.subscribe((data: any) => {
@@ -127,7 +161,7 @@ export class NotificationsSidebarComponent implements OnInit, OnDestroy {
   }
 
   closeSidebar() {
-    this.notificationService.toggleSidebar(true);
+    this.isSidebarOpened = false;
   }
 
   trackByFn(index: number) {
index 7b323683474ef50cef808b4c8ee9bd900aef07f8..04540abc5c60f6f9db7fa71aff5b5fe0d450b448 100644 (file)
@@ -109,6 +109,9 @@ $color-nav-links-hover: $color-primary !default;
 $color-nav-active-link-bg: $color-primary !default;
 $color-nav-border-top-collapse: $color-white-gray !default;
 
+$navbar-height: 43px;
+$pwd-exp-height: 37.6px;
+
 /*Helper*/
 $color-helper-bg: $color-primary !default;