Skip to content

Commit 92fb586

Browse files
author
pujaoshahu
committed
mgr/dashboard: Add RGW bucket notification listing in dashboard
Fixes: https://tracker.ceph.com/issues/70880 Signed-off-by: pujaoshahu <[email protected]> Signed-off-by: pujashahu <[email protected]>
1 parent 6d3dffc commit 92fb586

File tree

11 files changed

+258
-7
lines changed

11 files changed

+258
-7
lines changed

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,13 @@
238238
(updateBucketDetails)="updateBucketDetails(extractLifecycleDetails.bind(this))"></cd-rgw-bucket-lifecycle-list>
239239
</ng-template>
240240
</ng-container>
241+
<ng-container ngbNavItem="notification">
242+
<a ngbNavLink
243+
i18n>Notification</a>
244+
<ng-template ngbNavContent>
245+
<cd-rgw-bucket-notification-list [bucket]="selection"></cd-rgw-bucket-notification-list>
246+
</ng-template>
247+
</ng-container>
241248
</nav>
242249

243250
<div [ngbNavOutlet]="nav"></div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<fieldset>
2+
<legend i18n
3+
class="cd-header">
4+
Notification Configuration
5+
<cd-help-text>
6+
Configure bucket notification to trigger alerts for specific events, such as object creation or transitions, based on prefixes or tags.
7+
</cd-help-text>
8+
</legend>
9+
</fieldset>
10+
<ng-container *ngIf="notification$ | async as notification">
11+
<cd-table #table
12+
[data]="notification"
13+
[columns]="columns"
14+
columnMode="flex"
15+
selectionType="single"
16+
identifier="Id"
17+
(fetchData)="fetchData()">
18+
</cd-table>
19+
</ng-container>
20+
<ng-template #filterTpl
21+
let-config="data.value">
22+
<ng-container *ngIf="config">
23+
<ng-container *ngFor="let item of config | keyvalue">
24+
<ng-container *ngIf="item.value?.FilterRule?.length">
25+
<div class="cds--label">
26+
{{ item.key }}:
27+
</div>
28+
<div [cdsStack]="'horizontal'"
29+
*ngFor="let rule of item.value.FilterRule">
30+
<cds-tag size="sm"
31+
class="badge-background-gray">{{ rule.Name }}: {{ rule.Value }}</cds-tag>
32+
</div>
33+
<br>
34+
</ng-container>
35+
</ng-container>
36+
</ng-container>
37+
</ng-template>
38+
<ng-template #eventTpl
39+
let-event="data.value">
40+
<ng-container *ngIf="event">
41+
<cds-tag size="sm"
42+
class="badge-background-primary">
43+
{{ event }}
44+
</cds-tag>
45+
</ng-container>
46+
</ng-template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@use '@carbon/layout';
2+
3+
::ng-deep.cds--type-mono {
4+
margin-right: layout.$spacing-02;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { HttpClientTestingModule } from '@angular/common/http/testing';
3+
import { RgwBucketNotificationListComponent } from './rgw-bucket-notification-list.component';
4+
import { configureTestBed } from '~/testing/unit-test-helper';
5+
import { ComponentsModule } from '~/app/shared/components/components.module';
6+
import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
7+
import { of } from 'rxjs';
8+
9+
class MockRgwBucketService {
10+
listNotification = jest.fn((bucket: string) => of([{ bucket, notifications: [] }]));
11+
}
12+
describe('RgwBucketNotificationListComponent', () => {
13+
let component: RgwBucketNotificationListComponent;
14+
let fixture: ComponentFixture<RgwBucketNotificationListComponent>;
15+
let rgwtbucketService: RgwBucketService;
16+
let rgwnotificationListSpy: jasmine.Spy;
17+
18+
configureTestBed({
19+
declarations: [RgwBucketNotificationListComponent],
20+
imports: [ComponentsModule, HttpClientTestingModule],
21+
providers: [
22+
{ provide: 'bucket', useValue: { bucket: 'bucket1', owner: 'dashboard' } },
23+
{ provide: RgwBucketService, useClass: MockRgwBucketService }
24+
]
25+
});
26+
27+
beforeEach(() => {
28+
fixture = TestBed.createComponent(RgwBucketNotificationListComponent);
29+
component = fixture.componentInstance;
30+
rgwtbucketService = TestBed.inject(RgwBucketService);
31+
rgwnotificationListSpy = spyOn(rgwtbucketService, 'listNotification').and.callThrough();
32+
33+
fixture = TestBed.createComponent(RgwBucketNotificationListComponent);
34+
component = fixture.componentInstance;
35+
fixture.detectChanges();
36+
});
37+
38+
it('should create', () => {
39+
expect(component).toBeTruthy();
40+
});
41+
it('should call list', () => {
42+
rgwtbucketService.listNotification('testbucket').subscribe((response) => {
43+
expect(response).toEqual([{ bucket: 'testbucket', notifications: [] }]);
44+
});
45+
expect(rgwnotificationListSpy).toHaveBeenCalledWith('testbucket');
46+
});
47+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core';
2+
import { BehaviorSubject, Observable, of } from 'rxjs';
3+
import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
4+
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
5+
import { TableComponent } from '~/app/shared/datatable/table/table.component';
6+
import { CdTableAction } from '~/app/shared/models/cd-table-action';
7+
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
8+
import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
9+
import { Permission } from '~/app/shared/models/permissions';
10+
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
11+
import { URLBuilderService } from '~/app/shared/services/url-builder.service';
12+
import { Bucket } from '../models/rgw-bucket';
13+
import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
14+
import { catchError, switchMap } from 'rxjs/operators';
15+
import { TopicConfiguration } from '~/app/shared/models/notification-configuration.model';
16+
import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
17+
import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
18+
19+
const BASE_URL = 'rgw/bucket';
20+
@Component({
21+
selector: 'cd-rgw-bucket-notification-list',
22+
templateUrl: './rgw-bucket-notification-list.component.html',
23+
styleUrl: './rgw-bucket-notification-list.component.scss',
24+
providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
25+
})
26+
export class RgwBucketNotificationListComponent extends ListWithDetails implements OnInit {
27+
@Input() bucket: Bucket;
28+
table: TableComponent;
29+
permission: Permission;
30+
tableActions: CdTableAction[];
31+
columns: CdTableColumn[] = [];
32+
selection: CdTableSelection = new CdTableSelection();
33+
notification$: Observable<TopicConfiguration[]>;
34+
subject = new BehaviorSubject<TopicConfiguration[]>([]);
35+
context: CdTableFetchDataContext;
36+
@ViewChild('filterTpl', { static: true })
37+
filterTpl: TemplateRef<any>;
38+
@ViewChild('eventTpl', { static: true })
39+
eventTpl: TemplateRef<any>;
40+
41+
constructor(
42+
private rgwBucketService: RgwBucketService,
43+
private authStorageService: AuthStorageService,
44+
public actionLabels: ActionLabelsI18n
45+
) {
46+
super();
47+
}
48+
49+
ngOnInit() {
50+
this.permission = this.authStorageService.getPermissions().rgw;
51+
this.columns = [
52+
{
53+
name: $localize`Name`,
54+
prop: 'Id',
55+
flexGrow: 2
56+
},
57+
{
58+
name: $localize`Topic`,
59+
prop: 'Topic',
60+
flexGrow: 1,
61+
cellTransformation: CellTemplate.copy
62+
},
63+
{
64+
name: $localize`Event`,
65+
prop: 'Event',
66+
flexGrow: 1,
67+
cellTemplate: this.eventTpl
68+
},
69+
{
70+
name: $localize`Filter`,
71+
prop: 'Filter',
72+
flexGrow: 1,
73+
cellTemplate: this.filterTpl
74+
}
75+
];
76+
77+
this.notification$ = this.subject.pipe(
78+
switchMap(() =>
79+
this.rgwBucketService.listNotification(this.bucket.bucket).pipe(
80+
catchError((error) => {
81+
this.context.error(error);
82+
return of(null);
83+
})
84+
)
85+
)
86+
);
87+
}
88+
89+
fetchData() {
90+
this.subject.next([]);
91+
}
92+
}

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-topic-list/rgw-topic-list.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
22
import { RgwTopicListComponent } from './rgw-topic-list.component';
3-
import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
43
import { SharedModule } from '~/app/shared/shared.module';
54
import { configureTestBed, PermissionHelper } from '~/testing/unit-test-helper';
65
import { RgwTopicDetailsComponent } from '../rgw-topic-details/rgw-topic-details.component';
@@ -9,6 +8,7 @@ import { RouterTestingModule } from '@angular/router/testing';
98
import { HttpClientTestingModule } from '@angular/common/http/testing';
109
import { ToastrModule } from 'ngx-toastr';
1110
import { of } from 'rxjs';
11+
import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
1212

1313
describe('RgwTopicListComponent', () => {
1414
let component: RgwTopicListComponent;

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ import {
8484
TooltipModule,
8585
ComboBoxModule,
8686
ToggletipModule,
87-
IconService
87+
IconService,
88+
LayoutModule
8889
} from 'carbon-components-angular';
8990
import EditIcon from '@carbon/icons/es/edit/16';
9091
import ScalesIcon from '@carbon/icons/es/scales/20';
@@ -112,6 +113,7 @@ import { NfsClusterComponent } from '../nfs/nfs-cluster/nfs-cluster.component';
112113
import { RgwTopicListComponent } from './rgw-topic-list/rgw-topic-list.component';
113114
import { RgwTopicDetailsComponent } from './rgw-topic-details/rgw-topic-details.component';
114115
import { RgwTopicFormComponent } from './rgw-topic-form/rgw-topic-form.component';
116+
import { RgwBucketNotificationListComponent } from './rgw-bucket-notification-list/rgw-bucket-notification-list.component';
115117

116118
@NgModule({
117119
imports: [
@@ -148,7 +150,8 @@ import { RgwTopicFormComponent } from './rgw-topic-form/rgw-topic-form.component
148150
ComboBoxModule,
149151
ToggletipModule,
150152
RadioModule,
151-
SelectModule
153+
SelectModule,
154+
LayoutModule
152155
],
153156
exports: [
154157
RgwDaemonDetailsComponent,
@@ -212,7 +215,8 @@ import { RgwTopicFormComponent } from './rgw-topic-form/rgw-topic-form.component
212215
RgwRateLimitDetailsComponent,
213216
RgwTopicListComponent,
214217
RgwTopicDetailsComponent,
215-
RgwTopicFormComponent
218+
RgwTopicFormComponent,
219+
RgwBucketNotificationListComponent
216220
],
217221
providers: [TitleCasePipe]
218222
})

src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,24 @@ export class RgwBucketService extends ApiClient {
281281
getGlobalBucketRateLimit() {
282282
return this.http.get(`${this.url}/ratelimit`);
283283
}
284+
285+
listNotification(bucket_name: string) {
286+
return this.rgwDaemonService.request((params: HttpParams) => {
287+
params = params.appendAll({
288+
bucket_name: bucket_name
289+
});
290+
return this.http.get(`${this.url}/notification`, { params: params });
291+
});
292+
}
293+
294+
setNotification(bucket_name: string, notification: string, owner: string) {
295+
return this.rgwDaemonService.request((params: HttpParams) => {
296+
params = params.appendAll({
297+
bucket_name: bucket_name,
298+
notification: notification,
299+
owner: owner
300+
});
301+
return this.http.put(`${this.url}/notification`, null, { params: params });
302+
});
303+
}
284304
}

src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-topic.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export class RgwTopicService extends ApiClient {
1717
super();
1818
}
1919

20-
listTopic(): Observable<Topic[]> {
21-
return this.http.get<Topic[]>(this.baseURL);
20+
listTopic(): Observable<Topic> {
21+
return this.http.get<Topic>(this.baseURL);
2222
}
2323

2424
getTopic(key: string) {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export interface NotificationConfiguration {
2+
TopicConfiguration: TopicConfiguration[];
3+
}
4+
5+
export interface TopicConfiguration {
6+
Id: string;
7+
Topic: string;
8+
Event: string[];
9+
Filter?: Filter;
10+
}
11+
12+
export interface Filter {
13+
Key: Key;
14+
Metadata: Metadata;
15+
Tags: Tags;
16+
}
17+
18+
export interface Key {
19+
FilterRules: FilterRules[];
20+
}
21+
export interface Metadata {
22+
FilterRules: FilterRules[];
23+
}
24+
export interface Tags {
25+
FilterRules: FilterRules[];
26+
}
27+
export interface FilterRules {
28+
Name: string;
29+
Value: string;
30+
}

0 commit comments

Comments
 (0)