Fix Bug 2296: Newly Created Ns should come first in NG-UI
[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     /** Max length of Uint8Array */
132     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
133     private unit8Array: number = 255;
134
135     /** Instance of the rest service @private */
136     private restService: RestService;
137
138     /** Service holds the router information @private */
139     private router: Router;
140
141     /** Random color string generator length @private */
142     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
143     private colorStringLength: number = 256;
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         const chars: string = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
264         let result: string = '';
265         for (let randomStringRef: number = this.randomStringLength; randomStringRef > 0; --randomStringRef) {
266             result += chars[Math.floor(Math.random() * chars.length)];
267         }
268         return result;
269     }
270
271     /** Function to read uploaded file String @public */
272     public async getFileString(files: FileList, fileType: string): Promise<string | ArrayBuffer> {
273         const reader: FileReader = new FileReader();
274         return new Promise<string | ArrayBuffer>((resolve: Function, reject: Function): void => {
275             if (this.vaildataFileInfo(files[0], fileType)) {
276                 this.readFileContent(reader, files[0], fileType);
277             } else {
278                 reject('typeError');
279             }
280             reader.onload = (): void => {
281                 if (reader.result === null) {
282                     reject('contentError');
283                 }
284                 resolve(reader.result);
285             };
286             reader.onerror = (event: Event): void => {
287                 reject('contentError');
288             };
289         });
290     }
291
292     /** Method to handle tar and tar.gz file for shared YAML file content @public */
293     public async targzFile(packageInfo: PACKAGEINFO): Promise<string | ArrayBuffer> {
294         return new Promise<string | ArrayBuffer>((resolve: Function, reject: Function): void => {
295             const httpOptions: GETAPIURLHEADER = this.getHttpOptions();
296             let apiUrl: string = '';
297             apiUrl = packageInfo.packageType === 'nsd' ? environment.NSDESCRIPTORS_URL + '/' + packageInfo.id + '/nsd_content' :
298                 environment.VNFPACKAGES_URL + '/' + packageInfo.id + '/package_content';
299             this.restService.getResource(apiUrl, httpOptions).subscribe((response: ArrayBuffer): void => {
300                 try {
301                     // eslint-disable-next-line @typescript-eslint/no-explicit-any
302                     const tar: any = new Tar();
303                     const originalInput: Uint8Array = pako.inflate(response, { to: 'Uint8Array' });
304                     untar(originalInput.buffer).then((extractedFiles: TARSETTINGS[]): void => {
305                         const getFoldersFiles: {}[] = extractedFiles;
306                         const folderNameStr: string = extractedFiles[0].name;
307                         getFoldersFiles.forEach((value: TARSETTINGS): void => {
308                             const fileValueObj: FILESETTINGS = this.createFileValueObject(value);
309                             const getRootFolder: string[] = value.name.split('/');
310                             if (value.name.startsWith(folderNameStr) &&
311                                 (value.name.endsWith('.yaml') || value.name.endsWith('.yml')) &&
312                                 getRootFolder.length === this.directoryCount) {
313                                 tar.append(value.name, packageInfo.descriptor, fileValueObj);
314                             } else {
315                                 if (value.type !== 'L') {
316                                     tar.append(value.name, new Uint8Array(value.buffer), fileValueObj);
317                                 }
318                             }
319                         });
320                         const out: Uint8Array = tar.out;
321                         const originalOutput: Uint8Array = pako.gzip(out);
322                         resolve(originalOutput.buffer);
323                     }, (err: string): void => {
324                         reject('');
325                     });
326                 } catch (e) {
327                     reject('');
328                 }
329             }, (error: HttpErrorResponse): void => {
330                 if (error.status === HttpStatus.NOT_FOUND || error.status === HttpStatus.UNAUTHORIZED) {
331                     this.router.navigateByUrl('404', { skipLocationChange: true }).catch((): void => {
332                         // Catch Navigation Error
333                     });
334                 } else {
335                     this.restService.handleError(error, 'get');
336                     reject('');
337                 }
338             });
339         });
340     }
341
342     /** Method to return the file information @public */
343     public createFileValueObject(value: TARSETTINGS): FILESETTINGS {
344         return {
345             type: value.type,
346             linkname: value.linkname,
347             owner: value.uname,
348             group: value.gname
349         };
350     }
351
352     /** Method to check given string is JSON or not @public */
353     public checkJson(jsonString: string): boolean {
354         jsonString = jsonString.replace(/'/g, '"');
355         try {
356             JSON.parse(jsonString);
357         } catch (e) {
358             return false;
359         }
360         return true;
361     }
362
363     /** Clean the form before submit @public */
364     public cleanForm(formGroup: FormGroup, formName?: String): void {
365         Object.keys(formGroup.controls).forEach((key: string) => {
366             if ((!isNullOrUndefined((formGroup.get(key) as FormArray | FormGroup).controls)) && key !== 'config') {
367                 // eslint-disable-next-line @typescript-eslint/no-shadow
368                 for (const { item, index } of (formGroup.get(key).value).map((item: {}, index: number) => ({ item, index }))) {
369                     // eslint-disable-next-line security/detect-object-injection
370                     const newFormGroup: FormGroup = (formGroup.get(key) as FormArray).controls[index] as FormGroup;
371                     this.cleanForm(newFormGroup);
372                 }
373             } else if (formGroup.get(key).value !== undefined && formGroup.get(key).value !== null && key !== 'config') {
374                 if (!Array.isArray(formGroup.get(key).value)) {
375                     if (typeof formGroup.get(key).value === 'string') {
376                         formGroup.get(key).setValue(formGroup.get(key).value.trim());
377                     }
378                 }
379             } else if (key === 'config' && formName === 'vim') {
380                 const newFormGroup: FormGroup = formGroup.get(key) as FormGroup;
381                 this.cleanForm(newFormGroup);
382             }
383         });
384     }
385
386     /** Method to return the config of pager value for ngSmarttable @public */
387     public paginationPagerConfig(): PAGERSMARTTABLE {
388         return {
389             display: true,
390             perPage: environment.paginationNumber
391         };
392     }
393
394     /** Method to return the class for the table for ngSmarttable @public */
395     public tableClassConfig(): SMARTTABLECLASS {
396         return {
397             class: 'table list-data'
398         };
399     }
400
401     /** Method to return all languages name and its code @public */
402     public languageCodeList(): {}[] {
403         return [
404             { code: 'en', language: 'English' },
405             { code: 'es', language: 'Spanish' },
406             { code: 'pt', language: 'Portuguese' },
407             { code: 'de', language: 'German' }
408         ];
409     }
410
411     /** Fetch OSM Version @public */
412     public fetchOSMVersion(): void {
413         this.restService.getResource(environment.OSM_VERSION_URL).subscribe((res: { version: string }): void => {
414             const version: string[] = res.version.split('+');
415             if (!isNullOrUndefined(version[0])) {
416                 this.osmVersion = 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(Math.random() * this.colorStringLength);
429         const y: number = Math.floor(Math.random() * this.colorStringLength);
430         const z: number = Math.floor(Math.random() * this.colorStringLength);
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 }