]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: rgw sync policy routing fixes 58679/head
authorNaman Munet <nmunet@redhat.com>
Wed, 17 Jul 2024 10:04:55 +0000 (15:34 +0530)
committerNaman Munet <nmunet@redhat.com>
Mon, 22 Jul 2024 08:24:09 +0000 (13:54 +0530)
Fixes: https://tracker.ceph.com/issues/66977
Signed-off-by: Naman Munet <nmunet@redhat.com>
(cherry picked from commit ca4759b8f48fc62ff61fd290657586abd75c293c)

Conflicts:
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.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts

17 files changed:
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.e2e.spec.ts
src/pybind/mgr/dashboard/frontend/cypress/e2e/rgw/multisite.po.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-multisite-details/rgw-multisite-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy-form/rgw-multisite-sync-policy-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy-form/rgw-multisite-sync-policy-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy/rgw-multisite-sync-policy.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts

index a0306f126b321197c5a21ac6585dfcffdb63c874..9110f723d5dc30f4150296aa005c09036bee5599 100644 (file)
@@ -5,20 +5,12 @@ describe('Multisite page', () => {
 
   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');
+  describe('table tests', () => {
+    it('should show table on sync-policy page', () => {
+      multisite.navigateTo();
+      multisite.tableExist();
     });
   });
 
@@ -30,11 +22,12 @@ describe('Multisite page', () => {
     });
 
     it('should edit policy status', () => {
+      multisite.navigateTo();
       multisite.edit('test', 'Forbidden');
     });
 
     it('should delete policy', () => {
-      multisite.getTab('Sync Policy').click();
+      multisite.navigateTo();
       multisite.delete('test');
     });
   });
