From 219fe61b3f8d15284ae3b36a133c2b34d3b444fc Mon Sep 17 00:00:00 2001 From: "SANDHYA.JS" Date: Tue, 23 Jan 2024 15:52:43 +0530 Subject: [PATCH] Fix Bug 2336: Manual Healing option in Ui *In NS Instances page. Click "Manual Healing" in the actions menu. *Then a new pop-up page will opened. *In the popup there will be drop-down containing membervnfIndex and Day1 operation. *When the membervnfIndex and Day1 operation are selected including muti VDU optional can be selected then Click Apply button. *The pop-up window is closed and the page is directed to "History of operations" page for this NS. Change-Id: I3041bcfb56232c6f4b0f56441d89295cc04cfa02 Signed-off-by: SANDHYA.JS (cherry picked from commit 319d328d0bbb1e156ac767851c8c33acb518dc94) --- src/app/AppModule.ts | 4 +- .../ns-instances/NSInstancesComponent.html | 1 + .../ns-instances/NSInstancesComponent.ts | 39 ++- .../utilities/healing/HealingComponent.html | 107 +++++++ .../utilities/healing/HealingComponent.scss | 17 + src/app/utilities/healing/HealingComponent.ts | 300 ++++++++++++++++++ .../NSInstancesActionComponent.html | 4 + .../NSInstancesActionComponent.ts | 29 +- .../utilities/scaling/ScalingComponent.html | 2 +- src/assets/i18n/de.json | 3 + src/assets/i18n/en.json | 3 + src/assets/i18n/es.json | 3 + src/assets/i18n/pt.json | 3 + src/assets/scss/app.scss | 31 ++ src/models/CommonModel.ts | 1 + src/models/NSInstanceModel.ts | 15 + tsconfig.json | 3 +- 17 files changed, 541 insertions(+), 24 deletions(-) create mode 100644 src/app/utilities/healing/HealingComponent.html create mode 100644 src/app/utilities/healing/HealingComponent.scss create mode 100644 src/app/utilities/healing/HealingComponent.ts diff --git a/src/app/AppModule.ts b/src/app/AppModule.ts index 889f4be..5a9c306 100644 --- a/src/app/AppModule.ts +++ b/src/app/AppModule.ts @@ -45,6 +45,7 @@ import { DeleteComponent } from 'DeleteComponent'; import { DeviceCheckService } from 'DeviceCheckService'; import { GoToTopDirective } from 'GoToTopDirective'; import { HeaderComponent } from 'HeaderComponent'; +import { HealingComponent } from 'HealingComponent'; import { InstantiateNetSliceTemplateComponent } from 'InstantiateNetSliceTemplate'; import { InstantiateNsComponent } from 'InstantiateNs'; import { LayoutComponent } from 'LayoutComponent'; @@ -134,7 +135,8 @@ const customNotifierOptions: NotifierOptions = { NsUpdateComponent, WarningComponent, StartStopRebuildComponent, - VerticalScalingComponent + VerticalScalingComponent, + HealingComponent ], imports: [ NotifierModule.withConfig(customNotifierOptions), diff --git a/src/app/instances/ns-instances/NSInstancesComponent.html b/src/app/instances/ns-instances/NSInstancesComponent.html index 523ee44..568ac86 100644 --- a/src/app/instances/ns-instances/NSInstancesComponent.html +++ b/src/app/instances/ns-instances/NSInstancesComponent.html @@ -32,6 +32,7 @@ Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.i {{configStateSecondStep}} {{operationalStateThirdStep}} {{operationalStateFourthStep}} + {{operationalStateFifthStep}} diff --git a/src/app/instances/ns-instances/NSInstancesComponent.ts b/src/app/instances/ns-instances/NSInstancesComponent.ts index e7de4a7..14d6349 100644 --- a/src/app/instances/ns-instances/NSInstancesComponent.ts +++ b/src/app/instances/ns-instances/NSInstancesComponent.ts @@ -84,6 +84,9 @@ export class NSInstancesComponent implements OnInit { /** operational State scaling data @public */ public operationalStateFourthStep: string = CONFIGCONSTANT.operationalStateFourthStep; + /** operational State healing data @public */ + public operationalStateFifthStep: string = CONFIGCONSTANT.operationalStateFifthStep; + /** Config State init data @public */ public configStateFirstStep: string = CONFIGCONSTANT.configStateFirstStep; @@ -161,27 +164,33 @@ export class NSInstancesComponent implements OnInit { { value: this.operationalStateFirstStep, title: this.operationalStateFirstStep }, { value: this.operationalStateSecondStep, title: this.operationalStateSecondStep }, { value: this.operationalStateThirdStep, title: this.operationalStateThirdStep }, - { value: this.operationalStateFourthStep, title: this.operationalStateFourthStep } + { value: this.operationalStateFourthStep, title: this.operationalStateFourthStep }, + { value: this.operationalStateFifthStep, title: this.operationalStateFifthStep } ] } }, valuePrepareFunction: (cell: NSDInstanceData, row: NSDInstanceData): string => { if (row.OperationalStatus === this.operationalStateFirstStep) { return ` - - `; + + `; } else if (row.OperationalStatus === this.operationalStateSecondStep) { return ` - - `; + + `; } else if (row.OperationalStatus === this.operationalStateThirdStep) { return ` - - `; + + `; } else if (row.OperationalStatus === this.operationalStateFourthStep) { return ` - - `; + + `; + } + else if (row.OperationalStatus === this.operationalStateFifthStep) { + return ` + + `; } else { return `${row.OperationalStatus}`; } @@ -203,16 +212,16 @@ export class NSInstancesComponent implements OnInit { valuePrepareFunction: (cell: NSDInstanceData, row: NSDInstanceData): string => { if (row.ConfigStatus === this.configStateFirstStep) { return ` - - `; + + `; } else if (row.ConfigStatus === this.configStateSecondStep) { return ` - - `; + + `; } else if (row.ConfigStatus === this.configStateThirdStep) { return ` - - `; + + `; } else { return `${row.ConfigStatus}`; } diff --git a/src/app/utilities/healing/HealingComponent.html b/src/app/utilities/healing/HealingComponent.html new file mode 100644 index 0000000..ad22db6 --- /dev/null +++ b/src/app/utilities/healing/HealingComponent.html @@ -0,0 +1,107 @@ + + +
+ + +
+ \ No newline at end of file diff --git a/src/app/utilities/healing/HealingComponent.scss b/src/app/utilities/healing/HealingComponent.scss new file mode 100644 index 0000000..f7fbc0a --- /dev/null +++ b/src/app/utilities/healing/HealingComponent.scss @@ -0,0 +1,17 @@ +/* + Copyright 2020 TATA ELXSI + + Licensed under the Apache License, Version 2.0 (the 'License'); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Author: SANDHYA JS (sandhya.j@tataelxsi.co.in) +*/ diff --git a/src/app/utilities/healing/HealingComponent.ts b/src/app/utilities/healing/HealingComponent.ts new file mode 100644 index 0000000..b3a1072 --- /dev/null +++ b/src/app/utilities/healing/HealingComponent.ts @@ -0,0 +1,300 @@ +/* + Copyright 2020 TATA ELXSI + + Licensed under the Apache License, Version 2.0 (the 'License'); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Author: SANDHYA JS (sandhya.j@tataelxsi.co.in) +*/ +/** + * @file Healing Component + */ +import { isNullOrUndefined } from 'util'; +import { HttpHeaders } from '@angular/common/http'; +import { Component, Injector, Input, OnInit } from '@angular/core'; +import { AbstractControl, FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateService } from '@ngx-translate/core'; +import { APIURLHEADER, ERRORDATA, MODALCLOSERESPONSEDATA, URLPARAMS } from 'CommonModel'; +import { environment } from 'environment'; +import { VDUMAP, VDUMAPPINGS } from 'NSInstanceModel'; +import { RestService } from 'RestService'; +import { SharedService } from 'SharedService'; +import { InstanceData, VDUR, VNFInstanceDetails } from 'VNFInstanceModel'; +/** + * Creating component + * @Component takes HealingComponent.html as template url + */ +@Component({ + selector: 'app-healing', + templateUrl: './HealingComponent.html', + styleUrls: ['./HealingComponent.scss'] +}) +export class HealingComponent implements OnInit { + /** To inject services @public */ + public injector: Injector; + /** Instance for active modal service @public */ + public activeModal: NgbActiveModal; + /** Check the loading results @public */ + public isLoadingResults: Boolean = false; + /** Give the message for the loading @public */ + public message: string = 'PLEASEWAIT'; + /** Member index of the NS @public */ + public memberVNFIndex: {}[] = []; + /** Items for the memberVNFIndex @public */ + public memberTypes: {}[]; + /** Items for the Day1 operation @public */ + public day1Operation: {}[]; + /** Items for the vdu-Id and count-index @public */ + public vdu: {}[]; + /** Selected VNFInstanceId @public */ + public selectedvnfId: string = ''; + /** Contains vduId @public */ + public vduId: {}; + /** VDU Mapping @public */ + public vduMap: VDUMAP = {}; + /** Items for countIndex @public */ + public countIndex: {}[]; + /** Contains vnfInstanceId of the selected MemberVnfIndex @public */ + public instanceId: string; + /** FormGroup instance added to the form @ html @public */ + public healingForm: FormGroup; + /** Form valid on submit trigger @public */ + public submitted: boolean = false; + /** VDU Form array @private */ + private vduFormArray: FormArray; + /** Array holds VNFR Data filtered with nsr ID @private */ + private nsIdFilteredData: {}[] = []; + /** FormBuilder instance added to the formBuilder @private */ + private formBuilder: FormBuilder; + /** Instance of the rest service @private */ + private restService: RestService; + /** Controls the header form @private */ + private headers: HttpHeaders; + /** Contains tranlsate instance @private */ + private translateService: TranslateService; + /** Input contains component objects @private */ + @Input() private params: URLPARAMS; + /** Contains all methods related to shared @private */ + private sharedService: SharedService; + /** Holds teh instance of AuthService class of type AuthService @private */ + private router: Router; + + constructor(injector: Injector) { + this.injector = injector; + this.restService = this.injector.get(RestService); + this.activeModal = this.injector.get(NgbActiveModal); + this.translateService = this.injector.get(TranslateService); + this.formBuilder = this.injector.get(FormBuilder); + this.sharedService = this.injector.get(SharedService); + this.router = this.injector.get(Router); + } + + /** convenience getter for easy access to form fields */ + get f(): FormGroup['controls'] { return this.healingForm.controls; } + + /** + * Lifecyle Hooks the trigger before component is instantiate + */ + public ngOnInit(): void { + this.initializeForm(); + this.getmemberIndex(); + this.day1Operation = [ + { id: 'true', name: this.translateService.instant('True') }, + { id: '', name: this.translateService.instant('False') } + ]; + this.headers = new HttpHeaders({ + 'Content-Type': 'application/json', + Accept: 'application/json', + 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0' + }); + } + + /** Generate primitive params @public */ + get vduParamsBuilder(): FormGroup { + return this.formBuilder.group({ + vduId: [null], + countIndex: [null], + runDay1: [null] + }); + } + + /** Initialize Healing Forms @public */ + public initializeForm(): void { + this.healingForm = this.formBuilder.group({ + memberIndex: [null, [Validators.required]], + run_day1: [null], + vdu: this.formBuilder.array([]) + }); + } + + /** Handle FormArray Controls @public */ + public getControls(): AbstractControl[] { + return (this.healingForm.get('vdu') as FormArray).controls; + } + + /** Get the member-vnf-index from NS Package -> vnf-profile @private */ + private getmemberIndex(): void { + this.isLoadingResults = true; + const vnfInstanceData: {}[] = []; + this.restService.getResource(environment.VNFINSTANCES_URL).subscribe((vnfInstancesData: VNFInstanceDetails[]): void => { + vnfInstancesData.forEach((vnfData: VNFInstanceDetails): void => { + const nsrId: string = 'nsr-id-ref'; + const memberIndex: string = 'member-vnf-index-ref'; + const vnfDataObj: {} = + { + VNFInstanceId: vnfData._id, + // eslint-disable-next-line security/detect-object-injection + MemberIndex: vnfData[memberIndex], + // eslint-disable-next-line security/detect-object-injection + NS: vnfData[nsrId] + }; + vnfInstanceData.push(vnfDataObj); + }); + const nsId: string = 'NS'; + // eslint-disable-next-line security/detect-object-injection + this.nsIdFilteredData = vnfInstanceData.filter((vnfdData: {}[]): boolean => vnfdData[nsId] === this.params.id); + this.nsIdFilteredData.forEach((resVNF: InstanceData): void => { + const assignMemberIndex: {} = { + id: resVNF.MemberIndex, + vnfinstanceId: resVNF.VNFInstanceId + }; + this.memberVNFIndex.push(assignMemberIndex); + }); + this.memberTypes = this.memberVNFIndex; + this.isLoadingResults = false; + }, (error: ERRORDATA): void => { + this.restService.handleError(error, 'get'); + this.isLoadingResults = false; + }); + } + + /** Getting vdu-id & count-index from VNFInstance API */ + public getVdu(id: string): void { + this.isLoadingResults = true; + this.vdu = []; + const vnfInstanceData: {}[] = []; + this.instanceId = id; + if (!isNullOrUndefined(id)) { + this.restService.getResource(environment.VNFINSTANCES_URL + '/' + id). + subscribe((vnfInstanceDetail: VNFInstanceDetails): void => { + this.selectedvnfId = vnfInstanceDetail['vnfd-ref']; + if (!isNullOrUndefined(vnfInstanceDetail.vdur)) { + vnfInstanceDetail.vdur.forEach((vdu: VDUR): void => { + const vnfInstanceDataObj: {} = + { + 'count-index': vdu['count-index'], + VDU: vdu['vdu-id-ref'] + }; + vnfInstanceData.push(vnfInstanceDataObj); + }); + this.vdu = vnfInstanceData; + this.vduId = this.vdu.filter((vdu: { VDU?: string }, index: number, self: {}[]): {} => + index === self.findIndex((t: { VDU?: string }): {} => ( + // eslint-disable-next-line security/detect-object-injection + t.VDU === vdu.VDU + )) + ); + this.isLoadingResults = false; + } + } + , (error: ERRORDATA): void => { + this.restService.handleError(error, 'get'); + this.isLoadingResults = false; + }); + } + } + + /** Getting count-index by filtering id */ + public getCountIndex(id: string): void { + const VDU: string = 'VDU'; + // eslint-disable-next-line security/detect-object-injection + this.countIndex = this.vdu.filter((vnfdData: {}[]): boolean => vnfdData[VDU] === id); + } + + + /** Add vdu @public */ + public addVdu(): void { + this.vduFormArray = this.healingForm.get('vdu') as FormArray; + this.vduFormArray.push(this.vduParamsBuilder); + } + + /** Remove vdu @public */ + public removeMapping(index: number): void { + this.vduFormArray.removeAt(index); + } + + /** If form is valid and call healInstances method to initialize healing @public */ + public manualHealingTrigger(): void { + this.submitted = true; + let healingPayload: object = {}; + this.sharedService.cleanForm(this.healingForm); + if (this.healingForm.invalid) { return; } // Proceed, onces form is valid + this.vduMap.vdu_mappings = []; + this.healingForm.value.vdu.forEach((res: VDUMAPPINGS): void => { + this.vduMap.vdu_mappings.push({ 'vdu-id': res.vduId, 'count-index': res.countIndex, 'run-day1': Boolean(res.runDay1) }); + }); + if (this.vduMap.vdu_mappings.length !== 0) { + healingPayload = { + healVnfData: + [{ + vnfInstanceId: this.instanceId, + cause: 'manual', + additionalParams: { + 'run-day1': false, + vdu: this.vduMap.vdu_mappings + } + }] + }; + } else { + healingPayload = { + healVnfData: + [{ + vnfInstanceId: this.instanceId, + cause: 'manual', + additionalParams: { + 'run-day1': Boolean(this.getFormControl('run_day1').value) + } + }] + }; + } + this.healInstances(healingPayload); + } + + /** Initialize the healing @public */ + public healInstances(healingPayload: object): void { + this.isLoadingResults = true; + const apiURLHeader: APIURLHEADER = { + url: environment.NSDINSTANCES_URL + '/' + this.params.id + '/heal', + httpOptions: { headers: this.headers } + }; + const modalData: MODALCLOSERESPONSEDATA = { + message: 'Done' + }; + this.restService.postResource(apiURLHeader, healingPayload).subscribe((result: {}): void => { + this.activeModal.close(modalData); + this.router.navigate(['/instances/ns/history-operations/' + this.params.id]).catch((): void => { + // Catch Navigation Error + }); + }, (error: ERRORDATA): void => { + this.restService.handleError(error, 'post'); + this.isLoadingResults = false; + }); + } + + /** Used to get the AbstractControl of controlName passed @private */ + private getFormControl(controlName: string): AbstractControl { + // eslint-disable-next-line security/detect-object-injection + return this.healingForm.controls[controlName]; + } +} diff --git a/src/app/utilities/ns-instances-action/NSInstancesActionComponent.html b/src/app/utilities/ns-instances-action/NSInstancesActionComponent.html index 517dbde..6a32735 100644 --- a/src/app/utilities/ns-instances-action/NSInstancesActionComponent.html +++ b/src/app/utilities/ns-instances-action/NSInstancesActionComponent.html @@ -70,6 +70,10 @@ Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.i placement="left" data-container="body" ngbTooltip="{{'SCALING' | translate}}"> {{'SCALING' | translate}} +