Fix Bug 2309: VIM update issue
[osm/NG-UI.git] / src / app / vim-accounts / new-vimaccount / NewVimaccountComponent.ts
1 /*
2  Copyright 2020 TATA ELXSI
3
4  Licensed under the Apache License, Version 2.0 (the 'License');
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7
8   http://www.apache.org/licenses/LICENSE-2.0
9
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15
16  Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.in), BARATH KUMAR R (barath.r@tataelxsi.co.in)
17 */
18 /**
19  * @file Vim Account Component.
20  */
21 import { isNullOrUndefined } from 'util';
22 import { HttpHeaders } from '@angular/common/http';
23 import { Component, ElementRef, Injector, OnInit, ViewChild } from '@angular/core';
24 import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
25 import { ActivatedRoute, Router } from '@angular/router';
26 import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
27 import { TranslateService } from '@ngx-translate/core';
28 import { NotifierService } from 'angular-notifier';
29 import 'codemirror/addon/dialog/dialog';
30 import 'codemirror/addon/display/autorefresh';
31 import 'codemirror/addon/display/fullscreen';
32 import 'codemirror/addon/edit/closebrackets';
33 import 'codemirror/addon/edit/matchbrackets';
34 import 'codemirror/addon/fold/brace-fold';
35 import 'codemirror/addon/fold/foldcode';
36 import 'codemirror/addon/fold/foldgutter';
37 import 'codemirror/addon/search/search';
38 import 'codemirror/addon/search/searchcursor';
39 import 'codemirror/keymap/sublime';
40 import 'codemirror/lib/codemirror';
41 import 'codemirror/mode/javascript/javascript';
42 import 'codemirror/mode/markdown/markdown';
43 import 'codemirror/mode/yaml/yaml';
44 import {
45   APIURLHEADER, CONFIGCONSTANT, ERRORDATA, MODALCLOSERESPONSEDATA, TYPEAWS, TYPEAZURE, TYPEOPENSTACK, TYPEOPENVIMNEBULA, TYPEOTERS,
46   TYPESECTION, TYPEVMWARE, VIM_TYPES
47 } from 'CommonModel';
48 import { environment } from 'environment';
49 import * as jsyaml from 'js-yaml';
50 import { RestService } from 'RestService';
51 import { SharedService } from 'SharedService';
52 import { VimAccountDetails } from 'VimAccountModel';
53 import { WarningComponent } from 'WarningComponent';
54 /**
55  * Creating component
56  * @Component takes NewVimaccountComponent.html as template url
57  */
58 @Component({
59   selector: 'app-new-vimaccount',
60   templateUrl: './NewVimaccountComponent.html',
61   styleUrls: ['./NewVimaccountComponent.scss']
62 })
63 /** Exporting a class @exports NewVimaccountComponent */
64 export class NewVimaccountComponent implements OnInit {
65   /** To inject services @public */
66   public injector: Injector;
67
68   /** FormGroup vim New Account added to the form @ html @public */
69   public vimNewAccountForm: FormGroup;
70
71   /** Supported Vim type for the dropdown */
72   public vimType: TYPESECTION[];
73
74   /** Supported Vim type for the dropdown */
75   public selectedVimType: string;
76
77   /** Form submission Add */
78   public submitted: boolean = false;
79
80   /** Showing more details of collapase */
81   public isCollapsed: boolean = false;
82
83   /** Check the Projects loading results @public */
84   public isLocationLoadingResults: boolean = false;
85
86   /** Give the message for the loading @public */
87   public message: string = 'PLEASEWAIT';
88
89   /** Set the check value  @public */
90   public check: boolean = false;
91
92   /** Handle the formate Change @public */
93   public defaults: {} = {
94     'text/x-yaml': ''
95   };
96
97   /** To Set Mode @public */
98   public mode: string = 'text/x-yaml';
99
100   /** To Set Mode @public */
101   public modeDefault: string = 'yaml';
102
103   /** options @public */
104   public options: {} = {
105     // eslint-disable-next-line no-invalid-this
106     mode: this.modeDefault,
107     showCursorWhenSelecting: true,
108     autofocus: false,
109     autoRefresh: true,
110     lineNumbers: true,
111     lineWrapping: true,
112     foldGutter: true,
113     gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
114     autoCloseBrackets: true,
115     matchBrackets: true,
116     theme: 'neat',
117     keyMap: 'sublime'
118   };
119
120   /** Data @public */
121   public data: string = '';
122
123   /** contains vim ID @public */
124   public vimID: string;
125
126   /** Element ref for fileInput @public */
127   @ViewChild('fileInput', { static: true }) public fileInput: ElementRef;
128
129   /** Element ref for fileInput @public */
130   @ViewChild('fileInputLabel', { static: true }) public fileInputLabel: ElementRef;
131
132   /** Contains all methods related to shared @private */
133   public sharedService: SharedService;
134
135   /** Check key for the Edit form @public */
136   public checkFormKeys: string[] =
137     [
138       'name',
139       'vim_type',
140       'vim_tenant_name',
141       'description',
142       'vim_url',
143       'schema_type',
144       'vim_user',
145       'vim_password',
146       'locationName',
147       'latitude',
148       'longitude',
149       'config'
150     ];
151
152   /** Contains config details in edit @public */
153   public config: {};
154
155   /** Contains latitude value @public  */
156   public latitude: string;
157
158   /** Contains longitude value @public  */
159   public longitude: string;
160
161   /** Contains location value @public */
162   public locationName: string;
163
164   /** Contains VIMAccount Details @private */
165   private details: VimAccountDetails;
166
167   /** Contains config with location @private */
168   private configLocation: string;
169
170   /** Check for config length @private */
171   // eslint-disable-next-line @typescript-eslint/no-magic-numbers
172   private configLength: number = 3;
173
174   /** Contains config length from get api @private */
175   private getConfigLength: number;
176
177   /** Contains config when update @private */
178   private updateConfig: object;
179
180   /** Contains config length when update @private */
181   private updateConfigLength: number;
182
183   /** Instance of the rest service @private */
184   private restService: RestService;
185
186   /** Holds the instance of router class @private */
187   private router: Router;
188
189   /** Controls the header form @private */
190   private headers: HttpHeaders;
191
192   /** FormBuilder instance added to the formBuilder @private */
193   private formBuilder: FormBuilder;
194
195   /** Notifier service to popup notification @private */
196   private notifierService: NotifierService;
197
198   /** Contains tranlsate instance @private */
199   private translateService: TranslateService;
200
201   /** Holds teh instance of AuthService class of type AuthService @private */
202   private activatedRoute: ActivatedRoute;
203
204   /** Instance of the modal service @private */
205   private modalService: NgbModal;
206
207   constructor(injector: Injector) {
208     this.injector = injector;
209     this.restService = this.injector.get(RestService);
210     this.formBuilder = this.injector.get(FormBuilder);
211     this.router = this.injector.get(Router);
212     this.notifierService = this.injector.get(NotifierService);
213     this.translateService = this.injector.get(TranslateService);
214     this.sharedService = this.injector.get(SharedService);
215     this.activatedRoute = this.injector.get(ActivatedRoute);
216     this.modalService = this.injector.get(NgbModal);
217   }
218
219   /** convenience getter for easy access to form fields */
220   get f(): FormGroup['controls'] { return this.vimNewAccountForm.controls; }
221
222   /**
223    * Lifecyle Hooks the trigger before component is instantiate
224    */
225   public ngOnInit(): void {
226     this.vimID = this.activatedRoute.snapshot.paramMap.get('id');
227     this.vimType = VIM_TYPES;
228     this.headers = new HttpHeaders({
229       Accept: 'application/json',
230       'Content-Type': 'application/json',
231       'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0'
232     });
233     this.initializeForm();
234     if (!isNullOrUndefined(this.vimID)) {
235       this.getVIMDetails(this.vimID);
236     }
237   }
238
239   /** VIM Initialize Forms @public */
240   public initializeForm(): void {
241     this.vimNewAccountForm = this.formBuilder.group({
242       name: [null, Validators.required],
243       vim_type: [null, Validators.required],
244       vim_tenant_name: [null, Validators.required],
245       description: [null],
246       vim_url: [null, [Validators.required, Validators.pattern(this.sharedService.REGX_URL_PATTERN)]],
247       schema_type: [''],
248       vim_user: [null, Validators.required],
249       vim_password: [null, Validators.required],
250       locationName: [''],
251       latitude: ['', Validators.pattern(this.sharedService.REGX_LAT_PATTERN)],
252       longitude: ['', Validators.pattern(this.sharedService.REGX_LONG_PATTERN)],
253       config: this.paramsBuilder()
254     });
255   }
256
257   /** Generate params for config @public */
258   public paramsBuilder(): FormGroup {
259     return this.formBuilder.group({
260       location: [null]
261     });
262   }
263
264   /** Fetching the vim details from get api @protected */
265   private getVIMDetails(id: string): void {
266     this.isLocationLoadingResults = true;
267     this.restService.getResource(environment.VIMACCOUNTS_URL + '/' + id).subscribe((vimAccountsData: VimAccountDetails) => {
268       this.details = vimAccountsData;
269       if (!isNullOrUndefined(this.details.config.location)) {
270         this.configLocation = this.details.config.location;
271         if (this.configLocation.indexOf(',') !== -1) {
272           this.locationName = this.configLocation.split(',')[0];
273           this.latitude = this.configLocation.split(',')[1];
274           this.longitude = this.configLocation.split(',')[2];
275         }
276       }
277       delete this.details.config.location;
278       this.getConfigLength = Object.keys(this.details.config).length;
279       this.getFormControl('schema_type').disable();
280       this.getFormControl('vim_url').disable();
281       this.getFormControl('vim_type').disable();
282       this.config = { ...this.details.config };
283       this.details.vim_password = '';
284       this.setEditValue(this.details, this.checkFormKeys);
285       this.isLocationLoadingResults = false;
286     }, (error: ERRORDATA) => {
287       this.restService.handleError(error, 'get');
288       this.isLocationLoadingResults = false;
289     });
290   }
291
292   /** Set the value for the Edit Section @public */
293   public setEditValue(formValues: VimAccountDetails, checkKey: string[]): void {
294     Object.keys(formValues).forEach((name: string): void => {
295       if (checkKey.includes(name)) {
296         if (name === 'config') {
297           this.loadConfig();
298           this.getFormControl('locationName').patchValue(this.locationName);
299           this.getFormControl('latitude').patchValue(this.latitude);
300           this.getFormControl('longitude').patchValue(this.longitude);
301         }
302         else {
303           // eslint-disable-next-line security/detect-object-injection
304           this.getFormControl(name).setValue(formValues[name], { onlySelf: true });
305           this.getFormControl(name).updateValueAndValidity();
306         }
307       }
308     });
309   }
310
311   /** On modal submit newVimAccountSubmit will called @public */
312   public newVimAccountSubmit(): void {
313     this.submitted = true;
314
315     if (!this.vimNewAccountForm.invalid) {
316       this.isLocationLoadingResults = true;
317       this.sharedService.cleanForm(this.vimNewAccountForm, 'vim');
318       if (!isNullOrUndefined(this.data) && this.data !== '') {
319         Object.assign(this.vimNewAccountForm.value.config, jsyaml.load(this.data.toString(), { json: true }));
320       } else {
321         Object.keys(this.vimNewAccountForm.value.config).forEach((res: string): void => {
322           if (res !== 'location') {
323             // eslint-disable-next-line @typescript-eslint/no-dynamic-delete, security/detect-object-injection
324             delete this.vimNewAccountForm.value.config[res];
325           }
326         });
327       }
328       if (!isNullOrUndefined(this.vimNewAccountForm.value.latitude) && !isNullOrUndefined(this.vimNewAccountForm.value.longitude)) {
329         this.vimNewAccountForm.value.config.location = this.vimNewAccountForm.value.locationName + ',' +
330           this.vimNewAccountForm.value.longitude + ',' +
331           this.vimNewAccountForm.value.latitude;
332       }
333
334       if (isNullOrUndefined(this.vimNewAccountForm.value.config.location)) {
335         delete this.vimNewAccountForm.value.config.location;
336       }
337
338       Object.keys(this.vimNewAccountForm.value.config).forEach((res: string): void => {
339         // eslint-disable-next-line security/detect-object-injection
340         if (isNullOrUndefined(this.vimNewAccountForm.value.config[res]) || this.vimNewAccountForm.value.config[res] === '') {
341           // eslint-disable-next-line @typescript-eslint/no-dynamic-delete, security/detect-object-injection
342           delete this.vimNewAccountForm.value.config[res];
343         }
344       });
345       delete this.vimNewAccountForm.value.config.location;
346       if (!isNullOrUndefined(this.data)) {
347         this.updateConfig = jsyaml.load(this.data, { json: true });
348         if (!isNullOrUndefined(this.updateConfig)) {
349           this.updateConfigLength = Object.keys(this.updateConfig).length;
350         }
351       }
352       if (this.updateConfig === undefined) {
353         this.notifierService.notify('warning', this.translateService.instant('PAGE.VIMDETAILS.VIMDELETE'));
354         this.isLocationLoadingResults = false;
355       } else if (this.getConfigLength > this.updateConfigLength) {
356         this.notifierService.notify('warning', this.translateService.instant('PAGE.VIMDETAILS.VIMEMPTY'));
357         this.isLocationLoadingResults = false;
358       }
359       if (!isNullOrUndefined(this.vimID) && ((this.getConfigLength <= this.updateConfigLength))) {
360         this.editVIM();
361       } else if (isNullOrUndefined(this.vimID)) {
362         this.createNewVIM();
363       }
364     }
365   }
366
367   /** Create a new VIM Account @public */
368   public createNewVIM(): void {
369     const apiURLHeader: APIURLHEADER = {
370       url: environment.VIMACCOUNTS_URL,
371       httpOptions: { headers: this.headers }
372     };
373     delete this.vimNewAccountForm.value.locationName;
374     delete this.vimNewAccountForm.value.latitude;
375     delete this.vimNewAccountForm.value.longitude;
376     this.restService.postResource(apiURLHeader, this.vimNewAccountForm.value)
377       .subscribe((result: { id: string }): void => {
378         this.notifierService.notify('success', this.translateService.instant('PAGE.VIM.CREATEDSUCCESSFULLY'));
379         this.isLocationLoadingResults = false;
380         this.router.navigate(['vim/info/' + result.id]).catch((): void => {
381           // Error Cached;
382         });
383       }, (error: ERRORDATA): void => {
384         this.restService.handleError(error, 'post');
385         this.isLocationLoadingResults = false;
386       });
387   }
388
389   /** Create a edit VIM Account @public */
390   public editVIM(): void {
391     const apiURLHeader: APIURLHEADER = {
392       url: environment.VIMACCOUNTS_URL + '/' + this.vimID,
393       httpOptions: { headers: this.headers }
394     };
395     delete this.vimNewAccountForm.value.locationName;
396     delete this.vimNewAccountForm.value.latitude;
397     delete this.vimNewAccountForm.value.longitude;
398     this.restService.patchResource(apiURLHeader, this.vimNewAccountForm.value)
399       .subscribe((result: { id: string }): void => {
400         this.notifierService.notify('success', this.translateService.instant('PAGE.VIM.UPDATEDSUCCESSFULLY'));
401         this.isLocationLoadingResults = false;
402         this.router.navigate(['vim/info/' + this.vimID]).catch((): void => {
403           // Error Cached;
404         });
405       }, (error: ERRORDATA): void => {
406         this.restService.handleError(error, 'post');
407         this.isLocationLoadingResults = false;
408       });
409   }
410   /** HandleChange function @public */
411   public handleChange($event: string): void {
412     this.data = $event;
413   }
414
415   /** Routing to VIM Account Details Page @public */
416   public onVimAccountBack(): void {
417     this.router.navigate(['vim/details']).catch((): void => {
418       // Error Cached
419     });
420   }
421
422   /** Drag and drop feature and fetchind the details of files  @private */
423   public filesDropped(files: FileList): void {
424     if (files && files.length === 1) {
425       this.sharedService.getFileString(files, 'yaml').then((fileContent: string): void => {
426         const getJson: string = jsyaml.load(fileContent, { json: true });
427         this.defaults['text/x-yaml'] = fileContent;
428         this.data = fileContent;
429       }).catch((err: string): void => {
430         if (err === 'typeError') {
431           this.notifierService.notify('error', this.translateService.instant('YAMLFILETYPEERRROR'));
432         } else {
433           this.notifierService.notify('error', this.translateService.instant('ERROR'));
434         }
435         this.fileInputLabel.nativeElement.innerText = this.translateService.instant('CHOOSEFILE');
436         this.fileInput.nativeElement.value = null;
437       });
438     } else if (files && files.length > 1) {
439       this.notifierService.notify('error', this.translateService.instant('DROPFILESVALIDATION'));
440     }
441     this.fileInputLabel.nativeElement.innerText = files[0].name;
442     this.fileInput.nativeElement.value = null;
443   }
444
445   /** Check data is empty or not to load config @public */
446   public checkData(): void {
447     if (this.data !== '' && this.data.length > this.configLength) {
448       // eslint-disable-next-line security/detect-non-literal-fs-filename
449       const modalRef: NgbModalRef = this.modalService.open(WarningComponent, { backdrop: 'static' });
450       modalRef.componentInstance.heading = this.translateService.instant('PAGE.VIMDETAILS.VIMHEADER');
451       modalRef.componentInstance.confirmationMessage = this.translateService.instant('PAGE.VIMDETAILS.VIMCONTENT');
452       modalRef.componentInstance.submitMessage = this.translateService.instant('PAGE.VIMDETAILS.VIMSUBMIT');
453       modalRef.result.then((result: MODALCLOSERESPONSEDATA): void => {
454         if (result.message === CONFIGCONSTANT.done) {
455           this.loadSampleConfig();
456         }
457       }).catch((): void => {
458         // Catch Navigation Error
459       });
460     } else if (this.data.length < this.configLength || this.data === '') {
461       this.loadSampleConfig();
462     }
463   }
464
465   /** Load sample config based on VIM type @public */
466   public loadSampleConfig(): void {
467     if (this.selectedVimType === 'openstack') {
468       this.defaults['text/x-yaml'] = jsyaml.dump(TYPEOPENSTACK);
469       this.data = JSON.stringify(TYPEOPENSTACK, null, '\t');
470     } else if (this.selectedVimType === 'aws') {
471       this.defaults['text/x-yaml'] = jsyaml.dump(TYPEAWS);
472       this.data = JSON.stringify(TYPEAWS, null, '\t');
473     } else if (this.selectedVimType === 'vmware') {
474       this.defaults['text/x-yaml'] = jsyaml.dump(TYPEVMWARE);
475       this.data = JSON.stringify(TYPEVMWARE, null, '\t');
476     } else if (this.selectedVimType === 'openvim' || this.selectedVimType === 'opennebula') {
477       this.defaults['text/x-yaml'] = jsyaml.dump(TYPEOPENVIMNEBULA);
478       this.data = JSON.stringify(TYPEOPENVIMNEBULA, null, '\t');
479     } else if (this.selectedVimType === 'azure' || this.selectedVimType === 'opennebula') {
480       this.defaults['text/x-yaml'] = jsyaml.dump(TYPEAZURE);
481       this.data = JSON.stringify(TYPEAZURE, null, '\t');
482     } else {
483       this.defaults['text/x-yaml'] = jsyaml.dump(TYPEOTERS);
484       this.data = JSON.stringify(TYPEOTERS, null, '\t');
485     }
486   }
487
488   /** Load sample config based on VIM type in edit @public */
489   public loadConfig(): void {
490     this.clearConfig();
491     if (this.details.vim_type === 'openstack') {
492       this.defaults['text/x-yaml'] = jsyaml.dump(this.config);
493       this.data = JSON.stringify(this.config, null, '\t');
494     } else if (this.details.vim_type === 'aws') {
495       this.defaults['text/x-yaml'] = jsyaml.dump(this.config);
496       this.data = JSON.stringify(this.config, null, '\t');
497     } else if (this.details.vim_type === 'vmware') {
498       this.defaults['text/x-yaml'] = jsyaml.dump(this.config);
499       this.data = JSON.stringify(this.config, null, '\t');
500     } else if (this.details.vim_type === 'openvim' || this.details.vim_type === 'opennebula') {
501       this.defaults['text/x-yaml'] = jsyaml.dump(this.config);
502       this.data = JSON.stringify(this.config, null, '\t');
503     } else if (this.details.vim_type === 'azure' || this.details.vim_type === 'opennebula') {
504       this.defaults['text/x-yaml'] = jsyaml.dump(this.config);
505       this.data = JSON.stringify(this.config, null, '\t');
506     } else {
507       this.defaults['text/x-yaml'] = jsyaml.dump(this.config);
508       this.data = JSON.stringify(this.config, null, '\t');
509     }
510   }
511
512   /** Clear config parameters @public */
513   public clearConfig(): void {
514     this.check = true;
515     if (this.data !== '' && this.data.length > this.configLength) {
516       // eslint-disable-next-line security/detect-non-literal-fs-filename
517       const modalRef: NgbModalRef = this.modalService.open(WarningComponent, { backdrop: 'static' });
518       modalRef.componentInstance.heading = this.translateService.instant('PAGE.VIMDETAILS.VIMHEADER');
519       modalRef.componentInstance.confirmationMessage = this.translateService.instant('PAGE.VIMDETAILS.CLEARCONTENT');
520       modalRef.componentInstance.submitMessage = this.translateService.instant('PAGE.VIMDETAILS.VIMSUBMIT');
521       modalRef.result.then((result: MODALCLOSERESPONSEDATA): void => {
522         if (result.message === CONFIGCONSTANT.done) {
523           this.defaults['text/x-yaml'] = '';
524           this.data = '';
525           this.fileInput.nativeElement.value = null;
526         }
527       }).catch((): void => {
528         // Catch Navigation Error
529       });
530     } else {
531       this.defaults['text/x-yaml'] = '';
532       this.data = '';
533       this.fileInput.nativeElement.value = null;
534     }
535   }
536
537   /** Used to get the AbstractControl of controlName passed @private */
538   private getFormControl(controlName: string): AbstractControl {
539     // eslint-disable-next-line security/detect-object-injection
540     return this.vimNewAccountForm.controls[controlName];
541   }
542 }