From: pujashahu Date: Wed, 24 Jun 2026 08:08:54 +0000 (+0530) Subject: mgr/dashboard: Implement backword support for Create existing nvme-oF gateway groups... X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=f6d0164a05580cf71d74e5e11fcb7a1b21e595be;p=ceph.git mgr/dashboard: Implement backword support for Create existing nvme-oF gateway groups through UI Fixes : https://tracker.ceph.com/issues/77632 Signed-off-by: pujaoshahu --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts index 722832ae977..2291a091997 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts @@ -10,6 +10,7 @@ import { ActionLabels, URLVerbs } from '~/app/shared/constants/app.constants'; import { FeatureTogglesGuardService } from '~/app/shared/services/feature-toggles-guard.service'; import { ModuleStatusGuardService } from '~/app/shared/services/module-status-guard.service'; import { SharedModule } from '~/app/shared/shared.module'; +import { TextLabelListComponent } from '~/app/shared/components/text-label-list/text-label-list.component'; import { IscsiSettingComponent } from './iscsi-setting/iscsi-setting.component'; import { IscsiTabsComponent } from './iscsi-tabs/iscsi-tabs.component'; import { IscsiTargetDetailsComponent } from './iscsi-target-details/iscsi-target-details.component'; @@ -148,7 +149,8 @@ import { NvmeofEditAuthenticationComponent } from './nvmeof-edit-authentication/ LayoutModule, ThemeModule, NvmeofSetupCardsComponent, - NvmeofGatewayGroupFilterComponent + NvmeofGatewayGroupFilterComponent, + TextLabelListComponent ], declarations: [ RbdListComponent, @@ -380,6 +382,17 @@ const routes: Routes = [ } } }, + { + path: `${URLVerbs.EDIT}/:name`, + component: NvmeofGroupFormComponent, + data: { + breadcrumbs: ActionLabels.EDIT, + pageHeader: { + title: $localize`Edit Gateway Group`, + description: $localize`Modify gateway group configuration.` + } + } + }, { path: `${URLVerbs.VIEW}/:group`, component: NvmeGatewayViewComponent, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.html index 287361a41dc..8d4007806bc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.html @@ -1,43 +1,27 @@ -
-
- - - - - - + + - -
- - -
-
-
-
-
+
+ + +
+ + @@ -45,10 +29,9 @@ + let-value="data.value"> {{ value }} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.scss index 71694a4ab86..a5f8a62aac9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.scss @@ -4,7 +4,3 @@ cd-details-card { display: block; margin-bottom: layout.$spacing-05; } - -.table-actions { - margin-top: layout.$spacing-03; -} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts index 04c3bf4fbe7..0c436fa10d6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-group/nvmeof-gateway-group.component.ts @@ -26,7 +26,6 @@ import { ModalCdsService } from '~/app/shared/services/modal-cds.service'; import { CephServiceService } from '~/app/shared/api/ceph-service.service'; import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component'; -import { DetailItem } from '~/app/shared/components/details-card/details-card.component'; import { FinishedTask } from '~/app/shared/models/finished-task'; import { DeletionImpact } from '~/app/shared/enum/delete-confirmation-modal-impact.enum'; import { NotificationService } from '~/app/shared/services/notification.service'; @@ -74,11 +73,11 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { gatewayGroupName = ''; subsystemCount = 0; gatewayCount = 0; - selectedGatewayDetails: DetailItem[] = []; private lastGroupCount = 0; viewUrl = `/${BASE_URL}/view`; icons = Icons; + iconSize = IconSize; constructor( @@ -191,8 +190,7 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { }), catchError(() => { return of([]); - }), - finalize(() => this.setTableLoading(false)) + }) ) ), shareReplay({ bufferSize: 1, refCount: true }), @@ -210,20 +208,12 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { .subscribe(() => this.fetchData()); } fetchData(): void { - this.setTableLoading(true); this.subject.next([]); this.checkNodesAvailability(); } - private setTableLoading(loading: boolean): void { - if (this.table) { - this.table.loadingIndicator = loading; - } - } - updateSelection(selection: CdTableSelection): void { this.selection = selection; - this.selectedGatewayDetails = this.buildGatewayDetails(selection.first()); } deleteGatewayGroupModal() { @@ -350,78 +340,8 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { this.router.navigate([this.urlBuilder.getEdit(selectedGroup.name)]); } - private buildGatewayDetails(selectedGroup: any): DetailItem[] { - if (!selectedGroup) { - return []; - } - - const runningGateways = selectedGroup.statusCount?.running ?? 0; - const errorGateways = selectedGroup.statusCount?.error ?? 0; - const totalGateways = runningGateways + errorGateways; - - return [ - { - label: $localize`Gateway name`, - value: selectedGroup.name - }, - { - label: $localize`Gateway nodes`, - value: totalGateways - }, - { - label: $localize`Encryption`, - value: selectedGroup.spec?.enable_auth ? $localize`Enabled` : $localize`Disabled`, - type: 'status' - }, - { - label: $localize`mTLS`, - value: selectedGroup.spec?.enable_mtls ? $localize`Enabled` : $localize`Disabled`, - type: 'status' - } - ]; - } - ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } - - editSelectedGatewayGroup(): void { - const selectedGroup = this.selection.first(); - if (!selectedGroup) { - return; - } - this.router.navigate([this.urlBuilder.getEdit(selectedGroup.name)]); - } - - private buildGatewayDetails(selectedGroup: any): DetailItem[] { - if (!selectedGroup) { - return []; - } - - const runningGateways = selectedGroup.statusCount?.running ?? 0; - const errorGateways = selectedGroup.statusCount?.error ?? 0; - const totalGateways = runningGateways + errorGateways; - - return [ - { - label: $localize`Gateway name`, - value: selectedGroup.name - }, - { - label: $localize`Gateway nodes`, - value: totalGateways - }, - { - label: $localize`Encryption`, - value: selectedGroup.spec?.enable_auth ? $localize`Enabled` : $localize`Disabled`, - type: 'status' - }, - { - label: $localize`mTLS`, - value: selectedGroup.spec?.enable_mtls ? $localize`Enabled` : $localize`Disabled`, - type: 'status' - } - ]; - } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.html index b8025d08f06..0a97671fa3b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.html @@ -1,7 +1,7 @@
@if (mode === 'details' && groupName) { - + } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts index fecbbe14a47..89fe4865356 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-gateway-node/nvmeof-gateway-node.component.ts @@ -439,8 +439,6 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy, OnChanges this.hostsLoaded.emit(this.count); } - onEditGateway(): void {} - private buildGatewayDetails( serviceSpec: CephServiceSpec, gatewayNodeCount: number diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.html index 94cf5fc5b58..70dfb6453fb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.html @@ -48,59 +48,28 @@ i18n>Special characters are not allowed.
-
-
- Enable encryption - -
-
- @if (groupForm.controls.enableEncryption.value) { -
-
- Encryption configuration - - - This field is required. -
+ +
+
+

Select target nodes

+ + + Gateway nodes to run NVMe-oF target pods/services + + +
+
+
- } - -
-
-

Select target nodes

- - - Gateway nodes to run NVMe-oF target pods/services - - -
-
- -
@@ -151,7 +120,8 @@ id="encryptionKey" formControlName="encryptionKey" rows="4" - cols="100"> + cols="100" + cdsTheme="g10"> { @@ -25,6 +26,7 @@ describe('NvmeofGroupFormComponent', () => { let formHelper: FormHelper; let taskWrapperService: TaskWrapperService; let cephServiceService: CephServiceService; + let nvmeofService: NvmeofService; let router: Router; beforeEach(async () => { @@ -51,8 +53,12 @@ describe('NvmeofGroupFormComponent', () => { component = fixture.componentInstance; taskWrapperService = TestBed.inject(TaskWrapperService); cephServiceService = TestBed.inject(CephServiceService); + nvmeofService = TestBed.inject(NvmeofService); router = TestBed.inject(Router); + // Mock NvmeofService.exists so the async unique validator resolves immediately + spyOn(nvmeofService, 'exists').and.returnValue(of(false)); + component.ngOnInit(); form = component.groupForm; formHelper = new FormHelper(form); @@ -126,7 +132,7 @@ describe('NvmeofGroupFormComponent', () => { expect(cephServiceService.create).toHaveBeenCalledWith({ service_type: 'nvmeof', - service_id: 'default', + service_id: 'nvmeof.default', group: 'default', placement: { hosts: ['host1', 'host2'] @@ -153,7 +159,7 @@ describe('NvmeofGroupFormComponent', () => { ); }); - it('should create service with encryption when enabled', () => { + it('should create service with encryption key when enabled', () => { component.gatewayNodeComponent = { getSelectedHosts: (): any[] => [{ hostname: 'host1' }], getSelectedHostnames: (): string[] => ['host1'] @@ -179,7 +185,6 @@ describe('NvmeofGroupFormComponent', () => { } as any; component.groupForm.get('groupName').setValue('mtls-internal'); - component.groupForm.get('pool').setValue('rbd'); component.groupForm.get('enableEncryption').setValue(true); component.groupForm.get('encryptionKey').setValue('test-encryption-key'); component.groupForm.get('enableMtls').setValue(true); @@ -191,7 +196,7 @@ describe('NvmeofGroupFormComponent', () => { expect(cephServiceService.create).toHaveBeenCalledWith( jasmine.objectContaining({ service_type: 'nvmeof', - service_id: 'rbd.mtls-internal', + service_id: 'nvmeof.mtls-internal', ssl: true, enable_auth: true, certificate_source: 'cephadm-signed', @@ -207,7 +212,6 @@ describe('NvmeofGroupFormComponent', () => { } as any; component.groupForm.get('groupName').setValue('mtls-external'); - component.groupForm.get('pool').setValue('rbd'); component.groupForm.get('enableEncryption').setValue(true); component.groupForm.get('encryptionKey').setValue('test-encryption-key'); component.groupForm.get('enableMtls').setValue(true); @@ -222,7 +226,7 @@ describe('NvmeofGroupFormComponent', () => { expect(cephServiceService.create).toHaveBeenCalledWith( jasmine.objectContaining({ - service_id: 'rbd.mtls-external', + service_id: 'nvmeof.mtls-external', ssl: true, enable_auth: true, certificate_source: 'inline', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.ts index 7053bbbe9f9..dd54c824f19 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { UntypedFormControl, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; import { CdForm } from '~/app/shared/forms/cd-form'; import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; @@ -10,10 +11,8 @@ import { NvmeofGatewayNodeComponent } from '../nvmeof-gateway-node/nvmeof-gatewa import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { CephServiceService } from '~/app/shared/api/ceph-service.service'; import { FinishedTask } from '~/app/shared/models/finished-task'; -import { Router } from '@angular/router'; import { CdValidators } from '~/app/shared/forms/cd-validators'; import { NvmeofService } from '~/app/shared/api/nvmeof.service'; -import { Pool } from '../../pool/pool'; import { CertificateType } from '~/app/shared/models/service.interface'; @Component({ @@ -32,9 +31,10 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { resource: string; group = ''; pageURL = ''; - pools: Pool[] = []; - poolsLoading = false; hasAvailableNodes = true; + editing = false; + gatewayGroupName = ''; + existingServiceData: any = null; constructor( private authStorageService: AuthStorageService, @@ -42,7 +42,8 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { private taskWrapperService: TaskWrapperService, private cephServiceService: CephServiceService, private nvmeofService: NvmeofService, - private router: Router + private router: Router, + private route: ActivatedRoute ) { super(); this.permission = this.authStorageService.getPermissions().nvmeof; @@ -50,30 +51,42 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { } ngOnInit() { - this.action = this.actionLabels.CREATE; this.createForm(); + + this.route.params.subscribe((params) => { + if (params['name']) { + this.editing = true; + this.gatewayGroupName = params['name']; + this.action = this.actionLabels.EDIT; + this.loadGatewayGroupData(params['name']); + } else { + this.editing = false; + this.action = this.actionLabels.CREATE; + } + }); } createForm() { + const groupNameValidators = [ + Validators.required, + (control: any) => { + const value = control.value; + return value && /[^a-zA-Z0-9_-]/.test(value) ? { invalidChars: true } : null; + } + ]; + + const groupNameAsyncValidators = this.editing + ? [] + : [CdValidators.unique(this.nvmeofService.exists, this.nvmeofService)]; + this.groupForm = new CdFormGroup({ - groupName: new UntypedFormControl( - null, - [ - Validators.required, - (control) => { - const value = control.value; - return value && /[^a-zA-Z0-9_-]/.test(value) ? { invalidChars: true } : null; - } - ], - [CdValidators.unique(this.nvmeofService.exists, this.nvmeofService)] - ), + groupName: new UntypedFormControl(null, groupNameValidators, groupNameAsyncValidators), unmanaged: new UntypedFormControl(false), enableEncryption: new UntypedFormControl(false), encryptionConfig: new UntypedFormControl(null), encryptionKey: new UntypedFormControl(null), enableMtls: new UntypedFormControl(false), certificateType: new UntypedFormControl(CertificateType.internal), - pool: new UntypedFormControl('rbd'), custom_sans: new UntypedFormControl([]), rootCACert: new UntypedFormControl(null), clientCert: new UntypedFormControl(null), @@ -83,7 +96,6 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { }); this.groupForm.get('enableEncryption')?.valueChanges.subscribe((enabled) => { - // Keep both legacy and new encryption fields in sync. if (!enabled) { return; } @@ -96,6 +108,50 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { encryptionConfigControl.setValue(encryptionKeyControl.value, { emitEvent: false }); } }); + + if (this.editing) { + this.groupForm.get('groupName')?.disable(); + } + } + + loadGatewayGroupData(groupName: string) { + this.nvmeofService.listGatewayGroups().subscribe( + (gatewayGroups: any) => { + const groups = gatewayGroups?.[0] ?? []; + const group = groups.find((g: any) => g.spec?.group === groupName); + + if (group) { + this.existingServiceData = group; + const spec = group.spec || {}; + + this.groupForm.patchValue({ + groupName: groupName, + unmanaged: spec.unmanaged || false, + enableEncryption: spec.ssl || false, + enableMtls: spec.ssl || false, + certificateType: + spec.certificate_source === 'inline' + ? CertificateType.external + : CertificateType.internal, + encryptionKey: spec.encryption_key || '', + custom_sans: spec.custom_sans || [] + }); + + if (spec.certificate_source === 'inline') { + this.groupForm.patchValue({ + rootCACert: spec.root_ca_cert || null, + clientCert: spec.client_cert || null, + clientKey: spec.client_key || null, + serverCert: spec.server_cert || null, + serverKey: spec.server_key || null + }); + } + } + }, + (_error) => { + // Error loading gateway group data + } + ); } onHostsLoaded(count: number): void { @@ -139,19 +195,21 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { return; } - const formValues = this.groupForm.value; + const formValues = this.groupForm.getRawValue(); const selectedHostnames = this.gatewayNodeComponent?.getSelectedHostnames() || []; if (selectedHostnames.length === 0) { this.groupForm.setErrors({ cdSubmitButton: true }); return; } - const taskUrl = `service/${URLVerbs.CREATE}`; - const serviceId = `${formValues.groupName}`; + + const groupName = this.editing ? this.gatewayGroupName : formValues.groupName; + const serviceName = `nvmeof.${groupName}`; + const taskUrl = this.editing ? `service/${URLVerbs.EDIT}` : `service/${URLVerbs.CREATE}`; const serviceSpec: Record = { service_type: 'nvmeof', - service_id: serviceId, - group: formValues.groupName, + service_id: serviceName, + group: groupName, placement: { hosts: selectedHostnames }, @@ -171,11 +229,6 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { serviceSpec['certificate_source'] = formValues.certificateType === CertificateType.internal ? 'cephadm-signed' : 'inline'; - if (formValues.pool) { - serviceSpec['pool'] = formValues.pool; - serviceSpec['service_id'] = `${formValues.pool}.${formValues.groupName}`; - } - if ( formValues.certificateType === CertificateType.internal && formValues.custom_sans?.length > 0 @@ -192,12 +245,16 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { } } + const apiCall = this.editing + ? this.cephServiceService.update(serviceSpec) + : this.cephServiceService.create(serviceSpec); + this.taskWrapperService .wrapTaskAroundCall({ task: new FinishedTask(taskUrl, { - service_name: `nvmeof.${serviceId}` + service_name: serviceName }), - call: this.cephServiceService.create(serviceSpec) + call: apiCall }) .subscribe({ complete: () => { @@ -213,10 +270,6 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { this.router.navigateByUrl('/block/nvmeof/gateways'); } - onCertificateTypeChange(type: CertificateType): void { - this.groupForm.get('certificateType')?.setValue(type); - } - onFileUpload(event: Event, controlName: string): void { const target = event.target as HTMLInputElement; const file = target?.files?.[0]; @@ -229,4 +282,21 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { reader.onload = () => control.setValue(reader.result); reader.readAsText(file, 'utf8'); } + + onCertificateTypeChange(type: CertificateType): void { + this.groupForm.get('certificateType')?.setValue(type); + if (type === CertificateType.internal) { + this.groupForm.patchValue({ + rootCACert: null, + clientCert: null, + clientKey: null, + serverCert: null, + serverKey: null + }); + } else { + this.groupForm.patchValue({ + custom_sans: [] + }); + } + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts index e49ba58057f..48021924dd8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.spec.ts @@ -194,4 +194,19 @@ describe('NvmeofNamespacesListComponent', () => { component.fetchData(); }); + + it('should update group and trigger namespace fetch on group change', () => { + const fetchDataSpy = jest.spyOn(component, 'fetchData'); + + component.onGroupChange('g1'); + + expect(component.group).toBe('g1'); + expect(fetchDataSpy).not.toHaveBeenCalled(); // onGroupChange calls namespaceSubject.next() directly + }); + + it('should clear group on onGroupChange with null', () => { + component.group = 'g1'; + component.onGroupChange(null); + expect(component.group).toBeNull(); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.ts index 71b05f17cde..427dd606894 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-namespaces-list/nvmeof-namespaces-list.component.ts @@ -223,6 +223,18 @@ export class NvmeofNamespacesListComponent implements OnInit, OnDestroy { }); } + get group(): string | null { + return this.groupHandler.group; + } + + set group(value: string | null) { + this.groupHandler.group = value; + } + + onGroupChange(group: string | null): void { + this.groupHandler.onGroupChange(group); + } + updateSelection(selection: CdTableSelection) { this.selection = selection; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts index 26b6a0b2721..1e213685305 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts @@ -1153,7 +1153,7 @@ export class ServiceFormComponent extends CdForm implements OnInit { } else { this.showRealmCreationForm = false; } - this.updateRgwControlStates(); + this.updateRgwControlStates(); }, (_error) => { const defaultZone = new RgwZone(); @@ -1162,7 +1162,7 @@ export class ServiceFormComponent extends CdForm implements OnInit { defaultZonegroup.name = 'default'; this.zoneList.push(defaultZone); this.zonegroupList.push(defaultZonegroup); - this.updateRgwControlStates(); + this.updateRgwControlStates(); } ); } @@ -1226,7 +1226,9 @@ export class ServiceFormComponent extends CdForm implements OnInit { this.toggleFormControlState('zone_name', this.editing || this.zoneList.length === 0); } - private updateGrafanaPasswordControlState(serviceType = this.serviceForm?.get('service_type')?.value): void { + private updateGrafanaPasswordControlState( + serviceType = this.serviceForm?.get('service_type')?.value + ): void { this.toggleFormControlState( 'grafana_admin_password', this.editing && serviceType === 'grafana' diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.html index 3d558fb8c56..b0d356e099b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.html @@ -2,47 +2,34 @@ @if (cardTitle) {

{{ cardTitle }}

- @if (showEditButton) { - - } }
-
+
@if (details && details.length > 0) { -
-
- @for (detail of getVisibleDetails(); track detail.label) { -
-
- -
- @switch (detail.type) { - @case ('status') { -
- - {{ getDisplayValue(detail.value) }} -
- } - - @default { - {{ getDisplayValue(detail.value) }} - } - } -
-
+
+ @for (detail of getVisibleDetails(); track detail.label) { +
+ +
+ @switch (detail.type) { + @case ('status') { +
+ + {{ getDisplayValue(detail.value) }} +
+ } + + @default { + {{ getDisplayValue(detail.value) }} + } + }
- }
+ }
}