]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-form/osd-form.component.ts
c2384425e7019127d087b54ba8813dfdc50513db
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / osd / osd-form / osd-form.component.ts
1 import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
2 import { FormControl } from '@angular/forms';
3 import { Router } from '@angular/router';
4
5 import _ from 'lodash';
6
7 import { InventoryDevice } from '~/app/ceph/cluster/inventory/inventory-devices/inventory-device.model';
8 import { HostService } from '~/app/shared/api/host.service';
9 import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
10 import { OsdService } from '~/app/shared/api/osd.service';
11 import { FormButtonPanelComponent } from '~/app/shared/components/form-button-panel/form-button-panel.component';
12 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
13 import { Icons } from '~/app/shared/enum/icons.enum';
14 import { CdForm } from '~/app/shared/forms/cd-form';
15 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
16 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
17 import { FinishedTask } from '~/app/shared/models/finished-task';
18 import {
19 DeploymentOptions,
20 OsdDeploymentOptions
21 } from '~/app/shared/models/osd-deployment-options';
22 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
23 import { ModalService } from '~/app/shared/services/modal.service';
24 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
25 import { OsdCreationPreviewModalComponent } from '../osd-creation-preview-modal/osd-creation-preview-modal.component';
26 import { DevicesSelectionChangeEvent } from '../osd-devices-selection-groups/devices-selection-change-event.interface';
27 import { DevicesSelectionClearEvent } from '../osd-devices-selection-groups/devices-selection-clear-event.interface';
28 import { OsdDevicesSelectionGroupsComponent } from '../osd-devices-selection-groups/osd-devices-selection-groups.component';
29 import { DriveGroup } from './drive-group.model';
30 import { 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 })
37 export class OsdFormComponent extends CdForm implements OnInit {
38 @ViewChild('dataDeviceSelectionGroups')
39 dataDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent;
40
41 @ViewChild('walDeviceSelectionGroups')
42 walDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent;
43
44 @ViewChild('dbDeviceSelectionGroups')
45 dbDeviceSelectionGroups: OsdDevicesSelectionGroupsComponent;
46
47 @ViewChild('previewButtonPanel')
48 previewButtonPanel: FormButtonPanelComponent;
49
50 @Input()
51 hideTitle = false;
52
53 @Input()
54 hideSubmitBtn = false;
55
56 @Output() emitDriveGroup: EventEmitter<DriveGroup> = new EventEmitter();
57
58 @Output() emitDeploymentOption: EventEmitter<object> = new EventEmitter();
59
60 @Output() emitMode: EventEmitter<boolean> = new EventEmitter();
61
62 icons = Icons;
63
64 form: CdFormGroup;
65 columns: Array<CdTableColumn> = [];
66
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
82 hasOrchestrator = true;
83
84 simpleDeployment = true;
85
86 deploymentOptions: DeploymentOptions;
87 optionNames = Object.values(OsdDeploymentOptions);
88
89 constructor(
90 public actionLabels: ActionLabelsI18n,
91 private authStorageService: AuthStorageService,
92 private orchService: OrchestratorService,
93 private hostService: HostService,
94 private router: Router,
95 private modalService: ModalService,
96 private osdService: OsdService,
97 private taskWrapper: TaskWrapperService
98 ) {
99 super();
100 this.resource = $localize`OSDs`;
101 this.action = this.actionLabels.CREATE;
102 this.features = {
103 encrypted: {
104 key: 'encrypted',
105 desc: $localize`Encryption`
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;
115 if (status.available) {
116 this.getDataDevices();
117 } else {
118 this.loadingNone();
119 }
120 });
121
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 });
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({
142 walSlots: new FormControl(0),
143 dbSlots: new FormControl(0),
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 }, {})
150 ),
151 deploymentOption: new FormControl(0)
152 });
153 }
154
155 getDataDevices() {
156 this.hostService.inventoryDeviceList().subscribe(
157 (devices: InventoryDevice[]) => {
158 this.allDevices = _.filter(devices, 'available');
159 this.availDevices = [...this.allDevices];
160 this.loadingReady();
161 },
162 () => {
163 this.allDevices = [];
164 this.availDevices = [];
165 this.loadingError();
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);
216
217 this.emitDriveGroup.emit(this.driveGroup);
218 }
219
220 onDevicesCleared(event: DevicesSelectionClearEvent) {
221 if (event.type === 'data') {
222 this.availDevices = [...this.allDevices];
223 this.walDeviceSelectionGroups.devices = [];
224 this.dbDeviceSelectionGroups.devices = [];
225 this.disableFeatures();
226 this.driveGroup.reset();
227 this.form.get('walSlots').setValue(0, { emitEvent: false });
228 this.form.get('dbSlots').setValue(0, { emitEvent: false });
229 } else {
230 this.availDevices = [...this.availDevices, ...event.clearedDevices];
231 this.driveGroup.clearDeviceSelection(event.type);
232 const slotControlName = `${event.type}Slots`;
233 this.form.get(slotControlName).setValue(0, { emitEvent: false });
234 }
235 }
236
237 emitDeploymentSelection() {
238 const option = this.form.get('deploymentOption').value;
239 const encrypted = this.form.get('encrypted').value;
240 this.emitDeploymentOption.emit({ option: option, encrypted: encrypted });
241 }
242
243 emitDeploymentMode() {
244 this.simpleDeployment = !this.simpleDeployment;
245 if (!this.simpleDeployment && this.dataDeviceSelectionGroups.devices.length === 0) {
246 this.disableFeatures();
247 } else {
248 this.enableFeatures();
249 }
250 this.emitMode.emit(this.simpleDeployment);
251 }
252
253 submit() {
254 if (this.simpleDeployment) {
255 const option = this.form.get('deploymentOption').value;
256 const encrypted = this.form.get('encrypted').value;
257 const deploymentSpec = { option: option, encrypted: encrypted };
258 const title = this.deploymentOptions.options[deploymentSpec.option].title;
259 const trackingId = `${title} deployment`;
260 this.taskWrapper
261 .wrapTaskAroundCall({
262 task: new FinishedTask('osd/' + URLVerbs.CREATE, {
263 tracking_id: trackingId
264 }),
265 call: this.osdService.create([deploymentSpec], trackingId, 'predefined')
266 })
267 .subscribe({
268 complete: () => {
269 this.router.navigate(['/osd']);
270 }
271 });
272 } else {
273 // use user name and timestamp for drive group name
274 const user = this.authStorageService.getUsername();
275 this.driveGroup.setName(`dashboard-${user}-${_.now()}`);
276 const modalRef = this.modalService.show(OsdCreationPreviewModalComponent, {
277 driveGroups: [this.driveGroup.spec]
278 });
279 modalRef.componentInstance.submitAction.subscribe(() => {
280 this.router.navigate(['/osd']);
281 });
282 this.previewButtonPanel.submitButton.loading = false;
283 }
284 }
285 }