Add Xapp Onboarder frontend UI 55/4555/1
authorNicolas Hu <jh245g@att.com>
Mon, 17 Aug 2020 14:11:50 +0000 (10:11 -0400)
committerNicolas Hu <jh245g@att.com>
Mon, 17 Aug 2020 14:20:42 +0000 (10:20 -0400)
Signed-off-by: Jun (Nicolas) Hu <jh245g@att.com>
Issue-ID: OAM-108
Change-Id: Ic5f1d997a9c5dbc6786c6d364e92f7e5a30c938c

dashboard/webapp-frontend/src/app/catalog/catalog.component.html
dashboard/webapp-frontend/src/app/catalog/catalog.component.ts
dashboard/webapp-frontend/src/app/onboard/onboard.component.html [new file with mode: 0644]
dashboard/webapp-frontend/src/app/onboard/onboard.component.scss [new file with mode: 0644]
dashboard/webapp-frontend/src/app/onboard/onboard.component.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/onboard/onboard.component.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/rd.module.ts
dashboard/webapp-frontend/src/app/services/xapp-onboarder/xapp-onboarder.service.spec.ts [new file with mode: 0644]
dashboard/webapp-frontend/src/app/services/xapp-onboarder/xapp-onboarder.service.ts [new file with mode: 0644]
docs/release-notes.rst

index 78d525c..b54d5db 100644 (file)
   limitations under the License.
   ========================LICENSE_END===================================
   -->
-<div class="catalog__section">
-  <h3 class="rd-global-page-title">xApp Catalog</h3>
-
-  <table mat-table [dataSource]="dataSource" matSort class="catalog-table mat-elevation-z8">
-
-    <ng-container matColumnDef="name">
-      <mat-header-cell *matHeaderCellDef mat-sort-header> App Name </mat-header-cell>
-      <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
-    </ng-container>
-
-    <ng-container matColumnDef="version">
-      <mat-header-cell *matHeaderCellDef mat-sort-header> Version </mat-header-cell>
-      <mat-cell *matCellDef="let element"> {{element.version}} </mat-cell>
-    </ng-container>
-
-    <ng-container matColumnDef="action">
-      <mat-header-cell *matHeaderCellDef> Action </mat-header-cell>
-      <mat-cell *matCellDef="let element">
-        <div class="catalog-button-row">
-          <button mat-icon-button (click)="onConfigureApp(element)">
-            <mat-icon matTooltip="Adjust settings">settings</mat-icon>
-          </button>
-          <button mat-icon-button (click)="onDeployApp(element)">
-            <mat-icon matTooltip="Deploy app">cloud_upload</mat-icon>
-          </button>
-        </div>
-      </mat-cell>
-    </ng-container>
-
-    <ng-container matColumnDef="noRecordsFound">
-      <mat-footer-cell *matFooterCellDef>No records found.</mat-footer-cell>
-    </ng-container>
-
-    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
-    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
-    <mat-footer-row *matFooterRowDef="['noRecordsFound']" [ngClass]="{'display-none': dataSource.rowCount > 0}"></mat-footer-row>
-
-  </table>
-
-  <div class="spinner-container" *ngIf="dataSource.loading$ | async">
-    <mat-spinner diameter=50></mat-spinner>
-  </div>
+  <div class="catalog__section">
+    <h3 class="rd-global-page-title">xApp Catalog</h3>
+
+    <button mat-flat-button color="primary" class="right-button" (click)="onboard()">Onboard Xapp</button>
+
+    <table mat-table [dataSource]="dataSource" matSort class="catalog-table mat-elevation-z8">
+
+      <ng-container matColumnDef="name">
+        <mat-header-cell *matHeaderCellDef mat-sort-header> App Name </mat-header-cell>
+        <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
+      </ng-container>
+
+      <ng-container matColumnDef="version">
+        <mat-header-cell *matHeaderCellDef mat-sort-header> Version </mat-header-cell>
+        <mat-cell *matCellDef="let element"> {{element.version}} </mat-cell>
+      </ng-container>
+
+      <ng-container matColumnDef="action">
+        <mat-header-cell *matHeaderCellDef> Action </mat-header-cell>
+        <mat-cell *matCellDef="let element">
+          <div class="catalog-button-row">
+            <button mat-icon-button (click)="onConfigureApp(element)">
+              <mat-icon matTooltip="Adjust settings">settings</mat-icon>
+            </button>
+            <button mat-icon-button (click)="onDeployApp(element)">
+              <mat-icon matTooltip="Deploy app">cloud_upload</mat-icon>
+            </button>
+          </div>
+        </mat-cell>
+      </ng-container>
 
