]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard : Carbonize configuration form 69070/head
authorAbhishek Desai <abhishek.desai1@ibm.com>
Sun, 24 May 2026 18:49:39 +0000 (00:19 +0530)
committerAbhishek Desai <abhishek.desai1@ibm.com>
Wed, 24 Jun 2026 11:52:05 +0000 (17:22 +0530)
fixes : https://tracker.ceph.com/issues/76778
Signed-off-by: Abhishek Desai <abhishek.desai1@ibm.com>
src/pybind/mgr/dashboard/controllers/cluster_configuration.py
src/pybind/mgr/dashboard/frontend/cypress/e2e/cluster/configuration.po.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts

index eeda4289eb8c3d9f34ad509dae936a5cd8fef669..6a9c278579b4848636ee81acbc8231fc37a9b418 100644 (file)
@@ -54,7 +54,7 @@ class ClusterConfiguration(RESTController):
         module_options = cephadm_module_config.get('module_options', {})
 
         for option_name, opt in module_options.items():
-            current_value = mgr.get_module_option_ex(
+            mgr_value = mgr.get_module_option_ex(
                 'cephadm', option_name, opt.get('default_value'))
 
             option = dict(opt)
@@ -64,11 +64,19 @@ class ClusterConfiguration(RESTController):
             option['enum_values'] = option.pop('enum_allowed', [])
             option['services'] = ['mgr']
             option['can_update_at_runtime'] = True
-            option['value'] = [{'section': 'mgr', 'value': current_value}]
+            option['value'] = []
             option['source'] = 'mgr_module'
+            option['_mgr_value'] = mgr_value
 
             cephadm_options.append(option)
 
+        self._append_config_option_values(cephadm_options)
+
+        for option in cephadm_options:
+            mgr_value = option.pop('_mgr_value')
+            if not any(v['section'] == 'mgr' for v in option.get('value', [])):
+                option['value'].append({'section': 'mgr', 'value': mgr_value})
+
         return cephadm_options
 
     def _append_config_option_values(self, options):
index 920d118be87e47c818fb146da103b89a58ed0ed5..6c2b44965285190b46f4f491951f1fdb62e14bf4 100644 (file)
@@ -5,6 +5,14 @@ export class ConfigurationPageHelper extends PageHelper {
     index: { url: '#/configuration', id: 'cd-configuration' }
   };
 
+  private waitForEditForm(name: string) {
+    cy.contains('h3', `Edit ${name}`).should('be.visible');
+  }
+
+  private getSectionInput(section: string) {
+    return cy.get(`input#${section}`);
+  }
+
   /**
    * Clears out all the values in a config to reset before and after testing
    * Does not work for configs with checkbox only, possible future PR
@@ -14,16 +22,16 @@ export class ConfigurationPageHelper extends PageHelper {
     const valList = ['global', 'mon', 'mgr', 'osd', 'mds', 'client']; // Editable values
     this.getFirstTableCell(name).click();
     cy.contains('button', 'Edit').click();
-    // Waits for the data to load
-    cy.contains('.card-header', `Edit ${name}`);
+    this.waitForEditForm(name);
 
     for (const i of valList) {
-      cy.get(`#${i}`).clear();
+      this.getSectionInput(i).clear({ force: true }).blur({ force: true });
     }
     // Clicks save button and checks that values are not present for the selected config
     cy.get('[data-testid=submitBtn]').click();
 
-    cy.wait(3 * 1000);
+    cy.url().should('include', '#/configuration');
+    cy.get(this.pages.index.id);
 
     this.clearFilter();
 
@@ -54,18 +62,18 @@ export class ConfigurationPageHelper extends PageHelper {
     this.getFirstTableCell(name).click();
     cy.contains('button', 'Edit').click();
 
-    // Waits for data to load
-    cy.contains('.card-header', `Edit ${name}`);
+    this.waitForEditForm(name);
 
     values.forEach((valtuple) => {
       // Finds desired value based off given list
-      cy.get(`#${valtuple[0]}`).type(valtuple[1]); // of values and inserts the given number for the value
+      this.getSectionInput(valtuple[0]).type(valtuple[1]);
     });
 
     // Clicks save button then waits until the desired config is visible, clicks it,
     // then checks that each desired value appears with the desired number
     cy.get('[data-testid=submitBtn]').click();
-    cy.wait(3 * 1000);
+    cy.url().should('include', '#/configuration');
+    cy.get(this.pages.index.id);
 
     // Enter config setting name into filter box
     this.searchTable(name, 100);
index 13080dd7b2edb312dd7519fb3991e5839372a017..4c802c8fd072e0ed8b01a371b03de609dfcac2a0 100644 (file)
-<div class="cd-col-form"
-     *cdFormLoading="loading">
-  <form name="configForm"
-        #formDir="ngForm"
-        [formGroup]="configForm"
-        novalidate>
-    <div class="card">
-      <div class="card-header">
-        <ng-container i18>Edit</ng-container> {{ configForm.getValue('name') }}
-      </div>
-
-      <div class="card-body">
-        <!-- Name -->
-        <div class="form-group row">
-          <label i18n
-                 class="cd-col-form-label">Name</label>
-          <div class="cd-col-form-input">
-            <input class="form-control"
-                   type="text"
-                   id="name"
-                   formControlName="name"
-                   readonly>
-          </div>
+<ng-container *cdFormLoading="loading">
+  <form
+    [formGroup]="configForm"
+    novalidate
+  >
+    <div
+      cdsGrid
+      [useCssGrid]="true"
+      [narrow]="true"
+      [fullWidth]="true"
+    >
+      <div
+        cdsCol
+        [columnNumbers]="{ sm: 4, md: 8 }"
+      >
+        <div
+          cdsRow
+          class="form-heading"
+        >
+          <h3 i18n>Edit {{ configForm.getValue('name') }}</h3>
         </div>
 
-        <!-- Description -->
-        <div class="form-group row"
-             *ngIf="configForm.getValue('desc')">
-          <label i18n
-                 class="cd-col-form-label">Description</label>
-          <div class="cd-col-form-input">
-            <textarea class="form-control resize-vertical"
-                      id="desc"
-                      formControlName="desc"
-                      readonly>
-            </textarea>
-          </div>
+        <!-- Name -->
+        <div
+          cdsRow
+          class="form-item"
+        >
+          <cds-text-label
+            labelInputID="name"
+            i18n
+          >
+            Name
+            <input
+              cdsText
+              type="text"
+              id="name"
+              formControlName="name"
+              readonly
+            />
+          </cds-text-label>
         </div>
 
         <!-- Long description -->
-        <div class="form-group row"
-             *ngIf="configForm.getValue('long_desc')">
-          <label i18n
-                 class="cd-col-form-label">Long description</label>
-          <div class="cd-col-form-input">
-            <textarea class="form-control resize-vertical"
-                      id="long_desc"
-                      formControlName="long_desc"
-                      readonly>
-            </textarea>
-          </div>
+        @if (response?.long_desc) {
+        <div
+          cdsRow
+          class="form-item"
+        >
+          <label
+            class="cds--label"
+            i18n
+          >Description</label>
+          <p class="cds--type-body-01 mb-0">{{ response.long_desc }}</p>
         </div>
+        }
 
         <!-- Default -->
-        <div class="form-group row"
-             *ngIf="configForm.getValue('default') !== ''">
-          <label i18n
-                 class="cd-col-form-label">Default</label>
-          <div class="cd-col-form-input">
-            <input class="form-control"
-                   type="text"
-                   id="default"
-                   formControlName="default"
-                   readonly>
-          </div>
+        @if (configForm.getValue('default') !== '') {
+        <div
+          cdsRow
+          class="form-item"
+        >
+          <cds-text-label
+            labelInputID="default"
+            i18n
+          >
+            Default
+            <input
+              cdsText
+              type="text"
+              id="default"
+              formControlName="default"
+              readonly
+            />
+          </cds-text-label>
         </div>
+        }
 
         <!-- Daemon default -->
-        <div class="form-group row"
-             *ngIf="configForm.getValue('daemon_default') !== ''">
-          <label i18n
-                 class="cd-col-form-label">Daemon default</label>
-          <div class="cd-col-form-input">
-            <input class="form-control"
-                   type="text"
-                   id="daemon_default"
-                   formControlName="daemon_default"
-                   readonly>
-          </div>
+        @if (configForm.getValue('daemon_default') !== '') {
+        <div
+          cdsRow
+          class="form-item"
+        >
+          <cds-text-label
+            labelInputID="daemon_default"
+            i18n
+          >
+            Daemon default
+            <input
+              cdsText
+              type="text"
+              id="daemon_default"
+              formControlName="daemon_default"
+              readonly
+            />
+          </cds-text-label>
         </div>
+        }
 
         <!-- Services -->
-        <div class="form-group row"
-             *ngIf="configForm.getValue('services').length > 0">
-          <label i18n
-                 class="cd-col-form-label">Services</label>
-          <div class="cd-col-form-input">
-            <span *ngFor="let service of configForm.getValue('services')"
-                  class="form-component-badge">
-              <cds-tag [size]="'md'"
-                       class="tag-dark">
-                {{ service }}
-              </cds-tag>
-            </span>
+        @if (configForm.getValue('services')?.length > 0) {
+        <div
+          cdsRow
+          class="form-item form-item-append align-items-center"
+        >
+          <div
+            cdsCol
+            [columnNumbers]="{ sm: 2, md: 2, lg: 2 }"
+          >
+            <label
+              class="cds--label mb-0"
+              i18n
+            >Services</label>
+          </div>
+          <div
+            cdsCol
+            [columnNumbers]="{ sm: 6, md: 6, lg: 6 }"
+          >
+            @for (service of configForm.getValue('services'); track service) {
+            <cds-tag
+              [size]="'md'"
+              class="tag-dark me-2"
+            >
+              {{ service }}
+            </cds-tag>
+            }
           </div>
         </div>
+        }
 
         <!-- Values -->
         <div formGroupName="values">
-          <h3 i18n
-              class="cd-header">Values</h3>
-          <ng-container *ngFor="let section of availSections">
-            <div class="form-group row"
-                 *ngIf="type === 'bool'">
-              <label class="cd-col-form-label"
-                     [for]="section">{{ section }}
-              </label>
-              <div class="cd-col-form-input">
-                <select id="pool"
-                        name="pool"
-                        class="form-select"
-                        [formControlName]="section">
-                  <option [ngValue]="null"
-                          i18n>-- Default --</option>
-                  <option [ngValue]="true"
-                          i18n>true</option>
-                  <option [ngValue]="false"
-                          i18n>false</option>
-                </select>
-              </div>
+          <div
+            cdsRow
+            class="form-heading"
+          >
+            <h3 i18n>Values</h3>
+          </div>
+
+          @for (section of availSections; track section) {
+            @if (type === 'bool') {
+            <div
+              cdsRow
+              class="form-item"
+            >
+              <cds-select
+                [formControlName]="section"
+                [label]="section"
+                [id]="section"
+                [labelInputID]="section"
+                cdValidate
+                #sectionValidate="cdValidate"
+                [invalid]="sectionValidate.isInvalid"
+                [invalidText]="sectionErrors"
+              >
+                <option
+                  [ngValue]="null"
+                  i18n
+                >-- Default --</option>
+                <option
+                  [ngValue]="true"
+                  i18n
+                >true</option>
+                <option
+                  [ngValue]="false"
+                  i18n
+                >false</option>
+              </cds-select>
+              <ng-template #sectionErrors>
+                <ng-container
+                  *ngTemplateOutlet="validationErrors; context: { control: configForm.get(section) }"
+                ></ng-container>
+              </ng-template>
+            </div>
+            }
+
+            @if (type !== 'bool' && inputType === 'number') {
+            <div
+              cdsRow
+              class="form-item"
+            >
+              <cds-number
+                [formControlName]="section"
+                [label]="section"
+                [id]="section"
+                [step]="getStep(type, configForm.getValue(section))"
+                [min]="minValue"
+                [max]="maxValue"
+                cdValidate
+                #sectionValidate="cdValidate"
+                [invalid]="sectionValidate.isInvalid"
+                [invalidText]="sectionErrors"
+              >
+              </cds-number>
+              <ng-template #sectionErrors>
+                <ng-container
+                  *ngTemplateOutlet="validationErrors; context: { control: configForm.get(section) }"
+                ></ng-container>
+              </ng-template>
             </div>
+            }
 
-            <div class="form-group row"
-                 *ngIf="type !== 'bool'">
-              <label class="cd-col-form-label"
-                     [for]="section">{{ section }}
-              </label>
-              <div class="cd-col-form-input">
-                <input class="form-control"
-                       [type]="inputType"
-                       [id]="section"
-                       [placeholder]="humanReadableType"
-                       [formControlName]="section"
-                       [step]="getStep(type, configForm.getValue(section))">
-                <span class="invalid-feedback"
-                      *ngIf="configForm.showError(section, formDir, 'pattern')">
-                  {{ patternHelpText }}
-                </span>
-                <span class="invalid-feedback"
-                      *ngIf="configForm.showError(section, formDir, 'invalidUuid')">
-                  {{ patternHelpText }}
-                </span>
-                <span class="invalid-feedback"
-                      *ngIf="configForm.showError(section, formDir, 'max')"
-                      i18n>The entered value is too high! It must not be greater than {{ maxValue }}.</span>
-                <span class="invalid-feedback"
-                      *ngIf="configForm.showError(section, formDir, 'min')"
-                      i18n>The entered value is too low! It must not be lower than {{ minValue }}.</span>
-              </div>
+            @if (type !== 'bool' && inputType !== 'number') {
+            <div
+              cdsRow
+              class="form-item"
+            >
+              <cds-text-label
+                [labelInputID]="section"
+                [invalid]="sectionValidate.isInvalid"
+                [invalidText]="sectionErrors"
+              >
+                {{ section }}
+                <input
+                  cdsText
+                  cdValidate
+                  #sectionValidate="cdValidate"
+                  [type]="inputType"
+                  [id]="section"
+                  [formControlName]="section"
+                  [invalid]="sectionValidate.isInvalid"
+                />
+              </cds-text-label>
+              <ng-template #sectionErrors>
+                <ng-container
+                  *ngTemplateOutlet="validationErrors; context: { control: configForm.get(section) }"
+                ></ng-container>
+              </ng-template>
             </div>
-          </ng-container>
+            }
+          }
+        </div>
+
+        <div cdsRow>
+          <cd-form-button-panel
+            (submitActionEvent)="forceUpdate ? openCriticalConfirmModal() : submit()"
+            [form]="configForm"
+            [submitText]="actionLabels.UPDATE"
+            wrappingClass="text-right form-button"
+          ></cd-form-button-panel>
         </div>
-      </div>
-      <!-- Footer -->
-      <div class="card-footer">
-        <cd-form-button-panel (submitActionEvent)="forceUpdate ? openCriticalConfirmModal() : submit()"
-                              [form]="configForm"
-                              [submitText]="actionLabels.UPDATE"
-                              wrappingClass="text-right"></cd-form-button-panel>
       </div>
     </div>
   </form>
-</div>
+</ng-container>
 
+<ng-template
+  #validationErrors
+  let-control="control"
+>
+  @if (control?.invalid && (control.dirty || control.touched)) {
+    @if (control.hasError('min')) {
+    <span
+      class="invalid-feedback"
+      i18n
+    >The entered value is too low! It must not be lower than {{ minValue }}.</span>
+    }
+    @if (control.hasError('max')) {
+    <span
+      class="invalid-feedback"
+      i18n
+    >The entered value is too high! It must not be greater than {{ maxValue }}.</span>
+    }
+    @if (control.hasError('pattern') || control.hasError('invalidUuid')) {
+    <span class="invalid-feedback">{{ patternHelpText }}</span>
+    }
+  }
+</ng-template>
index f4a0557795a340d18ab06247edb37a1971f24cdb..c98f93dc93b407045a2fc243ff4b009e22b91e87 100644 (file)
@@ -141,8 +141,12 @@ export class ConfigurationFormComponent extends CdForm implements OnInit {
 
     this.availSections.forEach((section) => {
       const sectionValue = this.configForm.getValue(section);
-      if (sectionValue !== null) {
+      const hadValue = this.response?.value?.some((v) => v.section === section);
+
+      if (sectionValue !== null && sectionValue !== undefined && sectionValue !== '') {
         values.push({ section: section, value: sectionValue });
+      } else if (hadValue) {
+        values.push({ section: section, value: '' });
       }
     });
 
@@ -188,8 +192,8 @@ export class ConfigurationFormComponent extends CdForm implements OnInit {
           this.configForm.setErrors({ cdSubmitButton: true });
         }
       );
+    } else {
+      this.router.navigate(['/configuration']);
     }
-
-    this.router.navigate(['/configuration']);
   }
 }