]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashborad: RGW sync policy
authorNaman Munet <namanmunet@Namans-MacBook-Pro.local>
Tue, 25 Jun 2024 11:54:51 +0000 (17:24 +0530)
committerNaman Munet <nmunet@redhat.com>
Tue, 16 Jul 2024 12:03:41 +0000 (17:33 +0530)
Fixes: https://tracker.ceph.com/issues/66576
Signed-off-by: Naman Munet <nmunet@redhat.com>
(cherry picked from commit 94f9a2bf18f43cc5b05dd8c216fe9e0975969106)

18 files changed:
src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.e2e.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/jest.config.cjs
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-multisite-details/rgw-multisite-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/enum/cell-template.enum.ts
src/pybind/mgr/dashboard/openapi.yaml

index 08f62a2f315f7399685d64c253d28475dbe63b2e..9c5d1106eb467a106941c6881e1ee0b294ba05e5 100644 (file)
@@ -129,8 +129,21 @@ class RgwMultisiteController(RESTController):
     @Endpoint(path='/sync-policy')
     @EndpointDoc("Get the sync policy")
     @ReadPermission
-    def get_sync_policy(self, bucket_name='', zonegroup_name=''):
+    def get_sync_policy(self, bucket_name='', zonegroup_name='', all_policy=None):
         multisite_instance = RgwMultisite()
