Frontend EI Coordinator 43/5143/2
authormaximesson <maxime.bonneau@est.tech>
Mon, 9 Nov 2020 09:25:45 +0000 (10:25 +0100)
committerPatrik Buhr <patrik.buhr@est.tech>
Wed, 25 Nov 2020 08:48:18 +0000 (08:48 +0000)
Change-Id: Ic13740d159f9a783aa80c40b3fad65f9a0718e22
Issue-ID: NONRTRIC-294
Signed-off-by: maximesson <maxime.bonneau@est.tech>
23 files changed:
webapp-frontend/package.json
webapp-frontend/src/app/controlpanel-routing.module.ts
webapp-frontend/src/app/controlpanel.module.ts
webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.html [new file with mode: 0644]
webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.scss [new file with mode: 0644]
webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.spec.ts [new file with mode: 0644]
webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.ts [new file with mode: 0644]
webapp-frontend/src/app/ei-coordinator/ei-job.datasource.ts [new file with mode: 0644]
webapp-frontend/src/app/ei-coordinator/ei-producer.datasource.ts [new file with mode: 0644]
webapp-frontend/src/app/ei-coordinator/ei-type.datasource.ts [new file with mode: 0644]
webapp-frontend/src/app/interfaces/ei.jobs.ts [new file with mode: 0644]
webapp-frontend/src/app/main/main.component.html
webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
webapp-frontend/src/app/node-modules/node-modules.component.html [new file with mode: 0644]
webapp-frontend/src/app/node-modules/node-modules.component.scss [new file with mode: 0644]
webapp-frontend/src/app/node-modules/node-modules.component.spec.ts [new file with mode: 0644]
webapp-frontend/src/app/node-modules/node-modules.component.ts [new file with mode: 0644]
webapp-frontend/src/app/services/ei/ei.service.spec.ts [new file with mode: 0644]
webapp-frontend/src/app/services/ei/ei.service.ts [new file with mode: 0644]
webapp-frontend/src/app/ui/ei-card/ei-card.component.html [new file with mode: 0644]
webapp-frontend/src/app/ui/ei-card/ei-card.component.scss [new file with mode: 0644]
webapp-frontend/src/app/ui/ei-card/ei-card.component.spec.ts [new file with mode: 0644]
webapp-frontend/src/app/ui/ei-card/ei-card.component.ts [new file with mode: 0644]

index dd68902..b7823a1 100644 (file)
@@ -23,7 +23,7 @@
     "@angular/platform-browser-dynamic": "^8.2.14",
     "@angular/router": "^8.2.14",
     "@fortawesome/fontawesome-free": "^5.12.0",
-    "@kubernetes/client-node": "^0.10.3",
+    "@kubernetes/client-node": "^0.12.3",
     "@material/radio": "^2.3.0",
     "@types/chart.js": "^2.9.11",
     "@types/uuid": "^7.0.3",
