]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: carbon datatable minor bug fixes
authorNizamudeen A <nia@redhat.com>
Thu, 29 Aug 2024 06:16:58 +0000 (11:46 +0530)
committerNizamudeen A <nia@redhat.com>
Mon, 2 Sep 2024 08:42:10 +0000 (14:12 +0530)
- fixes empty white spaces in the table
- fixes some tables detail component where it didn't had carbon table
- fix the multi-site details view table not showing up
- fix the nvmeof modals like namespace deletion not getting opened
- fix the cached reload button status not showing correctly in rgw table
- fix show button in the rgw users detail page broken
- fix the multi-cluster list component issue where details component is
  not following carbon attributes
- fixing the configuration page where it does a page reload on form
  submit. instead doing a table refresh

- additionally fixed a validation issue in the service form

fixes the test failures

Fixes: https://tracker.ceph.com/issues/67788
Fixes: https://tracker.ceph.com/issues/67552
Signed-off-by: Nizamudeen A <nia@redhat.com>
35 files changed:
src/pybind/mgr/dashboard/frontend/cypress/e2e/block/images.po.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.po.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/multi-cluster/multi-cluster.e2e-spec.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/multi-cluster/multi-cluster.po.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/page-helper.po.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/buckets.po.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-initiators-list/nvmeof-initiators-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.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-list/rbd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.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/create-cluster/create-cluster-review.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.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-config-modal/rgw-config-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-configuration-page/rgw-configuration-page.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-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/ceph/rgw/rgw-user-details/rgw-user-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/classes/list-with-details.class.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/classes/table-status-view-cache.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/classes/table-status-view-cache.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/classes/table-status.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/classes/table-status.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/crud-table/crud-table.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.scss
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table-actions/table-actions.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html

index 2e2a9d545d256b151c30b3981cabd8f91738345c..7bac7d12bed9f6ff5d60c42a9b189007509b5d62 100644 (file)
@@ -38,7 +38,7 @@ export class ImagesPageHelper extends PageHelper {
     cy.get('[data-cy=submitBtn]').click();
 
     this.getExpandCollapseElement(newName).click();
-    cy.get('.table.table-striped.table-bordered').contains('td', newSize);
+    cy.get('[data-testid=rbd-details-table]').contains('td', newSize);
   }
 
   // Selects RBD image and moves it to the trash,
index e55e9aa588d6ea7589f6e3fd8f839a4aaab0ecf3..82e79a676ec60e052d0a11b95e6109d7a04bd04f 100644 (file)
@@ -75,7 +75,7 @@ export class ConfigurationPageHelper extends PageHelper {
     values.forEach((value) => {
       // iterates through list of values and
       // checks if the value appears in details with the correct number attatched
-      cy.contains('.table.table-striped.table-bordered', `${value[0]}\: ${value[1]}`);
+      cy.contains('[data-testid=config-details-table]', `${value[0]}\: ${value[1]}`);
     });
   }
 }