+        all_policy = str_to_bool(all_policy)
+        if all_policy:
+            sync_policy_list = []
+            buckets = json.loads(RgwBucket().list(stats=False))
+            for bucket in buckets:
+                sync_policy = multisite_instance.get_sync_policy(bucket, zonegroup_name)
+                for policy in sync_policy['groups']:
+                    policy['bucketName'] = bucket
+                    sync_policy_list.append(policy)
+            other_sync_policy = multisite_instance.get_sync_policy(bucket_name, zonegroup_name)
+            for policy in other_sync_policy['groups']:
+                sync_policy_list.append(policy)
+            return sync_policy_list
         return multisite_instance.get_sync_policy(bucket_name, zonegroup_name)
 
     @Endpoint(path='/sync-policy-group')
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.e2e.spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.e2e.spec.ts
new file mode 100644 (file)
index 0000000..13f8930
--- /dev/null
@@ -0,0 +1,30 @@
+import { MultisitePageHelper } from './multisite.po';
+
+describe('Multisite page', () => {
+  const multisite = new MultisitePageHelper();
+
+  beforeEach(() => {
+    cy.login();
+    multisite.navigateTo();
+  });
+
+  describe('tabs and table tests', () => {
+    it('should show two tabs', () => {
+      multisite.getTabsCount().should('eq', 2);
+    });
+
+    it('should show Configuration tab as a first tab', () => {
+      multisite.getTabText(0).should('eq', 'Configuration');
+    });
+
+    it('should show sync policy tab as a second tab', () => {
+      multisite.getTabText(1).should('eq', 'Sync Policy');
+    });
+
+    it('should show empty table in Sync Policy page', () => {
+      multisite.getTab('Sync Policy').click();
+      multisite.getDataTables().should('exist');
+      multisite.getTableCount('total').should('eq', 0);
+    });
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.ts
new file mode 100644 (file)
index 0000000..b48b58e
--- /dev/null
@@ -0,0 +1,8 @@
+import { PageHelper } from '../page-helper.po';
+
+const pages = {
+  index: { url: '#/rgw/multisite', id: 'cd-rgw-multisite-details' }
+};
+export class MultisitePageHelper extends PageHelper {
+  pages = pages;
+}
index 9cdf6be4b46375ea0f40d19bcc08ddb8d0e42cdb..0dbe5e159471c9eb471087dba0ef212fe8429c8e 100644 (file)
@@ -32,7 +32,7 @@ const jestConfig = {
   },
   setupFiles: ['jest-canvas-mock'],
   coverageReporters: ['cobertura', 'html'],
-  modulePathIgnorePatterns: ['<rootDir>/coverage/', '<rootDir>/node_modules/simplebar-angular'],
+  modulePathIgnorePatterns: ['<rootDir>/coverage/', '<rootDir>/node_modules/simplebar-angular', '<rootDir>/cypress'],
   testMatch: ['**/*.spec.ts'],
   testRunner: 'jest-jasmine2'
 };
index 291013a5ce2f13badd72f46f2b88635bfc7c84b1..f6b9bbae8f33e950d65b6b9a16b547ec752ad651 100644 (file)
-<div class="row">
-  <div class="col-sm-12 col-lg-12">
-    <div>
-      <cd-alert-panel   *ngIf="!rgwModuleStatus"
-                        type="info"
-                        spacingClass="mb-3"
-                        class="d-flex align-items-center"
-                        i18n>In order to access the import/export feature, the rgw module must be enabled
+<nav ngbNav
+     #nav="ngbNav"
+     class="nav-tabs"
+     (navChange)="onNavChange($event)">
+  <ng-container ngbNavItem="configuration">
+    <a ngbNavLink
+       i18n>Configuration</a>
+    <ng-template ngbNavContent>
+      <div>
+        <cd-alert-panel
+          *ngIf="!rgwModuleStatus"
+          type="info"
+          spacingClass="mb-3"
+          class="d-flex align-items-center"
+          i18n
+          >In order to access the import/export feature, the rgw module must be enabled
 
-        <button class="btn btn-light mx-2"
-                type="button"
-                (click)="enableRgwModule()">Enable</button>
-      </cd-alert-panel>
-      <cd-alert-panel   *ngIf="restartGatewayMessage"
-                        type="warning"
-                        spacingClass="mb-3"
-                        i18n>Please restart all Ceph Object Gateway instances in all zones to ensure consistent multisite configuration updates.
-        <a class="text-decoration-underline"
-           routerLink="/services">
-           Cluster->Services</a>
-      </cd-alert-panel>
-      <cd-table-actions class="btn-group mb-4 me-2"
-                        [permission]="permission"
-                        [selection]="selection"
-                        [tableActions]="createTableActions">
-      </cd-table-actions>
-      <span *ngIf="showMigrateAction">
-        <cd-table-actions class="btn-group mb-4 me-2 secondary"
-                          [permission]="permission"
-                          [btnColor]="'light'"
-                          [selection]="selection"
-                          [tableActions]="migrateTableAction">
+          <button class="btn btn-light mx-2"
+                  type="button"
+                  (click)="enableRgwModule()">
+            Enable
+          </button>
+        </cd-alert-panel>
+        <cd-alert-panel
+          *ngIf="restartGatewayMessage"
+          type="warning"
+          spacingClass="mb-3"
+          i18n>Please restart all Ceph Object Gateway instances in all zones to ensure consistent
+          multisite configuration updates.
+          <a class="text-decoration-underline"
+             routerLink="/services"> Cluster->Services</a>
+        </cd-alert-panel>
+        <cd-table-actions
+          class="btn-group mb-4 me-2"
+          [permission]="permission"
+          [selection]="selection"
+          [tableActions]="createTableActions"
+        >
         </cd-table-actions>
-      </span>
-      <cd-table-actions class="btn-group mb-4 me-2"
-                        [permission]="permission"
-                        [btnColor]="'light'"
-                        [selection]="selection"
-                        [tableActions]="importAction">
-      </cd-table-actions>
-      <cd-table-actions class="btn-group mb-4 me-2"
-                        [permission]="permission"
-                        [btnColor]="'light'"
-                        [selection]="selection"
-                        [tableActions]="exportAction">
-      </cd-table-actions>
-    </div>
-    <div class="card">
-      <div class="card-header"
-           i18n>Topology Viewer</div>
-      <div class="card-body">
-        <div class="row">
-          <div class="col-sm-6 col-lg-6 tree-container">
-            <i *ngIf="loadingIndicator"
-               [ngClass]="[icons.large, icons.spinner, icons.spin]"></i>
-            <tree-root #tree
-                       [nodes]="nodes"
-                       [options]="treeOptions"
-                       (updateData)="onUpdateData()">
-              <ng-template #treeNodeTemplate
-                           let-node>
-                <span *ngIf="node.data.name"
-                      class="me-3">
-                  <span *ngIf="(node.data.show_warning)">
-                    <i  class="text-danger"
+        <span *ngIf="showMigrateAction">
+          <cd-table-actions
+            class="btn-group mb-4 me-2 secondary"
+            [permission]="permission"
+            [btnColor]="'light'"
+            [selection]="selection"
+            [tableActions]="migrateTableAction"
+          >
+          </cd-table-actions>
+        </span>
+        <cd-table-actions
+          class="btn-group mb-4 me-2"
+          [permission]="permission"
+          [btnColor]="'light'"
+          [selection]="selection"
+          [tableActions]="importAction"
+        >
+        </cd-table-actions>
+        <cd-table-actions
+          class="btn-group mb-4 me-2"
+          [permission]="permission"
+          [btnColor]="'light'"
+          [selection]="selection"
+          [tableActions]="exportAction">
+        </cd-table-actions>
+      </div>
+      <div class="card">
+        <div class="card-header"
+             i18n>Topology Viewer</div>
+        <div class="card-body">
+          <div class="row">
+            <div class="col-sm-6 col-lg-6 tree-container">
+              <i *ngIf="loadingIndicator"
+                 [ngClass]="[icons.large, icons.spinner, icons.spin]"></i>
+              <tree-root
+                #tree
+                [nodes]="nodes"
+                [options]="treeOptions"
+                (updateData)="onUpdateData()">
+                <ng-template
+                  #treeNodeTemplate
+                  let-node>
+                  <span *ngIf="node.data.name"
+                        class="me-3">
+                    <span *ngIf="node.data.show_warning">
+                      <i
+                        class="text-danger"
                         i18n-title
                         [title]="node.data.warning_message"
-                        [ngClass]="icons.danger"></i>
-                  </span>
-                  <i [ngClass]="node.data.icon"></i>
+                        [ngClass]="icons.danger"
+                      ></i>
+                    </span>
+                    <i [ngClass]="node.data.icon"></i>
                     {{ node.data.name }}
-                </span>
-                <span class="badge badge-success me-2"
-                      *ngIf="node.data.is_default">
-                  default
-                </span>
-                <span class="badge badge-warning me-2"
-                      *ngIf="node.data.is_master">
-                  master
-                </span>
-                <span class="badge badge-warning me-2"
-                      *ngIf="node.data.secondary_zone">
-                  secondary-zone
-                </span>
-                <div class="btn-group align-inline-btns"
-                     *ngIf="node.isFocused"
-                     role="group">
-                  <div [title]="editTitle"
-                       i18n-title>
-                    <button type="button"
-                            class="btn btn-light dropdown-toggle-split ms-1"
-                            (click)="openModal(node, true)"
-                            [disabled]="getDisable() || node.data.secondary_zone">
-                      <i [ngClass]="[icons.edit]"></i>
-                    </button>
-                  </div>
-                  <div [title]="deleteTitle"
-                       i18n-title>
-                    <button type="button"
-                            class="btn btn-light ms-1"
-                            [disabled]="isDeleteDisabled(node) || node.data.secondary_zone"
-                            (click)="delete(node)">
-                      <i [ngClass]="[icons.destroy]"></i>
-                    </button>
+                  </span>
+                  <span class="badge badge-success me-2"
+                        *ngIf="node.data.is_default">
+                    default
+                  </span>
+                  <span class="badge badge-warning me-2"
+                        *ngIf="node.data.is_master"> master </span>
+                  <span class="badge badge-warning me-2"
+                        *ngIf="node.data.secondary_zone">
+                    secondary-zone
+                  </span>
+                  <div class="btn-group align-inline-btns"
+                       *ngIf="node.isFocused"
+                       role="group">
+                    <div [title]="editTitle"
+                         i18n-title>
+                      <button
+                        type="button"
+                        class="btn btn-light dropdown-toggle-split ms-1"
+                        (click)="openModal(node, true)"
+                        [disabled]="getDisable() || node.data.secondary_zone">
+                        <i [ngClass]="[icons.edit]"></i>
+                      </button>
+                    </div>
+                    <div [title]="deleteTitle"
+                         i18n-title>
+                      <button
+                        type="button"
+                        class="btn btn-light ms-1"
+                        [disabled]="isDeleteDisabled(node) || node.data.secondary_zone"
+                        (click)="delete(node)">
+                        <i [ngClass]="[icons.destroy]"></i>
+                      </button>
+                    </div>
                   </div>
-                </div>
-              </ng-template>
-            </tree-root>
-          </div>
-          <div class="col-sm-6 col-lg-6 metadata"
-               *ngIf="metadata">
-            <legend>{{ metadataTitle }}</legend>
-            <div>
-              <cd-table-key-value cdTableDetail
-                                  [data]="metadata">
-              </cd-table-key-value>
+                </ng-template>
+              </tree-root>
+            </div>
+            <div class="col-sm-6 col-lg-6 metadata"
+                 *ngIf="metadata">
+              <legend>{{ metadataTitle }}</legend>
+              <div>
+                <cd-table-key-value
+                cdTableDetail
+                [data]="metadata"></cd-table-key-value>
+              </div>
             </div>
           </div>
         </div>
       </div>
-    </div>
-  </div>
-</div>
+    </ng-template>
+  </ng-container>
+  <ng-container ngbNavItem="syncPolicy">
+    <a ngbNavLink
+       i18n>Sync Policy</a>
+    <ng-template ngbNavContent>
+      <cd-rgw-multisite-sync-policy></cd-rgw-multisite-sync-policy>
+    </ng-template>
+  </ng-container>
+</nav>
+
+<div [ngbNavOutlet]="nav"></div>
index be65424cf7ab947696b24cd7841dd1bcbe927e6e..ef833a0324ce8f6e528a3ba20500a02eceb9d3cd 100644 (file)
@@ -8,6 +8,7 @@ import { SharedModule } from '~/app/shared/shared.module';
 import { RgwMultisiteDetailsComponent } from './rgw-multisite-details.component';
 import { RouterTestingModule } from '@angular/router/testing';
 import { configureTestBed } from '~/testing/unit-test-helper';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 
 describe('RgwMultisiteDetailsComponent', () => {
   let component: RgwMultisiteDetailsComponent;
@@ -21,7 +22,8 @@ describe('RgwMultisiteDetailsComponent', () => {
       TreeModule,
       SharedModule,
       ToastrModule.forRoot(),
-      RouterTestingModule
+      RouterTestingModule,
+      NgbNavModule
     ]
   });
 
index 6e898e789456b2e5afeae279496879cb61f95dab..d5e0fd063f8d52c0f0eed7420b844ec3fd3b481a 100644 (file)
@@ -267,7 +267,6 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
       }
     });
   }
