From: Jun (Nicolas) Hu Date: Fri, 10 May 2019 18:31:49 +0000 (-0400) Subject: Add undeploy xApp function X-Git-Tag: R2~108^2 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=3c6dec3133de760191c89a01884acc0614460699;p=portal%2Fric-dashboard.git Add undeploy xApp function use icons to replace text add shared confirm dialog add shared notification Change-Id: Icaa1cb3e41506b9d1132a91eae8beabf03afc6f6 Signed-off-by: Jun (Nicolas) Hu Signed-off-by: Lott, Christopher (cl778h) --- diff --git a/.gitignore b/.gitignore index 9eacc902..50288669 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ /nbdist/ /.nb-gradle/ /build/ + +### visual studio ### +.vs \ No newline at end of file diff --git a/.gitreview b/.gitreview index 9b79e644..9829e1ba 100644 --- a/.gitreview +++ b/.gitreview @@ -1,4 +1,4 @@ [gerrit] -host=gerrit.oran-osc.org +host=gerrit.o-ran-sc.org port=29418 project=portal/ric-dashboard.git diff --git a/docs/release-notes.rst b/docs/release-notes.rst index e1b87fb7..19f3b57a 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -28,6 +28,9 @@ Version 1.0.2, 13 May 2019 * Allow the user to initiate an E2 (X2) connection between RIC and gNB/eNB * User input validations on connections between RIC and eNB/gNB in the dashboard * Add ANR xApp backend with mock implementation +* Add undeploy xApp function +* Add shared confirm dialog +* Add shared notification Version 1.0.1, 6 May 2019 ------------------------- diff --git a/webapp-frontend/src/app/app.module.ts b/webapp-frontend/src/app/app.module.ts index a9783508..184ad467 100644 --- a/webapp-frontend/src/app/app.module.ts +++ b/webapp-frontend/src/app/app.module.ts @@ -21,7 +21,7 @@ import { BrowserModule } from '@angular/platform-browser'; // tslint:disable-next-line:max-line-length import { MatIconModule, MatCardModule, MatListModule, MatSidenavModule, MatButtonToggleModule, MatSliderModule, MatGridListModule, MatSlideToggleModule, - MatExpansionModule, MatTabsModule, MatDialogModule, MatFormFieldModule, MatButtonModule, MatInputModule} from '@angular/material'; + MatExpansionModule, MatTabsModule, MatDialogModule, MatFormFieldModule, MatButtonModule, MatInputModule, MatSnackBarModule} from '@angular/material'; import { BrowserAnimationsModule} from '@angular/platform-browser/animations'; import { NgModule } from '@angular/core'; import { Ng2SmartTableModule } from 'ng2-smart-table'; @@ -32,7 +32,7 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { LoginComponent } from './login/login.component'; -import { CatalogComponent, AppCatalogDeployDialog } from './catalog/catalog.component'; +import { CatalogComponent } from './catalog/catalog.component'; import { UiService } from './services/ui/ui.service'; import { AdminService } from './services/admin/admin.service'; import { CatalogService } from './services/catalog/catalog.service'; @@ -49,6 +49,7 @@ import { StatCardComponent } from './ui/stat-card/stat-card.component'; import { ModalEventComponent } from './ui/modal-event/modal-event.component'; import { XappComponent } from './xapp/xapp.component'; import { ConfigEventComponent } from './ui/config-event/config-event.component'; +import { ConfirmDialogComponent } from './ui/confirm-dialog/confirm-dialog.component'; @NgModule({ @@ -67,8 +68,8 @@ import { ConfigEventComponent } from './ui/config-event/config-event.component'; ModalEventComponent, XappComponent, ConfigEventComponent, - AppCatalogDeployDialog, - AppRANConnectDialog + AppRANConnectDialog, + ConfirmDialogComponent, ], imports: [ BrowserModule, @@ -92,6 +93,7 @@ import { ConfigEventComponent } from './ui/config-event/config-event.component'; MatButtonModule, MatInputModule, Ng2SmartTableModule, + MatSnackBarModule, MDBBootstrapModule.forRoot(), ], exports: [ @@ -112,8 +114,8 @@ import { ConfigEventComponent } from './ui/config-event/config-event.component'; MatInputModule ], entryComponents: [ - AppCatalogDeployDialog, - AppRANConnectDialog + AppRANConnectDialog, + ConfirmDialogComponent, ], providers: [ UiService, diff --git a/webapp-frontend/src/app/catalog/catalog.component.ts b/webapp-frontend/src/app/catalog/catalog.component.ts index 726183e5..f0fcbce8 100644 --- a/webapp-frontend/src/app/catalog/catalog.component.ts +++ b/webapp-frontend/src/app/catalog/catalog.component.ts @@ -20,12 +20,8 @@ import { Component, Inject } from '@angular/core'; import { LocalDataSource } from 'ng2-smart-table'; import { CatalogService } from '../services/catalog/catalog.service'; -import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; - -export interface DialogData { - name: string; -} - +import { ConfirmDialogService } from './../services/ui/confirm-dialog.service' +import { NotificationService } from './../services/ui/notification.service' @Component({ selector: 'app-catalog', @@ -42,8 +38,8 @@ export class CatalogComponent { edit: false, delete: false, custom: [ - { name: 'deployxApp', title: 'Deploy'}, - ], + { name: 'deployxApp', title: 'Deploy' }, + ], position: 'right' }, @@ -65,44 +61,30 @@ export class CatalogComponent { source: LocalDataSource = new LocalDataSource(); - constructor(private service: CatalogService, public dialog: MatDialog) { - this.service.getAll().subscribe((val:any[]) => this.source.load(val)); + constructor( + private service: CatalogService, + private confirmDialogService: ConfirmDialogService, + private notification: NotificationService) { + this.service.getAll().subscribe((val: any[]) => this.source.load(val)); } + onDeployxApp(event): void { + this.confirmDialogService.openConfirmDialog('Are you sure you want to deploy this xApp?') + .afterClosed().subscribe(res => { + if (res) { + this.service.deployXapp(event.data.name).subscribe( + response => { + switch (response.status) { + case 200: + this.notification.success('xApp deploy succeeded!'); + break; + default: + this.notification.warn('xApp deploy failed.'); + } + } + ); + } + }); - onDeployxApp(event): void { - const dialogRef = this.dialog.open(AppCatalogDeployDialog, { - width: '400px', - data: { name: event.data.name } - }); - - dialogRef.afterClosed().subscribe(result => { - console.log('The dialog was closed'); - }); - } - -} - -@Component({ - selector: 'app-catalog-deploy-dialog', - templateUrl: 'catalog.component.deploy-dialog.html', - styleUrls: ['./catalog.component.css'] -}) - -export class AppCatalogDeployDialog{ - - constructor( - public dialogRef: MatDialogRef, - private service: CatalogService, - @Inject(MAT_DIALOG_DATA) public data: DialogData) { } - - onNoClick(): void { - this.dialogRef.close(); - } - - deployXapp(): void { - this.service.deployXapp(this.data.name).subscribe((val: any[]) => { }); - this.dialogRef.close(); - } - -} + } +} \ No newline at end of file diff --git a/webapp-frontend/src/app/control/control.component.css b/webapp-frontend/src/app/control/control.component.css index f4cd56c7..798e94d3 100644 --- a/webapp-frontend/src/app/control/control.component.css +++ b/webapp-frontend/src/app/control/control.component.css @@ -17,6 +17,7 @@ * limitations under the License. * ========================LICENSE_END=================================== */ + .control__section { position: relative; top: -150px; @@ -38,3 +39,14 @@ :host /deep/ ng2-smart-table thead th{ text-align: left; } + +:host /deep/ ng2-st-tbody-custom a.ng2-smart-action.ng2-smart-action-custom-custom { + display: inline-block; + width: 50px; + text-align: center; + font-size: 1.1em; +} + +:host /deep/ i.material-icons.red-close { + color: red !important; +} \ No newline at end of file diff --git a/webapp-frontend/src/app/control/control.component.html b/webapp-frontend/src/app/control/control.component.html index 5ed5d751..0ae71ac6 100644 --- a/webapp-frontend/src/app/control/control.component.html +++ b/webapp-frontend/src/app/control/control.component.html @@ -19,7 +19,7 @@ -->

