]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: SMB - Edit Cluster 61822/head
authorDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Wed, 12 Feb 2025 13:30:29 +0000 (19:00 +0530)
committerDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Tue, 25 Feb 2025 11:54:58 +0000 (17:24 +0530)
Fixes: https://tracker.ceph.com/issues/69964
Signed-off-by: Dnyaneshwari Talwekar <dtalweka@redhat.com>
src/pybind/mgr/dashboard/controllers/smb.py
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-form/smb-cluster-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-form/smb-cluster-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb-cluster-list/smb-cluster-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/smb/smb.model.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/smb.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts
src/pybind/mgr/dashboard/openapi.yaml

index bc9323e9947c2c0f4043ad422bbf4e92461d70f8..88f471c76eb56507df647835e0a6ec2f6f25d4de 100644 (file)
@@ -36,6 +36,10 @@ CLUSTER_SCHEMA = {
         "ref": (str, "Reference identifier for the user group resource")
     }], "User group settings for user auth mode"),
     "custom_dns": ([str], "List of custom DNS server addresses"),
+    "public_addrs": ([{
+        "address": (str, "This address will be assigned to one of the host's network devices"),
+        "destination": (str, "Defines where the system will assign the managed IPs.")
+    }], "Public Address"),
     "placement": ({
         "count": (int, "Number of instances to place")
     }, "Placement configuration for the resource")
index 283d9e8f1a570d7b42023f05d2e1c9328d83c084..b9b9d453a0581aed18eabb8b9e1b25dc8e74ca76 100644 (file)
@@ -460,6 +460,11 @@ const routes: Routes = [
                 component: SmbShareFormComponent,
                 data: { breadcrumbs: ActionLabels.CREATE }
               },
