]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts
import ceph quincy 17.2.6
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / osd / osd-form / osd-form.component.ts
CommitLineData
a4b75251 1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
20effc67 2import { FormControl } from '@angular/forms';
9f95a23c
TL
3import { Router } from '@angular/router';
4
f67539c2
TL
5import _ from 'lodash';
6
7import { InventoryDevice } from '~/app/ceph/cluster/inventory/inventory-devices/inventory-device.model';
8import { HostService } from '~/app/shared/api/host.service';
9import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
2a845540 10import { OsdService } from '~/app/shared/api/osd.service';
f67539c2 11import { FormButtonPanelComponent } from '~/app/shared/components/form-button-panel/form-button-panel.component';
2a845540 12import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
f67539c2
TL
13import { Icons } from '~/app/shared/enum/icons.enum';
14import { CdForm } from '~/app/shared/forms/cd-form';
15import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
16import { CdTableColumn } from '~/app/shared/models/cd-table-column';
2a845540
TL
17import { FinishedTask } from '~/app/shared/models/finished-task';
18import {
19 DeploymentOptions,
20 OsdDeploymentOptions
21} from '~/app/shared/models/osd-deployment-options';
f67539c2
TL
22import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
23import { ModalService } from '~/app/shared/services/modal.service';
2a845540 24import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
9f95a23c
TL
25import { OsdCreationPreviewModalComponent } from '../osd-creation-preview-modal/osd-creation-preview-modal.component';
26import { DevicesSelectionChangeEvent } from '../osd-devices-selection-groups/devices-selection-change-event.interface';
27import { DevicesSelectionClearEvent } from '../osd-devices-selection-groups/devices-selection-clear-event.interface';
28import { OsdDevicesSelectionGroupsComponent } from '../osd-devices-selection-groups/osd-devices-selection-groups.component';
29import { DriveGroup } from './drive-group.model';
30import { OsdFeature } from './osd-feature.interface';
31
32@Component({
33 selector: 'cd-osd-form',
34 templateUrl: './osd-form.component.html',
35 styleUrls: ['./osd-form.component.scss']
36})
f67539c2
TL
37export class OsdFormComponent extends CdForm implements OnInit {
38 @ViewChild('dataDeviceSelectionGroups')
9f95a23c
TL
39 dataDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent;
40
f67539c2 41 @ViewChild('walDeviceSelectionGroups')
9f95a23c
TL
42 walDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent;
43
f67539c2 44 @ViewChild('dbDeviceSelectionGroups')
9f95a23c
TL
45 dbDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent;
46
f67539c2
TL
47 @ViewChild('previewButtonPanel')
48 previewButtonPanel: FormButtonPanelComponent;
9f95a23c 49
a4b75251
TL
50 @Input()
51 hideTitle = false;
52
53 @Input()
54 hideSubmitBtn = false;
55
56 @Output() emitDriveGroup: EventEmitter<DriveGroup> = new EventEmitter();
57
2a845540
TL
58 @Output() emitDeploymentOption: EventEmitter<object> = new EventEmitter();
59
60 @Output() emitMode: EventEmitter<boolean> = new EventEmitter();
61
9f95a23c
TL
62 icons = Icons;
63
64 form: CdFormGroup;
65 columns: Array<CdTableColumn> = [];
66
9f95a23c
TL
67 allDevices: InventoryDevice[] = [];
68
69 availDevices: InventoryDevice[] = [];
70 dataDeviceFilters: any[] = [];
71 dbDeviceFilters: any[] = [];
72 walDeviceFilters: any[] = [];
73 hostname = '';
74 driveGroup = new DriveGroup();
75
76 action: string;
77 resource: string;
78
79 features: { [key: string]: OsdFeature };
80 featureList: OsdFeature[] = [];
81
f67539c2 82 hasOrchestrator = true;
9f95a23c 83
2a845540
TL
84 simpleDeployment = true;
85
86 deploymentOptions: DeploymentOptions;
87 optionNames = Object.values(OsdDeploymentOptions);
88
9f95a23c
TL
89 constructor(
90 public actionLabels: ActionLabelsI18n,
91 private authStorageService: AuthStorageService,
9f95a23c 92 private orchService: OrchestratorService,
f67539c2 93 private hostService: HostService,
9f95a23c 94 private router: Router,
a4b75251 95 private modalService: ModalService,
2a845540
TL
96 private osdService: OsdService,
97 private taskWrapper: TaskWrapperService
9f95a23c 98 ) {
f67539c2
TL
99 super();
100 this.resource = $localize`OSDs`;
9f95a23c
TL
101 this.action = this.actionLabels.CREATE;
102 this.features = {
103 encrypted: {
104 key: 'encrypted',
f67539c2 105 desc: $localize`Encryption`
9f95a23c
TL
106 }
107 };
108 this.featureList = _.map(this.features, (o, key) => Object.assign(o, { key: key }));
109 this.createForm();
110 }
111
112 ngOnInit() {
113 this.orchService.status().subscribe((status) => {
114 this.hasOrchestrator = status.available;
f67539c2 115 if (status.available) {
9f95a23c 116 this.getDataDevices();
f67539c2
TL
117 } else {
118 this.loadingNone();
9f95a23c
TL
119 }
120 });
121
2a845540
TL
122 this.osdService.getDeploymentOptions().subscribe((options) => {
123 this.deploymentOptions = options;
124 this.form.get('deploymentOption').setValue(this.deploymentOptions?.recommended_option);
125
126 if (this.deploymentOptions?.recommended_option) {
127 this.enableFeatures();
128 }
129 });
9f95a23c
TL
130 this.form.get('walSlots').valueChanges.subscribe((value) => this.setSlots('wal', value));
131 this.form.get('dbSlots').valueChanges.subscribe((value) => this.setSlots('db', value));
132 _.each(this.features, (feature) => {
133 this.form
134 .get('features')
135 .get(feature.key)
136 .valueChanges.subscribe((value) => this.featureFormUpdate(feature.key, value));
137 });
138 }
139
140 createForm() {
141 this.form = new CdFormGroup({
20effc67
TL
142 walSlots: new FormControl(0),
143 dbSlots: new FormControl(0),
9f95a23c
TL
144 features: new CdFormGroup(
145 this.featureList.reduce((acc: object, e) => {
146 // disable initially because no data devices are selected
147 acc[e.key] = new FormControl({ value: false, disabled: true });
148 return acc;
149 }, {})
2a845540
TL
150 ),
151 deploymentOption: new FormControl(0)
9f95a23c
TL
152 });
153 }
154
155 getDataDevices() {
f67539c2 156 this.hostService.inventoryDeviceList().subscribe(
9f95a23c
TL
157 (devices: InventoryDevice[]) => {
158 this.allDevices = _.filter(devices, 'available');
159 this.availDevices = [...this.allDevices];
f67539c2 160 this.loadingReady();
9f95a23c
TL
161 },
162 () => {
163 this.allDevices = [];
164 this.availDevices = [];
f67539c2 165 this.loadingError();
9f95a23c
TL
166 }
167 );
168 }
169
170 setSlots(type: string, slots: number) {
171 if (typeof slots !== 'number') {
172 return;
173 }
174 if (slots >= 0) {
175 this.driveGroup.setSlots(type, slots);
176 }
177 }
178
179 featureFormUpdate(key: string, checked: boolean) {
180 this.driveGroup.setFeature(key, checked);
181 }
182
183 enableFeatures() {
184 this.featureList.forEach((feature) => {
185 this.form.get(feature.key).enable({ emitEvent: false });
186 });
187 }
188
189 disableFeatures() {
190 this.featureList.forEach((feature) => {
191 const control = this.form.get(feature.key);
192 control.disable({ emitEvent: false });
193 control.setValue(false, { emitEvent: false });
194 });
195 }
196
197 onDevicesSelected(event: DevicesSelectionChangeEvent) {
198 this.availDevices = event.dataOut;
199
200 if (event.type === 'data') {
201 // If user selects data devices for a single host, make only remaining devices on
202 // that host as available.
203 const hostnameFilter = _.find(event.filters, { prop: 'hostname' });
204 if (hostnameFilter) {
205 this.hostname = hostnameFilter.value.raw;
206 this.availDevices = event.dataOut.filter((device: InventoryDevice) => {
207 return device.hostname === this.hostname;
208 });
209 this.driveGroup.setHostPattern(this.hostname);
210 } else {
211 this.driveGroup.setHostPattern('*');
212 }
213 this.enableFeatures();
214 }
215 this.driveGroup.setDeviceSelection(event.type, event.filters);
a4b75251
TL
216
217 this.emitDriveGroup.emit(this.driveGroup);
9f95a23c
TL
218 }
219
220 onDevicesCleared(event: DevicesSelectionClearEvent) {
221 if (event.type === 'data') {
39ae355f 222 this.hostname = '';
9f95a23c
TL
223 this.availDevices = [...this.allDevices];
224 this.walDeviceSelectionGroups.devices = [];
225 this.dbDeviceSelectionGroups.devices = [];
226 this.disableFeatures();
227 this.driveGroup.reset();
228 this.form.get('walSlots').setValue(0, { emitEvent: false });
229 this.form.get('dbSlots').setValue(0, { emitEvent: false });
230 } else {
231 this.availDevices = [...this.availDevices, ...event.clearedDevices];
232 this.driveGroup.clearDeviceSelection(event.type);
233 const slotControlName = `${event.type}Slots`;
234 this.form.get(slotControlName).setValue(0, { emitEvent: false });
235 }
236 }
237
2a845540
TL
238 emitDeploymentSelection() {
239 const option = this.form.get('deploymentOption').value;
240 const encrypted = this.form.get('encrypted').value;
241 this.emitDeploymentOption.emit({ option: option, encrypted: encrypted });
242 }
243
244 emitDeploymentMode() {
245 this.simpleDeployment = !this.simpleDeployment;
246 if (!this.simpleDeployment && this.dataDeviceSelectionGroups.devices.length === 0) {
247 this.disableFeatures();
248 } else {
249 this.enableFeatures();
250 }
251 this.emitMode.emit(this.simpleDeployment);
252 }
253
9f95a23c 254 submit() {
2a845540
TL
255 if (this.simpleDeployment) {
256 const option = this.form.get('deploymentOption').value;
257 const encrypted = this.form.get('encrypted').value;
258 const deploymentSpec = { option: option, encrypted: encrypted };
259 const title = this.deploymentOptions.options[deploymentSpec.option].title;
260 const trackingId = `${title} deployment`;
261 this.taskWrapper
262 .wrapTaskAroundCall({
263 task: new FinishedTask('osd/' + URLVerbs.CREATE, {
264 tracking_id: trackingId
265 }),
266 call: this.osdService.create([deploymentSpec], trackingId, 'predefined')
267 })
268 .subscribe({
269 complete: () => {
270 this.router.navigate(['/osd']);
271 }
272 });
273 } else {
274 // use user name and timestamp for drive group name
275 const user = this.authStorageService.getUsername();
276 this.driveGroup.setName(`dashboard-${user}-${_.now()}`);
277 const modalRef = this.modalService.show(OsdCreationPreviewModalComponent, {
278 driveGroups: [this.driveGroup.spec]
279 });
280 modalRef.componentInstance.submitAction.subscribe(() => {
281 this.router.navigate(['/osd']);
282 });
283 this.previewButtonPanel.submitButton.loading = false;
284 }
9f95a23c
TL
285 }
286}