]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: stay on active tab 34946/head
authorVolker Theile <vtheile@suse.com>
Tue, 30 Jun 2020 09:26:22 +0000 (11:26 +0200)
committerVolker Theile <vtheile@suse.com>
Fri, 3 Jul 2020 15:33:26 +0000 (17:33 +0200)
* Remember the selected tab and restore the state on tabset init.
* Remove tab headers where only one tab is shown.

Fixes: https://tracker.ceph.com/issues/43120
Signed-off-by: Volker Theile <vtheile@suse.com>
24 files changed:
src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.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/rbd-details/rbd-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-tabs/cephfs-tabs.component.html
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/hosts/host-details/host-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.html
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/osd/osd-details/osd-details.component.html
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/nfs/nfs-details/nfs-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-details/pool-details.component.html
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-daemon-details/rgw-daemon-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-details/role-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/directives.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/stateful-tab.directive.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/directives/stateful-tab.directive.ts [new file with mode: 0644]

index fda568206752822168f96a184ec034af3d8007b3..33b3e357a68d744f55fffe2d06303442babef102 100644 (file)
@@ -23,10 +23,9 @@ describe('Configuration page', () => {
       configuration.getTableSelectedCount().should('eq', 1);
     });
 
-    it('should check that details table opens and tab is correct', () => {
+    it('should check that details table opens (w/o tab header)', () => {
       configuration.getStatusTables().should('be.visible');
-      configuration.getTabsCount().should('eq', 1);
-      configuration.getTabText(0).should('eq', 'Details');
+      configuration.getTabs().should('not.exist');
     });
   });
 
index 44532b6667812364703a1cc7d1e849d5fa87a006..b1a2fdfda3775c0a188dd6e8920c7bfbbb5c6e72 100644 (file)
@@ -69,12 +69,16 @@ export abstract class PageHelper {
     cy.get('.breadcrumb-item.active').should('have.text', text);
   }
 
