]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: Migrate Tabs from ngx-bootstrap to ng-bootstrap
authorTiago Melo <tmelo@suse.com>
Fri, 22 May 2020 15:41:58 +0000 (15:41 +0000)
committerTiago Melo <tmelo@suse.com>
Wed, 3 Jun 2020 14:22:48 +0000 (14:22 +0000)
Using ng-bootstrap for Tabs will allow us to easily implement some new features
like only loading 1 tab at a time (already implemented here) and
saving/restoring last opened tab.

Modified the table component to use a clone of the columns list.
Making changes directly to columns var was causing problem when the table was
loaded a second time.

Fixes: https://tracker.ceph.com/issues/45017
Signed-off-by: Tiago Melo <tmelo@suse.com>
102 files changed:
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.po.ts
src/pybind/mgr/dashboard/frontend/package-lock.json
src/pybind/mgr/dashboard/frontend/src/app/app.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-tabs/iscsi-tabs.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-tabs/iscsi-tabs.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-tabs/iscsi-tabs.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-details/iscsi-target-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-list/iscsi-target-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/daemon-list/daemon-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/image-list/image-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/image-list/image-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/mirroring.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/overview/overview.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/mirroring/pool-list/pool-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-details/rbd-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-namespace-list/rbd-namespace-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-performance/rbd-performance.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-tabs/rbd-tabs.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-tabs/rbd-tabs.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-tabs/rbd-tabs.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-details/configuration-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-details/host-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/hosts.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-details/mgr-module-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/mgr-modules/mgr-modules.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-details/osd-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/monitoring-list/monitoring-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/rules-list/rules-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-details/service-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-details/nfs-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-details/rgw-daemon-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/ceph-shared.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/shared/smart-list/smart-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-list/user-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-tabs/user-tabs.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-tabs/user-tabs.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-tabs/user-tabs.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts
src/pybind/mgr/dashboard/frontend/src/testing/unit-test-helper.ts