-</div>
+      <ng-container matColumnDef="noRecordsFound">
+        <mat-footer-cell *matFooterCellDef>No records found.</mat-footer-cell>
+      </ng-container>
+
+      <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
+      <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
+      <mat-footer-row *matFooterRowDef="['noRecordsFound']" [ngClass]="{'display-none': dataSource.rowCount > 0}"></mat-footer-row>
+
+    </table>
+
+    <div class="spinner-container" *ngIf="dataSource.loading$ | async">
+      <mat-spinner diameter=50></mat-spinner>
+    </div>
+
+  </div>
index 4ae7b89..ac8e6ec 100644 (file)
@@ -31,6 +31,7 @@ import { LoadingDialogService } from '../services/ui/loading-dialog.service';
 import { UiService } from '../services/ui/ui.service';
 import { AppConfigurationComponent } from './../app-configuration/app-configuration.component';
 import { ConfirmDialogService } from '../services/ui/confirm-dialog.service';
+import { OnboardComponent } from './../onboard/onboard.component';
 import { NotificationService } from '../services/ui/notification.service';
 import { CatalogDataSource } from './catalog.datasource';
 
@@ -125,4 +126,24 @@ export class CatalogComponent implements OnInit, OnDestroy {
       );
   }
 
+  onboard(): void {
+    if (this.darkMode) {
+      this.panelClass = 'dark-theme';
+    } else {
+      this.panelClass = '';
+    }
+    const dialogRef = this.dialog.open(OnboardComponent, {
+      panelClass: this.panelClass,
+      width: '400px',
+      maxHeight: '1000px',
+      position: {
+        top: '10%'
+      },
+      data: {
+        instanceKey: this.instanceKey
+      }
+
+    });
+  }
+
 }
