]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
c0f66ed33626f14d8e27728202552827b018fea0
[ceph.git] /
1 import { HttpParams } from '@angular/common/http';
2 import { Component, Input, OnInit, ViewChild } from '@angular/core';
3 import { AbstractControl, UntypedFormControl, Validators } from '@angular/forms';
4 import { ActivatedRoute, Router } from '@angular/router';
5
6 import { NgbActiveModal, NgbModalRef, NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
7 import _ from 'lodash';
8 import { forkJoin, merge, Observable, Subject, Subscription } from 'rxjs';
9 import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators';
10 import { CreateRgwServiceEntitiesComponent } from '~/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component';
11 import { RgwRealm, RgwZonegroup, RgwZone } from '~/app/ceph/rgw/models/rgw-multisite';
12
13 import { CephServiceService } from '~/app/shared/api/ceph-service.service';
14 import { HostService } from '~/app/shared/api/host.service';
15 import { PoolService } from '~/app/shared/api/pool.service';
16 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
17 import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
18 import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
19 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
20 import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
21 import { SelectOption } from '~/app/shared/components/select/select-option.model';
22 import {
23   ActionLabelsI18n,
24   TimerServiceInterval,
25   URLVerbs
26 } from '~/app/shared/constants/app.constants';
27 import { CdForm } from '~/app/shared/forms/cd-form';
28 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
29 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
30 import { CdValidators } from '~/app/shared/forms/cd-validators';
31 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
32 import { FinishedTask } from '~/app/shared/models/finished-task';
33 import { CephServiceSpec } from '~/app/shared/models/service.interface';
34 import { ModalService } from '~/app/shared/services/modal.service';
35 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
36 import { TimerService } from '~/app/shared/services/timer.service';
37
38 @Component({
39   selector: 'cd-service-form',
40   templateUrl: './service-form.component.html',
41   styleUrls: ['./service-form.component.scss']
42 })
43 export class ServiceFormComponent extends CdForm implements OnInit {
44   public sub = new Subscription();
45
46   readonly MDS_SVC_ID_PATTERN = /^[a-zA-Z_.-][a-zA-Z0-9_.-]*$/;
47   readonly SNMP_DESTINATION_PATTERN = /^[^\:]+:[0-9]/;
48   readonly SNMP_ENGINE_ID_PATTERN = /^[0-9A-Fa-f]{10,64}/g;
49   readonly INGRESS_SUPPORTED_SERVICE_TYPES = ['rgw', 'nfs'];
50   readonly SMB_CONFIG_URI_PATTERN = /^(http:|https:|rados:|rados:mon-config-key:)/;
51   @ViewChild(NgbTypeahead, { static: false })
52   typeahead: NgbTypeahead;
53
54   @Input() hiddenServices: string[] = [];
55
56   @Input() editing = false;
57
58   @Input() serviceName: string;
59
60   @Input() serviceType: string;
61
62   serviceForm: CdFormGroup;
63   action: string;
64   resource: string;
65   serviceTypes: string[] = [];
66   serviceIds: string[] = [];
67   hosts: any;
68   labels: string[];
69   labelClick = new Subject<string>();
70   labelFocus = new Subject<string>();
71   pools: Array<object>;
72   services: Array<CephServiceSpec> = [];
73   pageURL: string;
74   serviceList: CephServiceSpec[];
75   multisiteInfo: object[] = [];
76   defaultRealmId = '';
77   defaultZonegroupId = '';
78   defaultZoneId = '';
79   realmList: RgwRealm[] = [];
80   zonegroupList: RgwZonegroup[] = [];
81   zoneList: RgwZone[] = [];
82   bsModalRef: NgbModalRef;
83   defaultZonegroup: RgwZonegroup;
84   showRealmCreationForm = false;
85   defaultsInfo: { defaultRealmName: string; defaultZonegroupName: string; defaultZoneName: string };
86   realmNames: string[];
87   zonegroupNames: string[];
88   zoneNames: string[];
89   smbFeaturesList = ['domain'];
90
91   constructor(
92     public actionLabels: ActionLabelsI18n,
93     private cephServiceService: CephServiceService,
94     private formBuilder: CdFormBuilder,
95     private hostService: HostService,
96     private poolService: PoolService,
97     private router: Router,
98     private taskWrapperService: TaskWrapperService,
99     public timerService: TimerService,
100     public timerServiceVariable: TimerServiceInterval,
101     public rgwRealmService: RgwRealmService,
102     public rgwZonegroupService: RgwZonegroupService,
103     public rgwZoneService: RgwZoneService,
104     public rgwMultisiteService: RgwMultisiteService,
105     private route: ActivatedRoute,
106     public activeModal: NgbActiveModal,
107     public modalService: ModalService
108   ) {
109     super();
110     this.resource = $localize`service`;
111     this.hosts = {
112       options: [],
113       messages: new SelectMessages({
114         empty: $localize`There are no hosts.`,
115         filter: $localize`Filter hosts`
116       })
117     };
118     this.createForm();
119   }
120
121   createForm() {
122     this.serviceForm = this.formBuilder.group({
123       // Global
124       service_type: [null, [Validators.required]],
125       service_id: [
126         null,
127         [
128           CdValidators.composeIf(
129             {
130               service_type: 'mds'
131             },
132             [
133               Validators.required,
134               CdValidators.custom('mdsPattern', (value: string) => {
135                 if (_.isEmpty(value)) {
136                   return false;
137                 }
138                 return !this.MDS_SVC_ID_PATTERN.test(value);
139               })
140             ]
141           ),
142           CdValidators.requiredIf({
143             service_type: 'nfs'
144           }),
145           CdValidators.requiredIf({
146             service_type: 'iscsi'
147           }),
148           CdValidators.requiredIf({
149             service_type: 'ingress'
150           }),
151           CdValidators.requiredIf({
152             service_type: 'smb'
153           }),
154           CdValidators.composeIf(
155             {
156               service_type: 'rgw'
157             },
158             [Validators.required]
159           ),
160           CdValidators.custom('uniqueName', (service_id: string) => {
161             return this.serviceIds && this.serviceIds.includes(service_id);
162           })
163         ]
164       ],
165       placement: ['hosts'],
166       label: [
167         null,
168         [
169           CdValidators.requiredIf({
170             placement: 'label',
171             unmanaged: false
172           })
173         ]
174       ],
175       hosts: [[]],
176       count: [null, [CdValidators.number(false)]],
177       unmanaged: [false],
178       // iSCSI
179       pool: [
180         null,
181         [
182           CdValidators.requiredIf({
183             service_type: 'iscsi'
184           })
185         ]
186       ],
187       // RGW
188       rgw_frontend_port: [null, [CdValidators.number(false)]],
189       realm_name: [null],
190       zonegroup_name: [null],
191       zone_name: [null],
192       // iSCSI
193       trusted_ip_list: [null],
194       api_port: [null, [CdValidators.number(false)]],
195       api_user: [
196         null,
197         [
198           CdValidators.requiredIf({
199             service_type: 'iscsi',
200             unmanaged: false
201           })
202         ]
203       ],
204       api_password: [
205         null,
206         [
207           CdValidators.requiredIf({
208             service_type: 'iscsi',
209             unmanaged: false
210           })
211         ]
212       ],
213       // smb
214       cluster_id: [
215         null,
216         [
217           CdValidators.requiredIf({
218             service_type: 'smb'
219           })
220         ]
221       ],
222       features: new CdFormGroup(
223         this.smbFeaturesList.reduce((acc: object, e) => {
224           acc[e] = new UntypedFormControl(false);
225           return acc;
226         }, {})
227       ),
228       config_uri: [
229         null,
230         [
231           CdValidators.composeIf(
232             {
233               service_type: 'smb'
234             },
235             [
236               Validators.required,
237               CdValidators.custom('configUriPattern', (value: string) => {
238                 if (_.isEmpty(value)) {
239                   return false;
240                 }
241                 return !this.SMB_CONFIG_URI_PATTERN.test(value);
242               })
243             ]
244           )
245         ]
246       ],
247       custom_dns: [null],
248       join_sources: [null],
249       user_sources: [null],
250       include_ceph_users: [null],
251       // Ingress
252       backend_service: [
253         null,
254         [
255           CdValidators.requiredIf({
256             service_type: 'ingress'
257           })
258         ]
259       ],
260       virtual_ip: [
261         null,
262         [
263           CdValidators.requiredIf({
264             service_type: 'ingress'
265           })
266         ]
267       ],
268       frontend_port: [
269         null,
270         [
271           CdValidators.number(false),
272           CdValidators.requiredIf({
273             service_type: 'ingress'
274           })
275         ]
276       ],
277       monitor_port: [
278         null,
279         [
280           CdValidators.number(false),
281           CdValidators.requiredIf({
282             service_type: 'ingress'
283           })
284         ]
285       ],
286       virtual_interface_networks: [null],
287       // RGW, Ingress & iSCSI
288       ssl: [false],
289       ssl_cert: [
290         '',
291         [
292           CdValidators.composeIf(
293             {
294               service_type: 'rgw',
295               unmanaged: false,
296               ssl: true
297             },
298             [Validators.required, CdValidators.pemCert()]
299           ),
300           CdValidators.composeIf(
301             {
302               service_type: 'iscsi',
303               unmanaged: false,
304               ssl: true
305             },
306             [Validators.required, CdValidators.sslCert()]
307           ),
308           CdValidators.composeIf(
309             {
310               service_type: 'ingress',
311               unmanaged: false,
312               ssl: true
313             },
314             [Validators.required, CdValidators.pemCert()]
315           )
316         ]
317       ],
318       ssl_key: [
319         '',
320         [
321           CdValidators.composeIf(
322             {
323               service_type: 'iscsi',
324               unmanaged: false,
325               ssl: true
326             },
327             [Validators.required, CdValidators.sslPrivKey()]
328           )
329         ]
330       ],
331       // snmp-gateway
332       snmp_version: [
333         null,
334         [
335           CdValidators.requiredIf({
336             service_type: 'snmp-gateway'
337           })
338         ]
339       ],
340       snmp_destination: [
341         null,
342         {
343           validators: [
344             CdValidators.requiredIf({
345               service_type: 'snmp-gateway'
346             }),
347             CdValidators.custom('snmpDestinationPattern', (value: string) => {
348               if (_.isEmpty(value)) {
349                 return false;
350               }
351               return !this.SNMP_DESTINATION_PATTERN.test(value);
352             })
353           ]
354         }
355       ],
356       engine_id: [
357         null,
358         [
359           CdValidators.requiredIf({
360             service_type: 'snmp-gateway'
361           }),
362           CdValidators.custom('snmpEngineIdPattern', (value: string) => {
363             if (_.isEmpty(value)) {
364               return false;
365             }
366             return !this.SNMP_ENGINE_ID_PATTERN.test(value);
367           })
368         ]
369       ],
370       auth_protocol: [
371         'SHA',
372         [
373           CdValidators.requiredIf({
374             service_type: 'snmp-gateway'
375           })
376         ]
377       ],
378       privacy_protocol: [null],
379       snmp_community: [
380         null,
381         [
382           CdValidators.requiredIf({
383             snmp_version: 'V2c'
384           })
385         ]
386       ],
387       snmp_v3_auth_username: [
388         null,
389         [
390           CdValidators.requiredIf({
391             service_type: 'snmp-gateway'
392           })
393         ]
394       ],
395       snmp_v3_auth_password: [
396         null,
397         [
398           CdValidators.requiredIf({
399             service_type: 'snmp-gateway'
400           })
401         ]
402       ],
403       snmp_v3_priv_password: [
404         null,
405         [
406           CdValidators.requiredIf({
407             privacy_protocol: { op: '!empty' }
408           })
409         ]
410       ],
411       grafana_port: [null, [CdValidators.number(false)]],
412       grafana_admin_password: [null]
413     });
414   }
415
416   ngOnInit(): void {
417     this.action = this.actionLabels.CREATE;
418     if (this.router.url.includes('services/(modal:create')) {
419       this.pageURL = 'services';
420     } else if (this.router.url.includes('services/(modal:edit')) {
421       this.editing = true;
422       this.pageURL = 'services';
423       this.route.params.subscribe((params: { type: string; name: string }) => {
424         this.serviceName = params.name;
425         this.serviceType = params.type;
426       });
427     }
428
429     this.cephServiceService
430       .list(new HttpParams({ fromObject: { limit: -1, offset: 0 } }))
431       .observable.subscribe((services: CephServiceSpec[]) => {
432         this.serviceList = services;
433         this.services = services.filter((service: any) =>
434           this.INGRESS_SUPPORTED_SERVICE_TYPES.includes(service.service_type)
435         );
436       });
437
438     this.cephServiceService.getKnownTypes().subscribe((resp: Array<string>) => {
439       // Remove service types:
440       // osd       - This is deployed a different way.
441       // container - This should only be used in the CLI.
442       this.hiddenServices.push('osd', 'container');
443
444       this.serviceTypes = _.difference(resp, this.hiddenServices).sort();
445     });
446     const hostContext = new CdTableFetchDataContext(() => undefined);
447     this.hostService.list(hostContext.toParams(), 'false').subscribe((resp: object[]) => {
448       const options: SelectOption[] = [];
449       _.forEach(resp, (host: object) => {
450         if (_.get(host, 'sources.orchestrator', false)) {
451           const option = new SelectOption(false, _.get(host, 'hostname'), '');
452           options.push(option);
453         }
454       });
455       this.hosts.options = [...options];
456     });
457     this.hostService.getLabels().subscribe((resp: string[]) => {
458       this.labels = resp;
459     });
460     this.poolService.getList().subscribe((resp: Array<object>) => {
461       this.pools = resp;
462     });
463
464     if (this.editing) {
465       this.action = this.actionLabels.EDIT;
466       this.disableForEditing(this.serviceType);
467       this.cephServiceService
468         .list(new HttpParams({ fromObject: { limit: -1, offset: 0 } }), this.serviceName)
469         .observable.subscribe((response: CephServiceSpec[]) => {
470           const formKeys = ['service_type', 'service_id', 'unmanaged'];
471           formKeys.forEach((keys) => {
472             this.serviceForm.get(keys).setValue(response[0][keys]);
473           });
474           if (!response[0]['unmanaged']) {
475             const placementKey = Object.keys(response[0]['placement'])[0];
476             let placementValue: string;
477             ['hosts', 'label'].indexOf(placementKey) >= 0
478               ? (placementValue = placementKey)
479               : (placementValue = 'hosts');
480             this.serviceForm.get('placement').setValue(placementValue);
481             this.serviceForm.get('count').setValue(response[0]['placement']['count']);
482             if (response[0]?.placement[placementValue]) {
483               this.serviceForm.get(placementValue).setValue(response[0]?.placement[placementValue]);
484             }
485           }
486           switch (this.serviceType) {
487             case 'iscsi':
488               const specKeys = ['pool', 'api_password', 'api_user', 'trusted_ip_list', 'api_port'];
489               specKeys.forEach((key) => {
490                 this.serviceForm.get(key).setValue(response[0].spec[key]);
491               });
492               this.serviceForm.get('ssl').setValue(response[0].spec?.api_secure);
493               if (response[0].spec?.api_secure) {
494                 this.serviceForm.get('ssl_cert').setValue(response[0].spec?.ssl_cert);
495                 this.serviceForm.get('ssl_key').setValue(response[0].spec?.ssl_key);
496               }
497               break;
498             case 'rgw':
499               this.serviceForm
500                 .get('rgw_frontend_port')
501                 .setValue(response[0].spec?.rgw_frontend_port);
502               this.getServiceIds(
503                 'rgw',
504                 response[0].spec?.rgw_realm,
505                 response[0].spec?.rgw_zonegroup,
506                 response[0].spec?.rgw_zone
507               );
508               this.serviceForm.get('ssl').setValue(response[0].spec?.ssl);
509               if (response[0].spec?.ssl) {
510                 this.serviceForm
511                   .get('ssl_cert')
512                   .setValue(response[0].spec?.rgw_frontend_ssl_certificate);
513               }
514               break;
515             case 'ingress':
516               const ingressSpecKeys = [
517                 'backend_service',
518                 'virtual_ip',
519                 'frontend_port',
520                 'monitor_port',
521                 'virtual_interface_networks',
522                 'ssl'
523               ];
524               ingressSpecKeys.forEach((key) => {
525                 this.serviceForm.get(key).setValue(response[0].spec[key]);
526               });
527               if (response[0].spec?.ssl) {
528                 this.serviceForm.get('ssl_cert').setValue(response[0].spec?.ssl_cert);
529                 this.serviceForm.get('ssl_key').setValue(response[0].spec?.ssl_key);
530               }
531               break;
532             case 'smb':
533               const smbSpecKeys = [
534                 'cluster_id',
535                 'config_uri',
536                 'features',
537                 'join_sources',
538                 'user_sources',
539                 'custom_dns',
540                 'include_ceph_users'
541               ];
542               smbSpecKeys.forEach((key) => {
543                 if (key === 'features') {
544                   if (response[0].spec?.features) {
545                     response[0].spec.features.forEach((feature) => {
546                       this.serviceForm.get(`features.${feature}`).setValue(true);
547                     });
548                   }
549                 } else {
550                   this.serviceForm.get(key).setValue(response[0].spec[key]);
551                 }
552               });
553               break;
554             case 'snmp-gateway':
555               const snmpCommonSpecKeys = ['snmp_version', 'snmp_destination'];
556               snmpCommonSpecKeys.forEach((key) => {
557                 this.serviceForm.get(key).setValue(response[0].spec[key]);
558               });
559               if (this.serviceForm.getValue('snmp_version') === 'V3') {
560                 const snmpV3SpecKeys = [
561                   'engine_id',
562                   'auth_protocol',
563                   'privacy_protocol',
564                   'snmp_v3_auth_username',
565                   'snmp_v3_auth_password',
566                   'snmp_v3_priv_password'
567                 ];
568                 snmpV3SpecKeys.forEach((key) => {
569                   if (key !== null) {
570                     if (
571                       key === 'snmp_v3_auth_username' ||
572                       key === 'snmp_v3_auth_password' ||
573                       key === 'snmp_v3_priv_password'
574                     ) {
575                       this.serviceForm.get(key).setValue(response[0].spec['credentials'][key]);
576                     } else {
577                       this.serviceForm.get(key).setValue(response[0].spec[key]);
578                     }
579                   }
580                 });
581               } else {
582                 this.serviceForm
583                   .get('snmp_community')
584                   .setValue(response[0].spec['credentials']['snmp_community']);
585               }
586               break;
587             case 'grafana':
588               this.serviceForm.get('grafana_port').setValue(response[0].spec.port);
589               this.serviceForm
590                 .get('grafana_admin_password')
591                 .setValue(response[0].spec.initial_admin_password);
592               break;
593           }
594         });
595     }
596   }
597
598   getDefaultsEntities(
599     defaultRealmId: string,
600     defaultZonegroupId: string,
601     defaultZoneId: string
602   ): { defaultRealmName: string; defaultZonegroupName: string; defaultZoneName: string } {
603     const defaultRealm = this.realmList.find((x: { id: string }) => x.id === defaultRealmId);
604     const defaultZonegroup = this.zonegroupList.find(
605       (x: { id: string }) => x.id === defaultZonegroupId
606     );
607     const defaultZone = this.zoneList.find((x: { id: string }) => x.id === defaultZoneId);
608     const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
609     const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : 'default';
610     const defaultZoneName = defaultZone !== undefined ? defaultZone.name : 'default';
611     if (defaultZonegroupName === 'default' && !this.zonegroupNames.includes(defaultZonegroupName)) {
612       const defaultZonegroup = new RgwZonegroup();
613       defaultZonegroup.name = 'default';
614       this.zonegroupList.push(defaultZonegroup);
615     }
616     if (defaultZoneName === 'default' && !this.zoneNames.includes(defaultZoneName)) {
617       const defaultZone = new RgwZone();
618       defaultZone.name = 'default';
619       this.zoneList.push(defaultZone);
620     }
621     return {
622       defaultRealmName: defaultRealmName,
623       defaultZonegroupName: defaultZonegroupName,
624       defaultZoneName: defaultZoneName
625     };
626   }
627
628   getServiceIds(
629     selectedServiceType: string,
630     realm_name?: string,
631     zonegroup_name?: string,
632     zone_name?: string
633   ) {
634     this.serviceIds = this.serviceList
635       ?.filter((service) => service['service_type'] === selectedServiceType)
636       .map((service) => service['service_id']);
637
638     if (selectedServiceType === 'rgw') {
639       const observables = [
640         this.rgwRealmService.getAllRealmsInfo(),
641         this.rgwZonegroupService.getAllZonegroupsInfo(),
642         this.rgwZoneService.getAllZonesInfo()
643       ];
644       this.sub = forkJoin(observables).subscribe(
645         (multisiteInfo: [object, object, object]) => {
646           this.multisiteInfo = multisiteInfo;
647           this.realmList =
648             this.multisiteInfo[0] !== undefined && this.multisiteInfo[0].hasOwnProperty('realms')
649               ? this.multisiteInfo[0]['realms']
650               : [];
651           this.zonegroupList =
652             this.multisiteInfo[1] !== undefined &&
653             this.multisiteInfo[1].hasOwnProperty('zonegroups')
654               ? this.multisiteInfo[1]['zonegroups']
655               : [];
656           this.zoneList =
657             this.multisiteInfo[2] !== undefined && this.multisiteInfo[2].hasOwnProperty('zones')
658               ? this.multisiteInfo[2]['zones']
659               : [];
660           this.realmNames = this.realmList.map((realm) => {
661             return realm['name'];
662           });
663           this.zonegroupNames = this.zonegroupList.map((zonegroup) => {
664             return zonegroup['name'];
665           });
666           this.zoneNames = this.zoneList.map((zone) => {
667             return zone['name'];
668           });
669           this.defaultRealmId = multisiteInfo[0]['default_realm'];
670           this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
671           this.defaultZoneId = multisiteInfo[2]['default_zone'];
672           this.defaultsInfo = this.getDefaultsEntities(
673             this.defaultRealmId,
674             this.defaultZonegroupId,
675             this.defaultZoneId
676           );
677           if (!this.editing) {
678             this.serviceForm.get('realm_name').setValue(this.defaultsInfo['defaultRealmName']);
679             this.serviceForm
680               .get('zonegroup_name')
681               .setValue(this.defaultsInfo['defaultZonegroupName']);
682             this.serviceForm.get('zone_name').setValue(this.defaultsInfo['defaultZoneName']);
683           } else {
684             if (realm_name && !this.realmNames.includes(realm_name)) {
685               const realm = new RgwRealm();
686               realm.name = realm_name;
687               this.realmList.push(realm);
688             }
689             if (zonegroup_name && !this.zonegroupNames.includes(zonegroup_name)) {
690               const zonegroup = new RgwZonegroup();
691               zonegroup.name = zonegroup_name;
692               this.zonegroupList.push(zonegroup);
693             }
694             if (zone_name && !this.zoneNames.includes(zone_name)) {
695               const zone = new RgwZone();
696               zone.name = zone_name;
697               this.zoneList.push(zone);
698             }
699             if (zonegroup_name === undefined && zone_name === undefined) {
700               zonegroup_name = 'default';
701               zone_name = 'default';
702             }
703             this.serviceForm.get('realm_name').setValue(realm_name);
704             this.serviceForm.get('zonegroup_name').setValue(zonegroup_name);
705             this.serviceForm.get('zone_name').setValue(zone_name);
706           }
707           if (this.realmList.length === 0) {
708             this.showRealmCreationForm = true;
709           } else {
710             this.showRealmCreationForm = false;
711           }
712         },
713         (_error) => {
714           const defaultZone = new RgwZone();
715           defaultZone.name = 'default';
716           const defaultZonegroup = new RgwZonegroup();
717           defaultZonegroup.name = 'default';
718           this.zoneList.push(defaultZone);
719           this.zonegroupList.push(defaultZonegroup);
720         }
721       );
722     }
723   }
724
725   disableForEditing(serviceType: string) {
726     const disableForEditKeys = ['service_type', 'service_id'];
727     disableForEditKeys.forEach((key) => {
728       this.serviceForm.get(key).disable();
729     });
730     switch (serviceType) {
731       case 'ingress':
732         this.serviceForm.get('backend_service').disable();
733     }
734   }
735
736   searchLabels = (text$: Observable<string>) => {
737     return merge(
738       text$.pipe(debounceTime(200), distinctUntilChanged()),
739       this.labelFocus,
740       this.labelClick.pipe(filter(() => !this.typeahead.isPopupOpen()))
741     ).pipe(
742       map((value) =>
743         this.labels
744           .filter((label: string) => label.toLowerCase().indexOf(value.toLowerCase()) > -1)
745           .slice(0, 10)
746       )
747     );
748   };
749
750   fileUpload(files: FileList, controlName: string) {
751     const file: File = files[0];
752     const reader = new FileReader();
753     reader.addEventListener('load', (event: ProgressEvent<FileReader>) => {
754       const control: AbstractControl = this.serviceForm.get(controlName);
755       control.setValue(event.target.result);
756       control.markAsDirty();
757       control.markAsTouched();
758       control.updateValueAndValidity();
759     });
760     reader.readAsText(file, 'utf8');
761   }
762
763   prePopulateId() {
764     const control: AbstractControl = this.serviceForm.get('service_id');
765     const backendService = this.serviceForm.getValue('backend_service');
766     // Set Id as read-only
767     control.reset({ value: backendService, disabled: true });
768   }
769
770   onSubmit() {
771     const self = this;
772     const values: object = this.serviceForm.getRawValue();
773     const serviceType: string = values['service_type'];
774     let taskUrl = `service/${URLVerbs.CREATE}`;
775     if (this.editing) {
776       taskUrl = `service/${URLVerbs.EDIT}`;
777     }
778     const serviceSpec: object = {
779       service_type: serviceType,
780       placement: {},
781       unmanaged: values['unmanaged']
782     };
783     let svcId: string;
784     if (serviceType === 'rgw') {
785       serviceSpec['rgw_realm'] = values['realm_name'] ? values['realm_name'] : null;
786       serviceSpec['rgw_zonegroup'] =
787         values['zonegroup_name'] !== 'default' ? values['zonegroup_name'] : null;
788       serviceSpec['rgw_zone'] = values['zone_name'] !== 'default' ? values['zone_name'] : null;
789       svcId = values['service_id'];
790     } else {
791       svcId = values['service_id'];
792     }
793     const serviceId: string = svcId;
794     let serviceName: string = serviceType;
795     if (_.isString(serviceId) && !_.isEmpty(serviceId)) {
796       serviceName = `${serviceType}.${serviceId}`;
797       serviceSpec['service_id'] = serviceId;
798     }
799
800     // These services has some fields to be
801     // filled out even if unmanaged is true
802     switch (serviceType) {
803       case 'ingress':
804         serviceSpec['backend_service'] = values['backend_service'];
805         serviceSpec['service_id'] = values['backend_service'];
806         if (_.isNumber(values['frontend_port']) && values['frontend_port'] > 0) {
807           serviceSpec['frontend_port'] = values['frontend_port'];
808         }
809         if (_.isString(values['virtual_ip']) && !_.isEmpty(values['virtual_ip'])) {
810           serviceSpec['virtual_ip'] = values['virtual_ip'].trim();
811         }
812         if (_.isNumber(values['monitor_port']) && values['monitor_port'] > 0) {
813           serviceSpec['monitor_port'] = values['monitor_port'];
814         }
815         break;
816
817       case 'iscsi':
818         serviceSpec['pool'] = values['pool'];
819         break;
820
821       case 'smb':
822         serviceSpec['cluster_id'] = values['cluster_id']?.trim();
823         serviceSpec['config_uri'] = values['config_uri']?.trim();
824         for (const feature in values['features']) {
825           if (values['features'][feature]) {
826             (serviceSpec['features'] = serviceSpec['features'] || []).push(feature);
827           }
828         }
829         serviceSpec['custom_dns'] = values['custom_dns']?.trim();
830         serviceSpec['join_sources'] = values['join_sources']?.trim();
831         serviceSpec['user_sources'] = values['user_sources']?.trim();
832         serviceSpec['include_ceph_users'] = values['include_ceph_users']?.trim();
833         break;
834
835       case 'snmp-gateway':
836         serviceSpec['credentials'] = {};
837         serviceSpec['snmp_version'] = values['snmp_version'];
838         serviceSpec['snmp_destination'] = values['snmp_destination'];
839         if (values['snmp_version'] === 'V3') {
840           serviceSpec['engine_id'] = values['engine_id'];
841           serviceSpec['auth_protocol'] = values['auth_protocol'];
842           serviceSpec['credentials']['snmp_v3_auth_username'] = values['snmp_v3_auth_username'];
843           serviceSpec['credentials']['snmp_v3_auth_password'] = values['snmp_v3_auth_password'];
844           if (values['privacy_protocol'] !== null) {
845             serviceSpec['privacy_protocol'] = values['privacy_protocol'];
846             serviceSpec['credentials']['snmp_v3_priv_password'] = values['snmp_v3_priv_password'];
847           }
848         } else {
849           serviceSpec['credentials']['snmp_community'] = values['snmp_community'];
850         }
851         break;
852     }
853
854     if (!values['unmanaged']) {
855       switch (values['placement']) {
856         case 'hosts':
857           if (values['hosts'].length > 0) {
858             serviceSpec['placement']['hosts'] = values['hosts'];
859           }
860           break;
861         case 'label':
862           serviceSpec['placement']['label'] = values['label'];
863           break;
864       }
865       if (_.isNumber(values['count']) && values['count'] > 0) {
866         serviceSpec['placement']['count'] = values['count'];
867       }
868       switch (serviceType) {
869         case 'rgw':
870           if (_.isNumber(values['rgw_frontend_port']) && values['rgw_frontend_port'] > 0) {
871             serviceSpec['rgw_frontend_port'] = values['rgw_frontend_port'];
872           }
873           serviceSpec['ssl'] = values['ssl'];
874           if (values['ssl']) {
875             serviceSpec['rgw_frontend_ssl_certificate'] = values['ssl_cert']?.trim();
876           }
877           break;
878         case 'iscsi':
879           if (_.isString(values['trusted_ip_list']) && !_.isEmpty(values['trusted_ip_list'])) {
880             serviceSpec['trusted_ip_list'] = values['trusted_ip_list'].trim();
881           }
882           if (_.isNumber(values['api_port']) && values['api_port'] > 0) {
883             serviceSpec['api_port'] = values['api_port'];
884           }
885           serviceSpec['api_user'] = values['api_user'];
886           serviceSpec['api_password'] = values['api_password'];
887           serviceSpec['api_secure'] = values['ssl'];
888           if (values['ssl']) {
889             serviceSpec['ssl_cert'] = values['ssl_cert']?.trim();
890             serviceSpec['ssl_key'] = values['ssl_key']?.trim();
891           }
892           break;
893         case 'ingress':
894           serviceSpec['ssl'] = values['ssl'];
895           if (values['ssl']) {
896             serviceSpec['ssl_cert'] = values['ssl_cert']?.trim();
897             serviceSpec['ssl_key'] = values['ssl_key']?.trim();
898           }
899           serviceSpec['virtual_interface_networks'] = values['virtual_interface_networks'];
900           break;
901         case 'grafana':
902           serviceSpec['port'] = values['grafana_port'];
903           serviceSpec['initial_admin_password'] = values['grafana_admin_password'];
904       }
905     }
906
907     this.taskWrapperService
908       .wrapTaskAroundCall({
909         task: new FinishedTask(taskUrl, {
910           service_name: serviceName
911         }),
912         call: this.editing
913           ? this.cephServiceService.update(serviceSpec)
914           : this.cephServiceService.create(serviceSpec)
915       })
916       .subscribe({
917         error() {
918           self.serviceForm.setErrors({ cdSubmitButton: true });
919         },
920         complete: () => {
921           this.pageURL === 'services'
922             ? this.router.navigate([this.pageURL, { outlets: { modal: null } }])
923             : this.activeModal.close();
924         }
925       });
926   }
927
928   clearValidations() {
929     const snmpVersion = this.serviceForm.getValue('snmp_version');
930     const privacyProtocol = this.serviceForm.getValue('privacy_protocol');
931     if (snmpVersion === 'V3') {
932       this.serviceForm.get('snmp_community').clearValidators();
933     } else {
934       this.serviceForm.get('engine_id').clearValidators();
935       this.serviceForm.get('auth_protocol').clearValidators();
936       this.serviceForm.get('privacy_protocol').clearValidators();
937       this.serviceForm.get('snmp_v3_auth_username').clearValidators();
938       this.serviceForm.get('snmp_v3_auth_password').clearValidators();
939     }
940     if (privacyProtocol === null) {
941       this.serviceForm.get('snmp_v3_priv_password').clearValidators();
942     }
943   }
944
945   createMultisiteSetup() {
946     this.bsModalRef = this.modalService.show(CreateRgwServiceEntitiesComponent, {
947       size: 'lg'
948     });
949     this.bsModalRef.componentInstance.submitAction.subscribe(() => {
950       this.getServiceIds('rgw');
951     });
952   }
953 }