]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fixing manual deployment option for osd creation flow 69302/head
authorDevika Babrekar <devika.babrekar@ibm.com>
Wed, 8 Jul 2026 13:10:12 +0000 (18:40 +0530)
committerDevika Babrekar <devika.babrekar@ibm.com>
Wed, 8 Jul 2026 14:41:27 +0000 (20:11 +0530)
Signed-off-by: Devika Babrekar <devika.babrekar@ibm.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/inventory/inventory-devices/inventory-devices.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-devices-selection-groups/osd-devices-selection-groups.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts

index c52784b5155a8a5908793b39ff9854305d8c52b0..eafd0baaf3d5cb65bf13575ca690f530dd4dd3e5 100644 (file)
@@ -106,13 +106,13 @@ export class InventoryDevicesComponent implements OnInit, OnDestroy {
     ];
     const columns = [
       {
-        name: $localize`Hostname`,
-        prop: 'hostname',
+        name: $localize`Device path`,
+        prop: 'path',
         flexGrow: 1
       },
       {
-        name: $localize`Device path`,
-        prop: 'path',
+        name: $localize`Hostname`,
+        prop: 'hostname',
         flexGrow: 1
       },
       {
index 5e0b0c90cc2c0c89aed63ff97ab682a2af41e0c2..08ccc03299654327f44b149da2d3714ef0c4329b 100644 (file)
     </cd-helper>
   </label>
   <div class="cd-col-form-input">
-    @if (devices.length === 0 && inlineSelection) {
-    @if (!canSelect) {
+    @if (inlineSelection) {
+    @if (showAddPrimaryFirstAlert) {
     <cd-alert-panel type="info"
                     size="slim"
                     [showTitle]="false">
       <ng-container i18n>{{ tooltips.addPrimaryFirst }}</ng-container>
     </cd-alert-panel>
     }
-    @if (canSelect) {
-    @if (availDevices.length === 0) {
+    @if (showNoAvailDevicesAlert) {
     <cd-alert-panel type="warning"
                     size="slim"
                     [showTitle]="false">
       <ng-container i18n>No available devices</ng-container>
     </cd-alert-panel>
     }
-    @if (availDevices.length > 0 && !canInlineSubmit) {
+    @if (showFilterSection) {
+    @if (!canInlineSubmit) {
     <cd-alert-panel type="warning"
                     size="slim"
                     [showTitle]="false">
-      <ng-container i18n>At least one of these filters must be applied in order to proceed:</ng-container>
+      <ng-container i18n>Select at least one of these filters to fetch the data:</ng-container>
       @for (filter of requiredFilters; track filter) {
       <cds-tag class="tag-dark ms-2">{{ filter }}</cds-tag>
       }
     </cd-alert-panel>
     }
-    <cd-inventory-devices [devices]="availDevices"
+    <div class="device-filter-form">
+      <div class="filter-dropdown-row">
+        @for (field of filterFields; track field.prop) {
+        <div class="filter-dropdown">
+          <cds-select (valueChange)="onFilterFieldChange(field.prop, $event)"
+                      [id]="'device_filter_' + field.prop"
+                      [label]="field.name"
+                      size="md"
+                      [attr.data-testid]="'device-filter-' + field.prop">
+            <option value=""
+                    i18n>Any</option>
+            @for (option of field.options; track option.raw) {
+            <option [value]="option.raw"
+                    [selected]="selectedFilters[field.prop] === option.raw">
+              {{ option.formatted }}
+            </option>
+            }
+          </cds-select>
+        </div>
+        }
+      </div>
+    </div>
+    @if (showSelectionSummary) {
+    <div class="selection-summary pb-2 my-2 border-bottom">
+      @for (filter of displayedFilters; track filter.prop) {
+      <cds-tag class="tag-dark me-2">{{ filter.name }}: {{ filter.value.formatted }}</cds-tag>
+      }
+      <a class="tc_clearSelections"
+         href=""
+         (click)="clearDevices(); false">
+        <svg [cdsIcon]="icons.clearFilters"
+             [size]="icons.size16"></svg>
+        <ng-container i18n>Clear</ng-container>
+      </a>
+      <div class="selection-capacity cds-mt-2">
+        <span i18n>Raw capacity: {{ inlineCapacity | dimlessBinary }}.</span>
+      </div>
+    </div>
+    }
+    <cd-inventory-devices [devices]="tableDevices"
                           [hostname]="hostname"
                           [diskType]="name === 'Primary' ? 'hdd' : 'ssd'"
                           [hiddenColumns]="['available', 'osd_ids']"
-                          [filterColumns]="filterColumns"
-                          (filterChange)="onInlineFilterChange($event)">
+                          [filterColumns]="[]">
     </cd-inventory-devices>
-    @if (canInlineSubmit) {
-    <div class="text-center cds-mt-3 cds-mb-3">
-      <span i18n>Number of devices: {{ inlineFilteredDevices.length }}. Raw capacity:
-        {{ inlineCapacity | dimlessBinary }}.</span>
-    </div>
-    }
-    <button type="button"
-            class="btn btn-light"
-            (click)="submitInlineSelection()"
-            [disabled]="!canInlineSubmit || inlineFilteredDevices.length === 0">
-      <svg [cdsIcon]="icons.add"
-           [size]="icons.size16"></svg>
-      <ng-container i18n>Add</ng-container>
-    </button>
     }
     } @else {
     @if (devices.length === 0) {
                             [filterColumns]="[]">
       </cd-inventory-devices>
     </div>
-    @if (type === 'data') {
-    <div class="float-end">
-      <span i18n>Raw capacity: {{ capacity | dimlessBinary }}</span>
-    </div>
-    }
     }
     }
   </div>
index bf3223c234d17cd1c9252807c207f248b69fbe1f..742a942df5a60008e35246f5130487a051035338 100644 (file)
     width: 100%;
   }
 }
+
+.device-filter-form {
+  margin-bottom: 1rem;
+}
+
+.filter-dropdown-row {
+  display: flex;
+  flex-wrap: nowrap;
+  gap: 0.75rem;
+  width: 100%;
+}
+
+.filter-dropdown {
+  flex: 1 1 0;
+  min-width: 0;
+}
+
+.selection-summary {
+  .selection-capacity {
+    text-align: center;
+  }
+}
index 8e6b1e9865c44d82aad91047dc17fa9793c44cfa..d66f7ad93e6bedde5cee19213f96805b5ce2ced0 100644 (file)
@@ -16,11 +16,6 @@ describe('OsdDevicesSelectionGroupsComponent', () => {
   let fixtureHelper: FixtureHelper;
   const devices: InventoryDevice[] = [Mocks.getInventoryDevice('node0', '1')];
 
-  const buttonSelector = '.cd-col-form-input button';
-  const getButton = () => {
-    const debugElement = fixtureHelper.getElementByCss(buttonSelector);
-    return debugElement.nativeElement;
-  };
   const clearTextSelector = '.tc_clearSelections';
   const getClearText = () => {
     const debugElement = fixtureHelper.getElementByCss(clearTextSelector);
@@ -42,7 +37,9 @@ describe('OsdDevicesSelectionGroupsComponent', () => {
     fixture = TestBed.createComponent(OsdDevicesSelectionGroupsComponent);
     fixtureHelper = new FixtureHelper(fixture);
     component = fixture.componentInstance;
+    component.type = 'data';
     component.canSelect = true;
+    component.inlineSelection = true;
   });
 
   describe('without available devices', () => {
@@ -55,14 +52,11 @@ describe('OsdDevicesSelectionGroupsComponent', () => {
       expect(component).toBeTruthy();
     });
 
-    it('should display Add button in disabled state', () => {
-      const button = getButton();
-      expect(button).toBeTruthy();
-      expect(button.disabled).toBe(true);
-      expect(button.textContent).toBe('Add');
+    it('should not display filter form', () => {
+      fixtureHelper.expectElementVisible('.device-filter-form', false);
     });
 
-    it('should not display devices table', () => {
+    it('should display devices table', () => {
       fixtureHelper.expectElementVisible('cd-inventory-devices', false);
     });
   });
@@ -77,23 +71,29 @@ describe('OsdDevicesSelectionGroupsComponent', () => {
       expect(component).toBeTruthy();
     });
 
-    it('should display Add button in enabled state', () => {
-      const button = getButton();
-      expect(button).toBeTruthy();
-      expect(button.disabled).toBe(false);
-      expect(button.textContent).toBe('Add');
+    it('should display filter form', () => {
+      fixtureHelper.expectElementVisible('.device-filter-form', true);
     });
 
-    it('should not display devices table', () => {
-      fixtureHelper.expectElementVisible('cd-inventory-devices', false);
+    it('should display empty devices table', () => {
+      fixtureHelper.expectElementVisible('cd-inventory-devices', true);
+      expect(component.tableDevices).toEqual([]);
     });
   });
 
   describe('with devices selected', () => {
     beforeEach(() => {
       component.isOsdPage = true;
-      component.availDevices = [];
+      component.availDevices = devices;
       component.devices = devices;
+      component.appliedFilters = [
+        {
+          name: 'Type',
+          prop: 'human_readable_type',
+          value: { raw: 'nvme/ssd', formatted: 'nvme/ssd' }
+        }
+      ];
+      component.selectedFilters = { human_readable_type: 'nvme/ssd' };
       component.ngOnInit();
       fixture.detectChanges();
     });
@@ -111,12 +111,87 @@ describe('OsdDevicesSelectionGroupsComponent', () => {
     it('should clear devices by clicking Clear link', () => {
       spyOn(component.cleared, 'emit');
       fixtureHelper.clickElement(clearTextSelector);
-      fixtureHelper.expectElementVisible('cd-inventory-devices', false);
+      fixtureHelper.expectElementVisible('cd-inventory-devices', true);
       const event: Record<string, any> = {
-        type: undefined,
+        type: 'data',
         clearedDevices: devices
       };
       expect(component.cleared.emit).toHaveBeenCalledWith(event);
+      expect(component.devices).toEqual([]);
+    });
+  });
+
+  describe('wal inline selection', () => {
+    beforeEach(() => {
+      component.type = 'wal';
+      component.name = 'WAL';
+      component.availDevices = devices;
+      component.canSelect = true;
+      component.inlineSelection = true;
+      fixture.detectChanges();
+    });
+
+    it('should display filter form instead of Add button', () => {
+      fixtureHelper.expectElementVisible('.device-filter-form', true);
+      fixtureHelper.expectElementVisible('cd-inventory-devices', true);
+      fixtureHelper.expectElementVisible('.cd-col-form-input button.btn-light', false);
+    });
+
+    it('should display empty devices table before filters are applied', () => {
+      expect(component.tableDevices).toEqual([]);
+    });
+
+    it('should auto-apply filtered devices when a required filter is selected', () => {
+      spyOn(component.selected, 'emit');
+      component.onFilterFieldChange('human_readable_type', 'nvme/ssd');
+      fixture.detectChanges();
+
+      expect(component.tableDevices.length).toBe(1);
+      expect(component.devices.length).toBe(1);
+      expect(component.selected.emit).toHaveBeenCalled();
+    });
+  });
+
+  describe('wal inline selection without primary devices', () => {
+    beforeEach(() => {
+      component.type = 'wal';
+      component.name = 'WAL';
+      component.availDevices = devices;
+      component.canSelect = false;
+      component.inlineSelection = true;
+      fixture.detectChanges();
+    });
+
+    it('should display add primary first alert without filter form', () => {
+      fixtureHelper.expectElementVisible('.device-filter-form', false);
+      fixtureHelper.expectElementVisible('cd-inventory-devices', false);
+    });
+  });
+
+  describe('db inline selection', () => {
+    beforeEach(() => {
+      component.type = 'db';
+      component.name = 'DB';
+      component.availDevices = devices;
+      component.canSelect = true;
+      component.inlineSelection = true;
+      fixture.detectChanges();
+    });
+
+    it('should display filter form instead of Add button', () => {
+      fixtureHelper.expectElementVisible('.device-filter-form', true);
+      fixtureHelper.expectElementVisible('cd-inventory-devices', true);
+      fixtureHelper.expectElementVisible('.cd-col-form-input button.btn-light', false);
+    });
+
+    it('should auto-apply filtered devices when a required filter is selected', () => {
+      spyOn(component.selected, 'emit');
+      component.onFilterFieldChange('sys_api.vendor', 'AAA');
+      fixture.detectChanges();
+
+      expect(component.tableDevices.length).toBe(1);
+      expect(component.devices.length).toBe(1);
+      expect(component.selected.emit).toHaveBeenCalled();
     });
   });
 });
index 79d03d10b45d67f0bf3e8718c1f84b336549665f..b9c454c2565997139bd34baf73abd393ef5225dd 100644 (file)
@@ -1,4 +1,14 @@
-import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
+import {
+  ChangeDetectionStrategy,
+  ChangeDetectorRef,
+  Component,
+  EventEmitter,
+  Input,
+  OnChanges,
+  OnInit,
+  Output,
+  PipeTransform
+} from '@angular/core';
 import { Router } from '@angular/router';
 
 import _ from 'lodash';
@@ -7,16 +17,30 @@ import { InventoryDevice } from '~/app/ceph/cluster/inventory/inventory-devices/
 import { OsdService } from '~/app/shared/api/osd.service';
 import { Icons } from '~/app/shared/enum/icons.enum';
 import { CdTableColumnFiltersChange } from '~/app/shared/models/cd-table-column-filters-change';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { OsdDevicesSelectionModalComponent } from '../osd-devices-selection-modal/osd-devices-selection-modal.component';
 import { DevicesSelectionChangeEvent } from './devices-selection-change-event.interface';
 import { DevicesSelectionClearEvent } from './devices-selection-clear-event.interface';
 
+interface DeviceFilterOption {
+  raw: string;
+  formatted: string;
+}
+
+interface DeviceFilterField {
+  prop: string;
+  name: string;
+  pipe?: PipeTransform;
+  options: DeviceFilterOption[];
+}
+
 @Component({
   selector: 'cd-osd-devices-selection-groups',
   templateUrl: './osd-devices-selection-groups.component.html',
   styleUrls: ['./osd-devices-selection-groups.component.scss'],
-  standalone: false
+  standalone: false,
+  changeDetection: ChangeDetectionStrategy.OnPush
 })
 export class OsdDevicesSelectionGroupsComponent implements OnInit, OnChanges {
   // data, wal, db
@@ -60,9 +84,12 @@ export class OsdDevicesSelectionGroupsComponent implements OnInit, OnChanges {
     $localize`Model`,
     $localize`Size`
   ];
-  inlineFilteredDevices: InventoryDevice[] = [];
+  filterFields: DeviceFilterField[] = [];
+  selectedFilters: Record<string, string | undefined> = {};
+  filteredDevices: InventoryDevice[] = [];
   inlineCapacity = 0;
   canInlineSubmit = false;
+  hasAnyFilter = false;
   inlineFilterEvent?: CdTableColumnFiltersChange;
   tooltips = {
     noAvailDevices: $localize`No available devices`,
@@ -70,12 +97,27 @@ export class OsdDevicesSelectionGroupsComponent implements OnInit, OnChanges {
     addByFilters: $localize`Add devices by using filters`
   };
 
+  private filterFieldConfig: Array<{
+    prop: string;
+    name: string;
+    pipe?: PipeTransform;
+  }>;
+
   constructor(
     private modalService: ModalService,
     public osdService: OsdService,
-    private router: Router
+    private router: Router,
+    private dimlessBinary: DimlessBinaryPipe,
+    private cdr: ChangeDetectorRef
   ) {
     this.isOsdPage = this.router.url.includes('/osd');
+    this.filterFieldConfig = [
+      { prop: 'human_readable_type', name: $localize`Type` },
+      { prop: 'hostname', name: $localize`Hostname` },
+      { prop: 'sys_api.vendor', name: $localize`Vendor` },
+      { prop: 'sys_api.model', name: $localize`Model` },
+      { prop: 'sys_api.size', name: $localize`Size`, pipe: this.dimlessBinary }
+    ];
   }
 
   ngOnInit() {
@@ -87,12 +129,78 @@ export class OsdDevicesSelectionGroupsComponent implements OnInit, OnChanges {
       this.osdService?.osdDevices
         ? (this.expansionCanSelect = this.osdService?.osdDevices['disableSelect'])
         : (this.expansionCanSelect = false);
+      if (this.inlineSelection) {
+        this.restoreSelectedFiltersFromApplied();
+      }
     }
-    this.updateAddButtonTooltip();
+    if (this.inlineSelection) {
+      this.initDefaultFilterValues();
+      this.updateFilterFields();
+      this.updateFilteredDevices();
+    } else {
+      this.updateAddButtonTooltip();
+    }
+    this.cdr.markForCheck();
   }
 
   ngOnChanges() {
-    this.updateAddButtonTooltip();
+    if (this.inlineSelection) {
+      this.initDefaultFilterValues();
+      this.updateFilterFields();
+      this.updateFilteredDevices();
+    } else {
+      this.updateAddButtonTooltip();
+    }
+    this.cdr.markForCheck();
+  }
+
+  get showAddPrimaryFirstAlert(): boolean {
+    return this.type !== 'data' && !this.canSelect && this.devices.length === 0;
+  }
+
+  get showFilterSection(): boolean {
+    if (this.type === 'data') {
+      return this.availDevices?.length > 0 || this.devices?.length > 0;
+    }
+
+    if (this.devices?.length > 0) {
+      return true;
+    }
+
+    return this.canSelect && this.availDevices?.length > 0;
+  }
+
+  get showNoAvailDevicesAlert(): boolean {
+    if (this.type !== 'data' && !this.canSelect) {
+      return false;
+    }
+
+    return !this.showFilterSection;
+  }
+
+  get tableDevices(): InventoryDevice[] {
+    return this.canInlineSubmit ? this.filteredDevices : [];
+  }
+
+  get displayedFilters(): CdTableColumnFiltersChange['filters'] {
+    return this.inlineFilterEvent?.filters ?? this.appliedFilters ?? [];
+  }
+
+  get showSelectionSummary(): boolean {
+    return (
+      (this.canInlineSubmit && this.tableDevices.length > 0) ||
+      (this.devices.length > 0 && this.appliedFilters.length > 0)
+    );
+  }
+
+  onFilterFieldChange(prop: string, value: string) {
+    const normalizedValue = value === '' ? undefined : value;
+    const hadDevices = this.devices.length > 0;
+    const clearedDevices = [...this.devices];
+    this.selectedFilters[prop] = normalizedValue;
+    this.updateFilteredDevices();
+    this.syncSelectionState(hadDevices, clearedDevices);
+    this.cdr.markForCheck();
   }
 
   showSelectionModal() {
@@ -112,33 +220,143 @@ export class OsdDevicesSelectionGroupsComponent implements OnInit, OnChanges {
     });
   }
 
-  onInlineFilterChange(event: CdTableColumnFiltersChange) {
+  clearDevices() {
+    if (!this.isOsdPage) {
+      this.expansionCanSelect = false;
+      this.osdService.osdDevices['disableSelect'] = false;
+      this.osdService.osdDevices = [];
+    }
+    const event = {
+      type: this.type,
+      clearedDevices: [...this.devices]
+    };
+    this.devices = [];
+    this.appliedFilters = [];
+    this.capacity = 0;
+    this.selectedFilters = {};
     this.inlineCapacity = 0;
     this.canInlineSubmit = false;
     this.inlineFilterEvent = undefined;
+    if (this.inlineSelection) {
+      this.initDefaultFilterValues();
+      this.updateFilterFields();
+      this.updateFilteredDevices();
+    }
+    this.cleared.emit(event);
+    this.cdr.markForCheck();
+  }
+
+  private get devicesForFiltering(): InventoryDevice[] {
+    return _.uniqBy([...(this.availDevices || []), ...(this.devices || [])], 'uid');
+  }
 
-    if (_.isEmpty(event.filters)) {
-      this.inlineFilteredDevices = [];
+  private get canApplySelection(): boolean {
+    return this.type === 'data' || this.canSelect;
+  }
+
+  private initDefaultFilterValues() {
+    if (this.hostname && !this.selectedFilters['hostname']) {
+      this.selectedFilters['hostname'] = this.hostname;
+    }
+  }
+
+  private restoreSelectedFiltersFromApplied() {
+    if (_.isEmpty(this.appliedFilters)) {
       return;
     }
+    this.appliedFilters.forEach((filter) => {
+      this.selectedFilters[filter.prop as string] = filter.value?.raw;
+    });
+  }
+
+  private updateFilterFields() {
+    this.filterFields = this.filterFieldConfig
+      .filter((field) => this.filterColumns.includes(field.prop))
+      .map((field) => ({
+        ...field,
+        options: this.buildFilterOptions(field.prop, field.pipe)
+      }));
+  }
 
-    const filters = event.filters.filter((filter) => filter.prop !== 'hostname');
-    this.canInlineSubmit = !_.isEmpty(filters);
-    this.inlineFilteredDevices = event.data;
-    this.inlineCapacity = _.sumBy(this.inlineFilteredDevices, 'sys_api.size');
-    this.inlineFilterEvent = event;
+  private buildFilterOptions(prop: string, pipe?: PipeTransform): DeviceFilterOption[] {
+    const values = _.sortedUniq(
+      _.filter(_.map(this.devicesForFiltering, prop), (value) => {
+        return (_.isString(value) && value !== '') || _.isBoolean(value) || _.isFinite(value);
+      }).sort()
+    );
+
+    return values.map((value) => this.createFilterOption(value, pipe));
   }
 
-  submitInlineSelection() {
-    if (
-      !this.inlineFilterEvent ||
-      !this.canInlineSubmit ||
-      this.inlineFilteredDevices.length === 0
-    ) {
+  private createFilterOption(value: unknown, pipe?: PipeTransform): DeviceFilterOption {
+    const raw = _.toString(value);
+    return {
+      raw,
+      formatted: pipe ? pipe.transform(value) : raw
+    };
+  }
+
+  private syncSelectionState(hadDevices: boolean, clearedDevices: InventoryDevice[]) {
+    if (this.canApplySelection && this.canInlineSubmit && this.filteredDevices.length > 0) {
+      this.applySelectionResult(this.inlineFilterEvent);
       return;
     }
 
-    this.applySelectionResult(this.inlineFilterEvent);
+    if (hadDevices) {
+      this.resetSelectionState();
+      this.cleared.emit({
+        type: this.type,
+        clearedDevices
+      });
+    }
+  }
+
+  private resetSelectionState() {
+    this.devices = [];
+    this.capacity = 0;
+    this.appliedFilters = [];
+  }
+
+  private updateFilteredDevices() {
+    let data = [...this.devicesForFiltering];
+    const appliedFilters: CdTableColumnFiltersChange['filters'] = [];
+
+    this.filterFields.forEach((field) => {
+      const selectedValue = this.selectedFilters[field.prop];
+      if (_.isUndefined(selectedValue)) {
+        return;
+      }
+
+      const option = field.options.find((entry) => entry.raw === selectedValue);
+      if (!option) {
+        delete this.selectedFilters[field.prop];
+        return;
+      }
+
+      appliedFilters.push({
+        name: field.name,
+        prop: field.prop,
+        value: option
+      });
+      data = data.filter((row) => `${_.get(row, field.prop)}` === selectedValue);
+    });
+
+    this.hasAnyFilter = !_.isEmpty(appliedFilters);
+    const filtersWithoutHostname = appliedFilters.filter((filter) => filter.prop !== 'hostname');
+    this.canInlineSubmit = !_.isEmpty(filtersWithoutHostname);
+    this.filteredDevices = this.hasAnyFilter ? data : [];
+    this.inlineCapacity = _.sumBy(this.filteredDevices, 'sys_api.size');
+
+    if (this.hasAnyFilter) {
+      const filteredUids = new Set(this.filteredDevices.map((device) => device.uid));
+      this.inlineFilterEvent = {
+        filters: appliedFilters,
+        data: this.filteredDevices,
+        dataOut: this.devicesForFiltering.filter((device) => !filteredUids.has(device.uid))
+      };
+    } else {
+      this.inlineFilterEvent = undefined;
+    }
   }
 
   private applySelectionResult(result: CdTableColumnFiltersChange) {
@@ -160,7 +378,6 @@ export class OsdDevicesSelectionGroupsComponent implements OnInit, OnChanges {
       this.addButtonTooltip = this.tooltips.noAvailDevices;
     } else {
       if (!this.canSelect) {
-        // No primary devices added yet.
         this.addButtonTooltip = this.tooltips.addPrimaryFirst;
       } else if (this.availDevices.length === 0) {
         this.addButtonTooltip = this.tooltips.noAvailDevices;
@@ -169,22 +386,4 @@ export class OsdDevicesSelectionGroupsComponent implements OnInit, OnChanges {
       }
     }
   }
-
-  clearDevices() {
-    if (!this.isOsdPage) {
-      this.expansionCanSelect = false;
-      this.osdService.osdDevices['disableSelect'] = false;
-      this.osdService.osdDevices = [];
-    }
-    const event = {
-      type: this.type,
-      clearedDevices: [...this.devices]
-    };
-    this.devices = [];
-    this.inlineFilteredDevices = [];
-    this.inlineCapacity = 0;
-    this.canInlineSubmit = false;
-    this.inlineFilterEvent = undefined;
-    this.cleared.emit(event);
-  }
 }
index 91dc4ca6eccd67a93bc590def3c7036968ad08b1..700c8af631eb4a7cc9f72efbf9fa97687c571cf6 100644 (file)
@@ -9,9 +9,9 @@
   (closeRequested)="onCancel()"
   (stepChanged)="populateReviewData()"
   [isSubmitLoading]="isSubmitLoading"
-  [submitButtonLabel]="simpleDeployment ? createOsdsLabel : actionLabels.PREVIEW"
+  [submitButtonLabel]="createOsdsLabel">
   type="wide">
-
+<!-- [submitButtonLabel]="simpleDeployment ? createOsdsLabel : actionLabels.PREVIEW" -->
   <cd-tearsheet-step>
   <div class="osd-tearsheet-content">
     <form
           i18n>None selected</p>
         }
       </div>
+
+       <div
+          cdsCol
+          [columnNumbers]="{ sm: 4, md: 8, lg: 12 }"
+          class="cds-mt-7">
+        <h4
+          class="cds--type-heading-compact-01"
+          i18n>Device Groups</h4>
+      </div>
+
+       <div
+          cdsCol
+          [columnNumbers]="{ sm: 4, md: 4, lg: 4 }"
+          class="cds-mt-5">
+          <p
+            class="cds--type-label-01"
+            i18n>Service Type</p>
+          <p
+            class="cds--type-label-02 cds-mt-2"
+            i18n>{{driveGroup.spec.service_type}}</p>   
+        </div>
+
+        <div
+          cdsCol
+          [columnNumbers]="{ sm: 4, md: 4, lg: 4 }"
+          class="cds-mt-5">
+          <p
+            class="cds--type-label-01"
+            i18n>Service ID</p>
+          <p
+            class="cds--type-label-02 cds-mt-2"
+            i18n>{{driveGroup.spec.service_id}}</p>       
+        </div>
+
+        <!-- <div
+          cdsCol
+          [columnNumbers]="{ sm: 4, md: 4, lg: 4 }"
+          class="cds-mt-5">
+          <p
+            class="cds--type-label-01"
+            i18n>Data devices</p>
+           <div class="osd-review-section cds-pl-4 cds-mt-3">
+              <p class="cds--type-label-01 cds-mb-2">Rotational</p>
+              <p class="cds--type-label-02 cds-mt-1">{{ driveGroup.spec.data_devices.rotational }}</p>
+            </div>       
+        </div> -->
+
       }
 
       <div
index 63dfd3d58b2ed9fea13c7c85b5e68bdb480697b7..90bc800f57cf2d2658450f49e4a7eef10e65b001 100644 (file)
@@ -29,10 +29,10 @@ import {
 } from '~/app/shared/models/osd-deployment-options';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { FormatterService } from '~/app/shared/services/formatter.service';
-import { ModalService } from '~/app/shared/services/modal.service';
+// import { ModalService } from '~/app/shared/services/modal.service';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { TearsheetComponent } from '~/app/shared/components/tearsheet/tearsheet.component';
-import { OsdCreationPreviewModalComponent } from '../osd-creation-preview-modal/osd-creation-preview-modal.component';
+// import { OsdCreationPreviewModalComponent } from '../osd-creation-preview-modal/osd-creation-preview-modal.component';
 import { DevicesSelectionChangeEvent } from '../osd-devices-selection-groups/devices-selection-change-event.interface';
 import { DevicesSelectionClearEvent } from '../osd-devices-selection-groups/devices-selection-clear-event.interface';
 import { OsdDevicesSelectionGroupsComponent } from '../osd-devices-selection-groups/osd-devices-selection-groups.component';
@@ -143,7 +143,7 @@ export class OsdFormComponent extends CdForm implements OnInit, OnDestroy {
     private hostService: HostService,
     private router: Router,
     private formatterService: FormatterService,
-    private modalService: ModalService,
+    // private modalService: ModalService,
     private osdService: OsdService,
     private taskWrapper: TaskWrapperService
   ) {
@@ -222,6 +222,7 @@ export class OsdFormComponent extends CdForm implements OnInit, OnDestroy {
 
   private formatHostPattern(pattern?: string): string {
     if (!pattern || pattern === '*') {
+      // this.driveGroup.setHostPattern('*');
       return $localize`All hosts`;
     }
 
@@ -463,6 +464,9 @@ export class OsdFormComponent extends CdForm implements OnInit, OnDestroy {
   }
 
   populateReviewData() {
+    const user = this.authStorageService.getUsername();
+    this.driveGroup.setName(`dashboard-${user}-${_.now()}`);
+
     this.reviewDeploymentModeLabel = this.simpleDeployment
       ? $localize`Automatic`
       : $localize`Manual selection`;
@@ -537,16 +541,38 @@ export class OsdFormComponent extends CdForm implements OnInit, OnDestroy {
         });
     } else {
       // use user name and timestamp for drive group name
-      const user = this.authStorageService.getUsername();
-      this.driveGroup.setName(`dashboard-${user}-${_.now()}`);
-      const modalRef = this.modalService.show(OsdCreationPreviewModalComponent, {
-        driveGroups: [this.driveGroup.spec]
-      });
-      modalRef.componentInstance.submitAction.subscribe(() => {
-        this.navigateAfterCreate();
-      });
-      this.isSubmitLoading = false;
-      this.previewButtonPanel.submitButton.loading = false;
+
+      // const user = this.authStorageService.getUsername();
+      // this.driveGroup.setName(`dashboard-${user}-${_.now()}`);
+      // const modalRef = this.modalService.show(OsdCreationPreviewModalComponent, {
+      //   driveGroups: [this.driveGroup.spec]
+      // });
+      // modalRef.componentInstance.submitAction.subscribe(() => {
+      //   this.navigateAfterCreate();
+      // });
+      // this.isSubmitLoading = false;
+      // this.previewButtonPanel.submitButton.loading = false;
+
+      // let driveGroups = [this.driveGroup.spec];
+
+      const trackingId = _.join(_.map([this.driveGroup.spec], 'service_id'), ', ');
+      this.taskWrapper
+        .wrapTaskAroundCall({
+          task: new FinishedTask('osd/' + URLVerbs.CREATE, {
+            tracking_id: trackingId
+          }),
+          call: this.osdService.create([this.driveGroup.spec], trackingId)
+        })
+        .subscribe({
+          error: () => {
+            this.isSubmitLoading = false;
+          },
+          complete: () => {
+            this.isSubmitLoading = false;
+            this.navigateAfterCreate();
+            this.previewButtonPanel.submitButton.loading = false;
+          }
+        });
     }
   }