+  getTabs() {
+    return cy.get('.nav.nav-tabs li');
+  }
+
   getTabText(index: number) {
-    return cy.get('.nav.nav-tabs li').its(index).text();
+    return this.getTabs().its(index).text();
   }
 
   getTabsCount(): any {
-    return cy.get('.nav.nav-tabs li').its('length');
+    return this.getTabs().its('length');
   }
 
   /**
index 584a09a9ad717e2933bd8578505bedae213db889..ee2f3d0421b3ea2294c8383fb511e10e6bdf3247 100644 (file)
@@ -1,7 +1,8 @@
 <ul ngbNav
     #nav="ngbNav"
-    class="nav-tabs">
-  <li ngbNavItem>
+    class="nav-tabs"
+    cdStatefulTab="image-list">
+  <li ngbNavItem="issues">
     <a ngbNavLink
        i18n>Issues</a>
     <ng-template ngbNavContent>
@@ -13,7 +14,7 @@
       </cd-table>
     </ng-template>
   </li>
-  <li ngbNavItem>
+  <li ngbNavItem="syncing">
     <a ngbNavLink
        i18n>Syncing</a>
     <ng-template ngbNavContent>
@@ -25,7 +26,7 @@
       </cd-table>
     </ng-template>
   </li>
-  <li ngbNavItem>
+  <li ngbNavItem="ready">
     <a ngbNavLink
        i18n>Ready</a>
     <ng-template ngbNavContent>
index 1288b858a72f74361ff13bb7b97d76d007bbfe08..583ba9669230e66ba518698911bc4f507005232a 100644 (file)
@@ -5,8 +5,9 @@
 <ng-container *ngIf="selection">
   <ul ngbNav
       #nav="ngbNav"
-      class="nav-tabs">
-    <li ngbNavItem>
+      class="nav-tabs"
+      cdStatefulTab="rbd-details">
+    <li ngbNavItem="details">
       <a ngbNavLink
          i18n>Details</a>
       <ng-template ngbNavContent>
         </table>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="snapshots">
       <a ngbNavLink
          i18n>Snapshots</a>
       <ng-template ngbNavContent>
                               [rbdName]="selection.name"></cd-rbd-snapshot-list>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="configuration">
       <a ngbNavLink
          i18n>Configuration</a>
       <ng-template ngbNavContent>
index cc1f8226ed732abb6a3ac9daf5619ecf74498ef9..7a222a1000e32e5fa72be822bff4e3ccbb6a4558 100644 (file)
@@ -2,8 +2,9 @@
   <ul ngbNav
       #nav="ngbNav"
       (navChange)="softRefresh()"
-      class="nav-tabs">
-    <li ngbNavItem>
+      class="nav-tabs"
+      cdStatefulTab="cephfs-tabs">
+    <li ngbNavItem="details">
       <a ngbNavLink
          i18n>Details</a>
       <ng-template ngbNavContent>
@@ -11,7 +12,7 @@
         </cd-cephfs-detail>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="clients">
       <a ngbNavLink>
         <ng-container i18n>Clients</ng-container>
         <span class="badge badge-pill badge-tab ml-1">{{ clients.data.length }}</span>
         </cd-cephfs-clients>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="directories">
       <a ngbNavLink
          i18n>Directories</a>
       <ng-template ngbNavContent>
         <cd-cephfs-directories [id]="id"></cd-cephfs-directories>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="performance-details">
       <a ngbNavLink
          i18n>Performance Details</a>
       <ng-template ngbNavContent>
index 93c375d70d61eda3942b5c66980a22b356c379f9..8debf9dc66bc5fb2e5ae487e4db04f139d6998eb 100755 (executable)
 <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>
+  <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-container>
index f94224736a9c979bc7811efdd12eefa4ec3d0962..40b05d829a4e2ae632c3015c6404a833e24f2b67 100755 (executable)
@@ -1,7 +1,5 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
-
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
 import { DataTableModule } from '../../../../shared/datatable/datatable.module';
 import { SharedModule } from '../../../../shared/shared.module';
@@ -13,7 +11,7 @@ describe('ConfigurationDetailsComponent', () => {
 
   configureTestBed({
     declarations: [ConfigurationDetailsComponent],
-    imports: [DataTableModule, SharedModule, NgbNavModule],
+    imports: [DataTableModule, SharedModule],
     providers: [i18nProviders]
   });
 
index 6c71d59b93efc159de8e7cd712106cd25dbab7d7..fa4ce2f26e8fb69066c3842eb780a3bda4b224f7 100644 (file)
@@ -1,15 +1,16 @@
 <ng-container *ngIf="selection">
   <ul ngbNav
       #nav="ngbNav"
-      class="nav-tabs">
-    <li ngbNavItem>
+      class="nav-tabs"
+      cdStatefulTab="host-details">
+    <li ngbNavItem="devices">
       <a ngbNavLink
          i18n>Devices</a>
       <ng-template ngbNavContent>
         <cd-device-list [hostname]="selection['hostname']"></cd-device-list>
       </ng-template>
     </li>
-    <li ngbNavItem
+    <li ngbNavItem="inventory"
         *ngIf="permissions.hosts.read">
       <a ngbNavLink
          i18n>Inventory</a>
@@ -17,7 +18,7 @@
         <cd-inventory [hostname]="selectedHostname"></cd-inventory>
       </ng-template>
     </li>
-    <li ngbNavItem
+    <li ngbNavItem="daemons"
         *ngIf="permissions.hosts.read">
       <a ngbNavLink
          i18n>Daemons</a>
@@ -26,7 +27,7 @@
         </cd-service-daemon-list>
       </ng-template>
     </li>
-    <li ngbNavItem
+    <li ngbNavItem="performance-details"
         *ngIf="permissions.grafana.read">
       <a ngbNavLink
          i18n>Performance Details</a>
@@ -37,7 +38,7 @@
         </cd-grafana>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="device-health">
       <a ngbNavLink
          i18n>Device health</a>
       <ng-template ngbNavContent>
index a9848cee383e95ee2883ec614c54ad04a27e1b00..f0e49476b76f5ca8f0c1b5a50e2815f722e7f84f 100644 (file)
@@ -3,8 +3,9 @@
 
   <ul ngbNav
       #nav="ngbNav"
-      class="nav-tabs">
-    <li ngbNavItem>
+      class="nav-tabs"
+      cdStatefulTab="logs">
+    <li ngbNavItem="cluster-logs">
       <a ngbNavLink
          i18n>Cluster Logs</a>
       <ng-template ngbNavContent>
@@ -23,7 +24,7 @@
         </div>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="audit-logs">
       <a ngbNavLink
          i18n>Audit Logs</a>
       <ng-template ngbNavContent>
index 861575d0a14a9e13cdf96ed7c96aba8d3b19de3d..29cae36bafff141dba08fd4b791edc77b83f7c0c 100644 (file)
@@ -1,16 +1,4 @@
 <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>
+  <cd-table-key-value [data]="module_config">
+  </cd-table-key-value>
 </ng-container>
index d28861eeedf9c5af76046220402bcd24481b85da..f33e17cd11d43370bce36273d0e203de95b0544d 100644 (file)
@@ -1,8 +1,6 @@
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
-
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
 import { SharedModule } from '../../../../shared/shared.module';
 import { MgrModuleDetailsComponent } from './mgr-module-details.component';
@@ -13,7 +11,7 @@ describe('MgrModuleDetailsComponent', () => {
 
   configureTestBed({
     declarations: [MgrModuleDetailsComponent],
-    imports: [HttpClientTestingModule, SharedModule, NgbNavModule],
+    imports: [HttpClientTestingModule, SharedModule],
     providers: [i18nProviders]
   });
 
index 68225e2e46746258e5b8f5db6878a335aa6746b0..bca3f7407156d7f60c1a7bbfc44a2480ca83948d 100644 (file)
@@ -2,15 +2,16 @@
   <ul ngbNav
       #nav="ngbNav"
       id="tabset-osd-details"
-      class="nav-tabs">
-    <li ngbNavItem>
+      class="nav-tabs"
+      cdStatefulTab="osd-details">
+    <li ngbNavItem="devices">
       <a ngbNavLink
          i18n>Devices</a>
       <ng-template ngbNavContent>
         <cd-device-list [osdId]="osd?.id"></cd-device-list>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="attributes">
       <a ngbNavLink
          i18n>Attributes (OSD map)</a>
       <ng-template ngbNavContent>
@@ -18,7 +19,7 @@
         </cd-table-key-value>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="metadata">
       <a ngbNavLink
          i18n>Metadata</a>
       <ng-template ngbNavContent>
         </ng-template>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="device-health">
       <a ngbNavLink
          i18n>Device health</a>
       <ng-template ngbNavContent>
         <cd-smart-list [osdId]="osd?.id"></cd-smart-list>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="performance-counter">
       <a ngbNavLink
          i18n>Performance counter</a>
       <ng-template ngbNavContent>
@@ -49,7 +50,7 @@
         </cd-table-performance-counter>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="histogram">
       <a ngbNavLink
          i18n>Histogram</a>
       <ng-template ngbNavContent>
@@ -72,7 +73,7 @@
         </div>
       </ng-template>
     </li>
-    <li ngbNavItem
+    <li ngbNavItem="performance-details"
         *ngIf="grafanaPermission.read">
       <a ngbNavLink
          i18n>Performance Details</a>
index 46d3affd0d0098c46e426bad19701f5b00126152..704f0f98e0e92649a927049b83fbd219eaec0a8c 100644 (file)
@@ -1,16 +1,4 @@
 <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>
+  <cd-service-daemon-list [serviceName]="selection['service_name']">
+  </cd-service-daemon-list>
 </ng-container>
index dc2ec0b66c295a4a54ed6a748c51abe1c25c4948..1faa2bed8451e04bf699218e6d243944e332ef5a 100644 (file)
@@ -4,11 +4,7 @@ import { RouterTestingModule } from '@angular/router/testing';
 
 import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
-import {
-  configureTestBed,
-  i18nProviders,
-  TabHelper
-} from '../../../../../testing/unit-test-helper';
+import { configureTestBed, i18nProviders } 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';
@@ -43,21 +39,4 @@ describe('ServiceDetailsComponent', () => {
     fixture.detectChanges();
     expect(component).toBeTruthy();
   });
-
-  describe('Service details tabset', () => {
-    beforeEach(() => {
-      component.selection.selected = [{ serviceName: 'osd' }];
-      fixture.detectChanges();
-    });
-
-    it('should have a NgbNav', () => {
-      const ngbNav = TabHelper.getNgbNav(fixture);
-      expect(ngbNav).toBeDefined();
-    });
-
-    it('should show tabs', () => {
-      const ngbNavItems = TabHelper.getTextContents(fixture);
-      expect(ngbNavItems).toEqual(['Daemons']);
-    });
-  });
 });
index 4fc2aa2c9cd78f40a6260303da0a2e415fcb299f..2a8b9453f3fa13e2ebfc5a1e3d309e7321479cfe 100644 (file)
@@ -1,8 +1,9 @@
 <ng-container *ngIf="selection">
   <ul ngbNav
       #nav="ngbNav"
-      class="nav-tabs">
-    <li ngbNavItem>
+      class="nav-tabs"
+      cdStatefulTab="nfs-details">
+    <li ngbNavItem="details">
       <a ngbNavLink
          i18n>Details</a>
       <ng-template ngbNavContent>
@@ -10,7 +11,7 @@
         </cd-table-key-value>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="clients">
       <a ngbNavLink
          i18n>Clients ({{ clients.length }})</a>
       <ng-template ngbNavContent>
index c5a8a0b1a40d10beb99c3e2ec5f4c6cf997b7feb..7492ddde6079cfbca2fb1385b4f8fd1a87115457 100644 (file)
@@ -2,8 +2,9 @@
               cdTableDetail>
   <ul ngbNav
       #nav="ngbNav"
-      class="nav-tabs">
-    <li ngbNavItem>
+      class="nav-tabs"
+      cdStatefulTab="pool-details">
+    <li ngbNavItem="details">
       <a ngbNavLink
          i18n>Details</a>
       <ng-template ngbNavContent>
@@ -13,7 +14,7 @@
         </cd-table-key-value>
       </ng-template>
     </li>
-    <li ngbNavItem
+    <li ngbNavItem="performance-details"
         *ngIf="permissions.grafana.read">
       <a ngbNavLink
          i18n>Performance Details</a>
@@ -24,7 +25,7 @@
         </cd-grafana>
       </ng-template>
     </li>
-    <li ngbNavItem
+    <li ngbNavItem="configuration"
         *ngIf="selection.type === 'replicated'">
       <a ngbNavLink
          i18n>Configuration</a>
@@ -32,7 +33,7 @@
         <cd-rbd-configuration-table [data]="selectedPoolConfiguration"></cd-rbd-configuration-table>
       </ng-template>
     </li>
-    <li ngbNavItem
+    <li ngbNavItem="cache-tiers-details"
         *ngIf="selection['tiers']?.length > 0">
       <a ngbNavLink
          i18n>Cache Tiers Details</a>
index 1bb5bca2c61754c8e94e3966f442a54096313b80..310de113c4aac67fb0f8d3346dc6d10ebe901f67 100644 (file)
 <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.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>
+  <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>
-          <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>
+  <!-- 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>
+  <!-- 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-container>
index 3fa79f21050ce337d2b1134b4a4340027d8c27e8..68799f5ee36218d7777497acf10b0d5fd81301b3 100644 (file)
@@ -1,7 +1,5 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
-
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
 import { CdTableSelection } from '../../../shared/models/cd-table-selection';
 import { SharedModule } from '../../../shared/shared.module';
@@ -13,7 +11,7 @@ describe('RgwBucketDetailsComponent', () => {
 
   configureTestBed({
     declarations: [RgwBucketDetailsComponent],
-    imports: [SharedModule, NgbNavModule],
+    imports: [SharedModule],
     providers: [i18nProviders]
   });
 
index 77292949b2ab5998e626b5ac1315af813f5f72d5..873200d71b9f64c4d0227993c81133a58dc96b26 100644 (file)
@@ -1,8 +1,9 @@
 <ng-container *ngIf="selection">
   <ul ngbNav
       #nav="ngbNav"
-      class="nav-tabs">
-    <li ngbNavItem>
+      class="nav-tabs"
+      cdStatefulTab="rgw-daemon-details">
+    <li ngbNavItem="details">
       <a ngbNavLink
          i18n>Details</a>
       <ng-template ngbNavContent>
@@ -11,7 +12,7 @@
         </cd-table-key-value>
       </ng-template>
     </li>
-    <li ngbNavItem>
+    <li ngbNavItem="performance-counters">
       <a ngbNavLink
          i18n>Performance Counters</a>
       <ng-template ngbNavContent>
@@ -20,7 +21,7 @@
         </cd-table-performance-counter>
       </ng-template>
     </li>
-    <li ngbNavItem
+    <li ngbNavItem="performance-details"
         *ngIf="grafanaPermission.read">
       <a ngbNavLink
          i18n>Performance Details</a>
index c081756061a35dea91b3cd3c4112e4013522c76c..14c9fc2fee35992f29efb5bcea7f62489d47e626 100644 (file)
@@ -1,8 +1,9 @@
 <ng-container *ngIf="selection">
   <ul ngbNav
       #nav="ngbNav"
-      class="nav-tabs">
-    <li ngbNavItem>
+      class="nav-tabs"
+      cdStatefulTab="rgw-user-details">
+    <li ngbNavItem="details">
       <a ngbNavLink
          i18n>Details</a>
       <ng-template ngbNavContent>
         </div>
       </ng-template>
     </li>
-    <li ngbNavItem
+    <li ngbNavItem="keys"
         *ngIf="keys.length">
       <a ngbNavLink
          i18n>Keys</a>
index 0bb532ddfa191ba95d49b47a21c764c0445d2ee1..ca4b6781bbf58169781fbd30fed90ffae607e916 100644 (file)
@@ -1,23 +1,11 @@
 <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>
+  <cd-table [data]="scopes_permissions"
+            [columns]="columns"
+            columnMode="flex"
+            [toolHeader]="false"
+            [autoReload]="false"
+            [autoSave]="false"
+            [footer]="false"
+            [limit]="0">
+  </cd-table>
 </ng-container>
index a72c6f6c68c9cb63932e6f299ae9db563963ee8e..3b011043c7379e26b01b826309bc13ac069b5423 100644 (file)
@@ -8,6 +8,7 @@ import { FormLoadingDirective } from './form-loading.directive';
 import { IopsDirective } from './iops.directive';
 import { MillisecondsDirective } from './milliseconds.directive';
 import { PasswordButtonDirective } from './password-button.directive';
+import { StatefulTabDirective } from './stateful-tab.directive';
 import { TrimDirective } from './trim.directive';
 
 @NgModule({
@@ -21,7 +22,8 @@ import { TrimDirective } from './trim.directive';
     TrimDirective,
     MillisecondsDirective,
     IopsDirective,
-    FormLoadingDirective
+    FormLoadingDirective,
+    StatefulTabDirective
   ],
   exports: [
     AutofocusDirective,
@@ -32,7 +34,8 @@ import { TrimDirective } from './trim.directive';
     TrimDirective,
     MillisecondsDirective,
     IopsDirective,
-    FormLoadingDirective
+    FormLoadingDirective,
+    StatefulTabDirective
   ]
 })
 export class DirectivesModule {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/stateful-tab.directive.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/stateful-tab.directive.spec.ts
new file mode 100644 (file)
index 0000000..70ecb1a
--- /dev/null
@@ -0,0 +1,29 @@
+import { NgbNav, NgbNavChangeEvent, NgbNavConfig } from '@ng-bootstrap/ng-bootstrap';
+
+import { StatefulTabDirective } from './stateful-tab.directive';
+
+describe('StatefulTabDirective', () => {
+  it('should create an instance', () => {
+    const directive = new StatefulTabDirective(null);
+    expect(directive).toBeTruthy();
+  });
+
+  it('should get and select active tab', () => {
+    const nav = new NgbNav('tablist', new NgbNavConfig(), <any>null, null);
+    spyOn(nav, 'select');
+    spyOn(window.localStorage, 'getItem').and.returnValue('foo');
+    const directive = new StatefulTabDirective(nav);
+    directive.ngOnInit();
+    expect(window.localStorage.getItem).toHaveBeenCalled();
+    expect(nav.select).toHaveBeenCalledWith('foo');
+  });
+
+  it('should store active tab', () => {
+    spyOn(window.localStorage, 'setItem');
+    const directive = new StatefulTabDirective(null);
+    directive.cdStatefulTab = 'bar';
+    const event: NgbNavChangeEvent<string> = { activeId: '', nextId: 'xyz', preventDefault: null };
+    directive.onNavChange(event);
+    expect(window.localStorage.setItem).toHaveBeenCalledWith('tabset_bar', 'xyz');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/stateful-tab.directive.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/directives/stateful-tab.directive.ts
new file mode 100644 (file)
index 0000000..cf6f27e
--- /dev/null
@@ -0,0 +1,31 @@
+import { Directive, Host, HostListener, Input, OnInit, Optional } from '@angular/core';
+
+import { NgbNav, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap';
+
+@Directive({
+  selector: '[cdStatefulTab]'
+})
+export class StatefulTabDirective implements OnInit {
+  @Input()
+  cdStatefulTab: string;
+
+  private localStorage = window.localStorage;
+
+  constructor(@Optional() @Host() private nav: NgbNav) {}
+
+  ngOnInit() {
+    // Is an activate tab identifier stored in the local storage?
+    const activeId = this.localStorage.getItem(`tabset_${this.cdStatefulTab}`);
+    if (activeId) {
+      this.nav.select(activeId);
+    }
+  }
+
+  @HostListener('navChange', ['$event'])
+  onNavChange(event: NgbNavChangeEvent) {
+    // Store the current active tab identifier in the local storage.
+    if (this.cdStatefulTab && event.nextId) {
+      this.localStorage.setItem(`tabset_${this.cdStatefulTab}`, event.nextId);
+    }
+  }
+}