summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts
diff options
context:
space:
mode:
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.ts285
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;
+ }
+ }
+}