-
   /* setConfigValues() {
     this.rgwDaemonService
       .setMultisiteConfig(
@@ -589,4 +588,19 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
       }
     );
   }
+
+  onNavChange(event: any) {
+    if (event.nextId == 'configuration') {
+      this.metadata = null;
+      /*
+        It is a known issue with angular2-tree package when tree is hidden (for example inside tab or modal),
+        it is not rendered when it becomes visible. Solution is to call this.tree.sizeChanged() which recalculates
+        the rendered nodes according to the actual viewport size. (https://angular2-tree.readme.io/docs/common-issues)
+      */
+      setTimeout(() => {
+        this.tree.sizeChanged();
+        this.onUpdateData();
+      }, 200);
+    }
+  }
 }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.html
new file mode 100644 (file)
index 0000000..7de60b5
--- /dev/null
@@ -0,0 +1,18 @@
+  <legend i18n>
+    Multisite Sync Policy
+    <cd-help-text>
+      Multisite bucket-granularity sync policy provides fine grained control of data movement between buckets in different zones.
+    </cd-help-text>
+  </legend>
+  <cd-table #table
+            [data]="syncPolicyData"
+            [columns]="columns"
+            columnMode="flex"
+            selectionType="single"
+            [searchableObjects]="true"
+            [hasDetails]="false"
+            [serverSide]="false"
+            [count]="0"
+            [maxLimit]="25"
+            [toolHeader]="true">
+  </cd-table>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.spec.ts
new file mode 100644 (file)
index 0000000..6811f86
--- /dev/null
@@ -0,0 +1,26 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwMultisiteSyncPolicyComponent } from './rgw-multisite-sync-policy.component';
+import { HttpClientModule } from '@angular/common/http';
+import { TitleCasePipe } from '@angular/common';
+
+describe('RgwMultisiteSyncPolicyComponent', () => {
+  let component: RgwMultisiteSyncPolicyComponent;
+  let fixture: ComponentFixture<RgwMultisiteSyncPolicyComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [RgwMultisiteSyncPolicyComponent],
+      imports: [HttpClientModule],
+      providers: [TitleCasePipe]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwMultisiteSyncPolicyComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.ts
new file mode 100644 (file)
index 0000000..77b49a9
--- /dev/null
@@ -0,0 +1,74 @@
+import { TitleCasePipe } from '@angular/common';
+import { Component, OnInit } from '@angular/core';
+import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
+import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+
+@Component({
+  selector: 'cd-rgw-multisite-sync-policy',
+  templateUrl: './rgw-multisite-sync-policy.component.html',
+  styleUrls: ['./rgw-multisite-sync-policy.component.scss']
+})
+export class RgwMultisiteSyncPolicyComponent implements OnInit {
+  columns: Array<CdTableColumn> = [];
+  syncPolicyData: any = [];
+
+  constructor(
+    private rgwMultisiteService: RgwMultisiteService,
+    private titleCasePipe: TitleCasePipe
+  ) {}
+
+  ngOnInit(): void {
+    this.columns = [
+      {
+        name: $localize`Group Name`,
+        prop: 'groupName',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Status`,
+        prop: 'status',
+        flexGrow: 1,
+        cellTransformation: CellTemplate.tooltip,
+        customTemplateConfig: {
+          map: {
+            Enabled: { class: 'badge-success', tooltip: 'sync is allowed and enabled' },
+            Allowed: { class: 'badge-info', tooltip: 'sync is allowed' },
+            Forbidden: {
+              class: 'badge-warning',
+              tooltip:
+                'sync (as defined by this group) is not allowed and can override other groups'
+            }
+          }
+        },
+        pipe: this.titleCasePipe
+      },
+      {
+        name: $localize`Zonegroup`,
+        prop: 'zonegroup',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Bucket`,
+        prop: 'bucket',
+        flexGrow: 1
+      }
+    ];
+
+    this.rgwMultisiteService
+      .getSyncPolicy('', '', true)
+      .subscribe((allSyncPolicyData: Array<Object>) => {
+        if (allSyncPolicyData && allSyncPolicyData.length > 0) {
+          allSyncPolicyData.forEach((policy) => {
+            this.syncPolicyData.push({
+              groupName: policy['id'],
+              status: policy['status'],
+              bucket: policy['bucketName'],
+              zonegroup: ''
+            });
+          });
+          this.syncPolicyData = [...this.syncPolicyData];
+        }
+      });
+  }
+}
index d893bd688273c1b145c58ef72540d59d67f5fd4c..d07d8f1bf49016269d5056ab010e3f392a78a072 100644 (file)
@@ -1,4 +1,4 @@
-import { CommonModule } from '@angular/common';
+import { CommonModule, TitleCasePipe } from '@angular/common';
 import { NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule, Routes } from '@angular/router';
@@ -49,6 +49,7 @@ import { RgwSyncDataInfoComponent } from './rgw-sync-data-info/rgw-sync-data-inf
 import { BucketTagModalComponent } from './bucket-tag-modal/bucket-tag-modal.component';
 import { NfsListComponent } from '../nfs/nfs-list/nfs-list.component';
 import { NfsFormComponent } from '../nfs/nfs-form/nfs-form.component';
+import { RgwMultisiteSyncPolicyComponent } from './rgw-multisite-sync-policy/rgw-multisite-sync-policy.component';
 
 @NgModule({
   imports: [
@@ -106,8 +107,10 @@ import { NfsFormComponent } from '../nfs/nfs-form/nfs-form.component';
     RgwSyncPrimaryZoneComponent,
     RgwSyncMetadataInfoComponent,
     RgwSyncDataInfoComponent,
-    BucketTagModalComponent
-  ]
+    BucketTagModalComponent,
+    RgwMultisiteSyncPolicyComponent
+  ],
+  providers: [TitleCasePipe]
 })
 export class RgwModule {}
 
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.spec.ts
new file mode 100644 (file)
index 0000000..3f5b3d3
--- /dev/null
@@ -0,0 +1,50 @@
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { TestBed } from '@angular/core/testing';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { RgwMultisiteService } from './rgw-multisite.service';
+
+const mockSyncPolicyData: any = [
+  {
+    id: 'test',
+    data_flow: {},
+    pipes: [],
+    status: 'enabled',
+    bucketName: 'test'
+  },
+  {
+    id: 'test',
+    data_flow: {},
+    pipes: [],
+    status: 'enabled'
+  }
+];
+
+describe('RgwMultisiteService', () => {
+  let service: RgwMultisiteService;
+  let httpTesting: HttpTestingController;
+
+  configureTestBed({
+    providers: [RgwMultisiteService],
+    imports: [HttpClientTestingModule]
+  });
+
+  beforeEach(() => {
+    service = TestBed.inject(RgwMultisiteService);
+    httpTesting = TestBed.inject(HttpTestingController);
+  });
+
+  afterEach(() => {
+    httpTesting.verify();
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+
+  it('should fetch all the sync policy related or un-related to a bucket', () => {
+    service.getSyncPolicy('', '', true).subscribe();
+    const req = httpTesting.expectOne('api/rgw/multisite/sync-policy?all_policy=true');
+    expect(req.request.method).toBe('GET');
+    req.flush(mockSyncPolicyData);
+  });
+});
index 9081c21e440035c5d06fccf20ad8d95f505891d7..4f69d6ab2a4c1ec194060cb657bee709820506fb 100644 (file)
@@ -34,4 +34,17 @@ export class RgwMultisiteService {
   status() {
     return this.http.get(`${this.uiUrl}/status`);
   }
+
+  getSyncPolicy(bucketName?: string, zonegroup?: string, fetchAllPolicy = false) {
+    let params = new HttpParams();
+    if (bucketName) {
+      params = params.append('bucket_name', bucketName);
+    }
+    if (zonegroup) {
+      params = params.append('zonegroup_name', zonegroup);
+    }
+    // fetchAllPolicy - if true, will fetch all the policy either linked or not linked with the buckets
+    params = params.append('all_policy', fetchAllPolicy);
+    return this.http.get(`${this.url}/sync-policy`, { params });
+  }
 }
index f977273b0cf648764de2e3fba252fee65c4b7ec1..a856a4c487019b0b5b3ee844d25d3a4905143488 100644 (file)
   <span>{{ value | map:column?.customTemplateConfig }}</span>
 </ng-template>
 
+<ng-template #tooltipTpl
+             let-column="column"
+             let-value="value">
+  <span *ngFor="let item of (value | array);">
+    <span
+      i18n
+      i18n-ngbTooltip
+      class="{{(column?.customTemplateConfig?.map && column?.customTemplateConfig?.map[item]?.class) ? column.customTemplateConfig.map[item].class : ''}}"
+      ngbTooltip="{{(column?.customTemplateConfig?.map && column?.customTemplateConfig?.map[item]?.tooltip) ? column.customTemplateConfig.map[item].tooltip : ''}}">
+      {{value}}
+    </span>
+  </span>
+</ng-template>
+
 <ng-template #truncateTpl
              let-column="column"
              let-value="value">
index 6e39f4bff138eabe8172f34be07f33192474ab50..905646b55b8a8efc56b817e6bd0ba9e62f7a801e 100644 (file)
@@ -75,6 +75,8 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
   rowSelectionTpl: TemplateRef<any>;
   @ViewChild('pathTpl', { static: true })
   pathTpl: TemplateRef<any>;
+  @ViewChild('tooltipTpl', { static: true })
+  tooltipTpl: TemplateRef<any>;
 
   // This is the array with the items to be shown.
   @Input()
@@ -612,6 +614,7 @@ export class TableComponent implements AfterContentChecked, OnInit, OnChanges, O
     this.cellTemplates.truncate = this.truncateTpl;
     this.cellTemplates.timeAgo = this.timeAgoTpl;
     this.cellTemplates.path = this.pathTpl;
+    this.cellTemplates.tooltip = this.tooltipTpl;
   }
 
   useCustomClass(value: any): string {
index 2790f974978597c799b97c28e9837b2583bcf65f..5c4072f7f1fc6e9349c4d8b0a7e3d664bfc4f79c 100644 (file)
@@ -60,5 +60,17 @@ export enum CellTemplate {
   This template truncates a path to a shorter format and shows the whole path in a tooltip
   eg: /var/lib/ceph/osd/ceph-0 -> /var/.../ceph-0
   */
-  path = 'path'
+  path = 'path',
+  /*
+  This template is used to attach tooltip to the given column value
+  // {
+  //   ...
+  //   cellTransformation: CellTemplate.tooltip,
+  //   customTemplateConfig: {
+  //     map?: {
+  //       [key: any]: { class?: string, tooltip: string }
+  //     }
+  //  }
+  */
+  tooltip = 'tooltip'
 }
index 7593bbe5fc2555c4b022110fe0860fca244dc218..e893b0e664bc54a0222d93ba5830df5099f436ca 100644 (file)
@@ -10928,6 +10928,11 @@ paths:
         name: zonegroup_name
         schema:
           type: string
+      - allowEmptyValue: true
+        in: query
+        name: all_policy
+        schema:
+          type: string
       responses:
         '200':
           content: