]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard : Enable cephadm-signed TLS for NVMeOf 69475/head
authorAbhishek Desai <abhishek.desai1@ibm.com>
Mon, 15 Jun 2026 11:12:15 +0000 (16:42 +0530)
committerAbhishek Desai <abhishek.desai1@ibm.com>
Mon, 13 Jul 2026 13:04:26 +0000 (18:34 +0530)
fixes: https://tracker.ceph.com/issues/77406
Signed-off-by: Abhishek Desai <abhishek.desai1@ibm.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.ts [new file with mode: 0644]

index 6be23e612fa4f18155087cd7954054548175f7e0..ab67956ff4e3259d743f84fb1d0caf1b87917ca2 100644 (file)
@@ -104,6 +104,7 @@ import { MultiClusterFormComponent } from './multi-cluster/multi-cluster-form/mu
 import { MultiClusterListComponent } from './multi-cluster/multi-cluster-list/multi-cluster-list.component';
 import { DashboardV3Module } from '../dashboard-v3/dashboard-v3.module';
 import { MultiClusterDetailsComponent } from './multi-cluster/multi-cluster-details/multi-cluster-details.component';
+import { CertificateAuthorityFormComponent } from '~/app/shared/components/certificate-authority-form/certificate-authority-form.component';
 import { TextLabelListComponent } from '~/app/shared/components/text-label-list/text-label-list.component';
 
 @NgModule({
@@ -143,6 +144,7 @@ import { TextLabelListComponent } from '~/app/shared/components/text-label-list/
     TagModule,
     TabsModule,
     TextLabelListComponent,
+    CertificateAuthorityFormComponent,
     SelectModule,
     LayoutModule,
     NumberModule,
index 1122ccb134d52dd58269a2f79ff7eb92b6443086..58613e00cfc4a92a710e7da87b961d3228d2028f 100644 (file)
             serviceForm.controls.service_type.value
           )
         ) {
-          <ng-container *ngTemplateOutlet="certificateAuthorityManagement"></ng-container>
+          <cd-certificate-authority-form
+            [formGroup]="serviceForm"
+            [editing]="editing"
+            [currentCertificate]="currentCertificate"
+            [showCertSourceChangeWarning]="showCertSourceChangeWarning"
+            (certificateTypeChange)="onCertificateTypeChange($event)"
+          >
+          </cd-certificate-authority-form>
         }
 
         <!-- ssl_cert - Only show when SSL is enabled AND certificate type is external -->
           </fieldset>
         </div>
         @if (serviceForm.controls.enable_mtls.value) {
-          <ng-container *ngTemplateOutlet="certificateAuthorityManagement"></ng-container>
+          <cd-certificate-authority-form
+            [formGroup]="serviceForm"
+            [editing]="editing"
+            [currentCertificate]="currentCertificate"
+            [showCertSourceChangeWarning]="showCertSourceChangeWarning"
+            (certificateTypeChange)="onCertificateTypeChange($event)"
+          >
+          </cd-certificate-authority-form>
 
           @if (serviceForm.controls.certificateType.value === CertificateType.external) {
             <!-- root_ca_cert -->
   </span>
 </ng-template>
 
-<ng-template #certificateAuthorityManagement>
-  <div [formGroup]="serviceForm">
-    <!-- Current Certificate Section - Only shown in Edit mode when certificate exists -->
-    @if (editing && currentCertificate?.has_certificate) {
-      <div class="form-item">
-        <legend
-          class="cds--label cds--type-heading-compact-01"
-          i18n
-        >
-          Current Certificate
-        </legend>
-        <div class="row">
-          <div class="col-6">
-            <legend
-              class="cds--label"
-              i18n
-            >
-              Certificate
-            </legend>
-            <div>{{ currentCertificate.cert_name }}</div>
-          </div>
-          <div class="col-6">
-            <legend
-              class="cds--label"
-              i18n
-            >
-              Valid Until
-            </legend>
-            <div>
-              {{ currentCertificate.expiry_date | cdDate }} •
-              {{ currentCertificate.days_to_expiration }} <span i18n>days left</span>
-            </div>
-          </div>
-        </div>
-        <div class="row mt-3">
-          <div class="col-6">
-            <legend
-              class="cds--label"
-              i18n
-            >
-              Status
-            </legend>
-            <div class="align-items-center">
-              <cd-icon
-                [type]="statusIconMap[currentCertificate.status] || statusIconMap['default']"
-              ></cd-icon>
-              @switch (currentCertificate.status) {
-                @case ('valid') {
-                  <span i18n>Valid</span>
-                }
-                @case ('expiring') {
-                  <span i18n>Expiring soon</span>
-                }
-                @case ('expired') {
-                  <span i18n>Expired</span>
-                }
-                @case ('not_configured') {
-                  <span i18n>Not configured</span>
-                }
-                @case ('invalid') {
-                  <span i18n>Invalid</span>
-                }
-                @default {
-                  {{ currentCertificate.status }}
-                }
-              }
-            </div>
-          </div>
-          <div class="col-6">
-            <legend
-              class="cds--label"
-              i18n
-            >
-              Issuer
-            </legend>
-            <div>
-              @if (currentCertificate.signed_by === 'cephadm') {
-                <span i18n>Internal (Cephadm CA)</span>
-              } @else {
-                {{ currentCertificate.issuer || 'External' }}
-              }
-            </div>
-          </div>
-        </div>
-      </div>
-    }
-
-    <!-- Certificate Authority Selection -->
-    <div class="form-item">
-      <label
-        class="cds--label cds--type-heading-compact-01"
-        for="certificateType"
-        i18n
-        >Choose Certificate Authority</label
-      >
-      <cds-radio-group
-        formControlName="certificateType"
-        orientation="horizontal"
-        helperText="Select how certificates will be signed for this service. Choose internal to use the cluster's CA, or external to upload certificates signed by your organization."
-        i18n-helperText
-      >
-        <cds-radio
-          [value]="CertificateType.internal"
-          (change)="onCertificateTypeChange(CertificateType.internal)"
-          i18n
-        >
-          Internal
-        </cds-radio>
-        <cds-radio
-          [value]="CertificateType.external"
-          (change)="onCertificateTypeChange(CertificateType.external)"
-          i18n
-        >
-          External
-        </cds-radio>
-      </cds-radio-group>
-    </div>
-
-    @if (showCertSourceChangeWarning) {
-      <cd-alert-panel
-        type="warning"
-        spacingClass="mb-3"
-        i18n
-      >
-        Changing the certificate source will redeploy the service daemons to apply the new
-        certificate configuration.
-      </cd-alert-panel>
-    }
-
-    @if (serviceForm.controls.certificateType.value === CertificateType.internal) {
-      <cd-alert-panel
-        type="info"
-        spacingClass="mb-3"
-        i18n
-      >
-        Certificate will be generated automatically by Cephadm CA for internal certificate type.
-      </cd-alert-panel>
-      <div class="form-item">
-        <cd-text-label-list
-          formControlName="custom_sans"
-          label="Custom SAN Entries"
-          i18n-label
-          helperText="Optional list of Subject Alternative Names (hostnames, IPs, or DNS names) to include in the auto-generated certificate."
-          i18n-helperText
-        >
-        </cd-text-label-list>
-      </div>
-      @if (
-        serviceForm.controls.service_type.value === 'rgw' &&
-        serviceForm.controls.virtual_host_enabled.value
-      ) {
-        <div class="form-item">
-          <cds-checkbox
-            i18n-label
-            formControlName="wildcard_enabled"
-          >
-            Include wildcard certificate for bucket subdomains
-            <cd-help-text i18n>
-              Add wildcard certificates (*.domain) to allow SSL for all bucket subdomains. Required
-              for virtual-host style with SSL.
-            </cd-help-text>
-          </cds-checkbox>
-        </div>
-      }
-    }
-  </div>
-</ng-template>
-
 <ng-template
   #fileUploaderTextarea
   let-controlName="controlName"
index 7cdd9abb7b2d3a23b2854dc54d7df177b78e25a1..3371cc199e07a013f7acb87ce4550a3c0b1718c6 100644 (file)
@@ -40,8 +40,7 @@ import {
   CephServiceSpec,
   CertificateType,
   QatOptions,
-  QatSepcs,
-  CERTIFICATE_STATUS_ICON_MAP
+  QatSepcs
 } from '~/app/shared/models/service.interface';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
@@ -129,7 +128,6 @@ export class ServiceFormComponent extends CdForm implements OnInit {
   hostsAndLabels$: Observable<{ hosts: { content: string }[]; labels: { content: string }[] }>;
   currentCertificate: CephServiceCertificate = null;
   currentSpecCertificateSource: string = null;
-  statusIconMap = CERTIFICATE_STATUS_ICON_MAP;
 
   constructor(
     public actionLabels: ActionLabelsI18n,
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.html
new file mode 100644 (file)
index 0000000..8157874
--- /dev/null
@@ -0,0 +1,214 @@
+<div [formGroup]="formGroup">
+  @if (editing && currentCertificate?.has_certificate) {
+    <div class="form-item">
+      <span
+        class="cds--label cds--type-heading-compact-01"
+        i18n
+        >Current Certificate</span
+      >
+      <div
+        cdsRow
+        [condensed]="true"
+      >
+        <div
+          cdsCol
+          [columnNumbers]="[
+            ['sm', 4],
+            ['md', 4]
+          ]"
+        >
+          <div
+            cdsStack="vertical"
+            [gap]="1"
+          >
+            <span
+              class="cds--type-label-01"
+              i18n
+              >Certificate</span
+            >
+            <span class="cds--type-body-compact-01">{{ currentCertificate.cert_name }}</span>
+          </div>
+        </div>
+        <div
+          cdsCol
+          [columnNumbers]="[
+            ['sm', 4],
+            ['md', 4]
+          ]"
+        >
+          <div
+            cdsStack="vertical"
+            [gap]="1"
+          >
+            <span
+              class="cds--type-label-01"
+              i18n
+              >Valid Until</span
+            >
+            <span class="cds--type-body-compact-01">
+              {{ currentCertificate.expiry_date | cdDate }} •
+              {{ currentCertificate.days_to_expiration }}
+              <span i18n>days left</span>
+            </span>
+          </div>
+        </div>
+      </div>
+      <div
+        cdsRow
+        [condensed]="true"
+        class="cds-mt-3"
+      >
+        <div
+          cdsCol
+          [columnNumbers]="[
+            ['sm', 4],
+            ['md', 4]
+          ]"
+        >
+          <div
+            cdsStack="vertical"
+            [gap]="1"
+          >
+            <span
+              class="cds--type-label-01"
+              i18n
+              >Status</span
+            >
+            <div
+              cdsStack="horizontal"
+              [gap]="2"
+            >
+              <cd-icon
+                [type]="statusIconMap[currentCertificate.status] || statusIconMap['default']"
+              ></cd-icon>
+              @switch (currentCertificate.status) {
+                @case ('valid') {
+                  <span i18n>Valid</span>
+                }
+                @case ('expiring') {
+                  <span i18n>Expiring soon</span>
+                }
+                @case ('expired') {
+                  <span i18n>Expired</span>
+                }
+                @case ('not_configured') {
+                  <span i18n>Not configured</span>
+                }
+                @case ('invalid') {
+                  <span i18n>Invalid</span>
+                }
+                @default {
+                  {{ currentCertificate.status }}
+                }
+              }
+            </div>
+          </div>
+        </div>
+        <div
+          cdsCol
+          [columnNumbers]="[
+            ['sm', 4],
+            ['md', 4]
+          ]"
+        >
+          <div
+            cdsStack="vertical"
+            [gap]="1"
+          >
+            <span
+              class="cds--type-label-01"
+              i18n
+              >Issuer</span
+            >
+            <span class="cds--type-body-compact-01">
+              @if (currentCertificate.signed_by === 'cephadm') {
+                <span i18n>Internal (Cephadm CA)</span>
+              } @else {
+                {{ currentCertificate.issuer || 'External' }}
+              }
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+  }
+
+  <div class="form-item">
+    <label
+      class="cds--label cds--type-heading-compact-01"
+      for="certificateType"
+      i18n
+      >Choose Certificate Authority</label
+    >
+    <cds-radio-group
+      id="certificateType"
+      formControlName="certificateType"
+      orientation="horizontal"
+      helperText="Select how certificates will be signed for this service. Choose internal to use the cluster's CA, or external to upload certificates signed by your organization."
+      i18n-helperText
+    >
+      <cds-radio
+        [value]="CertificateType.internal"
+        (change)="onCertificateTypeChange(CertificateType.internal)"
+        i18n
+      >
+        Internal
+      </cds-radio>
+      <cds-radio
+        [value]="CertificateType.external"
+        (change)="onCertificateTypeChange(CertificateType.external)"
+        i18n
+      >
+        External
+      </cds-radio>
+    </cds-radio-group>
+  </div>
+
+  @if (showCertSourceChangeWarning) {
+    <cd-alert-panel
+      type="warning"
+      spacingClass="mb-3"
+      i18n
+    >
+      Changing the certificate source will redeploy the service daemons to apply the new certificate
+      configuration.
+    </cd-alert-panel>
+  }
+
+  @if (formGroup.controls.certificateType.value === CertificateType.internal) {
+    <cd-alert-panel
+      type="info"
+      spacingClass="mb-3"
+      i18n
+    >
+      Certificate will be generated automatically by Cephadm CA for internal certificate type.
+    </cd-alert-panel>
+    <div class="form-item">
+      <cd-text-label-list
+        formControlName="custom_sans"
+        label="Custom SAN Entries"
+        i18n-label
+        helperText="Optional list of Subject Alternative Names (hostnames, IPs, or DNS names) to include in the auto-generated certificate."
+        i18n-helperText
+      >
+      </cd-text-label-list>
+    </div>
+    @if (
+      formGroup.controls.service_type?.value === 'rgw' &&
+      formGroup.controls.virtual_host_enabled?.value
+    ) {
+      <div class="form-item">
+        <cds-checkbox
+          i18n-label
+          formControlName="wildcard_enabled"
+        >
+          Include wildcard certificate for bucket subdomains
+          <cd-help-text i18n>
+            Add wildcard certificates (*.domain) to allow SSL for all bucket subdomains. Required
+            for virtual-host style with SSL.
+          </cd-help-text>
+        </cds-checkbox>
+      </div>
+    }
+  }
+</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.spec.ts
new file mode 100644 (file)
index 0000000..8c25515
--- /dev/null
@@ -0,0 +1,41 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { expect as jestExpect } from '@jest/globals';
+
+import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
+import { CertificateType } from '~/app/shared/models/service.interface';
+import { CertificateAuthorityFormComponent } from './certificate-authority-form.component';
+
+describe('CertificateAuthorityFormComponent', () => {
+  let component: CertificateAuthorityFormComponent;
+  let fixture: ComponentFixture<CertificateAuthorityFormComponent>;
+  let formBuilder: CdFormBuilder;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [ReactiveFormsModule, CertificateAuthorityFormComponent]
+    }).compileComponents();
+
+    formBuilder = new CdFormBuilder();
+    fixture = TestBed.createComponent(CertificateAuthorityFormComponent);
+    component = fixture.componentInstance;
+    component.formGroup = formBuilder.group({
+      certificateType: [CertificateType.internal],
+      custom_sans: [[]],
+      service_type: ['rgw'],
+      virtual_host_enabled: [false],
+      wildcard_enabled: [false]
+    });
+  });
+
+  it('should create', () => {
+    fixture.detectChanges();
+    jestExpect(component).toBeTruthy();
+  });
+
+  it('should emit certificateTypeChange', () => {
+    const emitSpy = jest.spyOn(component.certificateTypeChange, 'emit');
+    component.onCertificateTypeChange(CertificateType.external);
+    jestExpect(emitSpy).toHaveBeenCalledWith(CertificateType.external);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/certificate-authority-form/certificate-authority-form.component.ts
new file mode 100644 (file)
index 0000000..f46c92b
--- /dev/null
@@ -0,0 +1,47 @@
+import { CommonModule } from '@angular/common';
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
+import { CheckboxModule, GridModule, LayoutModule, RadioModule } from 'carbon-components-angular';
+
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import {
+  CephServiceCertificate,
+  CertificateType,
+  CERTIFICATE_STATUS_ICON_MAP
+} from '~/app/shared/models/service.interface';
+import { PipesModule } from '~/app/shared/pipes/pipes.module';
+import { ComponentsModule } from '../components.module';
+import { TextLabelListComponent } from '../text-label-list/text-label-list.component';
+
+@Component({
+  selector: 'cd-certificate-authority-form',
+  templateUrl: './certificate-authority-form.component.html',
+  styleUrls: ['./certificate-authority-form.component.scss'],
+  standalone: true,
+  imports: [
+    CommonModule,
+    ReactiveFormsModule,
+    RadioModule,
+    CheckboxModule,
+    GridModule,
+    LayoutModule,
+    PipesModule,
+    ComponentsModule,
+    TextLabelListComponent
+  ]
+})
+export class CertificateAuthorityFormComponent {
+  readonly CertificateType = CertificateType;
+  readonly statusIconMap = CERTIFICATE_STATUS_ICON_MAP;
+
+  @Input() formGroup: CdFormGroup;
+  @Input() editing = false;
+  @Input() currentCertificate: CephServiceCertificate = null;
+  @Input() showCertSourceChangeWarning = false;
+
+  @Output() certificateTypeChange = new EventEmitter<CertificateType>();
+
+  onCertificateTypeChange(type: CertificateType): void {
+    this.certificateTypeChange.emit(type);
+  }
+}