Fix Bug 2339:Responsiveness issue in UI if name(NSD/VNFD/VIM) is too long
[osm/NG-UI.git] / src / services / SharedService.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 Provider for Shared Service
20  */
21 import { isNullOrUndefined } from 'util';
22 import { HttpErrorResponse, HttpHeaders } from '@angular/common/http';
23 import { EventEmitter, Injectable, Output } from '@angular/core';
24 import { FormArray, FormGroup } from '@angular/forms';
25 import { Router } from '@angular/router';
26 import { TranslateService } from '@ngx-translate/core';
27 import {
28     CONSTANTNUMBER,
29     DOMAINS,
30     ERRORDATA,
31     FILESETTINGS,
32     GETAPIURLHEADER,
33     PACKAGEINFO,
34     PAGERSMARTTABLE,
35     SMARTTABLECLASS,
36     TARSETTINGS,
37     TYPESECTION
38 } from 'CommonModel';
39 import { environment } from 'environment';
40 import * as HttpStatus from 'http-status-codes';
41 import * as untar from 'js-untar';
42 import { ActiveToast, ToastrService } from 'ngx-toastr';
43 import * as pako from 'pako';
44 import { RestService } from 'RestService';
45 import { Observable } from 'rxjs';
46 import { map } from 'rxjs/operators';
47
48 /** This is added globally by the tar.js library */
49 // eslint-disable-next-line @typescript-eslint/no-explicit-any
50 declare const Tar: any;
51
52 /**
53  * An Injectable is a class adorned with the @Injectable decorator function.
54  * @Injectable takes a metadata object that tells Angular how to compile and run module code
55  */
56 @Injectable({
57     providedIn: 'root'
58 })
59 /** Exporting a class @exports SharedService */
60 export class SharedService {
61     /** call the parent using event information @private */
62     @Output() public dataEvent: EventEmitter<{}> = new EventEmitter<{}>();
63
64     /** Variables to hold regexp pattern for URL */
65     public REGX_URL_PATTERN: RegExp = new RegExp(/^(http?|ftp|https):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z0-9]{2,15})(:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4})))*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/);
66
67     /** Variables to hold regexp pattern for IP Address */
68     public REGX_IP_PATTERN: RegExp = new RegExp(/^(?:(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(?!$)|$)){4}$/);
69
70     /** Variables to hold regexp pattern for Port Number */
71     public REGX_PORT_PATTERN: RegExp = new RegExp(/^((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$/);
72
73     /** Variables to hold regexp pattern for DPID */
74     public REGX_DPID_PATTERN: RegExp = new RegExp(/^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){7}$/);
75
76     /** Variable to hold regexp pattern for password */
77     public REGX_PASSWORD_PATTERN: RegExp = new RegExp(/^.*(?=.{8,})((?=.*[!@#$%^&*()\-_=+{};:,<.>]){1})(?=.*\d)((?=.*[a-z]){1})((?=.*[A-Z]){1}).*$/);
78
79     /** Variables to hold regexp pattern for Latitude */
80     public REGX_LAT_PATTERN: RegExp = new RegExp(/^(\+|-)?(?:90(?:(?:\.0{1,6})?)|(?:[0-9]|[1-8][0-9])(?:(?:\.[0-9]{1,15})?))$/);
81
82     /** Variables to hold regexp pattern for Longitude */
83     public REGX_LONG_PATTERN: RegExp = new RegExp(/^(\+|-)?(?:180(?:(?:\.0{1,6})?)|(?:[0-9]|[1-9][0-9]|1[0-7][0-9])(?:(?:\.[0-9]{1,15})?))$/);
84
85     /** Variables to hold maxlength for the description @public */
86     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
87     public MAX_LENGTH_DESCRIPTION: number = 500;
88
89     /** Variables to hold maxlength for the name @public */
90     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
91     public MAX_LENGTH_NAME: number = 50;
92
93     /** FormGroup instance added to the form @ html @public */
94     public formGroup: FormGroup;
95
96     /** Controls the go to top button on scroll  @public */
97     public showGotoTop: boolean;
98
99     /** Holds OSM Version value @public */
100     public osmVersion: string;
101
102     /** Holds Last Login Toaster Message @public */
103     public lastLoginMessage: string;
104
105     /** Holds Failed Attempts Toaster Message @public */
106     public failedAttemptsMessage: string;
107
108     /** Holds No Of Days Toaster Message @public */
109     public daysMessage: string;
110
111     /** express number for time manupulation -2 */
112     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
113     private epochTimeMinus2: number = -2;
114
115     /** express number for time manupulation 1000 */
116     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
117     private epochTime1000: number = 1000;
118
119     /** express number for time manupulation 60 */
120     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
121     private epochTime60: number = 60;
122
123     /** express number for time manupulation 24 */
124     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
125     private epochTime24: number = 24;
126
127     /** Random string generator length */
128     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
129     private randomStringLength: number = 4;
130
131     /** express number for rgb manipulation */
132     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
133     private colourHour: number = 10;
134
135     /** express number for rgb manipulation*/
136     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
137     private colourMin: number = 5;
138
139     /** Instance of the rest service @private */
140     private restService: RestService;
141
142     /** Service holds the router information @private */
143     private router: Router;
144
145     /** Check for the root directory @private */
146     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
147     private directoryCount: number = 2;
148
149     /** express number for time manupulation 1000 */
150     private toasterSettings: {} = {
151         enableHtml: true,
152         closeButton: true,
153         timeOut: 2000
154     };
155
156     /** Contains tranlsate instance @private */
157     private translateService: TranslateService;
158
159     /** Contains toaster instance @private */
160     private toaster: ToastrService;
161
162     constructor(restService: RestService, router: Router, translateService: TranslateService, toaster: ToastrService) {
163         this.restService = restService;
164         this.router = router;
165         this.translateService = translateService;
166         this.toaster = toaster;
167     }
168
169     /** convert epoch time function @public */
170     public convertEpochTime(unixtimestamp: number): string {
171         if (!isNullOrUndefined(unixtimestamp)) {
172             const monthsArr: string[] = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
173                 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
174             const date: Date = new Date(unixtimestamp * this.epochTime1000);
175             const year: number = date.getFullYear();
176             const month: string = monthsArr[date.getMonth()];
177             const day: number = date.getDate();
178             const hours: number = date.getHours();
179             const minutes: string = '0' + date.getMinutes();
180             const seconds: string = '0' + date.getSeconds();
181             // eslint-disable-next-line deprecation/deprecation
182             return month + '-' + day + '-' + year + ' ' + hours + ':' + minutes.substr(this.epochTimeMinus2) + ':'
183                 // eslint-disable-next-line deprecation/deprecation
184                 + seconds.substr(this.epochTimeMinus2);
185         }
186         return this.translateService.instant('NODATE');
187     }
188
189     /** convert epoch time function to No of days @public */
190     public converEpochToDays(date: string): number {
191         if (!isNullOrUndefined(date)) {
192             const today: Date = new Date();
193             const accountDate: Date = new Date(date);
194             const toasterDate: number = (accountDate.getTime() -
195                 today.getTime()) / this.epochTime1000 / this.epochTime60 / this.epochTime60 / this.epochTime24;
196             if (toasterDate >= 0 || toasterDate < 1) {
197                 return Math.round(toasterDate);
198             }
199             return Math.floor(toasterDate);
200         }
201         return this.translateService.instant('N/A');
202     }
203
204     /** show toaster for password & account expiry @public */
205     public showToaster(lastLogin: string, failedAttempts: string, passwordNoOfDays: string,
206         accountNoOfDays: string, passwordExpireMessage: string, accountExpireMessage: string,
207         passwordMessage: string, accountMessage: string): ActiveToast<string> {
208         this.lastLoginMessage = this.translateService.instant('PAGE.LOGIN.LASTACCESS');
209         this.failedAttemptsMessage = this.translateService.instant('PAGE.LOGIN.FAILED');
210         return this.toaster.info(this.lastLoginMessage + ':' + '&nbsp' + lastLogin +
211             '</br>' + this.failedAttemptsMessage + ':' + '&nbsp' + failedAttempts +
212             '</br>' + passwordExpireMessage + '&nbsp' + passwordNoOfDays + '&nbsp' + passwordMessage +
213             '</br>' + accountExpireMessage + '&nbsp' + accountNoOfDays + '&nbsp' + accountMessage,
214             this.translateService.instant('PAGE.LOGIN.LOGINHISTORY'), this.toasterSettings);
215     }
216
217     /** show toaster for password expiry @public */
218     public passwordToaster(lastLogin: string, failedAttempts: string, passwordNoOfDays: string,
219         passwordExpireMessage: string, passwordMessage: string): ActiveToast<string> {
220         this.lastLoginMessage = this.translateService.instant('PAGE.LOGIN.LASTACCESS');
221         this.failedAttemptsMessage = this.translateService.instant('PAGE.LOGIN.FAILED');
222         return this.toaster.info(this.lastLoginMessage + ':' + '&nbsp' + lastLogin +
223             '</br>' + this.failedAttemptsMessage + ':' + '&nbsp' + failedAttempts +
224             '</br>' + passwordExpireMessage + '&nbsp' + passwordNoOfDays + '&nbsp' + passwordMessage,
225             this.translateService.instant('PAGE.LOGIN.LOGINHISTORY'), this.toasterSettings);
226     }
227
228     /** show toaster for account expiry @public */
229     public accountToaster(lastLogin: string, failedAttempts: string,
230         accountNoOfDays: string, accountExpireMessage: string, accountMessage: string): ActiveToast<string> {
231         this.lastLoginMessage = this.translateService.instant('PAGE.LOGIN.LASTACCESS');
232         this.failedAttemptsMessage = this.translateService.instant('PAGE.LOGIN.FAILED');
233         return this.toaster.info(this.lastLoginMessage + ':' + '&nbsp' + lastLogin +
234             '</br>' + this.failedAttemptsMessage + ':' + '&nbsp' + failedAttempts +
235             '</br>' + accountExpireMessage + '&nbsp' + accountNoOfDays + '&nbsp' + accountMessage,
236             this.translateService.instant('PAGE.LOGIN.LOGINHISTORY'), this.toasterSettings);
237     }
238
239     /** Download Files function @public */
240     public downloadFiles(name: string, binaryData: Blob[], filetype: string): void {
241         const downloadLink: HTMLAnchorElement = document.createElement('a');
242         downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, { type: filetype }));
243         if (name !== undefined) {
244             // eslint-disable-next-line @typescript-eslint/no-explicit-any
245             const newVariable: any = window.navigator;
246             if (newVariable.msSaveOrOpenBlob) {
247                 newVariable.msSaveBlob(new Blob(binaryData, { type: filetype }), 'OSM_Export_' + name + '.tar.gz');
248             } else {
249                 downloadLink.setAttribute('download', 'OSM_Export_' + name + '.tar.gz');
250                 document.body.appendChild(downloadLink);
251                 downloadLink.click();
252             }
253         }
254     }
255
256     /** Call this method after delete perform action is completed in the ng-smart-table data @public */
257     public callData(): void {
258         this.dataEvent.emit();
259     }
260
261     /** Generate random string @public */
262     public randomString(): string {
263         let result: string = '';
264         for (let randomStringRef: number = this.randomStringLength; randomStringRef > 0; --randomStringRef) {
265             result += new Date().getSeconds();
266         }
267         return result;
268     }
269
270     /** Function to read uploaded file String @public */
271     public async getFileString(files: FileList, fileType: string): Promise<string | ArrayBuffer> {
272         const reader: FileReader = new FileReader();
273         return new Promise<string | ArrayBuffer>((resolve: Function, reject: Function): void => {
274             if (this.vaildataFileInfo(files[0], fileType)) {
275                 this.readFileContent(reader, files[0], fileType);
276             } else {
277                 reject('typeError');
278             }
279             reader.onload = (): void => {
280                 if (reader.result === null) {
281                     reject('contentError');
282                 }
283                 resolve(reader.result);
284             };
285             reader.onerror = (event: Event): void => {
286                 reject('contentError');
287             };
288         });
289     }
290
291     /** Method to handle tar and tar.gz file for shared YAML file content @public */
292     public async targzFile(packageInfo: PACKAGEINFO): Promise<string | ArrayBuffer> {
293         return new Promise<string | ArrayBuffer>((resolve: Function, reject: Function): void => {
294             const httpOptions: GETAPIURLHEADER = this.getHttpOptions();
295             let apiUrl: string = '';
296             apiUrl = packageInfo.packageType === 'nsd' ? environment.NSDESCRIPTORS_URL + '/' + packageInfo.id + '/nsd_content' :
297                 environment.VNFPACKAGES_URL + '/' + packageInfo.id + '/package_content';
298             this.restService.getResource(apiUrl, httpOptions).subscribe((response: ArrayBuffer): void => {
299                 try {
300                     // eslint-disable-next-line @typescript-eslint/no-explicit-any
301                     const tar: any = new Tar();
302                     const originalInput: Uint8Array = pako.inflate(response, { to: 'Uint8Array' });
303                     untar(originalInput.buffer).then((extractedFiles: TARSETTINGS[]): void => {
304                         const getFoldersFiles: {}[] = extractedFiles;
305                         const folderNameStr: string = extractedFiles[0].name;
306                         getFoldersFiles.forEach((value: TARSETTINGS): void => {
307                             const fileValueObj: FILESETTINGS = this.createFileValueObject(value);
308                             const getRootFolder: string[] = value.name.split('/');
309                             if (value.name.startsWith(folderNameStr) &&
310                                 (value.name.endsWith('.yaml') || value.name.endsWith('.yml')) &&
311                                 getRootFolder.length === this.directoryCount) {
312                                 tar.append(value.name, packageInfo.descriptor, fileValueObj);
313                             } else {
314                                 if (value.type !== 'L') {
315                                     tar.append(value.name, new Uint8Array(value.buffer), fileValueObj);
316                                 }
317                             }
318                         });
319                         const out: Uint8Array = tar.out;
320                         const originalOutput: Uint8Array = pako.gzip(out);
321                         resolve(originalOutput.buffer);
322                     }, (err: string): void => {
323                         reject('');
324                     });
325                 } catch (e) {
326                     reject('');
327                 }
328             }, (error: HttpErrorResponse): void => {
329                 if (error.status === HttpStatus.NOT_FOUND || error.status === HttpStatus.UNAUTHORIZED) {
330                     this.router.navigateByUrl('404', { skipLocationChange: true }).catch((): void => {
331                         // Catch Navigation Error
332                     });
333                 } else {
334                     this.restService.handleError(error, 'get');
335                     reject('');
336                 }
337             });
338         });
339     }
340
341     /** Method to return the file information @public */
342     public createFileValueObject(value: TARSETTINGS): FILESETTINGS {
343         return {
344             type: value.type,
345             linkname: value.linkname,
346             owner: value.uname,
347             group: value.gname
348         };
349     }
350
351     /** Method to check given string is JSON or not @public */
352     public checkJson(jsonString: string): boolean {
353         jsonString = jsonString.replace(/'/g, '"');
354         try {
355             JSON.parse(jsonString);
356         } catch (e) {
357             return false;
358         }
359         return true;
360     }
361
362     /** Clean the form before submit @public */
363     public cleanForm(formGroup: FormGroup, formName?: String): void {
364         Object.keys(formGroup.controls).forEach((key: string) => {
365             if ((!isNullOrUndefined((formGroup.get(key) as FormArray | FormGroup).controls)) && key !== 'config') {
366                 // eslint-disable-next-line @typescript-eslint/no-shadow
367                 for (const { item, index } of (formGroup.get(key).value).map((item: {}, index: number) => ({ item, index }))) {
368                     // eslint-disable-next-line security/detect-object-injection
369                     const newFormGroup: FormGroup = (formGroup.get(key) as FormArray).controls[index] as FormGroup;
370                     this.cleanForm(newFormGroup);
371                 }
372             } else if (formGroup.get(key).value !== undefined && formGroup.get(key).value !== null && key !== 'config') {
373                 if (!Array.isArray(formGroup.get(key).value)) {
374                     if (typeof formGroup.get(key).value === 'string') {
375                         formGroup.get(key).setValue(formGroup.get(key).value.trim());
376                     }
377                 }
378             } else if (key === 'config' && formName === 'vim') {
379                 const newFormGroup: FormGroup = formGroup.get(key) as FormGroup;
380                 this.cleanForm(newFormGroup);
381             }
382         });
383     }
384
385     /** Method to return the config of pager value for ngSmarttable @public */
386     public paginationPagerConfig(): PAGERSMARTTABLE {
387         return {
388             display: true,
389             perPage: environment.paginationNumber
390         };
391     }
392
393     /** Method to return the class for the table for ngSmarttable @public */
394     public tableClassConfig(): SMARTTABLECLASS {
395         return {
396             class: 'table list-data'
397         };
398     }
399
400     /** Method to return all languages name and its code @public */
401     public languageCodeList(): {}[] {
402         return [
403             { code: 'en', language: 'English' },
404             { code: 'es', language: 'Spanish' },
405             { code: 'pt', language: 'Portuguese' },
406             { code: 'de', language: 'German' }
407         ];
408     }
409
410     /** Fetch OSM Version @public */
411     public fetchOSMVersion(): void {
412         this.restService.getResource(environment.OSM_VERSION_URL).subscribe((res: { version: string }): void => {
413             const version: string[] = res.version.split('+');
414             if (!isNullOrUndefined(version[0])) {
415                 this.osmVersion = version[0];
416                 sessionStorage.setItem('version', version[0]);
417             } else {
418                 this.osmVersion = null;
419             }
420         }, (error: ERRORDATA): void => {
421             this.osmVersion = null;
422             this.restService.handleError(error, 'get');
423         });
424     }
425
426     /** Random RGB color code generator @public */
427     public generateColor(): string {
428         const x: number = Math.floor((new Date().getHours()) * this.colourHour);
429         const y: number = Math.floor((new Date().getMinutes()) * this.colourMin);
430         const z: number = Math.floor((new Date().getSeconds()) * this.colourMin);
431         return 'rgb(' + x + ',' + y + ',' + z + ')';
432     }
433
434     /** Add custom name/tag to the dropdown @public */
435     public addCustomTag(tag: string): string {
436         return tag;
437     }
438
439     /** Fetch file extension @public */
440     public fetchFileExtension(fileInfo: FileList): string {
441         return fileInfo[0].name.substring(fileInfo[0].name.lastIndexOf('.') + 1);
442     }
443
444     /** Get domain name @private */
445     public getDomainName(): Observable<TYPESECTION[]> {
446         return this.restService.getResource(environment.DOMAIN_URL).pipe(map((domains: DOMAINS): TYPESECTION[] => {
447             const domainList: TYPESECTION[] = [];
448             try {
449                 let domainNames: string[] = [];
450                 if (!isNullOrUndefined(domains.project_domain_name)) {
451                     domainNames = domainNames.concat(domains.project_domain_name.split(','));
452                 }
453                 if (!isNullOrUndefined(domains.user_domain_name)) {
454                     domainNames = domainNames.concat(domains.user_domain_name.split(','));
455                 }
456                 domainNames = Array.from(new Set(domainNames));
457                 if (domainNames.length > 0) {
458                     domainNames.forEach((domainName: string): void => {
459                         if (!domainName.endsWith(':ro')) {
460                             domainList.push({ title: domainName, value: domainName });
461                         }
462                     });
463                 }
464                 return domainList;
465             } catch (e) {
466                 return domainList;
467             }
468         }));
469     }
470
471     /** Sorting the list based on date @public */
472     public compareFunction = (dir: number, a: string, b: string): number => {
473         const first: number = new Date(a).getTime();
474         const second: number = new Date(b).getTime();
475         if (first < second) {
476             return -1 * dir;
477         }
478         if (first > second) {
479             return dir;
480         }
481         return 0;
482     };
483
484     /** Method to validate file extension and size @private */
485     private vaildataFileInfo(fileInfo: File, fileType: string): boolean {
486         const extension: string = fileInfo.name.substring(fileInfo.name.lastIndexOf('.') + 1);
487         const packageSize: number = CONSTANTNUMBER.oneMB * environment.packageSize;
488         if (fileType === 'yaml' && (extension.toLowerCase() === 'yaml' || extension.toLowerCase() === 'yml')
489             && fileInfo.size <= packageSize) {
490             return true;
491         } else if (extension.toLowerCase() === fileType && fileInfo.size <= packageSize) {
492             return true;
493         }
494         return false;
495     }
496
497     /** Method to read file content based on type @private */
498     private readFileContent(reader: FileReader, fileInfo: File, fileType: string): void {
499         if (fileType === 'gz') {
500             reader.readAsArrayBuffer(fileInfo);
501         } else {
502             reader.readAsText(fileInfo);
503         }
504     }
505
506     /** Method to handle http options @public */
507     private getHttpOptions(): GETAPIURLHEADER {
508         return {
509             headers: new HttpHeaders({
510                 Accept: 'application/gzip, application/json',
511                 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0'
512             }),
513             responseType: 'arraybuffer'
514         };
515     }
516 }