index 74f26df8e564566c21b1d4c7b56dd328b529202c..8d7bbd3bbe3cfe6779a4d33eded32ef7100d6c12 100644 (file)
@@ -21,9 +21,11 @@ describe('Muti-cluster management page', () => {
   it('should authenticate the second cluster', () => {
     multiCluster.auth(url, alias, username, password);
     multiCluster.existTableCell(alias);
+    multiCluster.checkConnectionStatus(alias, 'CONNECTED');
   });
 
   it('should switch to the second cluster and back to hub', () => {
+    multiCluster.checkConnectionStatus(alias, 'CONNECTED');
     dashboard.navigateTo();
     cy.get('[data-testid="selected-cluster"]').click();
     cy.get('[data-testid="select-a-cluster"]').contains(alias).click();
@@ -38,16 +40,19 @@ describe('Muti-cluster management page', () => {
   });
 
   it('should reconnect the second cluster', () => {
+    multiCluster.checkConnectionStatus(alias, 'CONNECTED');
     multiCluster.reconnect(alias, password);
     multiCluster.existTableCell(alias);
   });
 
   it('should edit the second cluster', () => {
+    multiCluster.checkConnectionStatus(alias, 'CONNECTED');
     multiCluster.edit(alias, editedAlias);
     multiCluster.existTableCell(editedAlias);
   });
 
   it('should disconnect the second cluster', () => {
+    multiCluster.checkConnectionStatus(editedAlias, 'CONNECTED');
     multiCluster.disconnect(editedAlias);
     multiCluster.existTableCell(editedAlias, false);
   });
index b7e109fbbf443d10573cdd4ad9f96101f645a3cc..5c683a947a696c04829976fde735f12c1b57a2b4 100644 (file)
@@ -10,6 +10,11 @@ const WAIT_TIMER = 1000;
 export class MultiClusterPageHelper extends PageHelper {
   pages = pages;
 
+  columnIndex = {
+    alias: 2,
+    connection: 3
+  };
+
   auth(url: string, alias: string, username: string, password: string) {
     cy.contains('button', 'Connect').click();
     cy.get('cd-multi-cluster-form').should('exist');
@@ -49,4 +54,20 @@ export class MultiClusterPageHelper extends PageHelper {
     });
     cy.wait(WAIT_TIMER);
   }
+
+  checkConnectionStatus(alias: string, expectedStatus = 'CONNECTED', shouldReload = true) {
+    let aliasIndex = this.columnIndex.alias;
+    let statusIndex = this.columnIndex.connection;
+    if (shouldReload) {
+      cy.reload(true, { log: true, timeout: 5 * 1000 });
+    }
+
+    this.getTableCell(aliasIndex, alias)
+      .parent()
+      .find(`[cdstabledata]:nth-child(${statusIndex}) .badge`)
+      .should(($ele) => {
+        const status = $ele.toArray().map((v) => v.innerText);
+        expect(status).to.include(expectedStatus);
+      });
+  }
 }
index af9f0d3e20946613860f79f0a0c334ec9045dea1..78c360f974d629995186ac1df976f337fdff52b4 100644 (file)
@@ -251,7 +251,9 @@ export abstract class PageHelper {
    * Grabs striped tables
    */
   getStatusTables() {
-    return cy.get('.table.table-striped');
+    return cy.get(
+      '.cds--data-table--sort.cds--data-table--no-border.cds--data-table.cds--data-table--md'
+    );
   }
 
   filterTable(name: string, option: string) {
index 28c30e2f02f557e4ff6c431acd8d0021310f4782..9a44051f7b80b28faf2acce0a6e32ef65a4bca7d 100644 (file)
@@ -76,7 +76,7 @@ export class BucketsPageHelper extends PageHelper {
       this.getExpandCollapseElement(name).click();
 
       // check its details table for edited owner field
-      cy.get('.table.table-striped.table-bordered').first().as('bucketDataTable');
+      cy.get('[data-testid="rgw-bucket-details"]').first().as('bucketDataTable');
 
       // Check versioning enabled:
       cy.get('@bucketDataTable').find('tr').its(0).find('td').last().as('versioningValueCell');
@@ -102,7 +102,7 @@ export class BucketsPageHelper extends PageHelper {
     this.getExpandCollapseElement(name).click();
 
     // Check versioning enabled:
-    cy.get('.table.table-striped.table-bordered').first().as('bucketDataTable');
+    cy.get('[data-testid="rgw-bucket-details"]').first().as('bucketDataTable');
     cy.get('@bucketDataTable').find('tr').its(0).find('td').last().as('versioningValueCell');
 
     cy.get('@versioningValueCell').should('have.text', this.versioningStateEnabled);
index 2491ccc0cb898a44ccddf194f6b658d577226481..fff38e6985a433577f69afaeaf7cc2174a2642b5 100644 (file)
@@ -10,7 +10,7 @@ import { FinishedTask } from '~/app/shared/models/finished-task';
 import { NvmeofSubsystemInitiator } from '~/app/shared/models/nvmeof';
 import { Permission } from '~/app/shared/models/permissions';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
-import { ModalService } from '~/app/shared/services/modal.service';
+import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 
 const BASE_URL = 'block/nvmeof/subsystems';
@@ -37,7 +37,7 @@ export class NvmeofInitiatorsListComponent implements OnInit, OnChanges {
     public actionLabels: ActionLabelsI18n,
     private authStorageService: AuthStorageService,
     private nvmeofService: NvmeofService,
-    private modalService: ModalService,
+    private modalService: ModalCdsService,
     private router: Router,
     private taskWrapper: TaskWrapperService
   ) {
index 26b48d8aad9bf96bedb5aa82777d1f2e881b5a79..3ece51f350d48fe84a188fa869e32863d7a940c6 100644 (file)
@@ -11,7 +11,7 @@ import { FinishedTask } from '~/app/shared/models/finished-task';
 import { NvmeofListener } from '~/app/shared/models/nvmeof';
 import { Permission } from '~/app/shared/models/permissions';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
-import { ModalService } from '~/app/shared/services/modal.service';
+import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 
 const BASE_URL = 'block/nvmeof/subsystems';
@@ -33,7 +33,7 @@ export class NvmeofListenersListComponent implements OnInit, OnChanges {
 
   constructor(
     public actionLabels: ActionLabelsI18n,
-    private modalService: ModalService,
+    private modalService: ModalCdsService,
     private authStorageService: AuthStorageService,
     private taskWrapper: TaskWrapperService,
     private nvmeofService: NvmeofService,
index cbf963995dd956d1a17b8ee89f5e503a7e321c50..c40b538c8208814525ac8016da281e3400f8a914 100644 (file)
@@ -13,7 +13,7 @@ import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
 import { IopsPipe } from '~/app/shared/pipes/iops.pipe';
 import { MbpersecondPipe } from '~/app/shared/pipes/mbpersecond.pipe';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
-import { ModalService } from '~/app/shared/services/modal.service';
+import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 
 const BASE_URL = 'block/nvmeof/subsystems';
@@ -36,7 +36,7 @@ export class NvmeofNamespacesListComponent implements OnInit, OnChanges {
   constructor(
     public actionLabels: ActionLabelsI18n,
     private router: Router,
-    private modalService: ModalService,
+    private modalService: ModalCdsService,
     private authStorageService: AuthStorageService,
     private taskWrapper: TaskWrapperService,
     private nvmeofService: NvmeofService,
index 7c175d0713e987a1e3c55f90e547dd322eb13dbf..fc2fbdff63e32b40f48ccf094b986bb887547216 100644 (file)
       <a ngbNavLink
          i18n>Details</a>
       <ng-template ngbNavContent>
-        <table class="table table-striped table-bordered">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rbd-details-table">
           <tbody>
-            <tr>
+            <tr cdstablerow>
               <td i18n
-                  class="bold w-25">Name</td>
+                  class="bold w-25"
+                  cdstabledata>Name</td>
               <td class="w-75">{{ selection.name }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
-                  class="bold">Pool</td>
+                  class="bold"
+                  cdstabledata>Pool</td>
               <td>{{ selection.pool_name }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Data Pool</td>
               <td>{{ selection.data_pool | empty }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Created</td>
               <td>{{ selection.timestamp | cdDate }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Size</td>
               <td>{{ selection.size | dimlessBinary }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Objects</td>
               <td>{{ selection.num_objs | dimless }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Object size</td>
               <td>{{ selection.obj_size | dimlessBinary }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Features</td>
               <td>
@@ -57,7 +60,7 @@
                 </span>
               </td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Provisioned</td>
               <td>
@@ -72,7 +75,7 @@
                 </span>
               </td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Total provisioned</td>
               <td>
                 </span>
               </td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Striping unit</td>
               <td>{{ selection.stripe_unit | dimlessBinary }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Striping count</td>
               <td>{{ selection.stripe_count }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Parent</td>
               <td>
                 <span *ngIf="!selection.parent">-</span>
               </td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Block name prefix</td>
               <td>{{ selection.block_name_prefix }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Order</td>
               <td>{{ selection.order }}</td>
             </tr>
-            <tr>
+            <tr cdstablerow>
               <td i18n
                   class="bold">Format Version</td>
               <td>{{ selection.image_format }}</td>
index d72d2ad8e8c26a27ea109890d3556709b86d7760..8f15dfddf1e21a1596953695f36f0aa57a90bb00 100644 (file)
 <ng-template #mirroringTpl
              let-value="data.value"
              let-row="data.row">
-  <span *ngIf="value.length === 3; else probb"
+  <span *ngIf="value?.length === 3; else probb"
         class="badge badge-info">{{ value[0] }}</span>&nbsp;
-  <span *ngIf="value.length === 3"
+  <span *ngIf="value?.length === 3"
         class="badge badge-info">{{ value[1] }}</span>&nbsp;
-  <span *ngIf="row.primary === true"
+  <span *ngIf="row?.primary === true"
         class="badge badge-info"
         i18n>primary</span>
-  <span *ngIf="row.primary === false"
+  <span *ngIf="row?.primary === false"
         class="badge badge-info"
         i18n>secondary</span>
   <ng-template #probb>
index 86d0978c1631eec5ae49341541befe1b53c48538..1a4bb4e0cf86a42b78a9a83daf646b1b097b1713 100644 (file)
@@ -77,7 +77,7 @@ export class RbdListComponent extends ListWithDetails implements OnInit {
   images: any;
   columns: CdTableColumn[];
   retries: number;
-  tableStatus = new TableStatus('light');
+  tableStatus = new TableStatus('ghost');
   selection = new CdTableSelection();
   icons = Icons;
   count = 0;
index 13bb16c9cead6a9a3dcb1ac61e4af615273bf618..8bcb15fb1d9a3b745a73ab43a547f9b044cc1f1b 100755 (executable)
@@ -1,5 +1,6 @@
 <ng-container *ngIf="selection">
-  <table class="table table-striped table-bordered">
+  <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+         data-testid="config-details-table">
     <tbody>
       <tr>
         <td i18n
index df61fd40a9536ef74ff4a6addb61855b209f5f0e..a87f4011b28d7ad8d9d8202782263b7427a98f90 100644 (file)
@@ -3,7 +3,7 @@
     <fieldset>
       <legend class="cd-header"
               i18n>Cluster Resources</legend>
-      <table class="table table-striped">
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
         <tr>
           <td i18n
               class="bold">Hosts</td>
index c9dbf9cc599122fa7bf0cb42541f78ba1c50a6e5..9a92cfe04eeb7e3be43cab97f144a9ea767acc53 100644 (file)
@@ -3,7 +3,7 @@
     <fieldset>
       <legend class="cd-header"
               i18n>Status</legend>
-      <table class="table table-striped"
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
              *ngIf="mon_status">
         <tbody>
           <tr>
index 6ee60716bd88e809701f829ce2d2b36e79fa6b9a..a9961f72ff67c38184591b3f917fca8a1b8a5cba 100644 (file)
                     (setExpandedRow)="setExpandedRow($event)"
                     [maxLimit]="25"
                     (updateSelection)="updateSelection($event)">
-            <div class="table-actions">
-              <cd-table-actions [permission]="permissions.user"
-                                [selection]="selection"
-                                class="btn-group"
-                                id="cluster-actions"
-                                [tableActions]="tableActions">
-              </cd-table-actions>
-            </div>
-            <cd-multi-cluster-details cdTableDetail
+            <cd-table-actions [permission]="permissions.user"
+                              [selection]="selection"
+                              class="table-actions"
+                              id="cluster-actions"
+                              [tableActions]="tableActions">
+            </cd-table-actions>
+            <cd-multi-cluster-details *cdTableDetail
                                       [permissions]="permissions"
                                       [selection]="expandedRow">
             </cd-multi-cluster-details>
index a37cca340142b806733945d9e981483a1a728843..9602c856aed81af9897ff5c0aaff567cd5f943ff 100644 (file)
@@ -195,7 +195,12 @@ export class ServiceFormComponent extends CdForm implements OnInit {
           })
         ]
       ],
-      group: [null, Validators.required],
+      group: [
+        null,
+        CdValidators.requiredIf({
+          service_type: 'nvmeof'
+        })
+      ],
       // RGW
       rgw_frontend_port: [null, [CdValidators.number(false)]],
       realm_name: [null],
index 74b3e953b52d796c66adeeb8cecb06f76f725635..31c54e59ebff97a0b6062bb9e8253e2c30044111 100644 (file)
@@ -8,7 +8,8 @@
          i18n>Details</a>
       <ng-template ngbNavContent>
 
-        <table class="table table-striped table-bordered">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-bucket-details">
           <tbody>
             <tr>
               <td i18n
@@ -51,7 +52,8 @@
         <!-- Bucket quota -->
         <div>
           <legend i18n>Bucket quota</legend>
-          <table class="table table-striped table-bordered">
+          <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+                 data-testid="rgw-bucket-quota-details">
             <tbody>
               <tr>
                 <td i18n
@@ -84,7 +86,8 @@
 
         <!-- Locking -->
         <legend i18n>Locking</legend>
-        <table class="table table-striped table-bordered">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-bucket-locking-details">
           <tbody>
             <tr>
               <td i18n
       <!-- Tags -->
       <ng-container *ngIf="(selection.tagset | keyvalue)?.length">
         <legend i18n>Tags</legend>
-        <table class="table table-striped table-bordered">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
           <tbody>
             <tr *ngFor="let tag of selection.tagset | keyvalue">
               <td i18n
          i18n>Policies</a>
       <ng-template ngbNavContent>
         <div class="table-scroller">
-          <table class="table table-striped table-bordered">
+          <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
             <tbody>
               <tr>
                 <td i18n
index 892916e86b5ead0e75193ceec03243c5f3708c76..d6bafb3ca022e968c63c150a6d09c3b9201b06d7 100644 (file)
@@ -1,6 +1,5 @@
 import { Component, EventEmitter, OnInit, Output } from '@angular/core';
 import { AbstractControl, Validators } from '@angular/forms';
-import { Router } from '@angular/router';
 
 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 import _ from 'lodash';
@@ -13,6 +12,7 @@ import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { CdValidators } from '~/app/shared/forms/cd-validators';
 import { NotificationService } from '~/app/shared/services/notification.service';
 import { rgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
 
 @Component({
   selector: 'cd-rgw-config-modal',
@@ -35,11 +35,11 @@ export class RgwConfigModalComponent implements OnInit {
   allEncryptionConfigValues: any = [];
   editing = false;
   action: string;
+  table: TableComponent;
 
   constructor(
     private formBuilder: CdFormBuilder,
     public activeModal: NgbActiveModal,
-    private router: Router,
     public actionLabels: ActionLabelsI18n,
     private rgwBucketService: RgwBucketService,
     private notificationService: NotificationService
@@ -180,9 +180,7 @@ export class RgwConfigModalComponent implements OnInit {
         },
         complete: () => {
           this.activeModal.close();
-          this.router.routeReuseStrategy.shouldReuseRoute = () => false;
-          this.router.onSameUrlNavigation = 'reload';
-          this.router.navigate([this.router.url]);
+          this.table?.refreshBtn();
         }
       });
   }
index 1a740f3461cf4fa13e90eb431d61449f6454c38b..a6b3e3287c5191f7367cad23477bfb74ba614cfc 100644 (file)
@@ -8,6 +8,7 @@
       <cd-table #table
                 [data]="encryptionConfigValues"
                 [columns]="columns"
+                (fetchData)="fetchData()"
                 identifier="unique_id"
                 [forceIdentifier]="true"
                 [hasDetails]="true"
index 12e1a365200d4cb63181de368e17c96afabcec99..fabf8f6697db5fc9e139dbab71d40a87000c0f39 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, EventEmitter, OnInit, Output } from '@angular/core';
+import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
 
 import { NgbActiveModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
 import _ from 'lodash';
@@ -17,6 +17,7 @@ import { Icons } from '~/app/shared/enum/icons.enum';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { RgwConfigModalComponent } from '../rgw-config-modal/rgw-config-modal.component';
 import { rgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
 
 @Component({
   selector: 'cd-rgw-configuration-page',
@@ -25,6 +26,8 @@ import { rgwBucketEncryptionModel } from '../models/rgw-bucket-encryption';
 })
 export class RgwConfigurationPageComponent extends ListWithDetails implements OnInit {
   readonly vaultAddress = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{4}$/;
+  @ViewChild(TableComponent)
+  table: TableComponent;
 
   kmsProviders: string[];
 
@@ -91,20 +94,6 @@ export class RgwConfigurationPageComponent extends ListWithDetails implements On
       }
     ];
 
-    this.rgwBucketService.getEncryptionConfig().subscribe((data: any) => {
-      this.allEncryptionValues = data;
-      const allowedBackends = rgwBucketEncryptionModel.kmsProviders;
-
-      const kmsBackends = this.getBackend(data, 'SSE_KMS');
-      const s3Backends = this.getBackend(data, 'SSE_S3');
-
-      const allKmsBackendsPresent = this.areAllAllowedBackendsPresent(allowedBackends, kmsBackends);
-      const allS3BackendsPresent = this.areAllAllowedBackendsPresent(allowedBackends, s3Backends);
-
-      this.disableCreate = allKmsBackendsPresent && allS3BackendsPresent;
-      this.encryptionConfigValues = Object.values(data).flat();
-    });
-
     this.excludeProps = this.columns.map((column) => column.prop);
     this.excludeProps.push('unique_id');
   }
@@ -122,7 +111,8 @@ export class RgwConfigurationPageComponent extends ListWithDetails implements On
       const initialState = {
         action: 'edit',
         editing: true,
-        selectedEncryptionConfigValues: this.selection.first()
+        selectedEncryptionConfigValues: this.selection.first(),
+        table: this.table
       };
       this.bsModalRef = this.modalService.show(RgwConfigModalComponent, initialState, {
         size: 'lg'
@@ -145,4 +135,20 @@ export class RgwConfigurationPageComponent extends ListWithDetails implements On
   setExpandedRow(expandedRow: any) {
     super.setExpandedRow(expandedRow);
   }
+
+  fetchData() {
+    this.rgwBucketService.getEncryptionConfig().subscribe((data: any) => {
+      this.allEncryptionValues = data;
+      const allowedBackends = rgwBucketEncryptionModel.kmsProviders;
+
+      const kmsBackends = this.getBackend(data, 'SSE_KMS');
+      const s3Backends = this.getBackend(data, 'SSE_S3');
+
+      const allKmsBackendsPresent = this.areAllAllowedBackendsPresent(allowedBackends, kmsBackends);
+      const allS3BackendsPresent = this.areAllAllowedBackendsPresent(allowedBackends, s3Backends);
+
+      this.disableCreate = allKmsBackendsPresent && allS3BackendsPresent;
+      this.encryptionConfigValues = Object.values(data).flat();
+    });
+  }
 }
index d0b2c2105d1ab50a2e54994c024e120f4dfd8c8d..9476368f3e78f0384fd4e2b671f424667728959f 100644 (file)
@@ -21,7 +21,8 @@
     <cd-table-actions class="btn-group mb-4 me-2"
                       [permission]="permission"
                       [selection]="selection"
-                      [tableActions]="createTableActions">
+                      [tableActions]="createTableActions"
+                      [primaryDropDown]="true">
     </cd-table-actions>
   </span>
   <ng-template #migrateAndReplicationActionTpl>
          *ngIf="metadata">
       <legend>{{ metadataTitle }}</legend>
       <div>
-        <cd-table-key-value *cdTableDetail
-                            [data]="metadata">
+        <cd-table-key-value [data]="metadata">
         </cd-table-key-value>
       </div>
     </div>
index 49aff08e387576053ae6538a98a275b052ae638a..a66b6354db09d897416de541d77d04a6436ee12d 100644 (file)
@@ -2,31 +2,21 @@
   <div *ngIf="user">
     <div *ngIf="keys.length">
       <legend i18n>Keys</legend>
-        <div>
-          <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-accent"
-                      [disabled]="!keysSelection.hasSingleSelection"
-                      (click)="showKeyModal()">
-                <i [ngClass]="[icons.show]"></i>
-                <ng-container i18n>Show</ng-container>
-              </button>
-            </div>
-          </div>
-        </cd-table>
-      </div>
+      <cd-table [data]="keys"
+                [columns]="keysColumns"
+                columnMode="flex"
+                selectionType="single"
+                forceIdentifier="true"
+                (updateSelection)="updateKeysSelection($event)">
+        <cd-table-actions class="table-actions"
+                          [permission]="{read: true}"
+                          [selection]="selection"
+                          [tableActions]="tableAction"></cd-table-actions>
+      </cd-table>
     </div>
 
     <legend i18n>Details</legend>
-    <table class="table table-striped table-bordered">
+    <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
       <tbody>
         <tr>
           <td i18n
@@ -97,7 +87,7 @@
     <!-- User quota -->
     <div *ngIf="user.user_quota">
       <legend i18n>User quota</legend>
-      <table class="table table-striped table-bordered">
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
         <tbody>
           <tr>
             <td i18n
     <!-- Bucket quota -->
     <div *ngIf="user.bucket_quota">
       <legend i18n>Bucket quota</legend>
-      <table class="table table-striped table-bordered">
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
         <tbody>
           <tr>
             <td i18n
index 27bb6cc46ef02caa9a1fba2b34e02d66f7ca0d98..f2fc7f4988028b36485cef9c9b1f35823ad406bb 100644 (file)
@@ -35,7 +35,7 @@ describe('RgwUserDetailsComponent', () => {
     fixture.detectChanges();
 
     const detailsTab = fixture.debugElement.nativeElement.querySelectorAll(
-      '.table.table-striped.table-bordered tr td'
+      '.cds--data-table--sort.cds--data-table--no-border tr td'
     );
     expect(detailsTab[10].textContent).toEqual('System user');
     expect(detailsTab[11].textContent).toEqual('Yes');
@@ -61,7 +61,7 @@ describe('RgwUserDetailsComponent', () => {
     fixture.detectChanges();
 
     const detailsTab = fixture.debugElement.nativeElement.querySelectorAll(
-      '.table.table-striped.table-bordered tr td'
+      '.cds--data-table--sort.cds--data-table--no-border tr td'
     );
     expect(detailsTab[14].textContent).toEqual('MFAs(Id)');
     expect(detailsTab[15].textContent).toEqual('testMFA1, testMFA2');
index 2c4a926120b78a0f0879a09d69072888c3c7339c..acb85a1a43ac7459200b0c83638c55b4a5dd11ec 100644 (file)
@@ -11,6 +11,8 @@ import { RgwUserS3Key } from '../models/rgw-user-s3-key';
 import { RgwUserSwiftKey } from '../models/rgw-user-swift-key';
 import { RgwUserS3KeyModalComponent } from '../rgw-user-s3-key-modal/rgw-user-s3-key-modal.component';
 import { RgwUserSwiftKeyModalComponent } from '../rgw-user-swift-key-modal/rgw-user-swift-key-modal.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { Permissions } from '~/app/shared/models/permissions';
 
 @Component({
   selector: 'cd-rgw-user-details',
@@ -34,6 +36,8 @@ export class RgwUserDetailsComponent implements OnChanges, OnInit {
   keys: any = [];
   keysColumns: CdTableColumn[] = [];
   keysSelection: CdTableSelection = new CdTableSelection();
+  tableAction: CdTableAction[] = [];
+  permissions: Permissions;
 
   icons = Icons;
 
@@ -59,6 +63,15 @@ export class RgwUserDetailsComponent implements OnChanges, OnInit {
   }
 
   ngOnChanges() {
+    this.tableAction = [
+      {
+        name: $localize`Show`,
+        permission: 'read',
+        click: () => this.showKeyModal(),
+        icon: Icons.show
+      }
+    ];
+
     if (this.selection) {
       this.user = this.selection;
 
index 2eaeeb35eecf85f6f31b7e7805e671ffdd59dfe1..b546a75b74ed8df57f817af09f4ab252201512c2 100644 (file)
@@ -19,7 +19,7 @@ export class ListWithDetails {
       this.staleTimeout = window.setTimeout(() => {
         this.ngZone.run(() => {
           this.tableStatus = new TableStatus(
-            'warning',
+            'secondary',
             $localize`The user list data might be stale. If needed, you can manually reload it.`
           );
         });
index cff2ec33a02bd2f59ec3eb7815d7101e785e7310..1dc089199a0f0b3f7195cc6bf369f2b6aa36c989 100644 (file)
@@ -5,23 +5,26 @@ describe('TableStatusViewCache', () => {
   it('should create an instance', () => {
     const ts = new TableStatusViewCache();
     expect(ts).toBeTruthy();
-    expect(ts).toEqual({ msg: '', type: 'light' });
+    expect(ts).toEqual({ msg: '', type: 'ghost' });
   });
 
   it('should create a ValueStale instance', () => {
     let ts = new TableStatusViewCache(ViewCacheStatus.ValueStale);
-    expect(ts).toEqual({ type: 'warning', msg: 'Displaying previously cached data.' });
+    expect(ts).toEqual({ type: 'secondary', msg: 'Displaying previously cached data.' });
 
     ts = new TableStatusViewCache(ViewCacheStatus.ValueStale, 'foo bar');
-    expect(ts).toEqual({ type: 'warning', msg: 'Displaying previously cached data for foo bar.' });
+    expect(ts).toEqual({
+      type: 'secondary',
+      msg: 'Displaying previously cached data for foo bar.'
+    });
   });
 
   it('should create a ValueNone instance', () => {
     let ts = new TableStatusViewCache(ViewCacheStatus.ValueNone);
-    expect(ts).toEqual({ type: 'info', msg: 'Retrieving data. Please wait...' });
+    expect(ts).toEqual({ type: 'primary', msg: 'Retrieving data. Please wait...' });
 
     ts = new TableStatusViewCache(ViewCacheStatus.ValueNone, 'foo bar');
-    expect(ts).toEqual({ type: 'info', msg: 'Retrieving data for foo bar. Please wait...' });
+    expect(ts).toEqual({ type: 'primary', msg: 'Retrieving data for foo bar. Please wait...' });
   });
 
   it('should create a ValueException instance', () => {
index 91c53a0aa06e036f4d8923a1ee54e7aa1404f158..e296b974785ed198eb12cb2ccb7d163cc37d19eb 100644 (file)
@@ -7,18 +7,18 @@ export class TableStatusViewCache extends TableStatus {
 
     switch (status) {
       case ViewCacheStatus.ValueOk:
-        this.type = 'light';
+        this.type = 'ghost';
         this.msg = '';
         break;
       case ViewCacheStatus.ValueNone:
-        this.type = 'info';
+        this.type = 'primary';
         this.msg =
           (statusFor ? $localize`Retrieving data for ${statusFor}.` : $localize`Retrieving data.`) +
           ' ' +
           $localize`Please wait...`;
         break;
       case ViewCacheStatus.ValueStale:
-        this.type = 'warning';
+        this.type = 'secondary';
         this.msg = statusFor
           ? $localize`Displaying previously cached data for ${statusFor}.`
           : $localize`Displaying previously cached data.`;
index 7fa7ba1a4ade20df8a437cc8fd40bf8bc26d3bb3..77deb54f25dce93e9a0fc0240018b59629ae32e1 100644 (file)
@@ -4,7 +4,7 @@ describe('TableStatus', () => {
   it('should create an instance', () => {
     const ts = new TableStatus();
     expect(ts).toBeTruthy();
-    expect(ts).toEqual({ msg: '', type: 'light' });
+    expect(ts).toEqual({ msg: '', type: 'ghost' });
   });
 
   it('should create with parameters', () => {
index fa9be80fef75cb06ce830e5af555327dd42d4c75..982453d2a333c5677ef9155e1bf0611be0a4783e 100644 (file)
@@ -1,3 +1,6 @@
 export class TableStatus {
-  constructor(public type: 'info' | 'warning' | 'danger' | 'light' = 'light', public msg = '') {}
+  constructor(
+    public type: 'primary' | 'secondary' | 'danger' | 'ghost' = 'ghost',
+    public msg = ''
+  ) {}
 }
index 73e4a307cfc7deed4bf5c7c04feb48088277a368..e5401c70d15c6ded883a041a3586369c1451fa92 100644 (file)
@@ -31,7 +31,7 @@
     </div>
     <ng-container *ngIf="expandedRow && meta.detail_columns.length > 0">
       <table *cdTableDetail
-             class="table table-striped table-bordered">
+             class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
         <tbody>
           <tr *ngFor="let column of meta.detail_columns">
             <td i18n
index 9988a9e3be3646bf081fc16ea59911bb78967e1f..386c2850cf30e27b9f73bf88e91cbefa09998c70 100644 (file)
          cdsIcon="add"
          size="16"></svg>
   </button>
+  <ng-container *ngIf="primaryDropDown">
+    <button class="primary-dropdown-btn"
+            [attr.aria-label]="dropDownOnly"
+            [offset]="{ x: -210, y: 65 }"
+            [cdsOverflowMenu]="overflowMenuTpl"
+            data-testid="table-action-btn">
+      <svg class="cds--btn__icon"
+           cdsIcon="caret--down"
+           size="16"></svg>
+    </button>
+    <ng-template #overflowMenuTpl>
+      <ng-container *ngFor="let action of dropDownActions">
+        <cds-overflow-menu-option *ngIf="currentAction !== action"
+                                  class="{{ toClassName(action) }}"
+                                  title="{{ useDisableDesc(action) }}"
+                                  (click)="useClickAction(action)"
+                                  [routerLink]="useRouterLink(action)"
+                                  [preserveFragment]="action.preserveFragment ? '' : null"
+                                  [disabled]="disableSelectionAction(action)"
+                                  [attr.aria-label]="action.name"
+                                  data-testid="table-action-option-btn"
+                                  i18n>
+        {{ action.name }}
+        </cds-overflow-menu-option>
+      </ng-container>
+    </ng-template>
+  </ng-container>
 </ng-container>
 
+<ng-template #caret>
+  <button [cdsButton]="currentAction.buttonKind"
+          class="caret-btn">
+    <svg class="cds--btn__icon"
+         cdsIcon="caret--down"
+         size="16"></svg>
+  </button>
+</ng-template>
 
 <ng-template #dropDownOnlyTpl>
   <cds-overflow-menu [customTrigger]="customTrigger"
index e13939163735d0eca6fbb398c4bf95ed63bda081..6b0a82d130fcff162d73413641a9e5fba27d2d98 100644 (file)
@@ -21,3 +21,14 @@ button.dropdown-item:hover {
 ::ng-deep .cds--toolbar-content .cds--overflow-menu {
   inline-size: auto !important;
 }
+
+.primary-dropdown-btn {
+  align-items: center;
+  background-color: vv.$primary;
+  border: 0;
+  display: flex;
+  fill: vv.$white;
+  height: 3rem;
+  justify-content: center;
+  width: 3rem;
+}
index b06619935cb0860f839884d2003285c8da51dae8..d8304127fab13eb5bc737b7223c4f4b567193594 100644 (file)
@@ -27,6 +27,8 @@ export class TableActionsComponent implements OnChanges, OnInit {
   // This disables the main action button.
   @Input()
   dropDownOnly?: string;
+  @Input()
+  primaryDropDown = false;
 
   currentAction?: CdTableAction;
   // Array with all visible actions
index f5c9e5466285023a8a4032159ab1ebb09521dfd3..0829c908a3fe4e9e6c7ea2d6a9798b3c453ea15f 100644 (file)
       </ng-container>
       <!-- end column filters -->
       <!-- refresh button -->
-      <button cdsButton="ghost"
+      <cds-icon-button
+              [kind]="status.type"
               [disabled]="!fetchData?.observers?.length"
               (click)="refreshBtn()"
-              title="Refresh"
+              [title]="status.msg"
               [description]="status.msg"
               i18n-title
               i18n-description
@@ -74,7 +75,7 @@
         <svg cdsIcon="renew"
              size="16"
              [ngClass]="{ 'cds--toolbar-action__icon': true, 'reload': loadingIndicator }"></svg>
-      </button>
+      </cds-icon-button>
       <!-- end refresh button -->
       <!-- show hide columns -->
       <button cdsButton="ghost"
              (deselectRow)="onDeselect($event)"
              (deselectAll)="onDeselectAll($event)">
     <tbody>
-      <tr cdstablerow>
-        <td *ngIf="!rows?.length && !loadingIndicator"
-            class="no-data"
+      <tr cdstablerow
+          *ngIf="!rows?.length && !loadingIndicator">
+        <td class="no-data"
             cdstabledata
-            [attr.colspan]="visibleColumns.length + 1">
+            [attr.colspan]="selectionType === 'single' ? visibleColumns.length + 1 : visibleColumns.length + 2">
           <span class="d-flex justify-content-center align-items-center"
                 i18n>No data to display</span>
         </td>
       </tr>
-      <tr cdstablerow>
-        <td *ngIf="loadingIndicator"
-            class="no-data"
+      <tr cdstablerow
+          *ngIf="loadingIndicator">
+        <td class="no-data"
             cdstabledata
             [attr.colspan]="visibleColumns.length + 1">
           <span class="d-flex justify-content-center align-items-center"