index 15bc30f1f3bb999d4e1fc4a2561756bd75afe204..7b610fb39d4133cc093ee10efb949cb9b994a42b 100644 (file)
@@ -42,8 +42,8 @@ describe('OSDs page', () => {
       });
 
       it('should show the correct text for the tab labels', () => {
-        cy.get('#tabset-osd-details > div > tab').then(($tabs) => {
-          const tabHeadings = $tabs.map((_i, e) => e.getAttribute('heading')).get();
+        cy.get('#tabset-osd-details > li > a').then(($tabs) => {
+          const tabHeadings = $tabs.map((_i, e) => e.textContent).get();
 
           expect(tabHeadings).to.eql([
             'Devices',
index 0ca60665db40d18a0904451d7eb0f45a421eeda5..eab4818743fd6c0f9ae3b71e89b67ae7c52b44e8 100644 (file)
@@ -5,12 +5,12 @@ export class DaemonsPageHelper extends PageHelper {
     index: { url: '#/rgw/daemon', id: 'cd-rgw-daemon-list' }
   };
 
-  getTableCell(tableIndex: number) {
+  getTableCell() {
     return cy
-      .get('.tab-container')
+      .get('.tab-content')
       .its(1)
       .find('cd-table')
-      .its(tableIndex)
+      .should('have.length', 1) // Only 1 table should be renderer
       .find('datatable-body-cell');
   }
 
@@ -20,23 +20,15 @@ export class DaemonsPageHelper extends PageHelper {
 
     // check details table is visible
     // check at least one field is present
-    this.getTableCell(0).should('visible').should('contain.text', 'ceph_version');
-    // check performance counters table is not currently visible
-    this.getTableCell(1).should('not.be.visible');
+    this.getTableCell().should('visible').should('contain.text', 'ceph_version');
 
     // click on performance counters tab and check table is loaded
     cy.contains('.nav-link', 'Performance Counters').click();
 
     // check at least one field is present
-    this.getTableCell(1).should('be.visible').should('contain.text', 'objecter.op_r');
-    // check details table is not currently visible
-    this.getTableCell(0).should('not.be.visible');
+    this.getTableCell().should('be.visible').should('contain.text', 'objecter.op_r');
 
     // click on performance details tab
     cy.contains('.nav-link', 'Performance Details').click();
-
-    // checks the other tabs' content isn't visible
-    this.getTableCell(0).should('not.be.visible');
-    this.getTableCell(1).should('not.be.visible');
   }
 }
index 57b383da505e6dcc12ada0891791f652249e0c2e..2f300863c30d4bc9f9d347579a127fd0a8ad7555 100644 (file)
       "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
       "dev": true
     },
+    "mem": {
+      "requires": {
+        "mimic-fn": "^1.0.0"
+      },
+      "version": "4.3.0"
+    },
     "memory-fs": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
     "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==",
-      "dev": true
+      "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
     },
     "mini-css-extract-plugin": {
       "version": "0.9.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=="
         }
       }
     },
index b50107a87069a092ca5d3952edea87035a588e01..350ba718f7ca7e3bd4a42c1cc305fddca1e53a6b 100644 (file)
@@ -14,7 +14,6 @@ import { I18n } from '@ngx-translate/i18n-polyfill';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 
 import { AppRoutingModule } from './app-routing.module';
@@ -47,7 +46,6 @@ export function jwtTokenGetter() {
     SharedModule,
     CephModule,
     BsDropdownModule.forRoot(),
-    TabsModule.forRoot(),
     JwtModule.forRoot({
       config: {
         tokenGetter: jwtTokenGetter
index 8a483b1395d631014a4f1ee779d26669325c397d..c568b0bcd1a60b487e8e06c7b870e40adc730ed8 100644 (file)
@@ -3,13 +3,13 @@ import { NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule, Routes } from '@angular/router';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { TreeModule } from 'angular-tree-component';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ModalModule } from 'ngx-bootstrap/modal';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { TooltipModule } from 'ngx-bootstrap/tooltip';
 
 import { ActionLabels, URLVerbs } from '../../shared/constants/app.constants';
@@ -60,7 +60,7 @@ import { RbdTrashRestoreModalComponent } from './rbd-trash-restore-modal/rbd-tra
     MirroringModule,
     FormsModule,
     ReactiveFormsModule,
-    TabsModule.forRoot(),
+    NgbNavModule,
     ProgressbarModule.forRoot(),
     BsDropdownModule.forRoot(),
     BsDatepickerModule.forRoot(),
index e1e43c291a5c6e897d160aeb8561fcc380f5a726..3d328cb6bf324a50ca51209ca9b7c4fc946bc212 100644 (file)
@@ -1,12 +1,14 @@
-<tabset>
-  <tab heading="Overview"
-       i18n-heading
-       [active]="url === '/block/iscsi/overview'"
-       (selectTab)="navigateTo('/block/iscsi/overview')">
-  </tab>
-  <tab heading="Targets"
-       i18n-heading
-       [active]="url === '/block/iscsi/targets'"
-       (selectTab)="navigateTo('/block/iscsi/targets')">
-  </tab>
-</tabset>
+<ul ngbNav
+    #nav="ngbNav"
+    [activeId]="router.url"
+    (navChange)="router.navigate([$event.nextId])"
+    class="nav-tabs">
+  <li ngbNavItem="/block/iscsi/overview">
+    <a ngbNavLink
+       i18n>Overview</a>
+  </li>
+  <li ngbNavItem="/block/iscsi/targets">
+    <a ngbNavLink
+       i18n>Targets</a>
+  </li>
+</ul>
index e51c9fad30678107f132fd790e3d3ff73e7de33a..456c930b0abab85506ac5f7403069be7d52afd7a 100644 (file)
@@ -1,7 +1,7 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../shared/shared.module';
@@ -12,7 +12,7 @@ describe('IscsiTabsComponent', () => {
   let fixture: ComponentFixture<IscsiTabsComponent>;
 
   configureTestBed({
-    imports: [SharedModule, TabsModule.forRoot(), RouterTestingModule],
+    imports: [SharedModule, RouterTestingModule, NgbNavModule],
     declarations: [IscsiTabsComponent]
   });
 
index 06835d3c8278c2448a7ae968a9737d13f1919714..6f729bc83843c75b94d23f8c252cb59c1a94d17e 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
 
 import { Router } from '@angular/router';
 
@@ -7,16 +7,6 @@ import { Router } from '@angular/router';
   templateUrl: './iscsi-tabs.component.html',
   styleUrls: ['./iscsi-tabs.component.scss']
 })
-export class IscsiTabsComponent implements OnInit {
-  url: string;
-
-  constructor(private router: Router) {}
-
-  ngOnInit() {
-    this.url = this.router.url;
-  }
-
-  navigateTo(url: string) {
-    this.router.navigate([url]);
-  }
+export class IscsiTabsComponent {
+  constructor(public router: Router) {}
 }
index 42d9ccefbd3d8f7a95253315b4cf348ac55b0067..a4b883fcb6c95a6c92447177cc192b1c7e2a00df 100644 (file)
@@ -280,7 +280,7 @@ export class IscsiTargetDetailsComponent implements OnChanges, OnInit {
       const tempData = this.metadata[node.data.cdId] || {};
 
       if (node.data.cdId === 'root') {
-        this.columns[2].isHidden = false;
+        this.detailTable?.toggleColumn({ target: { name: 'default', checked: true } });
         this.data = _.map(this.settings.target_default_controls, (value, key) => {
           value = this.format(value);
           return {
@@ -300,7 +300,7 @@ export class IscsiTargetDetailsComponent implements OnChanges, OnInit {
           });
         }
       } else if (node.data.cdId.toString().startsWith('disk_')) {
-        this.columns[2].isHidden = false;
+        this.detailTable?.toggleColumn({ target: { name: 'default', checked: true } });
         this.data = _.map(this.settings.disk_default_controls[tempData.backstore], (value, key) => {
           value = this.format(value);
           return {
@@ -326,7 +326,7 @@ export class IscsiTargetDetailsComponent implements OnChanges, OnInit {
           }
         });
       } else {
-        this.columns[2].isHidden = true;
+        this.detailTable?.toggleColumn({ target: { name: 'default', checked: false } });
         this.data = _.map(tempData, (value, key) => {
           return {
             displayName: key,
@@ -339,9 +339,7 @@ export class IscsiTargetDetailsComponent implements OnChanges, OnInit {
       this.data = undefined;
     }
 
-    if (this.detailTable) {
-      this.detailTable.updateColumns();
-    }
+    this.detailTable?.updateColumns();
   }
 
   onUpdateData() {
index 1335bf2d9f8be457ca4575021a0e2546ad7b2be1..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,9 +0,0 @@
-::ng-deep tabset.tabset > ul {
-  border-bottom: 1px solid #ddd;
-  float: left;
-  display: block;
-  margin-right: 20px;
-  border-bottom: 0;
-  border-right: 1px solid #ddd;
-  padding-right: 15px;
-}
index 50d636febe5c69fc5969fe6f52a1d83eda12ac5a..f8ba0fe77f7da67d9dd80486903d45695018a8ca 100644 (file)
@@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { TreeModule } from 'angular-tree-component';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 import { BehaviorSubject, of } from 'rxjs';
 
@@ -40,9 +40,9 @@ describe('IscsiTargetListComponent', () => {
       HttpClientTestingModule,
       RouterTestingModule,
       SharedModule,
-      TabsModule.forRoot(),
       TreeModule,
-      ToastrModule.forRoot()
+      ToastrModule.forRoot(),
+      NgbNavModule
     ],
     declarations: [IscsiTargetListComponent, IscsiTabsComponent, IscsiTargetDetailsComponent],
     providers: [TaskListService, i18nProviders]
index fb427fd64313d2af37bf13585ea13a1a55a7815f..3bd0d8c899c3208e13686014c783e2fa2c5dcb23 100644 (file)
@@ -4,7 +4,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../../shared/shared.module';
@@ -21,7 +20,6 @@ describe('DaemonListComponent', () => {
       BrowserAnimationsModule,
       SharedModule,
       BsDropdownModule.forRoot(),
-      TabsModule.forRoot(),
       ProgressbarModule.forRoot(),
       HttpClientTestingModule
     ],
index b8278b486536cbd9b18d37f7d1442146bd0a6d4f..51f82e647afc101eec8895d32d98243d49d3baa7 100644 (file)
@@ -1,35 +1,45 @@
-<tabset #tabset>
-  <tab heading="Issues"
-       i18n-heading>
-    <cd-table *ngIf="tabset.tabs[0]?.active"
-              [data]="image_error.data"
-              columnMode="flex"
-              [columns]="image_error.columns"
-              [autoReload]="-1"
-              (fetchData)="refresh()">
-    </cd-table>
-  </tab>
-  <tab heading="Syncing"
-       i18n-heading>
-    <cd-table *ngIf="tabset.tabs[1]?.active"
-              [data]="image_syncing.data"
-              columnMode="flex"
-              [columns]="image_syncing.columns"
-              [autoReload]="-1"
-              (fetchData)="refresh()">
-    </cd-table>
-  </tab>
-  <tab heading="Ready"
-       i18n-heading>
-    <cd-table *ngIf="tabset.tabs[2]?.active"
-              [data]="image_ready.data"
-              columnMode="flex"
-              [columns]="image_ready.columns"
-              [autoReload]="-1"
-              (fetchData)="refresh()">
-    </cd-table>
-  </tab>
-</tabset>
+<ul ngbNav
+    #nav="ngbNav"
+    class="nav-tabs">
+  <li ngbNavItem>
+    <a ngbNavLink
+       i18n>Issues</a>
+    <ng-template ngbNavContent>
+      <cd-table [data]="image_error.data"
+                columnMode="flex"
+                [columns]="image_error.columns"
+                [autoReload]="-1"
+                (fetchData)="refresh()">
+      </cd-table>
+    </ng-template>
+  </li>
+  <li ngbNavItem>
+    <a ngbNavLink
+       i18n>Syncing</a>
+    <ng-template ngbNavContent>
+      <cd-table [data]="image_syncing.data"
+                columnMode="flex"
+                [columns]="image_syncing.columns"
+                [autoReload]="-1"
+                (fetchData)="refresh()">
+      </cd-table>
+    </ng-template>
+  </li>
+  <li ngbNavItem>
+    <a ngbNavLink
+       i18n>Ready</a>
+    <ng-template ngbNavContent>
+      <cd-table [data]="image_ready.data"
+                columnMode="flex"
+                [columns]="image_ready.columns"
+                [autoReload]="-1"
+                (fetchData)="refresh()">
+      </cd-table>
+    </ng-template>
+  </li>
+</ul>
+
+<div [ngbNavOutlet]="nav"></div>
 
 <ng-template #stateTmpl
              let-row="row"
index aedc7e2bf2d3251e9c3c0b28705b0ded5066b269..786c98949f5eb1f05b63f47eff6818eaf208fb14 100644 (file)
@@ -2,9 +2,9 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../../shared/shared.module';
@@ -21,7 +21,7 @@ describe('ImageListComponent', () => {
       BrowserAnimationsModule,
       SharedModule,
       BsDropdownModule.forRoot(),
-      TabsModule.forRoot(),
+      NgbNavModule,
       ProgressbarModule.forRoot(),
       HttpClientTestingModule
     ],
index a051cf99b0f4ff84f7f58226944728c3942fa056..b15b69a6c3331a8dc95ab7767f14dc3aa3ad16dc 100644 (file)
@@ -3,12 +3,12 @@ import { NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 import { AlertModule } from 'ngx-bootstrap/alert';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ModalModule } from 'ngx-bootstrap/modal';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { TooltipModule } from 'ngx-bootstrap/tooltip';
 
 import { SharedModule } from '../../../shared/shared.module';
@@ -35,8 +35,8 @@ import { PoolListComponent } from './pool-list/pool-list.component';
   ],
   imports: [
     CommonModule,
-    TabsModule.forRoot(),
     SharedModule,
+    NgbNavModule,
     RouterModule,
     FormsModule,
     ReactiveFormsModule,
index fc1cd20fc9ca6548931144ade363e113e52ea01b..d3e06ecb3e37fa56eaeda7560c5a82cbc1a65fbb 100644 (file)
@@ -3,9 +3,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
@@ -32,7 +32,7 @@ describe('OverviewComponent', () => {
       BrowserAnimationsModule,
       SharedModule,
       BsDropdownModule.forRoot(),
-      TabsModule.forRoot(),
+      NgbNavModule,
       ProgressbarModule.forRoot(),
       HttpClientTestingModule,
       RouterTestingModule,
index 06e21fc0d9f8c1d726c12167b8769d498db81ed0..a9be82b7633f018f7b476c4511a4d50a878d7d96 100644 (file)
@@ -5,7 +5,6 @@ import { RouterTestingModule } from '@angular/router/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
@@ -23,7 +22,6 @@ describe('PoolListComponent', () => {
       BrowserAnimationsModule,
       SharedModule,
       BsDropdownModule.forRoot(),
-      TabsModule.forRoot(),
       ProgressbarModule.forRoot(),
       HttpClientTestingModule,
       RouterTestingModule,
index fee853628f3dbad8acdb55d4e774c2383d6a9c4b..6189e97ebf2201c8eaa021b88e05327cda1e4aa3 100644 (file)
   <ng-container i18n>Only available for RBD images with <strong>fast-diff</strong> enabled</ng-container>
 </ng-template>
 
-<tabset *ngIf="selection">
-  <tab i18n-heading
-       heading="Details">
-    <table class="table table-striped table-bordered">
-      <tbody>
-        <tr>
-          <td i18n
-              class="bold w-25">Name</td>
-          <td class="w-75">{{ selection.name }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Pool</td>
-          <td>{{ selection.pool_name }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Data Pool</td>
-          <td>{{ selection.data_pool | empty }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Created</td>
-          <td>{{ selection.timestamp | cdDate }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Size</td>
-          <td>{{ selection.size | dimlessBinary }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Objects</td>
-          <td>{{ selection.num_objs | dimless }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Object size</td>
-          <td>{{ selection.obj_size | dimlessBinary }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Features</td>
-          <td>
-            <span *ngFor="let feature of selection.features_name">
-              <span class="badge badge-dark mr-2">{{ feature }}</span>
-            </span>
-          </td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Provisioned</td>
-          <td>
-            <span *ngIf="selection.features_name?.indexOf('fast-diff') === -1">
-              <span class="form-text text-muted"
-                    [tooltip]="usageNotAvailableTooltipTpl"
-                    placement="right"
-                    i18n>N/A</span>
-            </span>
-            <span *ngIf="selection.features_name?.indexOf('fast-diff') !== -1">
-              {{ selection.disk_usage | dimlessBinary }}
-            </span>
-          </td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Total provisioned</td>
-          <td>
-            <span *ngIf="selection.features_name?.indexOf('fast-diff') === -1">
-              <span class="form-text text-muted"
-                    [tooltip]="usageNotAvailableTooltipTpl"
-                    placement="right"
-                    i18n>N/A</span>
-            </span>
-            <span *ngIf="selection.features_name?.indexOf('fast-diff') !== -1">
-              {{ selection.total_disk_usage | dimlessBinary }}
-            </span>
-          </td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Striping unit</td>
-          <td>{{ selection.stripe_unit | dimlessBinary }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Striping count</td>
-          <td>{{ selection.stripe_count }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Parent</td>
-          <td>
-            <span *ngIf="selection.parent">{{ selection.parent.pool_name }}<span *ngIf="selection.parent.pool_namespace">/{{ selection.parent.pool_namespace }}</span>/{{ selection.parent.image_name }}@{{ selection.parent.snap_name }}</span>
-            <span *ngIf="!selection.parent">-</span>
-          </td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Block name prefix</td>
-          <td>{{ selection.block_name_prefix }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Order</td>
-          <td>{{ selection.order }}</td>
-        </tr>
-      </tbody>
-    </table>
-  </tab>
-  <tab i18n-heading
-       heading="Snapshots">
-    <cd-rbd-snapshot-list [snapshots]="selection.snapshots"
-                          [featuresName]="selection.features_name"
-                          [poolName]="selection.pool_name"
-                          [namespace]="selection.namespace"
-                          [rbdName]="selection.name"></cd-rbd-snapshot-list>
-  </tab>
-  <tab i18n-heading
-       heading="Configuration">
-    <cd-rbd-configuration-table [data]="selection['configuration']"></cd-rbd-configuration-table>
-  </tab>
-</tabset>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <table class="table table-striped table-bordered">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold w-25">Name</td>
+              <td class="w-75">{{ selection.name }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Pool</td>
+              <td>{{ selection.pool_name }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Data Pool</td>
+              <td>{{ selection.data_pool | empty }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Created</td>
+              <td>{{ selection.timestamp | cdDate }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Size</td>
+              <td>{{ selection.size | dimlessBinary }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Objects</td>
+              <td>{{ selection.num_objs | dimless }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Object size</td>
+              <td>{{ selection.obj_size | dimlessBinary }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Features</td>
+              <td>
+                <span *ngFor="let feature of selection.features_name">
+                  <span class="badge badge-dark mr-2">{{ feature }}</span>
+                </span>
+              </td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Provisioned</td>
+              <td>
+                <span *ngIf="selection.features_name?.indexOf('fast-diff') === -1">
+                  <span class="form-text text-muted"
+                        [tooltip]="usageNotAvailableTooltipTpl"
+                        placement="right"
+                        i18n>N/A</span>
+                </span>
+                <span *ngIf="selection.features_name?.indexOf('fast-diff') !== -1">
+                  {{ selection.disk_usage | dimlessBinary }}
+                </span>
+              </td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Total provisioned</td>
+              <td>
+                <span *ngIf="selection.features_name?.indexOf('fast-diff') === -1">
+                  <span class="form-text text-muted"
+                        [tooltip]="usageNotAvailableTooltipTpl"
+                        placement="right"
+                        i18n>N/A</span>
+                </span>
+                <span *ngIf="selection.features_name?.indexOf('fast-diff') !== -1">
+                  {{ selection.total_disk_usage | dimlessBinary }}
+                </span>
+              </td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Striping unit</td>
+              <td>{{ selection.stripe_unit | dimlessBinary }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Striping count</td>
+              <td>{{ selection.stripe_count }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Parent</td>
+              <td>
+                <span *ngIf="selection.parent">{{ selection.parent.pool_name }}<span
+                        *ngIf="selection.parent.pool_namespace">/{{ selection.parent.pool_namespace }}</span>/{{ selection.parent.image_name }}@{{ selection.parent.snap_name }}</span>
+                <span *ngIf="!selection.parent">-</span>
+              </td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Block name prefix</td>
+              <td>{{ selection.block_name_prefix }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Order</td>
+              <td>{{ selection.order }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Snapshots</a>
+      <ng-template ngbNavContent>
+        <cd-rbd-snapshot-list [snapshots]="selection.snapshots"
+                              [featuresName]="selection.features_name"
+                              [poolName]="selection.pool_name"
+                              [namespace]="selection.namespace"
+                              [rbdName]="selection.name"></cd-rbd-snapshot-list>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Configuration</a>
+      <ng-template ngbNavContent>
+        <cd-rbd-configuration-table [data]="selection['configuration']"></cd-rbd-configuration-table>
+      </ng-template>
+    </li>
+  </ul>
 
-<ng-template
-  #poolConfigurationSourceTpl
-  let-row="row"
-  let-value="value">
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
+
+<ng-template #poolConfigurationSourceTpl
+             let-row="row"
+             let-value="value">
   <ng-container *ngIf="+value; else global">
     <strong i18n
             i18n-tooltip
           tooltip="This is the global value. No value for this option has been set for this image.">Global</span>
   </ng-template>
 </ng-template>
-
index 44646595e812bdb9df4eff386d5a399b42596b66..c555cb10254392f3565c79bc38c8578703055c52 100644 (file)
@@ -1,7 +1,7 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { TooltipModule } from 'ngx-bootstrap/tooltip';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
@@ -16,7 +16,7 @@ describe('RbdDetailsComponent', () => {
 
   configureTestBed({
     declarations: [RbdDetailsComponent, RbdSnapshotListComponent, RbdConfigurationListComponent],
-    imports: [SharedModule, TabsModule.forRoot(), TooltipModule.forRoot(), RouterTestingModule]
+    imports: [SharedModule, TooltipModule.forRoot(), RouterTestingModule, NgbNavModule]
   });
 
   beforeEach(() => {
index 0916391e06633a5e64e67d99c6f60821e7de954b..46f95d1449caaa78f3e646a80b3600a81a53303e 100644 (file)
@@ -1,5 +1,7 @@
 import { Component, Input, TemplateRef, ViewChild } from '@angular/core';
 
+import { NgbNav } from '@ng-bootstrap/ng-bootstrap';
+
 import { RbdFormModel } from '../rbd-form/rbd-form.model';
 
 @Component({
@@ -12,8 +14,12 @@ export class RbdDetailsComponent {
   selection: RbdFormModel;
   @Input()
   images: any;
+
   @ViewChild('poolConfigurationSourceTpl', { static: true })
   poolConfigurationSourceTpl: TemplateRef<any>;
 
+  @ViewChild(NgbNav, { static: true })
+  nav: NgbNav;
+
   constructor() {}
 }
index f9ad4cf7cb74b797f63258510bd7114717de232d..4a63ed0f9731fdfc85c5eba01cc7cb75e7a4a2c9 100644 (file)
@@ -3,10 +3,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { AlertModule } from 'ngx-bootstrap/alert';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ModalModule } from 'ngx-bootstrap/modal';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { TooltipModule } from 'ngx-bootstrap/tooltip';
 import { ToastrModule } from 'ngx-toastr';
 import { BehaviorSubject, of } from 'rxjs';
@@ -46,7 +46,7 @@ describe('RbdListComponent', () => {
       BrowserAnimationsModule,
       SharedModule,
       BsDropdownModule.forRoot(),
-      TabsModule.forRoot(),
+      NgbNavModule,
       ModalModule.forRoot(),
       TooltipModule.forRoot(),
       ToastrModule.forRoot(),
index 7abb507ca4d20996ac962cacc5784d0182c9bea0..294a598e251f236973e6042a90918ff6f3b6d9c9 100644 (file)
@@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { ToastrModule } from 'ngx-toastr';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
@@ -24,7 +24,7 @@ describe('RbdNamespaceListComponent', () => {
       HttpClientTestingModule,
       RouterTestingModule,
       ToastrModule.forRoot(),
-      TabsModule.forRoot()
+      NgbNavModule
     ],
     providers: [TaskListService, i18nProviders]
   });
index 1bb1e9b0cbf4f620fc045d6c817a87aed6f77fb4..bec05b924ef5c862dfb2c5e0adb0145096e4f8c5 100644 (file)
@@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../shared/shared.module';
@@ -14,7 +14,7 @@ describe('RbdPerformanceComponent', () => {
   let fixture: ComponentFixture<RbdPerformanceComponent>;
 
   configureTestBed({
-    imports: [HttpClientTestingModule, RouterTestingModule, SharedModule, TabsModule.forRoot()],
+    imports: [HttpClientTestingModule, RouterTestingModule, SharedModule, NgbNavModule],
     declarations: [RbdPerformanceComponent, RbdTabsComponent],
     providers: i18nProviders
   });
index c032d0d522b52ccde27e6c58b737472796db1ec3..9adf9c3b703cd17d604f0b058b9ca6c741ccdd8c 100644 (file)
@@ -3,9 +3,9 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { I18n } from '@ngx-translate/i18n-polyfill';
 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 import { Subject, throwError as observableThrowError } from 'rxjs';
 
@@ -57,7 +57,7 @@ describe('RbdSnapshotListComponent', () => {
       HttpClientTestingModule,
       PipesModule,
       RouterTestingModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       ToastrModule.forRoot()
     ],
     providers: [
index bc241a5bfde266f9295e231bd6aa0c2c89701b6f..657568c2230cc889434a3be8ddb4ebcbbf30aeae 100644 (file)
@@ -1,23 +1,23 @@
-<tabset>
-  <tab heading="Images"
-       i18n-heading
-       [active]="url === '/block/rbd'"
-       (selectTab)="navigateTo('/block/rbd')">
-  </tab>
-  <tab heading="Namespaces"
-       i18n-heading
-       [active]="url === '/block/rbd/namespaces'"
-       (selectTab)="navigateTo('/block/rbd/namespaces')">
-  </tab>
-  <tab heading="Trash"
-       i18n-heading
-       [active]="url === '/block/rbd/trash'"
-       (selectTab)="navigateTo('/block/rbd/trash')">
-  </tab>
-  <tab heading="Overall Performance"
-       i18n-heading
-       *ngIf="grafanaPermission.read"
-       [active]="url === '/block/rbd/performance'"
-       (selectTab)="navigateTo('/block/rbd/performance')">
-  </tab>
-</tabset>
+<ul ngbNav
+    #nav="ngbNav"
+    [activeId]="router.url"
+    (navChange)="router.navigate([$event.nextId])"
+    class="nav-tabs">
+  <li ngbNavItem="/block/rbd">
+    <a ngbNavLink
+       i18n>Images</a>
+  </li>
+  <li ngbNavItem="/block/rbd/namespaces">
+    <a ngbNavLink
+       i18n>Namespaces</a>
+  </li>
+  <li ngbNavItem="/block/rbd/trash">
+    <a ngbNavLink
+       i18n>Trash</a>
+  </li>
+  <li ngbNavItem="/block/rbd/performance"
+      *ngIf="grafanaPermission.read">
+    <a ngbNavLink
+       i18n>Overall Performance</a>
+  </li>
+</ul>
index 7d7b15d0fdd5bd650ae55ad92960f62a821f5863..52065ff5f0946b2d8bc1446637afe3f898f1f67c 100644 (file)
@@ -1,7 +1,7 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { RbdTabsComponent } from './rbd-tabs.component';
@@ -11,7 +11,7 @@ describe('RbdTabsComponent', () => {
   let fixture: ComponentFixture<RbdTabsComponent>;
 
   configureTestBed({
-    imports: [TabsModule.forRoot(), RouterTestingModule],
+    imports: [RouterTestingModule, NgbNavModule],
     declarations: [RbdTabsComponent]
   });
 
index 36923dffc78ffec41bb7509bde907c18cbbdd7bc..0f77d14731d0108432b70694ab5282e64b5475a5 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
 import { Router } from '@angular/router';
 
 import { Permission } from '../../../shared/models/permissions';
@@ -9,19 +9,11 @@ import { AuthStorageService } from '../../../shared/services/auth-storage.servic
   templateUrl: './rbd-tabs.component.html',
   styleUrls: ['./rbd-tabs.component.scss']
 })
-export class RbdTabsComponent implements OnInit {
+export class RbdTabsComponent {
   grafanaPermission: Permission;
   url: string;
 
-  constructor(private authStorageService: AuthStorageService, private router: Router) {
+  constructor(private authStorageService: AuthStorageService, public router: Router) {
     this.grafanaPermission = this.authStorageService.getPermissions().grafana;
   }
-
-  ngOnInit() {
-    this.url = this.router.url;
-  }
-
-  navigateTo(url: string) {
-    this.router.navigate([url]);
-  }
 }
index 557ab5ec76b17d46e18ff6d6e8b95ff4fc78fef9..3706a9ca50fdde49330ce65b28f1908bfb5a9d80 100644 (file)
@@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import * as moment from 'moment';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 import { of } from 'rxjs';
 
@@ -37,7 +37,7 @@ describe('RbdTrashListComponent', () => {
       HttpClientTestingModule,
       RouterTestingModule,
       SharedModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       ToastrModule.forRoot()
     ],
     providers: [TaskListService, i18nProviders]
index cd3800af5e161bfd72a074b47598dab8f3866e30..1dd6b4304751d63d52c8b94b7c23f05f848aba70 100644 (file)
@@ -1,34 +1,48 @@
-<tabset *ngIf="selection">
-  <tab i18n-heading
-       (selectTab)="softRefresh()"
-       heading="Details">
-    <cd-cephfs-detail [data]="details">
-    </cd-cephfs-detail>
-  </tab>
-  <tab (selectTab)="softRefresh()">
-    <ng-template tabHeading>
-      <ng-container i18n>Clients</ng-container>
-      <span class="badge badge-pill badge-tab ml-1">{{ clients.data.length }}</span>
-    </ng-template>
-    <cd-cephfs-clients [id]="id"
-                       [clients]="clients"
-                       (triggerApiUpdate)="refresh()">
-    </cd-cephfs-clients>
-  </tab>
-  <tab i18n-heading
-       (selectTab)="directoriesSelected = true"
-       heading="Directories">
-    <cd-cephfs-directories *ngIf="directoriesSelected"
-                           [id]="id">
-    </cd-cephfs-directories>
-  </tab>
-  <tab i18n-heading
-       *ngIf="grafanaPermission.read && grafanaId"
-       heading="Performance Details">
-    <cd-grafana [grafanaPath]="'mds-performance?var-mds_servers=mds.' + grafanaId"
-                uid="tbO9LAiZz"
-                grafanaStyle="one">
-    </cd-grafana>
-  </tab>
-</tabset>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      (navChange)="softRefresh()"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <cd-cephfs-detail [data]="details">
+        </cd-cephfs-detail>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink>
+        <ng-container i18n>Clients</ng-container>
+        <span class="badge badge-pill badge-tab ml-1">{{ clients.data.length }}</span>
+      </a>
+      <ng-template ngbNavContent>
+        <cd-cephfs-clients [id]="id"
+                           [clients]="clients"
+                           (triggerApiUpdate)="refresh()">
+        </cd-cephfs-clients>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Directories</a>
+      <ng-template ngbNavContent>
+        <cd-cephfs-directories *ngIf="directoriesSelected"
+                               [id]="id">
+        </cd-cephfs-directories>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Performance Details</a>
+      <ng-template ngbNavContent>
+        <cd-grafana [grafanaPath]="'mds-performance?var-mds_servers=mds.' + grafanaId"
+                    uid="tbO9LAiZz"
+                    grafanaStyle="one">
+        </cd-grafana>
+      </ng-template>
+    </li>
+  </ul>
 
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index 288863374673ca6ffcb94fe7d8592097d683418f..c026306cc6934953fe20e003a71900f5ba82e9d8 100644 (file)
@@ -2,9 +2,9 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { Component, Input } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { TreeModule } from 'angular-tree-component';
 import * as _ from 'lodash';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 import { of } from 'rxjs';
 
@@ -80,7 +80,7 @@ describe('CephfsTabsComponent', () => {
   configureTestBed({
     imports: [
       SharedModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       HttpClientTestingModule,
       TreeModule,
       ToastrModule.forRoot()
index 4601d5b093eafca3ffcbf2436529f57209c1faf0..24a1cd98340841ea17287afc6559b6a7caea9a68 100644 (file)
@@ -1,10 +1,10 @@
 import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { TreeModule } from 'angular-tree-component';
 import { ChartsModule } from 'ng2-charts';
 import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { AppRoutingModule } from '../../app-routing.module';
 import { SharedModule } from '../../shared/shared.module';
@@ -23,7 +23,7 @@ import { CephfsTabsComponent } from './cephfs-tabs/cephfs-tabs.component';
     ChartsModule,
     TreeModule.forRoot(),
     ProgressbarModule.forRoot(),
-    TabsModule.forRoot()
+    NgbNavModule
   ],
   declarations: [
     CephfsDetailComponent,
index 3bc76953b81c141ed0de4be9530055f93d233b7b..45a620aff1e39766f1369b2a3f2048f0ef706a76 100644 (file)
@@ -3,14 +3,13 @@ import { NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 
-import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgbNavModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
 import { TreeModule } from 'angular-tree-component';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 import { AlertModule } from 'ngx-bootstrap/alert';
 import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ModalModule } from 'ngx-bootstrap/modal';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { TimepickerModule } from 'ngx-bootstrap/timepicker';
 import { TooltipModule } from 'ngx-bootstrap/tooltip';
 
@@ -70,7 +69,7 @@ import { TelemetryComponent } from './telemetry/telemetry.component';
   imports: [
     CommonModule,
     PerformanceCounterModule,
-    TabsModule.forRoot(),
+    NgbNavModule,
     SharedModule,
     RouterModule,
     FormsModule,
index e690b4a91c10d705b5f7157c58ea418fc4453afa..93c375d70d61eda3942b5c66980a22b356c379f9 100755 (executable)
-<tabset *ngIf="selection">
-  <tab i18n-heading
-       heading="Details">
-    <table class="table table-striped table-bordered">
-      <tbody>
-        <tr>
-          <td i18n
-              class="bold w-25">Name</td>
-          <td class="w-75">{{ selection.name }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Description</td>
-          <td>{{ selection.desc }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Long description</td>
-          <td>{{ selection.long_desc }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Current values</td>
-          <td>
-            <span *ngFor="let conf of selection.value; last as isLast">
-              {{ conf.section }}: {{ conf.value }}{{ !isLast ? "," : "" }}<br />
-            </span>
-          </td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Default</td>
-          <td>{{ selection.default }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Daemon default</td>
-          <td>{{ selection.daemon_default }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Type</td>
-          <td>{{ selection.type }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Min</td>
-          <td>{{ selection.min }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Max</td>
-          <td>{{ selection.max }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Flags</td>
-          <td>
-            <span *ngFor="let flag of selection.flags">
-              <span title="{{ flags[flag] }}">
-                <span class="badge badge-dark mr-2">{{ flag | uppercase }}</span>
-              </span>
-            </span>
-          </td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Services</td>
-          <td>
-            <span *ngFor="let service of selection.services">
-              <span class="badge badge-dark mr-2">{{ service }}</span>
-            </span>
-          </td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Source</td>
-          <td>{{ selection.source }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Level</td>
-          <td>{{ selection.level }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Can be updated at runtime (editable)</td>
-          <td>{{ selection.can_update_at_runtime | booleanText }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Tags</td>
-          <td>{{ selection.tags }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">Enum values</td>
-          <td>{{ selection.enum_values }}</td>
-        </tr>
-        <tr>
-          <td i18n
-              class="bold">See also</td>
-          <td>{{ selection.see_also }}</td>
-        </tr>
-      </tbody>
-    </table>
-  </tab>
-</tabset>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <table class="table table-striped table-bordered">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold w-25">Name</td>
+              <td class="w-75">{{ selection.name }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Description</td>
+              <td>{{ selection.desc }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Long description</td>
+              <td>{{ selection.long_desc }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Current values</td>
+              <td>
+                <span *ngFor="let conf of selection.value; last as isLast">
+                  {{ conf.section }}: {{ conf.value }}{{ !isLast ? "," : "" }}<br />
+                </span>
+              </td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Default</td>
+              <td>{{ selection.default }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Daemon default</td>
+              <td>{{ selection.daemon_default }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Type</td>
+              <td>{{ selection.type }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Min</td>
+              <td>{{ selection.min }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Max</td>
+              <td>{{ selection.max }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Flags</td>
+              <td>
+                <span *ngFor="let flag of selection.flags">
+                  <span title="{{ flags[flag] }}">
+                    <span class="badge badge-dark mr-2">{{ flag | uppercase }}</span>
+                  </span>
+                </span>
+              </td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Services</td>
+              <td>
+                <span *ngFor="let service of selection.services">
+                  <span class="badge badge-dark mr-2">{{ service }}</span>
+                </span>
+              </td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Source</td>
+              <td>{{ selection.source }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Level</td>
+              <td>{{ selection.level }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Can be updated at runtime (editable)</td>
+              <td>{{ selection.can_update_at_runtime | booleanText }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Tags</td>
+              <td>{{ selection.tags }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Enum values</td>
+              <td>{{ selection.enum_values }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">See also</td>
+              <td>{{ selection.see_also }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index 8c50cb8fb31a78338e159ba56979de5b89c27ae0..f94224736a9c979bc7811efdd12eefa4ec3d0962 100755 (executable)
@@ -1,6 +1,6 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
 import { DataTableModule } from '../../../../shared/datatable/datatable.module';
@@ -13,7 +13,7 @@ describe('ConfigurationDetailsComponent', () => {
 
   configureTestBed({
     declarations: [ConfigurationDetailsComponent],
-    imports: [DataTableModule, SharedModule, TabsModule.forRoot()],
+    imports: [DataTableModule, SharedModule, NgbNavModule],
     providers: [i18nProviders]
   });
 
index 817ce39c3bbea15f9e291f49511d4a3352ce434f..32cee6c2a3f6a0a5f859cc3cd3c77abb201601c4 100644 (file)
@@ -5,7 +5,7 @@ import { By } from '@angular/platform-browser';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../shared/shared.module';
@@ -22,7 +22,7 @@ describe('ConfigurationComponent', () => {
       BrowserAnimationsModule,
       SharedModule,
       FormsModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       HttpClientTestingModule,
       RouterTestingModule
     ],
index a6706ddfebf22cded7cb840ddb2ee7878ff2cb67..94331a6bf6d614558db18da913090a77c59e4cf1 100644 (file)
@@ -3,7 +3,6 @@ import { DebugElement } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { TreeModule } from 'angular-tree-component';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { of } from 'rxjs';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
@@ -16,7 +15,7 @@ describe('CrushmapComponent', () => {
   let fixture: ComponentFixture<CrushmapComponent>;
   let debugElement: DebugElement;
   configureTestBed({
-    imports: [HttpClientTestingModule, TreeModule.forRoot(), TabsModule.forRoot(), SharedModule],
+    imports: [HttpClientTestingModule, TreeModule.forRoot(), SharedModule],
     declarations: [CrushmapComponent],
     providers: [HealthService]
   });
index ef072c8af68085392881e6dc3183731ed2873069..6c71d59b93efc159de8e7cd712106cd25dbab7d7 100644 (file)
@@ -1,33 +1,54 @@
-<tabset *ngIf="selection">
-  <tab i18n-heading
-       heading="Devices">
-    <cd-device-list [hostname]="selection['hostname']"></cd-device-list>
-  </tab>
-  <tab i18n-heading
-       heading="Inventory"
-       *ngIf="permissions.hosts.read">
-    <cd-inventory [hostname]="selectedHostname"></cd-inventory>
-  </tab>
-  <tab i18n-heading
-       heading="Daemons"
-       *ngIf="permissions.hosts.read">
-    <cd-service-daemon-list [hostname]="selectedHostname">
-    </cd-service-daemon-list>
-  </tab>
-  <tab i18n-heading
-       heading="Performance Details"
-       *ngIf="permissions.grafana.read">
-    <cd-grafana [grafanaPath]="'host-details?var-ceph_hosts=' + selectedHostname"
-                uid="rtOg0AiWz"
-                grafanaStyle="three">
-    </cd-grafana>
-  </tab>
-  <tab heading="Device health"
-       i18n-heading>
-    <cd-smart-list *ngIf="selectedHostname; else noHostname"
-                   [hostname]="selectedHostname"></cd-smart-list>
-  </tab>
-</tabset>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Devices</a>
+      <ng-template ngbNavContent>
+        <cd-device-list [hostname]="selection['hostname']"></cd-device-list>
+      </ng-template>
+    </li>
+    <li ngbNavItem
+        *ngIf="permissions.hosts.read">
+      <a ngbNavLink
+         i18n>Inventory</a>
+      <ng-template ngbNavContent>
+        <cd-inventory [hostname]="selectedHostname"></cd-inventory>
+      </ng-template>
+    </li>
+    <li ngbNavItem
+        *ngIf="permissions.hosts.read">
+      <a ngbNavLink
+         i18n>Daemons</a>
+      <ng-template ngbNavContent>
+        <cd-service-daemon-list [hostname]="selectedHostname">
+        </cd-service-daemon-list>
+      </ng-template>
+    </li>
+    <li ngbNavItem
+        *ngIf="permissions.grafana.read">
+      <a ngbNavLink
+         i18n>Performance Details</a>
+      <ng-template ngbNavContent>
+        <cd-grafana [grafanaPath]="'host-details?var-ceph_hosts=' + selectedHostname"
+                    uid="rtOg0AiWz"
+                    grafanaStyle="three">
+        </cd-grafana>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Device health</a>
+      <ng-template ngbNavContent>
+        <cd-smart-list *ngIf="selectedHostname; else noHostname"
+                       [hostname]="selectedHostname"></cd-smart-list>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
 
 <ng-template #noHostname>
   <cd-alert-panel type="error"
index 865386bc6cda56dc34b135261d30edda79200ce0..60f82860549fda2bfe9272fbc7793b86b1da3b9b 100644 (file)
@@ -5,10 +5,13 @@ import { RouterTestingModule } from '@angular/router/testing';
 
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 
-import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
+import {
+  configureTestBed,
+  i18nProviders,
+  TabHelper
+} from '../../../../../testing/unit-test-helper';
 import { CoreModule } from '../../../../core/core.module';
 import { Permissions } from '../../../../shared/models/permissions';
 import { SharedModule } from '../../../../shared/shared.module';
@@ -24,7 +27,6 @@ describe('HostDetailsComponent', () => {
     imports: [
       BrowserAnimationsModule,
       HttpClientTestingModule,
-      TabsModule.forRoot(),
       BsDropdownModule.forRoot(),
       NgBootstrapFormValidationModule.forRoot(),
       RouterTestingModule,
@@ -59,17 +61,17 @@ describe('HostDetailsComponent', () => {
     });
 
     it('should recognize a tabset child', () => {
-      const tabsetChild = component.tabsetChild;
+      const tabsetChild = TabHelper.getNgbNav(fixture);
       expect(tabsetChild).toBeDefined();
     });
 
     it('should show tabs', () => {
-      expect(component.tabsetChild.tabs.map((t) => t.heading)).toEqual([
+      expect(TabHelper.getTextContents(fixture)).toEqual([
         'Devices',
-        'Device health',
         'Inventory',
         'Daemons',
-        'Performance Details'
+        'Performance Details',
+        'Device health'
       ]);
     });
   });
index d8c50ff3dfd01f9f5f42a2d4e773aa182c049bcb..15ef44b2a2cbbd75570d649b8364aabba8dd360e 100644 (file)
@@ -1,6 +1,4 @@
-import { Component, Input, ViewChild } from '@angular/core';
-
-import { TabsetComponent } from 'ngx-bootstrap/tabs';
+import { Component, Input } from '@angular/core';
 
 import { Permissions } from '../../../../shared/models/permissions';
 
@@ -16,12 +14,7 @@ export class HostDetailsComponent {
   @Input()
   selection: any;
 
-  @ViewChild(TabsetComponent)
-  tabsetChild: TabsetComponent;
-
   get selectedHostname(): string {
     return this.selection !== undefined ? this.selection['hostname'] : null;
   }
-
-  constructor() {}
 }
index fa67d8c59deca2ec9db40aa9645802ada150598b..5d94dd5ab2e4010d6e28296f7ed139a8a02aea05 100644 (file)
@@ -1,48 +1,59 @@
-<tabset>
-  <tab i18n-heading
-       heading="Hosts List">
-    <cd-table [data]="hosts"
-              [columns]="columns"
-              columnMode="flex"
-              (fetchData)="getHosts($event)"
-              selectionType="single"
-              [hasDetails]="true"
-              (setExpandedRow)="setExpandedRow($event)"
-              (updateSelection)="updateSelection($event)">
-      <div class="table-actions btn-toolbar">
-        <cd-table-actions [permission]="permissions.hosts"
-                          [selection]="selection"
-                          class="btn-group"
-                          id="host-actions"
-                          [tableActions]="tableActions">
-        </cd-table-actions>
-      </div>
-      <ng-template #servicesTpl
-                   let-value="value">
-        <span *ngFor="let service of value; last as isLast">
-          <a class="service-link"
-             [routerLink]="[service.cdLink]"
-             [queryParams]="cdParams"
-             *ngIf="service.canRead">{{ service.type }}.{{ service.id }}
-          </a>
-          <span *ngIf="!service.canRead">
-            {{ service.type }}.{{ service.id }}
-          </span>
-          {{ !isLast ? ", " : "" }}
-        </span>
-      </ng-template>
-      <cd-host-details cdTableDetail
-                       [permissions]="permissions"
-                       [selection]="expandedRow">
-      </cd-host-details>
-    </cd-table>
-  </tab>
-  <tab i18n-heading
-       *ngIf="permissions.grafana.read"
-       heading="Overall Performance">
-    <cd-grafana [grafanaPath]="'host-overview?'"
-                uid="y0KGL0iZz"
-                grafanaStyle="two">
-    </cd-grafana>
-  </tab>
-</tabset>
+<ul ngbNav
+    #nav="ngbNav"
+    class="nav-tabs">
+  <li ngbNavItem>
+    <a ngbNavLink
+       i18n>Hosts List</a>
+    <ng-template ngbNavContent>
+      <cd-table [data]="hosts"
+                [columns]="columns"
+                columnMode="flex"
+                (fetchData)="getHosts($event)"
+                selectionType="single"
+                [hasDetails]="true"
+                (setExpandedRow)="setExpandedRow($event)"
+                (updateSelection)="updateSelection($event)">
+        <div class="table-actions btn-toolbar">
+          <cd-table-actions [permission]="permissions.hosts"
+                            [selection]="selection"
+                            class="btn-group"
+                            id="host-actions"
+                            [tableActions]="tableActions">
+          </cd-table-actions>
+        </div>
+        <cd-host-details cdTableDetail
+                         [permissions]="permissions"
+                         [selection]="expandedRow">
+        </cd-host-details>
+      </cd-table>
+    </ng-template>
+  </li>
+  <li ngbNavItem
+      *ngIf="permissions.grafana.read">
+    <a ngbNavLink
+       i18n>Overall Performance</a>
+    <ng-template ngbNavContent>
+      <cd-grafana [grafanaPath]="'host-overview?'"
+                  uid="y0KGL0iZz"
+                  grafanaStyle="two">
+      </cd-grafana>
+    </ng-template>
+  </li>
+</ul>
+
+<div [ngbNavOutlet]="nav"></div>
+
+<ng-template #servicesTpl
+             let-value="value">
+  <span *ngFor="let service of value; last as isLast">
+    <a class="service-link"
+       [routerLink]="[service.cdLink]"
+       [queryParams]="cdParams"
+       *ngIf="service.canRead">{{ service.type }}.{{ service.id }}
+    </a>
+    <span *ngIf="!service.canRead">
+      {{ service.type }}.{{ service.id }}
+    </span>
+    {{ !isLast ? ", " : "" }}
+  </span>
+</ng-template>
index 1844f70ab03f3571a6f1c7781479876cb2d6af82..6fe6984dbfef4fafaed5f9fa437170f9d97c01bf 100644 (file)
@@ -4,7 +4,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 import { of } from 'rxjs';
 
@@ -35,7 +34,6 @@ describe('HostsComponent', () => {
       CephSharedModule,
       SharedModule,
       HttpClientTestingModule,
-      TabsModule.forRoot(),
       BsDropdownModule.forRoot(),
       RouterTestingModule,
       ToastrModule.forRoot(),
index 00be424f3783415905375e0e5a1c470f2df2400e..a5900e50201ea19b15451f79546080b59b6e66d1 100644 (file)
@@ -1,41 +1,50 @@
 <div *ngIf="contentData">
   <ng-container *ngTemplateOutlet="logFiltersTpl"></ng-container>
 
-  <tabset>
-    <tab i18n-heading
-         heading="Cluster Logs">
-      <div class="card bg-light mb-3"
-           *ngIf="clog">
-        <div class="card-body">
-          <p *ngFor="let line of clog">
-            <span class="timestamp">{{ line.stamp | cdDate }}</span>
-            <span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
-            <span class="message">{{ line.message }}</span>
-          </p>
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Cluster Logs</a>
+      <ng-template ngbNavContent>
+        <div class="card bg-light mb-3"
+             *ngIf="clog">
+          <div class="card-body">
+            <p *ngFor="let line of clog">
+              <span class="timestamp">{{ line.stamp | cdDate }}</span>
+              <span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
+              <span class="message">{{ line.message }}</span>
+            </p>
 
-          <span *ngIf="contentData.clog.length === 0"
-                i18n>No entries found</span>
+            <span *ngIf="contentData.clog.length === 0"
+                  i18n>No entries found</span>
+          </div>
         </div>
-      </div>
-    </tab>
-
-    <tab i18n-heading
-         heading="Audit Logs">
-      <div class="card bg-light mb-3"
-           *ngIf="audit_log">
-        <div class="card-body">
-          <p *ngFor="let line of audit_log">
-            <span class="timestamp">{{ line.stamp | cdDate }}</span>
-            <span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
-            <span class="message">{{ line.message }}</span>
-          </p>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Audit Logs</a>
+      <ng-template ngbNavContent>
+        <div class="card bg-light mb-3"
+             *ngIf="audit_log">
+          <div class="card-body">
+            <p *ngFor="let line of audit_log">
+              <span class="timestamp">{{ line.stamp | cdDate }}</span>
+              <span class="priority {{ line.priority | logPriority }}">{{ line.priority }}</span>
+              <span class="message">{{ line.message }}</span>
+            </p>
 
-          <span *ngIf="contentData.audit_log.length === 0"
-                i18n>No entries found</span>
+            <span *ngIf="contentData.audit_log.length === 0"
+                  i18n>No entries found</span>
+          </div>
         </div>
-      </div>
-    </tab>
-  </tabset>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
 </div>
 
 <ng-template #logFiltersTpl>
index e4f69fc920dae200d4aea260212a0824f1d534a0..31cbc790b50599843c12214712ba48c6768baf3d 100644 (file)
@@ -2,8 +2,8 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { FormsModule } from '@angular/forms';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { TimepickerModule } from 'ngx-bootstrap/timepicker';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
@@ -17,7 +17,7 @@ describe('LogsComponent', () => {
   configureTestBed({
     imports: [
       HttpClientTestingModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       SharedModule,
       BsDatepickerModule.forRoot(),
       TimepickerModule.forRoot(),
index 6ed9b65da3996c4a6ee26390181a1409abb8ea03..861575d0a14a9e13cdf96ed7c96aba8d3b19de3d 100644 (file)
@@ -1,7 +1,16 @@
-<tabset *ngIf="selection">
-  <tab i18n-heading
-       heading="Details">
-    <cd-table-key-value [data]="module_config">
-    </cd-table-key-value>
-  </tab>
-</tabset>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <cd-table-key-value [data]="module_config">
+        </cd-table-key-value>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index 1e0575e71a6929d3e789adc783f2860387ab166f..d28861eeedf9c5af76046220402bcd24481b85da 100644 (file)
@@ -1,7 +1,7 @@
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../../shared/shared.module';
@@ -13,7 +13,7 @@ describe('MgrModuleDetailsComponent', () => {
 
   configureTestBed({
     declarations: [MgrModuleDetailsComponent],
-    imports: [HttpClientTestingModule, SharedModule, TabsModule.forRoot()],
+    imports: [HttpClientTestingModule, SharedModule, NgbNavModule],
     providers: [i18nProviders]
   });
 
index 1072a9e449e81871d4bf115d3e3eaab31c585805..de3f0acbe192421f2284dd434fe3728a5a0cf4ad 100644 (file)
@@ -3,7 +3,7 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { ToastrModule } from 'ngx-toastr';
 import { of as observableOf, throwError as observableThrowError } from 'rxjs';
 
@@ -33,7 +33,7 @@ describe('MgrModuleListComponent', () => {
       RouterTestingModule,
       SharedModule,
       HttpClientTestingModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       ToastrModule.forRoot()
     ],
     providers: [MgrModuleService, NotificationService, i18nProviders]
index 9dd94c3605579601a51d78a0f3e7978dfbc37bec..e78136e4ceb3aa486b424fd6defe571e7c6cb568 100644 (file)
@@ -2,8 +2,8 @@ import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { AppRoutingModule } from '../../../app-routing.module';
 import { SharedModule } from '../../../shared/shared.module';
@@ -17,7 +17,7 @@ import { MgrModuleListComponent } from './mgr-module-list/mgr-module-list.compon
     CommonModule,
     ReactiveFormsModule,
     SharedModule,
-    TabsModule.forRoot(),
+    NgbNavModule,
     NgBootstrapFormValidationModule
   ],
   declarations: [MgrModuleListComponent, MgrModuleFormComponent, MgrModuleDetailsComponent]
index 3430d00b48afc6d5c165c6739b5bc0401e4d39a1..569062d66ffceb1f8fe5516e85d8cf162cbb2e15 100644 (file)
@@ -1,71 +1,91 @@
-<tabset *ngIf="selection"
-        id="tabset-osd-details">
-  <tab heading="Devices"
-       i18n-heading>
-    <cd-device-list *ngIf="osd.loaded && osd.id !== null"
-                    [osdId]="osd.id"></cd-device-list>
-  </tab>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      id="tabset-osd-details"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Devices</a>
+      <ng-template ngbNavContent>
+        <cd-device-list *ngIf="osd.loaded && osd.id !== null"
+                        [osdId]="osd.id"></cd-device-list>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Attributes (OSD map)</a>
+      <ng-template ngbNavContent>
+        <cd-table-key-value *ngIf="osd.loaded"
+                            [data]="osd.details.osd_map">
+        </cd-table-key-value>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Metadata</a>
+      <ng-template ngbNavContent>
+        <cd-table-key-value *ngIf="osd.loaded && osd.details.osd_metadata; else noMetaData"
+                            (fetchData)="refresh()"
+                            [data]="osd.details.osd_metadata">
+        </cd-table-key-value>
+        <ng-template #noMetaData>
+          <cd-alert-panel type="warning"
+                          i18n>Metadata not available</cd-alert-panel>
+        </ng-template>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Device health</a>
+      <ng-template ngbNavContent>
+        <cd-smart-list [osdId]="osd.id"></cd-smart-list>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Performance counter</a>
+      <ng-template ngbNavContent>
+        <cd-table-performance-counter *ngIf="osd.loaded"
+                                      serviceType="osd"
+                                      [serviceId]="osd.id">
+        </cd-table-performance-counter>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Histogram</a>
+      <ng-template ngbNavContent>
+        <cd-alert-panel *ngIf="osd.loaded && osd.histogram_failed"
+                        type="warning"
+                        i18n>Histogram not available: {{ osd.histogram_failed }}</cd-alert-panel>
 
-  <tab heading="Attributes (OSD map)"
-       i18n-heading>
-    <cd-table-key-value *ngIf="osd.loaded"
-                        [data]="osd.details.osd_map">
-    </cd-table-key-value>
-  </tab>
+        <div class="row"
+             *ngIf="osd.loaded && osd.details.histogram">
+          <div class="col-md-6">
+            <h4 i18n>Writes</h4>
+            <cd-osd-performance-histogram [histogram]="osd.details.histogram.osd.op_w_latency_in_bytes_histogram">
+            </cd-osd-performance-histogram>
+          </div>
+          <div class="col-md-6">
+            <h4 i18n>Reads</h4>
+            <cd-osd-performance-histogram [histogram]="osd.details.histogram.osd.op_r_latency_out_bytes_histogram">
+            </cd-osd-performance-histogram>
+          </div>
+        </div>
+      </ng-template>
+    </li>
+    <li ngbNavItem
+        *ngIf="grafanaPermission.read">
+      <a ngbNavLink
+         i18n>Performance Details</a>
+      <ng-template ngbNavContent>
+        <cd-grafana [grafanaPath]="'osd-device-details?var-osd=osd.' + osd['id']"
+                    uid="CrAHE0iZz"
+                    grafanaStyle="GrafanaStyles.two">
+        </cd-grafana>
+      </ng-template>
+    </li>
+  </ul>
 
-  <tab heading="Metadata"
-       i18n-heading>
-    <cd-table-key-value *ngIf="osd.loaded && osd.details.osd_metadata; else noMetaData"
-                        (fetchData)="refresh()"
-                        [data]="osd.details.osd_metadata">
-    </cd-table-key-value>
-    <ng-template #noMetaData>
-      <cd-alert-panel type="warning"
-                      i18n>Metadata not available</cd-alert-panel>
-    </ng-template>
-  </tab>
-
-  <tab heading="Device health"
-       i18n-heading>
-    <cd-smart-list [osdId]="osd.id"></cd-smart-list>
-  </tab>
-
-  <tab heading="Performance counter"
-       i18n-heading>
-    <cd-table-performance-counter *ngIf="osd.loaded"
-                                  serviceType="osd"
-                                  [serviceId]="osd.id">
-    </cd-table-performance-counter>
-  </tab>
-
-  <tab heading="Histogram"
-       i18n-heading>
-    <cd-alert-panel *ngIf="osd.loaded && osd.histogram_failed"
-                    type="warning"
-                    i18n>Histogram not available: {{ osd.histogram_failed }}</cd-alert-panel>
-
-    <div class="row"
-         *ngIf="osd.loaded && osd.details.histogram">
-      <div class="col-md-6">
-        <h4 i18n>Writes</h4>
-        <cd-osd-performance-histogram [histogram]="osd.details.histogram.osd.op_w_latency_in_bytes_histogram">
-        </cd-osd-performance-histogram>
-      </div>
-      <div class="col-md-6">
-        <h4 i18n>Reads</h4>
-        <cd-osd-performance-histogram [histogram]="osd.details.histogram.osd.op_r_latency_out_bytes_histogram">
-        </cd-osd-performance-histogram>
-      </div>
-    </div>
-  </tab>
-
-  <tab i18n-heading
-       *ngIf="grafanaPermission.read"
-       heading="Performance Details">
-    <cd-grafana [grafanaPath]="'osd-device-details?var-osd=osd.' + osd['id']"
-                uid="CrAHE0iZz"
-                grafanaStyle="GrafanaStyles.two">
-    </cd-grafana>
-  </tab>
-
-</tabset>
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index 0a9bc9d49a0e6dc53e47dce0ca57db967e0a5443..0ed14a1aea95076d2ef548e3f3874e6002b20603 100644 (file)
@@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { DebugElement } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { of } from 'rxjs';
 
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
@@ -22,7 +22,7 @@ describe('OsdDetailsComponent', () => {
   let getDetailsSpy: jasmine.Spy;
 
   configureTestBed({
-    imports: [HttpClientTestingModule, TabsModule.forRoot(), SharedModule],
+    imports: [HttpClientTestingModule, NgbNavModule, SharedModule],
     declarations: [
       OsdDetailsComponent,
       DeviceListComponent,
index 196a07e2ffb1d4247a44d9a632b5c40430dc6e5a..d1a5fb653dd0eaf6b828323875b383d3cd85a482 100644 (file)
@@ -1,60 +1,63 @@
-<tabset>
-  <tab i18n-heading
-       heading="OSDs List">
+<ul ngbNav
+    #nav="ngbNav"
+    class="nav-tabs">
+  <li ngbNavItem>
+    <a ngbNavLink
+       i18n>OSDs List</a>
+    <ng-template ngbNavContent>
+      <cd-table [autoReload]="false"
+                [data]="osds"
+                (fetchData)="getOsdList()"
+                [columns]="columns"
+                selectionType="multiClick"
+                [hasDetails]="true"
+                (setExpandedRow)="setExpandedRow($event)"
+                (updateSelection)="updateSelection($event)"
+                [updateSelectionOnRefresh]="'never'">
 
-    <cd-table [autoReload]="false"
-              [data]="osds"
-              (fetchData)="getOsdList()"
-              [columns]="columns"
-              selectionType="multiClick"
-              [hasDetails]="true"
-              (setExpandedRow)="setExpandedRow($event)"
-              (updateSelection)="updateSelection($event)"
-              [updateSelectionOnRefresh]="'never'">
+        <div class="table-actions btn-toolbar">
+          <cd-table-actions [permission]="permissions.osd"
+                            [selection]="selection"
+                            class="btn-group"
+                            id="osd-actions"
+                            [tableActions]="tableActions">
+          </cd-table-actions>
+          <cd-table-actions [permission]="{read: true}"
+                            [selection]="selection"
+                            dropDownOnly="Cluster-wide configuration"
+                            btnColor="light"
+                            class="btn-group"
+                            id="cluster-wide-actions"
+                            [tableActions]="clusterWideActions">
+          </cd-table-actions>
+        </div>
 
-      <div class="table-actions btn-toolbar">
-        <cd-table-actions [permission]="permissions.osd"
-                          [selection]="selection"
-                          class="btn-group"
-                          id="osd-actions"
-                          [tableActions]="tableActions">
-        </cd-table-actions>
-        <cd-table-actions [permission]="{read: true}"
-                          [selection]="selection"
-                          dropDownOnly="Cluster-wide configuration"
-                          btnColor="light"
-                          class="btn-group"
-                          id="cluster-wide-actions"
-                          [tableActions]="clusterWideActions">
-        </cd-table-actions>
-      </div>
-
-      <cd-osd-details cdTableDetail
-                      [selection]="expandedRow">
-      </cd-osd-details>
-    </cd-table>
+        <cd-osd-details cdTableDetail
+                        [selection]="expandedRow">
+        </cd-osd-details>
+      </cd-table>
+    </ng-template>
+  </li>
 
-    <ng-template #osdUsageTpl
-                 let-row="row">
-      <cd-usage-bar [totalBytes]="row.stats.stat_bytes"
-                    [usedBytes]="row.stats.stat_bytes_used">
-      </cd-usage-bar>
+  <li ngbNavItem
+      *ngIf="permissions.grafana.read">
+    <a ngbNavLink
+       i18n>Overall Performance</a>
+    <ng-template ngbNavContent>
+      <cd-grafana [grafanaPath]="'osd-overview?'"
+                  uid="lo02I1Aiz"
+                  grafanaStyle="three">
+      </cd-grafana>
     </ng-template>
-  </tab>
-  <tab i18n-heading
-       *ngIf="permissions.grafana.read"
-       heading="Overall Performance">
-    <cd-grafana [grafanaPath]="'osd-overview?'"
-                uid="lo02I1Aiz"
-                grafanaStyle="three">
-    </cd-grafana>
-  </tab>
-</tabset>
+  </li>
+</ul>
+
+<div [ngbNavOutlet]="nav"></div>
 
 <ng-template #markOsdConfirmationTpl
              let-markActionDescription="markActionDescription">
   <ng-container i18n><strong>OSD(s) {{  getSelectedOsdIds() | join }}</strong> will be marked
-<strong>{{ markActionDescription }}</strong> if you proceed.</ng-container>
+  <strong>{{ markActionDescription }}</strong> if you proceed.</ng-container>
 </ng-template>
 
 <ng-template #criticalConfirmationTpl
   <div *ngIf="!safeToPerform"
        class="danger">
     <cd-alert-panel type="warning"
-                    i18n>The {selection.hasSingleSelection, select, 1 {OSD is} 0 {OSDs are}} not safe to be {{ actionDescription }}! {{ message }}</cd-alert-panel>
+                    i18n>The {selection.hasSingleSelection, select, 1 {OSD is} 0 {OSDs are}} not safe to be
+      {{ actionDescription }}! {{ message }}</cd-alert-panel>
   </div>
   <ng-container i18n><strong>OSD {{ getSelectedOsdIds() | join }}</strong> will be
-<strong>{{ actionDescription }}</strong> if you proceed.</ng-container>
+  <strong>{{ actionDescription }}</strong> if you proceed.</ng-container>
+</ng-template>
+
+<ng-template #osdUsageTpl
+             let-row="row">
+  <cd-usage-bar [totalBytes]="row.stats.stat_bytes"
+                [usedBytes]="row.stats.stat_bytes_used">
+  </cd-usage-bar>
 </ng-template>
index 22ac804fa8294b7f08fa7dcb9451aeda124c995d..43ad9f5938acece3551b296739e8cdc75bc42546 100644 (file)
@@ -7,7 +7,6 @@ import { RouterTestingModule } from '@angular/router/testing';
 
 import * as _ from 'lodash';
 import { BsModalService } from 'ngx-bootstrap/modal';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 import { EMPTY, of } from 'rxjs';
 
@@ -93,7 +92,6 @@ describe('OsdListComponent', () => {
       BrowserAnimationsModule,
       HttpClientTestingModule,
       PerformanceCounterModule,
-      TabsModule.forRoot(),
       ToastrModule.forRoot(),
       CephModule,
       ReactiveFormsModule,
index ba736415afc9abee3a4058c7b6377aa290db510c..c551d599bb20d25721633820d4678cdbd799a614 100644 (file)
@@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { ToastrModule } from 'ngx-toastr';
 
 import {
@@ -27,7 +27,7 @@ describe('ActiveAlertListComponent', () => {
     imports: [
       BrowserAnimationsModule,
       HttpClientTestingModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       RouterTestingModule,
       ToastrModule.forRoot(),
       SharedModule,
index f54c1fe515f554ead4338402f8f7e35dc26cab61..d63d70dd42a8c8bf19241f66635fb0021855369f 100644 (file)
@@ -1,38 +1,48 @@
-<tabset #tabs>
-  <tab id="active-alerts"
-       heading="Active Alerts"
-       (selectTab)="setFragment($event)"
-       i18n-heading>
-    <cd-active-alert-list *ngIf="isAlertmanagerConfigured"></cd-active-alert-list>
-    <cd-alert-panel type="info"
-                    i18n
-                    *ngIf="!isAlertmanagerConfigured">To see all active Prometheus alerts, please
-      provide the URL to the API of Prometheus' Alertmanager as described in the
-  <a href="{{docsUrl}}"
-     target="_blank">documentation</a>.</cd-alert-panel>
-  </tab>
-  <tab id="all-alerts"
-       heading="All Alerts"
-       (selectTab)="setFragment($event)"
-       i18n-heading>
-    <cd-rules-list *ngIf="isPrometheusConfigured"
-                   [data]="prometheusAlertService.rules"></cd-rules-list>
-    <cd-alert-panel type="info"
-                    *ngIf="!isPrometheusConfigured">To see all configured Prometheus alerts, please provide the URL to
-      the API of Prometheus as described in the
-  <a href="{{docsUrl}}"
-     target="_blank">documentation</a>.</cd-alert-panel>
-  </tab>
-  <tab id="silences"
-       heading="Silences"
-       (selectTab)="setFragment($event)"
-       i18n-heading>
-    <cd-silences-list *ngIf="isAlertmanagerConfigured"></cd-silences-list>
-    <cd-alert-panel *ngIf="!isAlertmanagerConfigured"
-                    type="info"
-                    i18n>To enable Silences, please provide the URL to the API of the Prometheus' Alertmanager as
-      described in the
-  <a href="{{docsUrl}}"
-     target="_blank">documentation</a>.</cd-alert-panel>
-  </tab>
-</tabset>
+<ul ngbNav
+    #nav="ngbNav"
+    [activeId]="route.snapshot.fragment"
+    (navChange)="setFragment($event.nextId)"
+    class="nav-tabs">
+  <li ngbNavItem="active-alerts">
+    <a ngbNavLink
+       i18n>Active Alerts</a>
+    <ng-template ngbNavContent>
+      <cd-active-alert-list *ngIf="isAlertmanagerConfigured"></cd-active-alert-list>
+      <cd-alert-panel type="info"
+                      i18n
+                      *ngIf="!isAlertmanagerConfigured">To see all active Prometheus alerts, please
+        provide the URL to the API of Prometheus' Alertmanager as described in the
+      <a href="{{docsUrl}}"
+         target="_blank">documentation</a>.</cd-alert-panel>
+    </ng-template>
+  </li>
+  <li ngbNavItem="all-alerts">
+    <a ngbNavLink
+       i18n>All Alerts</a>
+    <ng-template ngbNavContent>
+      <cd-rules-list *ngIf="isPrometheusConfigured"
+                     [data]="prometheusAlertService.rules"></cd-rules-list>
+      <cd-alert-panel type="info"
+                      *ngIf="!isPrometheusConfigured">To see all configured Prometheus alerts,
+        please provide the URL to
+        the API of Prometheus as described in the
+      <a href="{{docsUrl}}"
+         target="_blank">documentation</a>.</cd-alert-panel>
+    </ng-template>
+  </li>
+  <li ngbNavItem="silences">
+    <a ngbNavLink
+       i18n>Silences</a>
+    <ng-template ngbNavContent>
+      <cd-silences-list *ngIf="isAlertmanagerConfigured"></cd-silences-list>
+      <cd-alert-panel *ngIf="!isAlertmanagerConfigured"
+                      type="info"
+                      i18n>To enable Silences, please provide the URL to the API of the Prometheus' Alertmanager as
+        described in the
+      <a href="{{docsUrl}}"
+         target="_blank">documentation</a>.</cd-alert-panel>
+    </ng-template>
+  </li>
+</ul>
+
+<div [ngbNavOutlet]="nav"></div>
index e08e235fe751fcf6f6ebebad99add9f672aeaf97..03325f530e07ae906865bac8370426908b933be6 100644 (file)
@@ -1,8 +1,6 @@
-import { Component, OnInit, ViewChild } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
 
-import { TabDirective, TabsetComponent } from 'ngx-bootstrap/tabs';
-
 import { PrometheusService } from '../../../../shared/api/prometheus.service';
 import { CephReleaseNamePipe } from '../../../../shared/pipes/ceph-release-name.pipe';
 import { PrometheusAlertService } from '../../../../shared/services/prometheus-alert.service';
@@ -17,14 +15,11 @@ export class MonitoringListComponent implements OnInit {
   constructor(
     public prometheusAlertService: PrometheusAlertService,
     private prometheusService: PrometheusService,
-    private route: ActivatedRoute,
+    public route: ActivatedRoute,
     private router: Router,
     private summaryService: SummaryService,
     private cephReleaseNamePipe: CephReleaseNamePipe
   ) {}
-  @ViewChild('tabs', { static: true })
-  tabs: TabsetComponent;
-
   isPrometheusConfigured = false;
   isAlertmanagerConfigured = false;
 
@@ -42,21 +37,9 @@ export class MonitoringListComponent implements OnInit {
       const releaseName = this.cephReleaseNamePipe.transform(summary.version);
       this.docsUrl = `https://docs.ceph.com/docs/${releaseName}/mgr/dashboard/#enabling-prometheus-alerting`;
     });
-
-    // Activate tab according to given fragment
-    if (this.route.snapshot.fragment) {
-      const tab = this.tabs.tabs.find(
-        (t) => t.elementRef.nativeElement.id === this.route.snapshot.fragment
-      );
-      if (tab) {
-        tab.active = true;
-      }
-      // Ensure fragment is not removed, so page can always be reloaded with the same tab open.
-      this.router.navigate([], { fragment: this.route.snapshot.fragment });
-    }
   }
 
-  setFragment(element: TabDirective) {
-    this.router.navigate([], { fragment: element.id });
+  setFragment(element: string) {
+    this.router.navigate([], { fragment: element });
   }
 }
index c0050ac7dae7aec97a391f50c55a9c39f0eb2c43..8a23ed67ebfb99824d92272d5d0f36adbfde824d 100644 (file)
@@ -3,13 +3,22 @@
           [hasDetails]="true"
           (updateSelection)="setExpandedRow($event)"
           [selectionType]="'single'">
-  <tabset cdTableDetail
-          *ngIf="expandedRow">
-    <tab i18n-heading
-         heading="Details">
-      <cd-table-key-value [data]="expandedRow"
-                          [renderObjects]="true"
-                          [hideKeys]="hideKeys"></cd-table-key-value>
-    </tab>
-  </tabset>
+  <ng-container cdTableDetail
+                *ngIf="expandedRow">
+    <ul ngbNav
+        #nav="ngbNav"
+        class="nav-tabs">
+      <li ngbNavItem>
+        <a ngbNavLink
+           i18n>Details</a>
+        <ng-template ngbNavContent>
+          <cd-table-key-value [data]="expandedRow"
+                              [renderObjects]="true"
+                              [hideKeys]="hideKeys"></cd-table-key-value>
+        </ng-template>
+      </li>
+    </ul>
+
+    <div [ngbNavOutlet]="nav"></div>
+  </ng-container>
 </cd-table>
index 27581aae7dcf9ffcf69c266c76aea9b9087150f0..74073fcf0810cabc5e05ba4b7b7ae388e5d72b7d 100644 (file)
@@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
 import { PrometheusService } from '../../../../shared/api/prometheus.service';
@@ -16,7 +16,7 @@ describe('RulesListComponent', () => {
 
   configureTestBed({
     declarations: [RulesListComponent],
-    imports: [HttpClientTestingModule, SharedModule, TabsModule.forRoot(), BrowserAnimationsModule],
+    imports: [HttpClientTestingModule, SharedModule, NgbNavModule, BrowserAnimationsModule],
     providers: [PrometheusService, SettingsService, i18nProviders]
   });
 
index 6a60f83f6e442113d9e2bb15bd5d872dc8bbd868..9e7921b308f7a0d4da4eb0e8a75d65c67d116027 100644 (file)
@@ -5,7 +5,6 @@ import { RouterTestingModule } from '@angular/router/testing';
 
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { BsModalRef, BsModalService, ModalModule } from 'ngx-bootstrap/modal';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 import { of } from 'rxjs';
 
@@ -32,7 +31,6 @@ describe('SilenceListComponent', () => {
       BrowserAnimationsModule,
       SharedModule,
       BsDropdownModule.forRoot(),
-      TabsModule.forRoot(),
       ModalModule.forRoot(),
       ToastrModule.forRoot(),
       RouterTestingModule,
index 9849ccad22e76d531f6f9f39f1b7b4f30c4f4ae7..46d3affd0d0098c46e426bad19701f5b00126152 100644 (file)
@@ -1,7 +1,16 @@
-<tabset *ngIf="selection">
-  <tab i18n-heading
-       heading="Daemons">
-    <cd-service-daemon-list [serviceName]="selection['service_name']">
-    </cd-service-daemon-list>
-  </tab>
-</tabset>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Daemons</a>
+      <ng-template ngbNavContent>
+        <cd-service-daemon-list [serviceName]="selection['service_name']">
+        </cd-service-daemon-list>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index 97dfe0d03a8ac1cf7db91b694e816e6ea6d5302a..dc2ec0b66c295a4a54ed6a748c51abe1c25c4948 100644 (file)
@@ -2,9 +2,13 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
-import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
+import {
+  configureTestBed,
+  i18nProviders,
+  TabHelper
+} from '../../../../../testing/unit-test-helper';
 import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
 import { SummaryService } from '../../../../shared/services/summary.service';
 import { SharedModule } from '../../../../shared/shared.module';
@@ -16,7 +20,7 @@ describe('ServiceDetailsComponent', () => {
   let fixture: ComponentFixture<ServiceDetailsComponent>;
 
   configureTestBed({
-    imports: [HttpClientTestingModule, RouterTestingModule, TabsModule.forRoot(), SharedModule],
+    imports: [HttpClientTestingModule, RouterTestingModule, SharedModule, NgbNavModule],
     declarations: [ServiceDetailsComponent, ServiceDaemonListComponent],
     providers: [
       i18nProviders,
@@ -46,13 +50,14 @@ describe('ServiceDetailsComponent', () => {
       fixture.detectChanges();
     });
 
-    it('should recognize a tabset child', () => {
-      const tabsetChild = component.tabsetChild;
-      expect(tabsetChild).toBeDefined();
+    it('should have a NgbNav', () => {
+      const ngbNav = TabHelper.getNgbNav(fixture);
+      expect(ngbNav).toBeDefined();
     });
 
     it('should show tabs', () => {
-      expect(component.tabsetChild.tabs.map((t) => t.heading)).toEqual(['Daemons']);
+      const ngbNavItems = TabHelper.getTextContents(fixture);
+      expect(ngbNavItems).toEqual(['Daemons']);
     });
   });
 });
index 4f16127e25a6fef34493482b417d452855738886..09eb4d2f5990e7df7b78930699f4f9c582ff9331 100644 (file)
@@ -1,5 +1,4 @@
-import { Component, Input, OnInit, ViewChild } from '@angular/core';
-import { TabsetComponent } from 'ngx-bootstrap/tabs';
+import { Component, Input } from '@angular/core';
 
 import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
 import { Permissions } from '../../../../shared/models/permissions';
@@ -9,17 +8,10 @@ import { Permissions } from '../../../../shared/models/permissions';
   templateUrl: './service-details.component.html',
   styleUrls: ['./service-details.component.scss']
 })
-export class ServiceDetailsComponent implements OnInit {
-  @ViewChild(TabsetComponent)
-  tabsetChild: TabsetComponent;
-
+export class ServiceDetailsComponent {
   @Input()
   permissions: Permissions;
 
   @Input()
   selection: CdTableSelection;
-
-  constructor() {}
-
-  ngOnInit() {}
 }
index 49630da4ec5d6861dfc765f972911c5adeeb9206..60134defb3342da0507998e22b4193b02a41e238 100644 (file)
@@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
 import { RouterModule } from '@angular/router';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { ChartsModule } from 'ng2-charts';
 import { PopoverModule } from 'ngx-bootstrap/popover';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { SharedModule } from '../../shared/shared.module';
 import { CephSharedModule } from '../shared/ceph-shared.module';
@@ -22,7 +22,7 @@ import { OsdSummaryPipe } from './osd-summary.pipe';
   imports: [
     CephSharedModule,
     CommonModule,
-    TabsModule.forRoot(),
+    NgbNavModule,
     SharedModule,
     ChartsModule,
     RouterModule,
index c59717c524fdfdd320e62490d831a02ad4012691..2d03ea3e6e9bd79c36bcee0ddf87fb4d33ae330d 100644 (file)
@@ -1,13 +1,28 @@
 <div>
   <cd-refresh-selector></cd-refresh-selector>
-  <tabset *ngIf="hasGrafana">
-    <tab i18n-heading
-         heading="Health">
-      <cd-health></cd-health>
-    </tab>
-    <tab i18n-heading
-         heading="Statistics">
-    </tab>
-  </tabset>
+
+  <ng-container *ngIf="hasGrafana">
+    <ul ngbNav
+        #nav="ngbNav"
+        class="nav-tabs">
+      <li ngbNavItem>
+        <a ngbNavLink
+           i18n>Health</a>
+        <ng-template ngbNavContent>
+          <cd-health></cd-health>
+        </ng-template>
+      </li>
+      <li ngbNavItem>
+        <a ngbNavLink
+           i18n>Statistics</a>
+        <ng-template ngbNavContent>
+        </ng-template>
+      </li>
+
+    </ul>
+
+    <div [ngbNavOutlet]="nav"></div>
+  </ng-container>
+
   <cd-health *ngIf="!hasGrafana"></cd-health>
 </div>
index 079ae3e1cfbe92c365aed91eeb4aa852fd50fce6..468719ea95717a249356bd985b023e9c73d14509 100644 (file)
@@ -1,6 +1,8 @@
 import { NO_ERRORS_SCHEMA } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
+
 import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { DashboardComponent } from './dashboard.component';
 
@@ -9,6 +11,7 @@ describe('DashboardComponent', () => {
   let fixture: ComponentFixture<DashboardComponent>;
 
   configureTestBed({
+    imports: [NgbNavModule],
     declarations: [DashboardComponent],
     schemas: [NO_ERRORS_SCHEMA]
   });
index 5bb97e5eb221d04ffb0cf4e73055b483b3f44ba6..4fc2aa2c9cd78f40a6260303da0a2e415fcb299f 100644 (file)
@@ -1,19 +1,31 @@
-<tabset *ngIf="selection">
-  <tab heading="Details"
-       i18n-heading>
-    <cd-table-key-value [data]="data">
-    </cd-table-key-value>
-  </tab>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <cd-table-key-value [data]="data">
+        </cd-table-key-value>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Clients ({{ clients.length }})</a>
+      <ng-template ngbNavContent>
 
-  <tab heading="Clients ({{ clients.length }})"
-       i18n-heading>
-    <cd-table #table
-              [data]="clients"
-              columnMode="flex"
-              [columns]="clientsColumns"
-              identifier="addresses"
-              forceIdentifier="true"
-              selectionType="">
-    </cd-table>
-  </tab>
-</tabset>
+        <cd-table #table
+                  [data]="clients"
+                  columnMode="flex"
+                  [columns]="clientsColumns"
+                  identifier="addresses"
+                  forceIdentifier="true"
+                  selectionType="">
+        </cd-table>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index f1f3b49b683842f166e924dd54844d501262523c..9b8f7da73e043180fb63ea5535c5132966c413f8 100644 (file)
@@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { By } from '@angular/platform-browser';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import * as _ from 'lodash';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../shared/shared.module';
@@ -18,7 +18,7 @@ describe('NfsDetailsComponent', () => {
 
   configureTestBed({
     declarations: [NfsDetailsComponent],
-    imports: [BrowserAnimationsModule, SharedModule, TabsModule.forRoot(), HttpClientTestingModule],
+    imports: [BrowserAnimationsModule, SharedModule, HttpClientTestingModule, NgbNavModule],
     providers: i18nProviders
   });
 
@@ -98,7 +98,7 @@ describe('NfsDetailsComponent', () => {
   });
 
   it('should have 1 client', () => {
-    expect(elem('li.nav-item:nth-of-type(2) span').nativeElement.textContent).toBe('Clients (1)');
+    expect(elem('ul.nav-tabs li:nth-of-type(2) a').nativeElement.textContent).toBe('Clients (1)');
     expect(component.clients).toEqual([
       {
         access_type: 'RW',
index b4811722a85ee3f8c4fbbadc4b14bc8457fd547d..e789046148b07c68cc16065270d4032b56976f3d 100644 (file)
@@ -3,7 +3,7 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { ToastrModule } from 'ngx-toastr';
 import { of } from 'rxjs';
 
@@ -41,8 +41,8 @@ describe('NfsListComponent', () => {
       HttpClientTestingModule,
       RouterTestingModule,
       SharedModule,
-      ToastrModule.forRoot(),
-      TabsModule.forRoot()
+      NgbNavModule,
+      ToastrModule.forRoot()
     ],
     providers: [TaskListService, i18nProviders]
   });
index ac66522bb447dd5d4bfefa5475e409dc0ef8d792..568e47280f17cc8eaf48d643f02833c0cc5a385b 100644 (file)
@@ -3,9 +3,8 @@ import { NgModule } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 
-import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgbNavModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { SharedModule } from '../../shared/shared.module';
 import { Nfs501Component } from './nfs-501/nfs-501.component';
@@ -19,7 +18,7 @@ import { NfsListComponent } from './nfs-list/nfs-list.component';
     ReactiveFormsModule,
     RouterModule,
     SharedModule,
-    TabsModule.forRoot(),
+    NgbNavModule,
     CommonModule,
     NgbTypeaheadModule,
     NgBootstrapFormValidationModule
index a1515e02b7840d6367e3f18e1ed11593b52c8783..c5a8a0b1a40d10beb99c3e2ec5f4c6cf997b7feb 100644 (file)
@@ -1,34 +1,50 @@
-<tabset #tabsetChild
-        cdTableDetail
-        *ngIf="selection">
-  <tab i18n-heading
-       heading="Details">
-    <cd-table-key-value [renderObjects]="true"
-                        [data]="filterNonPoolData(selection)"
-                        [autoReload]="false">
-    </cd-table-key-value>
-  </tab>
-  <tab i18n-heading
-       *ngIf="permissions.grafana.read"
-       heading="Performance Details">
-    <cd-grafana [grafanaPath]="'ceph-pool-detail?var-pool_name='
-                + selection['pool_name']"
-                uid="-xyV8KCiz"
-                grafanaStyle="one">
-    </cd-grafana>
-  </tab>
-  <tab *ngIf="selection.type === 'replicated'"
-       i18n-heading
-       heading="Configuration">
-    <cd-rbd-configuration-table [data]="selectedPoolConfiguration"></cd-rbd-configuration-table>
-  </tab>
-  <tab i18n-heading
-       *ngIf="selection['tiers']?.length > 0"
-       heading="Cache Tiers Details">
-    <cd-table [data]="cacheTiers"
-              [columns]="cacheTierColumns"
-              [autoSave]="false"
-              columnMode="flex">
-    </cd-table>
-  </tab>
-</tabset>
+<ng-container *ngIf="selection"
+              cdTableDetail>
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <cd-table-key-value [renderObjects]="true"
+                            [data]="filterNonPoolData(selection)"
+                            [autoReload]="false">
+        </cd-table-key-value>
+      </ng-template>
+    </li>
+    <li ngbNavItem
+        *ngIf="permissions.grafana.read">
+      <a ngbNavLink
+         i18n>Performance Details</a>
+      <ng-template ngbNavContent>
+        <cd-grafana [grafanaPath]="'ceph-pool-detail?var-pool_name='+ selection['pool_name']"
+                    uid="-xyV8KCiz"
+                    grafanaStyle="one">
+        </cd-grafana>
+      </ng-template>
+    </li>
+    <li ngbNavItem
+        *ngIf="selection.type === 'replicated'">
+      <a ngbNavLink
+         i18n>Configuration</a>
+      <ng-template ngbNavContent>
+        <cd-rbd-configuration-table [data]="selectedPoolConfiguration"></cd-rbd-configuration-table>
+      </ng-template>
+    </li>
+    <li ngbNavItem
+        *ngIf="selection['tiers']?.length > 0">
+      <a ngbNavLink
+         i18n>Cache Tiers Details</a>
+      <ng-template ngbNavContent>
+        <cd-table [data]="cacheTiers"
+                  [columns]="cacheTierColumns"
+                  [autoSave]="false"
+                  columnMode="flex">
+        </cd-table>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index f6330b2d034d6ca019ff81ebd9c0f59d132427e2..9bb3f2422dfd9915dcd76d9312cb4b6720845a19 100644 (file)
@@ -3,9 +3,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsetComponent, TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
-import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { configureTestBed, i18nProviders, TabHelper } from '../../../../testing/unit-test-helper';
 import { Permissions } from '../../../shared/models/permissions';
 import { SharedModule } from '../../../shared/shared.module';
 import { RbdConfigurationListComponent } from '../../block/rbd-configuration-list/rbd-configuration-list.component';
@@ -18,7 +18,7 @@ describe('PoolDetailsComponent', () => {
   configureTestBed({
     imports: [
       BrowserAnimationsModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       SharedModule,
       HttpClientTestingModule,
       RouterTestingModule
@@ -51,16 +51,17 @@ describe('PoolDetailsComponent', () => {
 
     it('should recognize a tabset child', () => {
       fixture.detectChanges();
-      const tabsetChild: TabsetComponent = poolDetailsComponent.tabsetChild;
-      expect(tabsetChild).toBeDefined();
+      const ngbNav = TabHelper.getNgbNav(fixture);
+      expect(ngbNav).toBeDefined();
     });
 
     it('should show "Cache Tiers Details" tab if selected pool has "tiers"', () => {
       fixture.detectChanges();
-      const tabs = poolDetailsComponent.tabsetChild.tabs;
-      expect(tabs.length).toBe(3);
-      expect(tabs[2].heading).toBe('Cache Tiers Details');
-      expect(tabs[0].active).toBeTruthy();
+      const tabsItem = TabHelper.getNgbNavItems(fixture);
+      const tabsText = TabHelper.getTextContents(fixture);
+      expect(tabsItem.length).toBe(3);
+      expect(tabsText[2]).toBe('Cache Tiers Details');
+      expect(tabsItem[0].active).toBeTruthy();
     });
 
     it('should not show "Cache Tiers Details" tab if selected pool has no "tiers"', () => {
@@ -68,17 +69,19 @@ describe('PoolDetailsComponent', () => {
         tiers: []
       };
       fixture.detectChanges();
-      const tabs = poolDetailsComponent.tabsetChild.tabs;
+      const tabs = TabHelper.getNgbNavItems(fixture);
       expect(tabs.length).toEqual(2);
       expect(tabs[0].active).toBeTruthy();
     });
 
     it('current active status of tabs should not change when selection is the same as previous selection', () => {
       fixture.detectChanges();
-      const tabs = poolDetailsComponent.tabsetChild.tabs;
+      const tabs = TabHelper.getNgbNavItems(fixture);
       expect(tabs[0].active).toBeTruthy();
 
-      tabs[1].active = true;
+      const ngbNav = TabHelper.getNgbNav(fixture);
+      ngbNav.select(tabs[1].id);
+
       fixture.detectChanges();
       expect(tabs[1].active).toBeTruthy();
     });
index 9deb29198d3c4c6af08ac582fa5b5685a13d5a47..bf517a84dcce36a65a994440140fe71c904e838e 100644 (file)
@@ -1,8 +1,7 @@
-import { Component, Input, OnChanges, ViewChild } from '@angular/core';
+import { Component, Input, OnChanges } from '@angular/core';
 
 import { I18n } from '@ngx-translate/i18n-polyfill';
 import * as _ from 'lodash';
-import { TabsetComponent } from 'ngx-bootstrap/tabs';
 
 import { PoolService } from '../../../shared/api/pool.service';
 import { CdTableColumn } from '../../../shared/models/cd-table-column';
@@ -23,8 +22,6 @@ export class PoolDetailsComponent implements OnChanges {
   permissions: Permissions;
   @Input()
   cacheTiers: any[];
-  @ViewChild(TabsetComponent)
-  tabsetChild: TabsetComponent;
   selectedPoolConfiguration: RbdConfigurationEntry[];
 
   constructor(private i18n: I18n, private poolService: PoolService) {
index 1de6332b70ce10d265db1db28ca7c2666d06bbcb..8e6fd0aa6b3059340f1e9427780f4e0e7d946503 100644 (file)
               <span class="form-text text-muted"
                     id="ecp-info-block"
                     *ngIf="data.erasureInfo && form.getValue('erasureProfile')">
-                <tabset #ecpInfoTabs>
-                  <tab i18n-heading
-                       heading="Profile"
-                       class="ecp-info">
-                    <cd-table-key-value [renderObjects]="true"
-                                        [hideKeys]="['name']"
-                                        [data]="form.getValue('erasureProfile')"
-                                        [autoReload]="false">
-                    </cd-table-key-value>
-                  </tab>
-                  <tab i18n-heading
-                       heading="Used by pools"
-                       class="used-by-pools">
-                    <ng-template #ecpIsNotUsed>
-                      <span i18n>Profile is not in use.</span>
+                <ul ngbNav
+                    #ecpInfoTabs="ngbNav"
+                    class="nav-tabs">
+                  <li ngbNavItem="ecp-info">
+                    <a ngbNavLink
+                       i18n>Profile</a>
+                    <ng-template ngbNavContent>
+                      <cd-table-key-value [renderObjects]="true"
+                                          [hideKeys]="['name']"
+                                          [data]="form.getValue('erasureProfile')"
+                                          [autoReload]="false">
+                      </cd-table-key-value>
                     </ng-template>
-                    <ul *ngIf="ecpUsage; else ecpIsNotUsed">
-                      <li *ngFor="let pool of ecpUsage">
-                        {{ pool }}
-                      </li>
-                    </ul>
-                  </tab>
-                </tabset>
+                  </li>
+                  <li ngbNavItem="used-by-pools">
+                    <a ngbNavLink
+                       i18n>Used by pools</a>
+                    <ng-template ngbNavContent>
+                      <ng-template #ecpIsNotUsed>
+                        <span i18n>Profile is not in use.</span>
+                      </ng-template>
+                      <ul *ngIf="ecpUsage; else ecpIsNotUsed">
+                        <li *ngFor="let pool of ecpUsage">
+                          {{ pool }}
+                        </li>
+                      </ul>
+                    </ng-template>
+                  </li>
+                </ul>
+
+                <div [ngbNavOutlet]="ecpInfoTabs"></div>
               </span>
             </div>
           </div>
                     </button>
                   </span>
                 </div>
-                <span class="form-text text-muted"
-                      id="crush-info-block"
-                      *ngIf="data.crushInfo && form.getValue('crushRule')">
-                  <tabset #crushInfoTabs>
-                    <tab i18n-heading
-                         heading="Crush rule"
-                         class="crush-rule-info">
-                      <cd-table-key-value [renderObjects]="false"
-                                          [hideKeys]="['steps', 'ruleset', 'type', 'rule_name']"
-                                          [data]="form.getValue('crushRule')"
-                                          [autoReload]="false">
-                      </cd-table-key-value>
-                    </tab>
-                    <tab i18n-heading
-                         heading="Crush steps"
-                         class="crush-rule-steps">
-                      <ol>
-                        <li *ngFor="let step of form.get('crushRule').value.steps">
-                          {{ describeCrushStep(step) }}
-                        </li>
-                      </ol>
-                    </tab>
-                    <tab i18n-heading
-                         heading="Used by pools"
-                         class="used-by-pools">
-                      <ng-template #ruleIsNotUsed>
-                        <span i18n>Rule is not in use.</span>
+
+                <div class="form-text text-muted"
+                     id="crush-info-block"
+                     *ngIf="data.crushInfo && form.getValue('crushRule')">
+                  <ul ngbNav
+                      #crushInfoTabs="ngbNav"
+                      class="nav-tabs">
+                    <li ngbNavItem="crush-rule-info">
+                      <a ngbNavLink
+                         i18n>Crush rule</a>
+                      <ng-template ngbNavContent>
+                        <cd-table-key-value [renderObjects]="false"
+                                            [hideKeys]="['steps', 'ruleset', 'type', 'rule_name']"
+                                            [data]="form.getValue('crushRule')"
+                                            [autoReload]="false">
+                        </cd-table-key-value>
                       </ng-template>
-                      <ul *ngIf="crushUsage; else ruleIsNotUsed">
-                        <li *ngFor="let pool of crushUsage">
-                          {{ pool }}
-                        </li>
-                      </ul>
-                    </tab>
-                  </tabset>
-                </span>
+                    </li>
+                    <li ngbNavItem="crush-rule-steps">
+                      <a ngbNavLink
+                         i18n>Crush steps</a>
+                      <ng-template ngbNavContent>
+                        <ol>
+                          <li *ngFor="let step of form.get('crushRule').value.steps">
+                            {{ describeCrushStep(step) }}
+                          </li>
+                        </ol>
+                      </ng-template>
+                    </li>
+                    <li ngbNavItem="used-by-pools">
+                      <a ngbNavLink
+                         i18n>Used by pools</a>
+                      <ng-template ngbNavContent>
+
+                        <ng-template #ruleIsNotUsed>
+                          <span i18n>Rule is not in use.</span>
+                        </ng-template>
+                        <ul *ngIf="crushUsage; else ruleIsNotUsed">
+                          <li *ngFor="let pool of crushUsage">
+                            {{ pool }}
+                          </li>
+                        </ul>
+                      </ng-template>
+                    </li>
+                  </ul>
+
+                  <div [ngbNavOutlet]="crushInfoTabs"></div>
+                </div>
                 <span class="invalid-feedback"
                       *ngIf="form.showError('crushRule', formDir, 'required')"
                       i18n>This field is required!</span>
index 9d91e08dd6a0d0f53796bd02461f2e8844084be4..50bfb2359e13c25b89b83a85a0c1a81ed7937b79 100644 (file)
@@ -2,17 +2,17 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { AbstractControl } from '@angular/forms';
 import { By } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { ActivatedRoute, Router, Routes } from '@angular/router';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import * as _ from 'lodash';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
-import { TabsetComponent, TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 import { of } from 'rxjs';
 
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import {
   configureTestBed,
   FixtureHelper,
@@ -137,7 +137,7 @@ describe('PoolFormComponent', () => {
       HttpClientTestingModule,
       RouterTestingModule.withRoutes(routes),
       ToastrModule.forRoot(),
-      TabsModule.forRoot(),
+      NgbNavModule,
       PoolModule,
       NgBootstrapFormValidationModule.forRoot()
     ],
@@ -888,7 +888,6 @@ describe('PoolFormComponent', () => {
       describe('rule in use', () => {
         beforeEach(() => {
           spyOn(global, 'setTimeout').and.callFake((fn: Function) => fn());
-          component.crushInfoTabs = { tabs: [{}, {}, {}] } as TabsetComponent; // Mock it
           deleteSpy.calls.reset();
           selectRuleByIndex(2);
           component.deleteCrushRule();
@@ -900,12 +899,6 @@ describe('PoolFormComponent', () => {
           expect(component.data.crushInfo).toBe(true);
         });
 
-        it('should open the third crush info tab', () => {
-          expect(component.crushInfoTabs).toEqual({
-            tabs: [{}, {}, { active: true }]
-          } as TabsetComponent);
-        });
-
         it('should hide the tooltip when clicking on delete again', () => {
           component.deleteCrushRule();
           expect(component.crushDeletionBtn.isOpen).toBe(false);
@@ -1051,7 +1044,6 @@ describe('PoolFormComponent', () => {
       describe('rule in use', () => {
         beforeEach(() => {
           spyOn(global, 'setTimeout').and.callFake((fn: Function) => fn());
-          component.ecpInfoTabs = { tabs: [{}, {}] } as TabsetComponent; // Mock it
           deleteSpy.calls.reset();
           setSelectedEcp('ecp1');
           component.deleteErasureCodeProfile();
@@ -1067,12 +1059,6 @@ describe('PoolFormComponent', () => {
           expect(component.data.erasureInfo).toBe(true);
         });
 
-        it('should open the third crush info tab', () => {
-          expect(component.ecpInfoTabs).toEqual({
-            tabs: [{}, { active: true }]
-          } as TabsetComponent);
-        });
-
         it('should hide the tooltip when clicking on delete again', () => {
           component.deleteErasureCodeProfile();
           expect(component.ecpDeletionBtn.isOpen).toBe(false);
index 8bdc4f06622b7f75c79dd312ab2a13c630b23723..b06f6d054a444a492ded52954c97e97521be847f 100644 (file)
@@ -2,10 +2,11 @@ import { Component, EventEmitter, OnInit, Type, ViewChild } from '@angular/core'
 import { FormControl, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 
+import { NgbNav } from '@ng-bootstrap/ng-bootstrap';
+
 import { I18n } from '@ngx-translate/i18n-polyfill';
 import * as _ from 'lodash';
 import { BsModalService } from 'ngx-bootstrap/modal';
-import { TabsetComponent } from 'ngx-bootstrap/tabs';
 import { TooltipDirective } from 'ngx-bootstrap/tooltip';
 import { Observable, Subscription } from 'rxjs';
 
@@ -54,9 +55,9 @@ interface FormFieldDescription {
   styleUrls: ['./pool-form.component.scss']
 })
 export class PoolFormComponent extends CdForm implements OnInit {
-  @ViewChild('crushInfoTabs') crushInfoTabs: TabsetComponent;
+  @ViewChild('crushInfoTabs') crushInfoTabs: NgbNav;
   @ViewChild('crushDeletionBtn') crushDeletionBtn: TooltipDirective;
-  @ViewChild('ecpInfoTabs') ecpInfoTabs: TabsetComponent;
+  @ViewChild('ecpInfoTabs') ecpInfoTabs: NgbNav;
   @ViewChild('ecpDeletionBtn') ecpDeletionBtn: TooltipDirective;
 
   permission: Permission;
@@ -632,7 +633,7 @@ export class PoolFormComponent extends CdForm implements OnInit {
       deletionBtn: this.ecpDeletionBtn,
       dataName: 'erasureInfo',
       getTabs: () => this.ecpInfoTabs,
-      tabPosition: 1,
+      tabPosition: 'used-by-pools',
       nameAttribute: 'name',
       itemDescription: this.i18n('erasure code profile'),
       reloadFn: () => this.reloadECPs(),
@@ -658,8 +659,8 @@ export class PoolFormComponent extends CdForm implements OnInit {
     usage: string[];
     deletionBtn: TooltipDirective;
     dataName: string;
-    getTabs: () => TabsetComponent;
-    tabPosition: number;
+    getTabs: () => NgbNav;
+    tabPosition: string;
     nameAttribute: string;
     itemDescription: string;
     reloadFn: Function;
@@ -675,7 +676,7 @@ export class PoolFormComponent extends CdForm implements OnInit {
       setTimeout(() => {
         const tabs = getTabs();
         if (tabs) {
-          tabs.tabs[tabPosition].active = true;
+          tabs.select(tabPosition);
         }
       }, 50);
       return;
@@ -722,7 +723,7 @@ export class PoolFormComponent extends CdForm implements OnInit {
       deletionBtn: this.crushDeletionBtn,
       dataName: 'crushInfo',
       getTabs: () => this.crushInfoTabs,
-      tabPosition: 2,
+      tabPosition: 'used-by-pools',
       nameAttribute: 'rule_name',
       itemDescription: this.i18n('crush rule'),
       reloadFn: () => this.reloadCrushRules(),
index 8d6205817d81cf50b1e4bab81db7da9b89e95005..02a98a5de7cd0fc3eff52af2340fc53b25018f39 100644 (file)
@@ -1,47 +1,57 @@
-<tabset>
-  <tab i18n-heading
-       heading="Pools List">
-    <cd-view-cache *ngFor="let viewCacheStatus of viewCacheStatusList"
-                   [status]="viewCacheStatus.status"
-                   [statusFor]="viewCacheStatus.statusFor"></cd-view-cache>
+<ul ngbNav
+    #nav="ngbNav"
+    class="nav-tabs">
+  <li ngbNavItem>
+    <a ngbNavLink
+       i18n>Pools List</a>
+    <ng-template ngbNavContent>
+      <cd-view-cache *ngFor="let viewCacheStatus of viewCacheStatusList"
+                     [status]="viewCacheStatus.status"
+                     [statusFor]="viewCacheStatus.statusFor"></cd-view-cache>
 
-    <cd-table #table
-              id="pool-list"
-              [data]="pools"
-              [columns]="columns"
-              selectionType="single"
-              [hasDetails]="true"
-              (setExpandedRow)="setExpandedRow($event)"
-              (updateSelection)="updateSelection($event)">
-      <cd-table-actions id="pool-list-actions"
-                        class="table-actions"
-                        [permission]="permissions.pool"
-                        [selection]="selection"
-                        [tableActions]="tableActions">
-      </cd-table-actions>
-      <cd-pool-details cdTableDetail
-                       id="pool-list-details"
-                       [selection]="expandedRow"
-                       [permissions]="permissions"
-                       [cacheTiers]="cacheTiers">
-      </cd-pool-details>
-    </cd-table>
+      <cd-table #table
+                id="pool-list"
+                [data]="pools"
+                [columns]="columns"
+                selectionType="single"
+                [hasDetails]="true"
+                (setExpandedRow)="setExpandedRow($event)"
+                (updateSelection)="updateSelection($event)">
+        <cd-table-actions id="pool-list-actions"
+                          class="table-actions"
+                          [permission]="permissions.pool"
+                          [selection]="selection"
+                          [tableActions]="tableActions">
+        </cd-table-actions>
+        <cd-pool-details cdTableDetail
+                         id="pool-list-details"
+                         [selection]="expandedRow"
+                         [permissions]="permissions"
+                         [cacheTiers]="cacheTiers">
+        </cd-pool-details>
+      </cd-table>
+    </ng-template>
+  </li>
 
-    <ng-template #poolUsageTpl
-                 let-row="row">
-      <cd-usage-bar *ngIf="row.stats?.max_avail?.latest"
-                    [totalBytes]="row.stats.bytes_used.latest + row.stats.max_avail.latest"
-                    [usedBytes]="row.stats.bytes_used.latest">
-      </cd-usage-bar>
+  <li ngbNavItem
+      *ngIf="permissions.grafana.read">
+    <a ngbNavLink
+       i18n>Overall Performance</a>
+    <ng-template ngbNavContent>
+      <cd-grafana [grafanaPath]="'ceph-pools-overview?'"
+                  uid="z99hzWtmk"
+                  grafanaStyle="two">
+      </cd-grafana>
     </ng-template>
-  </tab>
+  </li>
+</ul>
+
+<div [ngbNavOutlet]="nav"></div>
 
-  <tab i18n-heading
-       *ngIf="permissions.grafana.read"
-       heading="Overall Performance">
-    <cd-grafana [grafanaPath]="'ceph-pools-overview?'"
-                uid="z99hzWtmk"
-                grafanaStyle="two">
-    </cd-grafana>
-  </tab>
-</tabset>
+<ng-template #poolUsageTpl
+             let-row="row">
+  <cd-usage-bar *ngIf="row.stats?.max_avail?.latest"
+                [totalBytes]="row.stats.bytes_used.latest + row.stats.max_avail.latest"
+                [usedBytes]="row.stats.bytes_used.latest">
+  </cd-usage-bar>
+</ng-template>
index dbe4ec6f209194af3a9501bf774574559017875c..43b4cd74b9dcff1176c53256a16fd0df7673bba3 100644 (file)
@@ -3,9 +3,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import * as _ from 'lodash';
 import { BsModalService } from 'ngx-bootstrap/modal';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { ToastrModule } from 'ngx-toastr';
 import { of } from 'rxjs';
 
@@ -54,7 +54,7 @@ describe('PoolListComponent', () => {
       SharedModule,
       ToastrModule.forRoot(),
       RouterTestingModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       HttpClientTestingModule
     ],
     providers: [i18nProviders, PgCategoryService]
index 98fd4360a0e99c69e88d279c714a5e68a4649fcc..566b33c59960d3f0eebc644e86e1477600ae9108 100644 (file)
@@ -3,10 +3,10 @@ import { NgModule } from '@angular/core';
 import { ReactiveFormsModule } from '@angular/forms';
 import { RouterModule, Routes } from '@angular/router';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { PopoverModule } from 'ngx-bootstrap/popover';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { TooltipModule } from 'ngx-bootstrap/tooltip';
 
 import { ActionLabels, URLVerbs } from '../../shared/constants/app.constants';
@@ -23,7 +23,7 @@ import { PoolListComponent } from './pool-list/pool-list.component';
   imports: [
     CephSharedModule,
     CommonModule,
-    TabsModule,
+    NgbNavModule,
     PopoverModule.forRoot(),
     SharedModule,
     RouterModule,
index 761c41b32b0ddda4203814917e3145a2d38eb7b9..1bb5bca2c61754c8e94e3966f442a54096313b80 100644 (file)
-<tabset *ngIf="selection">
-  <tab i18n-heading
-       heading="Details">
-    <div *ngIf="selection">
-      <table class="table table-striped table-bordered">
-        <tbody>
-          <tr>
-            <td i18n
-                class="bold w-25">Name</td>
-            <td class="w-75">{{ selection.bid }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">ID</td>
-            <td>{{ selection.id }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Owner</td>
-            <td>{{ selection.owner }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Index type</td>
-            <td>{{ selection.index_type }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Placement rule</td>
-            <td>{{ selection.placement_rule }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Marker</td>
-            <td>{{ selection.marker }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Maximum marker</td>
-            <td>{{ selection.max_marker }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Version</td>
-            <td>{{ selection.ver }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Master version</td>
-            <td>{{ selection.master_ver }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Modification time</td>
-            <td>{{ selection.mtime | cdDate }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Zonegroup</td>
-            <td>{{ selection.zonegroup }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Versioning</td>
-            <td>{{ selection.versioning }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">MFA Delete</td>
-            <td>{{ selection.mfa_delete }}</td>
-          </tr>
-        </tbody>
-      </table>
-
-      <!-- Bucket quota -->
-      <div *ngIf="selection.bucket_quota">
-        <legend i18n>Bucket quota</legend>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
         <table class="table table-striped table-bordered">
           <tbody>
             <tr>
               <td i18n
-                  class="bold w-25">Enabled</td>
-              <td class="w-75">{{ selection.bucket_quota.enabled | booleanText }}</td>
+                  class="bold w-25">Name</td>
+              <td class="w-75">{{ selection.bid }}</td>
             </tr>
             <tr>
               <td i18n
-                  class="bold">Maximum size</td>
-              <td *ngIf="selection.bucket_quota.max_size <= -1"
-                  i18n>Unlimited</td>
-              <td *ngIf="selection.bucket_quota.max_size > -1">
-                {{ selection.bucket_quota.max_size | dimless }}
-              </td>
+                  class="bold">ID</td>
+              <td>{{ selection.id }}</td>
             </tr>
             <tr>
               <td i18n
-                  class="bold">Maximum objects</td>
-              <td *ngIf="selection.bucket_quota.max_objects <= -1"
-                  i18n>Unlimited</td>
-              <td *ngIf="selection.bucket_quota.max_objects > -1">
-                {{ selection.bucket_quota.max_objects }}
-              </td>
+                  class="bold">Owner</td>
+              <td>{{ selection.owner }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Index type</td>
+              <td>{{ selection.index_type }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Placement rule</td>
+              <td>{{ selection.placement_rule }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Marker</td>
+              <td>{{ selection.marker }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Maximum marker</td>
+              <td>{{ selection.max_marker }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Version</td>
+              <td>{{ selection.ver }}</td>
             </tr>
-          </tbody>
-        </table>
-      </div>
-
-      <!-- Locking -->
-      <legend i18n>Locking</legend>
-      <table class="table table-striped table-bordered">
-        <tbody>
-          <tr>
-            <td i18n
-                class="bold w-25">Enabled</td>
-            <td class="w-75">{{ selection.lock_enabled | booleanText }}</td>
-          </tr>
-          <ng-container *ngIf="selection.lock_enabled">
             <tr>
               <td i18n
-                  class="bold">Mode</td>
-              <td>{{ selection.lock_mode }}</td>
+                  class="bold">Master version</td>
+              <td>{{ selection.master_ver }}</td>
             </tr>
             <tr>
               <td i18n
-                  class="bold">Days</td>
-              <td>{{ selection.lock_retention_period_days }}</td>
+                  class="bold">Modification time</td>
+              <td>{{ selection.mtime | cdDate }}</td>
             </tr>
             <tr>
               <td i18n
-                  class="bold">Years</td>
-              <td>{{ selection.lock_retention_period_years }}</td>
+                  class="bold">Zonegroup</td>
+              <td>{{ selection.zonegroup }}</td>
             </tr>
-          </ng-container>
-        </tbody>
-      </table>
-    </div>
-  </tab>
-</tabset>
+            <tr>
+              <td i18n
+                  class="bold">Versioning</td>
+              <td>{{ selection.versioning }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">MFA Delete</td>
+              <td>{{ selection.mfa_delete }}</td>
+            </tr>
+          </tbody>
+        </table>
+
+        <!-- Bucket quota -->
+        <div *ngIf="selection.bucket_quota">
+          <legend i18n>Bucket quota</legend>
+          <table class="table table-striped table-bordered">
+            <tbody>
+              <tr>
+                <td i18n
+                    class="bold w-25">Enabled</td>
+                <td class="w-75">{{ selection.bucket_quota.enabled | booleanText }}</td>
+              </tr>
+              <tr>
+                <td i18n
+                    class="bold">Maximum size</td>
+                <td *ngIf="selection.bucket_quota.max_size <= -1"
+                    i18n>Unlimited</td>
+                <td *ngIf="selection.bucket_quota.max_size > -1">
+                  {{ selection.bucket_quota.max_size | dimless }}
+                </td>
+              </tr>
+              <tr>
+                <td i18n
+                    class="bold">Maximum objects</td>
+                <td *ngIf="selection.bucket_quota.max_objects <= -1"
+                    i18n>Unlimited</td>
+                <td *ngIf="selection.bucket_quota.max_objects > -1">
+                  {{ selection.bucket_quota.max_objects }}
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+
+        <!-- Locking -->
+        <legend i18n>Locking</legend>
+        <table class="table table-striped table-bordered">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold w-25">Enabled</td>
+              <td class="w-75">{{ selection.lock_enabled | booleanText }}</td>
+            </tr>
+            <ng-container *ngIf="selection.lock_enabled">
+              <tr>
+                <td i18n
+                    class="bold">Mode</td>
+                <td>{{ selection.lock_mode }}</td>
+              </tr>
+              <tr>
+                <td i18n
+                    class="bold">Days</td>
+                <td>{{ selection.lock_retention_period_days }}</td>
+              </tr>
+              <tr>
+                <td i18n
+                    class="bold">Years</td>
+                <td>{{ selection.lock_retention_period_years }}</td>
+              </tr>
+            </ng-container>
+          </tbody>
+        </table>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index a2ede8d3f3f02c6b36a1a3893cc533b2fd456497..3fa79f21050ce337d2b1134b4a4340027d8c27e8 100644 (file)
@@ -1,6 +1,6 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
@@ -13,7 +13,7 @@ describe('RgwBucketDetailsComponent', () => {
 
   configureTestBed({
     declarations: [RgwBucketDetailsComponent],
-    imports: [SharedModule, TabsModule.forRoot()],
+    imports: [SharedModule, NgbNavModule],
     providers: [i18nProviders]
   });
 
index 98cd5fed526c964f51c770a12bed241573551675..435a151ad38ec80af400358c54c19e0ef4e78a78 100644 (file)
@@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { ModalModule } from 'ngx-bootstrap/modal';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import {
   configureTestBed,
@@ -27,7 +27,7 @@ describe('RgwBucketListComponent', () => {
       RouterTestingModule,
       ModalModule.forRoot(),
       SharedModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       HttpClientTestingModule
     ],
     providers: i18nProviders
index b7cea04f21cdaf5a2035a18458bf14a92d5c4c14..77292949b2ab5998e626b5ac1315af813f5f72d5 100644 (file)
@@ -1,22 +1,37 @@
-<tabset *ngIf="selection">
-  <tab i18n-heading
-       heading="Details">
-    <cd-table-key-value [data]="metadata"
-                        (fetchData)="getMetaData()">
-    </cd-table-key-value>
-  </tab>
-  <tab i18n-heading
-       heading="Performance Counters">
-    <cd-table-performance-counter serviceType="rgw"
-                                  [serviceId]="serviceId">
-    </cd-table-performance-counter>
-  </tab>
-  <tab i18n-heading
-       *ngIf="grafanaPermission.read"
-       heading="Performance Details">
-    <cd-grafana [grafanaPath]="'rgw-instance-detail?var-rgw_servers=rgw.' + this.serviceId"
-                uid="x5ARzZtmk"
-                grafanaStyle="one">
-    </cd-grafana>
-  </tab>
-</tabset>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <cd-table-key-value [data]="metadata"
+                            (fetchData)="getMetaData()">
+        </cd-table-key-value>
+      </ng-template>
+    </li>
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Performance Counters</a>
+      <ng-template ngbNavContent>
+        <cd-table-performance-counter serviceType="rgw"
+                                      [serviceId]="serviceId">
+        </cd-table-performance-counter>
+      </ng-template>
+    </li>
+    <li ngbNavItem
+        *ngIf="grafanaPermission.read">
+      <a ngbNavLink
+         i18n>Performance Details</a>
+      <ng-template ngbNavContent>
+        <cd-grafana [grafanaPath]="'rgw-instance-detail?var-rgw_servers=rgw.' + this.serviceId"
+                    uid="x5ARzZtmk"
+                    grafanaStyle="one">
+        </cd-grafana>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index f185f2133d9624ac6ab8e93cf7ee7c8ce677831e..3d2709cdfc155a1ac714c18b72dc1e6f7071df2a 100644 (file)
@@ -1,7 +1,7 @@
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../shared/shared.module';
@@ -14,7 +14,7 @@ describe('RgwDaemonDetailsComponent', () => {
 
   configureTestBed({
     declarations: [RgwDaemonDetailsComponent],
-    imports: [SharedModule, PerformanceCounterModule, TabsModule.forRoot(), HttpClientTestingModule]
+    imports: [SharedModule, PerformanceCounterModule, HttpClientTestingModule, NgbNavModule]
   });
 
   beforeEach(() => {
index 71d9ab67c94a41d985eac253a20d2b0209ff80c3..38683a6f6d8f72e1fe562a65bf646579afca3cb6 100644 (file)
@@ -1,33 +1,46 @@
-<tabset>
-  <tab i18n-heading
-       heading="Daemons List">
-    <cd-table [data]="daemons"
-              [columns]="columns"
-              columnMode="flex"
-              [hasDetails]="true"
-              (setExpandedRow)="setExpandedRow($event)"
-              (fetchData)="getDaemonList($event)">
-      <cd-rgw-daemon-details cdTableDetail
-                             [selection]="expandedRow">
-      </cd-rgw-daemon-details>
-    </cd-table>
-  </tab>
+<ul ngbNav
+    #nav="ngbNav"
+    class="nav-tabs">
+  <li ngbNavItem>
+    <a ngbNavLink
+       i18n>Daemons List</a>
+    <ng-template ngbNavContent>
+      <cd-table [data]="daemons"
+                [columns]="columns"
+                columnMode="flex"
+                [hasDetails]="true"
+                (setExpandedRow)="setExpandedRow($event)"
+                (fetchData)="getDaemonList($event)">
+        <cd-rgw-daemon-details cdTableDetail
+                               [selection]="expandedRow">
+        </cd-rgw-daemon-details>
+      </cd-table>
+    </ng-template>
+  </li>
 
-  <tab i18n-heading
-       *ngIf="grafanaPermission.read"
-       heading="Overall Performance">
-    <cd-grafana [grafanaPath]="'rgw-overview?'"
-                uid="WAkugZpiz"
-                grafanaStyle="two">
-    </cd-grafana>
-  </tab>
+  <li ngbNavItem
+      *ngIf="grafanaPermission.read">
+    <a ngbNavLink
+       i18n>Overall Performance</a>
+    <ng-template ngbNavContent>
+      <cd-grafana [grafanaPath]="'rgw-overview?'"
+                  uid="WAkugZpiz"
+                  grafanaStyle="two">
+      </cd-grafana>
+    </ng-template>
+  </li>
 
-  <tab i18n-heading
-       *ngIf="grafanaPermission.read && isMultiSite"
-       heading="Sync Performance">
-    <cd-grafana [grafanaPath]="'radosgw-sync-overview?'"
-                uid="rgw-sync-overview"
-                grafanaStyle="two">
-    </cd-grafana>
-  </tab>
-</tabset>
+  <li ngbNavItem
+      *ngIf="grafanaPermission.read && isMultiSite">
+    <a ngbNavLink
+       i18n>Sync Performance</a>
+    <ng-template ngbNavContent>
+      <cd-grafana [grafanaPath]="'radosgw-sync-overview?'"
+                  uid="rgw-sync-overview"
+                  grafanaStyle="two">
+      </cd-grafana>
+    </ng-template>
+  </li>
+</ul>
+
+<div [ngbNavOutlet]="nav"></div>
index 19aacc0acd95da6e65ea6df7511777de139abf79..49f41bc60316ff17975af7b1db2dd04ca48576bc 100644 (file)
@@ -3,10 +3,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { of } from 'rxjs';
 
-import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { configureTestBed, i18nProviders, TabHelper } from '../../../../testing/unit-test-helper';
 import { RgwSiteService } from '../../../shared/api/rgw-site.service';
 import { Permissions } from '../../../shared/models/permissions';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
@@ -21,11 +21,11 @@ describe('RgwDaemonListComponent', () => {
   let getPermissionsSpy: jasmine.Spy;
   let getRealmsSpy: jasmine.Spy;
   const permissions = new Permissions({ grafana: ['read'] });
-  const expectTabsAndHeading = (length: number, heading: string) => {
-    const tabs = fixture.debugElement.nativeElement.querySelectorAll('tab');
 
+  const expectTabsAndHeading = (length: number, heading: string) => {
+    const tabs = TabHelper.getTextContents(fixture);
     expect(tabs.length).toEqual(length);
-    expect(tabs[length - 1].getAttribute('heading')).toEqual(heading);
+    expect(tabs[length - 1]).toEqual(heading);
   };
 
   configureTestBed({
@@ -33,7 +33,7 @@ describe('RgwDaemonListComponent', () => {
     imports: [
       BrowserAnimationsModule,
       HttpClientTestingModule,
-      TabsModule.forRoot(),
+      NgbNavModule,
       PerformanceCounterModule,
       SharedModule,
       RouterTestingModule
index e4080a26362c27bf5351e3af0ab24b769cc0d890..c081756061a35dea91b3cd3c4112e4013522c76c 100644 (file)
-<tabset *ngIf="selection">
-  <tab i18n-heading
-       heading="Details">
-    <div *ngIf="user">
-      <table class="table table-striped table-bordered">
-        <tbody>
-          <tr>
-            <td i18n
-                class="bold w-25">Username</td>
-            <td class="w-75">{{ user.uid }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Full name</td>
-            <td>{{ user.display_name }}</td>
-          </tr>
-          <tr *ngIf="user.email?.length">
-            <td i18n
-                class="bold">Email address</td>
-            <td>{{ user.email }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Suspended</td>
-            <td>{{ user.suspended | booleanText }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">System</td>
-            <td>{{ user.system === 'true' | booleanText }}</td>
-          </tr>
-          <tr>
-            <td i18n
-                class="bold">Maximum buckets</td>
-            <td>{{ user.max_buckets | map:maxBucketsMap }}</td>
-          </tr>
-          <tr *ngIf="user.subusers && user.subusers.length">
-            <td i18n
-                class="bold">Subusers</td>
-            <td>
-              <div *ngFor="let subuser of user.subusers">
-                {{ subuser.id }} ({{ subuser.permissions }})
-              </div>
-            </td>
-          </tr>
-          <tr *ngIf="user.caps && user.caps.length">
-            <td i18n
-                class="bold">Capabilities</td>
-            <td>
-              <div *ngFor="let cap of user.caps">
-                {{ cap.type }} ({{ cap.perm }})
-              </div>
-            </td>
-          </tr>
-        </tbody>
-      </table>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <div *ngIf="user">
+          <table class="table table-striped table-bordered">
+            <tbody>
+              <tr>
+                <td i18n
+                    class="bold w-25">Username</td>
+                <td class="w-75">{{ user.uid }}</td>
+              </tr>
+              <tr>
+                <td i18n
+                    class="bold">Full name</td>
+                <td>{{ user.display_name }}</td>
+              </tr>
+              <tr *ngIf="user.email?.length">
+                <td i18n
+                    class="bold">Email address</td>
+                <td>{{ user.email }}</td>
+              </tr>
+              <tr>
+                <td i18n
+                    class="bold">Suspended</td>
+                <td>{{ user.suspended | booleanText }}</td>
+              </tr>
+              <tr>
+                <td i18n
+                    class="bold">System</td>
+                <td>{{ user.system === 'true' | booleanText }}</td>
+              </tr>
+              <tr>
+                <td i18n
+                    class="bold">Maximum buckets</td>
+                <td>{{ user.max_buckets | map:maxBucketsMap }}</td>
+              </tr>
+              <tr *ngIf="user.subusers && user.subusers.length">
+                <td i18n
+                    class="bold">Subusers</td>
+                <td>
+                  <div *ngFor="let subuser of user.subusers">
+                    {{ subuser.id }} ({{ subuser.permissions }})
+                  </div>
+                </td>
+              </tr>
+              <tr *ngIf="user.caps && user.caps.length">
+                <td i18n
+                    class="bold">Capabilities</td>
+                <td>
+                  <div *ngFor="let cap of user.caps">
+                    {{ cap.type }} ({{ cap.perm }})
+                  </div>
+                </td>
+              </tr>
+            </tbody>
+          </table>
 
-      <!-- User quota -->
-      <div *ngIf="user.user_quota">
-        <legend i18n>User quota</legend>
-        <table class="table table-striped table-bordered">
-          <tbody>
-            <tr>
-              <td i18n
-                  class="bold w-25">Enabled</td>
-              <td class="w-75">{{ user.user_quota.enabled | booleanText }}</td>
-            </tr>
-            <tr>
-              <td i18n
-                  class="bold">Maximum size</td>
-              <td *ngIf="!user.user_quota.enabled">-</td>
-              <td *ngIf="user.user_quota.enabled && user.user_quota.max_size <= -1"
-                  i18n>Unlimited</td>
-              <td *ngIf="user.user_quota.enabled && user.user_quota.max_size > -1">
-                {{ user.user_quota.max_size | dimlessBinary }}
-              </td>
-            </tr>
-            <tr>
-              <td i18n
-                  class="bold">Maximum objects</td>
-              <td *ngIf="!user.user_quota.enabled">-</td>
-              <td *ngIf="user.user_quota.enabled && user.user_quota.max_objects <= -1"
-                  i18n>Unlimited</td>
-              <td *ngIf="user.user_quota.enabled && user.user_quota.max_objects > -1">
-                {{ user.user_quota.max_objects }}
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </div>
+          <!-- User quota -->
+          <div *ngIf="user.user_quota">
+            <legend i18n>User quota</legend>
+            <table class="table table-striped table-bordered">
+              <tbody>
+                <tr>
+                  <td i18n
+                      class="bold w-25">Enabled</td>
+                  <td class="w-75">{{ user.user_quota.enabled | booleanText }}</td>
+                </tr>
+                <tr>
+                  <td i18n
+                      class="bold">Maximum size</td>
+                  <td *ngIf="!user.user_quota.enabled">-</td>
+                  <td *ngIf="user.user_quota.enabled && user.user_quota.max_size <= -1"
+                      i18n>Unlimited</td>
+                  <td *ngIf="user.user_quota.enabled && user.user_quota.max_size > -1">
+                    {{ user.user_quota.max_size | dimlessBinary }}
+                  </td>
+                </tr>
+                <tr>
+                  <td i18n
+                      class="bold">Maximum objects</td>
+                  <td *ngIf="!user.user_quota.enabled">-</td>
+                  <td *ngIf="user.user_quota.enabled && user.user_quota.max_objects <= -1"
+                      i18n>Unlimited</td>
+                  <td *ngIf="user.user_quota.enabled && user.user_quota.max_objects > -1">
+                    {{ user.user_quota.max_objects }}
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
 
-      <!-- Bucket quota -->
-      <div *ngIf="user.bucket_quota">
-        <legend i18n>Bucket quota</legend>
-        <table class="table table-striped table-bordered">
-          <tbody>
-            <tr>
-              <td i18n
-                  class="bold w-25">Enabled</td>
-              <td class="w-75">{{ user.bucket_quota.enabled | booleanText }}</td>
-            </tr>
-            <tr>
-              <td i18n
-                  class="bold">Maximum size</td>
-              <td *ngIf="!user.bucket_quota.enabled">-</td>
-              <td *ngIf="user.bucket_quota.enabled && user.bucket_quota.max_size <= -1"
-                  i18n>Unlimited</td>
-              <td *ngIf="user.bucket_quota.enabled && user.bucket_quota.max_size > -1">
-                {{ user.bucket_quota.max_size | dimlessBinary }}
-              </td>
-            </tr>
-            <tr>
-              <td i18n
-                  class="bold">Maximum objects</td>
-              <td *ngIf="!user.bucket_quota.enabled">-</td>
-              <td *ngIf="user.bucket_quota.enabled && user.bucket_quota.max_objects <= -1"
-                  i18n>Unlimited</td>
-              <td *ngIf="user.bucket_quota.enabled && user.bucket_quota.max_objects > -1">
-                {{ user.bucket_quota.max_objects }}
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </div>
-    </div>
-  </tab>
-
-  <tab *ngIf="keys.length"
-       i18n-heading
-       heading="Keys">
-    <cd-table [data]="keys"
-              [columns]="keysColumns"
-              columnMode="flex"
-              selectionType="multi"
-              forceIdentifier="true"
-              (updateSelection)="updateKeysSelection($event)">
-      <div class="table-actions">
-        <div class="btn-group"
-             dropdown>
-          <button type="button"
-                  class="btn btn-secondary"
-                  [disabled]="!keysSelection.hasSingleSelection"
-                  (click)="showKeyModal()">
-            <i [ngClass]="[icons.show]"></i>
-            <ng-container i18n>Show</ng-container>
-          </button>
+          <!-- Bucket quota -->
+          <div *ngIf="user.bucket_quota">
+            <legend i18n>Bucket quota</legend>
+            <table class="table table-striped table-bordered">
+              <tbody>
+                <tr>
+                  <td i18n
+                      class="bold w-25">Enabled</td>
+                  <td class="w-75">{{ user.bucket_quota.enabled | booleanText }}</td>
+                </tr>
+                <tr>
+                  <td i18n
+                      class="bold">Maximum size</td>
+                  <td *ngIf="!user.bucket_quota.enabled">-</td>
+                  <td *ngIf="user.bucket_quota.enabled && user.bucket_quota.max_size <= -1"
+                      i18n>Unlimited</td>
+                  <td *ngIf="user.bucket_quota.enabled && user.bucket_quota.max_size > -1">
+                    {{ user.bucket_quota.max_size | dimlessBinary }}
+                  </td>
+                </tr>
+                <tr>
+                  <td i18n
+                      class="bold">Maximum objects</td>
+                  <td *ngIf="!user.bucket_quota.enabled">-</td>
+                  <td *ngIf="user.bucket_quota.enabled && user.bucket_quota.max_objects <= -1"
+                      i18n>Unlimited</td>
+                  <td *ngIf="user.bucket_quota.enabled && user.bucket_quota.max_objects > -1">
+                    {{ user.bucket_quota.max_objects }}
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
         </div>
-      </div>
-    </cd-table>
-  </tab>
-</tabset>
+      </ng-template>
+    </li>
+    <li ngbNavItem
+        *ngIf="keys.length">
+      <a ngbNavLink
+         i18n>Keys</a>
+      <ng-template ngbNavContent>
+        <cd-table [data]="keys"
+                  [columns]="keysColumns"
+                  columnMode="flex"
+                  selectionType="multi"
+                  forceIdentifier="true"
+                  (updateSelection)="updateKeysSelection($event)">
+          <div class="table-actions">
+            <div class="btn-group"
+                 dropdown>
+              <button type="button"
+                      class="btn btn-secondary"
+                      [disabled]="!keysSelection.hasSingleSelection"
+                      (click)="showKeyModal()">
+                <i [ngClass]="[icons.show]"></i>
+                <ng-container i18n>Show</ng-container>
+              </button>
+            </div>
+          </div>
+        </cd-table>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index 1b29228c38cabc511e98c849b4816d134e654714..11a647690c3f75e2abf861673b0cfd308b4bf295 100644 (file)
@@ -2,10 +2,10 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { BsModalService } from 'ngx-bootstrap/modal';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
-import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { configureTestBed, i18nProviders, TabHelper } from '../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../shared/shared.module';
 import { RgwUserS3Key } from '../models/rgw-user-s3-key';
 import { RgwUserDetailsComponent } from './rgw-user-details.component';
@@ -16,7 +16,7 @@ describe('RgwUserDetailsComponent', () => {
 
   configureTestBed({
     declarations: [RgwUserDetailsComponent],
-    imports: [BrowserAnimationsModule, HttpClientTestingModule, SharedModule, TabsModule.forRoot()],
+    imports: [BrowserAnimationsModule, HttpClientTestingModule, SharedModule, NgbNavModule],
     providers: [BsModalService, i18nProviders]
   });
 
@@ -30,20 +30,18 @@ describe('RgwUserDetailsComponent', () => {
   it('should create', () => {
     expect(component).toBeTruthy();
 
-    const detailsTab = fixture.debugElement.nativeElement.querySelector('tab[heading="Details"]');
-    expect(detailsTab).toBeTruthy();
-    const keysTab = fixture.debugElement.nativeElement.querySelector('tab[heading="Keys"]');
-    expect(keysTab).toBeFalsy();
+    const tabs = TabHelper.getTextContents(fixture);
+    expect(tabs).toContain('Details');
+    expect(tabs).not.toContain('Keys');
   });
 
   it('should show "Details" tab', () => {
     component.selection = { uid: 'myUsername' };
     fixture.detectChanges();
 
-    const detailsTab = fixture.debugElement.nativeElement.querySelector('tab[heading="Details"]');
-    expect(detailsTab).toBeTruthy();
-    const keysTab = fixture.debugElement.nativeElement.querySelector('tab[heading="Keys"]');
-    expect(keysTab).toBeFalsy();
+    const tabs = TabHelper.getTextContents(fixture);
+    expect(tabs).toContain('Details');
+    expect(tabs).not.toContain('Keys');
   });
 
   it('should show "Keys" tab', () => {
@@ -52,10 +50,9 @@ describe('RgwUserDetailsComponent', () => {
     component.ngOnChanges();
     fixture.detectChanges();
 
-    const detailsTab = fixture.debugElement.nativeElement.querySelector('tab[heading="Details"]');
-    expect(detailsTab).toBeTruthy();
-    const keysTab = fixture.debugElement.nativeElement.querySelector('tab[heading="Keys"]');
-    expect(keysTab).toBeTruthy();
+    const tabs = TabHelper.getTextContents(fixture);
+    expect(tabs).toContain('Details');
+    expect(tabs).toContain('Keys');
   });
 
   it('should show correct "System" info', () => {
index 62f849d7bc2d0ec5383bfa4f5746e49440c1709e..0429da0f53dd325f4a91fbdba40410390ef84e34 100644 (file)
@@ -3,11 +3,11 @@ import { NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule, Routes } from '@angular/router';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 import { AlertModule } from 'ngx-bootstrap/alert';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
 import { ModalModule } from 'ngx-bootstrap/modal';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 import { TooltipModule } from 'ngx-bootstrap/tooltip';
 
 import { ActionLabels, URLVerbs } from '../../shared/constants/app.constants';
@@ -46,7 +46,7 @@ import { RgwUserSwiftKeyModalComponent } from './rgw-user-swift-key-modal/rgw-us
     PerformanceCounterModule,
     AlertModule.forRoot(),
     BsDropdownModule.forRoot(),
-    TabsModule.forRoot(),
+    NgbNavModule,
     TooltipModule.forRoot(),
     ModalModule.forRoot(),
     RouterModule,
index 9f523ca232b50c8ae635a2c4eef23f3c48827f6f..2ca947a612043804425241afa6506868c46a00b1 100644 (file)
@@ -1,12 +1,15 @@
 import { CommonModule } from '@angular/common';
 import { NgModule } from '@angular/core';
-import { TabsModule } from 'ngx-bootstrap/tabs';
+
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
+
 import { DataTableModule } from '../../shared/datatable/datatable.module';
 import { SharedModule } from '../../shared/shared.module';
 import { DeviceListComponent } from './device-list/device-list.component';
 import { SmartListComponent } from './smart-list/smart-list.component';
+
 @NgModule({
-  imports: [CommonModule, DataTableModule, SharedModule, TabsModule],
+  imports: [CommonModule, DataTableModule, SharedModule, NgbNavModule],
   exports: [DeviceListComponent, SmartListComponent],
   declarations: [DeviceListComponent, SmartListComponent]
 })
index 3bc29c518dcfcccd2da0878f26c52180e92be151..2179c049d3496f499afe3a4377b7f358989e40cc 100644 (file)
@@ -4,63 +4,85 @@
                   i18n>Failed to retrieve SMART data.</cd-alert-panel>
   <cd-alert-panel *ngIf="incompatible"
                   type="warning"
-                  i18n>The data received has the JSON format version 2.x and is currently incompatible with the dashboard.</cd-alert-panel>
+                  i18n>The data received has the JSON format version 2.x and is currently incompatible with the
+    dashboard.</cd-alert-panel>
 
   <ng-container *ngIf="!error && !incompatible">
     <cd-alert-panel *ngIf="!(data | keyvalue).length"
                     type="info"
                     i18n>No SMART data available.</cd-alert-panel>
 
-    <tabset *ngFor="let device of data | keyvalue">
-      <tab [heading]="device.value.device + ' (' + device.value.identifier + ')'">
-        <ng-container *ngIf="device.value.error; else noError">
-          <cd-alert-panel id="alert-error"
-                          type="warning">{{ device.value.userMessage }}</cd-alert-panel>
-        </ng-container>
-        <ng-template #noError>
-          <!-- HDD/NVMe self test -->
-          <ng-container *ngIf="device.value.info.smart_status.passed; else selfTestFailed">
-            <cd-alert-panel id="alert-self-test-passed"
-                            size="slim"
-                            type="info"
-                            i18n-title
-                            title="SMART overall-health self-assessment test result"
-                            i18n>passed</cd-alert-panel>
-          </ng-container>
-          <ng-template #selfTestFailed>
-            <cd-alert-panel id="alert-self-test-failed"
-                            size="slim"
-                            type="warning"
-                            i18n-title
-                            title="SMART overall-health self-assessment test result"
-                            i18n>failed</cd-alert-panel>
-          </ng-template>
+    <ng-container *ngFor="let device of data | keyvalue">
+      <ul ngbNav
+          #nav="ngbNav"
+          class="nav-tabs">
+        <li ngbNavItem>
+          <a ngbNavLink
+             i18n>{{ device.value.device }} ({{ device.value.identifier }})</a>
+          <ng-template ngbNavContent>
+
+            <ng-container *ngIf="device.value.error; else noError">
+              <cd-alert-panel id="alert-error"
+                              type="warning">{{ device.value.userMessage }}</cd-alert-panel>
+            </ng-container>
+
+            <ng-template #noError>
+              <!-- HDD/NVMe self test -->
+              <ng-container *ngIf="device.value.info.smart_status.passed; else selfTestFailed">
+                <cd-alert-panel id="alert-self-test-passed"
+                                size="slim"
+                                type="info"
+                                i18n-title
+                                title="SMART overall-health self-assessment test result"
+                                i18n>passed</cd-alert-panel>
+              </ng-container>
+              <ng-template #selfTestFailed>
+                <cd-alert-panel id="alert-self-test-failed"
+                                size="slim"
+                                type="warning"
+                                i18n-title
+                                title="SMART overall-health self-assessment test result"
+                                i18n>failed</cd-alert-panel>
+              </ng-template>
+            </ng-template>
 
-          <tabset>
-            <tab i18n-heading
-                 heading="Device Information">
-              <cd-table-key-value [renderObjects]="true"
-                                  [data]="device.value.info"></cd-table-key-value>
-            </tab>
+            <ul ngbNav
+                #innerNav="ngbNav"
+                class="nav-tabs">
+              <li ngbNavItem>
+                <a ngbNavLink
+                   i18n>Device Information</a>
+                <ng-template ngbNavContent>
+                  <cd-table-key-value [renderObjects]="true"
+                                      [data]="device.value.info"></cd-table-key-value>
+                </ng-template>
+              </li>
+              <li ngbNavItem>
+                <a ngbNavLink
+                   i18n>SMART</a>
+                <ng-template ngbNavContent>
+                  <cd-table *ngIf="device.value.smart.attributes"
+                            [data]="device.value.smart.attributes.table"
+                            updateSelectionOnRefresh="never"
+                            [columns]="smartDataColumns"></cd-table>
+                  <cd-table-key-value *ngIf="device.value.smart.nvmeData"
+                                      [renderObjects]="true"
+                                      [data]="device.value.smart.nvmeData"
+                                      updateSelectionOnRefresh="never"></cd-table-key-value>
+                  <cd-alert-panel *ngIf="!device.value.smart.attributes && !device.value.smart.nvmeData"
+                                  type="info"
+                                  i18n>No SMART data available for this device.</cd-alert-panel>
+                </ng-template>
+              </li>
+            </ul>
+
+            <div [ngbNavOutlet]="innerNav"></div>
+          </ng-template>
+        </li>
+      </ul>
 
-            <tab i18n-heading
-                 heading="SMART">
-              <cd-table *ngIf="device.value.smart.attributes"
-                        [data]="device.value.smart.attributes.table"
-                        updateSelectionOnRefresh="never"
-                        [columns]="smartDataColumns"></cd-table>
-              <cd-table-key-value *ngIf="device.value.smart.nvmeData"
-                                  [renderObjects]="true"
-                                  [data]="device.value.smart.nvmeData"
-                                  updateSelectionOnRefresh="never"></cd-table-key-value>
-              <cd-alert-panel *ngIf="!device.value.smart.attributes && !device.value.smart.nvmeData"
-                              type="info"
-                              i18n>No SMART data available for this device.</cd-alert-panel>
-            </tab>
-          </tabset>
-        </ng-template>
-      </tab>
-    </tabset>
+      <div [ngbNavOutlet]="nav"></div>
+    </ng-container>
   </ng-container>
 </ng-container>
 <ng-template #isLoading>
index 395608b34321b1f2adb74781ecbfe2630ff55a37..482200041e1503901ea5a4dbdb025a5fd39b2e66 100644 (file)
@@ -4,8 +4,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { By } from '@angular/platform-browser';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import * as _ from 'lodash';
-import { TabsetComponent, TabsetConfig, TabsModule } from 'ngx-bootstrap/tabs';
 import { of } from 'rxjs';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
@@ -81,8 +81,8 @@ describe('OsdSmartListComponent', () => {
 
   configureTestBed({
     declarations: [SmartListComponent],
-    imports: [BrowserAnimationsModule, TabsModule, SharedModule, HttpClientTestingModule],
-    providers: [i18nProviders, TabsetComponent, TabsetConfig]
+    imports: [BrowserAnimationsModule, SharedModule, HttpClientTestingModule, NgbNavModule],
+    providers: [i18nProviders]
   });
 
   beforeEach(() => {
index 9171e24e453aa22cfd8a53f1fccdfbf0ff8900f7..a4244149c4c5af913492f5eea3fd3cd8d24ddb60 100644 (file)
@@ -3,11 +3,11 @@ import { NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule, Routes } from '@angular/router';
 
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { NgBootstrapFormValidationModule } from 'ng-bootstrap-form-validation';
 import { ButtonsModule } from 'ngx-bootstrap/buttons';
 import { BsDatepickerModule } from 'ngx-bootstrap/datepicker';
 import { PopoverModule } from 'ngx-bootstrap/popover';
-import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { ActionLabels, URLVerbs } from '../../shared/constants/app.constants';
 import { SharedModule } from '../../shared/shared.module';
@@ -30,7 +30,7 @@ import { UserTabsComponent } from './user-tabs/user-tabs.component';
     PopoverModule.forRoot(),
     ReactiveFormsModule,
     SharedModule,
-    TabsModule.forRoot(),
+    NgbNavModule,
     RouterModule,
     NgBootstrapFormValidationModule,
     BsDatepickerModule.forRoot()
index ec054c67fbb25b005917ae00e9fafc673d2af7b7..0bb532ddfa191ba95d49b47a21c764c0445d2ee1 100644 (file)
@@ -1,14 +1,23 @@
-<tabset *ngIf="selection">
-  <tab heading="Details"
-       i18n-heading>
-    <cd-table [data]="scopes_permissions"
-              [columns]="columns"
-              columnMode="flex"
-              [toolHeader]="false"
-              [autoReload]="false"
-              [autoSave]="false"
-              [footer]="false"
-              [limit]="0">
-    </cd-table>
-  </tab>
-</tabset>
+<ng-container *ngIf="selection">
+  <ul ngbNav
+      #nav="ngbNav"
+      class="nav-tabs">
+    <li ngbNavItem>
+      <a ngbNavLink
+         i18n>Details</a>
+      <ng-template ngbNavContent>
+        <cd-table [data]="scopes_permissions"
+                  [columns]="columns"
+                  columnMode="flex"
+                  [toolHeader]="false"
+                  [autoReload]="false"
+                  [autoSave]="false"
+                  [footer]="false"
+                  [limit]="0">
+        </cd-table>
+      </ng-template>
+    </li>
+  </ul>
+
+  <div [ngbNavOutlet]="nav"></div>
+</ng-container>
index d07ef6fe447a2b120d94cde2ec72b4b3ea4a2633..7d1fe5b80bbd589a881b930af2cce8e44b9c3651 100644 (file)
@@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../shared/shared.module';
@@ -13,7 +13,7 @@ describe('RoleDetailsComponent', () => {
   let fixture: ComponentFixture<RoleDetailsComponent>;
 
   configureTestBed({
-    imports: [SharedModule, TabsModule.forRoot(), RouterTestingModule, HttpClientTestingModule],
+    imports: [SharedModule, RouterTestingModule, HttpClientTestingModule, NgbNavModule],
     declarations: [RoleDetailsComponent],
     providers: i18nProviders
   });
index 9b04f771b4433c21b75845a432765840dd728134..dc3c71c38ed41ddcba3308c17b16091c30b50325 100644 (file)
@@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { ToastrModule } from 'ngx-toastr';
 
 import {
@@ -27,7 +27,7 @@ describe('RoleListComponent', () => {
       BrowserAnimationsModule,
       SharedModule,
       ToastrModule.forRoot(),
-      TabsModule.forRoot(),
+      NgbNavModule,
       RouterTestingModule,
       HttpClientTestingModule
     ],
index 9f2f4de895c3442470be1a5ff4c2df1c9933c64e..e48bb99f5ce5bb1ee9a9950d048cc784390e264a 100644 (file)
@@ -63,7 +63,7 @@ export class UserFormComponent extends CdForm implements OnInit {
     private authService: AuthService,
     private authStorageService: AuthStorageService,
     private route: ActivatedRoute,
-    private router: Router,
+    public router: Router,
     private modalService: BsModalService,
     private roleService: RoleService,
     private userService: UserService,
index 0f0f5e8a3967bdac2d985e08ad086095a825bdfb..f5ead615831ef98bf5c0fa809f7d96e5c7b55563 100644 (file)
@@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { ToastrModule } from 'ngx-toastr';
 
 import {
@@ -25,7 +25,7 @@ describe('UserListComponent', () => {
       BrowserAnimationsModule,
       SharedModule,
       ToastrModule.forRoot(),
-      TabsModule.forRoot(),
+      NgbNavModule,
       RouterTestingModule,
       HttpClientTestingModule
     ],
index b707992a9635da516b7985259d42708b5bd0729c..3c102cf6676c0a922e5435956cab53566801679c 100644 (file)
@@ -1,12 +1,14 @@
-<tabset>
-  <tab heading="Users"
-       i18n-heading
-       [active]="url === '/user-management/users'"
-       (selectTab)="navigateTo('/user-management/users')">
-  </tab>
-  <tab heading="Roles"
-       i18n-heading
-       [active]="url === '/user-management/roles'"
-       (selectTab)="navigateTo('/user-management/roles')">
-  </tab>
-</tabset>
+<ul ngbNav
+    #nav="ngbNav"
+    [activeId]="router.url"
+    (navChange)="router.navigate([$event.nextId])"
+    class="nav-tabs">
+  <li ngbNavItem="/user-management/users">
+    <a ngbNavLink
+       i18n>Users</a>
+  </li>
+  <li ngbNavItem="/user-management/roles">
+    <a ngbNavLink
+       i18n>Roles</a>
+  </li>
+</ul>
index d73395141d031578c51ece1f9002b3c17cdad3a5..7e0af5980e302854b2dc98c80112b7c09932a0ad 100644 (file)
@@ -2,7 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { RouterTestingModule } from '@angular/router/testing';
 
-import { TabsModule } from 'ngx-bootstrap/tabs';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../shared/shared.module';
@@ -13,7 +13,7 @@ describe('UserTabsComponent', () => {
   let fixture: ComponentFixture<UserTabsComponent>;
 
   configureTestBed({
-    imports: [SharedModule, TabsModule.forRoot(), RouterTestingModule, HttpClientTestingModule],
+    imports: [SharedModule, RouterTestingModule, HttpClientTestingModule, NgbNavModule],
     declarations: [UserTabsComponent]
   });
 
index c1302a5ccc61629c74e4ae81c57fc54936b9c969..a143c1670fcb4ed51b47eb7a394b2fdec335b441 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
 
 import { Router } from '@angular/router';
 
@@ -7,16 +7,8 @@ import { Router } from '@angular/router';
   templateUrl: './user-tabs.component.html',
   styleUrls: ['./user-tabs.component.scss']
 })
-export class UserTabsComponent implements OnInit {
+export class UserTabsComponent {
   url: string;
 
-  constructor(private router: Router) {}
-
-  ngOnInit() {
-    this.url = this.router.url;
-  }
-
-  navigateTo(url: string) {
-    this.router.navigate([url]);
-  }
+  constructor(public router: Router) {}
 }
index a3ac199a4ba16f23502fb2dce0de90785b9feeb1..d5f598777fde14973a8694082e1069dba14f4442 100644 (file)
@@ -52,7 +52,7 @@ describe('TableComponent', () => {
     component = fixture.componentInstance;
 
     component.data = createFakeData(10);
-    component.columns = [
+    component.localColumns = component.columns = [
       { prop: 'a', name: 'Index', filterable: true },
       { prop: 'b', name: 'Index times ten' },
       { prop: 'c', name: 'Odd?', filterable: true }
@@ -295,7 +295,7 @@ describe('TableComponent', () => {
 
       beforeEach(() => {
         component.data = [testObject];
-        component.columns = [{ prop: 'obj', name: 'Object' }];
+        component.localColumns = [{ prop: 'obj', name: 'Object' }];
       });
 
       it('should not search through objects as default case', () => {
@@ -366,7 +366,7 @@ describe('TableComponent', () => {
     });
 
     it('should search through arrays', () => {
-      component.columns = [
+      component.localColumns = [
         { prop: 'a', name: 'Index' },
         { prop: 'b', name: 'ArrayColumn' }
       ];
@@ -454,15 +454,15 @@ describe('TableComponent', () => {
     });
 
     it('should have updated the column definitions', () => {
-      expect(component.columns[0].flexGrow).toBe(1);
-      expect(component.columns[1].flexGrow).toBe(2);
-      expect(component.columns[2].flexGrow).toBe(2);
-      expect(component.columns[2].resizeable).toBe(false);
+      expect(component.localColumns[0].flexGrow).toBe(1);
+      expect(component.localColumns[1].flexGrow).toBe(2);
+      expect(component.localColumns[2].flexGrow).toBe(2);
+      expect(component.localColumns[2].resizeable).toBe(false);
     });
 
     it('should have table columns', () => {
       expect(component.tableColumns.length).toBe(3);
-      expect(component.tableColumns).toEqual(component.columns);
+      expect(component.tableColumns).toEqual(component.localColumns);
     });
 
     it('should have a unique identifier which it searches for', () => {
index 9efb7c91915951901b31d1c887291347cd1c19c2..35b750d029a89325c7526dd8fce4fa305aa91fc3 100644 (file)
@@ -190,6 +190,12 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
    */
   expanded: any = undefined;
 
+  /**
+   * To prevent making changes to the original columns list, that might change
+   * how the table is renderer a second time, we now clone that list into a
+   * local variable and only use the clone.
+   */
+  localColumns: CdTableColumn[];
   tableColumns: CdTableColumn[];
   icons = Icons;
   cellTemplates: {
@@ -237,27 +243,29 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   }
 
   ngOnInit() {
+    this.localColumns = _.clone(this.columns);
+
     // ngx-datatable triggers calculations each time mouse enters a row,
     // this will prevent that.
     this.table.element.addEventListener('mouseenter', (e) => e.stopPropagation(), true);
     this._addTemplates();
     if (!this.sorts) {
       // Check whether the specified identifier exists.
-      const exists = _.findIndex(this.columns, ['prop', this.identifier]) !== -1;
+      const exists = _.findIndex(this.localColumns, ['prop', this.identifier]) !== -1;
       // Auto-build the sorting configuration. If the specified identifier doesn't exist,
       // then use the property of the first column.
       this.sorts = this.createSortingDefinition(
-        exists ? this.identifier : this.columns[0].prop + ''
+        exists ? this.identifier : this.localColumns[0].prop + ''
       );
       // If the specified identifier doesn't exist and it is not forced to use it anyway,
       // then use the property of the first column.
       if (!exists && !this.forceIdentifier) {
-        this.identifier = this.columns[0].prop + '';
+        this.identifier = this.localColumns[0].prop + '';
       }
     }
 
     this.initUserConfig();
-    this.columns.forEach((c) => {
+    this.localColumns.forEach((c) => {
       if (c.cellTransformation) {
         c.cellTemplate = this.cellTemplates[c.cellTransformation];
       }
@@ -298,7 +306,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
 
   initUserConfig() {
     if (this.autoSave) {
-      this.tableName = this._calculateUniqueTableName(this.columns);
+      this.tableName = this._calculateUniqueTableName(this.localColumns);
       this._loadUserConfig();
       this._initUserConfigAutoSave();
     }
@@ -311,7 +319,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
     if (!this.userConfig.columns) {
       this.updateUserColumns();
     } else {
-      this.columns.forEach((c, i) => {
+      this.localColumns.forEach((c, i) => {
         c.isHidden = this.userConfig.columns[i].isHidden;
       });
     }
@@ -364,7 +372,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   }
 
   updateUserColumns() {
-    this.userConfig.columns = this.columns.map((c) => ({
+    this.userConfig.columns = this.localColumns.map((c) => ({
       prop: c.prop,
       name: c.name,
       isHidden: !!c.isHidden
@@ -376,7 +384,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
    */
   initCheckboxColumn() {
     if (this.selectionType === 'multiClick') {
-      this.columns.unshift({
+      this.localColumns.unshift({
         prop: undefined,
         resizeable: false,
         sortable: false,
@@ -394,7 +402,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
    */
   initExpandCollapseColumn() {
     if (this.hasDetails) {
-      this.columns.unshift({
+      this.localColumns.unshift({
         prop: undefined,
         resizeable: false,
         sortable: false,
@@ -409,11 +417,11 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   }
 
   filterHiddenColumns() {
-    this.tableColumns = this.columns.filter((c) => !c.isHidden);
+    this.tableColumns = this.localColumns.filter((c) => !c.isHidden);
   }
 
   initColumnFilters() {
-    let filterableColumns = _.filter(this.columns, { filterable: true });
+    let filterableColumns = _.filter(this.localColumns, { filterable: true });
     filterableColumns = [...filterableColumns, ...this.extraFilterableColumns];
     this.columnFilters = filterableColumns.map((col: CdTableColumn) => {
       return {
@@ -692,7 +700,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
       $event.target.checked = true;
       return;
     }
-    _.find(this.columns, (c: CdTableColumn) => c.prop === prop).isHidden = hide;
+    _.find(this.localColumns, (c: CdTableColumn) => c.prop === prop).isHidden = hide;
     this.updateColumns();
   }
 
@@ -737,7 +745,9 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
     let rows = this.columnFilters.length !== 0 ? this.doColumnFiltering() : this.data;
 
     if (this.search.length > 0 && rows) {
-      const columns = this.columns.filter((c) => c.cellTransformation !== CellTemplate.sparkline);
+      const columns = this.localColumns.filter(
+        (c) => c.cellTransformation !== CellTemplate.sparkline
+      );
       // update the rows
       rows = this.subSearch(rows, TableComponent.prepareSearch(this.search), columns);
       // Whenever the filter changes, always go back to the first page
index 81047f906377875b702311ffde261c3a505d78a8..e6869c5ee193e531122e57424592bbc28096f4b9 100644 (file)
@@ -1,4 +1,4 @@
-import { LOCALE_ID, TRANSLATIONS, TRANSLATIONS_FORMAT, Type } from '@angular/core';
+import { DebugElement, LOCALE_ID, TRANSLATIONS, TRANSLATIONS_FORMAT, Type } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { AbstractControl } from '@angular/forms';
 import { By } from '@angular/platform-browser';
@@ -7,6 +7,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill';
 import { configureTestSuite } from 'ng-bullet';
 import { BsModalRef } from 'ngx-bootstrap/modal';
 
+import { NgbNav, NgbNavItem } from '@ng-bootstrap/ng-bootstrap';
 import { TableActionsComponent } from '../app/shared/datatable/table-actions/table-actions.component';
 import { Icons } from '../app/shared/enum/icons.enum';
 import { CdFormGroup } from '../app/shared/forms/cd-form-group';
@@ -542,3 +543,25 @@ export class Mocks {
     return rule;
   }
 }
+
+export class TabHelper {
+  static getNgbNav(fixture: ComponentFixture<any>) {
+    const debugElem: DebugElement = fixture.debugElement;
+    return debugElem.query(By.directive(NgbNav)).injector.get(NgbNav);
+  }
+
+  static getNgbNavItems(fixture: ComponentFixture<any>) {
+    const debugElems = this.getNgbNavItemsDebugElems(fixture);
+    return debugElems.map((de) => de.injector.get(NgbNavItem));
+  }
+
+  static getTextContents(fixture: ComponentFixture<any>) {
+    const debugElems = this.getNgbNavItemsDebugElems(fixture);
+    return debugElems.map((de) => de.nativeElement.textContent);
+  }
+
+  private static getNgbNavItemsDebugElems(fixture: ComponentFixture<any>) {
+    const debugElem: DebugElement = fixture.debugElement;
+    return debugElem.queryAll(By.directive(NgbNavItem));
+  }
+}