diff options
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts')
-rw-r--r-- | src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts new file mode 100644 index 000000000..c2384425e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts @@ -0,0 +1,285 @@ +import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { Router } from '@angular/router'; + +import _ from 'lodash'; + +import { InventoryDevice } from '~/app/ceph/cluster/inventory/inventory-devices/inventory-device.model'; +import { HostService } from '~/app/shared/api/host.service'; +import { OrchestratorService } from '~/app/shared/api/orchestrator.service'; +import { OsdService } from '~/app/shared/api/osd.service'; +import { FormButtonPanelComponent } from '~/app/shared/components/form-button-panel/form-button-panel.component'; +import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; +import { Icons } from '~/app/shared/enum/icons.enum'; +import { CdForm } from '~/app/shared/forms/cd-form'; +import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; +import { CdTableColumn } from '~/app/shared/models/cd-table-column'; +import { FinishedTask } from '~/app/shared/models/finished-task'; +import { + DeploymentOptions, + OsdDeploymentOptions +} from '~/app/shared/models/osd-deployment-options'; +import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; +import { ModalService } from '~/app/shared/services/modal.service'; +import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; +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'; +import { DriveGroup } from './drive-group.model'; +import { OsdFeature } from './osd-feature.interface'; + +@Component({ + selector: 'cd-osd-form', + templateUrl: './osd-form.component.html', + styleUrls: ['./osd-form.component.scss'] +}) +export class OsdFormComponent extends CdForm implements OnInit { + @ViewChild('dataDeviceSelectionGroups') + dataDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent; + + @ViewChild('walDeviceSelectionGroups') + walDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent; + + @ViewChild('dbDeviceSelectionGroups') + dbDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent; + + @ViewChild('previewButtonPanel') + previewButtonPanel: FormButtonPanelComponent; + + @Input() + hideTitle = false; + + @Input() + hideSubmitBtn = false; + + @Output() emitDriveGroup: EventEmitter<DriveGroup> = new EventEmitter(); + + @Output() emitDeploymentOption: EventEmitter<object> = new EventEmitter(); + + @Output() emitMode: EventEmitter<boolean> = new EventEmitter(); + + icons = Icons; + + form: CdFormGroup; + columns: Array<CdTableColumn> = []; + + allDevices: InventoryDevice[] = []; + + availDevices: InventoryDevice[] = []; + dataDeviceFilters: any[] = []; + dbDeviceFilters: any[] = []; + walDeviceFilters: any[] = []; + hostname = ''; + driveGroup = new DriveGroup(); + + action: string; + resource: string; + + features: { [key: string]: OsdFeature }; + featureList: OsdFeature[] = []; + + hasOrchestrator = true; + + simpleDeployment = true; + + deploymentOptions: DeploymentOptions; + optionNames = Object.values(OsdDeploymentOptions); + + constructor( + public actionLabels: ActionLabelsI18n, + private authStorageService: AuthStorageService, + private orchService: OrchestratorService, + private hostService: HostService, + private router: Router, + private modalService: ModalService, + private osdService: OsdService, + private taskWrapper: TaskWrapperService + ) { + super(); + this.resource = $localize`OSDs`; + this.action = this.actionLabels.CREATE; + this.features = { + encrypted: { + key: 'encrypted', + desc: $localize`Encryption` + } + }; + this.featureList = _.map(this.features, (o, key) => Object.assign(o, { key: key })); + this.createForm(); + } + + ngOnInit() { + this.orchService.status().subscribe((status) => { + this.hasOrchestrator = status.available; + if (status.available) { + this.getDataDevices(); + } else { + this.loadingNone(); + } + }); + + this.osdService.getDeploymentOptions().subscribe((options) => { + this.deploymentOptions = options; + this.form.get('deploymentOption').setValue(this.deploymentOptions?.recommended_option); + + if (this.deploymentOptions?.recommended_option) { + this.enableFeatures(); + } + }); + this.form.get('walSlots').valueChanges.subscribe((value) => this.setSlots('wal', value)); + this.form.get('dbSlots').valueChanges.subscribe((value) => this.setSlots('db', value)); + _.each(this.features, (feature) => { + this.form + .get('features') + .get(feature.key) + .valueChanges.subscribe((value) => this.featureFormUpdate(feature.key, value)); + }); + } + + createForm() { + this.form = new CdFormGroup({ + walSlots: new FormControl(0), + dbSlots: new FormControl(0), + features: new CdFormGroup( + this.featureList.reduce((acc: object, e) => { + // disable initially because no data devices are selected + acc[e.key] = new FormControl({ value: false, disabled: true }); + return acc; + }, {}) + ), + deploymentOption: new FormControl(0) + }); + } + + getDataDevices() { + this.hostService.inventoryDeviceList().subscribe( + (devices: InventoryDevice[]) => { + this.allDevices = _.filter(devices, 'available'); + this.availDevices = [...this.allDevices]; + this.loadingReady(); + }, + () => { + this.allDevices = []; + this.availDevices = []; + this.loadingError(); + } + ); + } + + setSlots(type: string, slots: number) { + if (typeof slots !== 'number') { + return; + } + if (slots >= 0) { + this.driveGroup.setSlots(type, slots); + } + } + + featureFormUpdate(key: string, checked: boolean) { + this.driveGroup.setFeature(key, checked); + } + + enableFeatures() { + this.featureList.forEach((feature) => { + this.form.get(feature.key).enable({ emitEvent: false }); + }); + } + + disableFeatures() { + this.featureList.forEach((feature) => { + const control = this.form.get(feature.key); + control.disable({ emitEvent: false }); + control.setValue(false, { emitEvent: false }); + }); + } + + onDevicesSelected(event: DevicesSelectionChangeEvent) { + this.availDevices = event.dataOut; + + if (event.type === 'data') { + // If user selects data devices for a single host, make only remaining devices on + // that host as available. + const hostnameFilter = _.find(event.filters, { prop: 'hostname' }); + if (hostnameFilter) { + this.hostname = hostnameFilter.value.raw; + this.availDevices = event.dataOut.filter((device: InventoryDevice) => { + return device.hostname === this.hostname; + }); + this.driveGroup.setHostPattern(this.hostname); + } else { + this.driveGroup.setHostPattern('*'); + } + this.enableFeatures(); + } + this.driveGroup.setDeviceSelection(event.type, event.filters); + + this.emitDriveGroup.emit(this.driveGroup); + } + + onDevicesCleared(event: DevicesSelectionClearEvent) { + if (event.type === 'data') { + this.availDevices = [...this.allDevices]; + this.walDeviceSelectionGroups.devices = []; + this.dbDeviceSelectionGroups.devices = []; + this.disableFeatures(); + this.driveGroup.reset(); + this.form.get('walSlots').setValue(0, { emitEvent: false }); + this.form.get('dbSlots').setValue(0, { emitEvent: false }); + } else { + this.availDevices = [...this.availDevices, ...event.clearedDevices]; + this.driveGroup.clearDeviceSelection(event.type); + const slotControlName = `${event.type}Slots`; + this.form.get(slotControlName).setValue(0, { emitEvent: false }); + } + } + + emitDeploymentSelection() { + const option = this.form.get('deploymentOption').value; + const encrypted = this.form.get('encrypted').value; + this.emitDeploymentOption.emit({ option: option, encrypted: encrypted }); + } + + emitDeploymentMode() { + this.simpleDeployment = !this.simpleDeployment; + if (!this.simpleDeployment && this.dataDeviceSelectionGroups.devices.length === 0) { + this.disableFeatures(); + } else { + this.enableFeatures(); + } + this.emitMode.emit(this.simpleDeployment); + } + + submit() { + if (this.simpleDeployment) { + const option = this.form.get('deploymentOption').value; + const encrypted = this.form.get('encrypted').value; + const deploymentSpec = { option: option, encrypted: encrypted }; + const title = this.deploymentOptions.options[deploymentSpec.option].title; + const trackingId = `${title} deployment`; + this.taskWrapper + .wrapTaskAroundCall({ + task: new FinishedTask('osd/' + URLVerbs.CREATE, { + tracking_id: trackingId + }), + call: this.osdService.create([deploymentSpec], trackingId, 'predefined') + }) + .subscribe({ + complete: () => { + this.router.navigate(['/osd']); + } + }); + } 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.router.navigate(['/osd']); + }); + this.previewButtonPanel.submitButton.loading = false; + } + } +} |