xApp Control

- +
diff --git a/webapp-frontend/src/app/control/control.component.ts b/webapp-frontend/src/app/control/control.component.ts index b493587a..1c883932 100644 --- a/webapp-frontend/src/app/control/control.component.ts +++ b/webapp-frontend/src/app/control/control.component.ts @@ -17,20 +17,23 @@ * limitations under the License. * ========================LICENSE_END=================================== */ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { LocalDataSource } from 'ng2-smart-table'; import { ControlService } from '../services/control/control.service'; import { Router } from '@angular/router'; +import { ConfirmDialogService } from './../services/ui/confirm-dialog.service' +import { NotificationService } from './../services/ui/notification.service' @Component({ selector: 'app-control', templateUrl: './control.component.html', - styleUrls: ['./control.component.css'] + styleUrls: ['./control.component.css'], + encapsulation: ViewEncapsulation.Emulated, }) export class ControlComponent { - settings = { + settings = { hideSubHeader: true, actions: { columnTitle: 'Actions', @@ -38,21 +41,22 @@ export class ControlComponent { edit: false, delete: false, custom: [ - { name: 'view', title: 'view', }, - ], + { name: 'view', title: 'visibility', }, + { name: 'undeploy', title: 'close', }, + ], position: 'right' }, columns: { xapp: { - title:'xApp Name', + title: 'xApp Name', type: 'string', }, name: { - title:'Instance Name', + title: 'Instance Name', type: 'string', }, - status: { + status: { title: 'Status', type: 'string', }, @@ -67,23 +71,58 @@ export class ControlComponent { txMessages: { title: 'txMessages', type: 'array', - }, + }, rxMessages: { - title: 'rxMessages', - type: 'array', + title: 'rxMessages', + type: 'array', }, }, }; source: LocalDataSource = new LocalDataSource(); - constructor(private service: ControlService, private router: Router) { - this.service.getxAppInstances((instances) => { this.source.load(instances); } ); + constructor( + private service: ControlService, + private router: Router, + private confirmDialogService: ConfirmDialogService, + private notification: NotificationService) { + this.service.getxAppInstances((instances) => { this.source.load(instances); }); + } + + onxAppControlAction(event) { + switch (event.action) { + case 'view': + this.view(event); + break; + case 'undeploy': + this.undeploy(event); + break; + } } view(event): void { - const url = '/xapp'; - this.router.navigate([url, event]); + const url = '/xapp'; + this.router.navigate([url, event]); + } + + undeploy(event): void { + this.confirmDialogService.openConfirmDialog('Are you sure you want to undeploy this xApp ?') + .afterClosed().subscribe(res => { + if (res) { + this.service.undeployxApp(event.data.xapp).subscribe( + response => { + this.service.getxAppInstances((instances) => { this.source.load(instances); }); + switch (response.status) { + case 200: + this.notification.success('xApp undeployed successfully!'); + break; + default: + this.notification.warn('xApp undeploy failed.'); + } + } + ); + } + }); } diff --git a/webapp-frontend/src/app/services/catalog/catalog.service.ts b/webapp-frontend/src/app/services/catalog/catalog.service.ts index fac75d83..f412a4ed 100644 --- a/webapp-frontend/src/app/services/catalog/catalog.service.ts +++ b/webapp-frontend/src/app/services/catalog/catalog.service.ts @@ -23,7 +23,7 @@ import { Observable } from 'rxjs'; @Injectable() export class CatalogService { - + constructor(private http: HttpClient) { } @@ -31,11 +31,12 @@ export class CatalogService { return this.http.get('api/xappmgr/xapps'); } - deployXapp(name) { - return this.http.post('api/xappmgr/xapps', - { - "xAppName": name - }); - } - + deployXapp(name) { + return this.http.post('api/xappmgr/xapps', + { + "xAppName": name + } + , { observe: 'response' }); + } + } diff --git a/webapp-frontend/src/app/services/control/control.service.ts b/webapp-frontend/src/app/services/control/control.service.ts index 1a1dc22a..41819c44 100644 --- a/webapp-frontend/src/app/services/control/control.service.ts +++ b/webapp-frontend/src/app/services/control/control.service.ts @@ -35,6 +35,10 @@ export class ControlService { } + undeployxApp(xapp) { + return this.http.delete(('api/xappmgr/xapps/' + xapp),{ observe: 'response' }) + } + fetchInstance(allxappdata) { var xAppInstances = [] for (const xappindex in allxappdata) { diff --git a/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts b/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts new file mode 100644 index 00000000..4e295212 --- /dev/null +++ b/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 and Nokia + * %% + * 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/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts b/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts new file mode 100644 index 00000000..0eccc631 --- /dev/null +++ b/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts @@ -0,0 +1,41 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2019 AT&T Intellectual Property and Nokia + * %% + * 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'; +import { ConfirmDialogComponent } from './../../ui/confirm-dialog/confirm-dialog.component' + +@Injectable({ + providedIn: 'root' +}) +export class ConfirmDialogService { + + constructor(private dialog: MatDialog) { } + + openConfirmDialog(msg) { + return this.dialog.open(ConfirmDialogComponent, { + width: '480px', + position: { top: "100px" }, + data: { + message: msg + } + }); + } +} diff --git a/webapp-frontend/src/app/services/ui/notification.service.spec.ts b/webapp-frontend/src/app/services/ui/notification.service.spec.ts new file mode 100644 index 00000000..0c2c2fbe --- /dev/null +++ b/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 and Nokia + * %% + * 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/webapp-frontend/src/app/services/ui/notification.service.ts b/webapp-frontend/src/app/services/ui/notification.service.ts new file mode 100644 index 00000000..36d8742e --- /dev/null +++ b/webapp-frontend/src/app/services/ui/notification.service.ts @@ -0,0 +1,52 @@ +/*- + * ========================LICENSE_START================================= + * O-RAN-SC + * %% + * Copyright (C) 2019 AT&T Intellectual Property and Nokia + * %% + * 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 { MatSnackBar, MatSnackBarConfig } from '@angular/material'; + +@Injectable({ + providedIn: 'root' +}) +export class NotificationService { + + constructor(public snackBar: MatSnackBar) { } + + config: MatSnackBarConfig = { + duration: 3000, + horizontalPosition: 'right', + verticalPosition: 'top' + } + + + success(msg) { + this.config['panelClass'] = ['notification', 'success', 'default']; + this.snackBar.open(msg, '', this.config); + } + + warn(msg) { + this.config['panelClass'] = ['notification', 'warn', 'default']; + this.snackBar.open(msg, '', this.config); + } + + error(msg) { + this.config['panelClass'] = ['notification', 'error', 'default']; + this.snackBar.open(msg, '', this.config); + } +} diff --git a/webapp-frontend/src/app/catalog/catalog.component.deploy-dialog.html b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html similarity index 55% rename from webapp-frontend/src/app/catalog/catalog.component.deploy-dialog.html rename to webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html index 467d2e46..ba523dea 100644 --- a/webapp-frontend/src/app/catalog/catalog.component.deploy-dialog.html +++ b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html @@ -19,17 +19,12 @@ --> - - - - \ No newline at end of file +
+
+
+ {{data.message}} +
+ diff --git a/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts new file mode 100644 index 00000000..90336c6e --- /dev/null +++ b/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 and Nokia + * %% + * 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; + + 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/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts new file mode 100644 index 00000000..cf93f300 --- /dev/null +++ b/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 and Nokia + * %% + * 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'; + +@Component({ + selector: 'app-confirm-dialog', + templateUrl: './confirm-dialog.component.html', +}) +export class ConfirmDialogComponent implements OnInit { + + constructor(@Inject(MAT_DIALOG_DATA) public data, + public dialogRef: MatDialogRef) { } + + ngOnInit() { + } + + closeDialog() { + this.dialogRef.close(false); + } +} diff --git a/webapp-frontend/src/styles.scss b/webapp-frontend/src/styles.scss index 4d97b1a3..aceca45e 100644 --- a/webapp-frontend/src/styles.scss +++ b/webapp-frontend/src/styles.scss @@ -9,3 +9,30 @@ html, body { font-family: Helvetica, Arial, sans-serif; } +/* notification */ +.confirm-dialog-container span.content-span { + padding: 35px 16px 20px 16px; + text-align: center; + font-size: 20px; +} + +snack-bar-container.success.default { + background-color: #5cb85c; + color: #fff; +} + +snack-bar-container.warn.default { + background-color: #f99157; + color: #fff; +} + +snack-bar-container.error.default { + background-color: #fc7169; + color: #fff; +} + + +snack-bar-container.notification simple-snack-bar { + font-size: 18px !important; + font-weight: bold; +} \ No newline at end of file