diff --git a/dashboard/webapp-frontend/src/app/onboard/onboard.component.html b/dashboard/webapp-frontend/src/app/onboard/onboard.component.html
new file mode 100644 (file)
index 0000000..478cbb2
--- /dev/null
@@ -0,0 +1,60 @@
+<!--
+  ========================LICENSE_START=================================
+  O-RAN-SC
+  %%
+  Copyright (C) 2020 AT&T Intellectual Property
+  %%
+  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===================================
+  -->
+
+
+
+<mat-tab-group>
+  <mat-tab label="From Local">
+    <input #configInput type="file" (change)="selectConfigFile($event)" style="display:none;" ngModel />
+    <mat-form-field class="example-full-width" (click)="configInput.click()">
+      <input matInput placeholder="Choose config file" value="{{configInput.value.substr(configInput.value.lastIndexOf('\\')+1)}}">
+      <mat-icon matSuffix>folder_open</mat-icon>
+    </mat-form-field>
+    <input #schemaInput type="file" (change)="selectControlsSchema($event)" style="display:none;" ngModel />
+    <mat-form-field class="example-full-width" (click)="schemaInput.click()">
+      <input matInput placeholder="Choose control schema" value="{{schemaInput.value.substr(schemaInput.value.lastIndexOf('\\')+1)}}">
+      <mat-icon matSuffix>folder_open</mat-icon>
+    </mat-form-field>
+    <div class="modal-footer justify-content-center">
+      <button mat-button class="mat-raised-button  mat-primary" (click)="uploadFromLocal()">upload</button>
+      <button mat-button class="mat-raised-button" [mat-dialog-close]="false">Cancel</button>
+    </div>
+
+  </mat-tab>
+
+  <mat-tab label="From URL">
+    <form [formGroup]="urlOnboardForm">
+      <mat-form-field >
+        <mat-label>config file URL</mat-label>
+        <input matInput formControlName="configURL">
+      </mat-form-field>
+      <mat-form-field >
+        <mat-label>control schema URL</mat-label>
+        <input matInput formControlName="schemaURL">
+      </mat-form-field>
+      <div class="modal-footer justify-content-center">
+        <button mat-button class="mat-raised-button  mat-primary"  [disabled]="!urlOnboardForm.valid" (click)="uploadFromURL(urlOnboardForm.value)" >upload</button>
+        <button mat-button class="mat-raised-button" [mat-dialog-close]="false">Cancel</button>
+      </div>
+    </form>
+  </mat-tab>
+
+</mat-tab-group>
+
diff --git a/dashboard/webapp-frontend/src/app/onboard/onboard.component.scss b/dashboard/webapp-frontend/src/app/onboard/onboard.component.scss
new file mode 100644 (file)
index 0000000..4c56688
--- /dev/null
@@ -0,0 +1,23 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2020 AT&T Intellectual Property
+ * %%
+ * 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===================================
+ */
+
+.mat-raised-button {
+  margin-right: 5px;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/onboard/onboard.component.spec.ts b/dashboard/webapp-frontend/src/app/onboard/onboard.component.spec.ts
new file mode 100644 (file)
index 0000000..dd62cda
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2020 AT&T Intellectual Property
+ * %%
+ * 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { OnboardComponent } from './onboard.component';
+
+describe('OnboardComponent', () => {
+  let component: OnboardComponent;
+  let fixture: ComponentFixture<OnboardComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ OnboardComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(OnboardComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/onboard/onboard.component.ts b/dashboard/webapp-frontend/src/app/onboard/onboard.component.ts
new file mode 100644 (file)
index 0000000..7f609a6
--- /dev/null
@@ -0,0 +1,137 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2020 AT&T Intellectual Property
+ * %%
+ * 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, Inject, OnInit } from '@angular/core';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { finalize } from 'rxjs/operators';
+import { XappOnboarderService } from '../services/xapp-onboarder/xapp-onboarder.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { LoadingDialogService } from '../services/ui/loading-dialog.service';
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+import { NotificationService } from '../services/ui/notification.service';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+
+@Component({
+  selector: 'rd-onboard',
+  templateUrl: './onboard.component.html',
+  styleUrls: ['./onboard.component.scss']
+})
+export class OnboardComponent implements OnInit {
+
+  private loadingSubject = new BehaviorSubject<boolean>(false);
+  public loading$ = this.loadingSubject.asObservable();
+  public urlOnboardForm: FormGroup;
+
+  constructor(
+    private dialogRef: MatDialogRef<OnboardComponent>,
+    private xappOnboarderService: XappOnboarderService,
+    private errorDiaglogService: ErrorDialogService,
+    private loadingDialogService: LoadingDialogService,
+    private notificationService: NotificationService,
+    @Inject(MAT_DIALOG_DATA) private data
+  ) { }
+
+  ngOnInit(): void {
+    this.urlOnboardForm = new FormGroup({
+      configURL: new FormControl('', [Validators.required]),
+      schemaURL: new FormControl('', [Validators.required])
+    })
+  }
+  ;
+  configFile: File;
+  controlsSchema: File;
+  descriptor = {
+    "config-file.json": {},
+    "controls-schema.json": {}
+  }
+  descriptor_url = {
+    "config-file.json_url": "",
+    "controls-schema.json_url": ""
+  }
+
+  uploadFromLocal() {
+    this.loadingDialogService.startLoading('Onboarding xApp');
+    this.xappOnboarderService.onboardXappFile(this.descriptor, this.data.instanceKey)
+      .pipe(
+        finalize(() => {
+          this.loadingDialogService.stopLoading();
+          this.dialogRef.close();
+        })
+      )
+      .subscribe(
+        (response: HttpResponse<Object>) => {
+          this.notificationService.success('Onboard succeeded!');
+        },
+        ((her: HttpErrorResponse) => {
+          let msg = her.message;
+          if (her.error && her.error.message) {
+            msg = her.error.message;
+          }
+          this.notificationService.warn('Onboard failed: ' + msg);
+        })
+      );
+  }
+
+  uploadFromURL(data) {
+    this.descriptor_url["config-file.json_url"] = data.configURL;
+    this.descriptor_url["controls-schema.json_url"] = data.schemaURL;
+    this.loadingDialogService.startLoading('Onboarding xApp');
+    this.xappOnboarderService.onboardXappURL(this.descriptor_url, this.data.instanceKey)
+      .pipe(
+        finalize(() => {
+          this.loadingDialogService.stopLoading();
+          this.dialogRef.close();
+        })
+      )
+      .subscribe(
+        (response: HttpResponse<Object>) => {
+          this.notificationService.success('Onboard succeeded!');
+        },
+        ((her: HttpErrorResponse) => {
+          let msg = her.message;
+          if (her.error && her.error.message) {
+            msg = her.error.message;
+          }
+          this.notificationService.warn('Onboard failed: ' + msg);
+        })
+      );
+  }
+
+
+  selectConfigFile(event) {
+    this.configFile = event.target.files[0];
+    let fileReader = new FileReader();
+    fileReader.onload = (e) => {
+      this.descriptor["config-file.json"] = JSON.parse(fileReader.result as string);
+    }
+    fileReader.readAsText(this.configFile);
+  }
+
+  selectControlsSchema(event) {
+    this.controlsSchema = event.target.files[0];
+    let fileReader = new FileReader();
+    fileReader.onload = (e) => {
+      this.descriptor["controls-schema.json"] = JSON.parse(fileReader.result as string);
+    }
+    fileReader.readAsText(this.controlsSchema);
+
+  }
+}
index c91ce3e..9de3bad 100644 (file)
@@ -67,6 +67,7 @@ import { EditDashboardUserDialogComponent } from './user/edit-dashboard-user-dia
 import { ErrorDialogComponent } from './ui/error-dialog/error-dialog.component';
 import { FooterComponent } from './footer/footer.component';
 import { InstanceSelectorDialogComponent } from './ui/instance-selector-dialog/instance-selector-dialog.component';
+import { OnboardComponent } from './onboard/onboard.component';
 import { LoadingDialogComponent } from './ui/loading-dialog/loading-dialog.component';
 import { MainComponent } from './main/main.component';
 import { PlatformComponent } from './platform/platform.component';
@@ -87,6 +88,7 @@ import { ErrorDialogService } from './services/ui/error-dialog.service';
 import { InstanceSelectorService } from './services/instance-selector/instance-selector.service';
 import { InstanceSelectorDialogService } from './services/ui/instance-selector-dialog.service';
 import { UiService } from './services/ui/ui.service';
+import { XappOnboarderService } from './services/xapp-onboarder/xapp-onboarder.service';
 
 
 @NgModule({
@@ -113,7 +115,8 @@ import { UiService } from './services/ui/ui.service';
     StatsComponent,
     StatsDialogComponent,
     UserComponent,
-    InstanceSelectorDialogComponent
+    InstanceSelectorDialogComponent,
+    OnboardComponent
   ],
   imports: [
     BrowserModule,
@@ -186,7 +189,8 @@ import { UiService } from './services/ui/ui.service';
     ErrorDialogService,
     InstanceSelectorService,
     InstanceSelectorDialogService,
-    UiService
+    UiService,
+    XappOnboarderService
   ],
   bootstrap: [RdComponent]
 })
diff --git a/dashboard/webapp-frontend/src/app/services/xapp-onboarder/xapp-onboarder.service.spec.ts b/dashboard/webapp-frontend/src/app/services/xapp-onboarder/xapp-onboarder.service.spec.ts
new file mode 100644 (file)
index 0000000..80cec3a
--- /dev/null
@@ -0,0 +1,35 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * 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 { TestBed } from '@angular/core/testing';
+
+import { XappOnboarderService } from './xapp-onboarder.service';
+
+describe('XappOnboarderService', () => {
+  let service: XappOnboarderService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(XappOnboarderService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/xapp-onboarder/xapp-onboarder.service.ts b/dashboard/webapp-frontend/src/app/services/xapp-onboarder/xapp-onboarder.service.ts
new file mode 100644 (file)
index 0000000..03d4571
--- /dev/null
@@ -0,0 +1,46 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * 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 { HttpClient, HttpResponse } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { DashboardService } from '../dashboard/dashboard.service';
+@Injectable({
+  providedIn: 'root'
+})
+export class XappOnboarderService {
+
+  private component = 'xappobrd';
+
+  constructor(
+    private dashboardSvc: DashboardService,
+    private httpClient: HttpClient
+  ) { }
+
+  onboardXappFile(descriptor: any, instanceKey: string): Observable<HttpResponse<Object>> {
+    const path = this.dashboardSvc.buildPath(this.component, instanceKey, 'onboard');
+    return this.httpClient.post(path, descriptor, { observe: 'response' });
+  }
+
+  onboardXappURL(descriptor_remote: any, instanceKey: string): Observable<HttpResponse<Object>> {
+    const path = this.dashboardSvc.buildPath(this.component, instanceKey, 'onboard','download');
+    return this.httpClient.post(path, descriptor_remote, { observe: 'response' });
+  }
+}
index 200ac4d..cfbae03 100644 (file)
@@ -5,9 +5,10 @@
 RIC Dashboard Release Notes
 ===========================
 
-Version 2.1.0, 8 Jun 2020
+Version 2.1.0, 17 Aug 2020
 --------------------------
 * Add Xapp Onboarder client to backend (`OAM-108 <https://jira.o-ran-sc.org/browse/OAM-108>`_)
+* Add Xapp Onboarder frontend UI (`OAM-108 <https://jira.o-ran-sc.org/browse/OAM-108>`_)
 
 Version 2.0.3, 3 Jun 2020
 --------------------------