@@ -47,7 +40,7 @@ describe('Multisite page', () => {
     });
     describe('symmetrical Flow creation started', () => {
       beforeEach(() => {
-        multisite.getTab('Sync Policy').click();
+        multisite.navigateTo();
         multisite.getExpandCollapseElement().click();
       });
 
@@ -67,7 +60,7 @@ describe('Multisite page', () => {
 
   describe('create, edit & delete directional sync Flow', () => {
     beforeEach(() => {
-      multisite.getTab('Sync Policy').click();
+      multisite.navigateTo();
       multisite.getExpandCollapseElement().click();
     });
 
@@ -82,7 +75,7 @@ describe('Multisite page', () => {
 
   describe('create, edit, delete pipe', () => {
     beforeEach(() => {
-      multisite.getTab('Sync Policy').click();
+      multisite.navigateTo();
       multisite.getExpandCollapseElement().click();
     });
 
index 11a43a7b8b30bf37c67d794115d026596d0d2b41..ffcae7197aaf8a17ac121e12791868599727069f 100644 (file)
@@ -2,9 +2,12 @@ import { PageHelper } from '../page-helper.po';
 
 const WAIT_TIMER = 1000;
 const pages = {
-  index: { url: '#/rgw/multisite', id: 'cd-rgw-multisite-details' },
-  create: { url: '#/rgw/multisite/sync-policy/create', id: 'cd-rgw-multisite-sync-policy-form' },
-  edit: { url: '#/rgw/multisite/sync-policy/edit', id: 'cd-rgw-multisite-sync-policy-form' }
+  index: { url: '#/rgw/multisite/sync-policy', id: 'cd-rgw-multisite-sync-policy' },
+  create: {
+    url: '#/rgw/multisite/sync-policy/(modal:create)',
+    id: 'cd-rgw-multisite-sync-policy-form'
+  },
+  edit: { url: '#/rgw/multisite/sync-policy/(modal:edit', id: 'cd-rgw-multisite-sync-policy-form' }
 };
 export class MultisitePageHelper extends PageHelper {
   pages = pages;
@@ -13,6 +16,11 @@ export class MultisitePageHelper extends PageHelper {
     status: 4
   };
 
+  tableExist() {
+    cy.get('cd-rgw-multisite-sync-policy cd-table').should('exist');
+    cy.get('cd-rgw-multisite-sync-policy cd-table-actions').should('exist');
+  }
+
   @PageHelper.restrictTo(pages.create.url)
   create(group_id: string, status: string) {
     // Enter in group_id
@@ -28,7 +36,7 @@ export class MultisitePageHelper extends PageHelper {
 
   @PageHelper.restrictTo(pages.index.url)
   edit(group_id: string, status: string) {
-    cy.visit(`${pages.edit.url}/${group_id}`);
+    cy.visit(`${pages.edit.url}/${group_id})`);
 
     // Change the status field
     this.selectOption('status', status);
@@ -42,9 +50,6 @@ export class MultisitePageHelper extends PageHelper {
 
   @PageHelper.restrictTo(pages.index.url)
   createSymmetricalFlow(flow_id: string, zones: string[]) {
-    cy.get('cd-rgw-multisite-sync-policy-details').should('exist');
-    this.getTab('Flow').should('exist');
-    this.getTab('Flow').click();
     cy.request({
       method: 'GET',
       url: '/api/rgw/daemon',
index 921a2dfe3eb7d1e803228fd09466b9010c76b0a3..3e7ac5eb8b6732cc7fc3d0ed77d03764c2102993 100644 (file)
-<nav ngbNav
-     #nav="ngbNav"
-     class="nav-tabs"
-     [(activeId)]="activeId"
-     (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"
-          >
-          </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>
-                    {{ 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>
-                    </div>
-                  </div>
-                </ng-template>
-              </tree-root>
+<cd-rgw-multisite-tabs></cd-rgw-multisite-tabs>
+<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>
+  <cd-table-actions class="btn-group mb-4 me-2 secondary"
+                    [permission]="permission"
+                    [btnColor]="'light'"
+                    [selection]="selection"
+                    [tableActions]="migrateTableAction">
+  </cd-table-actions>
+  <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="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>
+            {{ 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 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 [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>
       </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>
+    </div>
+  </div>
+</div>
index 4b65b7e37bd4573cb38112ffaf05585269949e3e..ae2b1a03035b6ad5c5b51fedcac3195a37c5ddf2 100644 (file)
@@ -37,6 +37,7 @@ import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
 import { BlockUI, NgBlockUI } from 'ng-block-ui';
 import { Router } from '@angular/router';
+import { RgwMultisiteSyncPolicyComponent } from '../rgw-multisite-sync-policy/rgw-multisite-sync-policy.component';
 
 @Component({
   selector: 'cd-rgw-multisite-details',
@@ -47,6 +48,7 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
   private sub = new Subscription();
 
   @ViewChild('tree') tree: TreeComponent;
+  @ViewChild(RgwMultisiteSyncPolicyComponent) syncPolicyComp: RgwMultisiteSyncPolicyComponent;
 
   messages = {
     noDefaultRealm: $localize`Please create a default realm first to enable this feature`,
@@ -116,10 +118,6 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
     private notificationService: NotificationService
   ) {
     this.permission = this.authStorageService.getPermissions().rgw;
-    const activeId = this.router.getCurrentNavigation()?.extras?.state?.activeId;
-    if (activeId) {
-      this.activeId = activeId;
-    }
   }
 
   openModal(entity: any, edit = false) {
@@ -593,19 +591,4 @@ 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);
-    }
-  }
 }
index 974474c593878b7bd98dba00604dc4541fa0e467..b7f614ef16b6f5d548ff96e9fbaa42b438cf6653 100644 (file)
@@ -1,29 +1,24 @@
 <cd-modal [modalRef]="activeModal">
-  <ng-container
-    i18n="form title"
-    class="modal-title">{{ action | titlecase }} {{ groupType | upperFirst }} Flow</ng-container>
+  <ng-container i18n="form title"
+                class="modal-title">{{ action | titlecase }} {{ groupType | upperFirst }} Flow</ng-container>
 
     <ng-container class="modal-content">
-      <form
-          name="flowForm"
-          #frm="ngForm"
-          [formGroup]="currentFormGroupContext"
-          novalidate>
+      <form name="flowForm"
+            #frm="ngForm"
+            [formGroup]="currentFormGroupContext"
+            novalidate>
         <div class="modal-body">
           <div class="form-group row">
-            <label
-              class="cd-col-form-label required"
-              for="flow_id"
-              i18n>Name</label>
+            <label class="cd-col-form-label required"
+                   for="flow_id"
+                   i18n>Name</label>
             <div class="cd-col-form-input">
-              <input
-                class="form-control"
-                type="text"
-                placeholder="Flow Name..."
-                id="flow_id"
-                name="flow_id"
-                formControlName="flow_id"
-                [readonly]="editing"/>
+              <input class="form-control"
+                     type="text"
+                     placeholder="Flow Name..."
+                     id="flow_id"
+                     name="flow_id"
+                     formControlName="flow_id"/>
             </div>
           </div>
           <div class="form-group row">
                    for="bucket"
                    i18n>Bucket Name</label>
             <div class="cd-col-form-input">
-              <input
-                id="bucket"
-                name="bucket"
-                class="form-control"
-                type="text"
-                i18n-placeholder
-                placeholder="Bucket Name..."
-                formControlName="bucket_name"/>
-              <span
-                class="invalid-feedback"
-                *ngIf="currentFormGroupContext.showError('bucket_name', frm, 'bucketNameNotAllowed')"
-                i18n>The bucket with chosen name does not exist.</span>
+              <input id="bucket"
+                     name="bucket"
+                     class="form-control"
+                     type="text"
+                     i18n-placeholder
+                     placeholder="Bucket Name..."
+                     formControlName="bucket_name"/>
+              <span class="invalid-feedback"
+                    *ngIf="currentFormGroupContext.showError('bucket_name', frm, 'bucketNameNotAllowed')"
+                    i18n>The bucket with chosen name does not exist.</span>
             </div>
           </div>
           <ng-container *ngIf="groupType == flowType.symmetrical; else directionalFlow">
             <div class="form-group row">
-              <label
-                  class="cd-col-form-label required"
-                  for="zones">
+              <label class="cd-col-form-label required"
+                     for="zones">
                 <ng-container i18n>Zones</ng-container>
                 <cd-helper>
                   <span i18n>Flow need to be associated with atleast one zone</span>
           </ng-container>
           <ng-template #directionalFlow>
             <div class="form-group row">
-              <label
-                class="cd-col-form-label required"
-                for="source_zone"
-                i18n>Source Zone
+              <label class="cd-col-form-label required"
+                     for="source_zone"
+                     i18n>Source Zone
               </label>
               <div class="cd-col-form-input">
                 <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'source_zone', zone: sourceZones }"></ng-container>
               </div>
             </div>
             <div class="form-group row">
-              <label
-                  class="cd-col-form-label required"
-                  for="destination_zone"
-                  i18n>Destination Zone</label>
+              <label class="cd-col-form-label required"
+                     for="destination_zone"
+                     i18n>Destination Zone</label>
               <div class="cd-col-form-input">
                 <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'destination_zone', zone: destinationZones }"></ng-container>
               </div>
           </ng-template>
         </div>
         <div class="modal-footer">
-          <cd-form-button-panel
-            (submitActionEvent)="submit()"
-            [form]="currentFormGroupContext"
-            [submitText]="(action | titlecase) + ' ' + (groupType | upperFirst) + ' ' + 'Flow'"></cd-form-button-panel>
+          <cd-form-button-panel (submitActionEvent)="submit()"
+                                [form]="currentFormGroupContext"
+                                [submitText]="(action | titlecase) + ' ' + (groupType | upperFirst) + ' ' + 'Flow'"></cd-form-button-panel>
         </div>
       </form>
     </ng-container>
   </cd-modal>
 
-<ng-template
-  #zoneMultiSelect
-  let-name="name"
-  let-zone="zone">
-  <cd-select-badges
-    [id]="name"
-    [name]="name"
-    [customBadges]="zone.customBadges"
-    [customBadgeValidators]="zone.data.validators"
-    [messages]="zone.data.messages"
-    [data]="zone.data.selected"
-    [options]="zone.data.available"
-    (selection)="zoneSelection()">
+<ng-template #zoneMultiSelect
+             let-name="name"
+             let-zone="zone">
+  <cd-select-badges [id]="name"
+                    [name]="name"
+                    [customBadges]="zone.customBadges"
+                    [customBadgeValidators]="zone.data.validators"
+                    [messages]="zone.data.messages"
+                    [data]="zone.data.selected"
+                    [options]="zone.data.available"
+                    (selection)="zoneSelection()">
   </cd-select-badges>
-  <i
-    *ngIf="zone.data.selected.length <= 0"
-    i18n-title
-    title="Flow should be associated with {{name?.split('_')}}"
-    class="{{ icons.warning }} icon-warning-color">
+  <i *ngIf="zone.data.selected.length <= 0"
+     i18n-title
+     title="Flow should be associated with {{name?.split('_').join(' ')}}"
+     class="{{ icons.warning }} icon-warning-color">
   </i>
-  <span
-    class="invalid-feedback"
-    *ngIf="currentFormGroupContext.showError(name, frm, 'required')"
-    i18n>{{name?.split('_')}} selection is required!
+  <span class="invalid-feedback"
+        *ngIf="currentFormGroupContext.showError(name, frm, 'required')"
+        i18n>{{name?.split('_').join(' ')}} selection is required!
   </span>
 </ng-template>
index 445ef4867ff8c5d181fd5e80edc69cf585bfec42..126279bf110176731c880f8b63caccde91a4d03c 100644 (file)
@@ -64,6 +64,7 @@ export class RgwMultisiteSyncFlowModalComponent implements OnInit {
         flow_id: this.flowSelectedRow.id,
         bucket_name: this.groupExpandedRow.bucket || ''
       });
+      this.currentFormGroupContext.get('flow_id').disable();
     }
 
     this.rgwDaemonService.selectedDaemon$
index 98f3e10bded1a47e1cf9849112ee09b5860128a6..e50666cdeaa96c5b909f8c1ce48345f0da523284 100644 (file)
 <cd-modal [modalRef]="activeModal">
-  <ng-container
-    i18n="form title"
-    class="modal-title">{{ action | titlecase }} Pipe</ng-container>
+  <ng-container i18n="form title"
+                class="modal-title">{{ action | titlecase }} Pipe</ng-container>
 
   <ng-container class="modal-content">
-    <form
-      name="pipeForm"
-      #frm="ngForm"
-      [formGroup]="pipeForm"
-      novalidate>
+    <form name="pipeForm"
+          #frm="ngForm"
+          [formGroup]="pipeForm"
+          novalidate>
       <div class="modal-body">
         <div class="form-group row">
-          <label
-            class="cd-col-form-label required"
-            for="pipe_id"
-            i18n>Name</label>
+          <label class="cd-col-form-label required"
+                 for="pipe_id"
+                 i18n>Name</label>
           <div class="cd-col-form-input">
-            <input
-              class="form-control"
-              type="text"
-              placeholder="Pipe Name..."
-              id="pipe_id"
-              name="pipe_id"
-              formControlName="pipe_id"
-              [readonly]="editing"/>
-          </div>
+            <input class="form-control"
+                   type="text"
+                   placeholder="Pipe Name..."
+                   id="pipe_id"
+                   name="pipe_id"
+                   formControlName="pipe_id"
+                   [readonly]="editing"/>
           </div>
+        </div>
           <div class="form-group row">
-            <label
-              class="cd-col-form-label required"
-              for="source_zone"
-              i18n>Source Zone </label>
+            <label class="cd-col-form-label required"
+                   for="source_zone"
+                   i18n>Source Zone </label>
             <div class="cd-col-form-input">
               <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'source_zones', zone: sourceZones }"></ng-container>
             </div>
           </div>
           <div class="form-group row">
-            <label
-              class="cd-col-form-label required"
-              for="destination_zone"
-              i18n>Destination Zone</label>
+            <label class="cd-col-form-label required"
+                   for="destination_zone"
+                   i18n>Destination Zone</label>
             <div class="cd-col-form-input">
               <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'destination_zones', zone: destZones }"></ng-container>
             </div>
           </div>
         <div class="form-group row">
-          <label
-            class="cd-col-form-label"
-            for="bucket"
-            i18n>Bucket Name</label>
+          <label class="cd-col-form-label"
+                 for="bucket"
+                 i18n>Bucket Name</label>
           <div class="cd-col-form-input">
-            <input
-              id="bucket"
-              name="bucket"
-              class="form-control"
-              type="text"
-              i18n-placeholder
-              placeholder="Bucket Name..."
-              formControlName="bucket_name"/>
+            <input id="bucket"
+                   name="bucket"
+                   class="form-control"
+                   type="text"
+                   i18n-placeholder
+                   placeholder="Bucket Name..."
+                   formControlName="bucket_name"/>
           </div>
         </div>
         <div class="form-group row">
-          <label
-            class="cd-col-form-label"
-            for="source_bucket"
-            i18n>Source Bucket</label>
+          <label class="cd-col-form-label"
+                 for="source_bucket"
+                 i18n>Source Bucket</label>
           <div class="cd-col-form-input">
-            <input
-              id="source_bucket"
-              name="source_bucket"
-              class="form-control"
-              type="text"
-              i18n-placeholder
-              placeholder="Source Bucket Name..."
-              formControlName="source_bucket"/>
+            <input id="source_bucket"
+                   name="source_bucket"
+                   class="form-control"
+                   type="text"
+                   i18n-placeholder
+                   placeholder="Source Bucket Name..."
+                   formControlName="source_bucket"/>
           </div>
           </div>
         <div class="form-group row">
-          <label
-            class="cd-col-form-label"
-            for="dest_bucket"
-            i18n>Destination Bucket</label>
+          <label class="cd-col-form-label"
+                 for="dest_bucket"
+                 i18n>Destination Bucket</label>
           <div class="cd-col-form-input">
-            <input
-              id="dest_bucket"
-              name="dest_bucket"
-              class="form-control"
-              type="text"
-              i18n-placeholder
-              placeholder="Destination Bucket Name..."
-              formControlName="destination_bucket"/>
+            <input id="dest_bucket"
+                   name="dest_bucket"
+                   class="form-control"
+                   type="text"
+                   i18n-placeholder
+                   placeholder="Destination Bucket Name..."
+                   formControlName="destination_bucket"/>
           </div>
         </div>
       </div>
       <div class="modal-footer">
-        <cd-form-button-panel
-          (submitActionEvent)="submit()"
-          [form]="pipeForm"
-          [submitText]="(action | titlecase) + ' ' + 'Pipe'">
+        <cd-form-button-panel (submitActionEvent)="submit()"
+                              [form]="pipeForm"
+                              [submitText]="(action | titlecase) + ' ' + 'Pipe'">
         </cd-form-button-panel>
       </div>
     </form>
   </ng-container>
 </cd-modal>
 
-<ng-template
-  #zoneMultiSelect
-  let-name="name"
-  let-zone="zone">
-  <cd-select-badges
-    id="{{ name }}"
-    name="{{ name }}"
-    [customBadges]="zone.customBadges"
-    [customBadgeValidators]="zone.data.validators"
-    [messages]="zone.data.messages"
-    [data]="zone.data.selected"
-    [options]="zone.data.available"
-    (selection)="onZoneSelection(name)">
+<ng-template #zoneMultiSelect
+             let-name="name"
+             let-zone="zone">
+  <cd-select-badges id="{{ name }}"
+                    name="{{ name }}"
+                    [customBadges]="zone.customBadges"
+                    [customBadgeValidators]="zone.data.validators"
+                    [messages]="zone.data.messages"
+                    [data]="zone.data.selected"
+                    [options]="zone.data.available"
+                    (selection)="onZoneSelection(name)">
   </cd-select-badges>
-  <i
-    *ngIf="zone.data.selected.length <= 0"
-    i18n-title
-    title="Pipe should be associated with {{ name }}"
-    class="{{ icons.warning }} icon-warning-color">
+  <i *ngIf="zone.data.selected.length <= 0"
+     i18n-title
+     title="Pipe should be associated with {{ name?.split('_').join(' ') }}"
+     class="{{ icons.warning }} icon-warning-color">
   </i>
-  <span
-    class="invalid-feedback"
-    *ngIf="pipeForm.showError(name, frm, 'required')"
-    i18n>{{ name }} selection is required!
+  <span class="invalid-feedback"
+        *ngIf="pipeForm.showError(name, frm, 'required')"
+        i18n>{{ name?.split('_').join(' ') }} selection is required!
   </span>
 </ng-template>
index 29d32ea967adf665824ad383f71603f731f950b4..73bca12ece3aa091752644f40d9395edd7b57dfb 100644 (file)
@@ -83,6 +83,7 @@ export class RgwMultisiteSyncPipeModalComponent implements OnInit {
           });
           this.sourceZones.data.available = [...zones];
           if (this.editing) {
+            this.pipeForm.get('pipe_id').disable();
             this.sourceZones.data.selected = this.pipeSelectedRow.source.zones;
             this.destZones.data.selected = this.pipeSelectedRow.dest.zones;
             this.pipeForm.patchValue({
index f1c2af06fe5a5c3758d175a536587c86d1337fe1..b9c6b2c96513ff5007eea283aab78e5a16e38e6f 100644 (file)
@@ -1,24 +1,19 @@
-<div class="cd-col-form">
-  <form
-    name="bucketForm"
-    #frm="ngForm"
-    [formGroup]="syncPolicyForm"
-    *cdFormLoading="loading"
-    novalidate>
-    <div class="card">
-      <div
-        i18n="form title"
-        class="card-header">
-        {{ action | titlecase }} {{ resource | upperFirst }}
-      </div>
-
-      <div class="card-body">
+<cd-modal [pageURL]="pageURL">
+  <span class="modal-title"
+        i18n>{{ action | titlecase }} {{ resource | upperFirst }}</span>
+  <ng-container class="modal-content">
+    <form
+      #frm="ngForm"
+      [formGroup]="syncPolicyForm"
+      *cdFormLoading="loading"
+      novalidate>
+      <div class="modal-body">
         <!-- Group Id -->
         <div class="form-group row">
           <label
             class="cd-col-form-label required"
             for="group_id"
-            i18n>Group Id</label>
+            i18n>Group Name</label>
           <div class="cd-col-form-input">
             <input
               id="group_id"
@@ -26,7 +21,7 @@
               class="form-control"
               type="text"
               i18n-placeholder
-              placeholder="Group Id..."
+              placeholder="Group Name..."
               formControlName="group_id"
               [readonly]="editing"/>
             <span
@@ -35,7 +30,6 @@
               i18n>This field is required.</span>
           </div>
         </div>
-
         <!-- Status -->
         <div class="form-group row">
           <label
@@ -64,7 +58,6 @@
               i18n>This field is required.</span>
           </div>
         </div>
-
         <!-- Bucket Name -->
         <div class="form-group row">
           <label
@@ -80,7 +73,6 @@
               i18n-placeholder
               placeholder="Bucket Name..."
               formControlName="bucket_name"
-              [readonly]="editing"
               [ngbTypeahead]="bucketDataSource"/>
             <span
               class="invalid-feedback"
         </div>
       </div>
 
-      <div class="card-footer">
-        <cd-form-button-panel
-          (submitActionEvent)="submit()"
-          [form]="syncPolicyForm"
-          [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
-          wrappingClass="text-right"></cd-form-button-panel>
+      <div class="modal-footer">
+        <div class="text-right">
+          <cd-form-button-panel (submitActionEvent)="submit()"
+                                [form]="syncPolicyForm"
+                                [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"></cd-form-button-panel>
+        </div>
       </div>
-    </div>
-  </form>
-</div>
+    </form>
+  </ng-container>
+</cd-modal>
index 300f61712353a1d4f1fe52bb269fa9dfbc91e098..dda1671e531f70091f9cedcec0770b76d0d65ff3 100644 (file)
@@ -12,7 +12,7 @@ import {
 } from 'rxjs/operators';
 import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
-import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
@@ -32,7 +32,7 @@ export class RgwMultisiteSyncPolicyFormComponent extends CdForm implements OnIni
   action: string;
   resource: string;
   syncPolicyStatus = RgwMultisiteSyncPolicyStatus;
-
+  pageURL: string;
   bucketDataSource = (text$: Observable<string>) => {
     return text$.pipe(
       debounceTime(200),
@@ -51,11 +51,12 @@ export class RgwMultisiteSyncPolicyFormComponent extends CdForm implements OnIni
     private rgwBucketService: RgwBucketService
   ) {
     super();
-    this.editing = this.router.url.startsWith(`/rgw/multisite/sync-policy/${URLVerbs.EDIT}`);
+    this.editing = this.router.url.includes('(modal:edit');
     this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
     this.resource = $localize`Sync Policy Group`;
     this.createForm();
     this.loadingReady();
+    this.pageURL = 'rgw/multisite/sync-policy';
   }
 
   ngOnInit(): void {
@@ -70,6 +71,7 @@ export class RgwMultisiteSyncPolicyFormComponent extends CdForm implements OnIni
             .subscribe((syncPolicy: any) => {
               this.loadingReady();
               if (syncPolicy) {
+                this.syncPolicyForm.get('bucket_name').disable();
                 this.syncPolicyForm.patchValue({
                   group_id: syncPolicy.id,
                   status: syncPolicy.status,
@@ -84,6 +86,11 @@ export class RgwMultisiteSyncPolicyFormComponent extends CdForm implements OnIni
     }
   }
 
+  goToListView() {
+    // passing state in order to return to same tab on details page
+    this.router.navigate([this.pageURL, { outlets: { modal: null }, state: { reload: true } }]);
+  }
+
   createForm() {
     this.syncPolicyForm = this.fb.group({
       group_id: ['', Validators.required],
@@ -92,11 +99,6 @@ export class RgwMultisiteSyncPolicyFormComponent extends CdForm implements OnIni
     });
   }
 
-  goToListView() {
-    // passing state in order to return to same tab on details page
-    this.router.navigate(['/rgw/multisite'], { state: { activeId: 'syncPolicy' } });
-  }
-
   submit() {
     if (this.syncPolicyForm.pristine) {
       this.goToListView();
@@ -111,7 +113,7 @@ export class RgwMultisiteSyncPolicyFormComponent extends CdForm implements OnIni
 
     if (!this.editing) {
       // Add
-      this.rgwMultisiteService.createSyncPolicyGroup(this.syncPolicyForm.value).subscribe(
+      this.rgwMultisiteService.createSyncPolicyGroup(this.syncPolicyForm.getRawValue()).subscribe(
         () => {
           this.notificationService.show(
             NotificationType.success,
@@ -125,7 +127,7 @@ export class RgwMultisiteSyncPolicyFormComponent extends CdForm implements OnIni
         }
       );
     } else {
-      this.rgwMultisiteService.modifySyncPolicyGroup(this.syncPolicyForm.value).subscribe(
+      this.rgwMultisiteService.modifySyncPolicyGroup(this.syncPolicyForm.getRawValue()).subscribe(
         () => {
           this.notificationService.show(
             NotificationType.success,
index 2dd4e239cf57ff6589e3d596941580a8396dd5ef..6a5cf899b10c8d8e016081ffe05236e38bf8ff32 100644 (file)
@@ -1,3 +1,5 @@
+<cd-rgw-multisite-tabs></cd-rgw-multisite-tabs>
+
 <legend i18n>
   Multisite Sync Policy
   <cd-help-text>
@@ -41,3 +43,5 @@
     Are you sure you want to delete these policy groups?
   </cd-alert-panel>
 </ng-template>
+<router-outlet name="modal"
+               (deactivate)="getPolicyList()"></router-outlet>
index 422e796671b38bfb7fc9987352d1f7fb6af923fb..3606e0387c594ddd551f2f8747b8b4b5ad448ebe 100644 (file)
@@ -1,10 +1,12 @@
 import { TitleCasePipe } from '@angular/common';
 import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { Router } from '@angular/router';
 import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs';
+import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
 import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
-import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
 import { TableComponent } from '~/app/shared/datatable/table/table.component';
 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
 import { Icons } from '~/app/shared/enum/icons.enum';
@@ -43,10 +45,11 @@ export class RgwMultisiteSyncPolicyComponent extends ListWithDetails implements
     private rgwMultisiteService: RgwMultisiteService,
     private titleCasePipe: TitleCasePipe,
     private actionLabels: ActionLabelsI18n,
-    private urlBuilder: URLBuilderService,
     private authStorageService: AuthStorageService,
     private modalService: ModalService,
-    private taskWrapper: TaskWrapperService
+    private taskWrapper: TaskWrapperService,
+    private router: Router,
+    private rgwDaemonService: RgwDaemonService
   ) {
     super();
   }
@@ -92,28 +95,26 @@ export class RgwMultisiteSyncPolicyComponent extends ListWithDetails implements
         flexGrow: 1
       }
     ];
-    const getSyncGroupName = () => {
-      if (this.selection.first() && this.selection.first().groupName) {
-        if (this.selection.first().bucket) {
-          return `${encodeURIComponent(this.selection.first().groupName)}/${encodeURIComponent(
-            this.selection.first().bucket
-          )}`;
-        }
-        return `${encodeURIComponent(this.selection.first().groupName)}`;
+    this.rgwDaemonService.list().subscribe();
+    const getEditURL = () => {
+      if (this.selection.first().groupName && this.selection.first().bucket) {
+        return `${URLVerbs.EDIT}/${this.selection.first().groupName}/${
+          this.selection.first().bucket
+        }`;
       }
-      return '';
+      return `${URLVerbs.EDIT}/${this.selection.first().groupName}`;
     };
     const addAction: CdTableAction = {
       permission: 'create',
       icon: Icons.add,
-      routerLink: () => this.urlBuilder.getCreate(),
+      click: () => this.router.navigate([BASE_URL, { outlets: { modal: URLVerbs.CREATE } }]),
       name: this.actionLabels.CREATE,
       canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
     };
     const editAction: CdTableAction = {
       permission: 'update',
       icon: Icons.edit,
-      routerLink: () => this.urlBuilder.getEdit(getSyncGroupName()),
+      click: () => this.router.navigate([BASE_URL, { outlets: { modal: getEditURL() } }]),
       name: this.actionLabels.EDIT
     };
     const deleteAction: CdTableAction = {
@@ -146,14 +147,16 @@ export class RgwMultisiteSyncPolicyComponent extends ListWithDetails implements
     this.selection = selection;
   }
 
-  getPolicyList(context: CdTableFetchDataContext) {
+  getPolicyList(context?: CdTableFetchDataContext) {
     this.rgwMultisiteService.getSyncPolicy('', '', true).subscribe(
       (resp: object[]) => {
         this.syncPolicyData = [];
         this.transformSyncPolicyData(resp);
       },
       () => {
-        context.error();
+        if (context) {
+          context.error();
+        }
       }
     );
   }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.html
new file mode 100644 (file)
index 0000000..8cc420b
--- /dev/null
@@ -0,0 +1,16 @@
+<ul class="nav nav-tabs">
+  <li class="nav-item">
+    <a class="nav-link"
+       routerLink="/rgw/multisite/configuration"
+       routerLinkActive="active"
+       ariaCurrentWhenActive="page"
+       i18n>Configuration</a>
+  </li>
+  <li class="nav-item">
+    <a class="nav-link"
+       routerLink="//rgw/multisite/sync-policy"
+       routerLinkActive="active"
+       ariaCurrentWhenActive="page"
+       i18n>Sync Policy</a>
+  </li>
+</ul>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.spec.ts
new file mode 100644 (file)
index 0000000..a22a498
--- /dev/null
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwMultisiteTabsComponent } from './rgw-multisite-tabs.component';
+
+describe('RgwMultisiteTabsComponent', () => {
+  let component: RgwMultisiteTabsComponent;
+  let fixture: ComponentFixture<RgwMultisiteTabsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [RgwMultisiteTabsComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwMultisiteTabsComponent);
+    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-tabs/rgw-multisite-tabs.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-tabs/rgw-multisite-tabs.component.ts
new file mode 100644 (file)
index 0000000..369f267
--- /dev/null
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'cd-rgw-multisite-tabs',
+  templateUrl: './rgw-multisite-tabs.component.html',
+  styleUrls: ['./rgw-multisite-tabs.component.scss']
+})
+export class RgwMultisiteTabsComponent {}
index accd83fc7387b1e9b2fc83e5550dc9a3e6987f5f..aa51e7502bb53d74e8c6668f97c674096183a01c 100644 (file)
@@ -59,6 +59,7 @@ import { RgwMultisiteSyncPolicyFormComponent } from './rgw-multisite-sync-policy
 import { RgwMultisiteSyncPolicyDetailsComponent } from './rgw-multisite-sync-policy-details/rgw-multisite-sync-policy-details.component';
 import { RgwMultisiteSyncFlowModalComponent } from './rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component';
 import { RgwMultisiteSyncPipeModalComponent } from './rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component';
+import { RgwMultisiteTabsComponent } from './rgw-multisite-tabs/rgw-multisite-tabs.component';
 
 @NgModule({
   imports: [
@@ -122,7 +123,8 @@ import { RgwMultisiteSyncPipeModalComponent } from './rgw-multisite-sync-pipe-mo
     RgwMultisiteSyncPolicyFormComponent,
     RgwMultisiteSyncPolicyDetailsComponent,
     RgwMultisiteSyncFlowModalComponent,
-    RgwMultisiteSyncPipeModalComponent
+    RgwMultisiteSyncPipeModalComponent,
+    RgwMultisiteTabsComponent
   ],
   providers: [TitleCasePipe]
 })
@@ -215,21 +217,31 @@ const routes: Routes = [
     path: 'multisite',
     data: { breadcrumbs: 'Multi-site' },
     children: [
-      { path: '', component: RgwMultisiteDetailsComponent },
+      { path: '', redirectTo: 'configuration', pathMatch: 'full' },
       {
-        path: `sync-policy/${URLVerbs.CREATE}`,
-        component: RgwMultisiteSyncPolicyFormComponent,
-        data: { breadcrumbs: `${ActionLabels.CREATE} Sync Policy` }
+        path: 'configuration',
+        component: RgwMultisiteDetailsComponent
       },
       {
-        path: `sync-policy/${URLVerbs.EDIT}/:groupName`,
-        component: RgwMultisiteSyncPolicyFormComponent,
-        data: { breadcrumbs: `${ActionLabels.EDIT} Sync Policy` }
-      },
-      {
-        path: `sync-policy/${URLVerbs.EDIT}/:groupName/:bucketName`,
-        component: RgwMultisiteSyncPolicyFormComponent,
-        data: { breadcrumbs: `${ActionLabels.EDIT} Sync Policy` }
+        path: 'sync-policy',
+        component: RgwMultisiteSyncPolicyComponent,
+        children: [
+          {
+            path: `${URLVerbs.CREATE}`,
+            component: RgwMultisiteSyncPolicyFormComponent,
+            outlet: 'modal'
+          },
+          {
+            path: `${URLVerbs.EDIT}/:groupName`,
+            component: RgwMultisiteSyncPolicyFormComponent,
+            outlet: 'modal'
+          },
+          {
+            path: `${URLVerbs.EDIT}/:groupName/:bucketName`,
+            component: RgwMultisiteSyncPolicyFormComponent,
+            outlet: 'modal'
+          }
+        ]
       }
     ]
   },