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-Tag: testing/wip-vshankar-testing-20260708.090602~39^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=507765d71e12539e428ffb04e8a82aca6a5e38d4;p=ceph-ci.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 (cherry picked from commit 26b7e9e4849f8f0f1d044cb23cc94a5ce910061d) --- 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 368a7de95b7..7dbdfbbd518 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'; @@ -147,7 +148,9 @@ import { NvmeofGatewayGroupFilterComponent } from './nvmeof-gateway-group-filter LayoutModule, ThemeModule, NvmeofSetupCardsComponent, - NvmeofGatewayGroupFilterComponent + NvmeofGatewayGroupFilterComponent, + TextLabelListComponent + // ProductiveCardComponent ], declarations: [ RbdListComponent, @@ -378,6 +381,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 5084e98a14b..435410a6aca 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,14 +1,12 @@ - + + [showEditButton]="false" + [columns]="4"> @@ -42,10 +40,11 @@ + let-value="data.value" + let-row="data.row"> + href="#" + (click)="onNameClick(row, $event)"> {{ 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 b89d8bc9a51..9b00fff7533 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 @@ -76,6 +76,7 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { gatewayCount = 0; selectedGatewayDetails: DetailItem[] = []; private lastGroupCount = 0; + showDetailsCard = false; viewUrl = `/${BASE_URL}/view`; icons = Icons; @@ -129,14 +130,6 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { canBePrimary: (selection: CdTableSelection) => !selection.hasSelection }; - const editAction: CdTableAction = { - permission: 'update', - icon: Icons.edit, - routerLink: () => this.urlBuilder.getEdit(this.selection.first()?.name), - name: this.actionLabels.EDIT, - canBePrimary: (selection: CdTableSelection) => selection.hasSingleSelection - }; - const viewAction: CdTableAction = { permission: 'read', icon: Icons.eye, @@ -153,7 +146,7 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection }; - this.tableActions = [createAction, editAction, viewAction, deleteAction]; + this.tableActions = [createAction, viewAction, deleteAction]; this.gatewayGroup$ = this.subject.pipe( switchMap(() => @@ -217,6 +210,7 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { updateSelection(selection: CdTableSelection): void { this.selection = selection; this.selectedGatewayDetails = this.buildGatewayDetails(selection.first()); + this.showDetailsCard = false; } deleteGatewayGroupModal() { @@ -275,6 +269,11 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { }) .pipe( tap({ + next: () => { + this.table.data = this.table.data.filter( + (row: CephServiceSpec) => row.spec?.group !== selectedGroup.spec.group + ); + }, complete: () => { this.nvmeofStateService.requestRefresh(); } @@ -292,6 +291,14 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { }); } + onNameClick(row: any, event?: Event): void { + event?.preventDefault(); + event?.stopPropagation(); + this.selection = new CdTableSelection([row]); + this.selectedGatewayDetails = this.buildGatewayDetails(row); + this.showDetailsCard = true; + } + private checkNodesAvailability(): void { forkJoin([this.nvmeofService.listGatewayGroups(), this.hostService.getAllHosts()]).subscribe( ([groups, hosts]: [GatewayGroup[][], any[]]) => { @@ -335,14 +342,6 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { this.router.navigate([this.viewUrl, groupName]); } - 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 []; @@ -368,7 +367,7 @@ export class NvmeofGatewayGroupComponent implements OnInit, OnDestroy { }, { label: $localize`mTLS`, - value: selectedGroup.spec?.enable_mtls ? $localize`Enabled` : $localize`Disabled`, + value: selectedGroup.spec?.enable_auth ? $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 7e420bfe86e..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,8 +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 b0595304e74..98e7190faf2 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 @@ -380,9 +380,9 @@ export class NvmeofGatewayNodeComponent implements OnInit, OnDestroy { }, { label: $localize`mTLS`, - value: $localize`Disabled`, + value: serviceSpec.spec?.enable_auth ? $localize`Enabled` : $localize`Disabled`, type: 'status', - statusIcon: 'error' + statusIcon: serviceSpec.spec?.enable_auth ? 'success' : 'error' } ]; } 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 bdac48c78b2..8cd803d79aa 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 @@ -50,49 +50,40 @@
- - +
- Enable encryption - -
-
- - - @if (groupForm.controls.enableEncryption.value) { -
-
- Encryption configuration - - - This field is required. + > + + + + + + This field is required. +
- } - -
+
@@ -113,14 +104,387 @@ >
+
+
+ + +
+
+
+ + Recommended +
+ + Secures group metadata and unlocks advanced authentication features. + +
+
+ + @if (!groupForm.controls.enableEncryption.value) { +
+
+ + Encryption is required if you plan to use DH-CHAP or mTLS authentication. + +
+
+ } + + @if (groupForm.controls.enableEncryption.value) { +
+
+ + Encryption key + + + This field is required. +
+
+ } +
+ + +
+
+
+ +
+ + Use mutual TLS (mTLS) to encrypt control and monitoring commands exchanged with NVMe-oF gateways. + +
+
+ + @if (!groupForm.controls.enableMtls.value) { +
+
+ + Disabling mTLS leaves gateway control and monitoring communication unencrypted. + +
+
+ } + + @if (groupForm.controls.enableMtls.value) { +
+
+ + + + Internal + + + External + + +
+
+ } +
+
+
+ + @if (groupForm.controls.enableMtls.value && groupForm.controls.certificateType.value === CertificateType.internal) { +
+
+ + Certificate will be generated automatically by Cephadm CA for internal certificate type. + + + +
+
+ } + + @if (groupForm.controls.enableMtls.value && groupForm.controls.certificateType.value === CertificateType.external) { + +
+
+ + Root CA Certificate Input + +
+ + +
+ + + + This field is required. +
+
+ + +
+
+ + Client Certificate Input + +
+ + +
+ + + + This field is required. +
+
+ + +
+
+ + Client Key Input + +
+ + +
+ + + + This field is required. +
+
+ + +
+
+ + Server Certificate Input + +
+ + +
+ + + + This field is required. +
+
+ + +
+
+ + Server Key Input + +
+ + +
+ + + + This field is required. +
+
+ } -
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.spec.ts index 5b60872cd1b..640d7a7d0bc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/nvmeof-group-form/nvmeof-group-form.component.spec.ts @@ -65,6 +65,7 @@ describe('NvmeofGroupFormComponent', () => { expect(form.controls.groupName.value).toBeNull(); expect(form.controls.unmanaged.value).toBe(false); expect(form.controls.enableEncryption.value).toBe(false); + expect(form.controls.certificateType.value).toBe('internal'); }); it('should set action to CREATE on init', () => { @@ -169,6 +170,69 @@ describe('NvmeofGroupFormComponent', () => { ); }); + it('should create service with cephadm-signed mTLS when internal selected', () => { + component.gatewayNodeComponent = { + getSelectedHosts: (): any[] => [{ hostname: 'host1' }], + getSelectedHostnames: (): string[] => ['host1'] + } 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); + component.groupForm.get('certificateType').setValue(component.CertificateType.internal); + component.groupForm.get('custom_sans').setValue(['gw1.local', '192.168.0.10']); + + component.onSubmit(); + + expect(cephServiceService.create).toHaveBeenCalledWith( + jasmine.objectContaining({ + service_type: 'nvmeof', + service_id: 'rbd.mtls-internal', + ssl: true, + enable_auth: true, + certificate_source: 'cephadm-signed', + custom_sans: ['gw1.local', '192.168.0.10'] + }) + ); + }); + + it('should create service with inline mTLS when external selected', () => { + component.gatewayNodeComponent = { + getSelectedHosts: (): any[] => [{ hostname: 'host1' }], + getSelectedHostnames: (): string[] => ['host1'] + } 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); + component.groupForm.get('certificateType').setValue(component.CertificateType.external); + component.groupForm.get('rootCACert').setValue('root'); + component.groupForm.get('clientCert').setValue('client-cert'); + component.groupForm.get('clientKey').setValue('client-key'); + component.groupForm.get('serverCert').setValue('server-cert'); + component.groupForm.get('serverKey').setValue('server-key'); + + component.onSubmit(); + + expect(cephServiceService.create).toHaveBeenCalledWith( + jasmine.objectContaining({ + service_id: 'rbd.mtls-external', + ssl: true, + enable_auth: true, + certificate_source: 'inline', + root_ca_cert: 'root', + client_cert: 'client-cert', + client_key: 'client-key', + server_cert: 'server-cert', + server_key: 'server-key' + }) + ); + }); + it('should navigate to list view on success', () => { component.gatewayNodeComponent = { getSelectedHosts: (): any[] => [{ hostname: 'host1' }], 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 f55ac8f7357..f27cbec917d 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,14 @@ 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'; +enum CertificateType { + internal = 'internal', + external = 'external' +} + @Component({ selector: 'cd-nvmeof-group-form', templateUrl: './nvmeof-group-form.component.html', @@ -30,6 +35,12 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { group: string; pageURL: string; hasAvailableNodes = true; + CertificateType = CertificateType; + editing = false; + gatewayGroupName = ''; + existingServiceData: any = null; + pools: Array<{ pool_name: string }> = [{ pool_name: 'rbd' }]; + poolsLoading = false; constructor( private authStorageService: AuthStorageService, @@ -37,7 +48,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; @@ -45,37 +57,155 @@ 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.createForm(); + this.loadGatewayGroupData(params['name']); + } else { + this.editing = false; + this.action = this.actionLabels.CREATE; + this.gatewayGroupName = ''; + this.createForm(); + } + }); } createForm() { - this.groupForm = new CdFormGroup({ - groupName: new UntypedFormControl( - null, - [ + const groupNameValidators = this.editing + ? [ + Validators.required, + (control) => { + const value = control.value; + return value && /[^a-zA-Z0-9_-]/.test(value) ? { invalidChars: true } : 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)] - ), + ]; + + const groupNameAsyncValidators = this.editing + ? [] + : [CdValidators.unique(this.nvmeofService.exists, this.nvmeofService)]; + + this.groupForm = new CdFormGroup({ + groupName: new UntypedFormControl(null, groupNameValidators, groupNameAsyncValidators), + pool: new UntypedFormControl('rbd', { + validators: [Validators.required] + }), unmanaged: new UntypedFormControl(false), enableEncryption: new UntypedFormControl(false), - encryptionConfig: new UntypedFormControl(null) + enableMtls: new UntypedFormControl(false), + encryptionKey: new UntypedFormControl('', [ + CdValidators.composeIf( + { + enableEncryption: true + }, + [Validators.required] + ) + ]), + certificateType: new UntypedFormControl(CertificateType.internal), + custom_sans: new UntypedFormControl([]), + rootCACert: new UntypedFormControl(null, [ + CdValidators.composeIf( + { + enableEncryption: true, + certificateType: 'external' + }, + [Validators.required] + ) + ]), + clientCert: new UntypedFormControl(null, [ + CdValidators.composeIf( + { + enableEncryption: true, + certificateType: 'external' + }, + [Validators.required] + ) + ]), + clientKey: new UntypedFormControl(null, [ + CdValidators.composeIf( + { + enableEncryption: true, + certificateType: 'external' + }, + [Validators.required] + ) + ]), + serverCert: new UntypedFormControl(null, [ + CdValidators.composeIf( + { + enableEncryption: true, + certificateType: 'external' + }, + [Validators.required] + ) + ]), + serverKey: new UntypedFormControl(null, [ + CdValidators.composeIf( + { + enableEncryption: true, + certificateType: CertificateType.external + }, + [Validators.required] + ) + ]) }); - this.groupForm.get('enableEncryption')?.valueChanges.subscribe((enabled) => { - const encryptionControl = this.groupForm.get('encryptionConfig'); - if (enabled) { - encryptionControl?.setValidators([Validators.required]); - } else { - encryptionControl?.clearValidators(); + // Disable group name field in edit mode + 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 || {}; + + // Prefill form with existing data + this.groupForm.patchValue({ + groupName: groupName, + pool: spec.pool || 'rbd', + 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 external certificates, load them + 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 } - encryptionControl?.updateValueAndValidity(); - }); + ); } onHostsLoaded(count: number): void { @@ -119,35 +249,59 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { return; } - const formValues = this.groupForm.value; + const formValues = this.groupForm.getRawValue(); // Use getRawValue to get disabled fields too const selectedHostnames = this.gatewayNodeComponent?.getSelectedHostnames() || []; if (selectedHostnames.length === 0) { this.groupForm.setErrors({ cdSubmitButton: true }); return; } - let taskUrl = `service/${URLVerbs.CREATE}`; - const serviceName = `${formValues.groupName}`; + + const groupName = this.editing ? this.gatewayGroupName : formValues.groupName; + const pool = formValues.pool; + const serviceName = `${pool}.${groupName}`; + + let taskUrl = this.editing ? `service/${URLVerbs.EDIT}` : `service/${URLVerbs.CREATE}`; const serviceSpec: Record = { service_type: 'nvmeof', service_id: serviceName, - group: formValues.groupName, + pool: pool, + group: groupName, placement: { hosts: selectedHostnames }, unmanaged: formValues.unmanaged }; - if (formValues.enableEncryption && formValues.encryptionConfig) { - serviceSpec['encryption_key'] = formValues.encryptionConfig; + if (formValues.enableMtls) { + serviceSpec['ssl'] = true; + serviceSpec['enable_auth'] = true; + serviceSpec['certificate_source'] = + formValues.certificateType === CertificateType.internal ? 'cephadm-signed' : 'inline'; + + if (formValues.certificateType === CertificateType.internal) { + if (formValues.custom_sans && formValues.custom_sans.length > 0) { + serviceSpec['custom_sans'] = formValues.custom_sans; + } + } else { + serviceSpec['root_ca_cert'] = formValues.rootCACert; + serviceSpec['client_cert'] = formValues.clientCert; + serviceSpec['client_key'] = formValues.clientKey; + serviceSpec['server_cert'] = formValues.serverCert; + serviceSpec['server_key'] = formValues.serverKey; + } } + const apiCall = this.editing + ? this.cephServiceService.update(serviceSpec) + : this.cephServiceService.create(serviceSpec); + this.taskWrapperService .wrapTaskAroundCall({ task: new FinishedTask(taskUrl, { service_name: `nvmeof.${serviceName}` }), - call: this.cephServiceService.create(serviceSpec) + call: apiCall }) .subscribe({ complete: () => { @@ -162,4 +316,32 @@ export class NvmeofGroupFormComponent extends CdForm implements OnInit { private goToListView() { this.router.navigateByUrl('/block/nvmeof/gateways'); } + + onFileUpload(event: any, controlName: string) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e: any) => { + this.groupForm.get(controlName)?.setValue(e.target.result); + }; + reader.readAsText(file); + } + } + + onCertificateTypeChange(type: CertificateType) { + 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/shared/components/components.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts index 294dd0ad5f2..91a5cbced6a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts @@ -138,7 +138,6 @@ import { TearsheetStepComponent } from './tearsheet-step/tearsheet-step.componen import { PageHeaderComponent } from './page-header/page-header.component'; import { SidebarLayoutComponent } from './sidebar-layout/sidebar-layout.component'; import { NumberWithUnitComponent } from './number-with-unit/number-with-unit.component'; -import { ProductiveCardComponent } from './productive-card/productive-card.component'; @NgModule({ imports: [ @@ -187,8 +186,7 @@ import { ProductiveCardComponent } from './productive-card/productive-card.compo TagModule, LinkModule, LayerModule, - ThemeModule, - ProductiveCardComponent + ThemeModule ], declarations: [ SparklineComponent, 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 d60b1f3b345..b1595fbcdf2 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 @@ -17,11 +17,14 @@ } -
+
@if (details && details.length > 0) { -
+
@for (detail of getVisibleDetails(); track detail.label) { -
+
@switch (detail.type) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.ts index 7cfed7235e0..9b2fad7afb8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/details-card/details-card.component.ts @@ -43,9 +43,6 @@ export class DetailsCardComponent { onEditClick(): void { this.editClicked.emit(); } - - @Input() - columns = 4; getVisibleDetails(): DetailItem[] { return (this.details || []).filter((detail) => !detail.hidden);