+              {
+                path: `${URLVerbs.EDIT}/:cluster_id`,
+                component: SmbClusterFormComponent,
+                data: { breadcrumbs: ActionLabels.EDIT }
+              },
               {
                 path: `ad/${URLVerbs.CREATE}`,
                 component: SmbJoinAuthFormComponent,
index d21972ce0ec08fc9f50fef590e5d473ca193e746..e0db02f92703cc61b4907a9b7e9b8c735ae0e249 100644 (file)
 <div cdsCol
      [columnNumbers]="{ md: 4 }"
      *ngIf="orchStatus$ | async as orchStatus">
-  <form name="smbForm"
-        #formDir="ngForm"
-        [formGroup]="smbForm"
-        novalidate>
-    <div i18n="form title"
-         class="form-header">
-      {{ action | titlecase }} {{ resource | upperFirst }}
-    </div>
-
-    <!-- Cluster Id -->
-    <div class="form-item">
-      <cds-text-label
-        labelInputID="cluster_id"
-        i18n
-        helperText="Unique identifier"
-        i18n-helperText
-        cdRequiredField="Cluster Name"
-        [invalid]="smbForm.controls.cluster_id.invalid && smbForm.controls.cluster_id.dirty"
-        [invalidText]="clusterError"
-        >Cluster Name
-        <input
-          cdsText
-          type="text"
-          id="cluster_id"
-          formControlName="cluster_id"
-          [invalid]="smbForm.controls.cluster_id.invalid && smbForm.controls.cluster_id.dirty"
-        />
-      </cds-text-label>
-      <ng-template #clusterError>
-        <span
-          class="invalid-feedback"
-          *ngIf="smbForm.showError('cluster_id', formDir, 'required')"
-          i18n
-          >This field is required.</span
-        >
-      </ng-template>
-    </div>
+  <ng-container *cdFormLoading="loading">
+    <form name="smbForm"
+          #formDir="ngForm"
+          [formGroup]="smbForm"
+          novalidate>
+      <div i18n="form title"
+           class="form-header">
+        {{ action | titlecase }} {{ resource | upperFirst }}
+      </div>
 
-    <!-- Auth Mode -->
-    <div class="form-item">
-      <cds-select
-        formControlName="auth_mode"
-        label="Authentication Mode"
-        cdRequiredField="Authentication Mode"
-        id="auth_mode"
-        [invalid]="smbForm.controls.auth_mode.invalid && smbForm.controls.auth_mode.dirty"
-        [invalidText]="authModeError"
-        (change)="onAuthModeChange()"
-        helperText="Active-directory authentication for domain member servers and User authentication for
-        Stand-alone servers configuration."
-        i18n-helperText
-      >
-        <option value="active-directory"
-                i18n>Active Directory</option>
-        <option value="user"
-                i18n>User</option>
-      </cds-select>
-      <ng-template #authModeError>
-        <span
-          class="invalid-feedback"
-          *ngIf="smbForm.showError('auth_mode', formDir, 'required')"
+      <!-- Cluster Id -->
+      <div class="form-item">
+        <cds-text-label
+          labelInputID="cluster_id"
           i18n
-          >This field is required.</span
-        >
-      </ng-template>
-    </div>
-
-    <!-- Domain Settings -->
-    <div class="form-item"
-         *ngIf="this.smbForm.get('auth_mode').value === 'active-directory'">
-      <div cdsCol
-           [columnNumbers]="{ md: 12 }"
-           class="d-flex">
-        <cds-text-label labelInputID="domain_settings"
-                        i18n
-                        cdRequiredField="Domain Settings">Active Directory (AD) Settings
-          <div class="cds-input-group">
-            <input
-              cdsText
-              type="text"
-              id="domain_settings"
-              formControlName="domain_settings"
-              [value]="domainSettingsObject?.realm"
-              (click)="editDomainSettingsModal()"
-              [invalid]="
-                !smbForm.controls.domain_settings.valid &&
-                smbForm.controls.domain_settings.dirty &&
-                smbForm.controls.domain_settings.touched
-              "
-            />
-            <cds-icon-button kind="ghost"
-                             (click)="editDomainSettingsModal()"
-                             size="md">
-              <svg cdsIcon="edit"
-                   size="32"
-                   class="cds--btn__icon"
-                   icon></svg>
-            </cds-icon-button>
-
-            <cds-icon-button kind="danger"
-                             (click)="deleteDomainSettingsModal()"
-                             size="md">
-              <svg cdsIcon="trash-can"
-                   size="32"
-                   class="cds--btn__icon"
-                   icon></svg>
-            </cds-icon-button>
-          </div>
+          helperText="Unique identifier"
+          i18n-helperText
+          cdRequiredField="Cluster Name"
+          [disabled]="isEdit"
+          [invalid]="smbForm.controls.cluster_id.invalid && smbForm.controls.cluster_id.dirty"
+          [invalidText]="clusterError"
+          >Cluster Name
+          <input
+            cdsText
+            type="text"
+            id="cluster_id"
+            formControlName="cluster_id"
+            [invalid]="smbForm.controls.cluster_id.invalid && smbForm.controls.cluster_id.dirty"
+          />
         </cds-text-label>
-      </div>
-      <span
-        class="invalid-feedback"
-        *ngIf="
-          smbForm.get('domain_settings').hasError('required') &&
-          smbForm.controls.domain_settings.touched
-        "
-        i18n>Specify the Realm and AD access resources in the Domain Settings field.</span
-      >
-      <div></div>
-    </div>
-
-    <!-- User Group Settings -->
-    <ng-container formArrayName="joinSources"
-                  *ngFor="let _ of joinSources.controls; index as i">
-      <div
-        cdsRow
-        *ngIf="this.smbForm.get('auth_mode').value === 'user' && usersGroups$ | async as usersGroups"
-        class="form-item form-item-append"
-      >
-        <div
-          cdsCol
-          [columnNumbers]="{ lg: 14 }"
-        >
-          <cds-select
-            label="Standalone user access resources"
-            i18n-label
-            [formControlName]="i"
-            [invalid]="smbForm.controls.joinSources.controls[i].invalid && smbForm.controls.joinSources.controls[i].dirty"
-            [invalidText]="ugError"
-          >
-            <option
-              [value]="null"
-              i18n
-            >-- List of users and groups access resources --
-            </option>
-            <option *ngFor="let ug of usersGroups"
-                    [value]="ug.users_groups_id">{{ ug.users_groups_id }}</option>
-          </cds-select>
-          <ng-template #ugError>
-            <span
-              class="invalid-feedback"
-              i18n
-            >This field is required.</span>
-          </ng-template>
-        </div>
-        <div
-          cdsCol
-          [columnNumbers]="{ lg: 1 }"
-          class="item-action-btn spacing"
-        >
-          <cds-icon-button
-            kind="danger"
-            *ngIf="i > 0"
-            size="sm"
-            (click)="removeUserGroupSetting(i)"
+        <ng-template #clusterError>
+          <span
+            class="invalid-feedback"
+            *ngIf="smbForm.showError('cluster_id', formDir, 'required')"
+            i18n
+            >This field is required.</span
           >
-            <svg
-              cdsIcon="trash-can"
-              size="32"
-              class="cds--btn__icon"></svg>
-          </cds-icon-button>
-        </div>
+        </ng-template>
       </div>
-    </ng-container>
-
-    <div class="form-item"
-         *ngIf="this.smbForm.get('auth_mode').value === 'user'">
-      <button cdsButton="tertiary"
-              type="button"
-              (click)="addUserGroupSetting()"
-              i18n>
-        Add user group
-        <svg cdsIcon="add"
-             size="32"
-             class="cds--btn__icon"
-             icon></svg>
-      </button>
-
-      <button
-        cdsButton="tertiary"
-        type="button"
-        (click)="navigateCreateUsersGroups()"
-        i18n
-      >
-        Create user group
-      <svg
-        cdsIcon="launch"
-        size="32"
-        class="cds--btn__icon"></svg>
-      </button>
-    </div>
 
-    <!-- Placement -->
-    <ng-container *ngIf="orchStatus.available">
+      <!-- Auth Mode -->
       <div class="form-item">
         <cds-select
-          label="Placement"
-          for="placement"
-          formControlName="placement"
-          id="placement"
+          formControlName="auth_mode"
+          label="Authentication Mode"
+          cdRequiredField="Authentication Mode"
+          id="auth_mode"
+          [invalid]="smbForm.controls.auth_mode.invalid && smbForm.controls.auth_mode.dirty"
+          [invalidText]="authModeError"
+          (change)="onAuthModeChange()"
+          [disabled]="isEdit"
+          helperText="Active-directory authentication for domain member servers and User authentication for
+          Stand-alone servers configuration."
+          i18n-helperText
         >
-          <option value="hosts"
-                  i18n>Hosts</option>
-          <option value="label"
-                  i18n>Labels</option>
+          <option value="active-directory"
+                  i18n>Active Directory</option>
+          <option value="user"
+                  i18n>User</option>
         </cds-select>
-      </div>
-      <ng-container *ngIf="hostsAndLabels$ | async as data">
-        <!-- Label -->
-        <div *ngIf="smbForm.controls.placement.value === 'label'"
-             class="form-item">
-          <cds-combo-box
-            type="multi"
-            selectionFeedback="top-after-reopen"
-            label="Label"
-            formControlName="label"
-            id="label"
-            placeholder="Select labels..."
-            [appendInline]="true"
-            [items]="data.labels"
-            i18n-placeholder
-            (selected)="multiSelector($event, 'label')"
-            [invalid]="smbForm.controls.label.invalid && smbForm.controls.label.dirty"
-            [invalidText]="labelError"
-            cdRequiredField="Label"
+        <ng-template #authModeError>
+          <span
+            class="invalid-feedback"
+            *ngIf="smbForm.showError('auth_mode', formDir, 'required')"
             i18n
+            >This field is required.</span
           >
-            <cds-dropdown-list></cds-dropdown-list>
-          </cds-combo-box>
-          <ng-template #labelError>
-            <span
-              class="invalid-feedback"
-              *ngIf="smbForm.showError('label', formDir, 'required')"
-              i18n
-              >This field is required.</span
-            >
-          </ng-template>
-        </div>
-
-        <!-- Hosts -->
-        <div *ngIf="smbForm.controls.placement.value === 'hosts'"
-             class="form-item">
-          <cds-combo-box
-            type="multi"
-            selectionFeedback="top-after-reopen"
-            label="Hosts"
-            formControlName="hosts"
-            id="hosts"
-            placeholder="Select hosts..."
-            i18n-placeholder
-            [appendInline]="true"
-            [items]="data.hosts"
-            (selected)="multiSelector($event, 'hosts')"
-            i18n
-          >
-            <cds-dropdown-list></cds-dropdown-list>
-          </cds-combo-box>
-        </div>
-      </ng-container>
-    </ng-container>
-
-    <div class="form-item">
-      <cds-number
-        [id]="'count'"
-        [formControlName]="'count'"
-        [label]="'Count'"
-        [min]="1"
-      ></cds-number>
-    </div>
+        </ng-template>
+      </div>
 
-    <!-- Custom DNS -->
-    <ng-container formArrayName="custom_dns"
-                  *ngFor="let _ of custom_dns.controls; index as i">
-      <div cdsRow
-           class="form-item form-item-append">
+      <!-- Domain Settings -->
+      <div class="form-item"
+           *ngIf="this.smbForm.get('auth_mode').value === 'active-directory'">
         <div cdsCol
-             [columnNumbers]="{ lg: 14 }">
-          <cds-text-label
-             for="custom_dns"
-             i18n
-           >DNS
-          <input cdsText
-                 [formControlName]="i"
-                 placeholder="192.168.76.204"/>
+             [columnNumbers]="{ md: 12 }"
+             class="d-flex">
+          <cds-text-label labelInputID="domain_settings"
+                          i18n
+                          cdRequiredField="Domain Settings">Active Directory (AD) Settings
+            <div class="cds-input-group">
+              <input
+                cdsText
+                type="text"
+                id="domain_settings"
+                formControlName="domain_settings"
+                [value]="domainSettingsObject?.realm"
+                (click)="editDomainSettingsModal()"
+                [invalid]="
+                  !smbForm.controls.domain_settings.valid &&
+                  smbForm.controls.domain_settings.dirty &&
+                  smbForm.controls.domain_settings.touched
+                "
+              />
+              <cds-icon-button kind="ghost"
+                               (click)="editDomainSettingsModal()"
+                               size="md">
+                <svg cdsIcon="edit"
+                     size="32"
+                     class="cds--btn__icon"
+                     icon></svg>
+              </cds-icon-button>
+
+              <cds-icon-button kind="danger"
+                               (click)="deleteDomainSettingsModal()"
+                               size="md">
+                <svg cdsIcon="trash-can"
+                     size="32"
+                     class="cds--btn__icon"
+                     icon></svg>
+              </cds-icon-button>
+            </div>
           </cds-text-label>
         </div>
+        <span
+          class="invalid-feedback"
+          *ngIf="
+            smbForm.get('domain_settings').hasError('required') &&
+            smbForm.controls.domain_settings.touched
+          "
+          i18n>Specify the Realm and AD access resources in the Domain Settings field.</span
+        >
+        <div></div>
+      </div>
+
+      <!-- User Group Settings -->
+      <ng-container formArrayName="joinSources"
+                    *ngFor="let _ of joinSources.controls; index as i">
         <div
-          cdsCol
-          [columnNumbers]="{ lg: 1 }"
-          class="item-action-btn spacing"
+          cdsRow
+          *ngIf="this.smbForm.get('auth_mode').value === 'user' && usersGroups$ | async as usersGroups"
+          class="form-item form-item-append"
         >
-          <cds-icon-button
-            kind="danger"
-            size="sm"
-            (click)="removeCustomDNS(i)"
+          <div
+            cdsCol
+            [columnNumbers]="{ lg: 14 }"
+          >
+            <cds-select
+              label="Standalone user access resources"
+              i18n-label
+              [formControlName]="i"
+              [invalid]="smbForm.controls.joinSources.controls[i].invalid && smbForm.controls.joinSources.controls[i].dirty"
+              [invalidText]="ugError"
+            >
+              <option
+                [value]="null"
+                i18n
+              >-- List of users and groups access resources --
+              </option>
+              <option *ngFor="let ug of usersGroups"
+                      [value]="ug.users_groups_id">{{ ug.users_groups_id }}</option>
+            </cds-select>
+            <ng-template #ugError>
+              <span
+                class="invalid-feedback"
+                i18n
+              >This field is required.</span>
+            </ng-template>
+          </div>
+          <div
+            cdsCol
+            [columnNumbers]="{ lg: 1 }"
+            class="item-action-btn spacing"
           >
-            <svg
-              cdsIcon="trash-can"
-              size="32"
-              class="cds--btn__icon"
+            <cds-icon-button
+              kind="danger"
+              *ngIf="i > 0"
+              size="sm"
+              (click)="removeUserGroupSetting(i)"
             >
-            </svg>
-          </cds-icon-button>
+              <svg
+                cdsIcon="trash-can"
+                size="32"
+                class="cds--btn__icon"></svg>
+            </cds-icon-button>
+          </div>
         </div>
-      </div>
-    </ng-container>
+      </ng-container>
+
+      <div class="form-item"
+           *ngIf="this.smbForm.get('auth_mode').value === 'user'">
+        <button cdsButton="tertiary"
+                type="button"
+                (click)="addUserGroupSetting()"
+                i18n>
+          Add user group
+          <svg cdsIcon="add"
+               size="32"
+               class="cds--btn__icon"
+               icon></svg>
+        </button>
 
-    <div class="form-item">
-      <button cdsButton="tertiary"
-              type="button"
-              (click)="addCustomDns()"
-              i18n>
-        Add custom DNS
+        <button
+          cdsButton="tertiary"
+          type="button"
+          (click)="navigateCreateUsersGroups()"
+          i18n
+        >
+          Create user group
         <svg
-          cdsIcon="add"
+          cdsIcon="launch"
           size="32"
-          class="cds--btn__icon"
-          icon></svg>
-      </button>
-      <cd-helper i18n>One or more IP Addresses that will be
-        applied to the Samba containers to override
-        the default DNS resolver(s). This option is
-        intended to be used when the host Ceph node
-        is not configured to resolve DNS entries within
-        AD domain(s).
-      </cd-helper>
-    </div>
-
-    <!-- Clustering -->
-    <div class="form-item">
-      <cds-select
-        formControlName="clustering"
-        for="clustering"
-        label="Clustering"
-        id="clustering"
-        helperText="Default value indicates that clustering should be enabled if the placement count value is any value other than 1. Always value enables clustering regardless of the placement count. Never value disables clustering regardless of the placement count. "
-        i18n-helperText
-      >
-        <option *ngFor="let data of allClustering"
-                i18n>{{ data | upperFirst }}</option>
-      </cds-select>
-    </div>
+          class="cds--btn__icon"></svg>
+        </button>
+      </div>
 
-    <!-- Public addrs -->
-    <ng-container formArrayName="public_addrs"
-                  *ngFor="let _ of public_addrs.controls; index as i">
-      <ng-container [formGroupName]="i">
-        <div cdsRow
-             class="form-item form-item-append">
-          <!-- Address -->
-          <div cdsCol
-               [columnNumbers]="{ lg: 7 }">
-            <cds-text-label
-              for="public_addrs"
+      <!-- Placement -->
+      <ng-container *ngIf="orchStatus.available">
+        <div class="form-item">
+          <cds-select
+            label="Placement"
+            for="placement"
+            formControlName="placement"
+            id="placement"
+          >
+            <option value="hosts"
+                    i18n>Hosts</option>
+            <option value="label"
+                    i18n>Labels</option>
+          </cds-select>
+        </div>
+        <ng-container *ngIf="hostsAndLabels$ | async as data">
+          <!-- Label -->
+          <div *ngIf="smbForm.controls.placement.value === 'label'"
+               class="form-item">
+            <cds-combo-box
+              type="multi"
+              selectionFeedback="top-after-reopen"
+              label="Label"
+              formControlName="label"
+              id="label"
+              placeholder="Select labels..."
+              [appendInline]="true"
+              [items]="data.labels"
+              i18n-placeholder
+              (selected)="multiSelector($event, 'label')"
+              [invalid]="smbForm.controls.label.invalid && smbForm.controls.label.dirty"
+              [invalidText]="labelError"
+              cdRequiredField="Label"
               i18n
-              helperText="This address will be assigned to one of the host's network devices and managed automatically."
-              i18n-helperText
-              cdrequiredField
-              [invalid]="smbForm?.controls['public_addrs']?.controls[i].controls.address.invalid && smbForm?.controls['public_addrs']?.controls[i].controls.address.dirty"
-              [invalidText]="addressError"
-            >Address
-              <input
-                cdsText
-                type="text"
-                formControlName="address"
-                placeholder="192.168.4.51/24"
-                [invalid]="smbForm?.controls['public_addrs'].controls[i].controls.address.invalid && smbForm?.controls['public_addrs']?.controls[i].controls.address.dirty"
-              />
-            </cds-text-label>
-            <ng-template #addressError>
+            >
+              <cds-dropdown-list></cds-dropdown-list>
+            </cds-combo-box>
+            <ng-template #labelError>
               <span
                 class="invalid-feedback"
+                *ngIf="smbForm.showError('label', formDir, 'required')"
+                i18n
+                >This field is required.</span
               >
-                <ng-container i18n> This field is required. </ng-container>
-              </span>
             </ng-template>
           </div>
-          <!-- Destination -->
+
+          <!-- Hosts -->
+          <div *ngIf="smbForm.controls.placement.value === 'hosts'"
+               class="form-item">
+            <cds-combo-box
+              type="multi"
+              selectionFeedback="top-after-reopen"
+              label="Hosts"
+              formControlName="hosts"
+              id="hosts"
+              placeholder="Select hosts..."
+              i18n-placeholder
+              [appendInline]="true"
+              [items]="data.hosts"
+              (selected)="multiSelector($event, 'hosts')"
+              i18n
+            >
+              <cds-dropdown-list></cds-dropdown-list>
+            </cds-combo-box>
+          </div>
+        </ng-container>
+      </ng-container>
+
+      <div class="form-item">
+        <cds-number
+          [id]="'count'"
+          [formControlName]="'count'"
+          [label]="'Count'"
+          [min]="1"
+        ></cds-number>
+      </div>
+
+      <!-- Custom DNS -->
+      <ng-container formArrayName="custom_dns"
+                    *ngFor="let _ of custom_dns.controls; index as i">
+        <div cdsRow
+             class="form-item form-item-append">
           <div cdsCol
-               [columnNumbers]="{ lg: 7 }">
+               [columnNumbers]="{ lg: 14 }">
             <cds-text-label
-              for="public_addrs"
+              for="custom_dns"
               i18n
-              helperText="Defines where the system will assign the managed IPs. Each string value must be a network address."
-              i18n-helperText
-            >Destination
-            <input
-              cdsText
-              type="text"
-              formControlName="destination"
-              placeholder="192.168.4.0/24"/>
+            >DNS
+            <input cdsText
+                   [formControlName]="i"
+                   placeholder="192.168.76.204"/>
             </cds-text-label>
           </div>
           <div
             <cds-icon-button
               kind="danger"
               size="sm"
-              (click)="removePublicAddrs(i)"
+              (click)="removeCustomDNS(i)"
             >
-              <svg cdsIcon="trash-can"
-                   size="32"
-                   class="cds--btn__icon"></svg>
+              <svg
+                cdsIcon="trash-can"
+                size="32"
+                class="cds--btn__icon"
+              >
+              </svg>
             </cds-icon-button>
           </div>
         </div>
       </ng-container>
-    </ng-container>
-    <div
-      *ngIf="(this.smbForm.get('count').value > 1 && this.smbForm.get('clustering').value.toLowerCase() == CLUSTERING.Default) || this.smbForm.get('clustering').value.toLowerCase() == CLUSTERING.Always"
-      class="form-item"
-    >
-      <button cdsButton="tertiary"
-              type="button"
-              (click)="addPublicAddrs()"
-              i18n>
-        Add public address
-        <svg
-          cdsIcon="add"
-          size="32"
-          class="cds--btn__icon"
-          icon></svg>
-      </button>
-      <cd-helper i18n>Assign virtual IP addresses that will be managed
-        by the clustering subsystem and may automatically
-        move between nodes running Samba containers.</cd-helper>
-    </div>
-    <cd-form-button-panel
-      (submitActionEvent)="submitAction()"
-      [form]="smbForm"
-      [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
-      wrappingClass="text-right"
-    ></cd-form-button-panel>
-  </form>
+
+      <div class="form-item">
+        <button cdsButton="tertiary"
+                type="button"
+                (click)="addCustomDns()"
+                i18n>
+          Add custom DNS
+          <svg
+            cdsIcon="add"
+            size="32"
+            class="cds--btn__icon"
+            icon></svg>
+        </button>
+        <cd-helper i18n>One or more IP Addresses that will be
+          applied to the Samba containers to override
+          the default DNS resolver(s). This option is
+          intended to be used when the host Ceph node
+          is not configured to resolve DNS entries within
+          AD domain(s).
+        </cd-helper>
+      </div>
+
+      <!-- Clustering -->
+      <div class="form-item">
+        <cds-select
+          formControlName="clustering"
+          for="clustering"
+          label="Clustering"
+          id="clustering"
+          helperText="Default value indicates that clustering should be enabled if the placement count value is any value other than 1. Always value enables clustering regardless of the placement count. Never value disables clustering regardless of the placement count. "
+          i18n-helperText
+        >
+          <option *ngFor="let data of allClustering"
+                  i18n>{{ data | upperFirst }}</option>
+        </cds-select>
+      </div>
+
+      <!-- Public addrs -->
+      <ng-container formArrayName="public_addrs"
+                    *ngFor="let _ of public_addrs.controls; index as i">
+        <ng-container [formGroupName]="i">
+          <div cdsRow
+               class="form-item form-item-append">
+            <!-- Address -->
+            <div cdsCol
+                 [columnNumbers]="{ lg: 7 }">
+              <cds-text-label
+                for="public_addrs"
+                i18n
+                helperText="This address will be assigned to one of the host's network devices and managed automatically."
+                i18n-helperText
+                cdrequiredField
+                [invalid]="smbForm?.controls['public_addrs']?.controls[i].controls.address.invalid && smbForm?.controls['public_addrs']?.controls[i].controls.address.dirty"
+                [invalidText]="addressError"
+              >Address
+                <input
+                  cdsText
+                  type="text"
+                  formControlName="address"
+                  placeholder="192.168.4.51/24"
+                  [invalid]="smbForm?.controls['public_addrs'].controls[i].controls.address.invalid && smbForm?.controls['public_addrs']?.controls[i].controls.address.dirty"
+                />
+              </cds-text-label>
+              <ng-template #addressError>
+                <span
+                  class="invalid-feedback"
+                >
+                  <ng-container i18n> This field is required. </ng-container>
+                </span>
+              </ng-template>
+            </div>
+            <!-- Destination -->
+            <div cdsCol
+                 [columnNumbers]="{ lg: 7 }">
+              <cds-text-label
+                for="public_addrs"
+                i18n
+                helperText="Defines where the system will assign the managed IPs. Each string value must be a network address."
+                i18n-helperText
+              >Destination
+              <input
+                cdsText
+                type="text"
+                formControlName="destination"
+                placeholder="192.168.4.0/24"/>
+              </cds-text-label>
+            </div>
+            <div
+              cdsCol
+              [columnNumbers]="{ lg: 1 }"
+              class="item-action-btn spacing"
+            >
+              <cds-icon-button
+                kind="danger"
+                size="sm"
+                (click)="removePublicAddrs(i)"
+              >
+                <svg cdsIcon="trash-can"
+                     size="32"
+                     class="cds--btn__icon"></svg>
+              </cds-icon-button>
+            </div>
+          </div>
+        </ng-container>
+      </ng-container>
+      <div
+        *ngIf="(this.smbForm.get('count').value > 1 && this.smbForm.get('clustering').value.toLowerCase() == CLUSTERING.Default) || this.smbForm.get('clustering').value.toLowerCase() == CLUSTERING.Always"
+        class="form-item"
+      >
+        <button cdsButton="tertiary"
+                type="button"
+                (click)="addPublicAddrs()"
+                i18n>
+          Add public address
+          <svg
+            cdsIcon="add"
+            size="32"
+            class="cds--btn__icon"
+            icon></svg>
+        </button>
+        <cd-helper i18n>Assign virtual IP addresses that will be managed
+          by the clustering subsystem and may automatically
+          move between nodes running Samba containers.</cd-helper>
+      </div>
+      <cd-form-button-panel
+        (submitActionEvent)="submitAction()"
+        [form]="smbForm"
+        [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+        wrappingClass="text-right"
+      ></cd-form-button-panel>
+    </form>
+  </ng-container>
 </div>
index cbbcd4abca1d979b3b8ecde424e87b5f2b4a11d6..e48619c5824ce354d59bc802deac416c40c5dd5c 100644 (file)
@@ -1,5 +1,5 @@
 import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
 import { forkJoin, Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 
@@ -14,7 +14,8 @@ import {
   CLUSTER_RESOURCE,
   ClusterRequestModel,
   SMBUsersGroups,
-  PublicAddress
+  PublicAddress,
+  SMBCluster
 } from '../smb.model';
 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
 import { Icons } from '~/app/shared/enum/icons.enum';
@@ -34,6 +35,7 @@ import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { SmbDomainSettingModalComponent } from '../smb-domain-setting-modal/smb-domain-setting-modal.component';
 import { CephServicePlacement } from '~/app/shared/models/service.interface';
 import { USERSGROUPS_URL } from '../smb-usersgroups-list/smb-usersgroups-list.component';
+import { UpperFirstPipe } from '~/app/shared/pipes/upper-first.pipe';
 
 @Component({
   selector: 'cd-smb-cluster-form',
@@ -53,6 +55,9 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
   resource: string;
   icons = Icons;
   domainSettingsObject: DomainSettings;
+  isEdit = false;
+  cluster_id: string;
+  clusterResponse: SMBCluster;
   modalData$!: Observable<DomainSettings>;
   usersGroups$: Observable<SMBUsersGroups[]>;
 
@@ -65,20 +70,25 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
     private modalService: ModalCdsService,
     private taskWrapperService: TaskWrapperService,
     private router: Router,
-    private cd: ChangeDetectorRef
+    private cd: ChangeDetectorRef,
+    private route: ActivatedRoute
   ) {
     super();
+
     this.resource = $localize`Cluster`;
     this.modalData$ = this.smbService.modalData$;
   }
+
   ngOnInit() {
     this.action = this.actionLabels.CREATE;
     this.usersGroups$ = this.smbService.listUsersGroups();
+    if (this.router.url.startsWith(`/cephfs/smb/${URLVerbs.EDIT}`)) {
+      this.isEdit = true;
+    }
     this.smbService.modalData$.subscribe((data: DomainSettings) => {
       this.domainSettingsObject = data;
       this.smbForm.get('domain_settings').setValue(data?.realm);
     });
-    this.createForm();
     this.hostsAndLabels$ = forkJoin({
       hosts: this.hostService.getAllHosts(),
       labels: this.hostService.getLabels()
@@ -88,9 +98,85 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
         labels: labels.map((label: string) => ({ content: label }))
       }))
     );
+
+    this.createForm();
+    if (this.isEdit) {
+      this.action = this.actionLabels.EDIT;
+      this.smbForm.get('cluster_id').disable();
+      this.smbForm.get('auth_mode').disable();
+      this.route.params.subscribe((params: { cluster_id: string }) => {
+        this.cluster_id = params.cluster_id;
+      });
+
+      this.smbService.getCluster(this.cluster_id).subscribe((res: SMBCluster) => {
+        this.clusterResponse = res;
+
+        const customDnsList = this.clusterResponse.custom_dns;
+        const customDnsFormArray = this.smbForm.get('custom_dns') as FormArray;
+        const joinSourcesArray = this.smbForm.get('joinSources') as FormArray;
+        const pubAddresses = this.clusterResponse.public_addrs;
+        const publicAddrsFormArray = this.smbForm.get('public_addrs') as FormArray;
+
+        if (this.clusterResponse.clustering) {
+          const responseClustering = this.clusterResponse.clustering;
+          const upperFirstPipe = new UpperFirstPipe();
+          const upperCaseCluster = upperFirstPipe.transform(responseClustering);
+          this.smbForm.get('clustering').setValue(upperCaseCluster || '');
+        }
+        if (customDnsList?.length) {
+          customDnsList.forEach((dns: string) => {
+            customDnsFormArray.push(new FormControl(dns));
+          });
+        }
+        if (this.clusterResponse.auth_mode == AUTHMODE.activeDirectory) {
+          this.domainSettingsObject = this.clusterResponse?.domain_settings;
+          this.smbForm.get('domain_settings').setValue(this.domainSettingsObject.realm);
+        } else {
+          if (
+            this.clusterResponse.user_group_settings &&
+            this.clusterResponse.user_group_settings.length > 0
+          ) {
+            this.clusterResponse.user_group_settings.forEach((JoinSource: JoinSource) => {
+              joinSourcesArray.push(new FormControl(JoinSource.ref));
+            });
+            const joinSourceRef = this.clusterResponse.user_group_settings.map(
+              (JoinSource: JoinSource) => JoinSource.ref
+            );
+            joinSourcesArray.setValue(joinSourceRef);
+          }
+        }
+        this.smbForm.get('cluster_id').setValue(this.clusterResponse.cluster_id);
+        this.smbForm.get('auth_mode').setValue(this.clusterResponse.auth_mode);
+        if (this.clusterResponse.placement.count) {
+          this.smbForm.get('count').setValue(this.clusterResponse.placement.count);
+        }
+        if (pubAddresses?.length) {
+          pubAddresses.forEach((pubAddress: PublicAddress) => {
+            publicAddrsFormArray.push(
+              this.formBuilder.group({
+                address: [pubAddress.address, Validators.required],
+                destination: [pubAddress.destination || '']
+              })
+            );
+          });
+        }
+      });
+    } else {
+      this.action = this.actionLabels.CREATE;
+      this.hostsAndLabels$ = forkJoin({
+        hosts: this.hostService.getAllHosts(),
+        labels: this.hostService.getLabels()
+      }).pipe(
+        map(({ hosts, labels }) => ({
+          hosts: hosts.map((host: any) => ({ content: host['hostname'] })),
+          labels: labels.map((label: string) => ({ content: label }))
+        }))
+      );
+    }
     this.orchStatus$ = this.orchService.status();
     this.allClustering = Object.values(CLUSTERING);
-    this.onAuthModeChange();
+    this.loadingReady();
+    if (!this.isEdit) this.onAuthModeChange();
   }
 
   createForm() {
@@ -157,9 +243,17 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
     } else if (authMode === AUTHMODE.User) {
       const control = new FormControl(null, Validators.required);
       userGroupSettingsControl.push(control);
+      if (domainSettingsControl) {
+        domainSettingsControl.setValue('');
+        this.domainSettingsObject = { realm: '', join_sources: [] };
+      }
       domainSettingsControl.setErrors(null);
-      domainSettingsControl.clearValidators();
       userGroupSettingsControl.setValidators(Validators.required);
+
+      if (domainSettingsControl) {
+        domainSettingsControl.clearValidators();
+        domainSettingsControl.updateValueAndValidity();
+      }
     } else {
       if (userGroupSettingsControl) {
         userGroupSettingsControl.clearValidators();
@@ -173,27 +267,50 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
     const domainSettingsControl = this.smbForm.get('domain_settings');
     const authMode = this.smbForm.get('auth_mode').value;
 
+    const values = this.smbForm.getRawValue();
+    const serviceSpec: object = {
+      placement: {}
+    };
+    switch (values['placement']) {
+      case PLACEMENT.host:
+        if (values['hosts'].length > 0) {
+          serviceSpec['placement']['hosts'] = this.selectedHosts;
+        }
+        break;
+      case PLACEMENT.label:
+        serviceSpec['placement']['label'] = this.selectedLabels;
+        break;
+    }
+
     // Domain Setting should be mandatory if authMode is "Active Directory"
     if (authMode === AUTHMODE.activeDirectory && !domainSettingsControl.value) {
       domainSettingsControl.setErrors({ required: true });
       this.smbForm.markAllAsTouched();
       return;
     }
-    const component = this;
+    if (this.isEdit) {
+      this.handleTaskRequest(URLVerbs.EDIT);
+    } else {
+      this.handleTaskRequest(URLVerbs.CREATE);
+    }
+  }
+
+  handleTaskRequest(urlVerb: string) {
     const requestModel = this.buildRequest();
     const BASE_URL = 'smb/cluster';
+    const component = this;
     const cluster_id = this.smbForm.get('cluster_id').value;
-    const taskUrl = `${BASE_URL}/${URLVerbs.CREATE}`;
+
     this.taskWrapperService
       .wrapTaskAroundCall({
-        task: new FinishedTask(taskUrl, { cluster_id }),
+        task: new FinishedTask(`${BASE_URL}/${urlVerb}`, { cluster_id }),
         call: this.smbService.createCluster(requestModel)
       })
       .subscribe({
         complete: () => {
           this.router.navigate([`cephfs/smb`]);
         },
-        error() {
+        error: () => {
           component.smbForm.setErrors({ cdSubmitButton: true });
         }
       });
@@ -202,15 +319,18 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
   private buildRequest() {
     const values = this.smbForm.getRawValue();
     const rawFormValue = _.cloneDeep(this.smbForm.value);
+    const clusterId = this.smbForm.get('cluster_id')?.value;
+    const authMode = this.smbForm.get('auth_mode')?.value;
+
     const joinSources: JoinSource[] = (this.domainSettingsObject?.join_sources || [])
       .filter((source: { ref: string }) => source.ref)
       .map((source: { ref: string }) => ({
         ref: source.ref,
-        source_type: RESOURCE.Resource
+        sourceType: RESOURCE.Resource
       }));
 
     const joinSourceObj = joinSources.map((source: JoinSource) => ({
-      source_type: RESOURCE.Resource,
+      sourceType: RESOURCE.Resource,
       ref: source.ref
     }));
 
@@ -222,8 +342,8 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
     const requestModel: ClusterRequestModel = {
       cluster_resource: {
         resource_type: CLUSTER_RESOURCE,
-        cluster_id: rawFormValue.cluster_id,
-        auth_mode: rawFormValue.auth_mode
+        cluster_id: clusterId,
+        auth_mode: authMode
       }
     };
 
@@ -260,6 +380,10 @@ export class SmbClusterFormComponent extends CdForm implements OnInit {
       requestModel.cluster_resource.clustering = rawFormValue.clustering.toLowerCase();
     }
 
+    if (rawFormValue.placement.count) {
+      requestModel.cluster_resource.count = rawFormValue.placement.count;
+    }
+
     return requestModel;
   }
 
index cdbd2a5f9e26e35243f82075a0bcf315561807a5..d7a6dfcc687b065e44cb6eba67f17f2ba13edf48 100644 (file)
@@ -14,11 +14,11 @@ import { Permission } from '~/app/shared/models/permissions';
 
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { SmbService } from '~/app/shared/api/smb.service';
-
+import { SMBCluster } from '../smb.model';
 import { Icons } from '~/app/shared/enum/icons.enum';
 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
 import { URLBuilderService } from '~/app/shared/services/url-builder.service';
-import { SMBCluster } from '../smb.model';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
@@ -38,9 +38,10 @@ export class SmbClusterListComponent extends ListWithDetails implements OnInit {
   permission: Permission;
   tableActions: CdTableAction[];
   context: CdTableFetchDataContext;
-  selection = new CdTableSelection();
   smbClusters$: Observable<SMBCluster[]>;
   subject$ = new BehaviorSubject<SMBCluster[]>([]);
+  selection = new CdTableSelection();
+  modalRef: NgbModalRef;
 
   constructor(
     private authStorageService: AuthStorageService,
@@ -73,9 +74,15 @@ export class SmbClusterListComponent extends ListWithDetails implements OnInit {
         permission: 'create',
         icon: Icons.add,
         routerLink: () => this.urlBuilder.getCreate(),
-
         canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection
       },
+      {
+        name: this.actionLabels.EDIT,
+        permission: 'update',
+        icon: Icons.edit,
+        routerLink: () =>
+          this.selection.first() && this.urlBuilder.getEdit(this.selection.first().cluster_id)
+      },
       {
         permission: 'delete',
         icon: Icons.destroy,
@@ -95,7 +102,6 @@ export class SmbClusterListComponent extends ListWithDetails implements OnInit {
       )
     );
   }
-
   loadSMBCluster() {
     this.subject$.next([]);
   }
index 58eb555b55a12fb08128596571412a0b2c5cadc3..83101fdbb7839ae029e3537dc355def72461e0fd 100644 (file)
@@ -8,8 +8,9 @@ export interface SMBCluster {
   user_group_settings?: JoinSource[];
   custom_dns?: string[];
   placement?: CephServicePlacement;
-  clustering?: typeof CLUSTERING;
+  clustering?: Clustering;
   public_addrs?: PublicAddress[];
+  count?: number;
 }
 
 export interface ClusterRequestModel {
@@ -45,7 +46,7 @@ export interface DomainSettings {
 }
 
 export interface JoinSource {
-  source_type?: string;
+  sourceType: string;
   ref: string;
 }
 
@@ -143,3 +144,5 @@ export const USERSGROUPS_RESOURCE = 'ceph.smb.usersgroups' as const;
 export const PROVIDER = 'samba-vfs';
 
 export const SHARE_URL = '/cephfs/smb/share/';
+
+type Clustering = 'default' | 'never' | 'always';
index a263a11b94d0dadb9f2f7b8c55b8c554f25dbdc9..16e06fdf409382812eb9c42544cb5b6dd5ddc9b6 100644 (file)
@@ -40,11 +40,12 @@ describe('SmbService', () => {
         resource_type: CLUSTER_RESOURCE,
         cluster_id: 'clusterUserTest',
         auth_mode: 'active-directory',
+        intent: 'present',
         domain_settings: {
           realm: 'DOMAIN1.SINK.TEST',
           join_sources: [
             {
-              source_type: 'resource',
+              sourceType: 'resource',
               ref: 'join1-admin'
             }
           ]
index 960886ef55c807a6d74983e8aab7b3e79245c738..bf4c64a37b05c1e24b7577d04ab414eb0bee5d9c 100644 (file)
@@ -97,4 +97,8 @@ export class SmbService {
       observe: 'response'
     });
   }
+
+  getCluster(cluster_id: string) {
+    return this.http.get(`${this.baseURL}/cluster/${cluster_id}`);
+  }
 }
index 6fb5a114821858d95258c0a59181c1de45b40152..27c469fe647d4425ab20869baa7661b1a2777373 100644 (file)
@@ -528,6 +528,9 @@ export class TaskMessageService {
     ),
     'cephfs/smb/standalone/create': this.newTaskMessage(this.commonOperations.create, (metadata) =>
       this.smbUsersgroups(metadata)
+    ),
+    'smb/cluster/edit': this.newTaskMessage(this.commonOperations.update, (metadata) =>
+      this.smbCluster(metadata)
     )
   };
 
index 2033da0c1ded663bcb7896c40bbb0551f5baa32f..73af08d45bb987f88d04fbed5a713459266c44df 100755 (executable)
@@ -15036,6 +15036,23 @@ paths:
                       required:
                       - count
                       type: object
+                    public_addrs:
+                      description: Public Address
+                      items:
+                        properties:
+                          address:
+                            description: This address will be assigned to one of the
+                              host's network devices
+                            type: string
+                          destination:
+                            description: Defines where the system will assign the
+                              managed IPs.
+                            type: string
+                        required:
+                        - address
+                        - destination
+                        type: object
+                      type: array
                     resource_type:
                       description: ceph.smb.cluster
                       type: string
@@ -15063,6 +15080,7 @@ paths:
                 - domain_settings
                 - user_group_settings
                 - custom_dns
+                - public_addrs
                 - placement
                 type: array
           description: OK
@@ -15161,6 +15179,23 @@ paths:
                               required:
                               - count
                               type: object
+                            public_addrs:
+                              description: Public Address
+                              items:
+                                properties:
+                                  address:
+                                    description: This address will be assigned to
+                                      one of the host's network devices
+                                    type: string
+                                  destination:
+                                    description: Defines where the system will assign
+                                      the managed IPs.
+                                    type: string
+                                required:
+                                - address
+                                - destination
+                                type: object
+                              type: array
                             resource_type:
                               description: ceph.smb.cluster
                               type: string
@@ -15188,6 +15223,7 @@ paths:
                           - domain_settings
                           - user_group_settings
                           - custom_dns
+                          - public_addrs
                           - placement
                           type: object
                         state:
@@ -15334,6 +15370,23 @@ paths:
                     required:
                     - count
                     type: object
+                  public_addrs:
+                    description: Public Address
+                    items:
+                      properties:
+                        address:
+                          description: This address will be assigned to one of the
+                            host's network devices
+                          type: string
+                        destination:
+                          description: Defines where the system will assign the managed
+                            IPs.
+                          type: string
+                      required:
+                      - address
+                      - destination
+                      type: object
+                    type: array
                   resource_type:
                     description: ceph.smb.cluster
                     type: string
@@ -15360,6 +15413,7 @@ paths:
                 - domain_settings
                 - user_group_settings
                 - custom_dns
+                - public_addrs
                 - placement
                 type: object
           description: OK