RIC Configuration from UI 66/5966/2
authorLathish <lathishbabu.ganesan@est.tech>
Fri, 23 Apr 2021 14:46:17 +0000 (15:46 +0100)
committerLathish <lathishbabu.ganesan@est.tech>
Thu, 29 Apr 2021 15:10:11 +0000 (16:10 +0100)
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 <lathishbabu.ganesan@est.tech>
12 files changed:
webapp-frontend/src/app/app-routing.module.ts
webapp-frontend/src/app/app.module.ts
webapp-frontend/src/app/interceptor.mock.ts
webapp-frontend/src/app/interfaces/ric.config.ts [new file with mode: 0644]
webapp-frontend/src/app/mock/ric-configuration.json [new file with mode: 0644]
webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts
webapp-frontend/src/app/ric-config/ric-config.component.html [new file with mode: 0644]
webapp-frontend/src/app/ric-config/ric-config.component.scss [new file with mode: 0644]
webapp-frontend/src/app/ric-config/ric-config.component.ts [new file with mode: 0644]
webapp-frontend/src/app/ric-config/ricconfig.module.ts [new file with mode: 0644]
webapp-frontend/src/app/services/policy/policy.service.ts

index 387fdc1..9798f51 100644 (file)
@@ -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 }
 ];
 
index adca33a..7646d02 100644 (file)
@@ -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: [
index 7163412..b1b81eb 100644 (file)
@@ -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 (file)
index 0000000..ba1ad2f
--- /dev/null
@@ -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 (file)
index 0000000..0183e2e
--- /dev/null
@@ -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
index d110619..8a34a8f 100644 (file)
   <a mat-list-item routerLink="/" (click)="onSidenavClose()">
     <mat-icon>home</mat-icon> <span class="nav-caption">Home</span>
   </a>
-  <a mat-list-item routerLink="/policy" (click)="onSidenavClose()">
-      <mat-icon>assignment</mat-icon> <span class="nav-caption">Policy</span>
+  <a mat-list-item (click)="toggle()">
+      <mat-icon>dashboard</mat-icon> <span class="nav-caption">Policy</span>
+      <mat-icon *ngIf="showSubMenu">expand_more</mat-icon>
+      <mat-icon *ngIf="!showSubMenu">chevron_right</mat-icon>
   </a>
+  <mat-nav-list [ngClass]="{'dark': darkMode}" *ngIf="showSubMenu">
+    <a mat-list-item routerLink="/policy" (click)="onSidenavClose()">
+      <mat-icon>dashboard</mat-icon> <span class="nav-caption">Policy Types</span>
+    </a>
+    <a mat-list-item routerLink="/ric-config" (click)="onSidenavClose()">
+      <mat-icon>build</mat-icon> <span class="nav-caption">RIC Config</span>
+    </a>
+  </mat-nav-list>
   <a mat-list-item routerLink="/ei-coordinator" (click)="onSidenavClose()">
-    <mat-icon>assignment</mat-icon> <span class="nav-caption">Enrichment Information Coordinator</span>
+    <mat-icon>dashboard</mat-icon> <span class="nav-caption">Enrichment Information Coordinator</span>
 </a>
 </mat-nav-list>
index d44da90..06be8f2 100644 (file)
@@ -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 (file)
index 0000000..8ea8082
--- /dev/null
@@ -0,0 +1,53 @@
+<!--
+  ========================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===================================
+  -->
+  <div fxLayout="row">
+    <div class="nrcp-global-page-title">RIC Configuration</div>
+    <button #refreshButton mat-icon-button color="primary" (click)="getConfig()">
+      <mat-icon>refresh</mat-icon>
+  </button>
+</div>
+<form [formGroup]="ricConfigForm">
+    <mat-form-field style="width: 800px;" appearance="fill">
+        <textarea style= "resize:none;" id="ricConfigInfo" formControlName="ricConfigInfo" matInput cdkTextareaAutosize
+            cdkAutosizeMinRows="10" cdkAutosizeMaxRows="20" placeholder="Ric Configuration"
+            matTooltip="Ric Configuration" matTooltipPosition="before">
+    </textarea>
+        <div *ngIf="ricConfigInfo.invalid && (ricConfigInfo.dirty || ricConfigInfo.touched)">
+            <div *ngIf="ricConfigInfo.errors.required">
+                <mat-error role="alert">
+                    This field is required.
+                </mat-error>
+            </div>
+            <div *ngIf="ricConfigInfo.errors.invalidJson">
+                <mat-error role="alert">
+                    Should be a valid JSON.
+                </mat-error>
+            </div>
+        </div>
+        <button id="formatButton" (click)="formatJsonInput();" mat-raised-button>
+            Format JSON
+        </button>
+    </mat-form-field>
+</form>
+<div>
+<button style="margin-right:20px" id="update" (click)="updateRicConfig();" mat-raised-button [disabled]="!ricConfigInfo.valid">
+  Update
+</button>
+</div>
\ 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 (file)
index 0000000..421d33d
--- /dev/null
@@ -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 (file)
index 0000000..cff6bd9
--- /dev/null
@@ -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<string> = new EventEmitter<string>();
+  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<Object>) => {
+      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 (file)
index 0000000..712356b
--- /dev/null
@@ -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 { }
index 879b758..95b3694 100644 (file)
@@ -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<any>(url);
   }
+
+  getConfiguration(): Observable<RicConfig> {
+    const url = this.buildPath("configuration");
+    return this.httpClient.get<RicConfig>(url);
+  }
+
+  updateConfiguration(ricConfig: RicConfig): Observable<RicConfig> {
+    const httpOptions = {
+      headers: new HttpHeaders({'Content-Type': 'application/json'}),
+      observe: 'response' as 'body'
+    }
+    const url = this.buildPath("configuration");
+    return this.httpClient.put<RicConfig>(url, ricConfig, httpOptions);
+  }
 }