Skip to content

Commit 734c5eb

Browse files
authored
Merge pull request ceph#59807 from afreen23/wip-group-switcher
mgr/dashboard: Add group selector in subsystems views Reviewed-by: Afreen Misbah <[email protected]>
2 parents 9f669bf + 7c6c16c commit 734c5eb

File tree

12 files changed

+206
-46
lines changed

12 files changed

+206
-46
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { NvmeofInitiatorsFormComponent } from './nvmeof-initiators-form/nvmeof-i
5353
import {
5454
ButtonModule,
5555
CheckboxModule,
56+
ComboBoxModule,
5657
DatePickerModule,
5758
GridModule,
5859
IconModule,
@@ -95,7 +96,8 @@ import Reset from '@carbon/icons/es/reset/32';
9596
SelectModule,
9697
NumberModule,
9798
ModalModule,
98-
DatePickerModule
99+
DatePickerModule,
100+
ComboBoxModule
99101
],
100102
declarations: [
101103
RbdListComponent,

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-form/nvmeof-listeners-form.component.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import { FormatterService } from '~/app/shared/services/formatter.service';
1313
import { CdValidators } from '~/app/shared/forms/cd-validators';
1414
import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
1515
import { HostService } from '~/app/shared/api/host.service';
16-
import { DaemonService } from '~/app/shared/api/daemon.service';
1716
import { map } from 'rxjs/operators';
1817
import { forkJoin } from 'rxjs';
18+
import { CephServiceSpec } from '~/app/shared/models/service.interface';
1919

2020
@Component({
2121
selector: 'cd-nvmeof-listeners-form',
@@ -31,6 +31,7 @@ export class NvmeofListenersFormComponent implements OnInit {
3131
listenerForm: CdFormGroup;
3232
subsystemNQN: string;
3333
hosts: Array<object> = null;
34+
group: string;
3435

3536
constructor(
3637
public actionLabels: ActionLabelsI18n,
@@ -42,8 +43,7 @@ export class NvmeofListenersFormComponent implements OnInit {
4243
private route: ActivatedRoute,
4344
public activeModal: NgbActiveModal,
4445
public formatterService: FormatterService,
45-
public dimlessBinaryPipe: DimlessBinaryPipe,
46-
private daemonService: DaemonService
46+
public dimlessBinaryPipe: DimlessBinaryPipe
4747
) {
4848
this.permission = this.authStorageService.getPermissions().nvmeof;
4949
this.hostPermission = this.authStorageService.getPermissions().hosts;
@@ -53,13 +53,20 @@ export class NvmeofListenersFormComponent implements OnInit {
5353

5454
setHosts() {
5555
forkJoin({
56-
daemons: this.daemonService.list(['nvmeof']),
56+
gwGroups: this.nvmeofService.listGatewayGroups(),
5757
hosts: this.hostService.getAllHosts()
5858
})
5959
.pipe(
60-
map(({ daemons, hosts }) => {
61-
const hostNamesFromDaemon = daemons.map((daemon: any) => daemon.hostname);
62-
return hosts.filter((host: any) => hostNamesFromDaemon.includes(host.hostname));
60+
map(({ gwGroups, hosts }) => {
61+
// Find the gateway hosts in current group
62+
const selectedGwGroup: CephServiceSpec = gwGroups?.[0]?.find(
63+
(gwGroup: CephServiceSpec) => gwGroup?.spec?.group === this.group
64+
);
65+
const gatewayHosts: string[] = selectedGwGroup?.placement?.hosts;
66+
// Return the gateway hosts in current group with their metadata
67+
return gatewayHosts
68+
? hosts.filter((host: any) => gatewayHosts.includes(host.hostname))
69+
: [];
6370
})
6471
)
6572
.subscribe((nvmeofHosts: any[]) => {
@@ -71,7 +78,10 @@ export class NvmeofListenersFormComponent implements OnInit {
7178
this.createForm();
7279
this.action = this.actionLabels.CREATE;
7380
this.route.params.subscribe((params: { subsystem_nqn: string }) => {
74-
this.subsystemNQN = params.subsystem_nqn;
81+
this.subsystemNQN = params?.subsystem_nqn;
82+
});
83+
this.route.queryParams.subscribe((params) => {
84+
this.group = params?.['group'];
7585
});
7686
this.setHosts();
7787
}
@@ -118,7 +128,9 @@ export class NvmeofListenersFormComponent implements OnInit {
118128
component.listenerForm.setErrors({ cdSubmitButton: true });
119129
},
120130
complete: () => {
121-
this.router.navigate([this.pageURL, { outlets: { modal: null } }]);
131+
this.router.navigate([this.pageURL, { outlets: { modal: null } }], {
132+
queryParams: { group: this.group }
133+
});
122134
}
123135
});
124136
}

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-listeners-list/nvmeof-listeners-list.component.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const BASE_URL = 'block/nvmeof/subsystems';
2424
export class NvmeofListenersListComponent implements OnInit, OnChanges {
2525
@Input()
2626
subsystemNQN: string;
27+
@Input()
28+
group: string;
2729

2830
listenerColumns: any;
2931
tableActions: CdTableAction[];
@@ -64,10 +66,10 @@ export class NvmeofListenersListComponent implements OnInit, OnChanges {
6466
permission: 'create',
6567
icon: Icons.add,
6668
click: () =>
67-
this.router.navigate([
68-
BASE_URL,
69-
{ outlets: { modal: [URLVerbs.CREATE, this.subsystemNQN, 'listener'] } }
70-
]),
69+
this.router.navigate(
70+
[BASE_URL, { outlets: { modal: [URLVerbs.CREATE, this.subsystemNQN, 'listener'] } }],
71+
{ queryParams: { group: this.group } }
72+
),
7173
canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
7274
},
7375
{

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
<a ngbNavLink
1616
i18n>Listeners</a>
1717
<ng-template ngbNavContent>
18-
<cd-nvmeof-listeners-list [subsystemNQN]="subsystemNQN">
18+
<cd-nvmeof-listeners-list [subsystemNQN]="subsystemNQN"
19+
[group]="group">
1920
</cd-nvmeof-listeners-list>
2021
</ng-template>
2122
</ng-container>

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-details/nvmeof-subsystems-details.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { NvmeofSubsystem } from '~/app/shared/models/nvmeof';
99
export class NvmeofSubsystemsDetailsComponent implements OnChanges {
1010
@Input()
1111
selection: NvmeofSubsystem;
12+
@Input()
13+
group: NvmeofSubsystem;
1214

1315
selectedItem: any;
1416
data: any;

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('NvmeofSubsystemsFormComponent', () => {
2020
let form: CdFormGroup;
2121
let formHelper: FormHelper;
2222
const mockTimestamp = 1720693470789;
23+
const mockGroupName = 'default';
2324

2425
beforeEach(async () => {
2526
spyOn(Date, 'now').and.returnValue(mockTimestamp);
@@ -42,6 +43,7 @@ describe('NvmeofSubsystemsFormComponent', () => {
4243
form = component.subsystemForm;
4344
formHelper = new FormHelper(form);
4445
fixture.detectChanges();
46+
component.group = mockGroupName;
4547
});
4648

4749
it('should create', () => {
@@ -60,7 +62,8 @@ describe('NvmeofSubsystemsFormComponent', () => {
6062
expect(nvmeofService.createSubsystem).toHaveBeenCalledWith({
6163
nqn: expectedNqn,
6264
max_namespaces: MAX_NAMESPACE,
63-
enable_ha: true
65+
enable_ha: true,
66+
gw_group: mockGroupName
6467
});
6568
});
6669

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems-form/nvmeof-subsystems-form.component.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Permission } from '~/app/shared/models/permissions';
99
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
1010
import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
1111
import { FinishedTask } from '~/app/shared/models/finished-task';
12-
import { Router } from '@angular/router';
12+
import { ActivatedRoute, Router } from '@angular/router';
1313
import { MAX_NAMESPACE, NvmeofService } from '~/app/shared/api/nvmeof.service';
1414

1515
@Component({
@@ -24,14 +24,16 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
2424
resource: string;
2525
pageURL: string;
2626
defaultMaxNamespace: number = MAX_NAMESPACE;
27+
group: string;
2728

2829
constructor(
2930
private authStorageService: AuthStorageService,
3031
public actionLabels: ActionLabelsI18n,
3132
public activeModal: NgbActiveModal,
3233
private nvmeofService: NvmeofService,
3334
private taskWrapperService: TaskWrapperService,
34-
private router: Router
35+
private router: Router,
36+
private route: ActivatedRoute
3537
) {
3638
this.permission = this.authStorageService.getPermissions().nvmeof;
3739
this.resource = $localize`Subsystem`;
@@ -49,6 +51,9 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
4951
);
5052

5153
ngOnInit() {
54+
this.route.queryParams.subscribe((params) => {
55+
this.group = params?.['group'];
56+
});
5257
this.createForm();
5358
this.action = this.actionLabels.CREATE;
5459
}
@@ -66,7 +71,13 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
6671
)
6772
],
6873
asyncValidators: [
69-
CdValidators.unique(this.nvmeofService.isSubsystemPresent, this.nvmeofService)
74+
CdValidators.unique(
75+
this.nvmeofService.isSubsystemPresent,
76+
this.nvmeofService,
77+
null,
78+
null,
79+
this.group
80+
)
7081
]
7182
}),
7283
max_namespaces: new UntypedFormControl(this.defaultMaxNamespace, {
@@ -87,8 +98,9 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
8798

8899
const request = {
89100
nqn,
90-
max_namespaces,
91-
enable_ha: true
101+
enable_ha: true,
102+
gw_group: this.group,
103+
max_namespaces
92104
};
93105

94106
if (!max_namespaces) {
@@ -106,7 +118,9 @@ export class NvmeofSubsystemsFormComponent implements OnInit {
106118
component.subsystemForm.setErrors({ cdSubmitButton: true });
107119
},
108120
complete: () => {
109-
this.router.navigate([this.pageURL, { outlets: { modal: null } }]);
121+
this.router.navigate([this.pageURL, { outlets: { modal: null } }], {
122+
queryParams: { group: this.group }
123+
});
110124
}
111125
});
112126
}

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.html

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
11
<cd-nvmeof-tabs></cd-nvmeof-tabs>
2+
3+
<div class="pb-3"
4+
cdsCol
5+
[columnNumbers]="{md: 4}">
6+
<cds-combo-box
7+
type="single"
8+
label="Selected Gateway Group"
9+
i18n-placeholder
10+
placeholder="Enter group"
11+
[items]="gwGroups"
12+
(selected)="onGroupSelection($event)"
13+
(clear)="onGroupClear()">
14+
<cds-dropdown-list></cds-dropdown-list>
15+
</cds-combo-box>
16+
</div>
17+
218
<legend i18n>
319
Subsystems
420
<cd-help-text>
@@ -23,7 +39,8 @@
2339
</div>
2440

2541
<cd-nvmeof-subsystems-details *cdTableDetail
26-
[selection]="expandedRow">
42+
[selection]="expandedRow"
43+
[group]="group">
2744
</cd-nvmeof-subsystems-details>
2845
</cd-table>
2946
<router-outlet name="modal"></router-outlet>

src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-subsystems/nvmeof-subsystems.component.spec.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
1111
import { NvmeofSubsystemsComponent } from './nvmeof-subsystems.component';
1212
import { NvmeofTabsComponent } from '../nvmeof-tabs/nvmeof-tabs.component';
1313
import { NvmeofSubsystemsDetailsComponent } from '../nvmeof-subsystems-details/nvmeof-subsystems-details.component';
14+
import { ComboBoxModule, GridModule } from 'carbon-components-angular';
1415

1516
const mockSubsystems = [
1617
{
@@ -26,10 +27,36 @@ const mockSubsystems = [
2627
}
2728
];
2829

30+
const mockGroups = [
31+
[
32+
{
33+
service_name: 'nvmeof.rbd.default',
34+
service_type: 'nvmeof',
35+
unmanaged: false,
36+
spec: {
37+
group: 'default'
38+
}
39+
},
40+
{
41+
service_name: 'nvmeof.rbd.foo',
42+
service_type: 'nvmeof',
43+
unmanaged: false,
44+
spec: {
45+
group: 'foo'
46+
}
47+
}
48+
],
49+
2
50+
];
51+
2952
class MockNvmeOfService {
3053
listSubsystems() {
3154
return of(mockSubsystems);
3255
}
56+
57+
listGatewayGroups() {
58+
return of(mockGroups);
59+
}
3360
}
3461

3562
class MockAuthStorageService {
@@ -53,7 +80,7 @@ describe('NvmeofSubsystemsComponent', () => {
5380
NvmeofTabsComponent,
5481
NvmeofSubsystemsDetailsComponent
5582
],
56-
imports: [HttpClientModule, RouterTestingModule, SharedModule],
83+
imports: [HttpClientModule, RouterTestingModule, SharedModule, ComboBoxModule, GridModule],
5784
providers: [
5885
{ provide: NvmeofService, useClass: MockNvmeOfService },
5986
{ provide: AuthStorageService, useClass: MockAuthStorageService },
@@ -77,4 +104,12 @@ describe('NvmeofSubsystemsComponent', () => {
77104
tick();
78105
expect(component.subsystems).toEqual(mockSubsystems);
79106
}));
107+
108+
it('should load gateway groups correctly', () => {
109+
expect(component.gwGroups.length).toBe(2);
110+
});
111+
112+
it('should set first group as default initially', () => {
113+
expect(component.group).toBe(mockGroups[0][0].spec.group);
114+
});
80115
});

0 commit comments

Comments
 (0)