From 2fa73553d31c7afb4bdf9eca2f77fad6b987c1d2 Mon Sep 17 00:00:00 2001 From: Lathish Date: Fri, 23 Apr 2021 15:46:17 +0100 Subject: [PATCH] RIC Configuration from UI This enables the user to add/delete/update RIC config from dashboard. Unit test for this component will be covered in next commit. Issue-ID: NONRTRIC-498 Change-Id: Ia055127522f1784595c1667f1a8e4f0f22e14bd3 Signed-off-by: Lathish --- webapp-frontend/src/app/app-routing.module.ts | 2 + webapp-frontend/src/app/app.module.ts | 2 + webapp-frontend/src/app/interceptor.mock.ts | 5 + webapp-frontend/src/app/interfaces/ric.config.ts | 23 ++++ .../src/app/mock/ric-configuration.json | 15 +++ .../sidenav-list/sidenav-list.component.html | 16 ++- .../sidenav-list/sidenav-list.component.ts | 6 + .../src/app/ric-config/ric-config.component.html | 53 +++++++++ .../src/app/ric-config/ric-config.component.scss | 23 ++++ .../src/app/ric-config/ric-config.component.ts | 129 +++++++++++++++++++++ .../src/app/ric-config/ricconfig.module.ts | 70 +++++++++++ .../src/app/services/policy/policy.service.ts | 16 +++ 12 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 webapp-frontend/src/app/interfaces/ric.config.ts create mode 100644 webapp-frontend/src/app/mock/ric-configuration.json create mode 100644 webapp-frontend/src/app/ric-config/ric-config.component.html create mode 100644 webapp-frontend/src/app/ric-config/ric-config.component.scss create mode 100644 webapp-frontend/src/app/ric-config/ric-config.component.ts create mode 100644 webapp-frontend/src/app/ric-config/ricconfig.module.ts diff --git a/webapp-frontend/src/app/app-routing.module.ts b/webapp-frontend/src/app/app-routing.module.ts index 387fdc1..9798f51 100644 --- a/webapp-frontend/src/app/app-routing.module.ts +++ b/webapp-frontend/src/app/app-routing.module.ts @@ -24,11 +24,13 @@ import { Routes, RouterModule } from '@angular/router'; import { MainComponent } from './main/main.component'; import { PolicyControlComponent } from '@policy/policy-control.component'; import { EICoordinatorComponent } from '@ei-coordinator/ei-coordinator.component'; +import { RicConfigComponent } from './ric-config/ric-config.component'; const routes: Routes = [ { path: '', component: MainComponent }, { path: 'policy', component: PolicyControlComponent }, + { path: 'ric-config', component: RicConfigComponent}, { path: 'ei-coordinator', component: EICoordinatorComponent } ]; diff --git a/webapp-frontend/src/app/app.module.ts b/webapp-frontend/src/app/app.module.ts index adca33a..7646d02 100644 --- a/webapp-frontend/src/app/app.module.ts +++ b/webapp-frontend/src/app/app.module.ts @@ -45,6 +45,7 @@ import { HttpMockRequestInterceptor } from './interceptor.mock'; import { environment } from 'environments/environment'; import { HttpRequestInterceptor } from './interceptor'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { RicConfigModule } from './ric-config/ricconfig.module'; export const isMock = environment.mock; @@ -71,6 +72,7 @@ export const isMock = environment.mock; MDBBootstrapModule.forRoot(), PolicyModule, EiCoordinatorModule, + RicConfigModule, ToastrModule.forRoot(), ], entryComponents: [ diff --git a/webapp-frontend/src/app/interceptor.mock.ts b/webapp-frontend/src/app/interceptor.mock.ts index 7163412..b1b81eb 100644 --- a/webapp-frontend/src/app/interceptor.mock.ts +++ b/webapp-frontend/src/app/interceptor.mock.ts @@ -47,6 +47,7 @@ import * as policytypes0 from "./mock/policy-type0.json"; import * as policyinstanceedit from "./mock/policy-instance-edit.json"; import * as ric1 from "./mock/ric1.json"; import * as ric2 from "./mock/ric2.json"; +import * as ricconfig from "./mock/ric-configuration.json"; import { delay } from "rxjs/operators"; const urls = [ @@ -154,6 +155,10 @@ const urls = [ url: "/ei-producer/v1/eiproducers/producer2/eijobs", json: eijobsProd2, }, + { + url: '/a1-policy/v2/configuration', + json: ricconfig + } ]; @Injectable() diff --git a/webapp-frontend/src/app/interfaces/ric.config.ts b/webapp-frontend/src/app/interfaces/ric.config.ts new file mode 100644 index 0000000..ba1ad2f --- /dev/null +++ b/webapp-frontend/src/app/interfaces/ric.config.ts @@ -0,0 +1,23 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2021 Nordix Foundation + * %% + * 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. + * ========================LICENSE_END=================================== + */ + + +export interface RicConfig { +} diff --git a/webapp-frontend/src/app/mock/ric-configuration.json b/webapp-frontend/src/app/mock/ric-configuration.json new file mode 100644 index 0000000..0183e2e --- /dev/null +++ b/webapp-frontend/src/app/mock/ric-configuration.json @@ -0,0 +1,15 @@ +{ + "config": { + "//description": "Application configuration", + "ric": [ + { + "name": "ric1", + "baseUrl": "http://localhost:30001/", + "managedElementIds": [ + "kista_1", + "kista_2" + ] + } + ] + } +} \ No newline at end of file diff --git a/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html index d110619..8a34a8f 100644 --- a/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html +++ b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html @@ -24,10 +24,20 @@ home Home - - assignment Policy + + dashboard Policy + expand_more + chevron_right + + + dashboard Policy Types + + + build RIC Config + + - assignment Enrichment Information Coordinator + dashboard Enrichment Information Coordinator diff --git a/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts index d44da90..06be8f2 100644 --- a/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts +++ b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts @@ -28,6 +28,7 @@ import { UiService } from '@services/ui/ui.service'; export class SidenavListComponent implements OnInit { darkMode: boolean; @Output() sidenavClose = new EventEmitter(); + showSubMenu = false; constructor(private ui: UiService) { } @@ -39,6 +40,11 @@ export class SidenavListComponent implements OnInit { public onSidenavClose = () => { this.sidenavClose.emit(); + this.showSubMenu = !this.showSubMenu; + } + + public toggle(){ + this.showSubMenu = !this.showSubMenu; } } diff --git a/webapp-frontend/src/app/ric-config/ric-config.component.html b/webapp-frontend/src/app/ric-config/ric-config.component.html new file mode 100644 index 0000000..8ea8082 --- /dev/null +++ b/webapp-frontend/src/app/ric-config/ric-config.component.html @@ -0,0 +1,53 @@ + +
+
RIC Configuration
+ +
+
+ + +
+
+ + This field is required. + +
+
+ + Should be a valid JSON. + +
+
+ +
+
+
+ +
\ No newline at end of file diff --git a/webapp-frontend/src/app/ric-config/ric-config.component.scss b/webapp-frontend/src/app/ric-config/ric-config.component.scss new file mode 100644 index 0000000..421d33d --- /dev/null +++ b/webapp-frontend/src/app/ric-config/ric-config.component.scss @@ -0,0 +1,23 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2021 Nordix Foundation + * %% + * 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. + * ========================LICENSE_END=================================== + */ + + textarea[cdktextareaautosize].mat-input-element { + overflow: auto; +} \ No newline at end of file diff --git a/webapp-frontend/src/app/ric-config/ric-config.component.ts b/webapp-frontend/src/app/ric-config/ric-config.component.ts new file mode 100644 index 0000000..cff6bd9 --- /dev/null +++ b/webapp-frontend/src/app/ric-config/ric-config.component.ts @@ -0,0 +1,129 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2021 Nordix Foundation + * %% + * 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. + * ========================LICENSE_END=================================== + */ +import { Component, OnInit, Output } from "@angular/core"; +import { RicConfig } from "@app/interfaces/ric.config"; +import { + AbstractControl, + ControlContainer, + FormControl, + FormGroup, + FormGroupDirective, + ValidatorFn, + Validators, +} from "@angular/forms"; +import { PolicyService } from "@services/policy/policy.service"; +import { EventEmitter } from "@angular/core"; +import { NotificationService } from "@app/services/ui/notification.service"; +import { HttpResponse } from "@angular/common/http"; + +@Component({ + selector: "nrcp-ric-config", + templateUrl: "./ric-config.component.html", + styleUrls: ["./ric-config.component.scss"], + viewProviders: [ + { provide: ControlContainer, useExisting: FormGroupDirective }, + ], +}) + +export class RicConfigComponent implements OnInit { + @Output() validJson: EventEmitter = new EventEmitter(); + ricConfig: string; + ricConfigForm: FormGroup = new FormGroup({}); + + constructor(private policyService: PolicyService, + private notificationService: NotificationService) { + } + + ngOnInit(): void { + let initialJson: RicConfig = "{}"; + this.getConfig(); + this.ricConfigForm.addControl( + "ricConfigInfo", + new FormControl(initialJson, [ + Validators.required, + this.jsonValidator(), + ]) + ); + } + + jsonValidator(): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + const notValid = !this.isJsonValid(control.value); + this.handleJsonChangeEvent(notValid, control.value); + return notValid ? { invalidJson: { value: control.value } } : null; + }; + } + + isJsonValid(json: string): boolean { + let valid = false as boolean; + try { + if (json != null) { + JSON.parse(json); + valid = true; + } + } catch (jsonError) {} + return valid; + } + + handleJsonChangeEvent(notValid: boolean, newValue: string): void { + let json = newValue; + if (notValid) { + json = null; + } + this.validJson.emit(json); + } + get ricConfigInfo(): AbstractControl { + return this.ricConfigForm + ? this.ricConfigForm.get("ricConfigInfo") + : null; + } + + formatJsonInput(): void { + let jsonBefore: string = this.ricConfigInfo.value; + let jsonAfter = formatJsonString(JSON.parse(jsonBefore)); + this.ricConfigInfo.setValue(jsonAfter); + } + + getConfig() { + this.policyService.getConfiguration().subscribe((ricConfig: RicConfig) => { + this.ricConfig = formatJsonString(ricConfig); + let initialJson: RicConfig; + if (this.ricConfig) { + initialJson = this.ricConfig; + } else { + initialJson = "{}"; + } + this.ricConfigInfo.setValue(initialJson); + }); + } + + updateRicConfig() { + let updateRic = this.ricConfigInfo.value.replace(/\n/g, ''); + this.policyService.updateConfiguration(updateRic).subscribe((response: HttpResponse) => { + if (response.status === 200) { + this.notificationService.success("RIC Configuration Updated!"); + } + }); + } +} + +export function formatJsonString(jsonToFormat: any): string { + return JSON.stringify(jsonToFormat, null, 2); +} diff --git a/webapp-frontend/src/app/ric-config/ricconfig.module.ts b/webapp-frontend/src/app/ric-config/ricconfig.module.ts new file mode 100644 index 0000000..712356b --- /dev/null +++ b/webapp-frontend/src/app/ric-config/ricconfig.module.ts @@ -0,0 +1,70 @@ +// - +// ========================LICENSE_START================================= +// O-RAN-SC +// %% +// Copyright (C) 2021: Nordix Foundation +// %% +// 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. +// ========================LICENSE_END=================================== +// + +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { MatTableModule } from '@angular/material/table'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatIconModule } from '@angular/material/icon'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSelectModule } from '@angular/material/select'; +import { MaterialDesignFrameworkModule } from 'angular6-json-schema-form'; +import { Routes, RouterModule } from '@angular/router'; +import { MatSortModule } from '@angular/material/sort'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { MatCardModule } from '@angular/material/card'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RicConfigComponent } from './ric-config.component'; + +const routes:Routes = [ + {path: 'ric-config', component: RicConfigComponent} +]; + +@NgModule({ + declarations: [ + RicConfigComponent + ], + imports: [ + BrowserAnimationsModule, + CommonModule, + FlexLayoutModule, + MatButtonModule, + MatCardModule, + MatDialogModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatSelectModule, + MatSortModule, + MaterialDesignFrameworkModule, + MatTableModule, + MatTooltipModule, + ReactiveFormsModule, + RouterModule.forChild(routes) + ], + exports: [ + RicConfigComponent + ], +}) +export class RicConfigModule { } diff --git a/webapp-frontend/src/app/services/policy/policy.service.ts b/webapp-frontend/src/app/services/policy/policy.service.ts index 879b758..95b3694 100644 --- a/webapp-frontend/src/app/services/policy/policy.service.ts +++ b/webapp-frontend/src/app/services/policy/policy.service.ts @@ -31,6 +31,8 @@ import { PolicyTypes, } from "@interfaces/policy.types"; import { Rics } from "@interfaces/ric"; +import { RicConfig } from "@app/interfaces/ric.config"; +import { HttpHeaders } from "@angular/common/http"; /** * Services for calling the policy endpoints. @@ -112,4 +114,18 @@ export class PolicyService { const url = this.buildPath("rics") + "?policytype_id=" + policyTypeId; return this.httpClient.get(url); } + + getConfiguration(): Observable { + const url = this.buildPath("configuration"); + return this.httpClient.get(url); + } + + updateConfiguration(ricConfig: RicConfig): Observable { + const httpOptions = { + headers: new HttpHeaders({'Content-Type': 'application/json'}), + observe: 'response' as 'body' + } + const url = this.buildPath("configuration"); + return this.httpClient.put(url, ricConfig, httpOptions); + } } -- 2.16.6