index 23665bc..366ef9f 100644 (file)
@@ -23,11 +23,13 @@ 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';
+import { EICoordinatorComponent } from './ei-coordinator/ei-coordinator.component';
 
 
 const routes: Routes = [
     { path: '', component: MainComponent },
-    { path: 'policy', component: PolicyControlComponent }
+    { path: 'policy', component: PolicyControlComponent },
+    { path: 'ei-coordinator', component: EICoordinatorComponent }
 ];
 
 @NgModule({
index 4abd69f..b20e871 100644 (file)
@@ -56,10 +56,16 @@ import { ControlpanelRoutingModule } from './controlpanel-routing.module';
 import { SidenavListComponent } from './navigation/sidenav-list/sidenav-list.component';
 import { UiService } from './services/ui/ui.service';
 import { CookieModule } from 'ngx-cookie';
+import { NodeModulesComponent } from './node-modules/node-modules.component';
+import { EICardComponent } from './ui/ei-card/ei-card.component';
+import { EICoordinatorComponent } from './ei-coordinator/ei-coordinator.component';
+
 
 @NgModule({
   declarations: [
     ConfirmDialogComponent,
+    EICardComponent,
+    EICoordinatorComponent,
     ErrorDialogComponent,
     FooterComponent,
     MainComponent,
@@ -69,7 +75,8 @@ import { CookieModule } from 'ngx-cookie';
     PolicyInstanceComponent,
     PolicyInstanceDialogComponent,
     ControlpanelComponent,
-    SidenavListComponent
+    SidenavListComponent,
+    NodeModulesComponent
   ],
   imports: [
     BrowserModule,
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.html b/webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.html
new file mode 100644 (file)
index 0000000..aee65c8
--- /dev/null
@@ -0,0 +1,65 @@
+<!--
+========================LICENSE_START=================================
+O-RAN-SC
+%%
+Copyright (C) 2020 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===================================
+-->
+
+<h3 class="rd-global-page-title">Enrichment Information Coordinator</h3>
+
+<br>
+<h4>Jobs</h4>
+<table mat-table EIJobTable [dataSource]="eiJobsDataSource" [ngClass]="{'table-dark': darkMode}" matSort
+    multiTemplateDataRows class="ei-coordinator-table mat-elevation-z8">
+    <ng-container matColumnDef="id">
+        <th mat-header-cell *matHeaderCellDef> Job ID </th>
+        <td mat-cell *matCellDef="let eiJob"> {{this.getDisplayName(eiJob)}} </td>
+    </ng-container>
+    <!--ng-container matColumnDef="data">
+        <th mat-header-cell *matHeaderCellDef> Job data </th>
+        <td mat-cell *matCellDef="let eiJob"> {{this.getEIJobInfo(eiJob)}} </td>
+    </ng-container-->
+    <ng-container matColumnDef="typeId">
+        <th mat-header-cell *matHeaderCellDef> Type ID </th>
+        <td mat-cell *matCellDef="let eiJob"> {{this.getEITypeId(eiJob)}} </td>
+    </ng-container>
+    <ng-container matColumnDef="targetUri">
+        <th mat-header-cell *matHeaderCellDef> Target URI </th>
+        <td mat-cell *matCellDef="let eiJob"> {{this.getTargetUri(eiJob)}} </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="['id', 'typeId', 'targetUri']"></tr>
+    <tr mat-row *matRowDef="let row; columns: ['id', 'typeId', 'targetUri'];"></tr>
+</table>
+
+<br>
+<h4>Producers</h4>
+<table mat-table EIProducerTable [dataSource]="eiProducersDataSource" [ngClass]="{'table-dark': darkMode}" matSort
+    multiTemplateDataRows class="ei-coordinator-table mat-elevation-z8">
+    <ng-container matColumnDef="id">
+        <th mat-header-cell *matHeaderCellDef> Producer ID </th>
+        <td mat-cell *matCellDef="let eiProducer"> {{this.getEIProducerId(eiProducer)}} </td>
+    </ng-container>
+    <ng-container matColumnDef="type">
+        <th mat-header-cell *matHeaderCellDef> Producer type </th>
+        <td mat-cell *matCellDef="let eiProducer"> {{this.getEIProducerTypes(eiProducer)}} </td>
+    </ng-container>
+    <ng-container matColumnDef="status">
+        <th mat-header-cell *matHeaderCellDef> Producer status </th>
+        <td mat-cell *matCellDef="let eiProducer"> {{this.getEIProducerStatus(eiProducer)}} </td>
+    </ng-container>
+    <tr mat-header-row *matHeaderRowDef="['id', 'type', 'status']"></tr>
+    <tr mat-row *matRowDef="let row; columns: ['id', 'type', 'status'];"></tr>
+</table>
\ No newline at end of file
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.scss b/webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.scss
new file mode 100644 (file)
index 0000000..8e873ec
--- /dev/null
@@ -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;
+}
+
+.ei-coordinator-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;
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.spec.ts b/webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.spec.ts
new file mode 100644 (file)
index 0000000..36b2cc8
--- /dev/null
@@ -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 { EICoordinatorComponent } from './ei-coordinator.component';
+
+describe('EICoordinatorComponent', () => {
+  let component: EICoordinatorComponent;
+  let fixture: ComponentFixture<EICoordinatorComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ EICoordinatorComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EICoordinatorComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.ts b/webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.ts
new file mode 100644 (file)
index 0000000..bcff218
--- /dev/null
@@ -0,0 +1,141 @@
+/*-
+ * ========================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 } from '@angular/material/dialog';
+import { MatSort } from '@angular/material/sort';
+import { animate, state, style, transition, trigger } from '@angular/animations';
+
+import { EIService } from '../services/ei/ei.service';
+import { EIJob, EIProducer } from '../interfaces/ei.jobs';
+import { EIProducerDataSource } from './ei-producer.datasource';
+import { EIJobDataSource } from './ei-job.datasource';
+import { NotificationService } from '../services/ui/notification.service';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { UiService } from '../services/ui/ui.service';
+
+class EIJobInfo {
+    constructor(public eiJob: EIJob) { }
+
+    isExpanded: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
+}
+
+@Component({
+    selector: 'rd-ei-coordinator',
+    templateUrl: './ei-coordinator.component.html',
+    styleUrls: ['./ei-coordinator.component.scss'],
+    animations: [
+        trigger('detailExpand', [
+            state('collapsed, void', style({ height: '0px', minHeight: '0', display: 'none' })),
+            state('expanded', style({ height: '*' })),
+            transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
+            transition('expanded <=> void', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
+        ]),
+    ],
+})
+export class EICoordinatorComponent implements OnInit {
+
+
+    eiJobsDataSource: EIJobDataSource;
+    eiProducersDataSource: EIProducerDataSource;
+    @ViewChild(MatSort, { static: true }) sort: MatSort;
+
+    eiJobInfo = new Map<string, EIJobInfo>();
+    darkMode: boolean;
+
+    constructor(
+        private eiSvc: EIService,
+        private dialog: MatDialog,
+        private notificationService: NotificationService,
+        private ui: UiService) { }
+
+    ngOnInit() {
+        this.eiJobsDataSource = new EIJobDataSource(this.eiSvc, this.sort, this.notificationService);
+        this.eiProducersDataSource = new EIProducerDataSource(this.eiSvc, this.sort, this.notificationService);
+        this.eiJobsDataSource.loadTable();
+        this.eiProducersDataSource.loadTable();
+        this.ui.darkModeState.subscribe((isDark) => {
+            this.darkMode = isDark;
+        });
+    }
+
+    toggleListInstances(eiJob: EIJob): void {
+        const info = this.getEIJobInfo(eiJob);
+        info.isExpanded.next(!info.isExpanded.getValue());
+    }
+
+    getEIJobInfo(eiJob: EIJob): EIJobInfo {
+        let info: EIJobInfo = this.eiJobInfo.get(eiJob.ei_job_data);
+        if (!info) {
+            info = new EIJobInfo(eiJob);
+            this.eiJobInfo.set(eiJob.ei_job_data, info);
+        }
+        return info;
+    }
+
+    getDisplayName(eiJob: EIJob): string {
+        if (eiJob.ei_job_identity) {
+            return eiJob.ei_job_identity;
+        }
+        return '< No id >';
+    }
+
+    getEITypeId(eiJob: EIJob): string {
+        if (eiJob.ei_type_identity){
+            return eiJob.ei_type_identity;
+        }
+        return '< No type >';
+    }
+
+    getTargetUri(eiJob: EIJob): string {
+        if (eiJob.target_uri){
+            return eiJob.target_uri;
+        }
+        return '< No target URI >';
+    }
+
+    isInstancesShown(eiJob: EIJob): boolean {
+        return this.getEIJobInfo(eiJob).isExpanded.getValue();
+    }
+
+    getExpandedObserver(eiJob: EIJob): Observable<boolean> {
+        return this.getEIJobInfo(eiJob).isExpanded.asObservable();
+    }
+
+    getEIProducerId(eiProducer: EIProducer): string {
+        if (eiProducer.ei_producer_id){
+            return eiProducer.ei_producer_id;
+        }
+        return '< No id>';
+    }
+
+    getEIProducerTypes(eiProducer: EIProducer): string[] {
+        if (eiProducer.ei_producer_types){
+            return eiProducer.ei_producer_types;
+        }
+        return ['< No types >'];
+    }
+
+    getEIProducerStatus(eiProducer: EIProducer): string {
+        if (eiProducer.status){
+            return eiProducer.status;
+        }
+        return '< No status >';
+    }
+}
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-job.datasource.ts b/webapp-frontend/src/app/ei-coordinator/ei-job.datasource.ts
new file mode 100644 (file)
index 0000000..a8a786f
--- /dev/null
@@ -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 { 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 { EIJob } from '../interfaces/ei.jobs';
+import { EIService } from '../services/ei/ei.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class EIJobDataSource extends DataSource<EIJob> {
+
+    private eiJobSubject = new BehaviorSubject<EIJob[]>([]);
+
+    private loadingSubject = new BehaviorSubject<boolean>(false);
+
+    public loading$ = this.loadingSubject.asObservable();
+
+    public rowCount = 1; // hide footer during intial load
+
+    constructor(
+        private eiSvc: EIService,
+        public sort: MatSort,
+        private notificationService: NotificationService) {
+        super();
+    }
+
+    loadTable() {
+        this.loadingSubject.next(true);
+        this.eiSvc.getEIJobs()
+            .pipe(
+                catchError((her: HttpErrorResponse) => {
+                    this.notificationService.error('Failed to get EI jobs: ' + her.error);
+                    return of([]);
+                }),
+                finalize(() => this.loadingSubject.next(false))
+            )
+            .subscribe((instances: EIJob[]) => {
+                console.log("Jobs: " + instances);
+                this.rowCount = instances.length;
+                this.eiJobSubject.next(instances);
+            });
+    }
+
+    connect(): Observable<EIJob[]> {
+        const dataMutations = [
+            this.eiJobSubject.asObservable(),
+            this.sort.sortChange
+        ];
+        return merge(...dataMutations).pipe(map(() => {
+            return this.getSortedData([...this.eiJobSubject.getValue()]);
+        }));
+    }
+
+    disconnect(): void {
+        this.eiJobSubject.complete();
+        this.loadingSubject.complete();
+    }
+
+    private getSortedData(data: EIJob[]) {
+        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 'id': return compare(a.ei_job_identity, b.ei_job_identity, isAsc);
+                //case 'eiTypeId': return compare(a.eiTypeId, b.eiTypeId, isAsc);
+                //case 'jobResultUri': return compare(a.jobResultUri, b.jobResultUri, isAsc);
+                //case 'jobOwner': return compare(a.jobOwner, b.jobOwner, isAsc);
+                //case 'jobStatusNotificationUri': return compare(a.jobStatusNotificationUri, b.jobStatusNotificationUri, isAsc);
+                //case 'jobDefinition': return compare(a.jobDefinition, b.jobDefinition, isAsc);
+                default: return 0;
+            }
+        });
+    }
+}
+
+function compare(a: string, b: string, isAsc: boolean) {
+    return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-producer.datasource.ts b/webapp-frontend/src/app/ei-coordinator/ei-producer.datasource.ts
new file mode 100644 (file)
index 0000000..866d785
--- /dev/null
@@ -0,0 +1,99 @@
+/*-
+ * ========================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 { EIProducer } from '../interfaces/ei.jobs';
+import { EIService } from '../services/ei/ei.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class EIProducerDataSource extends DataSource<EIProducer> {
+
+    private producerSubject = new BehaviorSubject<EIProducer[]>([]);
+
+    private loadingSubject = new BehaviorSubject<boolean>(false);
+
+    public loading$ = this.loadingSubject.asObservable();
+
+    public rowCount = 1; // hide footer during intial load
+
+    constructor(
+        private eiSvc: EIService,
+        public sort: MatSort,
+        private notificationService: NotificationService) {
+        super();
+    }
+
+    loadTable() {
+        this.loadingSubject.next(true);
+        this.eiSvc.getEIProducers()
+            .pipe(
+                catchError((her: HttpErrorResponse) => {
+                    this.notificationService.error('Failed to get producers: ' + her.error);
+                    return of([]);
+                }),
+                finalize(() => this.loadingSubject.next(false))
+            )
+            .subscribe((prods: EIProducer[]) => {
+                console.log("Producers: " + prods);
+                this.rowCount = prods.length;
+                this.producerSubject.next(prods);
+            });
+    }
+
+    connect(): Observable<EIProducer[]> {
+        const dataMutations = [
+            this.producerSubject.asObservable(),
+            this.sort.sortChange
+        ];
+        return merge(...dataMutations).pipe(map(() => {
+            return this.getSortedData([...this.producerSubject.getValue()]);
+        }));
+    }
+
+    disconnect(): void {
+        this.producerSubject.complete();
+        this.loadingSubject.complete();
+    }
+
+    private getSortedData(data: EIProducer[]) {
+        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 'ei_producer_id': return compare(a.ei_producer_id, b.ei_producer_id, isAsc);
+                default: return 0;
+            }
+        });
+    }
+}
+
+function compare(a: string, b: string, isAsc: boolean) {
+    return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-type.datasource.ts b/webapp-frontend/src/app/ei-coordinator/ei-type.datasource.ts
new file mode 100644 (file)
index 0000000..8d48196
--- /dev/null
@@ -0,0 +1,98 @@
+/*-
+ * ========================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 { EIService } from '../services/ei/ei.service';
+import { EIType } from '../interfaces/ei.jobs';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class EITypeDataSource extends DataSource<EIType> {
+
+    private eiTypeSubject = new BehaviorSubject<EIType[]>([]);
+
+    private loadingSubject = new BehaviorSubject<boolean>(false);
+
+    public loading$ = this.loadingSubject.asObservable();
+
+    public rowCount = 1; // hide footer during intial load
+
+    constructor(private eiSvc: EIService,
+        private sort: MatSort,
+        private notificationService: NotificationService) {
+        super();
+    }
+
+    loadTable() {
+        this.loadingSubject.next(true);
+        this.eiSvc.getEITypes()
+            .pipe(
+                catchError((her: HttpErrorResponse) => {
+                    this.notificationService.error('Failed to get EI types: ' + her.statusText + ', ' + her.error);
+                    return of([]);
+                }),
+                finalize(() => this.loadingSubject.next(false))
+            )
+            .subscribe((types: EIType[]) => {
+                console.log("Types: " + types);
+                this.rowCount = types.length;
+                this.eiTypeSubject.next(types);
+            });
+    }
+
+    connect(collectionViewer: CollectionViewer): Observable<EIType[]> {
+        const dataMutations = [
+            this.eiTypeSubject.asObservable(),
+            this.sort.sortChange
+        ];
+        return merge(...dataMutations).pipe(map(() => {
+            return this.getSortedData([...this.eiTypeSubject.getValue()]);
+        }));
+    }
+
+    disconnect(collectionViewer: CollectionViewer): void {
+        this.eiTypeSubject.complete();
+        this.loadingSubject.complete();
+    }
+
+    private getSortedData(data: EIType[]) {
+        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 'eiTypeId': return compare(a.description, b.description, isAsc);
+                default: return 0;
+            }
+        });
+    }
+}
+
+function compare(a: any, b: any, isAsc: boolean) {
+    return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/webapp-frontend/src/app/interfaces/ei.jobs.ts b/webapp-frontend/src/app/interfaces/ei.jobs.ts
new file mode 100644 (file)
index 0000000..e7d034a
--- /dev/null
@@ -0,0 +1,47 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2020 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 EI Coordinator
+
+export interface EIJobBis {
+    eiTypeId: string;
+    jobResultUri: string;
+    jobOwner: string;
+    jobStatusNotificationUri: string;
+    jobDefinition: any;
+  }
+
+  export interface EIJob {
+    ei_job_identity: string;
+    ei_job_data: any;
+    ei_type_identity: string;
+    target_uri: string;
+  }
+
+  export interface EIType {
+    id: string;
+    description: string;
+  }
+
+  export interface EIProducer {
+    ei_producer_id: string;
+    ei_producer_types: string[];
+    status: string;
+  }
\ No newline at end of file
index f05fd50..4eedb08 100644 (file)
@@ -20,4 +20,5 @@
   -->
 <div class = "main__container">
   <rd-policy-card></rd-policy-card>
+  <rd-ei-card></rd-ei-card>
 </div>
index 9dea90a..d110619 100644 (file)
@@ -27,4 +27,7 @@
   <a mat-list-item routerLink="/policy" (click)="onSidenavClose()">
       <mat-icon>assignment</mat-icon> <span class="nav-caption">Policy</span>
   </a>
+  <a mat-list-item routerLink="/ei-coordinator" (click)="onSidenavClose()">
+    <mat-icon>assignment</mat-icon> <span class="nav-caption">Enrichment Information Coordinator</span>
+</a>
 </mat-nav-list>
diff --git a/webapp-frontend/src/app/node-modules/node-modules.component.html b/webapp-frontend/src/app/node-modules/node-modules.component.html
new file mode 100644 (file)
index 0000000..a1950bc
--- /dev/null
@@ -0,0 +1 @@
+<p>node-modules works!</p>
diff --git a/webapp-frontend/src/app/node-modules/node-modules.component.scss b/webapp-frontend/src/app/node-modules/node-modules.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/webapp-frontend/src/app/node-modules/node-modules.component.spec.ts b/webapp-frontend/src/app/node-modules/node-modules.component.spec.ts
new file mode 100644 (file)
index 0000000..ea620d9
--- /dev/null
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NodeModulesComponent } from './node-modules.component';
+
+describe('NodeModulesComponent', () => {
+  let component: NodeModulesComponent;
+  let fixture: ComponentFixture<NodeModulesComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ NodeModulesComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(NodeModulesComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/webapp-frontend/src/app/node-modules/node-modules.component.ts b/webapp-frontend/src/app/node-modules/node-modules.component.ts
new file mode 100644 (file)
index 0000000..6d650a1
--- /dev/null
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'rd-node-modules',
+  templateUrl: './node-modules.component.html',
+  styleUrls: ['./node-modules.component.scss']
+})
+export class NodeModulesComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}
diff --git a/webapp-frontend/src/app/services/ei/ei.service.spec.ts b/webapp-frontend/src/app/services/ei/ei.service.spec.ts
new file mode 100644 (file)
index 0000000..6814d8a
--- /dev/null
@@ -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 { EIService } from './ei.service';
+
+describe('PolicyService', () => {
+  beforeEach(() => TestBed.configureTestingModule({}));
+
+  it('should be created', () => {
+    const service: EIService = TestBed.get(EIService);
+    expect(service).toBeTruthy();
+  });
+});
diff --git a/webapp-frontend/src/app/services/ei/ei.service.ts b/webapp-frontend/src/app/services/ei/ei.service.ts
new file mode 100644 (file)
index 0000000..cbc0a55
--- /dev/null
@@ -0,0 +1,80 @@
+/*-
+ * ========================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 { EIJob, EIType, EIProducer } from '../../interfaces/ei.jobs';
+import { ControlpanelSuccessTransport } from '../../interfaces/controlpanel.types';
+
+/**
+ * Services for calling the EI endpoints.
+ */
+@Injectable({
+    providedIn: 'root'
+})
+export class EIService {
+
+    private basePath = 'api/enrichment';
+    private eiTypePath = 'eitypes';
+    private eiJobPath = 'eijobs';
+    private eiProducerPath = 'eiproducers';
+
+    private buildPath(...args: any[]) {
+        let result = this.basePath;
+        args.forEach(part => {
+            result = result + '/' + part;
+        });
+        console.log("URL: "+result);
+        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<ControlpanelSuccessTransport>(url).pipe(
+            // Extract the string here
+            map(res => res['data'])
+        );
+    }
+
+    getEITypes(): Observable<EIType[]> {
+        const url = this.buildPath(this.eiTypePath);
+        return this.httpClient.get<EIType[]>(url);
+    }
+
+    getEIJobs(): Observable<EIJob[]> {
+        const url = this.buildPath(this.eiJobPath);
+        return this.httpClient.get<EIJob[]>(url);
+    }
+
+    getEIProducers(): Observable<EIProducer[]> {
+        const url = this.buildPath(this.eiProducerPath);
+        return this.httpClient.get<EIProducer[]>(url);
+    }
+}
diff --git a/webapp-frontend/src/app/ui/ei-card/ei-card.component.html b/webapp-frontend/src/app/ui/ei-card/ei-card.component.html
new file mode 100644 (file)
index 0000000..fff0670
--- /dev/null
@@ -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="ei__card" routerLink="/ei-coordinator" [ngClass]="{'add__card-dark': darkMode}">
+  <div class="header__container">
+    <span class="card__title">Enrichment Information Coordinator</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/oran-logo.png" width="250px"/>
+  </div>
+</div>
diff --git a/webapp-frontend/src/app/ui/ei-card/ei-card.component.scss b/webapp-frontend/src/app/ui/ei-card/ei-card.component.scss
new file mode 100644 (file)
index 0000000..8f91696
--- /dev/null
@@ -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===================================
+ */
+.ei__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, rgb(78, 78, 129), rgb(45, 44, 61));
+  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;
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/app/ui/ei-card/ei-card.component.spec.ts b/webapp-frontend/src/app/ui/ei-card/ei-card.component.spec.ts
new file mode 100644 (file)
index 0000000..c438461
--- /dev/null
@@ -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 {EICardComponent} from './ei-card.component';
+
+describe('EICardComponent', () => {
+  let component: EICardComponent;
+  let fixture: ComponentFixture<EICardComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [EICardComponent]
+    })
+      .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EICardComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/webapp-frontend/src/app/ui/ei-card/ei-card.component.ts b/webapp-frontend/src/app/ui/ei-card/ei-card.component.ts
new file mode 100644 (file)
index 0000000..6a7f83f
--- /dev/null
@@ -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-ei-card',
+  templateUrl: './ei-card.component.html',
+  styleUrls: ['./ei-card.component.scss']
+})
+export class EICardComponent implements OnInit, OnDestroy {
+  darkMode: boolean;
+
+  constructor(public router: Router, private ui: UiService) { }
+
+  ngOnInit() {
+    this.ui.darkModeState.subscribe((isDark) => {
+      this.darkMode = isDark;
+    });
+  }
+
+  ngOnDestroy() { }
+
+  openDetails() {
+    this.router.navigateByUrl('../../ei-coordinator');
+  }
+
+}