]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / nfs / nfs-form / nfs-form.component.ts
1 import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
2 import { FormControl, Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
4
5 import { I18n } from '@ngx-translate/i18n-polyfill';
6 import * as _ from 'lodash';
7 import { forkJoin, Observable, of } from 'rxjs';
8 import { map, mergeMap } from 'rxjs/operators';
9
10 import { NfsService } from '../../../shared/api/nfs.service';
11 import { RgwUserService } from '../../../shared/api/rgw-user.service';
12 import { SelectMessages } from '../../../shared/components/select/select-messages.model';
13 import { SelectOption } from '../../../shared/components/select/select-option.model';
14 import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
15 import { CdFormGroup } from '../../../shared/forms/cd-form-group';
16 import { CdValidators } from '../../../shared/forms/cd-validators';
17 import { FinishedTask } from '../../../shared/models/finished-task';
18 import { Permission } from '../../../shared/models/permissions';
19 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
20 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
21 import { NfsFormClientComponent } from '../nfs-form-client/nfs-form-client.component';
22
23 @Component({
24 selector: 'cd-nfs-form',
25 templateUrl: './nfs-form.component.html',
26 styleUrls: ['./nfs-form.component.scss']
27 })
28 export class NfsFormComponent implements OnInit {
29 @ViewChild('nfsClients')
30 nfsClients: NfsFormClientComponent;
31
32 permission: Permission;
33 nfsForm: CdFormGroup;
34 isEdit = false;
35
36 cluster_id = null;
37 export_id = null;
38
39 isNewDirectory = false;
40 isNewBucket = false;
41 isDefaultCluster = false;
42
43 allClusters: string[] = null;
44 allDaemons = {};
45
46 allFsals: any[] = [];
47 allRgwUsers: any[] = [];
48 allCephxClients: any[] = null;
49 allFsNames: any[] = null;
50
51 nfsAccessType: any[] = this.nfsService.nfsAccessType;
52 nfsSquash: any[] = this.nfsService.nfsSquash;
53
54 daemonsSelections: SelectOption[] = [];
55 daemonsMessages = new SelectMessages(
56 { noOptions: this.i18n('There are no daemons available.') },
57 this.i18n
58 );
59
60 pathDataSource: Observable<any> = Observable.create((observer: any) => {
61 observer.next(this.nfsForm.getValue('path'));
62 }).pipe(
63 mergeMap((token: string) => this.getPathTypeahead(token)),
64 map((val: any) => val.paths)
65 );
66
67 bucketDataSource: Observable<any> = Observable.create((observer: any) => {
68 observer.next(this.nfsForm.getValue('path'));
69 }).pipe(mergeMap((token: string) => this.getBucketTypeahead(token)));
70
71 constructor(
72 private authStorageService: AuthStorageService,
73 private nfsService: NfsService,
74 private route: ActivatedRoute,
75 private router: Router,
76 private rgwUserService: RgwUserService,
77 private formBuilder: CdFormBuilder,
78 private taskWrapper: TaskWrapperService,
79 private cdRef: ChangeDetectorRef,
80 private i18n: I18n
81 ) {
82 this.permission = this.authStorageService.getPermissions().pool;
83 this.createForm();
84 }
85
86 ngOnInit() {
87 const promises: any[] = [
88 this.nfsService.daemon(),
89 this.nfsService.fsals(),
90 this.nfsService.clients(),
91 this.nfsService.filesystems()
92 ];
93
94 if (this.router.url.startsWith('/nfs/edit')) {
95 this.isEdit = true;
96 }
97
98 if (this.isEdit) {
99 this.route.params.subscribe((params: { cluster_id: string; export_id: string }) => {
100 this.cluster_id = decodeURIComponent(params.cluster_id);
101 this.export_id = decodeURIComponent(params.export_id);
102 promises.push(this.nfsService.get(this.cluster_id, this.export_id));
103
104 this.getData(promises);
105 });
106 } else {
107 this.getData(promises);
108 }
109 }
110
111 getData(promises) {
112 forkJoin(promises).subscribe((data: any[]) => {
113 this.resolveDaemons(data[0]);
114 this.resolvefsals(data[1]);
115 this.resolveClients(data[2]);
116 this.resolveFilesystems(data[3]);
117 if (data[4]) {
118 this.resolveModel(data[4]);
119 }
120 });
121 }
122
123 createForm() {
124 this.nfsForm = new CdFormGroup({
125 cluster_id: new FormControl('', {
126 validators: [Validators.required]
127 }),
128 daemons: new FormControl([]),
129 fsal: new CdFormGroup({
130 name: new FormControl('', {
131 validators: [Validators.required]
132 }),
133 user_id: new FormControl('', {
134 validators: [
135 CdValidators.requiredIf({
136 name: 'CEPH'
137 })
138 ]
139 }),
140 fs_name: new FormControl('', {
141 validators: [
142 CdValidators.requiredIf({
143 name: 'CEPH'
144 })
145 ]
146 }),
147 rgw_user_id: new FormControl('', {
148 validators: [
149 CdValidators.requiredIf({
150 name: 'RGW'
151 })
152 ]
153 })
154 }),
155 path: new FormControl(''),
156 protocolNfsv3: new FormControl(true, {
157 validators: [
158 CdValidators.requiredIf({ protocolNfsv4: false }, (value) => {
159 return !value;
160 })
161 ]
162 }),
163 protocolNfsv4: new FormControl(true, {
164 validators: [
165 CdValidators.requiredIf({ protocolNfsv3: false }, (value) => {
166 return !value;
167 })
168 ]
169 }),
170 tag: new FormControl(''),
171 pseudo: new FormControl('', {
172 validators: [
173 CdValidators.requiredIf({ protocolNfsv4: true }),
174 Validators.pattern('^/[^><|&()]*$')
175 ]
176 }),
177 access_type: new FormControl('RW', {
178 validators: [Validators.required]
179 }),
180 squash: new FormControl('', {
181 validators: [Validators.required]
182 }),
183 transportUDP: new FormControl(true, {
184 validators: [
185 CdValidators.requiredIf({ transportTCP: false }, (value) => {
186 return !value;
187 })
188 ]
189 }),
190 transportTCP: new FormControl(true, {
191 validators: [
192 CdValidators.requiredIf({ transportUDP: false }, (value) => {
193 return !value;
194 })
195 ]
196 }),
197 clients: this.formBuilder.array([]),
198 security_label: new FormControl(false),
199 sec_label_xattr: new FormControl(
200 'security.selinux',
201 CdValidators.requiredIf({ security_label: true, 'fsal.name': 'CEPH' })
202 )
203 });
204
205 this.nfsForm.get('protocolNfsv4').valueChanges.subscribe(() => {
206 this.nfsForm.get('pseudo').updateValueAndValidity({ emitEvent: false });
207 });
208 }
209
210 resolveModel(res) {
211 if (res.fsal.name === 'CEPH') {
212 res.sec_label_xattr = res.fsal.sec_label_xattr;
213 }
214
215 this.daemonsSelections = _.map(
216 this.allDaemons[res.cluster_id],
217 (daemon) => new SelectOption(res.daemons.indexOf(daemon) !== -1, daemon, '')
218 );
219 this.daemonsSelections = [...this.daemonsSelections];
220
221 res.protocolNfsv3 = res.protocols.indexOf(3) !== -1;
222 res.protocolNfsv4 = res.protocols.indexOf(4) !== -1;
223 delete res.protocols;
224
225 res.transportTCP = res.transports.indexOf('TCP') !== -1;
226 res.transportUDP = res.transports.indexOf('UDP') !== -1;
227 delete res.transports;
228
229 res.clients.forEach((client) => {
230 let addressStr = '';
231 client.addresses.forEach((address) => {
232 addressStr += address + ', ';
233 });
234 if (addressStr.length >= 2) {
235 addressStr = addressStr.substring(0, addressStr.length - 2);
236 }
237 client.addresses = addressStr;
238 });
239
240 this.nfsForm.patchValue(res);
241 this.setPathValidation();
242 this.nfsClients.resolveModel(res.clients);
243 }
244
245 resolveDaemons(daemons) {
246 daemons = _.sortBy(daemons, ['daemon_id']);
247
248 this.allClusters = _(daemons)
249 .map((daemon) => daemon.cluster_id)
250 .sortedUniq()
251 .value();
252
253 _.forEach(this.allClusters, (cluster) => {
254 this.allDaemons[cluster] = [];
255 });
256
257 _.forEach(daemons, (daemon) => {
258 this.allDaemons[daemon.cluster_id].push(daemon.daemon_id);
259 });
260
261 const hasOneCluster = _.isArray(this.allClusters) && this.allClusters.length === 1;
262 this.isDefaultCluster = hasOneCluster && this.allClusters[0] === '_default_';
263 if (hasOneCluster) {
264 this.nfsForm.patchValue({
265 cluster_id: this.allClusters[0]
266 });
267 this.onClusterChange();
268 }
269 }
270
271 resolvefsals(res: string[]) {
272 res.forEach((fsal) => {
273 const fsalItem = this.nfsService.nfsFsal.find((currentFsalItem) => {
274 return fsal === currentFsalItem.value;
275 });
276
277 if (_.isObjectLike(fsalItem)) {
278 this.allFsals.push(fsalItem);
279 if (fsalItem.value === 'RGW') {
280 this.rgwUserService.list().subscribe((result: any) => {
281 result.forEach((user) => {
282 if (user.suspended === 0 && user.keys.length > 0) {
283 this.allRgwUsers.push(user.user_id);
284 }
285 });
286 });
287 }
288 }
289 });
290
291 if (this.allFsals.length === 1 && _.isUndefined(this.nfsForm.getValue('fsal'))) {
292 this.nfsForm.patchValue({
293 fsal: this.allFsals[0]
294 });
295 }
296 }
297
298 resolveClients(clients) {
299 this.allCephxClients = clients;
300 }
301
302 resolveFilesystems(filesystems) {
303 this.allFsNames = filesystems;
304 if (filesystems.length === 1) {
305 this.nfsForm.patchValue({
306 fsal: {
307 fs_name: filesystems[0].name
308 }
309 });
310 }
311 }
312
313 fsalChangeHandler() {
314 this.nfsForm.patchValue({
315 tag: this._generateTag(),
316 pseudo: this._generatePseudo()
317 });
318
319 this.setPathValidation();
320
321 this.cdRef.detectChanges();
322 }
323
324 setPathValidation() {
325 if (this.nfsForm.getValue('name') === 'RGW') {
326 this.nfsForm
327 .get('path')
328 .setValidators([Validators.required, Validators.pattern('^(/|[^/><|&()#?]+)$')]);
329 } else {
330 this.nfsForm
331 .get('path')
332 .setValidators([Validators.required, Validators.pattern('^/[^><|&()?]*$')]);
333 }
334 }
335
336 rgwUserIdChangeHandler() {
337 this.nfsForm.patchValue({
338 pseudo: this._generatePseudo()
339 });
340 }
341
342 getAccessTypeHelp(accessType) {
343 const accessTypeItem = this.nfsAccessType.find((currentAccessTypeItem) => {
344 if (accessType === currentAccessTypeItem.value) {
345 return currentAccessTypeItem;
346 }
347 });
348 return _.isObjectLike(accessTypeItem) ? accessTypeItem.help : '';
349 }
350
351 getId() {
352 if (
353 _.isString(this.nfsForm.getValue('cluster_id')) &&
354 _.isString(this.nfsForm.getValue('path'))
355 ) {
356 return this.nfsForm.getValue('cluster_id') + ':' + this.nfsForm.getValue('path');
357 }
358 return '';
359 }
360
361 getPathTypeahead(path) {
362 if (!_.isString(path) || path === '/') {
363 return of([]);
364 }
365
366 return this.nfsService.lsDir(path);
367 }
368
369 pathChangeHandler() {
370 this.nfsForm.patchValue({
371 pseudo: this._generatePseudo()
372 });
373
374 const path = this.nfsForm.getValue('path');
375 this.getPathTypeahead(path).subscribe((res: any) => {
376 this.isNewDirectory = path !== '/' && res.paths.indexOf(path) === -1;
377 });
378 }
379
380 bucketChangeHandler() {
381 this.nfsForm.patchValue({
382 tag: this._generateTag(),
383 pseudo: this._generatePseudo()
384 });
385
386 const bucket = this.nfsForm.getValue('path');
387 this.getBucketTypeahead(bucket).subscribe((res: any) => {
388 this.isNewBucket = bucket !== '' && res.indexOf(bucket) === -1;
389 });
390 }
391
392 getBucketTypeahead(path: string): Observable<any> {
393 const rgwUserId = this.nfsForm.getValue('rgw_user_id');
394
395 if (_.isString(rgwUserId) && _.isString(path) && path !== '/' && path !== '') {
396 return this.nfsService.buckets(rgwUserId);
397 } else {
398 return of([]);
399 }
400 }
401
402 _generateTag() {
403 let newTag = this.nfsForm.getValue('tag');
404 if (!this.nfsForm.get('tag').dirty) {
405 newTag = undefined;
406 if (this.nfsForm.getValue('fsal') === 'RGW') {
407 newTag = this.nfsForm.getValue('path');
408 }
409 }
410 return newTag;
411 }
412
413 _generatePseudo() {
414 let newPseudo = this.nfsForm.getValue('pseudo');
415 if (this.nfsForm.get('pseudo') && !this.nfsForm.get('pseudo').dirty) {
416 newPseudo = undefined;
417 if (this.nfsForm.getValue('fsal') === 'CEPH') {
418 newPseudo = '/cephfs';
419 if (_.isString(this.nfsForm.getValue('path'))) {
420 newPseudo += this.nfsForm.getValue('path');
421 }
422 } else if (this.nfsForm.getValue('fsal') === 'RGW') {
423 if (_.isString(this.nfsForm.getValue('rgw_user_id'))) {
424 newPseudo = '/' + this.nfsForm.getValue('rgw_user_id');
425 if (_.isString(this.nfsForm.getValue('path'))) {
426 newPseudo += '/' + this.nfsForm.getValue('path');
427 }
428 }
429 }
430 }
431 return newPseudo;
432 }
433
434 onClusterChange() {
435 const cluster_id = this.nfsForm.getValue('cluster_id');
436 this.daemonsSelections = _.map(
437 this.allDaemons[cluster_id],
438 (daemon) => new SelectOption(false, daemon, '')
439 );
440 this.daemonsSelections = [...this.daemonsSelections];
441 this.nfsForm.patchValue({ daemons: [] });
442 }
443
444 removeDaemon(index, daemon) {
445 this.daemonsSelections.forEach((value) => {
446 if (value.name === daemon) {
447 value.selected = false;
448 }
449 });
450
451 const daemons = this.nfsForm.get('daemons');
452 daemons.value.splice(index, 1);
453 daemons.setValue(daemons.value);
454
455 return false;
456 }
457
458 onDaemonSelection() {
459 this.nfsForm.get('daemons').setValue(this.nfsForm.getValue('daemons'));
460 }
461
462 submitAction() {
463 let action: Observable<any>;
464 const requestModel = this._buildRequest();
465
466 if (this.isEdit) {
467 action = this.taskWrapper.wrapTaskAroundCall({
468 task: new FinishedTask('nfs/edit', {
469 cluster_id: this.cluster_id,
470 export_id: this.export_id
471 }),
472 call: this.nfsService.update(this.cluster_id, this.export_id, requestModel)
473 });
474 } else {
475 // Create
476 action = this.taskWrapper.wrapTaskAroundCall({
477 task: new FinishedTask('nfs/create', {
478 path: requestModel.path,
479 fsal: requestModel.fsal,
480 cluster_id: requestModel.cluster_id
481 }),
482 call: this.nfsService.create(requestModel)
483 });
484 }
485
486 action.subscribe(
487 undefined,
488 () => this.nfsForm.setErrors({ cdSubmitButton: true }),
489 () => this.router.navigate(['/nfs'])
490 );
491 }
492
493 _buildRequest() {
494 const requestModel: any = _.cloneDeep(this.nfsForm.value);
495
496 if (_.isUndefined(requestModel.tag) || requestModel.tag === '') {
497 requestModel.tag = null;
498 }
499
500 if (this.isEdit) {
501 requestModel.export_id = this.export_id;
502 }
503
504 if (requestModel.fsal.name === 'CEPH') {
505 delete requestModel.fsal.rgw_user_id;
506 } else {
507 delete requestModel.fsal.fs_name;
508 delete requestModel.fsal.user_id;
509 }
510
511 requestModel.protocols = [];
512 if (requestModel.protocolNfsv3) {
513 requestModel.protocols.push(3);
514 } else {
515 requestModel.tag = null;
516 }
517 delete requestModel.protocolNfsv3;
518 if (requestModel.protocolNfsv4) {
519 requestModel.protocols.push(4);
520 } else {
521 requestModel.pseudo = null;
522 }
523 delete requestModel.protocolNfsv4;
524
525 requestModel.transports = [];
526 if (requestModel.transportTCP) {
527 requestModel.transports.push('TCP');
528 }
529 delete requestModel.transportTCP;
530 if (requestModel.transportUDP) {
531 requestModel.transports.push('UDP');
532 }
533 delete requestModel.transportUDP;
534
535 requestModel.clients.forEach((client) => {
536 if (_.isString(client.addresses)) {
537 client.addresses = _(client.addresses)
538 .split(/[ ,]+/)
539 .uniq()
540 .filter((address) => address !== '')
541 .value();
542 } else {
543 client.addresses = [];
544 }
545 });
546
547 if (requestModel.security_label === false || requestModel.fsal.name === 'RGW') {
548 requestModel.fsal.sec_label_xattr = null;
549 } else {
550 requestModel.fsal.sec_label_xattr = requestModel.sec_label_xattr;
551 }
552 delete requestModel.sec_label_xattr;
553
554 return requestModel;
555 }
556 }