Non-RT RIC Dashboard
First commit
Change-Id: I9e140d31d65d13df3ce07f6b87eac250ee952eab
Issue-ID: NONRTRIC-61
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.animations.ts b/dashboard/webapp-frontend/src/app/app-control/app-control.animations.ts
new file mode 100644
index 0000000..c00f877
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/app-control/app-control.animations.ts
@@ -0,0 +1,28 @@
+/*-
+ * ========================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 { animate, state, style, transition, trigger } from '@angular/animations';
+
+export const AppControlAnimations = {
+ messageTrigger: trigger('messageExpand', [
+ state('collapsed', style({ height: '0px', minHeight: '0', display: 'none' })),
+ state('expanded', style({ height: '*' })),
+ ])
+}
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.component.html b/dashboard/webapp-frontend/src/app/app-control/app-control.component.html
new file mode 100644
index 0000000..ec802f4
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/app-control/app-control.component.html
@@ -0,0 +1,100 @@
+<!--
+ ========================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===================================
+ -->
+
+<div class="app-control__section">
+ <h3 class="rd-global-page-title">xApp Control</h3>
+
+ <table mat-table [dataSource]="dataSource" matSort multiTemplateDataRows class="app-control-table mat-elevation-z8">
+
+ <ng-container matColumnDef="xapp">
+ <mat-header-cell *matHeaderCellDef mat-sort-header> App Name </mat-header-cell>
+ <mat-cell *matCellDef="let element"> {{element.xapp}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="name">
+ <mat-header-cell *matHeaderCellDef mat-sort-header> Instance Name</mat-header-cell>
+ <mat-cell *matCellDef="let element"> {{element.instance.name}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="status">
+ <mat-header-cell *matHeaderCellDef mat-sort-header> Status </mat-header-cell>
+ <mat-cell *matCellDef="let element"> {{element.instance.status}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="ip" >
+ <mat-header-cell *matHeaderCellDef mat-sort-header> IP </mat-header-cell>
+ <mat-cell *matCellDef="let element"> {{element.instance.ip}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="port">
+ <mat-header-cell *matHeaderCellDef mat-sort-header> Port </mat-header-cell>
+ <mat-cell *matCellDef="let element"> {{element.instance.port}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="action">
+ <mat-header-cell *matHeaderCellDef> Action </mat-header-cell>
+ <!-- click on button should not expand/collapse the row -->
+ <mat-cell *matCellDef="let element" (click)="$event.stopPropagation()">
+ <button mat-icon-button (click)="controlApp(element)">
+ <mat-icon matTooltip="Adjust settings">settings</mat-icon>
+ </button>
+ <button mat-icon-button (click)="onUndeployApp(element)">
+ <mat-icon matTooltip="Undeploy app">cloud_download</mat-icon>
+ </button>
+ </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="expandedDetail">
+ <td mat-cell *matCellDef="let element" [attr.colspan]="displayedColumns.length">
+ <div [@messageExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
+ <div>
+ txMessages:
+ </div>
+ <li *ngFor="let rxmessage of element.instance.rxMessages">
+ <span>{{rxmessage}}</span>
+ </li>
+ <div>
+ rxMessages:
+ </div>
+ <li *ngFor="let txmessage of element.instance.txMessages">
+ <span>{{txmessage}}</span>
+ </li>
+ </div>
+ </td>
+ </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 element; columns: displayedColumns;"
+ [class.example-expanded-row]="expandedElement === element"
+ (click)="expandedElement = expandedElement === element ? null : element"></mat-row>
+ <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="message-row"></tr>
+ <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>
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.component.scss b/dashboard/webapp-frontend/src/app/app-control/app-control.component.scss
new file mode 100644
index 0000000..232562f
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/app-control/app-control.component.scss
@@ -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===================================
+ */
+ .app-control__section {
+}
+
+
+.spinner-container {
+ height: 100px;
+ width: 100px;
+}
+
+.spinner-container mat-spinner {
+ margin: 0 auto 0 auto;
+}
+
+.app-control-table {
+ width: 100%;
+ min-height: 150px;
+ margin-top: 10px;
+ background-color: transparent;
+}
+
+tr.message-row {
+ height: 0;
+}
+
+.display-none {
+ display: none;
+}
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.component.spec.ts b/dashboard/webapp-frontend/src/app/app-control/app-control.component.spec.ts
new file mode 100644
index 0000000..6648d81
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/app-control/app-control.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AppControlComponent } from './app-control.component';
+
+describe('AppControlComponent', () => {
+ let component: AppControlComponent;
+ let fixture: ComponentFixture<AppControlComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ AppControlComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AppControlComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.component.ts b/dashboard/webapp-frontend/src/app/app-control/app-control.component.ts
new file mode 100644
index 0000000..07b931d
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/app-control/app-control.component.ts
@@ -0,0 +1,103 @@
+/*-
+ * ========================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 { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { MatSort } from '@angular/material/sort';
+import { Router } from '@angular/router';
+import { XappControlRow } from '../interfaces/app-mgr.types';
+import { AppMgrService } from '../services/app-mgr/app-mgr.service';
+import { ConfirmDialogService } from '../services/ui/confirm-dialog.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { LoadingDialogService } from '../services/ui/loading-dialog.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { AppControlAnimations } from './app-control.animations';
+import { AppControlDataSource } from './app-control.datasource';
+import { finalize } from 'rxjs/operators';
+
+@Component({
+ selector: 'rd-app-control',
+ templateUrl: './app-control.component.html',
+ styleUrls: ['./app-control.component.scss'],
+ animations: [AppControlAnimations.messageTrigger]
+})
+export class AppControlComponent implements OnInit {
+
+ displayedColumns: string[] = ['xapp', 'name', 'status', 'ip', 'port', 'action'];
+ dataSource: AppControlDataSource;
+ @ViewChild(MatSort, {static: true}) sort: MatSort;
+
+ constructor(
+ private appMgrSvc: AppMgrService,
+ private router: Router,
+ private confirmDialogService: ConfirmDialogService,
+ private errorDialogService: ErrorDialogService,
+ private loadingDialogService: LoadingDialogService,
+ private notificationService: NotificationService) { }
+
+ ngOnInit() {
+ this.dataSource = new AppControlDataSource(this.appMgrSvc, this.sort, this.notificationService);
+ this.dataSource.loadTable();
+ }
+
+ controlApp(app: XappControlRow): void {
+ // TODO: identify apps without hardcoding to names
+ const acAppPattern0 = /[Aa][Dd][Mm][Ii][Nn]/;
+ const acAppPattern1 = /[Aa][Dd][Mm][Ii][Ss]{2}[Ii][Oo][Nn]/;
+ const anrAppPattern0 = /ANR/;
+ const anrAppPattern1 = /[Aa][Uu][Tt][Oo][Mm][Aa][Tt][Ii][Cc]/;
+ const anrAppPattern2 = /[Nn][Ee][Ii][Gg][Hh][Bb][Oo][Rr]/;
+ if (acAppPattern0.test(app.xapp) || acAppPattern1.test(app.xapp)) {
+ this.router.navigate(['/ac']);
+ } else if (anrAppPattern0.test(app.xapp) || (anrAppPattern1.test(app.xapp) && anrAppPattern2.test(app.xapp))) {
+ this.router.navigate(['/anr']);
+ } else {
+ this.errorDialogService.displayError('No control available for ' + app.xapp + ' (yet)');
+ }
+ }
+
+ onUndeployApp(app: XappControlRow): void {
+ this.confirmDialogService.openConfirmDialog('Are you sure you want to undeploy App ' + app.xapp + '?')
+ .afterClosed().subscribe( (res: boolean) => {
+ if (res) {
+ this.loadingDialogService.startLoading("Undeploying " + app.xapp);
+ this.appMgrSvc.undeployXapp(app.xapp)
+ .pipe(
+ finalize(() => this.loadingDialogService.stopLoading())
+ )
+ .subscribe(
+ ( httpResponse: HttpResponse<Object>) => {
+ // Answers 204/No content on success
+ this.notificationService.success('App undeployed successfully!');
+ this.dataSource.loadTable();
+ },
+ ( (her: HttpErrorResponse) => {
+ // the error field should have an ErrorTransport object
+ let msg = her.message;
+ if (her.error && her.error.message) {
+ msg = her.error.message;
+ }
+ this.notificationService.warn('App undeploy failed: ' + msg);
+ })
+ );
+ }
+ });
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/app-control/app-control.datasource.ts b/dashboard/webapp-frontend/src/app/app-control/app-control.datasource.ts
new file mode 100644
index 0000000..e97dc63
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/app-control/app-control.datasource.ts
@@ -0,0 +1,134 @@
+/*-
+ * ========================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 { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material/sort';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { XappControlRow, XMDeployedApp, XMXappInstance } from '../interfaces/app-mgr.types';
+import { AppMgrService } from '../services/app-mgr/app-mgr.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class AppControlDataSource extends DataSource<XappControlRow> {
+
+ private appControlSubject = new BehaviorSubject<XappControlRow[]>([]);
+
+ private loadingSubject = new BehaviorSubject<boolean>(false);
+
+ public loading$ = this.loadingSubject.asObservable();
+
+ public rowCount = 1; // hide footer during intial load
+
+ private emptyInstances: XMXappInstance =
+ { ip: null,
+ name: null,
+ port: null,
+ status: null,
+ rxMessages: [],
+ txMessages: [],
+ };
+
+ constructor(private appMgrSvc: AppMgrService,
+ private sort: MatSort,
+ private notificationService: NotificationService) {
+ super();
+ }
+
+ loadTable() {
+ this.loadingSubject.next(true);
+ this.appMgrSvc.getDeployed()
+ .pipe(
+ catchError( (her: HttpErrorResponse) => {
+ console.log('AppControlDataSource failed: ' + her.message);
+ this.notificationService.error('Failed to get applications: ' + her.message);
+ return of([]);
+ }),
+ finalize(() => this.loadingSubject.next(false))
+ )
+ .subscribe( (xApps: XMDeployedApp[]) => {
+ this.rowCount = xApps.length;
+ const flattenedApps = this.flatten(xApps);
+ this.appControlSubject.next(flattenedApps);
+ });
+ }
+
+ connect(collectionViewer: CollectionViewer): Observable<XappControlRow[]> {
+ const dataMutations = [
+ this.appControlSubject.asObservable(),
+ this.sort.sortChange
+ ];
+ return merge(...dataMutations).pipe(map(() => {
+ return this.getSortedData([...this.appControlSubject.getValue()]);
+ }));
+ }
+
+ disconnect(collectionViewer: CollectionViewer): void {
+ this.appControlSubject.complete();
+ this.loadingSubject.complete();
+ }
+
+ private flatten(allxappdata: XMDeployedApp[]): XappControlRow[] {
+ const xAppInstances: XappControlRow[] = [];
+ for (const xapp of allxappdata) {
+ if (!xapp.instances) {
+ const row: XappControlRow = {
+ xapp: xapp.name,
+ instance: this.emptyInstances
+ };
+ xAppInstances.push(row);
+ } else {
+ for (const ins of xapp.instances) {
+ const row: XappControlRow = {
+ xapp: xapp.name,
+ instance: ins
+ };
+ xAppInstances.push(row);
+ }
+ }
+ }
+ return xAppInstances;
+ }
+
+ private getSortedData(data: XappControlRow[]) {
+ if (!this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a, b) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'xapp': return compare(a.xapp, b.xapp, isAsc);
+ case 'name': return compare(a.instance.name, b.instance.name, isAsc);
+ case 'status': return compare(a.instance.status, b.instance.status, isAsc);
+ case 'ip': return compare(a.instance.ip, b.instance.ip, isAsc);
+ case 'port': return compare(a.instance.port, b.instance.port, isAsc);
+ default: return 0;
+ }
+ });
+ }
+}
+
+function compare(a: any, b: any, isAsc: boolean) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/dashboard/webapp-frontend/src/app/control/control.component.html b/dashboard/webapp-frontend/src/app/control/control.component.html
new file mode 100644
index 0000000..e3258b6
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/control/control.component.html
@@ -0,0 +1,24 @@
+<!--
+ ========================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===================================
+ -->
+<div class="control__section">
+ <rd-ran-control></rd-ran-control>
+ <hr>
+ <rd-app-control></rd-app-control>
+</div>
diff --git a/dashboard/webapp-frontend/src/app/control/control.component.scss b/dashboard/webapp-frontend/src/app/control/control.component.scss
new file mode 100644
index 0000000..f06d0ce
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/control/control.component.scss
@@ -0,0 +1,22 @@
+/*-
+ * ========================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===================================
+ */
+.control__section {
+ background-color: transparent;
+}
diff --git a/dashboard/webapp-frontend/src/app/control/control.component.spec.ts b/dashboard/webapp-frontend/src/app/control/control.component.spec.ts
new file mode 100644
index 0000000..eb7d064
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/control/control.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ControlComponent } from './control.component';
+
+describe('ControlComponent', () => {
+ let component: ControlComponent;
+ let fixture: ComponentFixture<ControlComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ ControlComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ControlComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/control/control.component.ts b/dashboard/webapp-frontend/src/app/control/control.component.ts
new file mode 100644
index 0000000..18545af
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/control/control.component.ts
@@ -0,0 +1,34 @@
+/*-
+ * ========================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 { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'rd-control',
+ templateUrl: './control.component.html',
+ styleUrls: ['./control.component.scss']
+})
+export class ControlComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit() {
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/footer/footer.component.html b/dashboard/webapp-frontend/src/app/footer/footer.component.html
new file mode 100644
index 0000000..5dd1c36
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/footer/footer.component.html
@@ -0,0 +1,24 @@
+<!--
+ ========================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===================================
+ -->
+<div class="copyright__text" [ngClass]="{'footer-dark': darkMode}">
+ Copyright (C) 2019 AT&T Intellectual Property. Licensed under the Apache License, Version 2.0.
+ <br/>
+ Version {{dashboardVersion}}
+</div>
diff --git a/dashboard/webapp-frontend/src/app/footer/footer.component.scss b/dashboard/webapp-frontend/src/app/footer/footer.component.scss
new file mode 100644
index 0000000..e2456d7
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/footer/footer.component.scss
@@ -0,0 +1,28 @@
+/*-
+ * ========================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===================================
+ */
+.copyright__text {
+ color: gray;
+ letter-spacing: 0.1rem;
+ font-size: 12px;
+}
+
+.footer-dark {
+ color: white;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/footer/footer.component.spec.ts b/dashboard/webapp-frontend/src/app/footer/footer.component.spec.ts
new file mode 100644
index 0000000..6f80297
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/footer/footer.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FooterComponent } from './footer.component';
+
+describe('FooterComponent', () => {
+ let component: FooterComponent;
+ let fixture: ComponentFixture<FooterComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ FooterComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(FooterComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/footer/footer.component.ts b/dashboard/webapp-frontend/src/app/footer/footer.component.ts
new file mode 100644
index 0000000..e119d70
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/footer/footer.component.ts
@@ -0,0 +1,49 @@
+/*-
+ * ========================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 { Component, OnInit } from '@angular/core';
+import { DashboardSuccessTransport } from '../interfaces/dashboard.types';
+import { DashboardService } from '../services/dashboard/dashboard.service';
+import { UiService } from '../services/ui/ui.service';
+
+@Component({
+ selector: 'rd-footer',
+ templateUrl: './footer.component.html',
+ styleUrls: ['./footer.component.scss']
+})
+
+/**
+ * Fetches the version on load for display in the footer
+ */
+export class FooterComponent implements OnInit {
+ darkMode: boolean;
+ dashboardVersion: string;
+
+ // Inject the service
+ constructor(private dashboardService: DashboardService,
+ public ui: UiService ) { }
+
+ ngOnInit() {
+ this.dashboardService.getVersion().subscribe((res: DashboardSuccessTransport) => this.dashboardVersion = res.data);
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/ac-xapp.types.ts b/dashboard/webapp-frontend/src/app/interfaces/ac-xapp.types.ts
new file mode 100644
index 0000000..125f72f
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/interfaces/ac-xapp.types.ts
@@ -0,0 +1,33 @@
+/*-
+ * ========================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===================================
+ */
+
+// Models of data used by the AC xApp
+
+export interface ACAdmissionIntervalControl {
+ enforce: boolean;
+ window_length: number;
+ blocking_rate: number;
+ trigger_threshold: number;
+}
+
+export interface ACAdmissionIntervalControlAck {
+ status: string;
+ message: string;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/anr-xapp.types.ts b/dashboard/webapp-frontend/src/app/interfaces/anr-xapp.types.ts
new file mode 100644
index 0000000..6b7db25
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/interfaces/anr-xapp.types.ts
@@ -0,0 +1,47 @@
+/*-
+ * ========================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===================================
+ */
+
+// Models of data used by the ANR xApp
+
+export interface ANRGgNodeBTable {
+ gNodeBIds: Array<string>;
+}
+
+export interface ANRNeighborCellRelationTable {
+ ncrtRelations: Array<ANRNeighborCellRelation>;
+}
+
+export interface ANRNeighborCellRelation {
+ servingCellNrcgi: string;
+ neighborCellNrpci: string;
+ neighborCellNrcgi: string;
+ flagNoHo: boolean;
+ flagNoXn: boolean;
+ flagNoRemove: boolean;
+}
+
+export interface ANRNeighborCellRelationMod {
+ servingCellNrcgi: string;
+ neighborCellNrpci: string;
+ neighborCellNrcgi: string;
+ flagNoHo: boolean;
+ flagNoXn: boolean;
+ flagNoRemove: boolean;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/app-mgr.types.ts b/dashboard/webapp-frontend/src/app/interfaces/app-mgr.types.ts
new file mode 100644
index 0000000..064967c
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/interfaces/app-mgr.types.ts
@@ -0,0 +1,67 @@
+/*-
+ * ========================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===================================
+ */
+
+// Models of data used by the App Manager
+
+export interface XMSubscription {
+ eventType: string;
+ id: string;
+ maxRetries: number;
+ retryTimer: number;
+ targetUrl: string;
+}
+
+/**
+ * Name is the only required field
+ */
+export interface XMXappInfo {
+ name: string;
+ configName?: string;
+ namespace?: string;
+ serviceName?: string;
+ imageRepo?: string;
+ hostname?: string;
+}
+
+export interface XMXappInstance {
+ ip: string;
+ name: string;
+ port: number;
+ status: string;
+ rxMessages: Array<string>;
+ txMessages: Array<string>;
+}
+
+export interface XMDeployableApp {
+ name: string;
+ version: string;
+}
+
+export interface XMDeployedApp {
+ name: string;
+ status: string;
+ version: string;
+ instances: Array<XMXappInstance>;
+}
+
+export interface XappControlRow {
+ xapp: string;
+ instance: XMXappInstance;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/dashboard.types.ts b/dashboard/webapp-frontend/src/app/interfaces/dashboard.types.ts
new file mode 100644
index 0000000..90dfd15
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/interfaces/dashboard.types.ts
@@ -0,0 +1,57 @@
+/*-
+ * ========================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===================================
+ */
+
+// Models of data used by Dashboard admin services
+
+export interface DashboardSuccessTransport {
+ status: number;
+ data: string;
+}
+
+export interface EcompRoleFunction {
+ name: string;
+ code: string;
+ type: string;
+ action: string;
+}
+
+export interface EcompRole {
+ id: number;
+ name: string;
+ [position: number]: EcompRoleFunction;
+}
+
+export interface EcompUser {
+ orgId?: number;
+ managerId?: string;
+ firstName?: string;
+ middleInitial?: string;
+ lastName?: string;
+ phone?: string;
+ email?: string;
+ hrid?: string;
+ orgUserId?: string;
+ orgCode?: string;
+ orgManagerUserId?: string;
+ jobTitle?: string;
+ loginId: string;
+ active: boolean;
+ [position: number]: EcompRole;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/e2-mgr.types.ts b/dashboard/webapp-frontend/src/app/interfaces/e2-mgr.types.ts
new file mode 100644
index 0000000..f303c14
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/interfaces/e2-mgr.types.ts
@@ -0,0 +1,66 @@
+/*-
+ * ========================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===================================
+ */
+
+// Models of data used by the E2 Manager
+
+export interface E2SetupRequest {
+ ranName: string;
+ ranIp: string;
+ ranPort: string;
+}
+
+export interface E2ErrorResponse {
+ errorCode: string;
+ errorMessage: string;
+}
+
+export interface E2NodebIdentityGlobalNbId {
+ nbId: string;
+ plmnId: string;
+}
+
+export interface E2NodebIdentity {
+ inventoryName: string;
+ globalNbId: E2NodebIdentityGlobalNbId;
+}
+
+export interface E2GetNodebResponse {
+ connectionStatus: string; // actually one-of, but model as string
+ enb: object; // don't model this until needed
+ failureType: string; // actually one-of, but model as string
+ gnb: object; // don't model this until needed
+ ip: string;
+ nodeType: string; // actually one-of, but model as string
+ port: number; // actually integer
+ ranName: string;
+ setupFailure: object; // don't model this until needed
+}
+
+export interface E2RanDetails {
+ nodebIdentity: E2NodebIdentity;
+ nodebStatus: E2GetNodebResponse;
+}
+
+export interface RanDialogFormData {
+ ranIp: string;
+ ranName: string;
+ ranPort: string;
+ ranType: string;
+}
diff --git a/dashboard/webapp-frontend/src/app/interfaces/policy.types.ts b/dashboard/webapp-frontend/src/app/interfaces/policy.types.ts
new file mode 100644
index 0000000..bc94af9
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/interfaces/policy.types.ts
@@ -0,0 +1,38 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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===================================
+ */
+
+// Models of data used by the Policy Control
+
+export interface PolicyType {
+ policy_type_id: number;
+ name: string;
+ description: string;
+ create_schema: string;
+}
+
+export interface PolicyInstance {
+ instanceId: string;
+ instance: string;
+}
+
+export interface PolicyInstanceAck {
+ status: string;
+ message: string;
+}
diff --git a/dashboard/webapp-frontend/src/app/main/main.component.html b/dashboard/webapp-frontend/src/app/main/main.component.html
new file mode 100644
index 0000000..f05fd50
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/main/main.component.html
@@ -0,0 +1,23 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2019 AT&T Intellectual Property
+ Modifications Copyright (C) 2019 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 class = "main__container">
+ <rd-policy-card></rd-policy-card>
+</div>
diff --git a/dashboard/webapp-frontend/src/app/main/main.component.scss b/dashboard/webapp-frontend/src/app/main/main.component.scss
new file mode 100644
index 0000000..aeb3b5d
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/main/main.component.scss
@@ -0,0 +1,27 @@
+/*-
+ * ========================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===================================
+ */
+.main__container {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ grid-template-rows: repeat(auto-fill, 1fr);
+ align-items: center;
+ justify-items: center;
+ height: 100%;
+}
diff --git a/dashboard/webapp-frontend/src/app/main/main.component.spec.ts b/dashboard/webapp-frontend/src/app/main/main.component.spec.ts
new file mode 100644
index 0000000..47f86b4
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/main/main.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MainComponent } from './main.component';
+
+describe('MainComponent', () => {
+ let component: MainComponent;
+ let fixture: ComponentFixture<MainComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ MainComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MainComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/main/main.component.ts b/dashboard/webapp-frontend/src/app/main/main.component.ts
new file mode 100644
index 0000000..8967a42
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/main/main.component.ts
@@ -0,0 +1,33 @@
+/*-
+ * ========================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 { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'rd-main',
+ templateUrl: './main.component.html',
+ styleUrls: ['./main.component.scss']
+})
+export class MainComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit() { }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html b/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
new file mode 100644
index 0000000..d91c4a9
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
@@ -0,0 +1,33 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2019 AT&T Intellectual Property
+ Modifications Copyright (C) 2019 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===================================
+ -->
+
+<!-- browse icons at https://material.io/tools/icons/?style=baseline -->
+<mat-nav-list [ngClass]="{'dark': darkMode}">
+ <a mat-list-item routerLink="/" (click)="onSidenavClose()">
+ <mat-icon>home</mat-icon> <span class="nav-caption">Home</span>
+ </a>
+ <a mat-list-item routerLink="/control" (click)="onSidenavClose()">
+ <mat-icon>settings</mat-icon><span class="nav-caption">Control</span>
+ </a>
+ <a mat-list-item routerLink="/policy" (click)="onSidenavClose()">
+ <mat-icon>assignment</mat-icon> <span class="nav-caption">Policy</span>
+ </a>
+</mat-nav-list>
diff --git a/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.scss b/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.scss
new file mode 100644
index 0000000..72c724e
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.scss
@@ -0,0 +1,36 @@
+/*-
+ * ========================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===================================
+ */
+a {
+ text-decoration: none;
+ color: black;
+}
+
+.dark a {
+ color: white;
+}
+
+a:hover, a:active{
+ color: lightgray;
+}
+
+.nav-caption{
+ display: inline-block;
+ padding-left: 6px;
+}
diff --git a/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts b/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts
new file mode 100644
index 0000000..c0f05af
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================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 { Component, OnInit, Output, EventEmitter } from '@angular/core';
+import { UiService } from '../../services/ui/ui.service';
+
+@Component({
+ selector: 'rd-sidenav-list',
+ templateUrl: './sidenav-list.component.html',
+ styleUrls: ['./sidenav-list.component.scss']
+})
+export class SidenavListComponent implements OnInit {
+ darkMode: boolean;
+ @Output() sidenavClose = new EventEmitter();
+
+ constructor(public ui: UiService) { }
+
+ ngOnInit() {
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ }
+
+ public onSidenavClose = () => {
+ this.sidenavClose.emit();
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.html b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.html
new file mode 100644
index 0000000..04d440c
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.html
@@ -0,0 +1,82 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2019 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>
+ <h3 class="rd-global-page-title">Policy Control</h3>
+
+ <table mat-table [dataSource]="policyTypesDataSource" matSort multiTemplateDataRows
+ class="policy-type-table mat-elevation-z8">
+
+ <ng-container matColumnDef="name">
+ <mat-header-cell *matHeaderCellDef mat-sort-header>Policy Type</mat-header-cell>
+ <mat-cell *matCellDef="let policyType">
+ <mat-icon matTooltip="Properties">{{isInstancesShown(policyType) ? 'expand_less' : 'expand_more'}}</mat-icon>
+ {{getPolicyTypeName(policyType)}}
+ </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="description">
+ <mat-header-cell *matHeaderCellDef> Description </mat-header-cell>
+ <mat-cell *matCellDef="let policyType"> {{policyType.description}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="action">
+ <mat-header-cell class="action-cell" *matHeaderCellDef>Action </mat-header-cell>
+ <mat-cell class="action-cell" *matCellDef="let policyType" (click)="$event.stopPropagation()">
+ <button mat-icon-button (click)="createPolicyInstance(policyType)">
+ <mat-icon matTooltip="Create instance">add_box</mat-icon>
+ </button>
+ </mat-cell>
+ </ng-container>
+
+ <!-- =================== Policy instances for one type ======================== -->
+ <ng-container matColumnDef="instanceTableContainer">
+ <mat-cell *matCellDef="let policyType">
+ <rd-policy-instance
+ [policyType]=policyType
+ [expanded]=getObservable(policyType)>
+ </rd-policy-instance>
+ </mat-cell>
+ </ng-container>
+ <!-- ======= -->
+
+ <ng-container matColumnDef="noRecordsFound">
+ <mat-footer-cell *matFooterCellDef>No records found.</mat-footer-cell>
+ </ng-container>
+
+ <mat-header-row *matHeaderRowDef="['name', 'description', 'action']"></mat-header-row>
+ <mat-row *matRowDef="let policyType; columns: ['name', 'description', 'action']"
+ (click)="toggleListInstances(policyType)">
+ </mat-row>
+
+ <mat-row *matRowDef="let policyType; columns: ['instanceTableContainer'];"
+ [@detailExpand]="isInstancesShown(policyType) ? 'expanded' : 'collapsed'" style="overflow: hidden">
+ </mat-row>
+
+ <mat-footer-row *matFooterRowDef="['noRecordsFound']"
+ [ngClass]="{'display-none': policyTypesDataSource.rowCount > 0}">
+ </mat-footer-row>
+
+ </table>
+
+ <div class="spinner-container" *ngIf="policyTypesDataSource.loading$ | async">
+ <mat-spinner diameter="50"></mat-spinner>
+ </div>
+</div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.scss b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.scss
new file mode 100644
index 0000000..f93e4ff
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.scss
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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===================================
+ */
+
+.spinner-container {
+ height: 100px;
+ width: 100px;
+}
+
+.spinner-container mat-spinner {
+ margin: 0 auto 0 auto;
+}
+
+.policy-type-table {
+ width: 100%;
+ min-height: 150px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ background-color: transparent;
+}
+
+.action-cell {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.display-none {
+ display: none;
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.spec.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.spec.ts
new file mode 100644
index 0000000..7c8643a
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PolicyControlComponent } from './policy-control.component';
+
+describe('PolicyControlComponent', () => {
+ let component: PolicyControlComponent;
+ let fixture: ComponentFixture<PolicyControlComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ PolicyControlComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PolicyControlComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.ts
new file mode 100644
index 0000000..70b8c45
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-control.component.ts
@@ -0,0 +1,115 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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, ViewChild } from '@angular/core';
+import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
+import { MatSort } from '@angular/material/sort';
+import { animate, state, style, transition, trigger } from '@angular/animations';
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+
+import { PolicyService } from '../services/policy/policy.service';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyTypeDataSource } from './policy-type.datasource';
+import { PolicyInstanceDataSource } from './policy-instance.datasource';
+import { getPolicyDialogProperties } from './policy-instance-dialog.component';
+import { PolicyInstanceDialogComponent } from './policy-instance-dialog.component';
+import { PolicyInstance } from '../interfaces/policy.types';
+import { NotificationService } from '../services/ui/notification.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { ConfirmDialogService } from './../services/ui/confirm-dialog.service';
+import { Subject } from 'rxjs';
+
+class PolicyTypeInfo {
+ constructor(public type: PolicyType, public isExpanded: boolean) { }
+
+ isExpandedObservers: Subject<boolean> = new Subject<boolean>();
+};
+
+@Component({
+ selector: 'rd-policy-control',
+ templateUrl: './policy-control.component.html',
+ styleUrls: ['./policy-control.component.scss'],
+ animations: [
+ trigger('detailExpand', [
+ state('collapsed', style({ height: '0px', minHeight: '0', visibility: 'hidden' })),
+ state('expanded', style({ height: '*', visibility: 'visible' })),
+ transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
+ ]),
+ ],
+})
+export class PolicyControlComponent implements OnInit {
+
+ policyTypesDataSource: PolicyTypeDataSource;
+ @ViewChild(MatSort, { static: true }) sort: MatSort;
+
+ expandedTypes = new Map<string, PolicyTypeInfo>();
+
+ constructor(
+ private policySvc: PolicyService,
+ private dialog: MatDialog,
+ private errorDialogService: ErrorDialogService,
+ private notificationService: NotificationService,
+ private confirmDialogService: ConfirmDialogService) { }
+
+ ngOnInit() {
+ this.policyTypesDataSource = new PolicyTypeDataSource(this.policySvc, this.sort, this.notificationService);
+ this.policyTypesDataSource.loadTable();
+ }
+
+ createPolicyInstance(policyType: PolicyType): void {
+ const dialogRef = this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(policyType, null));
+ const info: PolicyTypeInfo = this.getPolicyTypeInfo(policyType);
+ dialogRef.afterClosed().subscribe(
+ (result: any) => {
+ info.isExpandedObservers.next(info.isExpanded);
+ }
+ );
+ }
+
+ toggleListInstances(policyType: PolicyType): void {
+ let info = this.getPolicyTypeInfo(policyType);
+ info.isExpanded = !info.isExpanded;
+ info.isExpandedObservers.next(info.isExpanded);
+ }
+
+ getPolicyTypeInfo(policyType: PolicyType): PolicyTypeInfo {
+ let info: PolicyTypeInfo = this.expandedTypes.get(policyType.name);
+ if (!info) {
+ info = new PolicyTypeInfo(policyType, false);
+ this.expandedTypes.set(policyType.name, info);
+ }
+ return info;
+ }
+
+ isInstancesShown(policyType: PolicyType): boolean {
+ return this.getPolicyTypeInfo(policyType).isExpanded;
+ }
+
+ getPolicyTypeName(type: PolicyType): string {
+ const schema = JSON.parse(type.create_schema);
+ if (schema.title) {
+ return schema.title;
+ }
+ return type.name;
+ }
+
+ getObservable(policyType: PolicyType): Subject<boolean> {
+ return this.getPolicyTypeInfo(policyType).isExpandedObservers;
+ }
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.html b/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.html
new file mode 100644
index 0000000..ad7ea49
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.html
@@ -0,0 +1,90 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2019 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 class="text-muted logo" fxLayout="row" fxLayoutGap="50px">
+ <div *ngIf="policyInstanceId">{{policyInstanceId}}</div>
+</div>
+<div class="mat-elevation-z8 header row logo">
+ <div class="logo">
+ <img src="../../assets/oran-logo.png" width="30px" height="30px" style="position: relative; z-index: 50" />
+ <svg class="logo__icon" viewBox="150.3 22.2 400 50">
+ <text [ngClass]="{'logo__text-dark': darkModeActive}" class="logo__text" fill="#432c85" font-size="30"
+ font-weight="600" letter-spacing=".1em" transform="translate(149 56)">
+ <tspan>Policy editor</tspan>
+ <tspan *ngIf="jsonSchemaObject.title"> {{this.jsonSchemaObject.title}}</tspan>
+ <tspan *ngIf="!jsonSchemaObject.title"> {{this.policyTypeName}}</tspan>
+ </text>
+ </svg>
+ </div>
+</div>
+
+<!--<div class="text-muted" *ngIf="jsonSchemaObject.description">{{jsonSchemaObject.description}}</div>-->
+
+<div fxLayout="row" fxLayoutAlign="space-around start" fxLayout.lt-sm="column" fxLayoutAlign.lt-sm="flex-start center">
+ <mat-card class="card">
+ <h4 class="default-cursor" (click)="toggleVisible('form')">
+ <mat-icon matTooltip="Properties">{{isVisible.form ? 'expand_less' : 'expand_more'}}</mat-icon>
+ Properties
+ </h4>
+ <div *ngIf="isVisible.form" class="json-schema-form" [@expandSection]="true">
+ <div *ngIf="!formActive">{{jsonFormStatusMessage}}</div>
+
+ <json-schema-form *ngIf="formActive" loadExternalAssets="true" [form]="jsonSchemaObject"
+ [(data)]="jsonObject" [options]="jsonFormOptions" [framework]="'material-design'" [language]="'en'"
+ (onChanges)="onChanges($event)" (onSubmit)="onSubmit($event)" (isValid)="isValid($event)"
+ (validationErrors)="validationErrors($event)">
+ </json-schema-form>
+ </div>
+ <hr />
+ <button mat-raised-button (click)="this.onSubmit()" [disabled]="!this.formIsValid" class="submitBtn"
+ style="margin-right:10px">Submit</button>
+ <button mat-raised-button (click)="this.onClose()">Close</button>
+ <hr />
+ <h4 [class.text-danger]="!formIsValid && !isVisible.json" [class.default-cursor]="formIsValid || isVisible.json"
+ (click)="toggleVisible('json')">
+ <mat-icon matTooltip="Json">{{isVisible.json ? 'expand_less' : 'expand_more'}}</mat-icon>
+ Json
+ </h4>
+ <div *ngIf="isVisible.json" fxLayout="column" [@expandSection]="true">
+ <div>
+ <strong *ngIf="formIsValid || prettyValidationErrors" [class.text-muted]="formIsValid"
+ [class.text-danger]="!formIsValid">
+ {{formIsValid ? 'Json' : 'Not valid'}}
+ </strong>
+ <span *ngIf="!formIsValid && !prettyValidationErrors">Invalid form</span>
+ <span *ngIf="prettyValidationErrors">— errors:</span>
+ <div *ngIf="prettyValidationErrors" class="text-danger" [innerHTML]="prettyValidationErrors"></div>
+ </div>
+ <div>
+ <pre [class.data-good]="!prettyValidationErrors && prettyLiveFormData !== '{}'"
+ [class.data-bad]="prettyValidationErrors">{{prettyLiveFormData}}
+ </pre>
+ </div>
+ </div>
+
+ <h4 class="default-cursor" (click)="toggleVisible('schema')">
+ <mat-icon matTooltip="Json Schema">{{isVisible.schema ? 'expand_less' : 'expand_more'}}</mat-icon>
+ Json Schema
+ </h4>
+ <div *ngIf="isVisible.schema" fxLayout="column" [@expandSection]="true">
+ <strong class="text-muted">Schema</strong>
+ <pre>{{schemaAsString}}</pre>
+ </div>
+ </mat-card>
+</div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.scss b/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.scss
new file mode 100644
index 0000000..7050020
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.scss
@@ -0,0 +1,56 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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===================================
+ */
+
+.header {
+ color: black;
+ background: linear-gradient(to right, white 0%, rgb(217, 216, 231) 100%);
+ font-size: 40px;
+ font-weight: 400;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.logo {
+ margin-left: 10px;
+}
+
+.logo__text {
+ fill: #2B244D;
+}
+
+.logo__text-dark {
+ fill: #ffffff;
+}
+
+.logo__icon {
+ height: 2rem;
+ margin-left: 1rem;
+}
+
+.submitBtn {
+ background-color: #4CAF50; /* Green */
+}
+
+.card {
+ height: 100%;
+ width: 100%;
+ margin-left: 10px;
+ margin-right: 1px;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.ts
new file mode 100644
index 0000000..0f483d4
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-instance-dialog.component.ts
@@ -0,0 +1,214 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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, ViewChild, Inject, AfterViewInit, Self } from '@angular/core';
+import { MatMenuTrigger } from '@angular/material/menu';
+import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
+import { trigger, state, style, animate, transition } from '@angular/animations';
+import * as uuid from 'uuid';
+
+import { JsonPointer } from 'angular6-json-schema-form';
+import { PolicyService } from '../services/policy/policy.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { NotificationService } from './../services/ui/notification.service';
+
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyInstance } from '../interfaces/policy.types';
+
+@Component({
+ selector: 'rd-policy-instance-dialog',
+ templateUrl: './policy-instance-dialog.component.html',
+ styleUrls: ['./policy-instance-dialog.component.scss'],
+ animations: [
+ trigger('expandSection', [
+ state('in', style({ height: '*' })),
+ transition(':enter', [
+ style({ height: 0 }), animate(100),
+ ]),
+ transition(':leave', [
+ style({ height: '*' }),
+ animate(100, style({ height: 0 })),
+ ]),
+ ]),
+ ],
+})
+export class PolicyInstanceDialogComponent implements OnInit, AfterViewInit {
+
+ formActive = false;
+ isVisible = {
+ form: true,
+ json: false,
+ schema: false
+ };
+
+ jsonFormStatusMessage = 'Loading form...';
+ jsonSchemaObject: any = {};
+ jsonObject: any = {};
+
+
+ jsonFormOptions: any = {
+ addSubmit: false, // Add a submit button if layout does not have one
+ debug: false, // Don't show inline debugging information
+ loadExternalAssets: true, // Load external css and JavaScript for frameworks
+ returnEmptyFields: false, // Don't return values for empty input fields
+ setSchemaDefaults: true, // Always use schema defaults for empty fields
+ defautWidgetOptions: { feedback: true }, // Show inline feedback icons
+ };
+
+ liveFormData: any = {};
+ formValidationErrors: any;
+ formIsValid = false;
+
+
+ @ViewChild(MatMenuTrigger, { static: true }) menuTrigger: MatMenuTrigger;
+
+ public policyInstanceId: string;
+ public policyTypeName: string;
+ private policyTypeId: number;
+
+ constructor(
+ private dataService: PolicyService,
+ private errorService: ErrorDialogService,
+ private notificationService: NotificationService,
+ @Inject(MAT_DIALOG_DATA) private data,
+ private dialogRef: MatDialogRef<PolicyInstanceDialogComponent>) {
+ this.formActive = false;
+ this.policyInstanceId = this.data.instanceId;
+ this.policyTypeName = this.data.name;
+ this.policyTypeId = this.data.policyTypeId;
+ this.parseJson(data.createSchema, data.instanceJson);
+ }
+
+ ngOnInit() {
+ this.jsonFormStatusMessage = 'Init';
+ this.formActive = true;
+ }
+
+ ngAfterViewInit() {
+ }
+
+ onSubmit() {
+ if (this.policyInstanceId == null) {
+ this.policyInstanceId = uuid.v4();
+ }
+ const policyJson: string = this.prettyLiveFormData;
+ const self: PolicyInstanceDialogComponent = this;
+ this.dataService.putPolicy(this.policyTypeId, this.policyInstanceId, policyJson).subscribe(
+ {
+ next(value) {
+ self.notificationService.success('Policy ' + self.policyTypeName + ':' + self.policyInstanceId + ' submitted');
+ },
+ error(error) {
+ self.errorService.displayError('updatePolicy failed: ' + error.message);
+ },
+ complete() { }
+ });
+ }
+
+ onClose() {
+ this.dialogRef.close();
+ }
+
+ public onChanges(data: any) {
+ this.liveFormData = data;
+ }
+
+ get prettyLiveFormData() {
+ return JSON.stringify(this.liveFormData, null, 2);
+ }
+
+ get schemaAsString() {
+ return JSON.stringify(this.jsonSchemaObject, null, 2);
+ }
+
+ get jsonAsString() {
+ return JSON.stringify(this.jsonObject, null, 2);
+ }
+
+ isValid(isValid: boolean): void {
+ this.formIsValid = isValid;
+ }
+
+ validationErrors(data: any): void {
+ this.formValidationErrors = data;
+ }
+
+ get prettyValidationErrors() {
+ if (!this.formValidationErrors) { return null; }
+ const errorArray = [];
+ for (const error of this.formValidationErrors) {
+ const message = error.message;
+ const dataPathArray = JsonPointer.parse(error.dataPath);
+ if (dataPathArray.length) {
+ let field = dataPathArray[0];
+ for (let i = 1; i < dataPathArray.length; i++) {
+ const key = dataPathArray[i];
+ field += /^\d+$/.test(key) ? `[${key}]` : `.${key}`;
+ }
+ errorArray.push(`${field}: ${message}`);
+ } else {
+ errorArray.push(message);
+ }
+ }
+ return errorArray.join('<br>');
+ }
+
+ private parseJson(createSchema: string, instanceJson: string): void {
+ try {
+ this.jsonSchemaObject = JSON.parse(createSchema);
+ if (this.data.instanceJson != null) {
+ this.jsonObject = JSON.parse(instanceJson);
+ }
+ } catch (jsonError) {
+ this.jsonFormStatusMessage =
+ 'Invalid JSON\n' +
+ 'parser returned:\n\n' + jsonError;
+ return;
+ }
+ }
+
+ public toggleVisible(item: string) {
+ this.isVisible[item] = !this.isVisible[item];
+ }
+}
+
+export function getPolicyDialogProperties(policyType: PolicyType, instance: PolicyInstance): MatDialogConfig {
+ const policyTypeId = policyType.policy_type_id;
+ const createSchema = policyType.create_schema;
+ const instanceId = instance ? instance.instanceId : null;
+ const instanceJson = instance ? instance.instance : null;
+ const name = policyType.name;
+
+ return {
+ maxWidth: '1200px',
+ height: '1200px',
+ width: '900px',
+ role: 'dialog',
+ disableClose: false,
+ data: {
+ policyTypeId,
+ createSchema,
+ instanceId,
+ instanceJson,
+ name
+ }
+ };
+}
+
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.html b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.html
new file mode 100644
index 0000000..60bfab2
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.html
@@ -0,0 +1,57 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2019 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===================================
+ -->
+<table #table mat-table class="instances-table mat-elevation-z8" matSort multiTemplateDataRows [dataSource]="instanceDataSource">
+
+ <ng-container matColumnDef="instanceId">
+ <mat-header-cell mat-sort-header *matHeaderCellDef >Instance</mat-header-cell>
+ <mat-cell *matCellDef="let element" (click)="modifyInstance(element)">{{element.instanceId}}
+ </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="action">
+ <mat-header-cell class="action-cell" *matHeaderCellDef>Action</mat-header-cell>
+ <mat-cell class="action-cell" *matCellDef="let instance">
+ <button mat-icon-button (click)="modifyInstance(instance)">
+ <mat-icon>edit</mat-icon>
+ </button>
+ <button mat-icon-button color="warn" (click)="deleteInstance(instance)">
+ <mat-icon>delete</mat-icon>
+ </button>
+ </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="noRecordsFound">
+ <mat-footer-cell *matFooterCellDef>No records found.</mat-footer-cell>
+ </ng-container>
+
+ <mat-header-row *matHeaderRowDef="['instanceId', 'action']"
+ [ngClass]="{'display-none': !this.hasInstances()}">
+ </mat-header-row>
+ <mat-row *matRowDef="let instance; columns: ['instanceId', 'action'];"></mat-row>
+
+ <mat-footer-row *matFooterRowDef="['noRecordsFound']" [ngClass]="{'display-none': this.hasInstances()}">
+ </mat-footer-row>
+
+</table>
+
+
+<div class="spinner-container" *ngIf="instanceDataSource.loading$ | async">
+ <mat-spinner diameter="50"></mat-spinner>
+</div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.scss b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.scss
new file mode 100644
index 0000000..5c1d7c3
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.scss
@@ -0,0 +1,41 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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===================================
+ */
+
+.instances-table {
+ width: 60%; ;
+ min-height: 150px;
+ min-width: 600px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ background-color:rgb(233, 233, 240);
+}
+
+.action-cell {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.display-none {
+ display: none;
+}
+
+.spinner-container mat-spinner {
+ margin: 0 auto 0 auto;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.ts
new file mode 100644
index 0000000..3544275
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.component.ts
@@ -0,0 +1,113 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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 { MatSort } from '@angular/material';
+import { Component, OnInit, ViewChild, Input, AfterViewInit } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyInstanceDataSource } from './policy-instance.datasource';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { PolicyService } from '../services/policy/policy.service';
+import { ConfirmDialogService } from './../services/ui/confirm-dialog.service';
+import { PolicyInstance } from '../interfaces/policy.types';
+import { PolicyInstanceDialogComponent } from './policy-instance-dialog.component';
+import { getPolicyDialogProperties } from './policy-instance-dialog.component';
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+@Component({
+ selector: 'rd-policy-instance',
+ templateUrl: './policy-instance.component.html',
+ styleUrls: ['./policy-instance.component.scss']
+})
+
+
+export class PolicyInstanceComponent implements OnInit, AfterViewInit {
+ instanceDataSource: PolicyInstanceDataSource;
+ @Input() policyType: PolicyType;
+ @Input() expanded: Observable<boolean>;
+ @ViewChild(MatSort, { static: true }) sort: MatSort;
+
+ constructor(
+ private policySvc: PolicyService,
+ private dialog: MatDialog,
+ private errorDialogService: ErrorDialogService,
+ private notificationService: NotificationService,
+ private confirmDialogService: ConfirmDialogService) {
+ }
+
+ ngOnInit() {
+ this.instanceDataSource = new PolicyInstanceDataSource(this.policySvc, this.sort, this.notificationService, this.policyType);
+ this.expanded.subscribe((isExpanded: boolean) => this.onExpand(isExpanded));
+ }
+
+ ngAfterViewInit() {
+ this.instanceDataSource.sort = this.sort;
+ }
+
+ private onExpand(isExpanded: boolean) {
+ if (isExpanded) {
+ this.instanceDataSource.loadTable();
+ }
+ }
+
+ modifyInstance(instance: PolicyInstance): void {
+ this.policySvc.getPolicy(this.policyType.policy_type_id, instance.instanceId).subscribe(
+ (refreshedJson: any) => {
+ instance.instance = JSON.stringify(refreshedJson);
+ this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(this.policyType, instance));
+ },
+ (httpError: HttpErrorResponse) => {
+ this.notificationService.error('Could not refresh instance ' + httpError.message);
+ this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(this.policyType, instance));
+ }
+ );
+ }
+
+ hasInstances(): boolean {
+ return this.instanceDataSource.rowCount > 0;
+ }
+
+ deleteInstance(instance: PolicyInstance): void {
+ this.confirmDialogService
+ .openConfirmDialog('Are you sure you want to delete this policy instance?')
+ .afterClosed().subscribe(
+ (res: any) => {
+ if (res) {
+ this.policySvc.deletePolicy(this.policyType.policy_type_id, instance.instanceId)
+ .subscribe(
+ (response: HttpResponse<Object>) => {
+ switch (response.status) {
+ case 200:
+ this.notificationService.success('Delete succeeded!');
+ this.instanceDataSource.loadTable();
+ break;
+ default:
+ this.notificationService.warn('Delete failed.');
+ }
+ },
+ (error: HttpErrorResponse) => {
+ this.errorDialogService.displayError(error.message);
+ });
+ }
+ });
+ }
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-instance.datasource.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.datasource.ts
new file mode 100644
index 0000000..c74c9ab
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-instance.datasource.ts
@@ -0,0 +1,100 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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 { DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { PolicyInstance } from '../interfaces/policy.types';
+import { PolicyService } from '../services/policy/policy.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { PolicyType } from '../interfaces/policy.types';
+
+export class PolicyInstanceDataSource extends DataSource<PolicyInstance> {
+
+ private policyInstanceSubject = new BehaviorSubject<PolicyInstance[]>([]);
+
+ private loadingSubject = new BehaviorSubject<boolean>(false);
+
+ public loading$ = this.loadingSubject.asObservable();
+
+ public rowCount = 1; // hide footer during intial load
+
+ constructor(
+ private policySvc: PolicyService,
+ public sort: MatSort,
+ private notificationService: NotificationService,
+ private policyType: PolicyType) {
+ super();
+ }
+
+ loadTable() {
+ this.loadingSubject.next(true);
+ this.policySvc.getPolicyInstances(this.policyType.policy_type_id)
+ .pipe(
+ catchError((her: HttpErrorResponse) => {
+ this.notificationService.error('Failed to get policy instances: ' + her.message);
+ return of([]);
+ }),
+ finalize(() => this.loadingSubject.next(false))
+ )
+ .subscribe((instances: PolicyInstance[]) => {
+ this.rowCount = instances.length;
+ this.policyInstanceSubject.next(instances);
+ });
+ }
+
+ connect(): Observable<PolicyInstance[]> {
+ const dataMutations = [
+ this.policyInstanceSubject.asObservable(),
+ this.sort.sortChange
+ ];
+ return merge(...dataMutations).pipe(map(() => {
+ return this.getSortedData([...this.policyInstanceSubject.getValue()]);
+ }));
+ }
+
+ disconnect(): void {
+ this.policyInstanceSubject.complete();
+ this.loadingSubject.complete();
+ }
+
+ private getSortedData(data: PolicyInstance[]) {
+ if (!this.sort || !this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a, b) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'instanceId': return compare(a.instanceId, b.instanceId, isAsc);
+ default: return 0;
+ }
+ });
+ }
+}
+
+function compare(a: string, b: string, isAsc: boolean) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/dashboard/webapp-frontend/src/app/policy-control/policy-type.datasource.ts b/dashboard/webapp-frontend/src/app/policy-control/policy-type.datasource.ts
new file mode 100644
index 0000000..1b2b93e
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/policy-control/policy-type.datasource.ts
@@ -0,0 +1,97 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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 { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyService } from '../services/policy/policy.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class PolicyTypeDataSource extends DataSource<PolicyType> {
+
+ private policyTypeSubject = new BehaviorSubject<PolicyType[]>([]);
+
+ private loadingSubject = new BehaviorSubject<boolean>(false);
+
+ public loading$ = this.loadingSubject.asObservable();
+
+ public rowCount = 1; // hide footer during intial load
+
+ constructor(private policySvc: PolicyService,
+ private sort: MatSort,
+ private notificationService: NotificationService) {
+ super();
+ }
+
+ loadTable() {
+ this.loadingSubject.next(true);
+ this.policySvc.getPolicyTypes()
+ .pipe(
+ catchError((her: HttpErrorResponse) => {
+ this.notificationService.error('Failed to get policy types: ' + her.message);
+ return of([]);
+ }),
+ finalize(() => this.loadingSubject.next(false))
+ )
+ .subscribe((types: PolicyType[]) => {
+ this.rowCount = types.length;
+ this.policyTypeSubject.next(types);
+ });
+ }
+
+ connect(collectionViewer: CollectionViewer): Observable<PolicyType[]> {
+ const dataMutations = [
+ this.policyTypeSubject.asObservable(),
+ this.sort.sortChange
+ ];
+ return merge(...dataMutations).pipe(map(() => {
+ return this.getSortedData([...this.policyTypeSubject.getValue()]);
+ }));
+ }
+
+ disconnect(collectionViewer: CollectionViewer): void {
+ this.policyTypeSubject.complete();
+ this.loadingSubject.complete();
+ }
+
+ private getSortedData(data: PolicyType[]) {
+ if (!this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a, b) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'name': return compare(a.name, b.name, isAsc);
+ default: return 0;
+ }
+ });
+ }
+}
+
+function compare(a: any, b: any, isAsc: boolean) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.html b/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.html
new file mode 100644
index 0000000..0642baa
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.html
@@ -0,0 +1,54 @@
+<!--
+ ========================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===================================
+ -->
+
+<div mat-dialog-title>
+ Setup RAN Connection
+</div>
+<form [formGroup]="ranDialogForm" novalidate autocomplete="off" (ngSubmit)="setupConnection(ranDialogForm.value)">
+ <div mat-dialog-content>
+ <div name="rantype">
+ <label id="request-type-radio-group-label">RAN type:</label>
+ <mat-radio-group aria-label="RAN Type" formControlName="ranType">
+ <mat-radio-button class="ran-type-radio-button" value="endc">EN-DC</mat-radio-button>
+ <mat-radio-button class="ran-type-radio-button" value="x2">X2</mat-radio-button>
+ </mat-radio-group>
+ </div>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="RAN Name" formControlName="ranName">
+ <mat-hint align="end">Example: ABCD123456</mat-hint>
+ <mat-error *ngIf="validateControl('ranName') && hasError('ranName', 'required')">Name is required</mat-error>
+ <mat-error *ngIf="hasError('ranName', 'length')">Valid name is required</mat-error>
+ </mat-form-field>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="IP" formControlName="ranIp">
+ <mat-error *ngIf="validateControl('ranIp') && hasError('ranIp', 'required')">IP is required</mat-error>
+ <mat-error *ngIf="hasError('ranIp', 'pattern')">Valid IP is required</mat-error>
+ </mat-form-field>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="Port" formControlName="ranPort">
+ <mat-error *ngIf="validateControl('ranPort') && hasError('ranPort', 'required')">Port is required</mat-error>
+ <mat-error *ngIf="hasError('ranPort', 'pattern')">Valid port number is required</mat-error>
+ </mat-form-field>
+ </div>
+ <div mat-dialog-actions class="modal-footer justify-content-center">
+ <button class="mat-raised-button" (click)="onCancel()">Cancel</button>
+ <button class="mat-raised-button mat-primary" [disabled]="!ranDialogForm.valid || processing">Connect</button>
+ </div>
+</form>
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.scss b/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.scss
new file mode 100644
index 0000000..484ceb9
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.scss
@@ -0,0 +1,29 @@
+/*-
+ * ========================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===================================
+ */
+
+ /* used to place form fields on separate lines/rows in dialog */
+.input-display-block {
+ display: block;
+}
+
+/* leave a bit of space */
+.ran-type-radio-button {
+ margin-left: 5px;
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts b/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts
new file mode 100644
index 0000000..ad87121
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ran-control/ran-connection-dialog.component.ts
@@ -0,0 +1,124 @@
+/*-
+ * ========================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 { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+import { Component, OnInit } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { MatDialogRef } from '@angular/material/dialog';
+import { Observable } from 'rxjs';
+import { finalize } from 'rxjs/operators';
+import { E2SetupRequest, RanDialogFormData } from '../interfaces/e2-mgr.types';
+import { E2ManagerService } from '../services/e2-mgr/e2-mgr.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { LoadingDialogService } from '../services/ui/loading-dialog.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+@Component({
+ selector: 'rd-ran-control-connect-dialog',
+ templateUrl: './ran-connection-dialog.component.html',
+ styleUrls: ['./ran-connection-dialog.component.scss']
+})
+
+export class RanControlConnectDialogComponent implements OnInit {
+
+ public ranDialogForm: FormGroup;
+ public processing = false;
+
+ constructor(
+ private dialogRef: MatDialogRef<RanControlConnectDialogComponent>,
+ private service: E2ManagerService,
+ private errorService: ErrorDialogService,
+ private loadingDialogService: LoadingDialogService,
+ private notifService: NotificationService) {
+ // opens with empty fields; accepts no data to display
+ }
+
+ ngOnInit() {
+ const namePattern = /^([A-Z0-9])+$/;
+ // tslint:disable-next-line:max-line-length
+ const ipPattern = /((((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$))/;
+ const portPattern = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/;
+ this.ranDialogForm = new FormGroup({
+ ranType: new FormControl('endc'),
+ ranName: new FormControl('', [Validators.required, Validators.pattern(namePattern)]),
+ ranIp: new FormControl('', [Validators.required, Validators.pattern(ipPattern)]),
+ ranPort: new FormControl('', [Validators.required, Validators.pattern(portPattern)])
+ });
+ }
+
+ onCancel() {
+ this.dialogRef.close(false);
+ }
+
+ setupConnection = (ranFormValue: RanDialogFormData) => {
+ if (!this.ranDialogForm.valid) {
+ // should never happen
+ return;
+ }
+ this.processing = true;
+ const setupRequest: E2SetupRequest = {
+ ranName: ranFormValue.ranName.trim(),
+ ranIp: ranFormValue.ranIp.trim(),
+ ranPort: ranFormValue.ranPort.trim()
+ };
+ this.loadingDialogService.startLoading('Setting up connection');
+ let observable: Observable<HttpResponse<Object>>;
+ if (ranFormValue.ranType === 'endc') {
+ observable = this.service.endcSetup(setupRequest);
+ } else {
+ observable = this.service.x2Setup(setupRequest);
+ }
+ observable
+ .pipe(
+ finalize(() => this.loadingDialogService.stopLoading())
+ )
+ .subscribe(
+ (response: any) => {
+ this.processing = false;
+ this.notifService.success('Connect request sent!');
+ this.dialogRef.close(true);
+ },
+ ((her: HttpErrorResponse) => {
+ this.processing = false;
+ // the error field carries the server's response
+ let msg = her.message;
+ if (her.error && her.error.message) {
+ msg = her.error.message;
+ }
+ this.errorService.displayError('Connect request failed: ' + msg);
+ // keep the dialog open
+ })
+ );
+ }
+
+ hasError(controlName: string, errorName: string) {
+ if (this.ranDialogForm.controls[controlName].hasError(errorName)) {
+ return true;
+ }
+ return false;
+ }
+
+ validateControl(controlName: string) {
+ if (this.ranDialogForm.controls[controlName].invalid && this.ranDialogForm.controls[controlName].touched) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.html b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.html
new file mode 100644
index 0000000..01dd292
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.html
@@ -0,0 +1,89 @@
+<!--
+ ========================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===================================
+-->
+
+<div class="ran-control__section">
+ <h3 class="rd-global-page-title">RAN Connections</h3>
+
+ <button mat-raised-button (click)="setupRANConnection()">Setup Connection..</button>
+ <button mat-raised-button color="warn" class="disconnect-all-button"
+ (click)="disconnectAllRANConnections()">Disconnect All</button>
+
+ <table mat-table class="ran-control-table mat-elevation-z8" [dataSource]="dataSource">
+
+ <ng-template #noValue></ng-template>
+
+ <ng-container matColumnDef="nbId">
+ <mat-header-cell *matHeaderCellDef>Nodeb ID</mat-header-cell>
+ <mat-cell *matCellDef="let ran">
+ <div *ngIf="ran.nodebIdentity.globalNbId; else noValue">{{ran.nodebIdentity.globalNbId.nbId}}</div>
+ </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="nodeType">
+ <mat-header-cell *matHeaderCellDef>Node Type</mat-header-cell>
+ <mat-cell *matCellDef="let ran">
+ <div *ngIf="ran.nodebStatus; else noValue">{{ran.nodebStatus.nodeType}}</div>
+ </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="ranName">
+ <mat-header-cell *matHeaderCellDef>RAN Name</mat-header-cell>
+ <mat-cell *matCellDef="let ran">
+ <div *ngIf="ran.nodebIdentity; else noValue">{{ran.nodebIdentity.inventoryName}}</div>
+ </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="ranIp">
+ <mat-header-cell *matHeaderCellDef>IP</mat-header-cell>
+ <mat-cell *matCellDef="let ran">
+ <div *ngIf="ran.nodebStatus; else noValue">{{ran.nodebStatus.ip}}</div>
+ </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="ranPort">
+ <mat-header-cell *matHeaderCellDef>Port</mat-header-cell>
+ <mat-cell *matCellDef="let ran">
+ <div *ngIf="ran.nodebStatus; else noValue">{{ran.nodebStatus.port}}</div>
+ </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="connectionStatus">
+ <mat-header-cell *matHeaderCellDef>Connection Status</mat-header-cell>
+ <mat-cell *matCellDef="let ran">
+ <div *ngIf="ran.nodebStatus; else noValue">{{ran.nodebStatus.connectionStatus}}</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>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.scss b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.scss
new file mode 100644
index 0000000..c86d062
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.scss
@@ -0,0 +1,51 @@
+/*-
+ * ========================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===================================
+ */
+ .ran-control__section {
+}
+
+.disconnect-all-button {
+ float: right;
+}
+
+.ran-control-table {
+ width: 100%;
+ min-height: 100px;
+ margin-top: 10px;
+ background-color:transparent;
+}
+
+.spinner-container {
+ height: 100px;
+ width: 100px;
+}
+
+.spinner-container mat-spinner {
+ margin: 0 auto 0 auto;
+}
+
+.version__text {
+ color: gray;
+ letter-spacing: 0.1rem;
+ font-size: 10px;
+}
+
+.display-none {
+ display: none;
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.spec.ts b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.spec.ts
new file mode 100644
index 0000000..aa0e8b8
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RanControlComponent } from './ran-control.component';
+
+describe('RanControlComponent', () => {
+ let component: RanControlComponent;
+ let fixture: ComponentFixture<RanControlComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ RanControlComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RanControlComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.ts b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.ts
new file mode 100644
index 0000000..b5aba66
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ran-control/ran-control.component.ts
@@ -0,0 +1,107 @@
+/*-
+ * ========================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 { HttpErrorResponse } from '@angular/common/http';
+import { Component, OnInit } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { finalize } from 'rxjs/operators';
+import { E2ManagerService } from '../services/e2-mgr/e2-mgr.service';
+import { ConfirmDialogService } from '../services/ui/confirm-dialog.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { LoadingDialogService } from '../services/ui/loading-dialog.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { RanControlConnectDialogComponent } from './ran-connection-dialog.component';
+import { RANControlDataSource } from './ran-control.datasource';
+import { UiService } from '../services/ui/ui.service';
+
+@Component({
+ selector: 'rd-ran-control',
+ templateUrl: './ran-control.component.html',
+ styleUrls: ['./ran-control.component.scss']
+})
+export class RanControlComponent implements OnInit {
+
+ darkMode: boolean;
+ panelClass: string = "";
+ displayedColumns: string[] = ['nbId', 'nodeType', 'ranName', 'ranIp', 'ranPort', 'connectionStatus'];
+ dataSource: RANControlDataSource;
+
+ constructor(private e2MgrSvc: E2ManagerService,
+ private errorDialogService: ErrorDialogService,
+ private confirmDialogService: ConfirmDialogService,
+ private notificationService: NotificationService,
+ private loadingDialogService: LoadingDialogService,
+ public dialog: MatDialog,
+ public ui: UiService) { }
+
+ ngOnInit() {
+ this.dataSource = new RANControlDataSource(this.e2MgrSvc, this.notificationService);
+ this.dataSource.loadTable();
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ }
+
+ setupRANConnection() {
+ if (this.darkMode) {
+ this.panelClass = "dark-theme";
+ } else {
+ this.panelClass = "";
+ }
+ const dialogRef = this.dialog.open(RanControlConnectDialogComponent, {
+ panelClass: this.panelClass,
+ width: '450px'
+ });
+ dialogRef.afterClosed()
+ .subscribe((result: boolean) => {
+ if (result) {
+ this.dataSource.loadTable();
+ }
+ });
+ }
+
+ disconnectAllRANConnections() {
+ const aboutError = 'Disconnect all RAN Connections Failed: ';
+ this.confirmDialogService.openConfirmDialog('Are you sure you want to disconnect all RAN connections?')
+ .afterClosed().subscribe( (res: boolean) => {
+ if (res) {
+ this.loadingDialogService.startLoading("Disconnecting");
+ this.e2MgrSvc.nodebPut()
+ .pipe(
+ finalize(() => this.loadingDialogService.stopLoading())
+ )
+ .subscribe(
+ ( body: any ) => {
+ this.notificationService.success('Disconnect succeeded!');
+ this.dataSource.loadTable();
+ },
+ (her: HttpErrorResponse) => {
+ // the error field should have an ErrorTransport object
+ let msg = her.message;
+ if (her.error && her.error.message) {
+ msg = her.error.message;
+ }
+ this.errorDialogService.displayError('Disconnect failed: ' + msg);
+ }
+ );
+ }
+ });
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/ran-control/ran-control.datasource.ts b/dashboard/webapp-frontend/src/app/ran-control/ran-control.datasource.ts
new file mode 100644
index 0000000..b23a3cd
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ran-control/ran-control.datasource.ts
@@ -0,0 +1,72 @@
+/*-
+ * ========================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 { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize } from 'rxjs/operators';
+import { E2RanDetails } from '../interfaces/e2-mgr.types';
+import { E2ManagerService } from '../services/e2-mgr/e2-mgr.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class RANControlDataSource extends DataSource<E2RanDetails> {
+
+ private ranControlSubject = new BehaviorSubject<E2RanDetails[]>([]);
+
+ private loadingSubject = new BehaviorSubject<boolean>(false);
+
+ public loading$ = this.loadingSubject.asObservable();
+
+ public rowCount = 1; // hide footer during intial load
+
+ constructor(private e2MgrSvcservice: E2ManagerService,
+ private notificationService: NotificationService) {
+ super();
+ }
+
+ loadTable() {
+ this.loadingSubject.next(true);
+ this.e2MgrSvcservice.getRan()
+ .pipe(
+ catchError( (her: HttpErrorResponse) => {
+ console.log('RANControlDataSource failed: ' + her.message);
+ this.notificationService.error('Failed to get RAN details: ' + her.message);
+ return of([]);
+ }),
+ finalize( () => this.loadingSubject.next(false) )
+ )
+ .subscribe( (ranControl: E2RanDetails[] ) => {
+ this.rowCount = ranControl.length;
+ this.ranControlSubject.next(ranControl);
+ });
+ }
+
+ connect(collectionViewer: CollectionViewer): Observable<E2RanDetails[]> {
+ return this.ranControlSubject.asObservable();
+ }
+
+ disconnect(collectionViewer: CollectionViewer): void {
+ this.ranControlSubject.complete();
+ this.loadingSubject.complete();
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/rd-routing.module.ts b/dashboard/webapp-frontend/src/app/rd-routing.module.ts
new file mode 100644
index 0000000..520f9a5
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/rd-routing.module.ts
@@ -0,0 +1,43 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2019 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 { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Routes, RouterModule } from '@angular/router';
+import { MainComponent } from './main/main.component';
+import { PolicyControlComponent} from './policy-control/policy-control.component';
+
+
+const routes: Routes = [
+ {path: '', component: MainComponent},
+ {path: 'policy', component: PolicyControlComponent}
+];
+
+@NgModule({
+ imports: [
+ CommonModule,
+ RouterModule.forRoot(routes)],
+ exports: [
+ RouterModule
+ ],
+ declarations: []
+})
+
+export class RdRoutingModule { }
diff --git a/dashboard/webapp-frontend/src/app/rd.component.html b/dashboard/webapp-frontend/src/app/rd.component.html
new file mode 100644
index 0000000..aef2941
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/rd.component.html
@@ -0,0 +1,126 @@
+<!--
+ ========================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===================================
+ -->
+<!-- Slide Menu-->
+<mat-drawer-container autosize >
+ <mat-drawer #drawer mode="push" [ngClass]="{'side-menu__dark': darkModeActive}">
+ <section class="menu-header" [ngClass]="{'menu-header__dark': darkModeActive}">
+
+ <div class="left__section3Col" *ngIf="drawer.opened" [ngClass]="{'menumargin-top': drawer.opened}">
+ <svg (click)="drawer.toggle()" class="hamburger__icon" id="Menu_Burger_Icon" data-name="Menu Burger Icon"
+ viewBox="31.5 30 49.9 32">
+ <rect id="Rectangle_9" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+ class="hamburger__icon__fill" data-name="Rectangle 9" rx="2"
+ transform="translate(31.5 58)"/>
+ <rect id="Rectangle_10" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+ class="hamburger__icon__fill" data-name="Rectangle 10" rx="2"
+ transform="translate(31.5 44)"/>
+ <rect id="Rectangle_11" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+ class="hamburger__icon__fill" data-name="Rectangle 11" rx="2"
+ transform="translate(31.5 30)"/>
+ </svg>
+ <img src="../../assets/oran-logo.png" width="180%" height="100%" style="position: relative; z-index: 50"/>
+ <svg class="logo__icon" viewBox="150.3 22.2 400 50">
+ <text [ngClass]="{'logo__text-dark': darkModeActive}" class="logo__text" fill="#432c85"
+ font-size="30" font-weight="600"
+ letter-spacing=".1em" transform="translate(149 56)">
+ <tspan x="0" y="0">Non RT RIC Dashboard</tspan>
+ </text>
+ </svg>
+
+ </div>
+
+ <div class="profile-image__container">
+ <img src="assets/profile_default.png" alt="profile-image"
+ class="profile__image">
+ </div>
+ <div class="account-details">
+ <span class="name__text">Demo</span>
+ <span class="email__text">demo@o-ran-sc.org</span>
+ </div>
+ </section>
+ <section #sidenav class="menu-body" >
+ <rd-sidenav-list ></rd-sidenav-list>
+ </section>
+ <section class="menu-footer">
+
+ </section>
+</mat-drawer>
+
+<mat-drawer-content>
+<div class="root__container">
+
+ <header [ngClass]="{'main__header-dark': darkModeActive}" class="main__header">
+
+ <div class="left__section3Col" *ngIf="!drawer.opened">
+ <svg (click)="drawer.toggle()" class="hamburger__icon" id="Menu_Burger_Icon" data-name="Menu Burger Icon"
+ viewBox="31.5 30 49.9 32">
+ <rect id="Rectangle_9" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+ class="hamburger__icon__fill" data-name="Rectangle 9" rx="2"
+ transform="translate(31.5 58)"/>
+ <rect id="Rectangle_10" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+ class="hamburger__icon__fill" data-name="Rectangle 10" rx="2"
+ transform="translate(31.5 44)"/>
+ <rect id="Rectangle_11" width="49.9" height="4" [ngClass]="{'hamburger__icon__fill-dark': darkModeActive}"
+ class="hamburger__icon__fill" data-name="Rectangle 11" rx="2"
+ transform="translate(31.5 30)"/>
+ </svg>
+ <img src="../../assets/oran-logo.png" width="180%" height="100%" style="position: relative; z-index: 50"/>
+ <svg class="logo__icon" viewBox="150.3 22.2 400 50">
+ <text [ngClass]="{'logo__text-dark': darkModeActive}" class="logo__text" fill="#432c85"
+ font-size="30" font-weight="600"
+ letter-spacing=".1em" transform="translate(149 56)">
+ <tspan x="0" y="0">Non RT RIC Dashboard</tspan>
+ </text>
+ </svg>
+
+ </div>
+
+ <h3 class="date__text"></h3>
+
+ <div class="mode-toggle__container">
+ <span class="mode-toggle__text">Light</span>
+ <label class="toggle-button__container">
+ <input (click)="modeToggleSwitch()" type="checkbox" class="mode-toggle__input"/>
+ <span [ngClass]="{'mode-toggle__bg-checked': darkModeActive}" class="mode-toggle__bg"></span>
+ <span [ngClass]="{'mode-toggle__circle-checked': darkModeActive}" class="mode-toggle__circle"></span>
+ </label>
+ <span class="mode-toggle__text">Dark</span>
+ </div>
+
+ </header>
+
+ <!-- Main Content -->
+ <main class="main__container" [ngClass]="{'main-container__bg-dark': darkModeActive}" >
+ <div class = main__container__body [ngClass]="{'dark-theme': darkModeActive}">
+ <div class="main-container__bg"></div>
+ <router-outlet></router-outlet>
+ </div>
+ </main>
+
+ <!-- Footer -->
+ <footer class="main__footer" [ngClass]="{'main-footer__bg-dark': darkModeActive}">
+ <div class="main-footer__bg">
+ <rd-footer></rd-footer>
+ </div>
+ </footer>
+
+</div>
+</mat-drawer-content>
+</mat-drawer-container>
diff --git a/dashboard/webapp-frontend/src/app/rd.component.scss b/dashboard/webapp-frontend/src/app/rd.component.scss
new file mode 100644
index 0000000..f9e4735
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/rd.component.scss
@@ -0,0 +1,376 @@
+/*-
+ * ========================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===================================
+ */
+.root__container {
+ width: 100vw;
+ height: 100vh;
+ display: grid;
+ grid-template-columns: auto;
+ grid-template-rows: 0.5fr auto;
+ position: relative;
+}
+
+/*
+================
+ Header
+================
+*/
+mat-sidenav-container, mat-sidenav-content, mat-sidenav {
+ height: 100%;
+}
+
+mat-sidenav {
+ width: 250px;
+}
+
+main {
+ padding: 10px;
+}
+
+/*
+ Slide Menu
+= = = = = = = = =
+*/
+.side-menu__dark {
+ color: white;
+ background: gray;
+}
+
+.side-menu__container {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ pointer-events: none;
+ z-index: 25;
+}
+
+.side-menu__container-active {
+ pointer-events: auto;
+}
+
+.side-menu__container::before {
+ content: '';
+ cursor: pointer;
+ position: absolute;
+ display: block;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ background-color: #0c1066;
+ opacity: 0;
+ transition: opacity 300ms linear;
+ will-change: opacity;
+}
+
+.side-menu__container-active::before {
+ opacity: 0.3;
+}
+
+.slide-menu {
+ box-sizing: border-box;
+ transform: translateX(-103%);
+ position: relative;
+ top: 0;
+ left: 0;
+ z-index: 10;
+ height: 100%;
+ width: 90%;
+ max-width: 26rem;
+ background-color: white;
+ box-shadow: 0 0 2rem rgba(0, 0, 255, 0.1);
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 2fr 4fr 1fr;
+ grid-gap: 1rem;
+ transition: transform 300ms linear;
+ will-change: transform;
+}
+
+.slide-menu-active {
+ transform: none;
+}
+.menu-header.menu-header__dark {
+ background: #2B244D;
+}
+
+.menu-header {
+ background: linear-gradient(to right, rgb(181, 199, 192), #82bbb6);
+ display: grid;
+ grid-template-rows: 1fr 4fr;
+ grid-template-columns: 1fr 4fr;
+ grid-template-areas: "greeting greeting" "image details";
+ box-sizing: border-box;
+ width: 100%;
+ align-content: center;
+ color: white;
+ box-shadow: 0 0.5rem 2rem rgba(0, 0, 255, 0.2);
+}
+
+mat-drawer {
+ width: 340px;
+}
+
+mat-drawer-content {
+ overflow: overlay;
+}
+
+.menumargin-top {
+ margin-top: 10px;
+}
+
+.greeting__text {
+ grid-area: greeting;
+ font-size: 1.25rem;
+ letter-spacing: 0.15rem;
+ text-transform: uppercase;
+ margin-top: 1rem;
+ justify-self: center;
+ align-self: center;
+}
+
+.account-details {
+ grid-area: details;
+ display: flex;
+ flex-flow: column;
+ margin-left: 1rem;
+ align-self: center;
+}
+
+.name__text {
+ font-size: 1.15rem;
+ margin-bottom: 0.5rem;
+}
+
+.email__text {
+ font-size: 0.9rem;
+ letter-spacing: 0.1rem;
+}
+
+.menu-body {
+ display: grid;
+ width: 100%;
+}
+
+.profile-image__container {
+ grid-area: image;
+ margin-right: 0.5rem;
+ border-radius: 50%;
+ height: 4rem;
+ width: 4rem;
+ overflow: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: white;
+ align-self: center;
+ margin-left: 2rem;
+}
+
+.profile__image {
+ max-width: 4rem;
+}
+
+.home_bg_image{
+ height:40em;
+ background-size:cover;
+ width:auto;
+ background-image:url('../assets/intelligence.png');
+ background-position:50% 50%;
+}
+
+/*Header*/
+.main__header {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 1fr 1fr 0.25fr;
+ grid-template-rows: 1fr;
+ box-shadow: 0 0 2rem rgba(0, 0, 255, 0.1);
+ height: 4rem;
+ margin: 0;
+ align-items: center;
+ transition: background-color 500ms linear;
+ animation: fadein 1s ease-in-out 0ms 1;
+}
+
+.main__header-dark {
+ background-color: #2B244D;
+ color: white;
+}
+
+.toggle-button__container {
+ cursor: pointer;
+ position: relative;
+ margin: 0 0.5rem;
+}
+
+.mode-toggle__input {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+}
+
+.mode-toggle__bg {
+ height: 1rem;
+ width: 2rem;
+ border-radius: 0.5rem;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: inline-block;
+ transition: background-color 300ms linear;
+}
+
+.mode-toggle__circle {
+ height: 1.30rem;
+ width: 1.30rem;
+ background-color: #2B244D;
+ position: absolute;
+ top: -0.2rem;
+ border-radius: 50%;
+ box-shadow: 0 0 0 rgba(0, 0, 255, 0.5);
+ transition: left 300ms linear;
+ left: 0.1rem;
+}
+
+.mode-toggle__circle-checked {
+ background-color: white;
+ left: 1.75rem;
+}
+
+.mode-toggle__bg-checked {
+ background-color: #FF0070;
+}
+
+.mode-toggle__text {
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.1rem;
+}
+
+/*Content*/
+.left__section {
+ display: grid;
+ grid-template-rows: 1fr;
+ grid-template-columns: 1fr 1fr;
+ max-width: 5rem;
+}
+
+.left__section3Col {
+ display: grid;
+ grid-template-rows: 1fr;
+ grid-template-columns: 1fr 1fr 1fr;
+ max-width: 6rem;
+}
+
+.date__text {
+ text-transform: uppercase;
+ letter-spacing: 0.1rem;
+ display: inline;
+ margin: 0.5rem 0;
+}
+
+/*SVGs*/
+.hamburger__icon {
+ position: relative;
+ z-index: 35;
+ height: 2rem;
+ padding: 0.5rem 1.5rem;
+ margin-right: 1rem;
+ cursor: pointer;
+}
+
+.logo__icon {
+ height: 2rem;
+ margin-left: 1rem;
+}
+
+.logo__text {
+ fill: #2B244D;
+}
+
+.logo__text-dark {
+ fill: #ffffff;
+}
+
+.hamburger__icon__fill {
+ fill: #2B244D;
+}
+
+.hamburger__icon__fill-dark {
+ fill: #ffffff;
+}
+
+/*
+================
+ Body
+================
+*/
+
+.main__container {
+ height: 100%;
+ width: 100%;
+}
+
+.main__container__body {
+ min-height: calc(100vh - 140px);
+}
+
+.main-container__bg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: -2;
+ opacity: 0;
+ background: white;
+ transition: opacity 300ms linear;
+}
+
+.main-container__bg-dark {
+ opacity: 1;
+ background: linear-gradient(to bottom, #B290FF, #2E1D65);
+ transition: opacity 300ms linear;
+}
+
+/*
+================-
+ Footer
+================
+*/
+.main__footer {
+ background: transparent;
+ z-index: 100;
+}
+
+.main__footer-dark {
+ background-color: #2B244D;
+ color: white;
+}
+
+.main-footer__bg-dark {
+ opacity: 1;
+ background: linear-gradient(to bottom, #B290FF, #2E1D65);
+ transition: opacity 300ms linear;
+}
+
+@media only screen and (max-width: 300px) {
+ .slide-menu {
+ width: 100%;
+ }
+}
diff --git a/dashboard/webapp-frontend/src/app/rd.component.spec.ts b/dashboard/webapp-frontend/src/app/rd.component.spec.ts
new file mode 100644
index 0000000..852386c
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/rd.component.spec.ts
@@ -0,0 +1,54 @@
+/*-
+ * ========================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, async } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ RouterTestingModule
+ ],
+ declarations: [
+ AppComponent
+ ],
+ }).compileComponents();
+ }));
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app).toBeTruthy();
+ });
+
+ it(`should have as title 'dashApp'`, () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app.title).toEqual('dashApp');
+ });
+
+ it('should render title in a h1 tag', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+ const compiled = fixture.debugElement.nativeElement;
+ expect(compiled.querySelector('h1').textContent).toContain('Welcome to dashApp!');
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/rd.component.ts b/dashboard/webapp-frontend/src/app/rd.component.ts
new file mode 100644
index 0000000..1673280
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/rd.component.ts
@@ -0,0 +1,49 @@
+/*-
+ * ========================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 { Component, OnInit } from '@angular/core';
+import { UiService } from './services/ui/ui.service';
+
+@Component({
+ selector: 'rd-root',
+ templateUrl: './rd.component.html',
+ styleUrls: ['./rd.component.scss']
+})
+export class RdComponent implements OnInit {
+ showMenu = false;
+ darkModeActive: boolean;
+
+ constructor(public ui: UiService) {
+ }
+
+ ngOnInit() {
+ this.ui.darkModeState.subscribe((value) => {
+ this.darkModeActive = value;
+ });
+ }
+
+ toggleMenu() {
+ this.showMenu = !this.showMenu;
+ }
+
+ modeToggleSwitch() {
+ this.ui.darkModeState.next(!this.darkModeActive);
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/rd.module.ts b/dashboard/webapp-frontend/src/app/rd.module.ts
new file mode 100644
index 0000000..378374a
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/rd.module.ts
@@ -0,0 +1,164 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2019 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 { BrowserModule } from '@angular/platform-browser';
+// tslint:disable-next-line:max-line-length
+import {MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule,
+ MatDialogModule, MatExpansionModule, MatFormFieldModule, MatGridListModule,
+ MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatPaginatorModule,
+ MatProgressSpinnerModule, MatSelectModule, MatSidenavModule, MatSliderModule,
+ MatSlideToggleModule, MatSnackBarModule, MatSortModule, MatTableModule,
+ MatTabsModule, MatToolbarModule} from '@angular/material';
+import { BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import { HttpClientModule } from '@angular/common/http';
+import { NgModule } from '@angular/core';
+import { MatRadioModule } from '@angular/material/radio';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { ChartsModule } from 'ng2-charts';
+import { MDBBootstrapModule } from 'angular-bootstrap-md';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { ToastrModule } from 'ngx-toastr';
+
+import { AddDashboardUserDialogComponent } from './user/add-dashboard-user-dialog/add-dashboard-user-dialog.component';
+import { AppControlComponent } from './app-control/app-control.component';
+import { AppMgrService } from './services/app-mgr/app-mgr.service';
+import { ConfirmDialogComponent } from './ui/confirm-dialog/confirm-dialog.component';
+import { ControlComponent } from './control/control.component';
+import { DashboardService } from './services/dashboard/dashboard.service';
+import { E2ManagerService } from './services/e2-mgr/e2-mgr.service';
+import { EditDashboardUserDialogComponent } from './user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component';
+import { ErrorDialogComponent } from './ui/error-dialog/error-dialog.component';
+import { ErrorDialogService } from './services/ui/error-dialog.service';
+import { FlexLayoutModule } from '@angular/flex-layout';
+import { FooterComponent } from './footer/footer.component';
+import { LoadingDialogComponent } from './ui/loading-dialog/loading-dialog.component';
+import { MainComponent } from './main/main.component';
+import { MaterialDesignFrameworkModule } from 'angular6-json-schema-form';
+import { ModalEventComponent } from './ui/modal-event/modal-event.component';
+import { PolicyCardComponent } from './ui/policy-card/policy-card.component';
+import { PolicyControlComponent } from './policy-control/policy-control.component';
+import { PolicyInstanceComponent } from './policy-control/policy-instance.component';
+import { PolicyInstanceDialogComponent } from './policy-control/policy-instance-dialog.component';
+import { RanControlComponent } from './ran-control/ran-control.component';
+import { RanControlConnectDialogComponent } from './ran-control/ran-connection-dialog.component';
+import { RdComponent } from './rd.component';
+import { RdRoutingModule } from './rd-routing.module';
+import { SidenavListComponent } from './navigation/sidenav-list/sidenav-list.component';
+import { UiService } from './services/ui/ui.service';
+import { UserComponent } from './user/user.component';
+
+@NgModule({
+ declarations: [
+ AddDashboardUserDialogComponent,
+ AppControlComponent,
+ ConfirmDialogComponent,
+ ControlComponent,
+ EditDashboardUserDialogComponent,
+ ErrorDialogComponent,
+ FooterComponent,
+ LoadingDialogComponent,
+ MainComponent,
+ ModalEventComponent,
+ PolicyCardComponent,
+ PolicyControlComponent,
+ PolicyInstanceComponent,
+ PolicyInstanceDialogComponent,
+ RanControlComponent,
+ RanControlConnectDialogComponent,
+ RdComponent,
+ SidenavListComponent,
+ UserComponent
+ ],
+ imports: [
+ BrowserModule,
+ BrowserAnimationsModule,
+ ChartsModule,
+ FlexLayoutModule,
+ FormsModule,
+ HttpClientModule,
+ MatButtonModule,
+ MatButtonToggleModule,
+ MatCardModule,
+ MatCheckboxModule,
+ MatDialogModule,
+ MaterialDesignFrameworkModule,
+ MatExpansionModule,
+ MatFormFieldModule,
+ MatGridListModule,
+ MatIconModule,
+ MatInputModule,
+ MatListModule,
+ MatMenuModule,
+ MatPaginatorModule,
+ MatProgressSpinnerModule,
+ MatRadioModule,
+ MatSelectModule,
+ MatSliderModule,
+ MatSidenavModule,
+ MatSlideToggleModule,
+ MatSnackBarModule,
+ MatSortModule,
+ MatTableModule,
+ MatTabsModule,
+ MatToolbarModule,
+ MatTooltipModule,
+ MDBBootstrapModule.forRoot(),
+ RdRoutingModule,
+ ReactiveFormsModule,
+ ToastrModule.forRoot()
+ ],
+ exports: [
+ ErrorDialogComponent,
+ FormsModule,
+ MatButtonModule,
+ MatButtonToggleModule,
+ MatCardModule,
+ MatDialogModule,
+ MatExpansionModule,
+ MatFormFieldModule,
+ MatGridListModule,
+ MatIconModule,
+ MatInputModule,
+ MatListModule,
+ MatSidenavModule,
+ MatSliderModule,
+ MatSlideToggleModule,
+ MatTabsModule,
+ RanControlConnectDialogComponent
+ ],
+ entryComponents: [
+ AddDashboardUserDialogComponent,
+ ConfirmDialogComponent,
+ EditDashboardUserDialogComponent,
+ ErrorDialogComponent,
+ LoadingDialogComponent,
+ PolicyInstanceDialogComponent,
+ RanControlConnectDialogComponent
+ ],
+ providers: [
+ AppMgrService,
+ DashboardService,
+ E2ManagerService,
+ ErrorDialogService,
+ UiService
+ ],
+ bootstrap: [RdComponent]
+})
+export class RdModule { }
diff --git a/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.spec.ts
new file mode 100644
index 0000000..b3bf4d4
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.spec.ts
@@ -0,0 +1,31 @@
+/*-
+ * ========================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 { ACXappService } from './ac-xapp.service';
+
+describe('ACXappService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: ACXappService = TestBed.get(ACXappService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.ts b/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.ts
new file mode 100644
index 0000000..d25e9db
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ac-xapp/ac-xapp.service.ts
@@ -0,0 +1,83 @@
+/*-
+ * ========================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 { Injectable } from '@angular/core';
+ import { HttpClient } from '@angular/common/http';
+ import { Observable } from 'rxjs';
+ import { map } from 'rxjs/operators';
+import { ACAdmissionIntervalControl, ACAdmissionIntervalControlAck } from '../../interfaces/ac-xapp.types';
+import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+
+/**
+ * Services for calling the Dashboard's A1 endpoints to get/put AC policies.
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class ACXappService {
+
+ private basePath = 'api/a1-p';
+ private policyPath = 'policies';
+ private acPolicyName = 'admission_control_policy';
+
+ private buildPath(...args: any[]) {
+ let result = this.basePath;
+ args.forEach(part => {
+ result = result + '/' + part;
+ });
+ return result;
+ }
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ /**
+ * Gets version details
+ * @returns Observable that should yield a String
+ */
+ getVersion(): Observable<string> {
+ const url = this.buildPath('version');
+ return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
+ // Extract the string here
+ map(res => res['data'])
+ );
+ }
+
+ /**
+ * Gets admission control policy.
+ * @returns Observable that should yield an ACAdmissionIntervalControl
+ */
+ getPolicy(): Observable<ACAdmissionIntervalControl> {
+ const url = this.buildPath(this.policyPath, this.acPolicyName);
+ return this.httpClient.get<ACAdmissionIntervalControl>(url);
+ }
+
+ /**
+ * Puts admission control policy.
+ * @param policy an instance of ACAdmissionIntervalControl
+ * @returns Observable that should yield a response code, no data
+ */
+ putPolicy(policy: ACAdmissionIntervalControl): Observable<any> {
+ const url = this.buildPath(this.policyPath, this.acPolicyName);
+ return this.httpClient.put<ACAdmissionIntervalControlAck>(url, policy, { observe: 'response' });
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.spec.ts b/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.spec.ts
new file mode 100644
index 0000000..c47dcd8
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.spec.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================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 { ANRXappService } from './anr-xapp.service';
+
+describe('ANRXappService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: ANRXappService = TestBed.get(ANRXappService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.ts b/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.ts
new file mode 100644
index 0000000..e06b708
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/anr-xapp/anr-xapp.service.ts
@@ -0,0 +1,136 @@
+/*-
+ * ========================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 { Injectable } from '@angular/core';
+import { HttpClient, HttpParams } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+import { ANRNeighborCellRelation, ANRNeighborCellRelationMod } from '../../interfaces/anr-xapp.types';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ANRXappService {
+
+ // Trailing slashes are confusing so omit them here
+ private basePath = 'api/xapp/anr';
+ private ncrtPath = 'ncrt';
+ private servingPath = 'servingcells';
+ private neighborPath = 'neighborcells';
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ private buildPath(...args: any[]) {
+ let result = this.basePath;
+ args.forEach(part => {
+ result = result + '/' + part;
+ });
+ return result;
+ }
+
+ /**
+ * Gets ANR xApp client version details
+ * @returns Observable that should yield a String
+ */
+ getVersion(): Observable<string> {
+ const url = this.buildPath('version');
+ return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
+ // Extract the string here
+ map(res => res['data'])
+ );
+ }
+
+ /**
+ * Performs a liveness probe
+ * @returns Observable that should yield a response code (no data)
+ */
+ getHealthAlive(): Observable<any> {
+ const url = this.buildPath('health/alive');
+ return this.httpClient.get(url, { observe: 'response' });
+ }
+
+ /**
+ * Performs a readiness probe
+ * @returns Observable that should yield a response code (no data)
+ */
+ getHealthReady(): Observable<any> {
+ const url = this.buildPath('health/ready');
+ return this.httpClient.get(url, { observe: 'response' });
+ }
+
+/**
+ * Gets array of gNodeB IDs
+ * @returns Observable that should yield a string array
+ */
+ getgNodeBs(): Observable<string[]> {
+ const url = this.buildPath('gnodebs');
+ return this.httpClient.get<string[]>(url).pipe(
+ // Extract the array of IDs here
+ map(res => res['gNodeBIds'])
+ );
+ }
+
+ /**
+ * Gets the neighbor cell relation table for all gNodeBs or based on query parameters
+ * @param ggnbId Optional parameter for the gNB ID
+ * @param servingCellNrcgi Serving cell NRCGI
+ * @param neighborCellNrpci Neighbor cell NRPCI
+ * @returns Observable of ANR neighbor cell relation array
+ */
+ getNcrtInfo(ggnodeb: string = '', servingCellNrcgi: string = '', neighborCellNrpci: string = ''): Observable<ANRNeighborCellRelation[]> {
+ const url = this.buildPath(this.ncrtPath);
+ return this.httpClient.get<ANRNeighborCellRelation[]>(url, {
+ params: new HttpParams()
+ .set('ggnodeb', ggnodeb)
+ .set('servingCellNrcgi', servingCellNrcgi)
+ .set('neighborCellNrpci', neighborCellNrpci)
+ }).pipe(
+ // Extract the array of relations here
+ map(res => res['ncrtRelations'])
+ );
+ }
+
+ /**
+ * Modify neighbor cell relation based on Serving Cell NRCGI and Neighbor Cell NRPCI
+ * @param servingCellNrcgi Serving cell NRCGI
+ * @param neighborCellNrpci Neighbor cell NRPCI
+ * @param mod Values to store in the specified relation
+ * @returns Observable that yields a response code only, no data
+ */
+ modifyNcr(servingCellNrcgi: string, neighborCellNrpci: string, mod: ANRNeighborCellRelationMod): Observable<Object> {
+ const url = this.buildPath(this.ncrtPath, this.servingPath, servingCellNrcgi, this.neighborPath, neighborCellNrpci);
+ return this.httpClient.put(url, mod, { observe: 'response' });
+ }
+
+ /**
+ * Deletes neighbor cell relation based on Serving Cell NRCGI and Neighbor Cell NRPCI
+ * @param servingCellNrcgi Serving cell NRCGI
+ * @param neighborCellNrpci Neighbor cell NRPCI
+ * @returns Observable that yields a response code only, no data
+ */
+ deleteNcr(servingCellNrcgi: string, neighborCellNrpci: string): Observable<Object> {
+ const url = this.buildPath(this.ncrtPath, this.servingPath, servingCellNrcgi, this.neighborPath, neighborCellNrpci);
+ return this.httpClient.delete(url, { observe: 'response' });
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.spec.ts b/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.spec.ts
new file mode 100644
index 0000000..fcc2aaa
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.spec.ts
@@ -0,0 +1,31 @@
+/*-
+ * ========================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 { AppMgrService } from './app-mgr.service';
+
+describe('AppMgrService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: AppMgrService = TestBed.get(AppMgrService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.ts b/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.ts
new file mode 100644
index 0000000..afe7a65
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/app-mgr/app-mgr.service.ts
@@ -0,0 +1,61 @@
+/*-
+ * ========================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 { Injectable } from '@angular/core';
+import { HttpClient, HttpResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { XMXappInfo, XMDeployableApp, XMDeployedApp } from '../../interfaces/app-mgr.types';
+
+@Injectable()
+export class AppMgrService {
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ private basePath = 'api/appmgr';
+
+ getDeployable(): Observable<XMDeployableApp[]> {
+ return this.httpClient.get<XMDeployableApp[]>(this.basePath + '/xapps/list');
+ }
+
+ getDeployed(): Observable<XMDeployedApp[]> {
+ return this.httpClient.get<XMDeployedApp[]>(this.basePath + '/xapps');
+ }
+
+ deployXapp(name: string): Observable<HttpResponse<Object>> {
+ const xappInfo: XMXappInfo = { name: name };
+ return this.httpClient.post((this.basePath + '/xapps'), xappInfo, { observe: 'response' });
+ }
+
+ undeployXapp(name: string): Observable<HttpResponse<Object>> {
+ return this.httpClient.delete((this.basePath + '/xapps'+ '/' + name), { observe: 'response' });
+ }
+
+ getConfig(): Observable<any[]>{
+ return this.httpClient.get<any[]>("/assets/mockdata/config.json");
+ //return this.httpClient.get<any[]>((this.basePath + '/config'));
+ }
+
+ putConfig(config: any): Observable<HttpResponse<Object>> {
+ return this.httpClient.put((this.basePath + '/config' ), config, { observe: 'response' });
+ }
+
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.spec.ts b/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.spec.ts
new file mode 100644
index 0000000..9d007ad
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.spec.ts
@@ -0,0 +1,31 @@
+/*-
+ * ========================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 { CaasIngressService } from './caas-ingress.service';
+
+describe('CaasIngressService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: CaasIngressService = TestBed.get(CaasIngressService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.ts b/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.ts
new file mode 100644
index 0000000..359a08f
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.ts
@@ -0,0 +1,58 @@
+/*-
+ * ========================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 { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { V1PodList } from '@kubernetes/client-node';
+import { Observable } from 'rxjs';
+
+/**
+* Services for calling the Dashboard's caas-ingress endpoints to get Kubernetes details.
+*/
+@Injectable({
+ providedIn: 'root'
+})
+export class CaasIngressService {
+
+ private basePath = 'api/caas-ingress';
+ private podsPath = 'pods';
+
+ private buildPath(...args: any[]) {
+ let result = this.basePath;
+ args.forEach(part => {
+ result = result + '/' + part;
+ });
+ return result;
+ }
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ /**
+ * Gets list of pods
+ * @returns Observable that should yield a V1PodList
+ */
+ getPodList(cluster: string, namespace: string): Observable<V1PodList> {
+ const url = this.buildPath('pods', 'cluster', cluster, 'namespace', namespace);
+ return this.httpClient.get<V1PodList>(url);
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.spec.ts b/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.spec.ts
new file mode 100644
index 0000000..606adb3
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.spec.ts
@@ -0,0 +1,31 @@
+/*-
+ * ========================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 { DashboardService } from './dashboard.service';
+
+describe('DashboardService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: DashboardService = TestBed.get(DashboardService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.ts b/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.ts
new file mode 100644
index 0000000..a9f2df6
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/dashboard/dashboard.service.ts
@@ -0,0 +1,64 @@
+/*-
+ * ========================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 { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { DashboardSuccessTransport, EcompUser } from '../../interfaces/dashboard.types';
+
+@Injectable({
+ providedIn: 'root'
+})
+
+/**
+ * Services to query the dashboard's admin endpoints.
+ */
+export class DashboardService {
+
+ private basePath = 'api/admin/';
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ /**
+ * Checks app health
+ * @returns Observable that should yield a DashboardSuccessTransport
+ */
+ getHealth(): Observable<DashboardSuccessTransport> {
+ return this.httpClient.get<DashboardSuccessTransport>(this.basePath + 'health');
+ }
+
+ /**
+ * Gets Dashboard version details
+ * @returns Observable that should yield a DashboardSuccessTransport object
+ */
+ getVersion(): Observable<DashboardSuccessTransport> {
+ return this.httpClient.get<DashboardSuccessTransport>(this.basePath + 'version');
+ }
+
+ /**
+ * Gets Dashboard users
+ * @returns Observable that should yield a EcompUser array
+ */
+ getUsers(): Observable<EcompUser[]> {
+ return this.httpClient.get<EcompUser[]>(this.basePath + 'user');
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.spec.ts b/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.spec.ts
new file mode 100644
index 0000000..b9e009c
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.spec.ts
@@ -0,0 +1,33 @@
+/*-
+ * ========================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 { E2ManagerService } from './e2-mgr.service';
+
+describe('CatalogService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: E2ManagerService
+ = TestBed.get(E2ManagerService
+ );
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.ts b/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.ts
new file mode 100644
index 0000000..b3388cf
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/e2-mgr/e2-mgr.service.ts
@@ -0,0 +1,83 @@
+/*-
+ * ========================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 { Injectable } from '@angular/core';
+import { HttpClient, HttpResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { E2RanDetails, E2SetupRequest } from '../../interfaces/e2-mgr.types';
+import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+
+@Injectable({
+ providedIn: 'root'
+})
+
+export class E2ManagerService {
+
+ private basePath = 'api/e2mgr/nodeb/';
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ /**
+ * Gets E2 client version details
+ * @returns Observable that should yield a String
+ */
+ getVersion(): Observable<string> {
+ const url = this.basePath + 'version';
+ return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
+ // Extract the string here
+ map(res => res['data'])
+ );
+ }
+
+ /**
+ * Gets RAN details
+ * @returns Observable that should yield an array of objects
+ */
+ getRan(): Observable<Array<E2RanDetails>> {
+ return this.httpClient.get<Array<E2RanDetails>>(this.basePath + 'ran');
+ }
+
+ /**
+ * Sends a request to setup an ENDC/gNodeB connection
+ * @returns Observable. On success there is no data, only a code.
+ */
+ endcSetup(req: E2SetupRequest): Observable<HttpResponse<Object>> {
+ return this.httpClient.post(this.basePath + 'endc-setup', req, { observe: 'response' });
+ }
+
+ /**
+ * Sends a request to setup an X2/eNodeB connection
+ * @returns Observable. On success there is no data, only a code.
+ */
+ x2Setup(req: E2SetupRequest): Observable<HttpResponse<Object>> {
+ return this.httpClient.post(this.basePath + 'x2-setup', req, { observe: 'response' });
+ }
+
+ /**
+ * Sends a request to drop all RAN connections
+ * @returns Observable with body.
+ */
+ nodebPut(): Observable<any> {
+ return this.httpClient.put((this.basePath + 'shutdown'), { observe: 'body' });
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/policy/policy.service.spec.ts b/dashboard/webapp-frontend/src/app/services/policy/policy.service.spec.ts
new file mode 100644
index 0000000..de1f4e6
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/policy/policy.service.spec.ts
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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 { TestBed } from '@angular/core/testing';
+
+import { PolicyService } from './policy.service';
+
+describe('PolicyService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: PolicyService = TestBed.get(PolicyService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/policy/policy.service.ts b/dashboard/webapp-frontend/src/app/services/policy/policy.service.ts
new file mode 100644
index 0000000..1c87081
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/policy/policy.service.ts
@@ -0,0 +1,104 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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 { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { PolicyType, PolicyInstance, PolicyInstanceAck } from '../../interfaces/policy.types';
+import { DashboardSuccessTransport } from '../../interfaces/dashboard.types';
+
+/**
+ * Services for calling the policy endpoints.
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class PolicyService {
+
+ private basePath = 'api/policy';
+ private policyTypePath = 'policytypes';
+ private policyPath = 'policies';
+
+ private buildPath(...args: any[]) {
+ let result = this.basePath;
+ args.forEach(part => {
+ result = result + '/' + part;
+ });
+ return result;
+ }
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ /**
+ * Gets version details
+ * @returns Observable that should yield a String
+ */
+ getVersion(): Observable<string> {
+ const url = this.buildPath('version');
+ return this.httpClient.get<DashboardSuccessTransport>(url).pipe(
+ // Extract the string here
+ map(res => res['data'])
+ );
+ }
+
+ getPolicyTypes(): Observable<PolicyType[]> {
+ const url = this.buildPath(this.policyTypePath);
+ return this.httpClient.get<PolicyType[]>(url);
+ }
+
+ getPolicyInstances(policyTypeId: number): Observable<PolicyInstance[]> {
+ const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath);
+ return this.httpClient.get<PolicyInstance[]>(url);
+ }
+
+ /**
+ * Gets policy parameters.
+ * @returns Observable that should yield a policy instance
+ */
+ getPolicy(policyTypeId: number, policyInstanceId: string): Observable<any> {
+ const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath, policyInstanceId);
+ return this.httpClient.get<any>(url);
+ }
+
+ /**
+ * Creates or replaces policy instance.
+ * @param policyTypeId ID of the policy type that the instance will have
+ * @param policyInstanceId ID of the instance
+ * @param policyJson Json with the policy content
+ * @returns Observable that should yield a response code, no data
+ */
+ putPolicy(policyTypeId: number, policyInstanceId: string, policyJson: string): Observable<any> {
+ const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath, policyInstanceId);
+ return this.httpClient.put<PolicyInstanceAck>(url, policyJson, { observe: 'response' });
+ }
+
+ /**
+ * Deletes a policy instance.
+ * @param policyTypeId
+ * @returns Observable that should yield a response code, no data
+ */
+ deletePolicy(policyTypeId: number, policyInstanceId: string): Observable<any> {
+ const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath, policyInstanceId);
+ return this.httpClient.delete(url, { observe: 'response' });
+ }
+}
diff --git a/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts
new file mode 100644
index 0000000..e7a115e
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================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 { ConfirmDialogService } from './confirm-dialog.service';
+
+describe('ConfirmDialogService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: ConfirmDialogService = TestBed.get(ConfirmDialogService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts b/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts
new file mode 100644
index 0000000..297e673
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts
@@ -0,0 +1,55 @@
+/*-
+ * ========================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 { Injectable } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { ConfirmDialogComponent } from './../../ui/confirm-dialog/confirm-dialog.component';
+import { UiService } from './ui.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ConfirmDialogService {
+
+ darkMode: boolean;
+ panelClass: string = "";
+
+ constructor(private dialog: MatDialog,
+ public ui: UiService) { }
+
+ openConfirmDialog(msg: string) {
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ if (this.darkMode) {
+ this.panelClass = "dark-theme";
+ } else {
+ this.panelClass = "";
+ }
+ return this.dialog.open(ConfirmDialogComponent, {
+ panelClass: this.panelClass,
+ width: '480px',
+ position: { top: '100px' },
+ data: {
+ message: msg
+ }
+ });
+ }
+}
diff --git a/dashboard/webapp-frontend/src/app/services/ui/error-dialog.service.ts b/dashboard/webapp-frontend/src/app/services/ui/error-dialog.service.ts
new file mode 100644
index 0000000..a5a12f4
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ui/error-dialog.service.ts
@@ -0,0 +1,53 @@
+/*-
+ * ========================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 { ErrorDialogComponent } from '../../ui/error-dialog/error-dialog.component';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatDialog } from '@angular/material/dialog';
+import { Injectable } from '@angular/core';
+import { UiService } from './ui.service';
+
+@Injectable()
+export class ErrorDialogService {
+
+ darkMode: boolean;
+ panelClass: string = "";
+ public errorMessage: string = '';
+
+ constructor(private dialog: MatDialog,
+ public ui: UiService) { }
+
+ public displayError(error: string) {
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ if (this.darkMode) {
+ this.panelClass = "dark-theme";
+ } else {
+ this.panelClass = "";
+ }
+ return this.dialog.open(ErrorDialogComponent, {
+ panelClass: this.panelClass,
+ width: '400px',
+ position: { top: '100px' },
+ disableClose: true,
+ data: { 'errorMessage': error }
+ });
+ }
+}
diff --git a/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.spec.ts
new file mode 100644
index 0000000..a73be7c
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.spec.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================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 { LoadingDialogService } from './loading-dialog.service';
+
+describe('LoadingDialogService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: LoadingDialogService = TestBed.get(LoadingDialogService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.ts b/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.ts
new file mode 100644
index 0000000..bf0830a
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ui/loading-dialog.service.ts
@@ -0,0 +1,65 @@
+/*-
+ * ========================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 { Injectable } from '@angular/core';
+import { MatDialog, MatDialogRef } from '@angular/material/dialog';
+import { LoadingDialogComponent } from './../../ui/loading-dialog/loading-dialog.component';
+import { UiService } from './ui.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class LoadingDialogService {
+
+ darkMode: boolean;
+ panelClass: string = "";
+
+ constructor(private dialog: MatDialog,
+ public ui: UiService) { }
+
+ private loadingDialogRef: MatDialogRef<LoadingDialogComponent>;
+
+ startLoading(msg: string) {
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ if (this.darkMode) {
+ this.panelClass = "dark-theme";
+ } else {
+ this.panelClass = "";
+ }
+ this.loadingDialogRef = this.dialog.open(LoadingDialogComponent, {
+ panelClass: this.panelClass,
+ disableClose: true,
+ width: '480px',
+ position: { top: '100px' },
+ data: {
+ message: msg
+ }
+ });
+ }
+
+ stopLoading() {
+ this.loadingDialogRef.close()
+ }
+
+}
+
+
diff --git a/dashboard/webapp-frontend/src/app/services/ui/notification.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ui/notification.service.spec.ts
new file mode 100644
index 0000000..9704720
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ui/notification.service.spec.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================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 { NotificationService } from './notification.service';
+
+describe('NotificationService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: NotificationService = TestBed.get(NotificationService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ui/notification.service.ts b/dashboard/webapp-frontend/src/app/services/ui/notification.service.ts
new file mode 100644
index 0000000..c7fe03f
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ui/notification.service.ts
@@ -0,0 +1,58 @@
+/*-
+ * ========================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 { Injectable } from '@angular/core';
+import { ToastrService } from 'ngx-toastr';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class NotificationService {
+
+ constructor(public toastr: ToastrService) { }
+
+ successConfig = {
+ timeOut: 10000,
+ closeButton: true
+ };
+
+ warningConfig = {
+ disableTimeOut: true,
+ closeButton: true
+ };
+
+ errorConfig = {
+ disableTimeOut: true,
+ closeButton: true
+ };
+
+ success(msg: string) {
+ this.toastr.success(msg, '', this.successConfig);
+ }
+
+ warn(msg: string) {
+ this.toastr.warning(msg, '', this.warningConfig);
+ }
+
+ error(msg: string) {
+ this.toastr.error(msg, '', this.errorConfig);
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/services/ui/ui.service.spec.ts b/dashboard/webapp-frontend/src/app/services/ui/ui.service.spec.ts
new file mode 100644
index 0000000..3df0e9c
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ui/ui.service.spec.ts
@@ -0,0 +1,34 @@
+/*-
+ * ========================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 {inject, TestBed} from '@angular/core/testing';
+
+import {UiService} from './ui.service';
+
+describe('UiService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [UiService]
+ });
+ });
+
+ it('should be created', inject([UiService], (service: UiService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/dashboard/webapp-frontend/src/app/services/ui/ui.service.ts b/dashboard/webapp-frontend/src/app/services/ui/ui.service.ts
new file mode 100644
index 0000000..1e2376a
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/services/ui/ui.service.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================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 {Injectable} from '@angular/core';
+import {BehaviorSubject} from 'rxjs';
+
+@Injectable()
+export class UiService {
+
+ darkModeState: BehaviorSubject<boolean>;
+
+ constructor() {
+ // TODO: if the user is signed in get the default value from Firebase
+ this.darkModeState = new BehaviorSubject<boolean>(false);
+ }
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html b/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html
new file mode 100644
index 0000000..7e86ba0
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html
@@ -0,0 +1,29 @@
+<!--
+ ========================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===================================
+ -->
+
+ <div mat-dialog-title>
+ </div>
+ <div mat-dialog-content>
+ {{data.message}}
+ </div>
+ <div mat-dialog-actions class="modal-footer justify-content-center">
+ <button mat-button class="mat-raised-button" [mat-dialog-close]="false">Cancel</button>
+ <button mat-button class="mat-raised-button mat-primary" [mat-dialog-close]="true">OK</button>
+ </div>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts b/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts
new file mode 100644
index 0000000..1edce02
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts
@@ -0,0 +1,45 @@
+/*-
+ * ========================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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ConfirmDialogComponent } from './confirm-dialog.component';
+
+describe('ConfirmDialogComponent', () => {
+ let component: ConfirmDialogComponent;
+ let fixture: ComponentFixture<ConfirmDialogComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ ConfirmDialogComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ConfirmDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts b/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts
new file mode 100644
index 0000000..3d99530
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts
@@ -0,0 +1,39 @@
+/*-
+ * ========================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 { Component, OnInit, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+@Component({
+ selector: 'rd-confirm-dialog',
+ templateUrl: './confirm-dialog.component.html',
+})
+export class ConfirmDialogComponent implements OnInit {
+
+ constructor(@Inject(MAT_DIALOG_DATA) public data,
+ public dialogRef: MatDialogRef<ConfirmDialogComponent>) { }
+
+ ngOnInit() {
+ }
+
+ closeDialog() {
+ this.dialogRef.close(false);
+ }
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.html b/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.html
new file mode 100644
index 0000000..8516b94
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.html
@@ -0,0 +1,27 @@
+<!--
+ ========================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===================================
+ -->
+
+ <div mat-dialog-content style="overflow-y: hidden;overflow:auto;">
+ <div class="error_message_text">{{data.errorMessage}}</div>
+ </div>
+ <div mat-dialog-actions class="justify-content-center">
+ <button mat-button class="mat-raised-button mat-primary" (click)="closeDialog()">Close</button>
+ </div>
+
diff --git a/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.scss b/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.scss
new file mode 100644
index 0000000..23de19e
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.scss
@@ -0,0 +1,25 @@
+/*-
+ * ========================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===================================
+ */
+
+ .error_message_text {
+ overflow-y: overlay;
+ max-height: 150px;
+ }
+
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.ts b/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.ts
new file mode 100644
index 0000000..1636c0d
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.ts
@@ -0,0 +1,41 @@
+/*-
+ * ========================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 { Component, OnInit, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+export interface ErrorData {
+ errorMessage: string;
+}
+
+@Component({
+ selector: 'rd-error-dialog',
+ templateUrl: './error-dialog.component.html',
+ styleUrls: ['./error-dialog.component.scss']
+})
+
+export class ErrorDialogComponent {
+
+ constructor(private dialogRef: MatDialogRef<ErrorDialogComponent>,
+ @Inject(MAT_DIALOG_DATA) public data: ErrorData) { }
+
+ public closeDialog = () => {
+ this.dialogRef.close();
+ }
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.html b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.html
new file mode 100644
index 0000000..75d6e59
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.html
@@ -0,0 +1,27 @@
+<!--
+ ========================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===================================
+ -->
+
+
+ <div mat-dialog-content>
+ {{data.message}}
+ </div>
+ <mat-spinner diameter=70></mat-spinner>
+
+
diff --git a/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.scss b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.scss
new file mode 100644
index 0000000..fb66930
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.scss
@@ -0,0 +1,23 @@
+/*-
+ * ========================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===================================
+ */
+
+mat-spinner {
+ margin: 10px
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.spec.ts b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.spec.ts
new file mode 100644
index 0000000..40fa832
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.spec.ts
@@ -0,0 +1,45 @@
+/*-
+ * ========================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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoadingDialogComponent } from './loading-dialog.component';
+
+describe('LoadingDialogComponent', () => {
+ let component: LoadingDialogComponent;
+ let fixture: ComponentFixture<LoadingDialogComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ LoadingDialogComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LoadingDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.ts b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.ts
new file mode 100644
index 0000000..c78ae4e
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/loading-dialog/loading-dialog.component.ts
@@ -0,0 +1,36 @@
+/*-
+ * ========================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 { Component, OnInit, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
+
+@Component({
+ selector: 'rd-loading-dialog',
+ templateUrl: './loading-dialog.component.html',
+ styleUrls: ['./loading-dialog.component.scss']
+})
+export class LoadingDialogComponent implements OnInit {
+
+ constructor(@Inject(MAT_DIALOG_DATA) public data) { }
+
+ ngOnInit() {
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.html b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.html
new file mode 100644
index 0000000..197561f
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.html
@@ -0,0 +1,70 @@
+<!--
+ ========================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===================================
+ -->
+<button type="button" mdbBtn color="default" rounded="true" data-toggle="modal" data-target="#basicExample"
+ (click)="frame.show()" mdbWavesEffect>Deploy</button>
+
+<div mdbModal #frame="mdbModal" class="modal fade left" id="frameModalTop" tabindex="-1" role="dialog"
+ aria-labelledby="myModalLabel" aria-hidden="true" (opened)="onOpened($event)">
+ <div class="modal-dialog modal-notify modal-info modal-side modal-top-left" role="document">
+ <!--Content-->
+ <div class="modal-content">
+ <!--Header-->
+ <div class="modal-header">
+ <p class="heading lead">xApp Info</p>
+
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="frame.hide()">
+ <span aria-hidden="true" class="white-text">×</span>
+ </button>
+ </div>
+
+ <!--Body-->
+ <div class="modal-body">
+
+ <img src="../../../assets/intelligence.png" alt=""
+ class="img-fluid">
+
+ <div class="text-center">
+ <p>xApp Manager Params</p>
+ <div class="md-form">
+ <textarea type="text" id="form8" class="md-textarea form-control" rows="1" mdbInput
+ [formControl]="contactFormModalHelm"></textarea>
+ <label data-error="wrong" data-success="right" for="form8">Delay</label>
+ </div>
+ <div class="md-form">
+ <textarea type="text" id="form8" class="md-textarea form-control" rows="1" mdbInput
+ [formControl]="contactFormModalHelm"></textarea>
+ <label data-error="wrong" data-success="right" for="form8">Load</label>
+ </div>
+ </div>
+ </div>
+
+ <!--Footer-->
+ <div class="modal-footer justify-content-center">
+ <a type="button" mdbBtn color="primary" class="waves-effect" mdbWavesEffect>
+ <mat-icon style="vertical-align: -21%;">launch</mat-icon> Deploy
+ </a>
+ <a type="button" mdbBtn color="primary" outline="true" class="waves-effect" mdbWavesEffect (click)="frame.hide()"
+ data-dismiss="modal">
+ <mat-icon style="vertical-align: -21%; size: 1em">close</mat-icon> Cancel</a>
+ </div>
+ </div>
+ <!--/.Content-->
+ </div>
+</div>
diff --git a/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.scss b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.scss
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.scss
diff --git a/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.spec.ts b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.spec.ts
new file mode 100644
index 0000000..0e4cf9d
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ModalEventComponent } from './modal-event.component';
+
+describe('ModalEventComponent', () => {
+ let component: ModalEventComponent;
+ let fixture: ComponentFixture<ModalEventComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ ModalEventComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ModalEventComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.ts b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.ts
new file mode 100644
index 0000000..18f5163
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/modal-event/modal-event.component.ts
@@ -0,0 +1,55 @@
+/*-
+ * ========================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 { Component, Input, OnInit , Output, EventEmitter } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+
+@Component({
+ selector: 'rd-modal-event',
+ templateUrl: './modal-event.component.html',
+ styleUrls: ['./modal-event.component.scss']
+})
+export class ModalEventComponent implements OnInit {
+
+ public renderValue;
+
+ @Input() value;
+ @Input() rowData: any;
+ @Output() save: EventEmitter<any> = new EventEmitter();
+ contactFormModalHelm = new FormControl('', Validators.required);
+ onOpened(event: any) {
+ console.log(event);
+ }
+
+
+ constructor() { }
+
+ ngOnInit() {
+ this.renderValue = this.value;
+ }
+
+ example() {
+ alert(this.renderValue);
+ }
+
+ onDeployxApp() {
+ this.save.emit(this.rowData);
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.html b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.html
new file mode 100644
index 0000000..2c82628
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.html
@@ -0,0 +1,31 @@
+<!--
+ ========================LICENSE_START=================================
+ O-RAN-SC
+ %%
+ Copyright (C) 2019 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 class="policy__card" routerLink="/policy" [ngClass]="{'add__card-dark': darkMode}">
+ <div class="header__container">
+ <span class="card__title">Policy Control</span><br><br><br>
+ <div style="color: black; size: 1em; text-align: center">
+ <mat-icon>build</mat-icon>Config</div>
+
+
+ </div>
+ <div class="body__container">
+ <img src="../../../assets/policy.png" width="250px"/>
+ </div>
+</div>
diff --git a/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.scss b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.scss
new file mode 100644
index 0000000..1b3c349
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.scss
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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===================================
+ */
+.policy__card {
+ background-color: #ffffff;
+ box-shadow: 0 0 2rem rgba(0, 0, 255, 0.1);
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 1fr 1fr;
+ padding: 2rem;
+ margin: 2rem;
+ width: 19rem;
+ height: 30rem;
+ justify-items: center;
+ cursor: pointer;
+ border-radius: 1.75rem;
+ animation: fadein 1.25s ease-in-out 0ms 1;
+ color: #443282;
+}
+
+.add__card-dark {
+ background: linear-gradient(to bottom, #711B86, #00057A);
+ color: white;
+}
+
+.card__title {
+ text-transform: uppercase;
+ letter-spacing: 0.1rem;
+}
+
+.body__container {
+ align-self: end;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-flow: column;
+}
+
+.add__icon {
+ width: 10rem;
+ margin-bottom: 1.15rem;
+}
diff --git a/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.spec.ts b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.spec.ts
new file mode 100644
index 0000000..eb678a9
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {PolicyCardComponent} from './policy-card.component';
+
+describe('PolicyCardComponent', () => {
+ let component: PolicyCardComponent;
+ let fixture: ComponentFixture<PolicyCardComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [PolicyCardComponent]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PolicyCardComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.ts b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.ts
new file mode 100644
index 0000000..174af1d
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/ui/policy-card/policy-card.component.ts
@@ -0,0 +1,46 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 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, OnDestroy, OnInit} from '@angular/core';
+import {Router} from '@angular/router';
+import {UiService} from '../../services/ui/ui.service';
+
+@Component({
+ selector: 'rd-policy-card',
+ templateUrl: './policy-card.component.html',
+ styleUrls: ['./policy-card.component.scss']
+})
+export class PolicyCardComponent implements OnInit, OnDestroy {
+ darkMode: boolean;
+
+ constructor(public router: Router, public ui: UiService) { }
+
+ ngOnInit() {
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ }
+
+ ngOnDestroy() { }
+
+ openDetails() {
+ this.router.navigateByUrl('../../policy');
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.html b/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.html
new file mode 100644
index 0000000..ef29a83
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.html
@@ -0,0 +1,44 @@
+<!--
+ ========================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===================================
+-->
+<div mat-dialog-title>
+ Add Dashboard User
+</div>
+
+<form [formGroup]="addUserDialogForm" novalidate autocomplete="off">
+ <div mat-dialog-content>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="First Name" formControlName="firstName">
+ </mat-form-field>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="Last Name" formControlName="lastName">
+ </mat-form-field>
+ </div>
+ <div name="status">
+ <label id="request-type-radio-group-label">Status:</label>
+ <mat-radio-group aria-label="status" formControlName="status">
+ <mat-radio-button value="Active">Active</mat-radio-button>
+ <mat-radio-button value="Inactive">Inactive</mat-radio-button>
+ </mat-radio-group>
+ </div>
+ <div mat-dialog-actions class="modal-footer justify-content-center">
+ <button class="mat-raised-button" (click)="onCancel()">Cancel</button>
+ <button class="mat-raised-button mat-primary" [disabled]="!addUserDialogForm.valid" (click)="addUser(addUserDialogForm.value)" >Add</button>
+ </div>
+</form>
diff --git a/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.scss b/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.scss
new file mode 100644
index 0000000..9717962
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.scss
@@ -0,0 +1,23 @@
+/*-
+ * ========================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===================================
+ */
+
+.input-display-block {
+ display: block;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.ts b/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.ts
new file mode 100644
index 0000000..faca105
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/add-dashboard-user-dialog/add-dashboard-user-dialog.component.ts
@@ -0,0 +1,61 @@
+/*-
+ * ========================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 { Component, OnInit } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { MatDialogRef } from '@angular/material/dialog';
+import { DashboardService } from '../../services/dashboard/dashboard.service';
+import { ErrorDialogService } from '../../services/ui/error-dialog.service';
+
+@Component({
+ selector: 'add-dashboard-user-dialog',
+ templateUrl: './add-dashboard-user-dialog.component.html',
+ styleUrls: ['./add-dashboard-user-dialog.component.scss']
+})
+export class AddDashboardUserDialogComponent implements OnInit {
+
+ public addUserDialogForm: FormGroup;
+
+ constructor(
+ private dialogRef: MatDialogRef<AddDashboardUserDialogComponent>,
+ private dashSvc: DashboardService,
+ private errorService: ErrorDialogService) { }
+
+ ngOnInit() {
+ this.addUserDialogForm = new FormGroup({
+ firstName: new FormControl('', [Validators.required]),
+ lastName: new FormControl('', [Validators.required]),
+ status: new FormControl('', [Validators.required])
+ });
+ }
+
+ onCancel() {
+ this.dialogRef.close(false);
+ }
+
+ public addUser = (FormValue) => {
+ if (this.addUserDialogForm.valid) {
+ // send the request to backend when it's ready
+ const aboutError = 'Not implemented yet';
+ this.errorService.displayError(aboutError);
+ }
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.html b/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.html
new file mode 100644
index 0000000..f64d9df
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.html
@@ -0,0 +1,44 @@
+<!--
+ ========================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===================================
+-->
+<div mat-dialog-title>
+ Edit Dashboard User
+</div>
+
+<form [formGroup]="editUserDialogForm" novalidate autocomplete="off">
+ <div mat-dialog-content>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="First Name" formControlName="firstName" >
+ </mat-form-field>
+ <mat-form-field class="input-display-block">
+ <input matInput type="text" placeholder="Last Name" formControlName="lastName" >
+ </mat-form-field>
+ </div>
+ <div name="status">
+ <label id="request-type-radio-group-label">Status:</label>
+ <mat-radio-group aria-label="status" formControlName="status">
+ <mat-radio-button value="Active">Active</mat-radio-button>
+ <mat-radio-button value="Inactive">Inactive</mat-radio-button>
+ </mat-radio-group>
+ </div>
+ <div mat-dialog-actions class="modal-footer justify-content-center">
+ <button class="mat-raised-button" (click)="onCancel()">Cancel</button>
+ <button class="mat-raised-button mat-primary" [disabled]="!editUserDialogForm.valid" (click)="editUser(editUserDialogForm.value)">Update</button>
+ </div>
+</form>
diff --git a/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.scss b/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.scss
new file mode 100644
index 0000000..9717962
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.scss
@@ -0,0 +1,23 @@
+/*-
+ * ========================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===================================
+ */
+
+.input-display-block {
+ display: block;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.ts b/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.ts
new file mode 100644
index 0000000..aef8860
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/edit-dashboard-user-dialog/edit-dashboard-user-dialog.component.ts
@@ -0,0 +1,63 @@
+/*-
+ * ========================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 { Component, Inject, OnInit } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { DashboardService } from '../../services/dashboard/dashboard.service';
+import { ErrorDialogService } from '../../services/ui/error-dialog.service';
+
+
+@Component({
+ selector: 'rd-edit-app-dashboard-user-dialog',
+ templateUrl: './edit-dashboard-user-dialog.component.html',
+ styleUrls: ['./edit-dashboard-user-dialog.component.scss']
+})
+export class EditDashboardUserDialogComponent implements OnInit {
+
+ public editUserDialogForm: FormGroup;
+
+ constructor(
+ @Inject(MAT_DIALOG_DATA) public data,
+ private dialogRef: MatDialogRef<EditDashboardUserDialogComponent>,
+ private dashSvc: DashboardService,
+ private errorService: ErrorDialogService) { }
+
+ ngOnInit() {
+ this.editUserDialogForm = new FormGroup({
+ firstName: new FormControl(this.data.firstName , [Validators.required]),
+ lastName: new FormControl(this.data.lastName, [Validators.required]),
+ status: new FormControl(this.data.status, [Validators.required])
+ });
+ }
+
+ onCancel() {
+ this.dialogRef.close(false);
+ }
+
+ public editUser = (FormValue) => {
+ if (this.editUserDialogForm.valid) {
+ // send the request to backend when it's ready
+ const aboutError = 'Not implemented yet';
+ this.errorService.displayError(aboutError);
+ }
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/app/user/user.component.html b/dashboard/webapp-frontend/src/app/user/user.component.html
new file mode 100644
index 0000000..5bffc79
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/user.component.html
@@ -0,0 +1,76 @@
+<!--
+ ========================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===================================
+-->
+<div class="user__section">
+ <h3 class="rd-global-page-title">Users</h3>
+ <button mat-raised-button (click)="addUser()">Add User</button>
+ <div class="spinner-container" *ngIf="dataSource.loading$ | async">
+ <mat-spinner></mat-spinner>
+ </div>
+ <table mat-table [dataSource]="dataSource" matSort class="user-table mat-elevation-z8">
+
+ <ng-container matColumnDef="loginId">
+ <mat-header-cell *matHeaderCellDef mat-sort-header> Login ID </mat-header-cell>
+ <mat-cell *matCellDef="let element"> {{element.loginId}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="firstName">
+ <mat-header-cell *matHeaderCellDef mat-sort-header> First Name </mat-header-cell>
+ <mat-cell *matCellDef="let element"> {{element.firstName}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="lastName">
+ <mat-header-cell *matHeaderCellDef mat-sort-header> Last Name </mat-header-cell>
+ <mat-cell *matCellDef="let element"> {{element.lastName}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="active">
+ <mat-header-cell *matHeaderCellDef mat-sort-header> Active? </mat-header-cell>
+ <mat-cell *matCellDef="let element"> {{element.active}} </mat-cell>
+ </ng-container>
+
+ <ng-container matColumnDef="action">
+ <mat-header-cell *matHeaderCellDef> Action </mat-header-cell>
+ <mat-cell *matCellDef="let element">
+ <div class="user-button-row">
+ <button mat-icon-button (click)="editUser(element)">
+ <mat-icon matTooltip="Edit properties">edit</mat-icon>
+ </button>
+ <button mat-icon-button color="warn" (click)="deleteUser(element)">
+ <mat-icon matTooltip="Delete user">delete</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>
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/app/user/user.component.scss b/dashboard/webapp-frontend/src/app/user/user.component.scss
new file mode 100644
index 0000000..f3a5d89
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/user.component.scss
@@ -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===================================
+ */
+.user__section {
+}
+
+.spinner-container {
+ height: 360px;
+ width: 390px;
+ position: fixed;
+}
+
+.spinner-container mat-spinner {
+ margin: 130px auto 0 auto;
+}
+
+.user-table {
+ width: 99%;
+ min-height: 150px;
+ margin-top: 10px;
+ background-color: transparent;
+}
+
+.user-button-row button {
+ margin-right: 5px;
+}
+
+.display-none {
+ display: none;
+}
diff --git a/dashboard/webapp-frontend/src/app/user/user.component.spec.ts b/dashboard/webapp-frontend/src/app/user/user.component.spec.ts
new file mode 100644
index 0000000..d4d822e
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/user.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UserComponent } from './user.component';
+
+describe('UserComponent', () => {
+ let component: AdminComponent;
+ let fixture: ComponentFixture<UserComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [UserComponent]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(UserComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/dashboard/webapp-frontend/src/app/user/user.component.ts b/dashboard/webapp-frontend/src/app/user/user.component.ts
new file mode 100644
index 0000000..a3573c0f
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/user.component.ts
@@ -0,0 +1,97 @@
+/*-
+ * ========================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 { Component, OnInit, ViewChild } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { MatSort } from '@angular/material/sort';
+import { DashboardService } from '../services/dashboard/dashboard.service';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { EcompUser } from './../interfaces/dashboard.types';
+import { NotificationService } from './../services/ui/notification.service';
+import { UserDataSource } from './user.datasource';
+import { AddDashboardUserDialogComponent } from './add-dashboard-user-dialog/add-dashboard-user-dialog.component';
+import { EditDashboardUserDialogComponent } from './edit-dashboard-user-dialog/edit-dashboard-user-dialog.component';
+import { UiService } from '../services/ui/ui.service';
+
+@Component({
+ selector: 'rd-user',
+ templateUrl: './user.component.html',
+ styleUrls: ['./user.component.scss']
+})
+
+export class UserComponent implements OnInit {
+
+ darkMode: boolean;
+ panelClass: string = "";
+ displayedColumns: string[] = ['loginId', 'firstName', 'lastName', 'active', 'action'];
+ dataSource: UserDataSource;
+ @ViewChild(MatSort, {static: true}) sort: MatSort;
+
+ constructor(
+ private dashboardSvc: DashboardService,
+ private errorService: ErrorDialogService,
+ private notificationService: NotificationService,
+ public dialog: MatDialog,
+ public ui: UiService) { }
+
+ ngOnInit() {
+ this.dataSource = new UserDataSource(this.dashboardSvc, this.sort, this.notificationService);
+ this.dataSource.loadTable();
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ }
+
+ editUser(user: EcompUser) {
+ if (this.darkMode) {
+ this.panelClass = "dark-theme"
+ } else {
+ this.panelClass = "";
+ }
+ const dialogRef = this.dialog.open(EditDashboardUserDialogComponent, {
+ panelClass: this.panelClass,
+ width: '450px',
+ data: user
+ });
+ dialogRef.afterClosed().subscribe(result => {
+ this.dataSource.loadTable();
+ });
+ }
+
+ deleteUser() {
+ const aboutError = 'Not implemented (yet).';
+ this.errorService.displayError(aboutError);
+ }
+
+ addUser() {
+ if (this.darkMode) {
+ this.panelClass = "dark-theme"
+ } else {
+ this.panelClass = "";
+ }
+ const dialogRef = this.dialog.open(AddDashboardUserDialogComponent, {
+ panelClass: this.panelClass,
+ width: '450px'
+ });
+ dialogRef.afterClosed().subscribe(result => {
+ this.dataSource.loadTable();
+ });
+ }
+}
+
diff --git a/dashboard/webapp-frontend/src/app/user/user.datasource.ts b/dashboard/webapp-frontend/src/app/user/user.datasource.ts
new file mode 100644
index 0000000..6b7805b
--- /dev/null
+++ b/dashboard/webapp-frontend/src/app/user/user.datasource.ts
@@ -0,0 +1,102 @@
+/*-
+ * ========================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 { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material/sort';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { EcompUser } from '../interfaces/dashboard.types';
+import { DashboardService } from '../services/dashboard/dashboard.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class UserDataSource extends DataSource<EcompUser> {
+
+ private userSubject = new BehaviorSubject<EcompUser[]>([]);
+
+ private loadingSubject = new BehaviorSubject<boolean>(false);
+
+ public loading$ = this.loadingSubject.asObservable();
+
+ public rowCount = 1; // hide footer during intial load
+
+ constructor(private dashboardSvc: DashboardService,
+ private sort: MatSort,
+ private notificationService: NotificationService) {
+ super();
+ }
+
+ loadTable() {
+ this.loadingSubject.next(true);
+ this.dashboardSvc.getUsers()
+ .pipe(
+ catchError( (her: HttpErrorResponse) => {
+ console.log('UserDataSource failed: ' + her.message);
+ this.notificationService.error('Failed to get users: ' + her.message);
+ return of([]);
+ }),
+ finalize(() => this.loadingSubject.next(false))
+ )
+ .subscribe( (users: EcompUser[]) => {
+ this.rowCount = users.length;
+ this.userSubject.next(users);
+ });
+ }
+
+ connect(collectionViewer: CollectionViewer): Observable<EcompUser[]> {
+ const dataMutations = [
+ this.userSubject.asObservable(),
+ this.sort.sortChange
+ ];
+ return merge(...dataMutations).pipe(map(() => {
+ return this.getSortedData([...this.userSubject.getValue()]);
+ }));
+ }
+
+ disconnect(collectionViewer: CollectionViewer): void {
+ this.userSubject.complete();
+ this.loadingSubject.complete();
+ }
+
+ private getSortedData(data: EcompUser[]) {
+ if (!this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a: EcompUser, b: EcompUser) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'loginId': return this.compare(a.loginId, b.loginId, isAsc);
+ case 'firstName': return this.compare(a.firstName, b.firstName, isAsc);
+ case 'lastName': return this.compare(a.lastName, b.lastName, isAsc);
+ case 'active': return this.compare(a.active, b.active, isAsc);
+ default: return 0;
+ }
+ });
+ }
+
+ private compare(a: any, b: any, isAsc: boolean) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+ }
+
+}
diff --git a/dashboard/webapp-frontend/src/assets/ORANlogo.png b/dashboard/webapp-frontend/src/assets/ORANlogo.png
new file mode 100644
index 0000000..4c3dfb1
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/ORANlogo.png
Binary files differ
diff --git a/dashboard/webapp-frontend/src/assets/at_t.png b/dashboard/webapp-frontend/src/assets/at_t.png
new file mode 100644
index 0000000..3cced1d
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/at_t.png
Binary files differ
diff --git a/dashboard/webapp-frontend/src/assets/intelligence.png b/dashboard/webapp-frontend/src/assets/intelligence.png
new file mode 100644
index 0000000..c40693e
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/intelligence.png
Binary files differ
diff --git a/dashboard/webapp-frontend/src/assets/latency.png b/dashboard/webapp-frontend/src/assets/latency.png
new file mode 100644
index 0000000..874d11b
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/latency.png
Binary files differ
diff --git a/dashboard/webapp-frontend/src/assets/mockdata/config.json b/dashboard/webapp-frontend/src/assets/mockdata/config.json
new file mode 100644
index 0000000..255b3fe
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/mockdata/config.json
@@ -0,0 +1,872 @@
+[
+ {
+ "metadata": {
+ "name": "Automatic Neighbor Relation",
+ "configName": "anr-appconfig",
+ "namespace": "ricxapp"
+ },
+ "descriptor": {
+ "$id": "http://example.com/root.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "definitions": {},
+ "properties": {
+ "controls": {
+ "$id": "#/properties/controls",
+ "properties": {
+ "active": {
+ "$id": "#/properties/controls/properties/active",
+ "default": false,
+ "examples": [
+ true
+ ],
+ "title": "The Active Schema",
+ "type": "boolean"
+ },
+ "interfaceId": {
+ "$id": "#/properties/controls/properties/interfaceId",
+ "properties": {
+ "globalENBId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId",
+ "properties": {
+ "bits": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/bits",
+ "default": 0,
+ "examples": [
+ 28
+ ],
+ "title": "The Bits Schema",
+ "maximum": 1024,
+ "type": "integer"
+ },
+ "id": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/id",
+ "default": 0,
+ "examples": [
+ 202251
+ ],
+ "title": "The Id Schema",
+ "type": "integer"
+ },
+ "plmnid": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/plmnid",
+ "default": "",
+ "examples": [
+ "310150"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Plmnid Schema",
+ "type": "string"
+ }
+ },
+ "required": [
+ "plmnid",
+ "id",
+ "bits"
+ ],
+ "title": "The Globalenbid Schema",
+ "type": "object"
+ }
+ },
+ "required": [
+ "globalENBId"
+ ],
+ "title": "The Interfaceid Schema",
+ "type": "object"
+ },
+ "subscription": {
+ "$id": "#/properties/controls/properties/subscription",
+ "properties": {
+ "retries": {
+ "$id": "#/properties/controls/properties/subscription/properties/retries",
+ "default": 0,
+ "examples": [
+ 1
+ ],
+ "title": "The Retries Schema",
+ "type": "integer"
+ },
+ "retryto": {
+ "$id": "#/properties/controls/properties/subscription/properties/retryto",
+ "default": 0,
+ "examples": [
+ 2
+ ],
+ "title": "The Retryto Schema",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "retries",
+ "retryto"
+ ],
+ "title": "The Subscription Schema",
+ "type": "object"
+ }
+ },
+ "required": [
+ "active",
+ "subscription",
+ "interfaceId"
+ ],
+ "title": "The Controls Schema",
+ "type": "object"
+ },
+ "db": {
+ "$id": "#/properties/db",
+ "properties": {
+ "host": {
+ "$id": "#/properties/db/properties/host",
+ "default": "",
+ "examples": [
+ "localhost"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Host Schema",
+ "type": "string"
+ },
+ "namespaces": {
+ "$id": "#/properties/db/properties/namespaces",
+ "items": {
+ "$id": "#/properties/db/properties/namespaces/items",
+ "default": "",
+ "examples": [
+ "sdl",
+ "rnib"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Items Schema",
+ "type": "string"
+ },
+ "title": "The Namespaces Schema",
+ "type": "array"
+ },
+ "port": {
+ "$id": "#/properties/db/properties/port",
+ "default": 0,
+ "examples": [
+ 6379
+ ],
+ "title": "The Port Schema",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "host",
+ "port",
+ "namespaces"
+ ],
+ "title": "The Db Schema",
+ "type": "object"
+ },
+ "local": {
+ "$id": "#/properties/local",
+ "properties": {
+ "host": {
+ "$id": "#/properties/local/properties/host",
+ "default": "",
+ "examples": [
+ ":8080"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Host Schema",
+ "type": "string"
+ }
+ },
+ "required": [
+ "host"
+ ],
+ "title": "The Local Schema",
+ "type": "object"
+ },
+ "logger": {
+ "$id": "#/properties/logger",
+ "properties": {
+ "level": {
+ "$id": "#/properties/logger/properties/level",
+ "default": 0,
+ "examples": [
+ 3
+ ],
+ "title": "The Level Schema",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "level"
+ ],
+ "title": "The Logger Schema",
+ "type": "object"
+ },
+ "metrics": {
+ "$id": "#/properties/metrics",
+ "items": {
+ "$id": "#/properties/metrics/items",
+ "properties": {
+ "description": {
+ "$id": "#/properties/metrics/items/properties/description",
+ "default": "",
+ "examples": [
+ "The total number of UE context creation events"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Description Schema",
+ "type": "string"
+ },
+ "enabled": {
+ "$id": "#/properties/metrics/items/properties/enabled",
+ "default": false,
+ "examples": [
+ true
+ ],
+ "title": "The Enabled Schema",
+ "type": "boolean"
+ },
+ "name": {
+ "$id": "#/properties/metrics/items/properties/name",
+ "default": "",
+ "examples": [
+ "UEContextCreated"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Name Schema",
+ "type": "string"
+ },
+ "type": {
+ "$id": "#/properties/metrics/items/properties/type",
+ "default": "",
+ "examples": [
+ "counter"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Type Schema",
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "type",
+ "enabled",
+ "description"
+ ],
+ "title": "The Items Schema",
+ "type": "object"
+ },
+ "title": "The Metrics Schema",
+ "type": "array"
+ },
+ "rmr": {
+ "$id": "#/properties/rmr",
+ "properties": {
+ "maxSize": {
+ "$id": "#/properties/rmr/properties/maxSize",
+ "default": 0,
+ "examples": [
+ 2072
+ ],
+ "title": "The Maxsize Schema",
+ "type": "integer"
+ },
+ "numWorkers": {
+ "$id": "#/properties/rmr/properties/numWorkers",
+ "default": 0,
+ "examples": [
+ 1
+ ],
+ "title": "The Numworkers Schema",
+ "type": "integer"
+ },
+ "protPort": {
+ "$id": "#/properties/rmr/properties/protPort",
+ "default": "",
+ "examples": [
+ "tcp:4560"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Protport Schema",
+ "type": "string"
+ },
+ "rxMessages": {
+ "$id": "#/properties/rmr/properties/rxMessages",
+ "items": {
+ "$id": "#/properties/rmr/properties/rxMessages/items",
+ "default": "",
+ "examples": [
+ "RIC_SUB_RESP",
+ "RIC_SUB_FAILURE",
+ "RIC_SUB_DEL_RESP",
+ "RIC_SUB_DEL_FAILURE",
+ "RIC_INDICATION"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Items Schema",
+ "type": "string"
+ },
+ "title": "The Rxmessages Schema",
+ "type": "array"
+ },
+ "txMessages": {
+ "$id": "#/properties/rmr/properties/txMessages",
+ "items": {
+ "$id": "#/properties/rmr/properties/txMessages/items",
+ "default": "",
+ "examples": [
+ "RIC_SUB_REQ",
+ "RIC_SUB_DEL_REQ"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Items Schema",
+ "type": "string"
+ },
+ "title": "The Txmessages Schema",
+ "type": "array"
+ }
+ },
+ "required": [
+ "protPort",
+ "maxSize",
+ "numWorkers",
+ "txMessages",
+ "rxMessages"
+ ],
+ "title": "The Rmr Schema",
+ "type": "object"
+ }
+ },
+ "required": [
+ "local",
+ "logger",
+ "rmr",
+ "db",
+ "controls",
+ "metrics"
+ ],
+ "title": "The Root Schema",
+ "type": "object"
+ },
+ "config": {
+ "controls": {
+ "active": true,
+ "interfaceId": {
+ "globalENBId": {
+ "bits": 28,
+ "id": 202251,
+ "plmnid": "310150"
+ }
+ },
+ "subscription": {
+ "retries": 1,
+ "retryto": 2
+ }
+ },
+ "db": {
+ "host": "localhost",
+ "namespaces": [
+ "sdl",
+ "rnib"
+ ],
+ "port": 6379
+ },
+ "local": {
+ "host": ":8080"
+ },
+ "logger": {
+ "level": 3
+ },
+ "metrics": [
+ {
+ "description": "The total number of UE context creation events",
+ "enabled": true,
+ "name": "UEContextCreated",
+ "type": "counter"
+ },
+ {
+ "description": "The total number of UE context release events",
+ "enabled": true,
+ "name": "UEContextReleased",
+ "type": "counter"
+ }
+ ],
+ "rmr": {
+ "maxSize": 2072,
+ "numWorkers": 1,
+ "protPort": "tcp:4560",
+ "rxMessages": [
+ "RIC_SUB_RESP",
+ "RIC_SUB_FAILURE",
+ "RIC_SUB_DEL_RESP",
+ "RIC_SUB_DEL_FAILURE",
+ "RIC_INDICATION"
+ ],
+ "txMessages": [
+ "RIC_SUB_REQ",
+ "RIC_SUB_DEL_REQ"
+ ]
+ }
+ },
+ "layout": [
+ {
+ "key": "controls.active",
+ "title": "Active"
+ },
+ {
+ "key": "controls.interfaceId.globalENBId",
+ "title": "Global ENB Id"
+ },
+ {
+ "type": "flex",
+ "flex-flow": "row wrap",
+ "items": [
+ {
+ "key": "controls.interfaceId.globalENBId.plmnid",
+ "title": "Plmn Id"
+ },
+ {
+ "key": "controls.interfaceId.globalENBId.id",
+ "title": "Id"
+
+ },
+ {
+ "key": "controls.interfaceId.globalENBId.bits",
+ "title": "Bits"
+ }
+ ]
+ },
+ {
+ "key": "controls.subscription",
+ "title": "Subscription"
+ },
+ {
+ "type": "flex",
+ "flex-flow": "row wrap",
+ "items": [
+ {
+ "key": "controls.subscription.retries",
+ "title": "Retries"
+ },
+ {
+ "key": "controls.subscription.retryto",
+ "title": "Retry to"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "metadata": {
+ "name": "UE Event Collector",
+ "configName": "UEEC-appconfig",
+ "namespace": "ricxapp"
+ },
+ "descriptor": {
+ "definitions": {},
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://example.com/root.json",
+ "type": "object",
+ "title": "The Root Schema",
+ "required": [
+ "local",
+ "logger",
+ "rmr",
+ "db",
+ "controls",
+ "metrics"
+ ],
+ "properties": {
+ "local": {
+ "$id": "#/properties/local",
+ "type": "object",
+ "title": "The Local Schema",
+ "required": [
+ "host"
+ ],
+ "properties": {
+ "host": {
+ "$id": "#/properties/local/properties/host",
+ "type": "string",
+ "title": "The Host Schema",
+ "default": "",
+ "examples": [
+ ":8080"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ },
+ "logger": {
+ "$id": "#/properties/logger",
+ "type": "object",
+ "title": "The Logger Schema",
+ "required": [
+ "level"
+ ],
+ "properties": {
+ "level": {
+ "$id": "#/properties/logger/properties/level",
+ "type": "integer",
+ "title": "The Level Schema",
+ "default": 0,
+ "examples": [
+ 3
+ ]
+ }
+ }
+ },
+ "rmr": {
+ "$id": "#/properties/rmr",
+ "type": "object",
+ "title": "The Rmr Schema",
+ "required": [
+ "protPort",
+ "maxSize",
+ "numWorkers",
+ "txMessages",
+ "rxMessages"
+ ],
+ "properties": {
+ "protPort": {
+ "$id": "#/properties/rmr/properties/protPort",
+ "type": "string",
+ "title": "The Protport Schema",
+ "default": "",
+ "examples": [
+ "tcp:4560"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "maxSize": {
+ "$id": "#/properties/rmr/properties/maxSize",
+ "type": "integer",
+ "title": "The Maxsize Schema",
+ "default": 0,
+ "examples": [
+ 2072
+ ]
+ },
+ "numWorkers": {
+ "$id": "#/properties/rmr/properties/numWorkers",
+ "type": "integer",
+ "title": "The Numworkers Schema",
+ "default": 0,
+ "examples": [
+ 1
+ ]
+ },
+ "txMessages": {
+ "$id": "#/properties/rmr/properties/txMessages",
+ "type": "array",
+ "title": "The Txmessages Schema",
+ "items": {
+ "$id": "#/properties/rmr/properties/txMessages/items",
+ "type": "string",
+ "title": "The Items Schema",
+ "default": "",
+ "examples": [
+ "RIC_SUB_REQ",
+ "RIC_SUB_DEL_REQ"
+ ],
+ "pattern": "^(.*)$"
+ }
+ },
+ "rxMessages": {
+ "$id": "#/properties/rmr/properties/rxMessages",
+ "type": "array",
+ "title": "The Rxmessages Schema",
+ "items": {
+ "$id": "#/properties/rmr/properties/rxMessages/items",
+ "type": "string",
+ "title": "The Items Schema",
+ "default": "",
+ "examples": [
+ "RIC_SUB_RESP",
+ "RIC_SUB_FAILURE",
+ "RIC_SUB_DEL_RESP",
+ "RIC_SUB_DEL_FAILURE",
+ "RIC_INDICATION"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ },
+ "db": {
+ "$id": "#/properties/db",
+ "type": "object",
+ "title": "The Db Schema",
+ "required": [
+ "host",
+ "port",
+ "namespaces"
+ ],
+ "properties": {
+ "host": {
+ "$id": "#/properties/db/properties/host",
+ "type": "string",
+ "title": "The Host Schema",
+ "default": "",
+ "examples": [
+ "localhost"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "port": {
+ "$id": "#/properties/db/properties/port",
+ "type": "integer",
+ "title": "The Port Schema",
+ "default": 0,
+ "examples": [
+ 6379
+ ]
+ },
+ "namespaces": {
+ "$id": "#/properties/db/properties/namespaces",
+ "type": "array",
+ "title": "The Namespaces Schema",
+ "items": {
+ "$id": "#/properties/db/properties/namespaces/items",
+ "type": "string",
+ "title": "The Items Schema",
+ "default": "",
+ "examples": [
+ "sdl",
+ "rnib"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ },
+ "controls": {
+ "$id": "#/properties/controls",
+ "type": "object",
+ "title": "The Controls Schema",
+ "required": [
+ "active",
+ "requestorId",
+ "ranFunctionId",
+ "ricActionId",
+ "interfaceId"
+ ],
+ "properties": {
+ "active": {
+ "$id": "#/properties/controls/properties/active",
+ "type": "boolean",
+ "title": "The Active Schema",
+ "default": false,
+ "examples": [
+ true
+ ]
+ },
+ "requestorId": {
+ "$id": "#/properties/controls/properties/requestorId",
+ "type": "integer",
+ "title": "The Requestorid Schema",
+ "default": 0,
+ "examples": [
+ 66
+ ]
+ },
+ "ranFunctionId": {
+ "$id": "#/properties/controls/properties/ranFunctionId",
+ "type": "integer",
+ "title": "The Ranfunctionid Schema",
+ "default": 0,
+ "examples": [
+ 1
+ ]
+ },
+ "ricActionId": {
+ "$id": "#/properties/controls/properties/ricActionId",
+ "type": "integer",
+ "title": "The Ricactionid Schema",
+ "default": 0,
+ "examples": [
+ 0
+ ]
+ },
+ "interfaceId": {
+ "$id": "#/properties/controls/properties/interfaceId",
+ "type": "object",
+ "title": "The Interfaceid Schema",
+ "required": [
+ "globalENBId"
+ ],
+ "properties": {
+ "globalENBId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId",
+ "type": "object",
+ "title": "The Globalenbid Schema",
+ "required": [
+ "plmnId",
+ "eNBId"
+ ],
+ "properties": {
+ "plmnId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/plmnId",
+ "type": "string",
+ "title": "The Plmnid Schema",
+ "default": "",
+ "examples": [
+ "43962"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "eNBId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/eNBId",
+ "type": "string",
+ "title": "The Enbid Schema",
+ "default": "",
+ "examples": [
+ "43962"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "metrics": {
+ "$id": "#/properties/metrics",
+ "type": "array",
+ "title": "The Metrics Schema",
+ "items": {
+ "$id": "#/properties/metrics/items",
+ "type": "object",
+ "title": "The Items Schema",
+ "required": [
+ "name",
+ "type",
+ "enabled",
+ "description"
+ ],
+ "properties": {
+ "name": {
+ "$id": "#/properties/metrics/items/properties/name",
+ "type": "string",
+ "title": "The Name Schema",
+ "default": "",
+ "examples": [
+ "UEContextCreated"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "type": {
+ "$id": "#/properties/metrics/items/properties/type",
+ "type": "string",
+ "title": "The Type Schema",
+ "default": "",
+ "examples": [
+ "counter"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "enabled": {
+ "$id": "#/properties/metrics/items/properties/enabled",
+ "type": "boolean",
+ "title": "The Enabled Schema",
+ "default": false,
+ "examples": [
+ true
+ ]
+ },
+ "description": {
+ "$id": "#/properties/metrics/items/properties/description",
+ "type": "string",
+ "title": "The Description Schema",
+ "default": "",
+ "examples": [
+ "The total number of UE context creation events"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ }
+ }
+ },
+ "config": {
+ "local": {
+ "host": ":8080"
+ },
+ "logger": {
+ "level": 3
+ },
+ "rmr": {
+ "protPort": "tcp:4560",
+ "maxSize": 2072,
+ "numWorkers": 1,
+ "txMessages": [ "RIC_SUB_REQ", "RIC_SUB_DEL_REQ" ],
+ "rxMessages": [ "RIC_SUB_RESP", "RIC_SUB_FAILURE", "RIC_SUB_DEL_RESP", "RIC_SUB_DEL_FAILURE", "RIC_INDICATION" ]
+ },
+ "db": {
+ "host": "localhost",
+ "port": 6379,
+ "namespaces": [ "sdl", "rnib" ]
+ },
+ "controls": {
+ "active": true,
+ "requestorId": 66,
+ "ranFunctionId": 1,
+ "ricActionId": 0,
+ "interfaceId": {
+ "globalENBId": {
+ "plmnId": "43962",
+ "eNBId": "43962"
+ }
+ }
+ },
+ "metrics": [
+ {
+ "name": "UEContextCreated",
+ "type": "counter",
+ "enabled": true,
+ "description": "The total number of UE context creation events"
+ },
+ {
+ "name": "UEContextReleased",
+ "type": "counter",
+ "enabled": true,
+ "description": "The total number of UE context release events"
+ }
+ ]
+ },
+ "layout": [
+ {
+ "key": "controls.active",
+ "title": "Active"
+ },
+ {
+ "key": "controls.requestorId",
+ "title": "Requestor Id"
+ },
+ {
+ "key": "controls.ranFunctionId",
+ "title": "RAN Function Id"
+ },
+ {
+ "key": "controls.ricActionId",
+ "title": "RIC Action Id"
+ },
+ {
+ "key": "controls.interfaceId.globalENBId",
+ "title": "Global ENB Id"
+ },
+ {
+ "type": "flex",
+ "flex-flow": "row wrap",
+ "items": [
+ {
+ "key": "controls.interfaceId.globalENBId.plmnId",
+ "title": "Plmn Id"
+ },
+ {
+ "key": "controls.interfaceId.globalENBId.eNBId",
+ "title": "ENB Id"
+
+ }
+ ]
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/assets/mockdata/db.json b/dashboard/webapp-frontend/src/assets/mockdata/db.json
new file mode 100644
index 0000000..31bd473
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/mockdata/db.json
@@ -0,0 +1,36 @@
+{
+ "config": [
+ {
+ "id": "jsonURL",
+ "value": "http://localhost:3000"
+ },
+ {
+ "id": "host",
+ "value": "http://localhost:3000"
+ },
+ {
+ "id": "metricspath",
+ "value": "/a1ric/metrics"
+ },
+ {
+ "id": "delaypath",
+ "value": "/a1ric/delay"
+ },
+ {
+ "id": "loadpath",
+ "value": "/a1ric/load"
+ }
+ ],
+ "metrics": {
+ "latency": 11,
+ "load": 100,
+ "ricload": 100,
+ "time": 123
+ },
+ "delay": {
+ "delay": 64877
+ },
+ "load": {
+ "load": 1
+ }
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/assets/mockdata/routes.json b/dashboard/webapp-frontend/src/assets/mockdata/routes.json
new file mode 100644
index 0000000..0745958
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/mockdata/routes.json
@@ -0,0 +1,4 @@
+{
+ "/a1ric/*": "/$1",
+ "/:resource/:id/show": "/:resource/:id"
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/assets/oran-logo.png b/dashboard/webapp-frontend/src/assets/oran-logo.png
new file mode 100644
index 0000000..c3b6ce5
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/oran-logo.png
Binary files differ
diff --git a/dashboard/webapp-frontend/src/assets/policy.png b/dashboard/webapp-frontend/src/assets/policy.png
new file mode 100644
index 0000000..11df15d
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/policy.png
Binary files differ
diff --git a/dashboard/webapp-frontend/src/assets/profile_default.png b/dashboard/webapp-frontend/src/assets/profile_default.png
new file mode 100644
index 0000000..2b90bf0
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/profile_default.png
Binary files differ
diff --git a/dashboard/webapp-frontend/src/assets/xAppControl.png b/dashboard/webapp-frontend/src/assets/xAppControl.png
new file mode 100644
index 0000000..9458a85
--- /dev/null
+++ b/dashboard/webapp-frontend/src/assets/xAppControl.png
Binary files differ
diff --git a/dashboard/webapp-frontend/src/environments/environment.prod.ts b/dashboard/webapp-frontend/src/environments/environment.prod.ts
new file mode 100644
index 0000000..58489a2
--- /dev/null
+++ b/dashboard/webapp-frontend/src/environments/environment.prod.ts
@@ -0,0 +1,22 @@
+/*-
+ * ========================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===================================
+ */
+export const environment = {
+ production: true
+};
diff --git a/dashboard/webapp-frontend/src/environments/environment.ts b/dashboard/webapp-frontend/src/environments/environment.ts
new file mode 100644
index 0000000..c7873a3
--- /dev/null
+++ b/dashboard/webapp-frontend/src/environments/environment.ts
@@ -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===================================
+ */
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+ production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
diff --git a/dashboard/webapp-frontend/src/favicon.ico b/dashboard/webapp-frontend/src/favicon.ico
new file mode 100644
index 0000000..00b0fd0
--- /dev/null
+++ b/dashboard/webapp-frontend/src/favicon.ico
Binary files differ
diff --git a/dashboard/webapp-frontend/src/index.html b/dashboard/webapp-frontend/src/index.html
new file mode 100644
index 0000000..f423e92
--- /dev/null
+++ b/dashboard/webapp-frontend/src/index.html
@@ -0,0 +1,32 @@
+<!--
+ ========================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===================================
+ -->
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Non RT RIC Dashboard</title>
+ <base href="/">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="icon" type="image/x-icon" href="assets/oran-logo.png">
+ </head>
+ <body>
+ <rd-root></rd-root>
+ </body>
+</html>
diff --git a/dashboard/webapp-frontend/src/karma.conf.js b/dashboard/webapp-frontend/src/karma.conf.js
new file mode 100644
index 0000000..a125d9b
--- /dev/null
+++ b/dashboard/webapp-frontend/src/karma.conf.js
@@ -0,0 +1,50 @@
+/*-
+ * ========================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===================================
+ */
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../coverage'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false
+ });
+};
diff --git a/dashboard/webapp-frontend/src/main.ts b/dashboard/webapp-frontend/src/main.ts
new file mode 100644
index 0000000..60c66e4
--- /dev/null
+++ b/dashboard/webapp-frontend/src/main.ts
@@ -0,0 +1,31 @@
+/*-
+ * ========================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 { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { RdModule } from './app/rd.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(RdModule)
+ .catch(err => console.error(err));
diff --git a/dashboard/webapp-frontend/src/polyfills.ts b/dashboard/webapp-frontend/src/polyfills.ts
new file mode 100644
index 0000000..09fd208
--- /dev/null
+++ b/dashboard/webapp-frontend/src/polyfills.ts
@@ -0,0 +1,104 @@
+/*-
+ * ========================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===================================
+ */
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
+ * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
+ */
+
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ * (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/dashboard/webapp-frontend/src/styles.scss b/dashboard/webapp-frontend/src/styles.scss
new file mode 100644
index 0000000..c74cf88
--- /dev/null
+++ b/dashboard/webapp-frontend/src/styles.scss
@@ -0,0 +1,44 @@
+/*-
+ * ========================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 '~bootstrap/dist/css/bootstrap.min.css';
+@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
+@import './styles/dark-theme';
+
+/* for sidenav to take a whole page */
+html, body {
+ margin: 0;
+ height: 100%;
+ font-family: Helvetica, Arial, sans-serif;
+}
+
+/* notification */
+.confirm-dialog-container span.content-span {
+ padding: 35px 16px 20px 16px;
+ text-align: center;
+ font-size: 20px;
+}
+
+.rd-global-page-title {
+ margin-left: 0.5%;
+ color: #432c85;
+ font-size: 25px;
+ font-weight: 100;
+}
diff --git a/dashboard/webapp-frontend/src/styles/dark-theme.scss b/dashboard/webapp-frontend/src/styles/dark-theme.scss
new file mode 100644
index 0000000..8cc0ece
--- /dev/null
+++ b/dashboard/webapp-frontend/src/styles/dark-theme.scss
@@ -0,0 +1,36 @@
+/*-
+ * ========================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 '~@angular/material/theming';
+@include mat-core();
+
+.dark-theme {
+ color: white;
+ $dark-primary: mat-palette($mat-yellow);
+ $dark-accent: mat-palette($mat-amber, A400, A100, A700);
+ $dark-warn: mat-palette($mat-red);
+ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
+
+ @include angular-material-theme($dark-theme);
+}
+
+.dark-theme .rd-global-page-title {
+ color:white;
+}
\ No newline at end of file
diff --git a/dashboard/webapp-frontend/src/test.ts b/dashboard/webapp-frontend/src/test.ts
new file mode 100644
index 0000000..e9d6a82
--- /dev/null
+++ b/dashboard/webapp-frontend/src/test.ts
@@ -0,0 +1,39 @@
+/*-
+ * ========================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===================================
+ */
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/dashboard/webapp-frontend/src/tsconfig.app.json b/dashboard/webapp-frontend/src/tsconfig.app.json
new file mode 100644
index 0000000..190fd30
--- /dev/null
+++ b/dashboard/webapp-frontend/src/tsconfig.app.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "types": []
+ },
+ "exclude": [
+ "test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/dashboard/webapp-frontend/src/tsconfig.spec.json b/dashboard/webapp-frontend/src/tsconfig.spec.json
new file mode 100644
index 0000000..de77336
--- /dev/null
+++ b/dashboard/webapp-frontend/src/tsconfig.spec.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "test.ts",
+ "polyfills.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/dashboard/webapp-frontend/src/tslint.json b/dashboard/webapp-frontend/src/tslint.json
new file mode 100644
index 0000000..30581c6
--- /dev/null
+++ b/dashboard/webapp-frontend/src/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "rd",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "rd",
+ "kebab-case"
+ ]
+ }
+}