Feature 11009 Ns Config Template as first class citizens in OSM
- Added UI support
- Added New page for NS config template under packages section
- User can create, edit & delete template
- Added template field under ns instance
Change-Id: I3e9a895b4415165d6c96b9840dee64cc197b40e4
Signed-off-by: SANDHYA.JS <sandhya.j@tataelxsi.co.in>
diff --git a/src/app/utilities/compose-packages/ComposePackages.html b/src/app/utilities/compose-packages/ComposePackages.html
index 1067054..03ec3b7 100644
--- a/src/app/utilities/compose-packages/ComposePackages.html
+++ b/src/app/utilities/compose-packages/ComposePackages.html
@@ -17,24 +17,56 @@
-->
<form [formGroup]="packagesForm" (ngSubmit)="createPackages()" autocomplete="off">
<div class="modal-header">
- <h4 class="modal-title" id="modal-basic-title">{{'CREATEPACKAGE' | translate}}</h4>
+ <h4 class="modal-title" *ngIf="!template" id="modal-basic-title">{{'CREATEPACKAGE' | translate}}</h4>
+ <h4 class="modal-title" *ngIf="params.page === 'ns-config-template'" id="modal-basic-title">
+ {{'PAGE.NSCONFIGTEMPLATE.CREATENSCONFIGTEMPLATE' | translate}}</h4>
+ <h4 class="modal-title" *ngIf="params.page === 'ns-config-template-edit'" id="modal-basic-title">
+ {{'PAGE.NSCONFIGTEMPLATE.EDITNSCONFIGTEMPLATE' | translate}}</h4>
<button class="button-xs" type="button" class="close" (click)="activeModal.close()">
<i class="fas fa-times-circle text-danger"></i>
</button>
</div>
<div class="modal-body">
<div class="form-group row mb-3">
- <label class="col-sm-12 col-form-label mandatory-label" [ngClass]="{'text-danger': packagesForm.invalid === true && submitted === true}">{{'MANDATORYCHECK' | translate}}</label>
- <label class="col-sm-6 col-form-label">{{'PACKAGE' | translate}} {{'NAME' | translate}}*</label>
+ <label class="col-sm-12 col-form-label mandatory-label"
+ [ngClass]="{'text-danger': packagesForm.invalid === true && submitted === true}">{{'MANDATORYCHECK' |
+ translate}}</label>
+ <label class="col-sm-6 col-form-label" *ngIf="!template else configtemplate">{{'PACKAGE' | translate}} {{'NAME' | translate}}*</label>
+ <ng-template #configtemplate>
+ <label class="col-sm-6 col-form-label">{{'NAME' | translate}}*</label>
+ </ng-template>
<div class="col-sm-6">
- <input type="text" class="form-control" placeholder="{{'PACKAGE' | translate}} {{'NAME' | translate}}"
- formControlName="name" id="name" [ngClass]="{ 'is-invalid': submitted && f.name.errors }" required>
+ <input type="text" class="form-control" placeholder="{{'NAME' | translate}}" formControlName="name" id="name"
+ [ngClass]="{ 'is-invalid': submitted && f.name.errors }" required>
+ </div>
+ </div>
+ <div class="form-group row mb-3" *ngIf="template">
+ <label class="col-sm-6 col-form-label">{{'NSD' | translate}} {{'NAME' | translate}}*</label>
+ <div class="col-sm-6">
+ <ng-select formControlName="nsdId" id="nsd" [items]="nsdDetails"
+ placeholder="{{'SELECT' | translate}} {{'NSDNAME' | translate}}" bindLabel="nsName" bindValue="nsName"
+ [ngClass]="{ 'is-invalid': submitted && f.nsdId.errors }">
+ </ng-select>
+ </div>
+ </div>
+ <div class="form-group row mb-3" *ngIf="template">
+ <label class="col-sm-6 col-form-label">{{'CONFIG' | translate}}*</label>
+ <div class="col-sm-6">
+ <textarea rows="10" cols="50" class="form-control" placeholder="{{'YAMLCONFIG' | translate}}"
+ formControlName="config" id="config"></textarea>
+ <div class="fileupload-text mt-1 mb-1">{{'FILEUPLOADLABEL' | translate}}</div>
+ <div class="custom-file">
+ <input type="file" #fileInputConfig class="fileupload custom-file-input"
+ (change)="configFile($event.target.files)" id="customConfigFile">
+ </div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" (click)="activeModal.close()">{{'CANCEL' | translate}}</button>
<button type="submit" class="btn btn-primary">{{'CREATE' | translate}}</button>
+ <button *ngIf="params.page === 'ns-config-template-edit'" type="submit" class="btn btn-primary">{{'EDIT' |
+ translate}}</button>
</div>
</form>
<app-loader [waitingMessage]="message" *ngIf="isLoadingResults"></app-loader>
\ No newline at end of file
diff --git a/src/app/utilities/compose-packages/ComposePackages.ts b/src/app/utilities/compose-packages/ComposePackages.ts
index 6f2a031..0dff17a 100644
--- a/src/app/utilities/compose-packages/ComposePackages.ts
+++ b/src/app/utilities/compose-packages/ComposePackages.ts
@@ -19,19 +19,21 @@
* @file Info Compose Package Model
*/
import { HttpClient, HttpHeaders } from '@angular/common/http';
-import { Component, Injector, Input, OnInit } from '@angular/core';
-import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Component, ElementRef, Injector, Input, OnInit, ViewChild } from '@angular/core';
+import { AbstractControl, 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 { NotifierService } from 'angular-notifier';
-import { APIURLHEADER, ERRORDATA, URLPARAMS } from 'CommonModel';
+import { APIURLHEADER, ERRORDATA, MODALCLOSERESPONSEDATA, URLPARAMS } from 'CommonModel';
import { DataService } from 'DataService';
import { environment } from 'environment';
import * as jsyaml from 'js-yaml';
+import { NSConfigData } from 'NSCONFIGTEMPLATEMODEL';
+import { NSDDetails } from 'NSDModel';
import * as pako from 'pako';
import { RestService } from 'RestService';
-import { SharedService } from 'SharedService';
+import { SharedService, isNullOrUndefined } from 'SharedService';
/** This is added globally by the tar.js library */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -64,13 +66,43 @@
public packagesForm: FormGroup;
/** Form submission Add */
- public submitted: boolean = false;
+ public submitted = false;
/** To handle loader status for API call @public */
- public isLoadingResults: boolean = false;
+ public isLoadingResults = false;
/** Give the message for the loading @public */
- public message: string = 'PLEASEWAIT';
+ public message = 'PLEASEWAIT';
+
+ /** contains NSD name @public */
+ public nsName: {}[] = [];
+
+ /** contains NSD details @public */
+ public nsdDetails: {}[];
+
+ /** contains NSD details filtered by id @public */
+ public nsdName: string;
+
+ /** Contains config details @public */
+ public config: string;
+
+ /** contains NSD details filtered by name @public */
+ public nsId: string;
+
+ /** Check if template or not @public */
+ public template = false;
+
+ /** Data of NS config @public */
+ public details: NSConfigData;
+
+ /** Data of NF packages @public */
+ public nsConfigData: NSConfigData[] = [];
+
+ /** Element ref for fileInputConfig @public */
+ @ViewChild('fileInputConfig') fileInputConfig: ElementRef<HTMLInputElement>;
+
+ /** Element ref for fileInputConfigLabel @public */
+ @ViewChild('fileInputConfigLabel') fileInputConfigLabel: ElementRef<HTMLLabelElement>;
/** FormBuilder instance added to the formBuilder @private */
private formBuilder: FormBuilder;
@@ -84,15 +116,15 @@
/** Controls the header form @private */
private headers: HttpHeaders;
- /** Create URL holds the end point of any packages @private */
- private createURL: string;
-
- /** Input contains component objects @private */
- @Input() private params: URLPARAMS;
+ /** Input contains component objects @public */
+ @Input() public params: URLPARAMS;
/** Holds the end point @private */
private endPoint: string;
+ /** ModalData instance of modal @private */
+ private modalData: MODALCLOSERESPONSEDATA;
+
/** Contains all methods related to shared @private */
private sharedService: SharedService;
@@ -121,48 +153,191 @@
* Lifecyle Hooks the trigger before component is instantiate
*/
public ngOnInit(): void {
- this.headers = new HttpHeaders({
- 'Content-Type': 'application/gzip',
- Accept: 'application/json',
- 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0'
- });
this.initializeForm();
+ if (this.params.page === 'ns-config-template') {
+ this.template = true;
+ this.getNsdPackageDetails();
+ } else if (this.params.page === 'ns-config-template-edit') {
+ this.template = true;
+ this.getNsdPackageDetails();
+ this.getFormControl('nsdId').disable();
+ } else {
+ this.getFormControl('nsdId').disable();
+ this.getFormControl('config').disable();
+ }
}
/** initialize Forms @public */
public initializeForm(): void {
this.packagesForm = this.formBuilder.group({
- name: ['', [Validators.required]]
+ name: ['', [Validators.required]],
+ nsdId: [null, [Validators.required]],
+ config: [null]
+ });
+ }
+
+ /** Get NSD Package details @public */
+ public getNsdPackageDetails(): void {
+ this.restService.getResource(environment.NSDESCRIPTORSCONTENT_URL)
+ .subscribe((nsdPackageData: NSDDetails[]): void => {
+ nsdPackageData.forEach((nsData: NSDDetails): void => {
+ const names: {} = {
+ nsName: nsData.name,
+ nsId: nsData._id
+ };
+ this.nsName.push(names);
+ });
+ this.nsdDetails = this.nsName;
+ if (this.params.page === 'ns-config-template-edit') {
+ this.getNSConfigDetails(environment.NSCONFIGTEMPLATE_URL + '/' + this.params.id, this.nsdDetails);
+ }
+ }, (error: ERRORDATA): void => {
+ this.restService.handleError(error, 'get');
+ });
+ }
+
+ /** Get the NSD Content List & patch value in edit form @public */
+ public getNSConfigDetails(URL: string, name: {}[]): void {
+ this.restService.getResource(URL).subscribe((content: NSConfigData): void => {
+ this.nsConfigData.push(content);
+ this.details = this.nsConfigData[0];
+ const nsId: string = 'nsId';
+ // eslint-disable-next-line security/detect-object-injection
+ const nsdId: {}[] = name.filter((nsdData: {}[]): boolean => nsdData[nsId] === this.details.nsdId);
+ const nsName: string = 'nsName';
+ for (const data of nsdId) {
+ // eslint-disable-next-line security/detect-object-injection
+ this.nsdName = data[nsName];
+ }
+ if (!isNullOrUndefined(this.details.config)) {
+ this.config = jsyaml.dump(this.details.config);
+ }
+ this.packagesForm.patchValue({ name: this.details.name, nsdId: this.nsdName, config: this.config });
+ this.isLoadingResults = false;
+ }, (error: ERRORDATA): void => {
+ this.restService.handleError(error, 'get');
+ this.isLoadingResults = false;
});
}
/** Create packages @public */
public createPackages(): void {
this.submitted = true;
+ this.modalData = {
+ message: 'Done'
+ };
this.sharedService.cleanForm(this.packagesForm);
if (!this.packagesForm.invalid) {
this.isLoadingResults = true;
- if (this.params.page === 'ns-package') {
- this.endPoint = environment.NSDESCRIPTORSCONTENT_URL;
- } else if (this.params.page === 'vnf-package') {
- this.endPoint = environment.VNFPACKAGESCONTENT_URL;
- }
- const descriptor: string = this.packageYaml(this.params.page);
- try {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const tar: any = new Tar();
- const out: Uint8Array = tar.append(this.packagesForm.value.name + '/' + this.packagesForm.value.name + '.yaml',
- descriptor, { type: '0' });
- const gzipContent: Uint8Array = pako.gzip(out);
- this.createPackageApi(gzipContent.buffer);
- } catch (e) {
- this.isLoadingResults = false;
- this.notifierService.notify('error', this.translateService.instant('ERROR'));
+ if (this.params.page === 'ns-package' || this.params.page === 'vnf-package') {
+ if (this.params.page === 'ns-package') {
+ this.endPoint = environment.NSDESCRIPTORSCONTENT_URL;
+ } else if (this.params.page === 'vnf-package') {
+ this.endPoint = environment.VNFPACKAGESCONTENT_URL;
+ }
+ const descriptor: string = this.packageYaml(this.params.page);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const tar: any = new Tar();
+ const out: Uint8Array = tar.append(this.packagesForm.value.name + '/' + this.packagesForm.value.name + '.yaml',
+ descriptor, { type: '0' });
+ const gzipContent: Uint8Array = pako.gzip(out);
+ this.createPackageApi(gzipContent.buffer);
+ } catch (e) {
+ this.isLoadingResults = false;
+ this.notifierService.notify('error', this.translateService.instant('ERROR'));
+ }
+ } else {
+ try {
+ this.headers = new HttpHeaders({
+ Accept: 'application/json',
+ 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0'
+ });
+ if (this.params.page === 'ns-config-template') {
+ this.endPoint = environment.NSCONFIGTEMPLATE_URL;
+ this.createTemplate(this.endPoint);
+ } else if (this.params.page === 'ns-config-template-edit') {
+ this.endPoint = environment.NSCONFIGTEMPLATE_URL + '/' + this.params.id + '/' + 'template_content';
+ this.editTemplate(this.endPoint);
+ }
+ } catch (e) {
+ this.isLoadingResults = false;
+ this.notifierService.notify('error', this.translateService.instant('ERROR'));
+ }
}
}
}
+
+ /** Post config template @public */
+ public createTemplate(urlHeader: string): void {
+ this.isLoadingResults = true;
+ const apiURLHeader: APIURLHEADER = {
+ url: urlHeader,
+ httpOptions: { headers: this.headers }
+ };
+ if (isNullOrUndefined(this.packagesForm.value.config) || this.packagesForm.value.config === '') {
+ delete this.packagesForm.value.config;
+ } else {
+ const validJSON: boolean = this.sharedService.checkJson(this.packagesForm.value.config);
+ if (validJSON) {
+ this.packagesForm.value.config = JSON.parse(this.packagesForm.value.config);
+ } else {
+ const getConfigJson: string = jsyaml.load(this.packagesForm.value.config, { json: true });
+ this.packagesForm.value.config = getConfigJson;
+ }
+ }
+ const nsName: string = 'nsName';
+ // eslint-disable-next-line security/detect-object-injection
+ const nsdId: {}[] = this.nsdDetails.filter((nsdData: {}[]): boolean => nsdData[nsName] === this.packagesForm.value.nsdId);
+ for (const data of nsdId) {
+ // eslint-disable-next-line @typescript-eslint/dot-notation
+ this.nsId = data['nsId'];
+ }
+ this.packagesForm.value.nsdId = this.nsId;
+ this.restService.postResource(apiURLHeader, (this.packagesForm.value)).subscribe((result: {}): void => {
+ this.activeModal.close(this.modalData);
+ this.isLoadingResults = false;
+ this.notifierService.notify('success', this.translateService.instant('PAGE.NSCONFIGTEMPLATE.TEMPLATECREATEDSUCCESSFULLY'));
+ }, (error: ERRORDATA): void => {
+ this.restService.handleError(error, 'post');
+ this.isLoadingResults = false;
+ });
+ }
+
+ /** Edit config template @public */
+ public editTemplate(urlHeader: string): void {
+ this.isLoadingResults = true;
+ const apiURLHeader: APIURLHEADER = {
+ url: urlHeader,
+ httpOptions: { headers: this.headers }
+ };
+ if (isNullOrUndefined(this.packagesForm.value.config) || this.packagesForm.value.config === '') {
+ delete this.packagesForm.value.config;
+ } else {
+ const validJSON: boolean = this.sharedService.checkJson(this.packagesForm.value.config);
+ if (validJSON) {
+ this.packagesForm.value.config = JSON.parse(this.packagesForm.value.config);
+ } else {
+ const getConfigJson: string = jsyaml.load(this.packagesForm.value.config, { json: true });
+ this.packagesForm.value.config = getConfigJson;
+ }
+ }
+ this.restService.putResource(apiURLHeader, (this.packagesForm.value)).subscribe((result: {}): void => {
+ this.activeModal.close(this.modalData);
+ this.isLoadingResults = false;
+ this.notifierService.notify('success', this.translateService.instant('PAGE.NSCONFIGTEMPLATE.TEMPLATEEDITEDSUCCESSFULLY'));
+ }, (error: ERRORDATA): void => {
+ this.restService.handleError(error, 'post');
+ this.isLoadingResults = false;
+ });
+ }
/** Create packages @public */
private createPackageApi(packageContent: ArrayBuffer | SharedArrayBuffer): void {
+ this.headers = new HttpHeaders({
+ 'Content-Type': 'application/gzip',
+ Accept: 'application/json',
+ 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0'
+ });
const apiURLHeader: APIURLHEADER = {
url: this.endPoint,
httpOptions: { headers: this.headers }
@@ -176,6 +351,42 @@
this.restService.handleError(error, 'post');
});
}
+ /** Config file process @private */
+ public configFile(files: FileList): void {
+ if (files && files.length === 1) {
+ const fileFormat: string = this.sharedService.fetchFileExtension(files).toLocaleLowerCase();
+ if (fileFormat === 'yaml' || fileFormat === 'yml') {
+ this.sharedService.getFileString(files, 'yaml').then((fileContent: string): void => {
+ this.packagesForm.get('config').setValue(fileContent);
+ }).catch((err: string): void => {
+ if (err === 'typeError') {
+ this.notifierService.notify('error', this.translateService.instant('YAMLFILETYPEERRROR'));
+ } else {
+ this.notifierService.notify('error', this.translateService.instant('ERROR'));
+ }
+ this.fileInputConfigLabel.nativeElement.innerText = this.translateService.instant('CHOOSEFILE');
+ this.fileInputConfig.nativeElement.value = null;
+ });
+ } else if (fileFormat === 'json') {
+ this.sharedService.getFileString(files, 'json').then((fileContent: string): void => {
+ const getConfigJson: string = jsyaml.load(fileContent, { json: true });
+ this.packagesForm.get('config').setValue(JSON.stringify(getConfigJson));
+ }).catch((err: string): void => {
+ if (err === 'typeError') {
+ this.notifierService.notify('error', this.translateService.instant('JSONFILETYPEERRROR'));
+ } else {
+ this.notifierService.notify('error', this.translateService.instant('ERROR'));
+ }
+ this.fileInputConfigLabel.nativeElement.innerText = this.translateService.instant('CHOOSEFILE');
+ this.fileInputConfig.nativeElement.value = null;
+ });
+ }
+ } else if (files && files.length > 1) {
+ this.notifierService.notify('error', this.translateService.instant('DROPFILESVALIDATION'));
+ }
+ this.fileInputConfigLabel.nativeElement.innerText = files[0].name;
+ this.fileInputConfig.nativeElement.value = null;
+ }
/** Compose NS Packages @private */
private composeNSPackages(id: string): void {
let packageUrl: string;
@@ -241,4 +452,10 @@
}
return jsyaml.dump(packageYaml, { sortKeys: true });
}
+
+ /** Used to get the AbstractControl of controlName passed @private */
+ private getFormControl(controlName: string): AbstractControl {
+ // eslint-disable-next-line security/detect-object-injection
+ return this.packagesForm.controls[controlName];
+ }
}