]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-form/pool-form.component.ts
import 15.2.4
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / pool / pool-form / pool-form.component.ts
index a98b8c5877159533c3bc05f125f42c850fb9e0f9..47519c0a0a34007aab3c4d81e6f6844676337631 100644 (file)
@@ -1,16 +1,21 @@
-import { Component, EventEmitter, OnInit } from '@angular/core';
+import { Component, EventEmitter, OnInit, Type, ViewChild } from '@angular/core';
 import { FormControl, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 
 import { I18n } from '@ngx-translate/i18n-polyfill';
 import * as _ from 'lodash';
 import { BsModalService } from 'ngx-bootstrap/modal';
-import { forkJoin, Subscription } from 'rxjs';
+import { TabsetComponent } from 'ngx-bootstrap/tabs';
+import { TooltipDirective } from 'ngx-bootstrap/tooltip';
+import { Observable, Subscription } from 'rxjs';
 
+import { CrushRuleService } from '../../../shared/api/crush-rule.service';
 import { ErasureCodeProfileService } from '../../../shared/api/erasure-code-profile.service';
 import { PoolService } from '../../../shared/api/pool.service';
 import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
+import { SelectOption } from '../../../shared/components/select/select-option.model';
 import { ActionLabelsI18n, URLVerbs } from '../../../shared/constants/app.constants';
+import { Icons } from '../../../shared/enum/icons.enum';
 import { CdFormGroup } from '../../../shared/forms/cd-form-group';
 import { CdValidators } from '../../../shared/forms/cd-validators';
 import {
@@ -27,7 +32,8 @@ import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { FormatterService } from '../../../shared/services/formatter.service';
 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
-import { ErasureCodeProfileFormComponent } from '../erasure-code-profile-form/erasure-code-profile-form.component';
+import { CrushRuleFormModalComponent } from '../crush-rule-form-modal/crush-rule-form-modal.component';
+import { ErasureCodeProfileFormModalComponent } from '../erasure-code-profile-form/erasure-code-profile-form-modal.component';
 import { Pool } from '../pool';
 import { PoolFormData } from './pool-form-data';
 
@@ -46,16 +52,22 @@ interface FormFieldDescription {
   styleUrls: ['./pool-form.component.scss']
 })
 export class PoolFormComponent implements OnInit {
+  @ViewChild('crushInfoTabs', { static: false }) crushInfoTabs: TabsetComponent;
+  @ViewChild('crushDeletionBtn', { static: false }) crushDeletionBtn: TooltipDirective;
+  @ViewChild('ecpInfoTabs', { static: false }) ecpInfoTabs: TabsetComponent;
+  @ViewChild('ecpDeletionBtn', { static: false }) ecpDeletionBtn: TooltipDirective;
+
   permission: Permission;
   form: CdFormGroup;
   ecProfiles: ErasureCodeProfile[];
   info: PoolFormInfo;
   routeParamsSubscribe: any;
   editing = false;
+  isReplicated = false;
+  isErasure = false;
   data = new PoolFormData(this.i18n);
   externalPgChange = false;
-  private modalSubscription: Subscription;
-  current = {
+  current: Record<string, any> = {
     rules: []
   };
   initializeConfigData = new EventEmitter<{
@@ -65,6 +77,12 @@ export class PoolFormComponent implements OnInit {
   currentConfigurationValues: { [configKey: string]: any } = {};
   action: string;
   resource: string;
+  icons = Icons;
+  pgAutoscaleModes: string[];
+  crushUsage: string[] = undefined; // Will only be set if a rule is used by some pool
+  ecpUsage: string[] = undefined; // Will only be set if a rule is used by some pool
+
+  private modalSubscription: Subscription;
 
   constructor(
     private dimlessBinaryPipe: DimlessBinaryPipe,
@@ -77,6 +95,7 @@ export class PoolFormComponent implements OnInit {
     private bsModalService: BsModalService,
     private taskWrapper: TaskWrapperService,
     private ecpService: ErasureCodeProfileService,
+    private crushRuleService: CrushRuleService,
     private i18n: I18n,
     public actionLabels: ActionLabelsI18n
   ) {
@@ -91,7 +110,8 @@ export class PoolFormComponent implements OnInit {
     this.permission = this.authStorageService.getPermissions().pool;
     if (
       !this.permission.read ||
-      ((!this.permission.update && this.editing) || (!this.permission.create && !this.editing))
+      (!this.permission.update && this.editing) ||
+      (!this.permission.create && !this.editing)
     ) {
       this.router.navigate(['/404']);
     }
@@ -115,7 +135,18 @@ export class PoolFormComponent implements OnInit {
     this.form = new CdFormGroup(
       {
         name: new FormControl('', {
-          validators: [Validators.pattern(/^[.A-Za-z0-9_/-]+$/), Validators.required]
+          validators: [
+            Validators.pattern(/^[.A-Za-z0-9_/-]+$/),
+            Validators.required,
+            CdValidators.custom('rbdPool', () => {
+              return (
+                this.form &&
+                this.form.getValue('name').includes('/') &&
+                this.data &&
+                this.data.applications.selected.indexOf('rbd') !== -1
+              );
+            })
+          ]
         }),
         poolType: new FormControl('', {
           validators: [Validators.required]
@@ -124,7 +155,12 @@ export class PoolFormComponent implements OnInit {
           validators: [
             CdValidators.custom(
               'tooFewOsds',
-              (rule) => this.info && rule && this.info.osd_count < rule.min_size
+              (rule: any) => this.info && rule && this.info.osd_count < rule.min_size
+            ),
+            CdValidators.custom(
+              'required',
+              (rule: CrushRule) =>
+                this.isReplicated && this.info.crush_rules_replicated.length > 0 && !rule
             )
           ]
         }),
@@ -135,54 +171,67 @@ export class PoolFormComponent implements OnInit {
         pgNum: new FormControl('', {
           validators: [Validators.required, Validators.min(1)]
         }),
+        pgAutoscaleMode: new FormControl(null),
         ecOverwrites: new FormControl(false),
-        compression: compressionForm
-      },
-      [
-        CdValidators.custom('form', () => null),
-        CdValidators.custom('rbdPool', () => {
-          return (
-            this.form &&
-            this.form.getValue('name').includes('/') &&
-            this.data &&
-            this.data.applications.selected.indexOf('rbd') !== -1
-          );
+        compression: compressionForm,
+        max_bytes: new FormControl(''),
+        max_objects: new FormControl(0, {
+          validators: [Validators.min(0)]
         })
-      ]
+      },
+      [CdValidators.custom('form', (): null => null)]
     );
   }
 
   ngOnInit() {
-    forkJoin(this.poolService.getInfo(), this.ecpService.list()).subscribe(
-      (data: [PoolFormInfo, ErasureCodeProfile[]]) => {
-        this.initInfo(data[0]);
-        this.initEcp(data[1]);
-        if (this.editing) {
-          this.initEditMode();
-        }
-        this.listenToChanges();
-        this.setComplexValidators();
+    this.poolService.getInfo().subscribe((info: PoolFormInfo) => {
+      this.initInfo(info);
+      if (this.editing) {
+        this.initEditMode();
+      } else {
+        this.setAvailableApps();
       }
-    );
+      this.listenToChanges();
+      this.setComplexValidators();
+    });
   }
 
   private initInfo(info: PoolFormInfo) {
+    this.pgAutoscaleModes = info.pg_autoscale_modes;
+    this.form.silentSet('pgAutoscaleMode', info.pg_autoscale_default_mode);
     this.form.silentSet('algorithm', info.bluestore_compression_algorithm);
-    info.compression_modes.push('unset');
     this.info = info;
+    this.initEcp(info.erasure_code_profiles);
   }
 
   private initEcp(ecProfiles: ErasureCodeProfile[]) {
-    const control = this.form.get('erasureProfile');
-    if (ecProfiles.length <= 1) {
-      control.disable();
+    this.setListControlStatus('erasureProfile', ecProfiles);
+    this.ecProfiles = ecProfiles;
+  }
+
+  /**
+   * Used to update the crush rule or erasure code profile listings.
+   *
+   * If only one rule or profile exists it will be selected.
+   * If nothing exists null will be selected.
+   * If more than one rule or profile exists the listing will be enabled,
+   * otherwise disabled.
+   */
+  private setListControlStatus(controlName: string, arr: any[]) {
+    const control = this.form.get(controlName);
+    const value = control.value;
+    if (arr.length === 1 && (!value || !_.isEqual(value, arr[0]))) {
+      control.setValue(arr[0]);
+    } else if (arr.length === 0 && value) {
+      control.setValue(null);
     }
-    if (ecProfiles.length === 1) {
-      control.setValue(ecProfiles[0]);
-    } else if (ecProfiles.length > 1 && control.disabled) {
+    if (arr.length <= 1) {
+      if (control.enabled) {
+        control.disable();
+      }
+    } else if (control.disabled) {
       control.enable();
     }
-    this.ecProfiles = ecProfiles;
   }
 
   private initEditMode() {
@@ -206,24 +255,25 @@ export class PoolFormComponent implements OnInit {
       initialData: pool.configuration,
       sourceType: RbdConfigurationSourceField.pool
     });
-
+    this.poolTypeChange(pool.type);
+    const rules = this.info.crush_rules_replicated.concat(this.info.crush_rules_erasure);
     const dataMap = {
       name: pool.pool_name,
       poolType: pool.type,
-      crushRule: this.info['crush_rules_' + pool.type].find(
-        (rule: CrushRule) => rule.rule_name === pool.crush_rule
-      ),
+      crushRule: rules.find((rule: CrushRule) => rule.rule_name === pool.crush_rule),
       size: pool.size,
       erasureProfile: this.ecProfiles.find((ecp) => ecp.name === pool.erasure_code_profile),
+      pgAutoscaleMode: pool.pg_autoscale_mode,
       pgNum: pool.pg_num,
       ecOverwrites: pool.flags_names.includes('ec_overwrites'),
       mode: pool.options.compression_mode,
       algorithm: pool.options.compression_algorithm,
       minBlobSize: this.dimlessBinaryPipe.transform(pool.options.compression_min_blob_size),
       maxBlobSize: this.dimlessBinaryPipe.transform(pool.options.compression_max_blob_size),
-      ratio: pool.options.compression_required_ratio
+      ratio: pool.options.compression_required_ratio,
+      max_bytes: this.dimlessBinaryPipe.transform(pool.quota_max_bytes),
+      max_objects: pool.quota_max_objects
     };
-
     Object.keys(dataMap).forEach((controlName: string) => {
       const value = dataMap[controlName];
       if (!_.isUndefined(value) && value !== '') {
@@ -231,9 +281,16 @@ export class PoolFormComponent implements OnInit {
       }
     });
     this.data.pgs = this.form.getValue('pgNum');
+    this.setAvailableApps(this.data.applications.default.concat(pool.application_metadata));
     this.data.applications.selected = pool.application_metadata;
   }
 
+  private setAvailableApps(apps: string[] = this.data.applications.default) {
+    this.data.applications.available = _.uniq(apps.sort()).map(
+      (x: string) => new SelectOption(false, x, '')
+    );
+  }
+
   private listenToChanges() {
     this.listenToChangesDuringAddEdit();
     if (!this.editing) {
@@ -269,23 +326,33 @@ export class PoolFormComponent implements OnInit {
 
   private listenToChangesDuringAdd() {
     this.form.get('poolType').valueChanges.subscribe((poolType) => {
-      this.form.get('size').updateValueAndValidity();
-      this.rulesChange();
-      if (poolType === 'replicated') {
-        this.replicatedRuleChange();
-      }
-      this.pgCalc();
+      this.poolTypeChange(poolType);
     });
-    this.form.get('crushRule').valueChanges.subscribe(() => {
-      if (this.form.getValue('poolType') === 'replicated') {
-        this.replicatedRuleChange();
+    this.form.get('crushRule').valueChanges.subscribe((rule) => {
+      // The crush rule can only be changed if type 'replicated' is set.
+      if (this.crushDeletionBtn && this.crushDeletionBtn.isOpen) {
+        this.crushDeletionBtn.hide();
       }
+      if (!rule) {
+        return;
+      }
+      this.crushRuleIsUsedBy(rule.rule_name);
+      this.replicatedRuleChange();
       this.pgCalc();
     });
     this.form.get('size').valueChanges.subscribe(() => {
+      // The size can only be changed if type 'replicated' is set.
       this.pgCalc();
     });
-    this.form.get('erasureProfile').valueChanges.subscribe(() => {
+    this.form.get('erasureProfile').valueChanges.subscribe((profile) => {
+      // The ec profile can only be changed if type 'erasure' is set.
+      if (this.ecpDeletionBtn && this.ecpDeletionBtn.isOpen) {
+        this.ecpDeletionBtn.hide();
+      }
+      if (!profile) {
+        return;
+      }
+      this.ecpIsUsedBy(profile.name);
       this.pgCalc();
     });
     this.form.get('mode').valueChanges.subscribe(() => {
@@ -301,26 +368,37 @@ export class PoolFormComponent implements OnInit {
     });
   }
 
-  private rulesChange() {
-    const poolType = this.form.getValue('poolType');
+  private poolTypeChange(poolType: string) {
+    if (poolType === 'replicated') {
+      this.setTypeBooleans(true, false);
+    } else if (poolType === 'erasure') {
+      this.setTypeBooleans(false, true);
+    } else {
+      this.setTypeBooleans(false, false);
+    }
     if (!poolType || !this.info) {
       this.current.rules = [];
       return;
     }
     const rules = this.info['crush_rules_' + poolType] || [];
-    const control = this.form.get('crushRule');
-    if (rules.length === 1) {
-      control.setValue(rules[0]);
-      control.disable();
-    } else {
-      control.setValue(null);
-      control.enable();
-    }
     this.current.rules = rules;
+    if (this.editing) {
+      return;
+    }
+    if (this.isReplicated) {
+      this.setListControlStatus('crushRule', rules);
+    }
+    this.replicatedRuleChange();
+    this.pgCalc();
+  }
+
+  private setTypeBooleans(replicated: boolean, erasure: boolean) {
+    this.isReplicated = replicated;
+    this.isErasure = erasure;
   }
 
   private replicatedRuleChange() {
-    if (this.form.getValue('poolType') !== 'replicated') {
+    if (!this.isReplicated) {
       return;
     }
     const control = this.form.get('size');
@@ -339,7 +417,7 @@ export class PoolFormComponent implements OnInit {
 
   getMinSize(): number {
     if (!this.info || this.info.osd_count < 1) {
-      return;
+      return 0;
     }
     const rule = this.form.getValue('crushRule');
     if (rule) {
@@ -350,7 +428,7 @@ export class PoolFormComponent implements OnInit {
 
   getMaxSize(): number {
     if (!this.info || this.info.osd_count < 1) {
-      return;
+      return 0;
     }
     const osds: number = this.info.osd_count;
     if (this.form.getValue('crushRule')) {
@@ -368,8 +446,7 @@ export class PoolFormComponent implements OnInit {
       return;
     }
     const pgMax = this.info.osd_count * 100;
-    const pgs =
-      poolType === 'replicated' ? this.replicatedPgCalc(pgMax) : this.erasurePgCalc(pgMax);
+    const pgs = this.isReplicated ? this.replicatedPgCalc(pgMax) : this.erasurePgCalc(pgMax);
     if (!pgs) {
       return;
     }
@@ -381,20 +458,16 @@ export class PoolFormComponent implements OnInit {
     }
   }
 
-  private replicatedPgCalc(pgs): number {
+  private replicatedPgCalc(pgs: number): number {
     const sizeControl = this.form.get('size');
     const size = sizeControl.value;
-    if (sizeControl.valid && size > 0) {
-      return pgs / size;
-    }
+    return sizeControl.valid && size > 0 ? pgs / size : 0;
   }
 
-  private erasurePgCalc(pgs): number {
+  private erasurePgCalc(pgs: number): number {
     const ecpControl = this.form.get('erasureProfile');
     const ecp = ecpControl.value;
-    if ((ecpControl.valid || ecpControl.disabled) && ecp) {
-      return pgs / (ecp.k + ecp.m);
-    }
+    return (ecpControl.valid || ecpControl.disabled) && ecp ? pgs / (ecp.k + ecp.m) : 0;
   }
 
   private alignPgs(pgs = this.form.getValue('pgNum')) {
@@ -409,7 +482,7 @@ export class PoolFormComponent implements OnInit {
           this.form.get('name').validator,
           CdValidators.custom(
             'uniqueName',
-            (name) =>
+            (name: string) =>
               this.data.pool &&
               this.info &&
               this.info.pool_names.indexOf(name) !== -1 &&
@@ -418,27 +491,23 @@ export class PoolFormComponent implements OnInit {
           )
         ]);
     } else {
-      CdValidators.validateIf(
-        this.form.get('size'),
-        () => this.form.get('poolType').value === 'replicated',
-        [
-          CdValidators.custom(
-            'min',
-            (value) => this.form.getValue('size') && value < this.getMinSize()
-          ),
-          CdValidators.custom(
-            'max',
-            (value) => this.form.getValue('size') && this.getMaxSize() < value
-          )
-        ]
-      );
+      CdValidators.validateIf(this.form.get('size'), () => this.isReplicated, [
+        CdValidators.custom(
+          'min',
+          (value: number) => this.form.getValue('size') && value < this.getMinSize()
+        ),
+        CdValidators.custom(
+          'max',
+          (value: number) => this.form.getValue('size') && this.getMaxSize() < value
+        )
+      ]);
       this.form
         .get('name')
         .setValidators([
           this.form.get('name').validator,
           CdValidators.custom(
             'uniqueName',
-            (name) => this.info && this.info.pool_names.indexOf(name) !== -1
+            (name: string) => this.info && this.info.pool_names.indexOf(name) !== -1
           )
         ]);
     }
@@ -448,13 +517,13 @@ export class PoolFormComponent implements OnInit {
   private setCompressionValidators() {
     CdValidators.validateIf(this.form.get('minBlobSize'), () => this.hasCompressionEnabled(), [
       Validators.min(0),
-      CdValidators.custom('maximum', (size) =>
+      CdValidators.custom('maximum', (size: string) =>
         this.oddBlobSize(size, this.form.getValue('maxBlobSize'))
       )
     ]);
     CdValidators.validateIf(this.form.get('maxBlobSize'), () => this.hasCompressionEnabled(), [
       Validators.min(0),
-      CdValidators.custom('minimum', (size) =>
+      CdValidators.custom('minimum', (size: string) =>
         this.oddBlobSize(this.form.getValue('minBlobSize'), size)
       )
     ]);
@@ -464,10 +533,10 @@ export class PoolFormComponent implements OnInit {
     ]);
   }
 
-  private oddBlobSize(minimum, maximum) {
-    minimum = this.formatter.toBytes(minimum);
-    maximum = this.formatter.toBytes(maximum);
-    return Boolean(minimum && maximum && minimum >= maximum);
+  private oddBlobSize(minimum: string, maximum: string) {
+    const min = this.formatter.toBytes(minimum);
+    const max = this.formatter.toBytes(maximum);
+    return Boolean(min && max && min >= max);
   }
 
   hasCompressionEnabled() {
@@ -483,35 +552,175 @@ export class PoolFormComponent implements OnInit {
   }
 
   addErasureCodeProfile() {
-    this.modalSubscription = this.modalService.onHide.subscribe(() => this.reloadECPs());
-    this.bsModalService.show(ErasureCodeProfileFormComponent);
+    this.addModal(ErasureCodeProfileFormModalComponent, (name) => this.reloadECPs(name));
+  }
+
+  private addModal(modalComponent: Type<any>, reload: (name: string) => void) {
+    this.hideOpenTooltips();
+    const modalRef = this.bsModalService.show(modalComponent);
+    modalRef.content.submitAction.subscribe((item: any) => {
+      reload(item.name);
+    });
   }
 
-  private reloadECPs() {
-    this.ecpService.list().subscribe((profiles: ErasureCodeProfile[]) => this.initEcp(profiles));
-    this.modalSubscription.unsubscribe();
+  private hideOpenTooltips() {
+    const hideTooltip = (btn: TooltipDirective) => btn && btn.isOpen && btn.hide();
+    hideTooltip(this.ecpDeletionBtn);
+    hideTooltip(this.crushDeletionBtn);
+  }
+
+  private reloadECPs(profileName?: string) {
+    this.reloadList({
+      newItemName: profileName,
+      getInfo: () => this.ecpService.list(),
+      initInfo: (profiles) => this.initEcp(profiles),
+      findNewItem: () => this.ecProfiles.find((p) => p.name === profileName),
+      controlName: 'erasureProfile'
+    });
+  }
+
+  private reloadList({
+    newItemName,
+    getInfo,
+    initInfo,
+    findNewItem,
+    controlName
+  }: {
+    newItemName: string;
+    getInfo: () => Observable<any>;
+    initInfo: (items: any) => void;
+    findNewItem: () => any;
+    controlName: string;
+  }) {
+    if (this.modalSubscription) {
+      this.modalSubscription.unsubscribe();
+    }
+    getInfo().subscribe((items: any) => {
+      initInfo(items);
+      if (!newItemName) {
+        return;
+      }
+      const item = findNewItem();
+      if (item) {
+        this.form.get(controlName).setValue(item);
+      }
+    });
   }
 
   deleteErasureCodeProfile() {
-    const ecp = this.form.getValue('erasureProfile');
-    if (!ecp) {
+    this.deletionModal({
+      value: this.form.getValue('erasureProfile'),
+      usage: this.ecpUsage,
+      deletionBtn: this.ecpDeletionBtn,
+      dataName: 'erasureInfo',
+      getTabs: () => this.ecpInfoTabs,
+      tabPosition: 1,
+      nameAttribute: 'name',
+      itemDescription: this.i18n('erasure code profile'),
+      reloadFn: () => this.reloadECPs(),
+      deleteFn: (name) => this.ecpService.delete(name),
+      taskName: 'ecp/delete'
+    });
+  }
+
+  private deletionModal({
+    value,
+    usage,
+    deletionBtn,
+    dataName,
+    getTabs,
+    tabPosition,
+    nameAttribute,
+    itemDescription,
+    reloadFn,
+    deleteFn,
+    taskName
+  }: {
+    value: any;
+    usage: string[];
+    deletionBtn: TooltipDirective;
+    dataName: string;
+    getTabs: () => TabsetComponent;
+    tabPosition: number;
+    nameAttribute: string;
+    itemDescription: string;
+    reloadFn: Function;
+    deleteFn: (name: string) => Observable<any>;
+    taskName: string;
+  }) {
+    if (!value) {
+      return;
+    }
+    if (usage) {
+      deletionBtn.toggle();
+      this.data[dataName] = true;
+      setTimeout(() => {
+        const tabs = getTabs();
+        if (tabs) {
+          tabs.tabs[tabPosition].active = true;
+        }
+      }, 50);
       return;
     }
-    const name = ecp.name;
-    this.modalSubscription = this.modalService.onHide.subscribe(() => this.reloadECPs());
+    const name = value[nameAttribute];
     this.modalService.show(CriticalConfirmationModalComponent, {
       initialState: {
-        itemDescription: this.i18n('erasure code profile'),
+        itemDescription,
         itemNames: [name],
-        submitActionObservable: () =>
-          this.taskWrapper.wrapTaskAroundCall({
-            task: new FinishedTask('ecp/delete', { name: name }),
-            call: this.ecpService.delete(name)
-          })
+        submitActionObservable: () => {
+          const deletion = deleteFn(name);
+          deletion.subscribe(() => reloadFn());
+          return this.taskWrapper.wrapTaskAroundCall({
+            task: new FinishedTask(taskName, { name: name }),
+            call: deletion
+          });
+        }
       }
     });
   }
 
+  addCrushRule() {
+    this.addModal(CrushRuleFormModalComponent, (name) => this.reloadCrushRules(name));
+  }
+
+  private reloadCrushRules(ruleName?: string) {
+    this.reloadList({
+      newItemName: ruleName,
+      getInfo: () => this.poolService.getInfo(),
+      initInfo: (info) => {
+        this.initInfo(info);
+        this.poolTypeChange('replicated');
+      },
+      findNewItem: () =>
+        this.info.crush_rules_replicated.find((rule) => rule.rule_name === ruleName),
+      controlName: 'crushRule'
+    });
+  }
+
+  deleteCrushRule() {
+    this.deletionModal({
+      value: this.form.getValue('crushRule'),
+      usage: this.crushUsage,
+      deletionBtn: this.crushDeletionBtn,
+      dataName: 'crushInfo',
+      getTabs: () => this.crushInfoTabs,
+      tabPosition: 2,
+      nameAttribute: 'rule_name',
+      itemDescription: this.i18n('crush rule'),
+      reloadFn: () => this.reloadCrushRules(),
+      deleteFn: (name) => this.crushRuleService.delete(name),
+      taskName: 'crushRule/delete'
+    });
+  }
+
+  crushRuleIsUsedBy(ruleName: string) {
+    this.crushUsage = ruleName ? this.info.used_rules[ruleName] : undefined;
+  }
+
+  ecpIsUsedBy(profileName: string) {
+    this.ecpUsage = profileName ? this.info.used_profiles[profileName] : undefined;
+  }
+
   submit() {
     if (this.form.invalid) {
       this.form.setErrors({ cdSubmitButton: true });
@@ -522,22 +731,49 @@ export class PoolFormComponent implements OnInit {
 
     this.assignFormFields(pool, [
       { externalFieldName: 'pool_type', formControlName: 'poolType' },
-      { externalFieldName: 'pg_num', formControlName: 'pgNum', editable: true },
-      this.form.getValue('poolType') === 'replicated'
+      {
+        externalFieldName: 'pg_autoscale_mode',
+        formControlName: 'pgAutoscaleMode',
+        editable: true
+      },
+      {
+        externalFieldName: 'pg_num',
+        formControlName: 'pgNum',
+        replaceFn: (value: number) => (this.form.getValue('pgAutoscaleMode') === 'on' ? 1 : value),
+        editable: true
+      },
+      this.isReplicated
         ? { externalFieldName: 'size', formControlName: 'size' }
         : {
             externalFieldName: 'erasure_code_profile',
             formControlName: 'erasureProfile',
             attr: 'name'
           },
-      { externalFieldName: 'rule_name', formControlName: 'crushRule', attr: 'rule_name' }
+      {
+        externalFieldName: 'rule_name',
+        formControlName: 'crushRule',
+        replaceFn: (value: CrushRule) => (this.isReplicated ? value && value.rule_name : undefined)
+      },
+      {
+        externalFieldName: 'quota_max_bytes',
+        formControlName: 'max_bytes',
+        replaceFn: this.formatter.toBytes,
+        editable: true,
+        resetValue: this.editing ? 0 : undefined
+      },
+      {
+        externalFieldName: 'quota_max_objects',
+        formControlName: 'max_objects',
+        editable: true,
+        resetValue: this.editing ? 0 : undefined
+      }
     ]);
 
     if (this.info.is_all_bluestore) {
       this.assignFormField(pool, {
         externalFieldName: 'flags',
         formControlName: 'ecOverwrites',
-        replaceFn: () => ['ec_overwrites']
+        replaceFn: () => (this.isErasure ? ['ec_overwrites'] : undefined)
       });
 
       if (this.form.getValue('mode') !== 'none') {
@@ -546,7 +782,7 @@ export class PoolFormComponent implements OnInit {
             externalFieldName: 'compression_mode',
             formControlName: 'mode',
             editable: true,
-            replaceFn: (value) => this.hasCompressionEnabled() && value
+            replaceFn: (value: boolean) => this.hasCompressionEnabled() && value
           },
           {
             externalFieldName: 'compression_algorithm',
@@ -580,7 +816,7 @@ export class PoolFormComponent implements OnInit {
             externalFieldName: 'compression_mode',
             formControlName: 'mode',
             editable: true,
-            replaceFn: () => 'unset'
+            replaceFn: () => 'unset' // Is used if no compression is set
           },
           {
             externalFieldName: 'srcpool',
@@ -599,10 +835,7 @@ export class PoolFormComponent implements OnInit {
 
     // Only collect configuration data for replicated pools, as QoS cannot be configured on EC
     // pools. EC data pools inherit their settings from the corresponding replicated metadata pool.
-    if (
-      this.form.get('poolType').value === 'replicated' &&
-      !_.isEmpty(this.currentConfigurationValues)
-    ) {
+    if (this.isReplicated && !_.isEmpty(this.currentConfigurationValues)) {
       pool['configuration'] = this.currentConfigurationValues;
     }
 
@@ -649,7 +882,7 @@ export class PoolFormComponent implements OnInit {
     pool[externalFieldName] = apiValue;
   }
 
-  private triggerApiTask(pool) {
+  private triggerApiTask(pool: Record<string, any>) {
     this.taskWrapper
       .wrapTaskAroundCall({
         task: new FinishedTask('pool/' + (this.editing ? URLVerbs.EDIT : URLVerbs.CREATE), {
@@ -670,6 +903,6 @@ export class PoolFormComponent implements OnInit {
   }
 
   appSelection() {
-    this.form.updateValueAndValidity({ emitEvent: false, onlySelf: true });
+    this.form.get('name').updateValueAndValidity({ emitEvent: false, onlySelf: true });
   }
 }