Repository: ceph/ceph
Pull request #61273: mgr/dashboard: Add RGW topic listing in dashboard
Author: login: pujaoshahu
Labels: name: pybind
name: dashboard
name: api-change
name: ui-tested
Created: 2025-01-09T05:24:51Z | Merged: 2025-04-14T07:44:48Z

Depends on https://github.com/ceph/ceph/pull/61075

Fixes:  https://tracker.ceph.com/issues/69143

Signed-off-by:  Puja Shahu <pshahu@redhat.com>

![image](https://github.com/user-attachments/assets/541ff237-4b21-48b8-801f-2977284ac6cc)




<!--
  - Please give your pull request a title like

      [component]: [short description]

  - Please use this format for each git commit message:

      [component]: [short description]

      [A longer multiline description]

      Fixes: [ticket URL on tracker.ceph.com, create one if necessary]
      Signed-off-by: [Your Name] <[your email]>

    For examples, use "git log".
-->

## Contribution Guidelines
- To sign and title your commits, please refer to [Submitting Patches to Ceph](https://github.com/ceph/ceph/blob/main/SubmittingPatches.rst).

- If you are submitting a fix for a stable branch (e.g. "quincy"), please refer to [Submitting Patches to Ceph - Backports](https://github.com/ceph/ceph/blob/master/SubmittingPatches-backports.rst) for the proper workflow.

- When filling out the below checklist, you may click boxes directly in the GitHub web UI.  When entering or editing the entire PR message in the GitHub web UI editor, you may also select a checklist item by adding an `x` between the brackets: `[x]`.  Spaces and capitalization matter when checking off items this way.

## Checklist
- Tracker (select at least one)
  - [x] References tracker ticket
  - [ ] Very recent bug; references commit where it was introduced
  - [ ] New feature (ticket optional)
  - [ ] Doc update (no ticket needed)
  - [ ] Code cleanup (no ticket needed)
- Component impact
  - [x] Affects [Dashboard](https://tracker.ceph.com/projects/dashboard/issues/new), opened tracker ticket
  - [ ] Affects [Orchestrator](https://tracker.ceph.com/projects/orchestrator/issues/new), opened tracker ticket
  - [ ] No impact that needs to be tracked
- Documentation (select at least one)
  - [ ] Updates relevant documentation
  - [x] No doc update is appropriate
- Tests (select at least one)
  - [ ] Includes [unit test(s)](https://docs.ceph.com/en/latest/dev/developer_guide/tests-unit-tests/)
  - [ ] Includes [integration test(s)](https://docs.ceph.com/en/latest/dev/developer_guide/testing_integration_tests/)
  - [ ] Includes bug reproducer
  - [x] No tests

<details>
<summary>Show available Jenkins commands</summary>

- `jenkins retest this please`
- `jenkins test classic perf`
- `jenkins test crimson perf`
- `jenkins test signed`
- `jenkins test make check`
- `jenkins test make check arm64`
- `jenkins test submodules`
- `jenkins test dashboard`
- `jenkins test dashboard cephadm`
- `jenkins test api`
- `jenkins test docs`
- `jenkins render docs`
- `jenkins test ceph-volume all`
- `jenkins test ceph-volume tox`
- `jenkins test windows`
- `jenkins test rook e2e`
</details>



Diff:
diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py
index 4c8d0b5e1eb93..10e6d0dbbc678 100755
--- a/src/pybind/mgr/dashboard/controllers/rgw.py
+++ b/src/pybind/mgr/dashboard/controllers/rgw.py
@@ -1506,7 +1506,7 @@ def create(
     def list(self, uid: Optional[str] = None, tenant: Optional[str] = None):
         rgw_topic_instance = RgwTopicmanagement()
         result = rgw_topic_instance.list_topics(uid, tenant)
-        return result
+        return result['topics'] if 'topics' in result else []
 
     @EndpointDoc(
         "Get RGW Topic",
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
new file mode 100644
index 0000000000000..dec284392da59
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
@@ -0,0 +1,89 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details"
+             i18n-heading>
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+             data-testid="rgw-topic-details">
+        <tbody>
+          <tr>
+            <td i18n
+                class="bold">Push endpoint arguments</td>
+            <td>{{ selection?.dest?.push_endpoint_args }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint topic</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Stored secret</td>
+            <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent</td>
+            <td>{{ selection?.dest?.persistent }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent queue</td>
+            <td>{{ selection?.dest?.persistent_queue }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Time to live</td>
+            <td>{{ selection?.dest?.time_to_live }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Max retries</td>
+            <td>{{ selection?.dest?.max_retries }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Retry sleep duration</td>
+            <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Opaque data</td>
+            <td>{{ selection?.opaqueData }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </cds-tab>
+    <cds-tab heading="Policies"
+             i18n-heading>
+      <div class="table-scroller">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold w-25 ">Policy</td>
+              <td><pre>{{ policy | json  }}</pre></td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </cds-tab>
+    <cds-tab heading="Subscribed buckets"
+             i18n-heading>
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
+        <tbody>
+          <tr>
+            <td i18n
+                class="bold w-25">Subscribed buckets</td>
+            <td><pre>{{ selection.subscribed_buckets | json}}</pre></td>
+          </tr>
+        </tbody>
+      </table>
+    </cds-tab>
+  </cds-tabs>
+</ng-container>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.scss
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts
new file mode 100644
index 0000000000000..85b1b1b00fa43
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts
@@ -0,0 +1,94 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { RgwTopicDetailsComponent } from './rgw-topic-details.component';
+import { TopicDetails } from '~/app/shared/models/topic.model';
+
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+const mockDestination: Destination = {
+  push_endpoint: 'http://localhost:8080',
+  push_endpoint_args: 'args',
+  push_endpoint_topic: 'topic',
+  stored_secret: 'secret',
+  persistent: true,
+  persistent_queue: 'queue',
+  time_to_live: 3600,
+  max_retries: 5,
+  retry_sleep_duration: 10
+};
+
+describe('RgwTopicDetailsComponent', () => {
+  let component: RgwTopicDetailsComponent;
+  let fixture: ComponentFixture<RgwTopicDetailsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [RgwTopicDetailsComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwTopicDetailsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should parse policy string correctly', () => {
+    const mockSelection: TopicDetails = {
+      name: 'testHttp',
+      owner: 'ownerName',
+      arn: 'arnValue',
+      dest: mockDestination,
+      policy: '{"key": "value"}',
+      opaqueData: 'test@12345',
+      subscribed_buckets: []
+    };
+
+    component.selection = mockSelection;
+    component.ngOnChanges({
+      selection: {
+        currentValue: mockSelection,
+        previousValue: null,
+        firstChange: true,
+        isFirstChange: () => true
+      }
+    });
+
+    expect(component.policy).toEqual({ key: 'value' });
+  });
+
+  it('should set policy to empty object if policy is not a string', () => {
+    const mockSelection: TopicDetails = {
+      name: 'testHttp',
+      owner: 'ownerName',
+      arn: 'arnValue',
+      dest: mockDestination,
+      policy: '{}',
+      subscribed_buckets: [],
+      opaqueData: ''
+    };
+
+    component.selection = mockSelection;
+    component.ngOnChanges({
+      selection: {
+        currentValue: mockSelection,
+        previousValue: null,
+        firstChange: true,
+        isFirstChange: () => true
+      }
+    });
+
+    expect(component.policy).toEqual({});
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
new file mode 100644
index 0000000000000..359c927ae4609
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
@@ -0,0 +1,29 @@
+import { Component, Input, SimpleChanges, OnChanges } from '@angular/core';
+
+import { TopicDetails } from '~/app/shared/models/topic.model';
+import * as _ from 'lodash';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent implements OnChanges {
+  @Input()
+  selection: TopicDetails;
+  policy: string;
+  constructor() {}
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['selection'] && this.selection) {
+      if (_.isString(this.selection.policy)) {
+        try {
+          this.policy = JSON.parse(this.selection.policy);
+        } catch (e) {
+          this.policy = '{}';
+        }
+      } else {
+        this.policy = this.selection.policy || {};
+      }
+    }
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.html
new file mode 100644
index 0000000000000..22fce6e40a78c
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.html
@@ -0,0 +1,15 @@
+  <ng-container *ngIf="topic$ | async as topics">
+  <cd-table #table
+            [autoReload]="false"
+            [data]="topics"
+            [columns]="columns"
+            columnMode="flex"
+            selectionType="single"
+            [hasDetails]="true"
+            (setExpandedRow)="setExpandedRow($event)"
+            (updateSelection)="updateSelection($event)"
+            (fetchData)="fetchData($event)">
+  <cd-rgw-topic-details *cdTableDetail
+                        [selection]="expandedRow"></cd-rgw-topic-details>
+  </cd-table>
+  </ng-container>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.scss
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts
new file mode 100644
index 0000000000000..c87427862dd4e
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts
@@ -0,0 +1,55 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwTopicListComponent } from './rgw-topic-list.component';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { SharedModule } from '~/app/shared/shared.module';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { RgwTopicDetailsComponent } from '../rgw-topic-details/rgw-topic-details.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { RouterTestingModule } from '@angular/router/testing';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ToastrModule } from 'ngx-toastr';
+describe('RgwTopicListComponent', () => {
+  let component: RgwTopicListComponent;
+  let fixture: ComponentFixture<RgwTopicListComponent>;
+  let rgwtTopicService: RgwTopicService;
+  let rgwTopicServiceListSpy: jasmine.Spy;
+
+  configureTestBed({
+    declarations: [RgwTopicListComponent, RgwTopicDetailsComponent],
+    imports: [BrowserAnimationsModule, RouterTestingModule, HttpClientTestingModule, SharedModule]
+  });
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [
+        BrowserAnimationsModule,
+        SharedModule,
+        HttpClientTestingModule,
+        ToastrModule.forRoot(),
+        RouterTestingModule
+      ],
+
+      declarations: [RgwTopicListComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwTopicListComponent);
+    component = fixture.componentInstance;
+    rgwtTopicService = TestBed.inject(RgwTopicService);
+    rgwTopicServiceListSpy = spyOn(rgwtTopicService, 'listTopic').and.callThrough();
+    fixture = TestBed.createComponent(RgwTopicListComponent);
+    component = fixture.componentInstance;
+    spyOn(component, 'setTableRefreshTimeout').and.stub();
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+    expect(rgwTopicServiceListSpy).toHaveBeenCalledTimes(1);
+  });
+
+  it('should call listTopic on ngOnInit', () => {
+    component.ngOnInit();
+    expect(rgwTopicServiceListSpy).toHaveBeenCalled();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
new file mode 100644
index 0000000000000..4c3dace75cec7
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
@@ -0,0 +1,89 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { BehaviorSubject, Observable, of } from 'rxjs';
+import { Topic } from '~/app/shared/models/topic.model';
+import { catchError, shareReplay, switchMap } from 'rxjs/operators';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  errorMessage: string;
+  selection: CdTableSelection = new CdTableSelection();
+  topic$: Observable<Topic[]>;
+  subject = new BehaviorSubject<Topic[]>([]);
+  name: string;
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Amazon resource name`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Push endpoint`,
+        prop: 'dest.push_endpoint',
+        flexGrow: 2
+      }
+    ];
+    this.topic$ = this.subject.pipe(
+      switchMap(() =>
+        this.rgwTopicService.listTopic().pipe(
+          catchError(() => {
+            this.context.error();
+            return of(null);
+          })
+        )
+      ),
+      shareReplay(1)
+    );
+  }
+
+  fetchData() {
+    this.subject.next([]);
+  }
+
+  updateSelection(selection: CdTableSelection) {
+    this.selection = selection;
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
index c5aeef5e91fb7..83a0414b000b1 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
@@ -96,6 +96,8 @@ import { RgwBucketLifecycleListComponent } from './rgw-bucket-lifecycle-list/rgw
 import { RgwRateLimitComponent } from './rgw-rate-limit/rgw-rate-limit.component';
 import { RgwRateLimitDetailsComponent } from './rgw-rate-limit-details/rgw-rate-limit-details.component';
 import { NfsClusterComponent } from '../nfs/nfs-cluster/nfs-cluster.component';
+import { RgwTopicListComponent } from './rgw-topic-list/rgw-topic-list.component';
+import { RgwTopicDetailsComponent } from './rgw-topic-details/rgw-topic-details.component';
 
 @NgModule({
   imports: [
@@ -128,11 +130,11 @@ import { NfsClusterComponent } from '../nfs/nfs-cluster/nfs-cluster.component';
     SelectModule,
     NumberModule,
     TabsModule,
-    RadioModule,
     TagModule,
     TooltipModule,
     ComboBoxModule,
-    ToggletipModule
+    ToggletipModule,
+    RadioModule
   ],
   exports: [
     RgwDaemonDetailsComponent,
@@ -193,7 +195,9 @@ import { NfsClusterComponent } from '../nfs/nfs-cluster/nfs-cluster.component';
     RgwStorageClassFormComponent,
     RgwBucketTieringFormComponent,
     RgwBucketLifecycleListComponent,
-    RgwRateLimitDetailsComponent
+    RgwRateLimitDetailsComponent,
+    RgwTopicListComponent,
+    RgwTopicDetailsComponent
   ],
   providers: [TitleCasePipe]
 })
@@ -392,6 +396,11 @@ const routes: Routes = [
     path: 'configuration',
     data: { breadcrumbs: 'Configuration' },
     children: [{ path: '', component: RgwConfigurationPageComponent }]
+  },
+  {
+    path: 'topic',
+    data: { breadcrumbs: 'Topic' },
+    children: [{ path: '', component: RgwTopicListComponent }]
   }
 ];
 
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
index 53d36bd1c849e..8bd4895eb646f 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
@@ -208,16 +208,21 @@
                             i18n-title
                             [useRouter]="true"
                             class="tc_submenuitem tc_submenuitem_rgw_overview"><span i18n>Overview</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/user"
+                            title="Users"
+                            i18n-title
+                            [useRouter]="true"
+                            class="tc_submenuitem tc_submenuitem_rgw_users"><span i18n>Users</span></cds-sidenav-item>
           <cds-sidenav-item route="/rgw/bucket"
                             title="Buckets"
                             i18n-title
                             [useRouter]="true"
                             class="tc_submenuitem tc_submenuitem_rgw_buckets"><span i18n>Buckets</span></cds-sidenav-item>
-          <cds-sidenav-item route="/rgw/user"
-                            title="Users"
-                            i18n-title
+          <cds-sidenav-item route="/rgw/topic"
                             [useRouter]="true"
-                            class="tc_submenuitem tc_submenuitem_rgw_users"><span i18n>Users</span></cds-sidenav-item>
+                            title="Topics"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_topics"><span i18n>Topics</span></cds-sidenav-item>
           <cds-sidenav-item route="/rgw/tiering"
                             title="Tiering"
                             i18n-title
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts
index 86bc4610acc7d..a9d512d9b31dd 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.spec.ts
@@ -137,8 +137,9 @@ describe('NavigationComponent', () => {
         [
           '.tc_menuitem_rgw',
           '.tc_submenuitem_rgw_daemons',
+          '.tc_submenuitem_rgw_users',
           '.tc_submenuitem_rgw_buckets',
-          '.tc_submenuitem_rgw_users'
+          '.tc_submenuitem_rgw_topics'
         ]
       ]
     ];
@@ -185,8 +186,9 @@ describe('NavigationComponent', () => {
         [
           '.tc_menuitem_rgw',
           '.tc_submenuitem_rgw_daemons',
+          '.tc_submenuitem_rgw_users',
           '.tc_submenuitem_rgw_buckets',
-          '.tc_submenuitem_rgw_users'
+          '.tc_submenuitem_rgw_topics'
         ]
       ]
     ];
@@ -246,8 +248,9 @@ describe('NavigationComponent', () => {
         '.tc_submenuitem_block_iscsi': 'iSCSI',
         '.tc_submenuitem_block_nvme': 'NVMe/TCP',
         '.tc_submenuitem_rgw_overview': 'Overview',
-        '.tc_submenuitem_rgw_buckets': 'Buckets',
         '.tc_submenuitem_rgw_users': 'Users',
+        '.tc_submenuitem_rgw_buckets': 'Buckets',
+        '.tc_submenuitem_rgw_topics': 'Topics',
         '.tc_submenuitem_rgw_multi-site': 'Multi-site',
         '.tc_submenuitem_rgw_daemons': 'Gateways',
         '.tc_submenuitem_rgw_nfs': 'NFS',
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.spec.ts
new file mode 100644
index 0000000000000..b30cf0443c89c
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.spec.ts
@@ -0,0 +1,56 @@
+import { TestBed } from '@angular/core/testing';
+
+import { RgwTopicService } from './rgw-topic.service';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+
+describe('RgwTopicService', () => {
+  let service: RgwTopicService;
+  let httpTesting: HttpTestingController;
+  configureTestBed({
+    imports: [HttpClientTestingModule]
+  });
+  configureTestBed({
+    imports: [HttpClientTestingModule],
+    providers: [RgwTopicService]
+  });
+
+  beforeEach(() => {
+    service = TestBed.inject(RgwTopicService);
+    httpTesting = TestBed.inject(HttpTestingController);
+  });
+
+  afterEach(() => {
+    httpTesting.verify();
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+
+  it('should call list with empty result', () => {
+    let result;
+    service.listTopic().subscribe((resp) => {
+      result = resp;
+    });
+    const req = httpTesting.expectOne(`api/rgw/topic`);
+    expect(req.request.method).toBe('GET');
+    req.flush([]);
+    expect(result).toEqual([]);
+  });
+  it('should call list with result', () => {
+    service.listTopic().subscribe((resp) => {
+      let result = resp;
+      return result;
+    });
+    let req = httpTesting.expectOne(`api/rgw/topic`);
+    expect(req.request.method).toBe('GET');
+    req.flush(['foo', 'bar']);
+  });
+
+  it('should call get', () => {
+    service.getTopic('foo').subscribe();
+    const req = httpTesting.expectOne(`api/rgw/topic/foo`);
+    expect(req.request.method).toBe('GET');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
new file mode 100644
index 0000000000000..8de7b32c04dbd
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
@@ -0,0 +1,24 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { Topic } from '~/app/shared/models/topic.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  listTopic(): Observable<Topic[]> {
+    return this.http.get<Topic[]>(this.baseURL);
+  }
+
+  getTopic(name: string) {
+    return this.http.get(`${this.baseURL}/${name}`);
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
new file mode 100644
index 0000000000000..31aa7b61c1225
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
@@ -0,0 +1,31 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface Topic {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Destination;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface TopicDetails {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Destination;
+  opaqueData: string;
+  policy: string;
+  subscribed_buckets: string[];
+}
diff --git a/src/pybind/mgr/dashboard/tests/test_rgw.py b/src/pybind/mgr/dashboard/tests/test_rgw.py
index 298b6a4c21361..00b2f5aff8818 100644
--- a/src/pybind/mgr/dashboard/tests/test_rgw.py
+++ b/src/pybind/mgr/dashboard/tests/test_rgw.py
@@ -535,28 +535,25 @@ def test_create_topic(self, mock_rgw_client, mock_get_daemons):
     def test_list_topic_with_details(self, mock_list_topics):
         mock_return_value = [
             {
-                "topic": {
-                    "owner": "dashboard",
-                    "name": "HttpTest",
-                    "dest": {
-                        "push_endpoint": "https://10.0.66.13:443",
-                        "push_endpoint_args": "verify_ssl=true",
-                        "push_endpoint_topic": "HttpTest",
-                        "stored_secret": False,
-                        "persistent": True,
-                        "persistent_queue": ":HttpTest",
-                        "time_to_live": "5",
-                        "max_retries": "2",
-                        "retry_sleep_duration": "2"
-                    },
-                    "arn": "arn:aws:sns:zg1-realm1::HttpTest",
-                    "opaqueData": "test123",
-                    "policy": "{}",
-                    "subscribed_buckets": []
-                }
+                "owner": "dashboard",
+                "name": "HttpTest",
+                "dest": {
+                    "push_endpoint": "https://10.0.66.13:443",
+                    "push_endpoint_args": "verify_ssl=true",
+                    "push_endpoint_topic": "HttpTest",
+                    "stored_secret": False,
+                    "persistent": True,
+                    "persistent_queue": ":HttpTest",
+                    "time_to_live": "5",
+                    "max_retries": "2",
+                    "retry_sleep_duration": "2"
+                },
+                "arn": "arn:aws:sns:zg1-realm1::HttpTest",
+                "opaqueData": "test123",
+                "policy": "{}",
+                "subscribed_buckets": []
             }
         ]
-
         mock_list_topics.return_value = mock_return_value
         controller = RgwTopic()
         result = controller.list(True, None)
@@ -565,8 +562,8 @@ def test_list_topic_with_details(self, mock_list_topics):
 
     @patch('dashboard.controllers.rgw.RgwTopic.get')
     def test_get_topic(self, mock_get_topic):
-        mock_return_value = {
-            "topic": {
+        mock_return_value = [
+            {
                 "owner": "dashboard",
                 "name": "HttpTest",
                 "dest": {
@@ -585,7 +582,7 @@ def test_get_topic(self, mock_get_topic):
                 "policy": "{}",
                 "subscribed_buckets": []
             }
-        }
+        ]
         mock_get_topic.return_value = mock_return_value
 
         controller = RgwTopic()


Reviews:
user: login: nmunet
body: Hi @pujaoshahu ,

Thank you for the PR. I haven’t had a chance to test it yet, but here are a few suggestions based on my initial review of the code.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-13T06:05:07Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-13T07:53:17Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-13T07:53:50Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-13T07:56:24Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-13T10:49:42Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-13T10:49:52Z
user: login: nmunet
body: Hi @pujaoshahu ,
here are few more from my side, looks good otherwise!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-15T09:17:27Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-15T11:59:29Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-15T13:14:01Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-15T13:14:12Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-15T13:15:16Z
user: login: nmunet
body: Thanks for the fixes, LGTM!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-01-15T14:44:21Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-15T17:44:11Z
user: login: Pegonzal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-23T17:19:08Z
user: login: Pegonzal
body: Thank you @pujaoshahu PR is looking good, I tested it and it is working as expected, I'm leaving you some comments from my side of things I saw.
Also, could you add some tests to this PR? 
Thanks!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-23T17:51:16Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T19:03:00Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T19:12:28Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T19:16:21Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T19:16:35Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T19:16:51Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T19:17:08Z
user: login: Pegonzal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-23T19:18:15Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T19:26:18Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T19:34:14Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T20:02:46Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T20:03:13Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-23T20:36:18Z
user: login: afreen23
body: Some initial comments, thanks
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-29T09:39:30Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-29T09:52:44Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-29T11:33:08Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-29T11:33:14Z
user: login: afreen23
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-30T10:52:05Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-31T04:50:54Z
user: login: afreen23
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-31T09:49:36Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T06:05:51Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T13:07:20Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T13:07:29Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T13:07:47Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T13:08:11Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T14:36:28Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T14:36:39Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T14:37:27Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T14:38:01Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T14:40:54Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T14:41:28Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T14:41:44Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T14:41:55Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T14:58:10Z
user: login: afreen23
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T12:03:03Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T05:19:13Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T05:19:28Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T05:19:40Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T05:20:31Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T05:20:48Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-21T05:20:52Z
user: login: nizamial09
body: Thank you @pujaoshahu, sorry to keep you waiting on this but left a couple of reviews.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T10:26:36Z
user: login: afreen23
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T21:57:28Z
user: login: afreen23
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T22:03:14Z
user: login: nizamial09
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T07:51:42Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:23:58Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:25:00Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:25:11Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:25:19Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:25:28Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:25:39Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:25:47Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:25:55Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:42:55Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T10:43:21Z
user: login: nizamial09
body: looks better now, thank you @pujaoshahu 

You have to fix the [unit test failures ](https://jenkins.ceph.com/job/ceph-pull-requests/154899/)and the lint failures, both front-end and back-end.

[Test Result](https://jenkins.ceph.com/job/ceph-pull-requests/154899/testReport/) (3 failures / +2)
[projectroot.src.pybind.mgr.dashboard.run_tox_mgr_dashboard_lint](https://jenkins.ceph.com/job/ceph-pull-requests/154899/testReport/junit/projectroot.src.pybind.mgr/dashboard/run_tox_mgr_dashboard_lint/)
[projectroot.src.test.mgr.mgr_dashboard_frontend_unittests](https://jenkins.ceph.com/job/ceph-pull-requests/154899/testReport/junit/projectroot.src.test/mgr/mgr_dashboard_frontend_unittests/)

And left a few minor comments, but all other LGTM, thank you

EDIT: also some extra things I noticed after I posted the review:
1. https://github.com/ceph/ceph/pull/61273#discussion_r2022209013
2. https://github.com/ceph/ceph/pull/61273#discussion_r2022211949
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-01T06:09:28Z
user: login: nizamial09
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T06:11:23Z
user: login: nizamial09
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-01T06:12:56Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:43:12Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:43:33Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:43:38Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:43:45Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:43:50Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:43:53Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:43:58Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:44:26Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:45:37Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:45:57Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:46:00Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:46:03Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:46:09Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:50:35Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T07:50:42Z
user: login: Pegonzal
body: Great work @pujaoshahu, overall PR looks good to me.

One thing I think we should look to add for the listing is an option for the user to filter the table items by tenant/user/owner
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T08:40:56Z
user: login: Pegonzal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T05:35:08Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-08T05:44:14Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-08T05:44:19Z
user: login: pujaoshahu
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-08T05:44:23Z
user: login: afreen23
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T07:44:17Z

Review comments:
diff_hunk: @@ -0,0 +1,80 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+// import { catchError, switchMap } from 'rxjs/operators';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid?: string = '';
+  tenant?: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`OpaqueData`,
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: nmunet
body: Opaque Data
```suggestion
        name: $localize`Opaque Data`,
```
created_at: 2025-01-13T05:44:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,80 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+// import { catchError, switchMap } from 'rxjs/operators';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid?: string = '';
+  tenant?: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`OpaqueData`,
+        prop: 'opaqueData',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadTopics(context?: CdTableFetchDataContext): void {
+    this.rgwTopicService.listTopic(this.uid, this.tenant).subscribe({
+      next: (data) => {
+        this.topics = data;
+      },
+      error: () => context.error()
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: nmunet
body: ```suggestion
      error: () => {
      if (context) {
      context.error()
      }
      }
```
created_at: 2025-01-13T05:45:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,80 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+// import { catchError, switchMap } from 'rxjs/operators';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid?: string = '';
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: nmunet
body: I don't feel this is syntactically correct, same with tenant
should be just `uid: string = '';`
correct me if I am wrong!
created_at: 2025-01-13T05:47:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -236,6 +236,11 @@
                             title="Configuration"
                             i18n-title
                             class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Configuration</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/topic"
+                            [useRouter]="true"
+                            title="Topic"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Topic</span></cds-sidenav-item>
path: src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
user: login: nmunet
body: Do we need this section here? I thought we are integrating it alongside bucket list view?

created_at: 2025-01-13T05:54:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -236,6 +236,11 @@
                             title="Configuration"
                             i18n-title
                             class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Configuration</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/topic"
+                            [useRouter]="true"
+                            title="Topic"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Topic</span></cds-sidenav-item>
path: src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
user: login: nmunet
body: If this is what is finalized then Should it not be `Bucket Notification` instead of `Topic`.
created_at: 2025-01-13T05:54:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,15 @@
+  <cd-table #table
+            [autoReload]="false"
+            [data]="topics"
+            [columns]="columns"
+            columnMode="flex"
+            selectionType="multiClick"
+            [hasDetails]="true"
+            (setExpandedRow)="setExpandedRow($event)"
+            (updateSelection)="updateSelection($event)"
+            identifier="topic_name"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.html
user: login: nmunet
body: As per my understanding we should use one of the prop name here, to uniquely identify each row
I don't see `topic_name` as valid prop in columns
created_at: 2025-01-13T06:00:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,15 @@
+  <cd-table #table
+            [autoReload]="false"
+            [data]="topics"
+            [columns]="columns"
+            columnMode="flex"
+            selectionType="multiClick"
+            [hasDetails]="true"
+            (setExpandedRow)="setExpandedRow($event)"
+            (updateSelection)="updateSelection($event)"
+            identifier="topic_name"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.html
user: login: pujaoshahu
body: changes are done 
created_at: 2025-01-13T07:53:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,80 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+// import { catchError, switchMap } from 'rxjs/operators';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid?: string = '';
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: changes are done 
created_at: 2025-01-13T07:53:50Z
author_association: NONE
diff_hunk: @@ -236,6 +236,11 @@
                             title="Configuration"
                             i18n-title
                             class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Configuration</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/topic"
+                            [useRouter]="true"
+                            title="Topic"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Topic</span></cds-sidenav-item>
path: src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
user: login: pujaoshahu
body: I discussed with Pedro, and we decided to separate the menu for topic listing. Due to this, I have made the necessary changes."


created_at: 2025-01-13T07:56:24Z
author_association: NONE
diff_hunk: @@ -0,0 +1,80 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+// import { catchError, switchMap } from 'rxjs/operators';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid?: string = '';
+  tenant?: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`OpaqueData`,
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: changes are made
created_at: 2025-01-13T10:49:42Z
author_association: NONE
diff_hunk: @@ -0,0 +1,80 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+// import { catchError, switchMap } from 'rxjs/operators';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid?: string = '';
+  tenant?: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`OpaqueData`,
+        prop: 'opaqueData',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadTopics(context?: CdTableFetchDataContext): void {
+    this.rgwTopicService.listTopic(this.uid, this.tenant).subscribe({
+      next: (data) => {
+        this.topics = data;
+      },
+      error: () => context.error()
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: changes are made
created_at: 2025-01-13T10:49:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,84 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+// import { catchError, switchMap } from 'rxjs/operators';
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: nmunet
body: can we pls remove this line
created_at: 2025-01-15T09:10:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent {
+  @Input()
+  selection: any;
+  tenant?: any;
+  topicname?: any;
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
user: login: nmunet
body: ```suggestion
  tenant: any;
  topicname: any;
```
created_at: 2025-01-15T09:11:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,98 @@
+  <ng-container *ngIf="!!selection">
+    <nav ngbNav
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: nmunet
body: we should not be using bootstrap elements in our new developments, as we are moving towards carbon.
If Possible, can you look into carbon alternative for this?
created_at: 2025-01-15T09:15:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,98 @@
+  <ng-container *ngIf="!!selection">
+    <nav ngbNav
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: Sure i will check 

created_at: 2025-01-15T11:59:29Z
author_association: NONE
diff_hunk: @@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent {
+  @Input()
+  selection: any;
+  tenant?: any;
+  topicname?: any;
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
user: login: pujaoshahu
body: changes are done
created_at: 2025-01-15T13:14:01Z
author_association: NONE
diff_hunk: @@ -0,0 +1,98 @@
+  <ng-container *ngIf="!!selection">
+    <nav ngbNav
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: changes are done
created_at: 2025-01-15T13:14:12Z
author_association: NONE
diff_hunk: @@ -0,0 +1,84 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+// import { catchError, switchMap } from 'rxjs/operators';
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: changes are done
created_at: 2025-01-15T13:15:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,80 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs>
+      <cds-tab heading="Destination Endpoints">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-topic-details">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold">Push Endpoint Args</td>
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: nmunet
body: Forgot about these, but I think it should be
```suggestion
                  class="bold">Push endpoint args</td>
```
same at all other places where we are capitalizing, as observed in overall dashboard
we can discuss it more within team if needed!
created_at: 2025-01-15T13:38:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,80 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs>
+      <cds-tab heading="Destination Endpoints">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-topic-details">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold">Push Endpoint Args</td>
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: Changes are done .

![image](https://github.com/user-attachments/assets/53a26b7b-ee82-4692-bc85-0bed68734e78)

created_at: 2025-01-15T17:44:11Z
author_association: NONE
diff_hunk: @@ -236,6 +236,11 @@
                             title="Configuration"
                             i18n-title
                             class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Configuration</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/topic"
+                            [useRouter]="true"
+                            title="Topic"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Topic</span></cds-sidenav-item>
path: src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
user: login: Pegonzal
body: Better in plural?
```suggestion
          <cds-sidenav-item route="/rgw/topic"
                            [useRouter]="true"
                            title="Topics"
                            i18n-title
                            class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Topics</span></cds-sidenav-item>
```
created_at: 2025-01-23T16:04:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -236,6 +236,11 @@
                             title="Configuration"
                             i18n-title
                             class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Configuration</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/topic"
+                            [useRouter]="true"
+                            title="Topic"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Topic</span></cds-sidenav-item>
path: src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
user: login: Pegonzal
body: My first proposal was for the topics to have it's own section, in the navigation menu. But this is up to discussion in my opinion and ideas are welcome.

created_at: 2025-01-23T17:19:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: Pegonzal
body: What about just `Endpoints` as the heading
```suggestion
      <cds-tab heading="Endpoints">
```
created_at: 2025-01-23T17:21:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent {
+  @Input()
+  selection: any;
+  tenant: string;
+  topicname: string;
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
user: login: Pegonzal
body: ```suggestion
  topicName: string;
```
created_at: 2025-01-23T17:23:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Opaque Data`,
+        prop: 'opaqueData',
+        flexGrow: 2
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: Pegonzal
body: What about listing `persitent` or `time_to_live` instead of Opaque Data?

@cloudbehl thoughts?
created_at: 2025-01-23T17:26:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Opaque Data`,
+        prop: 'opaqueData',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadTopics(context?: CdTableFetchDataContext): void {
+    this.rgwTopicService.list(this.uid, this.tenant).subscribe({
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: Pegonzal
body: We are listing all topics anyways
```suggestion
    this.rgwTopicService.list().subscribe({
```
created_at: 2025-01-23T17:37:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: Pegonzal
body: Not really needed, right?
```suggestion
```
created_at: 2025-01-23T17:37:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: Pegonzal
body: Not being used
```suggestion
```
created_at: 2025-01-23T17:37:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,19 @@
+export interface TopicModel {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: Pegonzal
body: ```suggestion
export interface Topic {
```
created_at: 2025-01-23T17:38:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-topic-details">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold">Push endpoint args</td>
+              <td>{{ selection?.dest?.push_endpoint_args }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Push endpoint topic</td>
+              <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Push endpoint</td>
+              <td class="w-75">{{ selection?.dest?.push_endpoint | json }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Stored secret</td>
+              <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Persistent</td>
+              <td>{{ selection?.dest?.persistent }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Persistent queue</td>
+              <td>{{ selection?.dest?.persistent_queue }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Time to live</td>
+              <td>{{ selection?.dest?.time_to_live }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Max retries</td>
+              <td>{{ selection?.dest?.max_retries }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Retry sleep duration</td>
+              <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </cds-tab>
+      <cds-tab heading="Policies">
+        <div class="table-scroller">
+          <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
+            <tbody>
+              <tr>
+                <td i18n
+                    class="bold w-25">Policy</td>
+                <td><pre>{{ selection.policy | json}}</pre></td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </cds-tab>
+      <cds-tab heading="Subscribed Buckets">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: Pegonzal
body: ```suggestion
      <cds-tab heading="Subscribed Buckets"
                       i18n-heading>
```
created_at: 2025-01-23T17:40:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-topic-details">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold">Push endpoint args</td>
+              <td>{{ selection?.dest?.push_endpoint_args }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Push endpoint topic</td>
+              <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Push endpoint</td>
+              <td class="w-75">{{ selection?.dest?.push_endpoint | json }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Stored secret</td>
+              <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Persistent</td>
+              <td>{{ selection?.dest?.persistent }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Persistent queue</td>
+              <td>{{ selection?.dest?.persistent_queue }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Time to live</td>
+              <td>{{ selection?.dest?.time_to_live }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Max retries</td>
+              <td>{{ selection?.dest?.max_retries }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Retry sleep duration</td>
+              <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </cds-tab>
+      <cds-tab heading="Policies">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: Pegonzal
body: ```suggestion
      <cds-tab heading="Policies"
      i18n-heading>
```
created_at: 2025-01-23T17:40:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: Pegonzal
body: ```suggestion
      <cds-tab heading="Destination Endpoints"
      i18n-heading>
```
created_at: 2025-01-23T17:40:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,15 @@
+  <cd-table #table
+            [autoReload]="false"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.html
user: login: Pegonzal
body: Why `[autoReload]="false"`?
created_at: 2025-01-23T17:42:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
+    let params = new HttpParams();
+    if (uid) {
+      params = params.append('uid', uid);
+    } else if (tenant) {
+      params = params.append('tenant', tenant);
+    }
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: Pegonzal
body: If you send uid, you can not set a tenant?

Should it be?
```suggestion
    if (uid) {
      params = params.append('uid', uid);
    }
   if (tenant) {
      params = params.append('tenant', tenant);
    }
```
created_at: 2025-01-23T17:45:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Opaque Data`,
+        prop: 'opaqueData',
+        flexGrow: 2
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: "I shared this screen with Q.E., and they told me to keep the opaque data."
Does this work for what you intended? Let me know if you'd like any further tweaks!
created_at: 2025-01-23T19:03:00Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: "Heading suggested by Q.E."
created_at: 2025-01-23T19:12:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,57 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
+    let params = new HttpParams();
+    if (uid) {
+      params = params.append('uid', uid);
+    } else if (tenant) {
+      params = params.append('tenant', tenant);
+    }
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: changes are done
created_at: 2025-01-23T19:16:21Z
author_association: NONE
diff_hunk: @@ -0,0 +1,15 @@
+  <cd-table #table
+            [autoReload]="false"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.html
user: login: pujaoshahu
body: I have checked other tables as well, and they used the same flag. That's why I kept it the same.
created_at: 2025-01-23T19:16:35Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: changes are done
created_at: 2025-01-23T19:16:51Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-topic-details">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold">Push endpoint args</td>
+              <td>{{ selection?.dest?.push_endpoint_args }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Push endpoint topic</td>
+              <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Push endpoint</td>
+              <td class="w-75">{{ selection?.dest?.push_endpoint | json }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Stored secret</td>
+              <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Persistent</td>
+              <td>{{ selection?.dest?.persistent }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Persistent queue</td>
+              <td>{{ selection?.dest?.persistent_queue }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Time to live</td>
+              <td>{{ selection?.dest?.time_to_live }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Max retries</td>
+              <td>{{ selection?.dest?.max_retries }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Retry sleep duration</td>
+              <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </cds-tab>
+      <cds-tab heading="Policies">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: changes are done 
created_at: 2025-01-23T19:17:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Opaque Data`,
+        prop: 'opaqueData',
+        flexGrow: 2
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: Pegonzal
body: Good to know you did the research, thanks for letting me know.
I was trying to understand all the fields better, and I was wondering if there was anything else worth to display on the main table. Tagged Ankush for now to get his input on this
created_at: 2025-01-23T19:18:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent {
+  @Input()
+  selection: any;
+  tenant: string;
+  topicname: string;
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
user: login: pujaoshahu
body: changes are done
created_at: 2025-01-23T19:26:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Opaque Data`,
+        prop: 'opaqueData',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadTopics(context?: CdTableFetchDataContext): void {
+    this.rgwTopicService.list(this.uid, this.tenant).subscribe({
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: In the CLI command, both parameters are mentioned, due to which I have to keep them. 
created_at: 2025-01-23T19:34:14Z
author_association: NONE
diff_hunk: @@ -236,6 +236,11 @@
                             title="Configuration"
                             i18n-title
                             class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Configuration</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/topic"
+                            [useRouter]="true"
+                            title="Topic"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_configuration"><span i18n>Topic</span></cds-sidenav-item>
path: src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
user: login: pujaoshahu
body: changes are done
created_at: 2025-01-23T20:02:46Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-topic-details">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold">Push endpoint args</td>
+              <td>{{ selection?.dest?.push_endpoint_args }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Push endpoint topic</td>
+              <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Push endpoint</td>
+              <td class="w-75">{{ selection?.dest?.push_endpoint | json }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold w-25">Stored secret</td>
+              <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Persistent</td>
+              <td>{{ selection?.dest?.persistent }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Persistent queue</td>
+              <td>{{ selection?.dest?.persistent_queue }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Time to live</td>
+              <td>{{ selection?.dest?.time_to_live }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Max retries</td>
+              <td>{{ selection?.dest?.max_retries }}</td>
+            </tr>
+            <tr>
+              <td i18n
+                  class="bold">Retry sleep duration</td>
+              <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </cds-tab>
+      <cds-tab heading="Policies">
+        <div class="table-scroller">
+          <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
+            <tbody>
+              <tr>
+                <td i18n
+                    class="bold w-25">Policy</td>
+                <td><pre>{{ selection.policy | json}}</pre></td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </cds-tab>
+      <cds-tab heading="Subscribed Buckets">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: changes are done
created_at: 2025-01-23T20:03:13Z
author_association: NONE
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: yes its required
created_at: 2025-01-23T20:36:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwTopicDetailsComponent } from './rgw-topic-details.component';
+
+describe('RgwTopicDetailsComponent', () => {
+  let component: RgwTopicDetailsComponent;
+  let fixture: ComponentFixture<RgwTopicDetailsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [RgwTopicDetailsComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwTopicDetailsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts
user: login: afreen23
body: we can add more tests here especially related to details
created_at: 2025-01-29T09:32:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,51 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwTopicListComponent } from './rgw-topic-list.component';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
+import { SharedModule } from '~/app/shared/shared.module';
+import { configureTestBed, PermissionHelper } from '~/testing/unit-test-helper';
+import { RgwTopicDetailsComponent } from '../rgw-topic-details/rgw-topic-details.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { RouterTestingModule } from '@angular/router/testing';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ToastrModule } from 'ngx-toastr';
+describe('RgwTopicListComponent', () => {
+  let component: RgwTopicListComponent;
+  let fixture: ComponentFixture<RgwTopicListComponent>;
+
+  configureTestBed({
+    declarations: [RgwTopicListComponent, RgwTopicDetailsComponent],
+    imports: [
+      BrowserAnimationsModule,
+      RouterTestingModule,
+      SharedModule,
+      NgbNavModule,
+      HttpClientTestingModule,
+      ToastrModule.forRoot()
+    ]
+  });
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [
+        BrowserAnimationsModule,
+        SharedModule,
+        HttpClientTestingModule,
+        ToastrModule.forRoot(),
+        RouterTestingModule
+      ],
+
+      declarations: [RgwTopicListComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwTopicListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts
user: login: afreen23
body: same as above more tests needed, basic ones just
created_at: 2025-01-29T09:32:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: afreen23
body: owner is rgw user here ?
created_at: 2025-01-29T09:35:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Opaque Data`,
+        prop: 'opaqueData',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadTopics(context?: CdTableFetchDataContext): void {
+    this.rgwTopicService.list(this.uid, this.tenant).subscribe({
+      next: (data) => {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: afreen23
body: types
created_at: 2025-01-29T09:35:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+
+@Component({
+  selector: 'cd-rgw-user-accounts-details',
+  templateUrl: './rgw-user-accounts-details.component.html',
+  styleUrls: ['./rgw-user-accounts-details.component.scss']
+})
+export class RgwUserAccountsDetailsComponent implements OnChanges {
+  @Input()
+  selection: any;
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-details/rgw-user-accounts-details.component.ts
user: login: afreen23
body: types
created_at: 2025-01-29T09:35:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+
+@Component({
+  selector: 'cd-rgw-user-accounts-details',
+  templateUrl: './rgw-user-accounts-details.component.html',
+  styleUrls: ['./rgw-user-accounts-details.component.scss']
+})
+export class RgwUserAccountsDetailsComponent implements OnChanges {
+  @Input()
+  selection: any;
+  quota = {};
+  bucket_quota = {};
+
+  constructor(private dimlessBinary: DimlessBinaryPipe) {}
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.selection && changes.selection.currentValue) {
+      this.quota = this.createDisplayValues('quota');
+      this.bucket_quota = this.createDisplayValues('bucket_quota');
+    }
+  }
+
+  createDisplayValues(quota_type: string) {
+    return {
+      Enabled: this.selection[quota_type].enabled ? 'Yes' : 'No',
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-details/rgw-user-accounts-details.component.ts
user: login: afreen23
body: localize
created_at: 2025-01-29T09:36:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+
+@Component({
+  selector: 'cd-rgw-user-accounts-details',
+  templateUrl: './rgw-user-accounts-details.component.html',
+  styleUrls: ['./rgw-user-accounts-details.component.scss']
+})
+export class RgwUserAccountsDetailsComponent implements OnChanges {
+  @Input()
+  selection: any;
+  quota = {};
+  bucket_quota = {};
+
+  constructor(private dimlessBinary: DimlessBinaryPipe) {}
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.selection && changes.selection.currentValue) {
+      this.quota = this.createDisplayValues('quota');
+      this.bucket_quota = this.createDisplayValues('bucket_quota');
+    }
+  }
+
+  createDisplayValues(quota_type: string) {
+    return {
+      Enabled: this.selection[quota_type].enabled ? 'Yes' : 'No',
+      'Maximum size': this.selection[quota_type].enabled
+        ? this.selection[quota_type].max_size <= -1
+          ? 'Unlimited'
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-details/rgw-user-accounts-details.component.ts
user: login: afreen23
body: localize
created_at: 2025-01-29T09:36:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,39 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+
+@Component({
+  selector: 'cd-rgw-user-accounts-details',
+  templateUrl: './rgw-user-accounts-details.component.html',
+  styleUrls: ['./rgw-user-accounts-details.component.scss']
+})
+export class RgwUserAccountsDetailsComponent implements OnChanges {
+  @Input()
+  selection: any;
+  quota = {};
+  bucket_quota = {};
+
+  constructor(private dimlessBinary: DimlessBinaryPipe) {}
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.selection && changes.selection.currentValue) {
+      this.quota = this.createDisplayValues('quota');
+      this.bucket_quota = this.createDisplayValues('bucket_quota');
+    }
+  }
+
+  createDisplayValues(quota_type: string) {
+    return {
+      Enabled: this.selection[quota_type].enabled ? 'Yes' : 'No',
+      'Maximum size': this.selection[quota_type].enabled
+        ? this.selection[quota_type].max_size <= -1
+          ? 'Unlimited'
+          : this.dimlessBinary.transform(this.selection[quota_type].max_size)
+        : '-',
+      'Maximum objects': this.selection[quota_type].enabled
+        ? this.selection[quota_type].max_objects <= -1
+          ? 'Unlimited'
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-details/rgw-user-accounts-details.component.ts
user: login: afreen23
body: since this is used twice make it a const and localize
created_at: 2025-01-29T09:36:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,294 @@
+<div cdsCol
+     [columnNumbers]="{md: 4}">
+  <ng-container *cdFormLoading="loading">
+    <form name="accountForm"
+          #formDir="ngForm"
+          [formGroup]="accountForm"
+          novalidate>
+
+      <div i18n="form title"
+           class="form-header">{{ action | titlecase }} {{ resource | upperFirst }}
+      </div>
+
+      <!-- Account Name -->
+      <div class="form-item">
+        <cds-text-label label="Account Name"
+                        for="acc_name"
+                        cdRequiredField="Account Name"
+                        [invalid]="!accountForm.controls.name.valid && accountForm.controls.name.dirty"
+                        [invalidText]="accountIdError"
+                        i18n>Account Name
+          <input cdsText
+                 type="text"
+                 placeholder="Enter account name"
+                 id="acc_name"
+                 name="acc_name"
+                 formControlName="name"/>
+        </cds-text-label>
+        <ng-template #accountIdError>
+          <span *ngIf="accountForm.showError('name', formDir, 'required')"
+                class="invalid-feedback">
+            <ng-container i18n>This field is required.</ng-container>
+          </span>
+        </ng-template>
+      </div>
+      <!-- Tenant -->
+      <div class="form-item">
+        <cds-text-label label="tenant"
+                        for="tenant"
+                        i18n>Tenant
+          <input cdsText
+                 type="text"
+                 placeholder="Enter tenant"
+                 id="tenant"
+                 name="tenant"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html
user: login: afreen23
body: ```suggestion
```
created_at: 2025-01-29T09:37:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,294 @@
+<div cdsCol
+     [columnNumbers]="{md: 4}">
+  <ng-container *cdFormLoading="loading">
+    <form name="accountForm"
+          #formDir="ngForm"
+          [formGroup]="accountForm"
+          novalidate>
+
+      <div i18n="form title"
+           class="form-header">{{ action | titlecase }} {{ resource | upperFirst }}
+      </div>
+
+      <!-- Account Name -->
+      <div class="form-item">
+        <cds-text-label label="Account Name"
+                        for="acc_name"
+                        cdRequiredField="Account Name"
+                        [invalid]="!accountForm.controls.name.valid && accountForm.controls.name.dirty"
+                        [invalidText]="accountIdError"
+                        i18n>Account Name
+          <input cdsText
+                 type="text"
+                 placeholder="Enter account name"
+                 id="acc_name"
+                 name="acc_name"
+                 formControlName="name"/>
+        </cds-text-label>
+        <ng-template #accountIdError>
+          <span *ngIf="accountForm.showError('name', formDir, 'required')"
+                class="invalid-feedback">
+            <ng-container i18n>This field is required.</ng-container>
+          </span>
+        </ng-template>
+      </div>
+      <!-- Tenant -->
+      <div class="form-item">
+        <cds-text-label label="tenant"
+                        for="tenant"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html
user: login: afreen23
body: ```suggestion
                        labelInputID="tenant"
```
created_at: 2025-01-29T09:37:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,294 @@
+<div cdsCol
+     [columnNumbers]="{md: 4}">
+  <ng-container *cdFormLoading="loading">
+    <form name="accountForm"
+          #formDir="ngForm"
+          [formGroup]="accountForm"
+          novalidate>
+
+      <div i18n="form title"
+           class="form-header">{{ action | titlecase }} {{ resource | upperFirst }}
+      </div>
+
+      <!-- Account Name -->
+      <div class="form-item">
+        <cds-text-label label="Account Name"
+                        for="acc_name"
+                        cdRequiredField="Account Name"
+                        [invalid]="!accountForm.controls.name.valid && accountForm.controls.name.dirty"
+                        [invalidText]="accountIdError"
+                        i18n>Account Name
+          <input cdsText
+                 type="text"
+                 placeholder="Enter account name"
+                 id="acc_name"
+                 name="acc_name"
+                 formControlName="name"/>
+        </cds-text-label>
+        <ng-template #accountIdError>
+          <span *ngIf="accountForm.showError('name', formDir, 'required')"
+                class="invalid-feedback">
+            <ng-container i18n>This field is required.</ng-container>
+          </span>
+        </ng-template>
+      </div>
+      <!-- Tenant -->
+      <div class="form-item">
+        <cds-text-label label="tenant"
+                        for="tenant"
+                        i18n>Tenant
+          <input cdsText
+                 type="text"
+                 placeholder="Enter tenant"
+                 id="tenant"
+                 name="tenant"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html
user: login: afreen23
body: same for other places too
created_at: 2025-01-29T09:38:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,294 @@
+<div cdsCol
+     [columnNumbers]="{md: 4}">
+  <ng-container *cdFormLoading="loading">
+    <form name="accountForm"
+          #formDir="ngForm"
+          [formGroup]="accountForm"
+          novalidate>
+
+      <div i18n="form title"
+           class="form-header">{{ action | titlecase }} {{ resource | upperFirst }}
+      </div>
+
+      <!-- Account Name -->
+      <div class="form-item">
+        <cds-text-label label="Account Name"
+                        for="acc_name"
+                        cdRequiredField="Account Name"
+                        [invalid]="!accountForm.controls.name.valid && accountForm.controls.name.dirty"
+                        [invalidText]="accountIdError"
+                        i18n>Account Name
+          <input cdsText
+                 type="text"
+                 placeholder="Enter account name"
+                 id="acc_name"
+                 name="acc_name"
+                 formControlName="name"/>
+        </cds-text-label>
+        <ng-template #accountIdError>
+          <span *ngIf="accountForm.showError('name', formDir, 'required')"
+                class="invalid-feedback">
+            <ng-container i18n>This field is required.</ng-container>
+          </span>
+        </ng-template>
+      </div>
+      <!-- Tenant -->
+      <div class="form-item">
+        <cds-text-label label="tenant"
+                        for="tenant"
+                        i18n>Tenant
+          <input cdsText
+                 type="text"
+                 placeholder="Enter tenant"
+                 id="tenant"
+                 name="tenant"
+                 formControlName="tenant"/>
+        </cds-text-label>
+      </div>
+
+      <!-- Email -->
+      <div class="form-item">
+        <cds-text-label label="email"
+                        for="email"
+                        [invalid]="!accountForm.controls.email.valid && accountForm.controls.email.dirty"
+                        [invalidText]="emailError"
+                        i18n>Email
+          <input cdsText
+                 type="text"
+                 placeholder="Enter email"
+                 id="email"
+                 name="email"
+                 formControlName="email"/>
+        </cds-text-label>
+        <ng-template #emailError>
+          <span *ngIf="accountForm.showError('email', formDir, 'email')">
+            <ng-container i18n> Please enter a valid email </ng-container>
+          </span>
+        </ng-template>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. bucket mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_buckets_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max buckets -->
+        <span *ngIf="1 == accountForm.get('max_buckets_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_buckets' }"></ng-container>
+          </span>
+        </div>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. users mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_users_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max users -->
+        <span *ngIf="1 == accountForm.get('max_users_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_users' }"></ng-container>
+          </span>
+        </div>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. roles mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_roles_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max roles -->
+        <span *ngIf="1 == accountForm.get('max_roles_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_roles' }"></ng-container>
+        </span>
+        </div>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. group mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_groups_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max group -->
+        <span *ngIf="1 == accountForm.get('max_groups_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_groups' }"></ng-container>
+          </span>
+        </div>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. acess keys mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_access_keys_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max acess keys -->
+        <span *ngIf="1 == accountForm.get('max_access_keys_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_access_keys' }"></ng-container>
+        </span>
+        </div>
+      </div>
+
+      <!-- Account Quota -->
+      <div class="form-item">
+        <ng-container *ngTemplateOutlet="quotaTemplate;context: { formControl: {
+                      enabled: 'account_quota_enabled',
+                      unlimitedSize: 'account_quota_max_size_unlimited',
+                      maxSize: 'account_quota_max_size',
+                      unlimitedObjects: 'account_quota_max_objects_unlimited',
+                      maxObjects: 'account_quota_max_objects'
+                      },
+                      quotaType: 'account'
+                      }">
+        </ng-container>
+      </div>
+
+      <!-- Bucket Quota -->
+      <div class="form-item">
+        <ng-container *ngTemplateOutlet="quotaTemplate;
+                      context: {
+                      formControl: {
+                      enabled: 'bucket_quota_enabled',
+                      unlimitedSize: 'bucket_quota_max_size_unlimited',
+                      maxSize: 'bucket_quota_max_size',
+                      unlimitedObjects: 'bucket_quota_max_objects_unlimited',
+                      maxObjects: 'bucket_quota_max_objects'
+                      },
+                      quotaType: 'bucket'
+                      }">
+        </ng-container>
+      </div>
+      <cd-form-button-panel (submitActionEvent)="submit()"
+                            [form]="formDir"
+                            [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+                            wrappingClass="text-right"></cd-form-button-panel>
+
+    </form>
+  </ng-container>
+</div>
+
+<ng-template #selectModeTemplate
+             [formGroup]="accountForm"
+             let-formControl="formControl">
+  <cds-select [formControlName]="formControl"
+              [name]="formControl"
+              [id]="formControl"
+              label="{{formControl.split('_')[1] | upperFirst}} Mode"
+              (change)="onModeChange($event.target.value, formControl)"
+              [helperText]="getHelperTextForMode(formControl)">
+    <option value="0">Unlimited</option>
+    <option value="-1">Disabled</option>
+    <option value="1">Custom</option>
+  </cds-select>
+</ng-template>
+
+<ng-template #accountMaxValueTemplate
+             let-formControl="formControl"
+             [formGroup]="accountForm">
+<cds-number [id]="formControl"
+            [name]="formControl"
+            formControlName="{{formControl}}"
+            label="{{formControl.split('_')[0] | upperFirst}}. {{formControl.split('_').length > 2 ? formControl.split('_')[1]+' '+formControl.split('_')[2]: formControl.split('_')[1]}}"
+            [min]="1"
+            cdRequiredField="{{formControl.split('_')[0] | upperFirst}}. {{formControl.split('_').length > 2 ? formControl.split('_')[1]+' '+formControl.split('_')[2]: formControl.split('_')[1]}}"
+            [invalid]="!accountForm.controls[formControl].valid && accountForm.controls[formControl].dirty"
+            [invalidText]="maxValError"></cds-number>
+<ng-template #maxValError>
+  <span *ngIf="accountForm.showError(formControl, formDir, 'required')"
+        i18n>This field is required.</span>
+  <span *ngIf="accountForm.controls[formControl].value == 0 && accountForm.showError(formControl, formDir, 'min')"
+        i18n>Enter number greater than 0</span>
+  <span *ngIf="accountForm.showError(formControl, formDir, 'pattern')"
+        i18n>Enter a valid positive number</span>
+</ng-template>
+</ng-template>
+
+<ng-template #quotaTemplate
+             let-quotaType="quotaType"
+             let-formControl="formControl"
+             [formGroup]="accountForm">
+  <fieldset class="cds--fieldset">
+    <legend class="cds--label">{{quotaType | upperFirst}} Quota</legend>
+    <div *ngIf="quotaType == 'account';else bucket"
+         class="quota-heading">Set quota on account owned by users.</div>
+    <ng-template #bucket>
+      <div class="quota-heading">
+        Set quota on buckets owned by an account.
+      </div>
+    </ng-template>
+    <!-- Enabled -->
+    <cds-checkbox [formControlName]="formControl.enabled">
+      Enabled
+    </cds-checkbox >
+    <div class="quota-sub-block"
+         *ngIf="accountForm.controls[formControl.enabled].value">
+      <!-- Unlimited size -->
+      <cds-checkbox *ngIf="accountForm.controls[formControl.enabled].value"
+                    [formControlName]="formControl.unlimitedSize">
+                    Unlimited size
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html
user: login: afreen23
body: i18n
created_at: 2025-01-29T09:38:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: yes
created_at: 2025-01-29T09:52:44Z
author_association: NONE
diff_hunk: @@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwTopicDetailsComponent } from './rgw-topic-details.component';
+
+describe('RgwTopicDetailsComponent', () => {
+  let component: RgwTopicDetailsComponent;
+  let fixture: ComponentFixture<RgwTopicDetailsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [RgwTopicDetailsComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwTopicDetailsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts
user: login: pujaoshahu
body: I will write the test case once the create and delete features are done.
created_at: 2025-01-29T11:33:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,51 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwTopicListComponent } from './rgw-topic-list.component';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
+import { SharedModule } from '~/app/shared/shared.module';
+import { configureTestBed, PermissionHelper } from '~/testing/unit-test-helper';
+import { RgwTopicDetailsComponent } from '../rgw-topic-details/rgw-topic-details.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { RouterTestingModule } from '@angular/router/testing';
+import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ToastrModule } from 'ngx-toastr';
+describe('RgwTopicListComponent', () => {
+  let component: RgwTopicListComponent;
+  let fixture: ComponentFixture<RgwTopicListComponent>;
+
+  configureTestBed({
+    declarations: [RgwTopicListComponent, RgwTopicDetailsComponent],
+    imports: [
+      BrowserAnimationsModule,
+      RouterTestingModule,
+      SharedModule,
+      NgbNavModule,
+      HttpClientTestingModule,
+      ToastrModule.forRoot()
+    ]
+  });
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [
+        BrowserAnimationsModule,
+        SharedModule,
+        HttpClientTestingModule,
+        ToastrModule.forRoot(),
+        RouterTestingModule
+      ],
+
+      declarations: [RgwTopicListComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwTopicListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts
user: login: pujaoshahu
body: I will write the test case once the create and delete features are done.
created_at: 2025-01-29T11:33:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,84 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints"
+               i18n-heading>
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-topic-details">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold">Push endpoint args</td>
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: afreen23
body: Are they called args or "arguments" ?
I would advice to chekc all terms here with doc team , thanks
created_at: 2025-01-30T10:48:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import { TestBed } from '@angular/core/testing';
+
+import { RgwTopicService } from './rgw-topic.service';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('RgwTopicService', () => {
+  let service: RgwTopicService;
+
+  configureTestBed({
+    imports: [HttpClientTestingModule]
+  });
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(RgwTopicService);
+  });
+
+  it('should be created', () => {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.spec.ts
user: login: afreen23
body: here we should add tests
created_at: 2025-01-30T10:49:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
+    let params = new HttpParams();
+    if (uid) {
+      params = params.append('uid', uid);
+    }
+    if (tenant) {
+      params = params.append('tenant', tenant);
+    }
+
+    return this.http
+      .get<any>(this.baseURL, { params })
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: afreen23
body: types
created_at: 2025-01-30T10:49:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: afreen23
body: why all optional ? where are we using `list()` ?
created_at: 2025-01-30T10:50:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
+    let params = new HttpParams();
+    if (uid) {
+      params = params.append('uid', uid);
+    }
+    if (tenant) {
+      params = params.append('tenant', tenant);
+    }
+
+    return this.http
+      .get<any>(this.baseURL, { params })
+      .pipe(
+        map((response) => {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: afreen23
body: types
created_at: 2025-01-30T10:51:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
+    let params = new HttpParams();
+    if (uid) {
+      params = params.append('uid', uid);
+    }
+    if (tenant) {
+      params = params.append('tenant', tenant);
+    }
+
+    return this.http
+      .get<any>(this.baseURL, { params })
+      .pipe(
+        map((response) => {
+          // Transform the response by flattening the 'dest' object
+          if (response && response.topics) {
+            return response.topics.map((topic: any) => ({
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: afreen23
body: avoid using `any`
created_at: 2025-01-30T10:51:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: "It worked in the CLI command because I passed it."
created_at: 2025-01-31T04:50:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: afreen23
body: I dont really understood , I meant why the arguments are marked optional I dont see nay instance where we are using its as `list()` but we always pass the args, or I missed ? Please clarify, thanks :)
created_at: 2025-01-31T09:49:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import { TestBed } from '@angular/core/testing';
+
+import { RgwTopicService } from './rgw-topic.service';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('RgwTopicService', () => {
+  let service: RgwTopicService;
+
+  configureTestBed({
+    imports: [HttpClientTestingModule]
+  });
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(RgwTopicService);
+  });
+
+  it('should be created', () => {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.spec.ts
user: login: pujaoshahu
body: i will create test case once functionality is completed 
created_at: 2025-02-12T06:05:51Z
author_association: NONE
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
+    let params = new HttpParams();
+    if (uid) {
+      params = params.append('uid', uid);
+    }
+    if (tenant) {
+      params = params.append('tenant', tenant);
+    }
+
+    return this.http
+      .get<any>(this.baseURL, { params })
+      .pipe(
+        map((response) => {
+          // Transform the response by flattening the 'dest' object
+          if (response && response.topics) {
+            return response.topics.map((topic: any) => ({
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: "I have made all the suggested changes in the new PR (https://github.com/ceph/ceph/pull/61586)."
created_at: 2025-02-12T13:07:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
+    let params = new HttpParams();
+    if (uid) {
+      params = params.append('uid', uid);
+    }
+    if (tenant) {
+      params = params.append('tenant', tenant);
+    }
+
+    return this.http
+      .get<any>(this.baseURL, { params })
+      .pipe(
+        map((response) => {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: "I have made all the suggested changes in the new PR (https://github.com/ceph/ceph/pull/61586)."
created_at: 2025-02-12T13:07:29Z
author_association: NONE
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: "I have made all the suggested changes in the new PR (https://github.com/ceph/ceph/pull/61586)."
created_at: 2025-02-12T13:07:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,58 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  list(uid?: string, tenant?: string): Observable<TopicModel[]> {
+    let params = new HttpParams();
+    if (uid) {
+      params = params.append('uid', uid);
+    }
+    if (tenant) {
+      params = params.append('tenant', tenant);
+    }
+
+    return this.http
+      .get<any>(this.baseURL, { params })
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: "I have made all the suggested changes in the new PR (https://github.com/ceph/ceph/pull/61586)."
created_at: 2025-02-12T13:08:11Z
author_association: NONE
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Opaque Data`,
+        prop: 'opaqueData',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadTopics(context?: CdTableFetchDataContext): void {
+    this.rgwTopicService.list(this.uid, this.tenant).subscribe({
+      next: (data) => {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: changes are made
created_at: 2025-02-12T14:36:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+
+@Component({
+  selector: 'cd-rgw-user-accounts-details',
+  templateUrl: './rgw-user-accounts-details.component.html',
+  styleUrls: ['./rgw-user-accounts-details.component.scss']
+})
+export class RgwUserAccountsDetailsComponent implements OnChanges {
+  @Input()
+  selection: any;
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-details/rgw-user-accounts-details.component.ts
user: login: pujaoshahu
body: This comment related to user-account-details page "
created_at: 2025-02-12T14:36:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+
+@Component({
+  selector: 'cd-rgw-user-accounts-details',
+  templateUrl: './rgw-user-accounts-details.component.html',
+  styleUrls: ['./rgw-user-accounts-details.component.scss']
+})
+export class RgwUserAccountsDetailsComponent implements OnChanges {
+  @Input()
+  selection: any;
+  quota = {};
+  bucket_quota = {};
+
+  constructor(private dimlessBinary: DimlessBinaryPipe) {}
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.selection && changes.selection.currentValue) {
+      this.quota = this.createDisplayValues('quota');
+      this.bucket_quota = this.createDisplayValues('bucket_quota');
+    }
+  }
+
+  createDisplayValues(quota_type: string) {
+    return {
+      Enabled: this.selection[quota_type].enabled ? 'Yes' : 'No',
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-details/rgw-user-accounts-details.component.ts
user: login: pujaoshahu
body: This comment related to user-account page "
created_at: 2025-02-12T14:37:27Z
author_association: NONE
diff_hunk: @@ -0,0 +1,84 @@
+  <ng-container *ngIf="!!selection">
+    <cds-tabs type="contained"
+              theme="light">
+      <cds-tab heading="Destination Endpoints"
+               i18n-heading>
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+               data-testid="rgw-topic-details">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold">Push endpoint args</td>
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: changes are made
created_at: 2025-02-12T14:38:01Z
author_association: NONE
diff_hunk: @@ -0,0 +1,294 @@
+<div cdsCol
+     [columnNumbers]="{md: 4}">
+  <ng-container *cdFormLoading="loading">
+    <form name="accountForm"
+          #formDir="ngForm"
+          [formGroup]="accountForm"
+          novalidate>
+
+      <div i18n="form title"
+           class="form-header">{{ action | titlecase }} {{ resource | upperFirst }}
+      </div>
+
+      <!-- Account Name -->
+      <div class="form-item">
+        <cds-text-label label="Account Name"
+                        for="acc_name"
+                        cdRequiredField="Account Name"
+                        [invalid]="!accountForm.controls.name.valid && accountForm.controls.name.dirty"
+                        [invalidText]="accountIdError"
+                        i18n>Account Name
+          <input cdsText
+                 type="text"
+                 placeholder="Enter account name"
+                 id="acc_name"
+                 name="acc_name"
+                 formControlName="name"/>
+        </cds-text-label>
+        <ng-template #accountIdError>
+          <span *ngIf="accountForm.showError('name', formDir, 'required')"
+                class="invalid-feedback">
+            <ng-container i18n>This field is required.</ng-container>
+          </span>
+        </ng-template>
+      </div>
+      <!-- Tenant -->
+      <div class="form-item">
+        <cds-text-label label="tenant"
+                        for="tenant"
+                        i18n>Tenant
+          <input cdsText
+                 type="text"
+                 placeholder="Enter tenant"
+                 id="tenant"
+                 name="tenant"
+                 formControlName="tenant"/>
+        </cds-text-label>
+      </div>
+
+      <!-- Email -->
+      <div class="form-item">
+        <cds-text-label label="email"
+                        for="email"
+                        [invalid]="!accountForm.controls.email.valid && accountForm.controls.email.dirty"
+                        [invalidText]="emailError"
+                        i18n>Email
+          <input cdsText
+                 type="text"
+                 placeholder="Enter email"
+                 id="email"
+                 name="email"
+                 formControlName="email"/>
+        </cds-text-label>
+        <ng-template #emailError>
+          <span *ngIf="accountForm.showError('email', formDir, 'email')">
+            <ng-container i18n> Please enter a valid email </ng-container>
+          </span>
+        </ng-template>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. bucket mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_buckets_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max buckets -->
+        <span *ngIf="1 == accountForm.get('max_buckets_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_buckets' }"></ng-container>
+          </span>
+        </div>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. users mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_users_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max users -->
+        <span *ngIf="1 == accountForm.get('max_users_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_users' }"></ng-container>
+          </span>
+        </div>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. roles mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_roles_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max roles -->
+        <span *ngIf="1 == accountForm.get('max_roles_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_roles' }"></ng-container>
+        </span>
+        </div>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. group mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_groups_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max group -->
+        <span *ngIf="1 == accountForm.get('max_groups_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_groups' }"></ng-container>
+          </span>
+        </div>
+      </div>
+
+      <div class="form-item form-item-append"
+           cdsRow>
+        <div cdsCol>
+          <!-- Max. acess keys mode -->
+          <ng-container *ngTemplateOutlet="selectModeTemplate;context: { formControl: 'max_access_keys_mode' }"></ng-container>
+        </div>
+        <div cdsCol>
+        <!-- Max acess keys -->
+        <span *ngIf="1 == accountForm.get('max_access_keys_mode').value">
+          <ng-container *ngTemplateOutlet="accountMaxValueTemplate;context: { formControl: 'max_access_keys' }"></ng-container>
+        </span>
+        </div>
+      </div>
+
+      <!-- Account Quota -->
+      <div class="form-item">
+        <ng-container *ngTemplateOutlet="quotaTemplate;context: { formControl: {
+                      enabled: 'account_quota_enabled',
+                      unlimitedSize: 'account_quota_max_size_unlimited',
+                      maxSize: 'account_quota_max_size',
+                      unlimitedObjects: 'account_quota_max_objects_unlimited',
+                      maxObjects: 'account_quota_max_objects'
+                      },
+                      quotaType: 'account'
+                      }">
+        </ng-container>
+      </div>
+
+      <!-- Bucket Quota -->
+      <div class="form-item">
+        <ng-container *ngTemplateOutlet="quotaTemplate;
+                      context: {
+                      formControl: {
+                      enabled: 'bucket_quota_enabled',
+                      unlimitedSize: 'bucket_quota_max_size_unlimited',
+                      maxSize: 'bucket_quota_max_size',
+                      unlimitedObjects: 'bucket_quota_max_objects_unlimited',
+                      maxObjects: 'bucket_quota_max_objects'
+                      },
+                      quotaType: 'bucket'
+                      }">
+        </ng-container>
+      </div>
+      <cd-form-button-panel (submitActionEvent)="submit()"
+                            [form]="formDir"
+                            [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+                            wrappingClass="text-right"></cd-form-button-panel>
+
+    </form>
+  </ng-container>
+</div>
+
+<ng-template #selectModeTemplate
+             [formGroup]="accountForm"
+             let-formControl="formControl">
+  <cds-select [formControlName]="formControl"
+              [name]="formControl"
+              [id]="formControl"
+              label="{{formControl.split('_')[1] | upperFirst}} Mode"
+              (change)="onModeChange($event.target.value, formControl)"
+              [helperText]="getHelperTextForMode(formControl)">
+    <option value="0">Unlimited</option>
+    <option value="-1">Disabled</option>
+    <option value="1">Custom</option>
+  </cds-select>
+</ng-template>
+
+<ng-template #accountMaxValueTemplate
+             let-formControl="formControl"
+             [formGroup]="accountForm">
+<cds-number [id]="formControl"
+            [name]="formControl"
+            formControlName="{{formControl}}"
+            label="{{formControl.split('_')[0] | upperFirst}}. {{formControl.split('_').length > 2 ? formControl.split('_')[1]+' '+formControl.split('_')[2]: formControl.split('_')[1]}}"
+            [min]="1"
+            cdRequiredField="{{formControl.split('_')[0] | upperFirst}}. {{formControl.split('_').length > 2 ? formControl.split('_')[1]+' '+formControl.split('_')[2]: formControl.split('_')[1]}}"
+            [invalid]="!accountForm.controls[formControl].valid && accountForm.controls[formControl].dirty"
+            [invalidText]="maxValError"></cds-number>
+<ng-template #maxValError>
+  <span *ngIf="accountForm.showError(formControl, formDir, 'required')"
+        i18n>This field is required.</span>
+  <span *ngIf="accountForm.controls[formControl].value == 0 && accountForm.showError(formControl, formDir, 'min')"
+        i18n>Enter number greater than 0</span>
+  <span *ngIf="accountForm.showError(formControl, formDir, 'pattern')"
+        i18n>Enter a valid positive number</span>
+</ng-template>
+</ng-template>
+
+<ng-template #quotaTemplate
+             let-quotaType="quotaType"
+             let-formControl="formControl"
+             [formGroup]="accountForm">
+  <fieldset class="cds--fieldset">
+    <legend class="cds--label">{{quotaType | upperFirst}} Quota</legend>
+    <div *ngIf="quotaType == 'account';else bucket"
+         class="quota-heading">Set quota on account owned by users.</div>
+    <ng-template #bucket>
+      <div class="quota-heading">
+        Set quota on buckets owned by an account.
+      </div>
+    </ng-template>
+    <!-- Enabled -->
+    <cds-checkbox [formControlName]="formControl.enabled">
+      Enabled
+    </cds-checkbox >
+    <div class="quota-sub-block"
+         *ngIf="accountForm.controls[formControl.enabled].value">
+      <!-- Unlimited size -->
+      <cds-checkbox *ngIf="accountForm.controls[formControl.enabled].value"
+                    [formControlName]="formControl.unlimitedSize">
+                    Unlimited size
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html
user: login: pujaoshahu
body: "comments are not related to list page"
created_at: 2025-02-12T14:40:53Z
author_association: NONE
diff_hunk: @@ -0,0 +1,294 @@
+<div cdsCol
+     [columnNumbers]="{md: 4}">
+  <ng-container *cdFormLoading="loading">
+    <form name="accountForm"
+          #formDir="ngForm"
+          [formGroup]="accountForm"
+          novalidate>
+
+      <div i18n="form title"
+           class="form-header">{{ action | titlecase }} {{ resource | upperFirst }}
+      </div>
+
+      <!-- Account Name -->
+      <div class="form-item">
+        <cds-text-label label="Account Name"
+                        for="acc_name"
+                        cdRequiredField="Account Name"
+                        [invalid]="!accountForm.controls.name.valid && accountForm.controls.name.dirty"
+                        [invalidText]="accountIdError"
+                        i18n>Account Name
+          <input cdsText
+                 type="text"
+                 placeholder="Enter account name"
+                 id="acc_name"
+                 name="acc_name"
+                 formControlName="name"/>
+        </cds-text-label>
+        <ng-template #accountIdError>
+          <span *ngIf="accountForm.showError('name', formDir, 'required')"
+                class="invalid-feedback">
+            <ng-container i18n>This field is required.</ng-container>
+          </span>
+        </ng-template>
+      </div>
+      <!-- Tenant -->
+      <div class="form-item">
+        <cds-text-label label="tenant"
+                        for="tenant"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-form/rgw-user-accounts-form.component.html
user: login: pujaoshahu
body: "This comment related to account page "
created_at: 2025-02-12T14:41:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+
+@Component({
+  selector: 'cd-rgw-user-accounts-details',
+  templateUrl: './rgw-user-accounts-details.component.html',
+  styleUrls: ['./rgw-user-accounts-details.component.scss']
+})
+export class RgwUserAccountsDetailsComponent implements OnChanges {
+  @Input()
+  selection: any;
+  quota = {};
+  bucket_quota = {};
+
+  constructor(private dimlessBinary: DimlessBinaryPipe) {}
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.selection && changes.selection.currentValue) {
+      this.quota = this.createDisplayValues('quota');
+      this.bucket_quota = this.createDisplayValues('bucket_quota');
+    }
+  }
+
+  createDisplayValues(quota_type: string) {
+    return {
+      Enabled: this.selection[quota_type].enabled ? 'Yes' : 'No',
+      'Maximum size': this.selection[quota_type].enabled
+        ? this.selection[quota_type].max_size <= -1
+          ? 'Unlimited'
+          : this.dimlessBinary.transform(this.selection[quota_type].max_size)
+        : '-',
+      'Maximum objects': this.selection[quota_type].enabled
+        ? this.selection[quota_type].max_objects <= -1
+          ? 'Unlimited'
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-details/rgw-user-accounts-details.component.ts
user: login: pujaoshahu
body: This comment related to account page "
created_at: 2025-02-12T14:41:44Z
author_association: NONE
diff_hunk: @@ -0,0 +1,39 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+
+@Component({
+  selector: 'cd-rgw-user-accounts-details',
+  templateUrl: './rgw-user-accounts-details.component.html',
+  styleUrls: ['./rgw-user-accounts-details.component.scss']
+})
+export class RgwUserAccountsDetailsComponent implements OnChanges {
+  @Input()
+  selection: any;
+  quota = {};
+  bucket_quota = {};
+
+  constructor(private dimlessBinary: DimlessBinaryPipe) {}
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes.selection && changes.selection.currentValue) {
+      this.quota = this.createDisplayValues('quota');
+      this.bucket_quota = this.createDisplayValues('bucket_quota');
+    }
+  }
+
+  createDisplayValues(quota_type: string) {
+    return {
+      Enabled: this.selection[quota_type].enabled ? 'Yes' : 'No',
+      'Maximum size': this.selection[quota_type].enabled
+        ? this.selection[quota_type].max_size <= -1
+          ? 'Unlimited'
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-accounts-details/rgw-user-accounts-details.component.ts
user: login: pujaoshahu
body: This comment related to account page "
created_at: 2025-02-12T14:41:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: "Q.E. suggested this header (owner). Let me know if you want to change the column name to 'user,' and I'll make the changes."
created_at: 2025-02-12T14:58:10Z
author_association: NONE
diff_hunk: @@ -0,0 +1,86 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: afreen23
body: ```suggestion
    <cds-tab heading="Details" i18n-heading>
```
created_at: 2025-03-20T11:47:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details">
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+             data-testid="rgw-topic-details">
+        <tbody>
+          <tr>
+            <td i18n
+                class="bold">Push endpoint arguments</td>
+            <td>{{ selection?.dest?.push_endpoint_args }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint topic</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Stored secret</td>
+            <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent</td>
+            <td>{{ selection?.dest?.persistent }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent queue</td>
+            <td>{{ selection?.dest?.persistent_queue }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Time to live</td>
+            <td>{{ selection?.dest?.time_to_live }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Max retries</td>
+            <td>{{ selection?.dest?.max_retries }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Retry sleep duration</td>
+            <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Opaquedata</td>
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: afreen23
body: ```suggestion
                class="bold">Opaque data</td>
```
created_at: 2025-03-20T11:48:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details">
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+             data-testid="rgw-topic-details">
+        <tbody>
+          <tr>
+            <td i18n
+                class="bold">Push endpoint arguments</td>
+            <td>{{ selection?.dest?.push_endpoint_args }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint topic</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Stored secret</td>
+            <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent</td>
+            <td>{{ selection?.dest?.persistent }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent queue</td>
+            <td>{{ selection?.dest?.persistent_queue }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Time to live</td>
+            <td>{{ selection?.dest?.time_to_live }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Max retries</td>
+            <td>{{ selection?.dest?.max_retries }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Retry sleep duration</td>
+            <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Opaquedata</td>
+            <td>{{ selection?.opaqueData }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </cds-tab>
+    <cds-tab heading="Policies">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: afreen23
body: ```suggestion
    <cds-tab heading="Policies" i18n-heading>
```
created_at: 2025-03-20T11:48:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwTopicDetailsComponent } from './rgw-topic-details.component';
+
+describe('RgwTopicDetailsComponent', () => {
+  let component: RgwTopicDetailsComponent;
+  let fixture: ComponentFixture<RgwTopicDetailsComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [RgwTopicDetailsComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RgwTopicDetailsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts
user: login: afreen23
body: Its okay to add some basic unit tests, ltes try to finish all items of a feature at once. We can test this way code as well while reviewing. thanks
created_at: 2025-03-20T11:50:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,14 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent {
+  @Input()
+  selection: any;
+  tenant: string;
+  topicname: string;
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
user: login: afreen23
body: I think its missed, and not updated

Since the variable is not used, I removed it from the code.
created_at: 2025-03-20T11:50:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,25 @@
+import { Component, Input, SimpleChanges, OnChanges } from '@angular/core';
+import { TopicDetailModel } from './topic-details.model';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent implements OnChanges {
+  @Input()
+  selection: TopicDetailModel;
+  tenant: string;
+  topicname: string;
+  policy: {};
+  constructor() {}
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['selection'] && this.selection) {
+      if (typeof this.selection.policy === 'string') {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
user: login: afreen23
body: Lets use lodash `.isString()`
created_at: 2025-03-20T11:53:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+// Interface for the policy statement
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: afreen23
body: is dest means Destination ? if yes we can have meaningful names here, I see you are already adding comment so it will be fine
created_at: 2025-03-20T11:54:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+// Interface for the policy statement
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
+
+// The main TopicDetailModel interface for selection
+export interface TopicDetailModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Dest; // Use the 'Dest' interface
+  opaqueData: string;
+  policy: {
+    Statement: PolicyStatement[]; // Use the 'PolicyStatement' interface
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: afreen23
body: ```suggestion
  dest: Dest;
  opaqueData: string;
  policy: {
    Statement: PolicyStatement[];
```
created_at: 2025-03-20T11:55:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+// Interface for the policy statement
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
+
+// The main TopicDetailModel interface for selection
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: afreen23
body: ```suggestion
```
created_at: 2025-03-20T11:55:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,36 @@
+// Interface for the policy statement
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: afreen23
body: ```suggestion
```
created_at: 2025-03-20T11:55:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Opaque Data`,
+        prop: 'opaqueData',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadTopics(context?: CdTableFetchDataContext): void {
+    this.rgwTopicService.list(this.uid, this.tenant).subscribe({
+      next: (data) => {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: afreen23
body: We can add types for data

Suggested changes are done 
created_at: 2025-03-20T11:58:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,83 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  uid: string = '';
+  tenant: string = '';
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: afreen23
body: Thats okay then , thanks for explaining.
created_at: 2025-03-20T11:58:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINTTYPE = {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: afreen23
body: ```suggestion
export const ENDPOINT_TYPE = {
```
created_at: 2025-03-20T11:58:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINTTYPE = {
+  HTTP: 'HTTP',
+  AMQP: 'AMQP',
+  Kafka: 'KAFKA'
+};
+export const AMQPACKLEVEL = {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: afreen23
body: ```suggestion
export const AMQP_ACK_LEVEL = {
```
created_at: 2025-03-20T11:59:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINTTYPE = {
+  HTTP: 'HTTP',
+  AMQP: 'AMQP',
+  Kafka: 'KAFKA'
+};
+export const AMQPACKLEVEL = {
+  Select: 'Select AMQP level',
+  none: 'none',
+  broker: 'broker',
+  routable: 'routable'
+};
+export const KAFKAACKLEVEL = {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: afreen23
body: ```suggestion
export const KAFKA_ACK_LEVEL = {
```
created_at: 2025-03-20T11:59:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details">
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+             data-testid="rgw-topic-details">
+        <tbody>
+          <tr>
+            <td i18n
+                class="bold">Push endpoint arguments</td>
+            <td>{{ selection?.dest?.push_endpoint_args }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint topic</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Stored secret</td>
+            <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent</td>
+            <td>{{ selection?.dest?.persistent }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent queue</td>
+            <td>{{ selection?.dest?.persistent_queue }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Time to live</td>
+            <td>{{ selection?.dest?.time_to_live }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Max retries</td>
+            <td>{{ selection?.dest?.max_retries }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Retry sleep duration</td>
+            <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Opaquedata</td>
+            <td>{{ selection?.opaqueData }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </cds-tab>
+    <cds-tab heading="Policies">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-03-21T05:19:13Z
author_association: NONE
diff_hunk: @@ -0,0 +1,86 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details">
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+             data-testid="rgw-topic-details">
+        <tbody>
+          <tr>
+            <td i18n
+                class="bold">Push endpoint arguments</td>
+            <td>{{ selection?.dest?.push_endpoint_args }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint topic</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Stored secret</td>
+            <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent</td>
+            <td>{{ selection?.dest?.persistent }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent queue</td>
+            <td>{{ selection?.dest?.persistent_queue }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Time to live</td>
+            <td>{{ selection?.dest?.time_to_live }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Max retries</td>
+            <td>{{ selection?.dest?.max_retries }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Retry sleep duration</td>
+            <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Opaquedata</td>
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-03-21T05:19:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,86 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details">
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: suggested changes are done
created_at: 2025-03-21T05:19:40Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINTTYPE = {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-03-21T05:20:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINTTYPE = {
+  HTTP: 'HTTP',
+  AMQP: 'AMQP',
+  Kafka: 'KAFKA'
+};
+export const AMQPACKLEVEL = {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-03-21T05:20:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINTTYPE = {
+  HTTP: 'HTTP',
+  AMQP: 'AMQP',
+  Kafka: 'KAFKA'
+};
+export const AMQPACKLEVEL = {
+  Select: 'Select AMQP level',
+  none: 'none',
+  broker: 'broker',
+  routable: 'routable'
+};
+export const KAFKAACKLEVEL = {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-03-21T05:20:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,51 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel, ApiResponse } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  listTopic(): Observable<TopicModel[]> {
+    return this.http.get<ApiResponse>(this.baseURL).pipe(
+      map((response) => {
+        // Transform the response by flattening the 'dest' object
+        if (response && response.topics) {
+          return response.topics.map((topic: TopicModel) => ({
+            owner: topic.owner,
+            name: topic.name,
+            arn: topic.arn,
+            dest: {
+              push_endpoint: topic.dest.push_endpoint,
+              push_endpoint_args: topic.dest.push_endpoint_args,
+              push_endpoint_topic: topic.dest.push_endpoint_topic,
+              stored_secret: topic.dest.stored_secret,
+              persistent: topic.dest.persistent,
+              persistent_queue: topic.dest.persistent_queue,
+              time_to_live: topic.dest.time_to_live,
+              max_retries: topic.dest.max_retries,
+              retry_sleep_duration: topic.dest.retry_sleep_duration
+            },
+            opaqueData: topic.opaqueData,
+            policy: topic.policy,
+            subscribed_buckets: topic.subscribed_buckets
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: nizamial09
body: why do we need to map the entire response like this? Can the API returns us what we want from the back-end itself? I think if its returning something like

{
  topics: {
   ...
  }
}

then we could just make the API return the topic dict instead of the current dictionary? and also take care of the empty thing in the back-end. so front-end can just show them without extra processing.
created_at: 2025-03-21T10:03:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: nizamial09
body: we shouldn't ideally need an extra interface for ApiResponse. There should be just one `Topic` interface and that should handle all.

```suggestion
```
created_at: 2025-03-21T10:16:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: nizamial09
body: ```suggestion
interface Destination {
```
created_at: 2025-03-21T10:16:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,51 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel, ApiResponse } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  listTopic(): Observable<TopicModel[]> {
+    return this.http.get<ApiResponse>(this.baseURL).pipe(
+      map((response) => {
+        // Transform the response by flattening the 'dest' object
+        if (response && response.topics) {
+          return response.topics.map((topic: TopicModel) => ({
+            owner: topic.owner,
+            name: topic.name,
+            arn: topic.arn,
+            dest: {
+              push_endpoint: topic.dest.push_endpoint,
+              push_endpoint_args: topic.dest.push_endpoint_args,
+              push_endpoint_topic: topic.dest.push_endpoint_topic,
+              stored_secret: topic.dest.stored_secret,
+              persistent: topic.dest.persistent,
+              persistent_queue: topic.dest.persistent_queue,
+              time_to_live: topic.dest.time_to_live,
+              max_retries: topic.dest.max_retries,
+              retry_sleep_duration: topic.dest.retry_sleep_duration
+            },
+            opaqueData: topic.opaqueData,
+            policy: topic.policy,
+            subscribed_buckets: topic.subscribed_buckets
+          }));
+        } else {
+          return []; // Return an empty array if there are no topics
+        }
+      })
+    );
+  }
+  get(name: string) {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: nizamial09
body: always leave an empty line before a new function. I thought this is handled by the linter.. if its not there something wrong with linter and we should fix it. But to keep things neat, let's always add an empty line before.
created_at: 2025-03-21T10:18:40Z
author_association: MEMBER
diff_hunk: @@ -391,6 +395,11 @@ const routes: Routes = [
     path: 'configuration',
     data: { breadcrumbs: 'Configuration' },
     children: [{ path: '', component: RgwConfigurationPageComponent }]
+  },
+  {
+    path: 'topic',
+    data: { breadcrumbs: 'Topic' },
+    children: [{ path: '', component: RgwTopicListComponent }]
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
user: login: nizamial09
body: this shouldn't be the last item in the Object navigation entries. `Configuration` should be the last. But I don't know what should be the order, probably just above the Configuration might be okay but I'll let @cloudbehl or @afreen23 confirm it
![image](https://github.com/user-attachments/assets/a5be568c-4d5a-429f-8094-2b96f6521a40)

created_at: 2025-03-21T10:19:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,81 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Push endpoint`,
+        prop: 'dest.push_endpoint',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadTopics(context?: CdTableFetchDataContext): void {
+    this.rgwTopicService.listTopic().subscribe({
+      next: (data:TopicModel[]) => {
+        this.topics = data;
+      },
+      error: () => {
+        if (context) {
+          context.error();
+        }
+      }
+    });
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: nizamial09
body: why not use async pipe? We should use async pipe more as we upgrade through our angular versions and keep our application modern. We already have several examples if we want to understand it better.
https://github.com/ceph/ceph/blob/main/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-group/cephfs-subvolume-group.component.ts#L140

created_at: 2025-03-21T10:22:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,81 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: nizamial09
body: ```suggestion
        name: $localize`Amazon resource name`,
```
created_at: 2025-03-21T10:22:44Z
author_association: MEMBER
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: nizamial09
body: I'd ask not to create separate files for a single entities model. Instead let's keep everything in a single file. We do have a [models/](https://github.com/ceph/ceph/tree/main/src/pybind/mgr/dashboard/frontend/src/app/shared/models) directory where we add all our models and call them. Let's create a single file `rgw-topics.model.ts` there and include all the models you want for this over there.
created_at: 2025-03-21T10:25:57Z
author_association: MEMBER
diff_hunk: @@ -391,6 +395,11 @@ const routes: Routes = [
     path: 'configuration',
     data: { breadcrumbs: 'Configuration' },
     children: [{ path: '', component: RgwConfigurationPageComponent }]
+  },
+  {
+    path: 'topic',
+    data: { breadcrumbs: 'Topic' },
+    children: [{ path: '', component: RgwTopicListComponent }]
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
user: login: afreen23
body: The order would be Overview -> Gateways -> Users -> Buckets -> Topics -> Tiering/Multisite -> Configuration.
For this one, Topics can be after Users/Buckets.

Moreover, Configuration/Multisite can be renamend and moved to better places if required.
created_at: 2025-03-25T21:57:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINT_TYPE = {
+  HTTP: 'HTTP',
+  AMQP: 'AMQP',
+  Kafka: 'KAFKA'
+};
+export const AMQP_ACK_LEVEL  = {
+  Select: 'Select AMQP level',
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: afreen23
body: ```suggestion
  select: 'Select AMQP level',
```
Also where is this used ?
created_at: 2025-03-25T22:03:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -391,6 +395,11 @@ const routes: Routes = [
     path: 'configuration',
     data: { breadcrumbs: 'Configuration' },
     children: [{ path: '', component: RgwConfigurationPageComponent }]
+  },
+  {
+    path: 'topic',
+    data: { breadcrumbs: 'Topic' },
+    children: [{ path: '', component: RgwTopicListComponent }]
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
user: login: nizamial09
body: yup, there were discussions on renaming Multi-site to proper one, but we never got to decide a proper name for it as it supports both single site and multi-site. It could be just Sites but not sure..
created_at: 2025-03-28T07:51:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINT_TYPE = {
+  HTTP: 'HTTP',
+  AMQP: 'AMQP',
+  Kafka: 'KAFKA'
+};
+export const AMQP_ACK_LEVEL  = {
+  Select: 'Select AMQP level',
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-03-28T10:23:58Z
author_association: NONE
diff_hunk: @@ -0,0 +1,51 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel, ApiResponse } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  listTopic(): Observable<TopicModel[]> {
+    return this.http.get<ApiResponse>(this.baseURL).pipe(
+      map((response) => {
+        // Transform the response by flattening the 'dest' object
+        if (response && response.topics) {
+          return response.topics.map((topic: TopicModel) => ({
+            owner: topic.owner,
+            name: topic.name,
+            arn: topic.arn,
+            dest: {
+              push_endpoint: topic.dest.push_endpoint,
+              push_endpoint_args: topic.dest.push_endpoint_args,
+              push_endpoint_topic: topic.dest.push_endpoint_topic,
+              stored_secret: topic.dest.stored_secret,
+              persistent: topic.dest.persistent,
+              persistent_queue: topic.dest.persistent_queue,
+              time_to_live: topic.dest.time_to_live,
+              max_retries: topic.dest.max_retries,
+              retry_sleep_duration: topic.dest.retry_sleep_duration
+            },
+            opaqueData: topic.opaqueData,
+            policy: topic.policy,
+            subscribed_buckets: topic.subscribed_buckets
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-03-28T10:25:00Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-03-28T10:25:11Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-03-28T10:25:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,51 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { TopicModel, ApiResponse } from '~/app/ceph/rgw/rgw-topic-list/topic.model';
+import { map } from 'rxjs/operators';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  listTopic(): Observable<TopicModel[]> {
+    return this.http.get<ApiResponse>(this.baseURL).pipe(
+      map((response) => {
+        // Transform the response by flattening the 'dest' object
+        if (response && response.topics) {
+          return response.topics.map((topic: TopicModel) => ({
+            owner: topic.owner,
+            name: topic.name,
+            arn: topic.arn,
+            dest: {
+              push_endpoint: topic.dest.push_endpoint,
+              push_endpoint_args: topic.dest.push_endpoint_args,
+              push_endpoint_topic: topic.dest.push_endpoint_topic,
+              stored_secret: topic.dest.stored_secret,
+              persistent: topic.dest.persistent,
+              persistent_queue: topic.dest.persistent_queue,
+              time_to_live: topic.dest.time_to_live,
+              max_retries: topic.dest.max_retries,
+              retry_sleep_duration: topic.dest.retry_sleep_duration
+            },
+            opaqueData: topic.opaqueData,
+            policy: topic.policy,
+            subscribed_buckets: topic.subscribed_buckets
+          }));
+        } else {
+          return []; // Return an empty array if there are no topics
+        }
+      })
+    );
+  }
+  get(name: string) {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-03-28T10:25:28Z
author_association: NONE
diff_hunk: @@ -391,6 +395,11 @@ const routes: Routes = [
     path: 'configuration',
     data: { breadcrumbs: 'Configuration' },
     children: [{ path: '', component: RgwConfigurationPageComponent }]
+  },
+  {
+    path: 'topic',
+    data: { breadcrumbs: 'Topic' },
+    children: [{ path: '', component: RgwTopicListComponent }]
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-03-28T10:25:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
+        prop: 'arn',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Push endpoint`,
+        prop: 'dest.push_endpoint',
+        flexGrow: 2
+      }
+    ];
+  }
+
+  loadTopics(context?: CdTableFetchDataContext): void {
+    this.rgwTopicService.listTopic().subscribe({
+      next: (data:TopicModel[]) => {
+        this.topics = data;
+      },
+      error: () => {
+        if (context) {
+          context.error();
+        }
+      }
+    });
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-03-28T10:25:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import _ from 'lodash';
+
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { Permission } from '~/app/shared/models/permissions';
+
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
+import { TopicModel } from './topic.model';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-rgw-topic-list',
+  templateUrl: './rgw-topic-list.component.html',
+  styleUrls: ['./rgw-topic-list.component.scss']
+})
+export class RgwTopicListComponent extends ListWithDetails implements OnInit {
+  @ViewChild('table', { static: true })
+  table: TableComponent;
+  columns: CdTableColumn[];
+  permission: Permission;
+  tableActions: CdTableAction[];
+  context: CdTableFetchDataContext;
+  topics: TopicModel[];
+  errorMessage: string;
+  selection: CdTableSelection = new CdTableSelection();
+  constructor(
+    private authStorageService: AuthStorageService,
+    public actionLabels: ActionLabelsI18n,
+    private rgwTopicService: RgwTopicService
+  ) {
+    super();
+    this.permission = this.authStorageService.getPermissions().rgw;
+  }
+
+  ngOnInit() {
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Owner`,
+        prop: 'owner',
+        flexGrow: 2
+      },
+      {
+        name: $localize`Arn`,
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.ts
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-03-28T10:25:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINT_TYPE = {
+  HTTP: 'HTTP',
+  AMQP: 'AMQP',
+  Kafka: 'KAFKA'
+};
+export const AMQP_ACK_LEVEL  = {
+  Select: 'Select AMQP level',
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: pujaoshahu
body: It is used in create page
created_at: 2025-03-28T10:42:54Z
author_association: NONE
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-03-28T10:43:20Z
author_association: NONE
diff_hunk: @@ -1496,7 +1496,7 @@ def create(
     def list(self, uid: Optional[str] = None, tenant: Optional[str] = None):
         rgw_topic_instance = RgwTopicmanagement()
         result = rgw_topic_instance.list_topics(uid, tenant)
-        return result
+        return result['topics']
path: src/pybind/mgr/dashboard/controllers/rgw.py
user: login: nizamial09
body: just to be safe.
```suggestion
        return result['topics'] if 'topics' in result else []
```
created_at: 2025-04-01T05:50:04Z
author_association: MEMBER
diff_hunk: @@ -213,6 +213,11 @@
                             i18n-title
                             [useRouter]="true"
                             class="tc_submenuitem tc_submenuitem_rgw_buckets"><span i18n>Buckets</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/topic"
+                            [useRouter]="true"
+                            title="Topics"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_topics"><span i18n>Topics</span></cds-sidenav-item>
path: src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
user: login: nizamial09
body: let's put it below Users for now, until we arrange everything properly as @afreen23 mentioned before. Users takes the precendence than Bucket topics for now atleast. 
created_at: 2025-04-01T06:04:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,30 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+
+import { map } from 'rxjs/operators';
+import { TopicModel } from '../models/topic.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  listTopic(): Observable<TopicModel[]> {
+    return this.http.get<TopicModel[]>(this.baseURL).pipe(
+      map((resp) => {
+        return resp;
+      })
+    );
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: nizamial09
body: no need for the `pipe` at all
```suggestion
    return this.http.get<TopicModel[]>(this.baseURL);
created_at: 2025-04-01T06:05:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Destination;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: nizamial09
body: duplicated?
```suggestion
```
created_at: 2025-04-01T06:05:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Destination;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
+
+// The main TopicDetailModel interface for selection
+export interface TopicDetailModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Dest; // Use the 'Dest' interface
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: nizamial09
body: ```suggestion
  dest: Destination;
```
created_at: 2025-04-01T06:06:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: nizamial09
body: tbh, its clear from the file name that this is a modal, we don't need to repeat that for every interface.
```suggestion
export interface Topic {
```
created_at: 2025-04-01T06:06:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Destination;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
+
+// The main TopicDetailModel interface for selection
+export interface TopicDetailModel {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: nizamial09
body: ```suggestion
export interface TopicDetails {
```
created_at: 2025-04-01T06:06:57Z
author_association: MEMBER
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: nizamial09
body: but the file is present here, can you remove the file as well @pujaoshahu ?
created_at: 2025-04-01T06:11:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,25 @@
+import { Component, Input, SimpleChanges, OnChanges } from '@angular/core';
+import { TopicDetailModel } from './topic-details.model';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent implements OnChanges {
+  @Input()
+  selection: TopicDetailModel;
+  tenant: string;
+  topicname: string;
+  policy: {};
+  constructor() {}
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['selection'] && this.selection) {
+      if (typeof this.selection.policy === 'string') {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
user: login: nizamial09
body: @pujaoshahu why do we need this? when would this policy comes as a string?
created_at: 2025-04-01T06:12:56Z
author_association: MEMBER
diff_hunk: @@ -213,6 +213,11 @@
                             i18n-title
                             [useRouter]="true"
                             class="tc_submenuitem tc_submenuitem_rgw_buckets"><span i18n>Buckets</span></cds-sidenav-item>
+          <cds-sidenav-item route="/rgw/topic"
+                            [useRouter]="true"
+                            title="Topics"
+                            i18n-title
+                            class="tc_submenuitem tc_submenuitem_rgw_topics"><span i18n>Topics</span></cds-sidenav-item>
path: src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.html
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-04-01T07:43:12Z
author_association: NONE
diff_hunk: @@ -0,0 +1,57 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Destination;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
+
+// The main TopicDetailModel interface for selection
+export interface TopicDetailModel {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-04-01T07:43:33Z
author_association: NONE
diff_hunk: @@ -0,0 +1,57 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-04-01T07:43:38Z
author_association: NONE
diff_hunk: @@ -0,0 +1,57 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Destination;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
+
+// The main TopicDetailModel interface for selection
+export interface TopicDetailModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Dest; // Use the 'Dest' interface
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-04-01T07:43:45Z
author_association: NONE
diff_hunk: @@ -0,0 +1,57 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Destination;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-04-01T07:43:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,30 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+
+import { map } from 'rxjs/operators';
+import { TopicModel } from '../models/topic.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  listTopic(): Observable<TopicModel[]> {
+    return this.http.get<TopicModel[]>(this.baseURL).pipe(
+      map((resp) => {
+        return resp;
+      })
+    );
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-04-01T07:43:53Z
author_association: NONE
diff_hunk: @@ -1496,7 +1496,7 @@ def create(
     def list(self, uid: Optional[str] = None, tenant: Optional[str] = None):
         rgw_topic_instance = RgwTopicmanagement()
         result = rgw_topic_instance.list_topics(uid, tenant)
-        return result
+        return result['topics']
path: src/pybind/mgr/dashboard/controllers/rgw.py
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-04-01T07:43:58Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+interface TopicDest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
+}
+
+export interface TopicModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: TopicDest;
+  opaqueData: string;
+  policy: string | {};
+  subscribed_buckets: any[];
+}
+
+export interface ApiResponse {
+  topics?: TopicModel[];
+}
+
+export const ENDPOINT_TYPE = {
+  HTTP: 'HTTP',
+  AMQP: 'AMQP',
+  Kafka: 'KAFKA'
+};
+export const AMQP_ACK_LEVEL  = {
+  Select: 'Select AMQP level',
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/topic.model.ts
user: login: pujaoshahu
body: Suggested changes are done
created_at: 2025-04-01T07:44:26Z
author_association: NONE
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: pujaoshahu
body: The file is removed from the directory
created_at: 2025-04-01T07:45:37Z
author_association: NONE
diff_hunk: @@ -0,0 +1,36 @@
+// Interface for the policy statement
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: pujaoshahu
body: The suggested changes have been made


created_at: 2025-04-01T07:45:57Z
author_association: NONE
diff_hunk: @@ -0,0 +1,36 @@
+// Interface for the policy statement
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
+
+// The main TopicDetailModel interface for selection
+export interface TopicDetailModel {
+  owner: string;
+  name: string;
+  arn: string;
+  dest: Dest; // Use the 'Dest' interface
+  opaqueData: string;
+  policy: {
+    Statement: PolicyStatement[]; // Use the 'PolicyStatement' interface
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: pujaoshahu
body: The suggested changes have been made


created_at: 2025-04-01T07:46:00Z
author_association: NONE
diff_hunk: @@ -0,0 +1,36 @@
+// Interface for the policy statement
+interface PolicyStatement {
+  Sid: string;
+  Effect: string;
+  Principal: {
+    AWS: string;
+  };
+  Action: string[];
+  Resource: string;
+}
+
+// Interface for the 'dest' object
+interface Dest {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: boolean;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: string;
+  max_retries: string;
+  retry_sleep_duration: string;
+}
+
+// The main TopicDetailModel interface for selection
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: pujaoshahu
body: The suggested changes have been made


created_at: 2025-04-01T07:46:03Z
author_association: NONE
diff_hunk: @@ -0,0 +1,25 @@
+import { Component, Input, SimpleChanges, OnChanges } from '@angular/core';
+import { TopicDetailModel } from './topic-details.model';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent implements OnChanges {
+  @Input()
+  selection: TopicDetailModel;
+  tenant: string;
+  topicname: string;
+  policy: {};
+  constructor() {}
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['selection'] && this.selection) {
+      if (typeof this.selection.policy === 'string') {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
user: login: pujaoshahu
body: "policy": "{}" it is coming like this from backend





created_at: 2025-04-01T07:46:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,25 @@
+import { Component, Input, SimpleChanges, OnChanges } from '@angular/core';
+import { TopicDetailModel } from './topic-details.model';
+
+@Component({
+  selector: 'cd-rgw-topic-details',
+  templateUrl: './rgw-topic-details.component.html',
+  styleUrls: ['./rgw-topic-details.component.scss']
+})
+export class RgwTopicDetailsComponent implements OnChanges {
+  @Input()
+  selection: TopicDetailModel;
+  tenant: string;
+  topicname: string;
+  policy: {};
+  constructor() {}
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['selection'] && this.selection) {
+      if (typeof this.selection.policy === 'string') {
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.ts
user: login: pujaoshahu
body: The suggested changes have been made


created_at: 2025-04-01T07:50:35Z
author_association: NONE
diff_hunk: @@ -0,0 +1,36 @@
+// Interface for the policy statement
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/topic-details.model.ts
user: login: pujaoshahu
body: The suggested changes have been made


created_at: 2025-04-01T07:50:42Z
author_association: NONE
diff_hunk: @@ -0,0 +1,16 @@
+  <ng-container *ngIf="topic$ | async as topics">
+  <cd-table #table
+            [autoReload]="false"
+            [data]="topics"
+            [columns]="columns"
+            columnMode="flex"
+            selectionType="single"
+            [hasDetails]="true"
+            (setExpandedRow)="setExpandedRow($event)"
+            (updateSelection)="updateSelection($event)"
+            identifier="name"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.html
user: login: Pegonzal
body: ```suggestion
```
Nit; not needed as first columns is used by default
created_at: 2025-04-07T08:25:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,89 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details"
+             i18n-heading>
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+             data-testid="rgw-topic-details">
+        <tbody>
+          <tr>
+            <td i18n
+                class="bold">Push endpoint arguments</td>
+            <td>{{ selection?.dest?.push_endpoint_args }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint topic</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Stored secret</td>
+            <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent</td>
+            <td>{{ selection?.dest?.persistent }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent queue</td>
+            <td>{{ selection?.dest?.persistent_queue }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Time to live</td>
+            <td>{{ selection?.dest?.time_to_live }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Max retries</td>
+            <td>{{ selection?.dest?.max_retries }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Retry sleep duration</td>
+            <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Opaque data</td>
+            <td>{{ selection?.opaqueData }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </cds-tab>
+    <cds-tab heading="Policies"
+             i18n-heading>
+      <div class="table-scroller">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold w-25 ">Policy</td>
+              <td><pre>{{ policy | json  }}</pre></td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </cds-tab>
+    <cds-tab heading="Subscribed Buckets"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: Pegonzal
body: NIT
```suggestion
    <cds-tab heading="Subscribed buckets"
```
created_at: 2025-04-07T08:27:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import { TestBed } from '@angular/core/testing';
+
+import { RgwTopicService } from './rgw-topic.service';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('RgwTopicService', () => {
+  let service: RgwTopicService;
+
+  configureTestBed({
+    imports: [HttpClientTestingModule]
+  });
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(RgwTopicService);
+  });
+
+  it('should be created', () => {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.spec.ts
user: login: Pegonzal
body: Do not forget those!

The suggested changes have been made
created_at: 2025-04-07T08:31:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,24 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { Topic } from '~/app/shared/models/topic.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  listTopic(): Observable<Topic[]> {
+    return this.http.get<Topic[]>(this.baseURL);
+  }
+
+  get(name: string) {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: Pegonzal
body: NIT
For consistency, either `listTopics` and `getTopic` or directly `list` and `get` which I personally prefer since this is within `TopicService`.


created_at: 2025-04-07T08:35:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: Pegonzal
body: Camel case

```suggestion
  pushEndpoint: string;
  pushEndpointArgs: string;
  pushEndpointTopic: string;
 ...
```
created_at: 2025-04-07T08:37:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+interface Destination {
+  push_endpoint: string;
+  push_endpoint_args: string;
+  push_endpoint_topic: string;
+  stored_secret: string;
+  persistent: boolean;
+  persistent_queue: string;
+  time_to_live: number;
+  max_retries: number;
+  retry_sleep_duration: number;
path: src/pybind/mgr/dashboard/frontend/src/app/shared/models/topic.model.ts
user: login: Pegonzal
body: this is being used for typing the data coming from the backend that is snaked_case so what I said does not make sense, marking it as resolved
created_at: 2025-04-08T05:35:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,24 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { ApiClient } from './api-client';
+import { Topic } from '~/app/shared/models/topic.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class RgwTopicService extends ApiClient {
+  baseURL = 'api/rgw/topic';
+
+  constructor(private http: HttpClient) {
+    super();
+  }
+
+  listTopic(): Observable<Topic[]> {
+    return this.http.get<Topic[]>(this.baseURL);
+  }
+
+  get(name: string) {
path: src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-04-08T05:44:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,89 @@
+<ng-container *ngIf="!!selection">
+  <cds-tabs type="contained"
+            theme="light">
+    <cds-tab heading="Details"
+             i18n-heading>
+      <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md"
+             data-testid="rgw-topic-details">
+        <tbody>
+          <tr>
+            <td i18n
+                class="bold">Push endpoint arguments</td>
+            <td>{{ selection?.dest?.push_endpoint_args }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint topic</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint_topic}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Push endpoint</td>
+            <td class="w-75">{{ selection?.dest?.push_endpoint }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold w-25">Stored secret</td>
+            <td class="w-75">{{ selection?.dest?.stored_secret}}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent</td>
+            <td>{{ selection?.dest?.persistent }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Persistent queue</td>
+            <td>{{ selection?.dest?.persistent_queue }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Time to live</td>
+            <td>{{ selection?.dest?.time_to_live }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Max retries</td>
+            <td>{{ selection?.dest?.max_retries }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Retry sleep duration</td>
+            <td>{{ selection?.dest?.retry_sleep_duration }}</td>
+          </tr>
+          <tr>
+            <td i18n
+                class="bold">Opaque data</td>
+            <td>{{ selection?.opaqueData }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </cds-tab>
+    <cds-tab heading="Policies"
+             i18n-heading>
+      <div class="table-scroller">
+        <table class="cds--data-table--sort cds--data-table--no-border cds--data-table cds--data-table--md">
+          <tbody>
+            <tr>
+              <td i18n
+                  class="bold w-25 ">Policy</td>
+              <td><pre>{{ policy | json  }}</pre></td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </cds-tab>
+    <cds-tab heading="Subscribed Buckets"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.html
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-04-08T05:44:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,16 @@
+  <ng-container *ngIf="topic$ | async as topics">
+  <cd-table #table
+            [autoReload]="false"
+            [data]="topics"
+            [columns]="columns"
+            columnMode="flex"
+            selectionType="single"
+            [hasDetails]="true"
+            (setExpandedRow)="setExpandedRow($event)"
+            (updateSelection)="updateSelection($event)"
+            identifier="name"
path: src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.html
user: login: pujaoshahu
body: The suggested changes have been made
created_at: 2025-04-08T05:44:23Z
author_association: NONE

PR comments:
user: login: github-actions[bot]
created_at: 2025-01-29T05:57:49Z
body: This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved
author_association: NONE
user: login: cloudbehl
created_at: 2025-01-29T13:54:45Z
body: Hi Puja, Few quick Nits


1. Shouldn't these be Details instead of `Destination Endpoints` . We call this Details in all other place
2. Why do we have multi-select checkbox, do we really need it?
3. Policies tab, which policy is it, is it life cycle policy? Also Policy is denoted as `{}` 
4. Subscribed Bucket shows empty list, probably there would be better representation of Buckets. 

![Screenshot from 2025-01-30 00-17-45](https://github.com/user-attachments/assets/bbc0c0d3-1117-4101-9384-c6abe1cba125)
![Screenshot from 2025-01-30 00-17-38](https://github.com/user-attachments/assets/245a5b1f-519d-47ea-a4b0-804bf3c7a289)
![Screenshot from 2025-01-30 00-17-31](https://github.com/user-attachments/assets/39d4ceac-0d19-48cd-9dda-fba27323eae7)

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-02-05T10:00:05Z
body: This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved
author_association: NONE
user: login: nizamial09
created_at: 2025-02-12T11:46:49Z
body: Looks good overall except some of the open comments. @pujaoshahu please try to reply or address them in the comment itself so we know they are properly closed out.

And just letting you know that since this PR is dependent on https://github.com/ceph/ceph/pull/61075, its good to put that in the description with a `Depends on:` message, which is good so we don't accidentally merge this PR and be left with a non-functional feature in the branch. for now I am adding it in the description btw
author_association: MEMBER
user: login: pujaoshahu
created_at: 2025-02-12T14:38:44Z
body: All suggested changes i have done in new PR(https://github.com/ceph/ceph/pull/61586)

> Looks good overall except some of the open comments. @pujaoshahu please try to reply or address them in the comment itself so we know they are properly closed out.
> 
> And just letting you know that since this PR is dependent on #61075, its good to put that in the description with a `Depends on:` message, which is good so we don't accidentally merge this PR and be left with a non-functional feature in the branch. for now I am adding it in the description btw



> Hi Puja, Few quick Nits
> 
> 1. Shouldn't these be Details instead of `Destination Endpoints` . We call this Details in all other place
> 2. Why do we have multi-select checkbox, do we really need it?
> 3. Policies tab, which policy is it, is it life cycle policy? Also Policy is denoted as `{}`
> 4. Subscribed Bucket shows empty list, probably there would be better representation of Buckets.
> 
> ![Screenshot from 2025-01-30 00-17-45](https://private-user-images.githubusercontent.com/6695156/407757765-bbc0c0d3-1117-4101-9384-c6abe1cba125.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzNzEyMjUsIm5iZiI6MTczOTM3MDkyNSwicGF0aCI6Ii82Njk1MTU2LzQwNzc1Nzc2NS1iYmMwYzBkMy0xMTE3LTQxMDEtOTM4NC1jNmFiZTFjYmExMjUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDIxMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTAyMTJUMTQzNTI1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YjFjYWE3MjhlMjVmZjQyZmFkZDEyNjc5M2ExMzRkYTY2ODdjMWM4NTkxODY2MmM2Zjg4OWZjYjk5NzhiNWFmZSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.bifl_zTJe3MPLmX61tlKFm2fvQ1vQ8T7Xb-o2GQDMac) ![Screenshot from 2025-01-30 00-17-38](https://private-user-images.githubusercontent.com/6695156/407757781-245a5b1f-519d-47ea-a4b0-804bf3c7a289.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzNzEyMjUsIm5iZiI6MTczOTM3MDkyNSwicGF0aCI6Ii82Njk1MTU2LzQwNzc1Nzc4MS0yNDVhNWIxZi01MTlkLTQ3ZWEtYTRiMC04MDRiZjNjN2EyODkucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDIxMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTAyMTJUMTQzNTI1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OTAwODEyMDExNGU3Yzg1YTdjZTAxMmZiZDYzMTVmNDhkMmE0ZDk1MmRlYWI1ODIwNDk1ZDNjY2RkZGJiMzU4OCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.Sfw6X_PcawoYX5FzBSRrtCwYgBN8I7uFXvu-NLKhchA) ![Screenshot from 2025-01-30 00-17-31](https://private-user-images.githubusercontent.com/6695156/407757788-39d4ceac-0d19-48cd-9dda-fba27323eae7.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzNzEyMjUsIm5iZiI6MTczOTM3MDkyNSwicGF0aCI6Ii82Njk1MTU2LzQwNzc1Nzc4OC0zOWQ0Y2VhYy0wZDE5LTQ4Y2QtOWRkYS1mYmEyNzMyM2VhZTcucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDIxMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTAyMTJUMTQzNTI1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MTVjYTdmMDk3ZTg0NTkyMWM5NDNmNWZmMzkxZjAwOTY1NTUyMmUxMTlhNDIyYTU0Y2RhMzYyZjc3NTQ3MmIwNyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.ly6EAHedqRUoUcVtpFyebBdcFJvYlKoDqzUzRAkbdCQ)

![image](https://github.com/user-attachments/assets/9bed6223-9600-44f7-b37e-8527443bde04)

author_association: NONE
user: login: pujaoshahu
created_at: 2025-02-12T14:39:21Z
body: > All suggested changes i have done in new PR(#61586)
> 
> > Looks good overall except some of the open comments. @pujaoshahu please try to reply or address them in the comment itself so we know they are properly closed out.
> > And just letting you know that since this PR is dependent on #61075, its good to put that in the description with a `Depends on:` message, which is good so we don't accidentally merge this PR and be left with a non-functional feature in the branch. for now I am adding it in the description btw
> 
> > Hi Puja, Few quick Nits
> > 
> > 1. Shouldn't these be Details instead of `Destination Endpoints` . We call this Details in all other place
> > 2. Why do we have multi-select checkbox, do we really need it?
> > 3. Policies tab, which policy is it, is it life cycle policy? Also Policy is denoted as `{}`
> > 4. Subscribed Bucket shows empty list, probably there would be better representation of Buckets.
> > 
> > ![Screenshot from 2025-01-30 00-17-45](https://private-user-images.githubusercontent.com/6695156/407757765-bbc0c0d3-1117-4101-9384-c6abe1cba125.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzNzEyMjUsIm5iZiI6MTczOTM3MDkyNSwicGF0aCI6Ii82Njk1MTU2LzQwNzc1Nzc2NS1iYmMwYzBkMy0xMTE3LTQxMDEtOTM4NC1jNmFiZTFjYmExMjUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDIxMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTAyMTJUMTQzNTI1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YjFjYWE3MjhlMjVmZjQyZmFkZDEyNjc5M2ExMzRkYTY2ODdjMWM4NTkxODY2MmM2Zjg4OWZjYjk5NzhiNWFmZSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.bifl_zTJe3MPLmX61tlKFm2fvQ1vQ8T7Xb-o2GQDMac) ![Screenshot from 2025-01-30 00-17-38](https://private-user-images.githubusercontent.com/6695156/407757781-245a5b1f-519d-47ea-a4b0-804bf3c7a289.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzNzEyMjUsIm5iZiI6MTczOTM3MDkyNSwicGF0aCI6Ii82Njk1MTU2LzQwNzc1Nzc4MS0yNDVhNWIxZi01MTlkLTQ3ZWEtYTRiMC04MDRiZjNjN2EyODkucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDIxMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTAyMTJUMTQzNTI1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OTAwODEyMDExNGU3Yzg1YTdjZTAxMmZiZDYzMTVmNDhkMmE0ZDk1MmRlYWI1ODIwNDk1ZDNjY2RkZGJiMzU4OCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.Sfw6X_PcawoYX5FzBSRrtCwYgBN8I7uFXvu-NLKhchA) ![Screenshot from 2025-01-30 00-17-31](https://private-user-images.githubusercontent.com/6695156/407757788-39d4ceac-0d19-48cd-9dda-fba27323eae7.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzNzEyMjUsIm5iZiI6MTczOTM3MDkyNSwicGF0aCI6Ii82Njk1MTU2LzQwNzc1Nzc4OC0zOWQ0Y2VhYy0wZDE5LTQ4Y2QtOWRkYS1mYmEyNzMyM2VhZTcucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDIxMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTAyMTJUMTQzNTI1WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MTVjYTdmMDk3ZTg0NTkyMWM5NDNmNWZmMzkxZjAwOTY1NTUyMmUxMTlhNDIyYTU0Y2RhMzYyZjc3NTQ3MmIwNyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.ly6EAHedqRUoUcVtpFyebBdcFJvYlKoDqzUzRAkbdCQ)
> 
> ![image](https://private-user-images.githubusercontent.com/170448660/412469584-9bed6223-9600-44f7-b37e-8527443bde04.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzNzE0MjUsIm5iZiI6MTczOTM3MTEyNSwicGF0aCI6Ii8xNzA0NDg2NjAvNDEyNDY5NTg0LTliZWQ2MjIzLTk2MDAtNDRmNy1iMzdlLTg1Mjc0NDNiZGUwNC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjEyJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIxMlQxNDM4NDVaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1jOGQ2YzE3ZmMzMDg1NzIzNWVhOTZkZmRkOGM3ZWUxMmI1YjA2ZjYzNGEzZGQzMGVhOWJlY2UzYTU5ZmRkNGFmJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.h7a6JEqIIEvBZmcPo2SO9XtSrkuuez-2U52lTmYks8w)

![image](https://github.com/user-attachments/assets/69cb4574-65f9-402c-81e4-d4e12a35832a)

author_association: NONE
user: login: pujaoshahu
created_at: 2025-02-12T14:50:59Z
body: > Looks good overall except some of the open comments. @pujaoshahu please try to reply or address them in the comment itself so we know they are properly closed out.
> 
> And just letting you know that since this PR is dependent on #61075, its good to put that in the description with a `Depends on:` message, which is good so we don't accidentally merge this PR and be left with a non-functional feature in the branch. for now I am adding it in the description btw

"Thanks for adding the description."
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-02-18T10:19:05Z
body: This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved
author_association: NONE
user: login: pujaoshahu
created_at: 2025-03-04T07:00:26Z
body: jenkins test make check
author_association: NONE
user: login: pujaoshahu
created_at: 2025-03-04T07:00:58Z
body: jenkins test api
author_association: NONE
user: login: pujaoshahu
created_at: 2025-03-04T09:47:53Z
body: jenkins retest this please
author_association: NONE
user: login: pujaoshahu
created_at: 2025-03-05T09:31:13Z
body: jenkins test windows
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-27T20:13:08Z
body: This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved
author_association: NONE
user: login: pujaoshahu
created_at: 2025-03-28T10:45:35Z
body: > Thank you @pujaoshahu, sorry to keep you waiting on this but left a couple of reviews.

I have fixed the PR based on the review comments. Let me know if any further changes are required.
author_association: NONE
user: login: cloudbehl
created_at: 2025-04-03T06:20:03Z
body: @pujaoshahu linting errors

```
[lint:html    ] 
[lint:html    ] [htmllint] found 0 errors out of 271 files
[lint:html    ] All files pass linting
[lint:scss    ] Browserslist: browsers data (caniuse-lite) is 6 months old. Please run:
[lint:scss    ]   npx update-browserslist-db@latest
[lint:scss    ]   Why you should do it regularly: https://github.com/browserslist/update-db#readme
[lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(27,7): error TS2322: Type 'string' is not assignable to type '{ Statement: PolicyStatement[]; }'.
[lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(35,9): error TS2322: Type 'boolean' is not assignable to type '() => boolean'.
[lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(43,7): error TS2741: Property 'Statement' is missing in type '{}' but required in type '{ Statement: PolicyStatement[]; }'.
[lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(51,9): error TS2322: Type 'boolean' is not assignable to type '() => boolean'.
[lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(9,1): error TS6133: 'Permission' is declared but its value is never read.
[lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(75,31): error TS2339: Property 'mockReturnValue' does not exist on type '() => Observable<Topic[]>'.
[lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(88,31): error TS2345: Argument of type '{ selected: number[]; }' is not assignable to parameter of type 'CdTableSelection'.
[lint:tsc     ]   Type '{ selected: number[]; }' is missing the following properties from type 'CdTableSelection': _selected, hasMultiSelection, hasSingleSelection, hasSelection, and 3 more.
```
author_association: CONTRIBUTOR
user: login: pujaoshahu
created_at: 2025-04-03T10:04:21Z
body: Ack fixing those issue

On Thu, 3 Apr, 2025, 11:50 am Ankush Behl, ***@***.***> wrote:

> @pujaoshahu <https://github.com/pujaoshahu> linting errors
>
> [lint:html    ]
> [lint:html    ] [htmllint] found 0 errors out of 271 files
> [lint:html    ] All files pass linting
> [lint:scss    ] Browserslist: browsers data (caniuse-lite) is 6 months old. Please run:
> [lint:scss    ]   npx ***@***.***
> [lint:scss    ]   Why you should do it regularly: https://github.com/browserslist/update-db#readme
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(27,7): error TS2322: Type 'string' is not assignable to type '{ Statement: PolicyStatement[]; }'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(35,9): error TS2322: Type 'boolean' is not assignable to type '() => boolean'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(43,7): error TS2741: Property 'Statement' is missing in type '{}' but required in type '{ Statement: PolicyStatement[]; }'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(51,9): error TS2322: Type 'boolean' is not assignable to type '() => boolean'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(9,1): error TS6133: 'Permission' is declared but its value is never read.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(75,31): error TS2339: Property 'mockReturnValue' does not exist on type '() => Observable<Topic[]>'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(88,31): error TS2345: Argument of type '{ selected: number[]; }' is not assignable to parameter of type 'CdTableSelection'.
> [lint:tsc     ]   Type '{ selected: number[]; }' is missing the following properties from type 'CdTableSelection': _selected, hasMultiSelection, hasSingleSelection, hasSelection, and 3 more.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/ceph/ceph/pull/61273#issuecomment-2774606729>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/BIUNOFGKTXLPYZPXN7NAIB32XTHKXAVCNFSM6AAAAABU3LG5J6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDONZUGYYDMNZSHE>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
> [image: cloudbehl]*cloudbehl* left a comment (ceph/ceph#61273)
> <https://github.com/ceph/ceph/pull/61273#issuecomment-2774606729>
>
> @pujaoshahu <https://github.com/pujaoshahu> linting errors
>
> [lint:html    ]
> [lint:html    ] [htmllint] found 0 errors out of 271 files
> [lint:html    ] All files pass linting
> [lint:scss    ] Browserslist: browsers data (caniuse-lite) is 6 months old. Please run:
> [lint:scss    ]   npx ***@***.***
> [lint:scss    ]   Why you should do it regularly: https://github.com/browserslist/update-db#readme
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(27,7): error TS2322: Type 'string' is not assignable to type '{ Statement: PolicyStatement[]; }'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(35,9): error TS2322: Type 'boolean' is not assignable to type '() => boolean'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(43,7): error TS2741: Property 'Statement' is missing in type '{}' but required in type '{ Statement: PolicyStatement[]; }'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(51,9): error TS2322: Type 'boolean' is not assignable to type '() => boolean'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(9,1): error TS6133: 'Permission' is declared but its value is never read.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(75,31): error TS2339: Property 'mockReturnValue' does not exist on type '() => Observable<Topic[]>'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(88,31): error TS2345: Argument of type '{ selected: number[]; }' is not assignable to parameter of type 'CdTableSelection'.
> [lint:tsc     ]   Type '{ selected: number[]; }' is missing the following properties from type 'CdTableSelection': _selected, hasMultiSelection, hasSingleSelection, hasSelection, and 3 more.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/ceph/ceph/pull/61273#issuecomment-2774606729>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/BIUNOFGKTXLPYZPXN7NAIB32XTHKXAVCNFSM6AAAAABU3LG5J6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDONZUGYYDMNZSHE>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>

author_association: NONE
user: login: Pegonzal
created_at: 2025-04-07T07:08:58Z
body: jenkins test make check
author_association: CONTRIBUTOR
user: login: pujaoshahu
created_at: 2025-04-08T08:01:26Z
body: jenkins test make check
author_association: NONE
user: login: pujaoshahu
created_at: 2025-04-09T06:32:14Z
body: > @pujaoshahu linting errors
> 
> ```
> [lint:html    ] 
> [lint:html    ] [htmllint] found 0 errors out of 271 files
> [lint:html    ] All files pass linting
> [lint:scss    ] Browserslist: browsers data (caniuse-lite) is 6 months old. Please run:
> [lint:scss    ]   npx update-browserslist-db@latest
> [lint:scss    ]   Why you should do it regularly: https://github.com/browserslist/update-db#readme
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(27,7): error TS2322: Type 'string' is not assignable to type '{ Statement: PolicyStatement[]; }'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(35,9): error TS2322: Type 'boolean' is not assignable to type '() => boolean'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(43,7): error TS2741: Property 'Statement' is missing in type '{}' but required in type '{ Statement: PolicyStatement[]; }'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-details/rgw-topic-details.component.spec.ts(51,9): error TS2322: Type 'boolean' is not assignable to type '() => boolean'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(9,1): error TS6133: 'Permission' is declared but its value is never read.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(75,31): error TS2339: Property 'mockReturnValue' does not exist on type '() => Observable<Topic[]>'.
> [lint:tsc     ] src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts(88,31): error TS2345: Argument of type '{ selected: number[]; }' is not assignable to parameter of type 'CdTableSelection'.
> [lint:tsc     ]   Type '{ selected: number[]; }' is missing the following properties from type 'CdTableSelection': _selected, hasMultiSelection, hasSingleSelection, hasSelection, and 3 more.
> ```
The error above has been resolved
author_association: NONE
user: login: pujaoshahu
created_at: 2025-04-10T05:38:47Z
body: jenkins test make check

author_association: NONE
user: login: pujaoshahu
created_at: 2025-04-10T05:39:27Z
body: jenkins retest this please

author_association: NONE
user: login: pujaoshahu
created_at: 2025-04-11T06:59:56Z
body: jenkins test make check
author_association: NONE
user: login: pujaoshahu
created_at: 2025-04-11T09:29:28Z
body: jenkins test make check arm64
author_association: NONE
user: login: pujaoshahu
created_at: 2025-04-14T05:46:22Z
body: jenkins test make check arm64
author_association: NONE
user: login: pujaoshahu
created_at: 2025-04-14T05:47:20Z
body: jenkins test dashboard
author_association: NONE