diff options
Diffstat (limited to 'portal-FE-common/src/app/pages')
55 files changed, 7564 insertions, 0 deletions
diff --git a/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.html b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.html index c43e4ba1..ce721c46 100644 --- a/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.html +++ b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.html @@ -69,6 +69,7 @@ ngbDropdownItem>{{app.appName}}</button> </div> </div> + <span class="onap-spinner" *ngIf="isLoading"></span> <!-- User admins list --> <div class="container adminApps" *ngIf="adminAppsRoles.length > 0"> <table mat-table [dataSource]="adminsAppsSource"> diff --git a/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.ts b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.ts index 8f80138e..46a86d7d 100644 --- a/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.ts +++ b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.ts @@ -119,6 +119,7 @@ export class NewAdminComponent implements OnInit { navigateBack() { this.dialogState = 1; + this.changedSelectedUser = null; } removeAdminApp(app: any) { @@ -204,6 +205,7 @@ export class NewAdminComponent implements OnInit { } updateAdminAppsRoles() { + this.isLoading = true; const modalRef = this.ngModal.open(InformationModalComponent); modalRef.componentInstance.title = "Admin Update"; modalRef.componentInstance.message = 'Are you sure you want to make these admin changes?'; @@ -212,7 +214,9 @@ export class NewAdminComponent implements OnInit { this.adminsService.updateAdminAppsRoles({ orgUserId: this.changedSelectedUser.orgUserId, appsRoles: this.adminAppsRoles }).subscribe(_data => { this.passBackNewAdminPopup.emit(_data); this.remindToAddUserIfNecessary(); + this.isLoading = false; }, (_err: HttpErrorResponse) => { + this.isLoading = false; this.passBackNewAdminPopup.emit(_err); const modalErrorRef = this.ngModal.open(ConfirmationModalComponent); modalErrorRef.componentInstance.title = "Error"; @@ -221,6 +225,7 @@ export class NewAdminComponent implements OnInit { } }); } + this.isLoading = false; }, (reason) => { return; }); diff --git a/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.html b/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.html new file mode 100644 index 00000000..cf6b2e5e --- /dev/null +++ b/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.html @@ -0,0 +1,37 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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============================================ + + + -->
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.scss b/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.scss new file mode 100644 index 00000000..7a773398 --- /dev/null +++ b/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.scss @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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============================================ + * + * + */
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.spec.ts b/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.spec.ts new file mode 100644 index 00000000..b37696f3 --- /dev/null +++ b/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.spec.ts @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { CatalogModalComponent } from './catalog-modal.component'; + +describe('CatalogModalComponent', () => { + let component: CatalogModalComponent; + let fixture: ComponentFixture<CatalogModalComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CatalogModalComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CatalogModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.ts b/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.ts new file mode 100644 index 00000000..93a6028a --- /dev/null +++ b/portal-FE-common/src/app/pages/catalog-modal/catalog-modal.component.ts @@ -0,0 +1,53 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-catalog-modal', + templateUrl: './catalog-modal.component.html', + styleUrls: ['./catalog-modal.component.scss'] +}) +export class CatalogModalComponent implements OnInit { + + constructor(public activeModal: NgbActiveModal) { } + + ngOnInit() { + } + +} diff --git a/portal-FE-common/src/app/pages/notification-history/notification-history.component.html b/portal-FE-common/src/app/pages/notification-history/notification-history.component.html new file mode 100644 index 00000000..24203a76 --- /dev/null +++ b/portal-FE-common/src/app/pages/notification-history/notification-history.component.html @@ -0,0 +1,112 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + + <!-- Heading --> + <div class="onap-main-view-title"> + <h1 class="heading-page">Recent Notifications</h1> + </div> + <br /> + <div> + This table shows notifications published in the last 30 days. + </div> + + + <!--Filter Search Box --> + <mat-form-field> + <input matInput type="text" (keyup)="applyFilter($event.target.value)" placeholder="Search in entire table"> + </mat-form-field> + + <!-- Recent Notification History table --> + <table mat-table [dataSource]="notificationsDataSource" matSort> + <!-- Message Source Column --> + <ng-container matColumnDef="messageSource"> + <th id="col1" mat-header-cell *matHeaderCellDef> Message Source </th> + <td id="rowheader_t1_{{i}}-messageSource" mat-cell *matCellDef="let element; let i = index;"> {{element.msgSource}} + </td> + </ng-container> + + <!-- Message Column --> + <ng-container matColumnDef="message"> + <th id="col2" mat-header-cell *matHeaderCellDef> Message </th> + <td id="rowheader_t1_{{i}}-message" mat-cell *matCellDef="let element; let i=index;"> {{element.msgDescription}} + </td> + </ng-container> + + <!-- Start Date (Local Time) Column --> + <ng-container matColumnDef="startDateLocalTime"> + <th id="col3" mat-header-cell *matHeaderCellDef> Start Date (Local Time) </th> + <td id="rowheader_t1_{{i}}-startDateLocalTime" mat-cell *matCellDef="let element; let i=index;"> {{element.startTime}} + </td> + </ng-container> + + <!-- End Date (Local Time) Column --> + <ng-container matColumnDef="endDateLocalTime"> + <th id="col4" mat-header-cell *matHeaderCellDef> End Date (Local Time) </th> + <td id="rowheader_t1_{{i}}-endDateLocalTime" mat-cell *matCellDef="let element; let i=index;">{{element.endTime}} + </td> + </ng-container> + + <!-- Priority Column --> + <ng-container matColumnDef="priority"> + <th id="col4" mat-header-cell *matHeaderCellDef> Priority </th> + <td id="rowheader_t1_{{i}}-priority" mat-cell *matCellDef="let element; let i=index;"> {{element.priority}} + </td> + </ng-container> + + <!-- Created By Column --> + <ng-container matColumnDef="createdBy"> + <th id="col4" mat-header-cell *matHeaderCellDef> Created By </th> + <td id="rowheader_t1_{{i}}-createdBy" mat-cell *matCellDef="let element; let i=index;"> {{element.loginId}} + </td> + </ng-container> + + <!-- Created Time Column --> + <ng-container matColumnDef="createdTime"> + <th id="col4" mat-header-cell *matHeaderCellDef> Created Time </th> + <td id="rowheader_t1_{{i}}-createdTime" mat-cell *matCellDef="let element; let i=index;"> {{element.createdDate}} + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> + </table> + <mat-paginator [pageSizeOptions]="[10, 20]" showFirstLastButtons></mat-paginator> + +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/notification-history/notification-history.component.scss b/portal-FE-common/src/app/pages/notification-history/notification-history.component.scss new file mode 100644 index 00000000..7a773398 --- /dev/null +++ b/portal-FE-common/src/app/pages/notification-history/notification-history.component.scss @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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============================================ + * + * + */
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/notification-history/notification-history.component.spec.ts b/portal-FE-common/src/app/pages/notification-history/notification-history.component.spec.ts new file mode 100644 index 00000000..6896359a --- /dev/null +++ b/portal-FE-common/src/app/pages/notification-history/notification-history.component.spec.ts @@ -0,0 +1,63 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { NotificationHistoryComponent } from './notification-history.component'; + +describe('NotificationHistoryComponent', () => { + let component: NotificationHistoryComponent; + let fixture: ComponentFixture<NotificationHistoryComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NotificationHistoryComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NotificationHistoryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/notification-history/notification-history.component.ts b/portal-FE-common/src/app/pages/notification-history/notification-history.component.ts new file mode 100644 index 00000000..bad509c8 --- /dev/null +++ b/portal-FE-common/src/app/pages/notification-history/notification-history.component.ts @@ -0,0 +1,91 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { MatTableDataSource } from '@angular/material'; +import { MatSort, MatPaginator } from '@angular/material'; +import { NotificationService } from '../../shared/services/index'; + +@Component({ + selector: 'app-notification-history', + templateUrl: './notification-history.component.html', + styleUrls: ['./notification-history.component.scss'] +}) +export class NotificationHistoryComponent implements OnInit { + + result: any; + notificationHistory: any= []; + displayedColumns: string[] = ['messageSource', 'message', 'startDateLocalTime','endDateLocalTime', 'priority', 'createdBy', 'createdTime']; + notificationsDataSource = new MatTableDataSource(this.notificationHistory); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + constructor(public notificationService: NotificationService) { } + + ngOnInit() { + this.getNotificationHistory(); + } + + getNotificationHistory(){ + //console.log("getNotificationHistory called"); + this.notificationService.getNotificationHistory() + .subscribe(_data => { + this.result = _data; + if (this.result == null || this.result == 'undefined') { + console.log('NotificationService::getNotificationHistory Failed: Result or result.data is null'); + }else { + //console.log('Notification Data ::',this.result); + this.notificationHistory = this.result; + this.populateTableData(this.notificationHistory); + } + }, error =>{ + console.log(error); + }); + } + + populateTableData(notificationHistory: Array<Object>){ + this.notificationsDataSource = new MatTableDataSource(notificationHistory); + this.notificationsDataSource.sort = this.sort; + this.notificationsDataSource.paginator = this.paginator; + } + + applyFilter(filterValue: string) { + this.notificationsDataSource.filter = filterValue.trim().toLowerCase(); + } + +} diff --git a/portal-FE-common/src/app/pages/role/add-role/add-role.component.html b/portal-FE-common/src/app/pages/role/add-role/add-role.component.html new file mode 100644 index 00000000..e3393a81 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/add-role/add-role.component.html @@ -0,0 +1,95 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <div class="modal-header"> + <h4 class="modal-title">{{title}}</h4> + <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross')"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <span class="onap-spinner" *ngIf="showSpinner"></span> + <div class="input-group"> + <div class="input-group-prepend"> + <span class="input-group-text">Name</span> + </div> + <input type="text" aria-label="name" maxlength="300" [(ngModel)]="role.name" placeholder="Enter name..." + [value]="role.name" class="form-control"> + </div> + + <div class="input-group"> + <div class="input-group-prepend"> + <span class="input-group-text">Priority</span> + </div> + <input type="text" aria-label="priority" maxlength="300" [(ngModel)]="role.priority" [value]="role.priority" + placeholder="Enter priority..." class="form-control"> + </div> + <br> + <div *ngIf="showGlobalRole" class="form-check" style="text-align:right;"> + <mat-checkbox class="example-margin" [(ngModel)]="isGlobalRoleChecked.isChecked"> Global Role </mat-checkbox> + </div> + <div class="role-function-list" *ngIf="dialogState === 2"> + <table mat-table [dataSource]="roleFunctionsDataSource"> + <!-- Active Column --> + <ng-container matColumnDef="active"> + <th id="col3" mat-header-cell *matHeaderCellDef> </th> + <td id="rowheader_t1_{{i}}-userId" mat-cell *matCellDef="let element; let i=index;"> + <mat-slide-toggle [(ngModel)]="element.selected" (change)="toggleRoleFunction(element)"></mat-slide-toggle> + </td> + </ng-container> + + <!-- Function Name Column --> + <ng-container matColumnDef="name"> + <th id="col2" mat-header-cell *matHeaderCellDef > Function Name </th> + <td id="rowheader_t1_{{i}}-lastName" mat-cell *matCellDef="let element; let i=index;"> + {{element.name}} + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> + </table> + </div> + </div> + <div class="modal-footer"> + <button type="submit" class="btn btn-primary" [disabled]="!role.name || showSpinner" + (click)="saveRole()">Save</button> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Cancel</button> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/role/add-role/add-role.component.scss b/portal-FE-common/src/app/pages/role/add-role/add-role.component.scss new file mode 100644 index 00000000..54f59fcd --- /dev/null +++ b/portal-FE-common/src/app/pages/role/add-role/add-role.component.scss @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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============================================ + * + * + */ +.onap-spinner { + z-index: 9999; +} + +.role-function-list{ + overflow-y: auto; + height: 500px; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/role/add-role/add-role.component.spec.ts b/portal-FE-common/src/app/pages/role/add-role/add-role.component.spec.ts new file mode 100644 index 00000000..1104010d --- /dev/null +++ b/portal-FE-common/src/app/pages/role/add-role/add-role.component.spec.ts @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { AddRoleComponent } from './add-role.component'; + +describe('AddRoleComponent', () => { + let component: AddRoleComponent; + let fixture: ComponentFixture<AddRoleComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AddRoleComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AddRoleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/role/add-role/add-role.component.ts b/portal-FE-common/src/app/pages/role/add-role/add-role.component.ts new file mode 100644 index 00000000..5c0a376e --- /dev/null +++ b/portal-FE-common/src/app/pages/role/add-role/add-role.component.ts @@ -0,0 +1,190 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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, Input, Output, EventEmitter } from '@angular/core'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { RoleService } from 'src/app/shared/services'; +import { environment } from 'src/environments/environment'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { Role } from 'src/app/shared/model'; +import { MatTableDataSource } from '@angular/material'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; + +@Component({ + selector: 'app-add-role', + templateUrl: './add-role.component.html', + styleUrls: ['./add-role.component.scss'] +}) +export class AddRoleComponent implements OnInit { + + @Input() title: string; + @Input() appId: string; + @Input() dialogState: number; + @Input() availableRole: any; + @Input() appRoleFunctions: any; + @Output() passBackAddRolePopup: EventEmitter<any> = new EventEmitter(); + availableRoleFunctions: any; + isGlobalRoleChecked = { + isChecked: false + } + role: Role; + roleFunctions: any; + showGlobalRole: boolean; + api = environment.api; + showSpinner: boolean; + displayedColumns: string[] = ['active', 'name']; + roleFunctionsDataSource = new MatTableDataSource(this.roleFunctions); + finalSelectedRoleFunctions: any; + constructor(public activeModal: NgbActiveModal, public ngbModal: NgbModal, private roleService: RoleService, public http: HttpClient) { } + + ngOnInit() { + this.role = new Role; + this.finalSelectedRoleFunctions = []; + if (this.appId == '1') + this.showGlobalRole = true; + if (this.dialogState === 2) { + this.isGlobalRoleChecked.isChecked = (this.availableRole.name.includes('global_')) ? true : false; + this.availableRoleFunctions = []; + this.role = this.availableRole; + this.roleFunctionsDataSource = new MatTableDataSource(this.setSelectedRoleFucntions()); + } + } + + setSelectedRoleFucntions() { + for (var i = 0; i < this.appRoleFunctions.length; i++) { + var availableRoleFunction = this.appRoleFunctions[i]; + availableRoleFunction['selected'] = false; + for (var j = 0; j < this.availableRole.roleFunctions.length; j++) { + if (availableRoleFunction.code === this.availableRole.roleFunctions[j].code + && availableRoleFunction.type === this.availableRole.roleFunctions[j].type + && availableRoleFunction.action === this.availableRole.roleFunctions[j].action) { + availableRoleFunction.selected = true; + console.log(availableRoleFunction.selected); + } + } + this.availableRoleFunctions.push(availableRoleFunction); + } + return this.availableRoleFunctions; + } + + toggleRoleFunction(_element) { + if (this.appRoleFunctions) { + for (var i = 0; i < this.appRoleFunctions.length; i++) { + var availableRoleFunction = this.appRoleFunctions[i]; + if (availableRoleFunction.selected && !this.finalSelectedRoleFunctions.includes(availableRoleFunction)) { + this.finalSelectedRoleFunctions.push(availableRoleFunction); + } + } + } + if (!_element.selected) { + for (var i = 0; i < this.finalSelectedRoleFunctions.length; i++) { + var availableRoleFunction = this.finalSelectedRoleFunctions[i]; + if (availableRoleFunction.code == _element.code + && availableRoleFunction.type == _element.type + && availableRoleFunction.action == _element.action) { + this.finalSelectedRoleFunctions.splice(i, 1); + } + } + } + } + + + + saveRole() { + var uuu = this.api.saveRole.replace(':appId', this.appId); + if (this.isGlobalRoleChecked.isChecked) { + this.role.name = (this.role.name.indexOf('global_') == -1) ? ('global_' + this.role.name) : (this.role.name); + this.saveOrUpdateRole(uuu); + } else { + var roleName = this.role.name.toLowerCase(); + if (roleName.includes('global_')) { + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = 'Confirmation'; + modalInfoRef.componentInstance.message = 'Global prefix:"global_" can only be used when the global flag is checked for the role name:' + this.role.name + '. Please try again!'; + } else { + this.role.childRoles = []; + this.role.roleFunctions = []; + this.saveOrUpdateRole(uuu); + } + } + } + + saveOrUpdateRole(uuu) { + var confirmMessage = (this.dialogState === 2) ? 'You are about to update the role/role functions. Do you want to continue?' : 'You are about to create the role `' + this.role.name + '` . Do you want to continue?'; + const modalInfoRef = this.ngbModal.open(InformationModalComponent); + modalInfoRef.componentInstance.title = 'Confirmation'; + modalInfoRef.componentInstance.message = confirmMessage; + modalInfoRef.result.then((_res) => { + if (_res === 'Ok') { + //overriding the final list of rolefunctions to role + if (this.finalSelectedRoleFunctions.length > 0) + this.role.roleFunctions = this.finalSelectedRoleFunctions; + var postData = { + role: this.role, + childRoles: this.role.childRoles, + roleFunctions: this.role.roleFunctions + }; + this.showSpinner = true + this.http.post(uuu, postData).toPromise().then((res: any) => { + this.showSpinner = false; + if (res && res.role) { + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = 'Success'; + modalInfoRef.componentInstance.message = 'Update Successful.'; + this.passBackAddRolePopup.emit(this.appId); + + } + else { + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = 'Error'; + modalInfoRef.componentInstance.message = res.error; + } + }, (res: HttpErrorResponse) => { + this.showSpinner = false; + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = 'Error'; + modalInfoRef.componentInstance.message = 'Error while saving.' + res.status; + } + ); + } + }, (_dismiss) => { + + }) + } + +} diff --git a/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.html b/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.html new file mode 100644 index 00000000..e9c7bd2d --- /dev/null +++ b/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.html @@ -0,0 +1,323 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <div class="modal-header"> + <h4 class="modal-title">{{title}}</h4> + <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross')"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <div *ngIf="dialogState === 1"> + <mat-form-field> + <mat-label> Select Upload Type: </mat-label> + <mat-select [(ngModel)]="selectedUploadDropdown"> + <mat-option *ngFor="let uploadOpt of ngRepeatBulkUploadOptions" [value]="uploadOpt"> + {{uploadOpt.title}}</mat-option> + </mat-select> + </mat-form-field> + </div> + <div *ngIf="dialogState === 2"> + <div class="upload-instructions">Select Upload File:</div> + <!-- input type=file is difficult to style. + Instead use a label styled as a button. --> + <label class="file-label"> + <input type="file" (change)="onFileSelect($event.target)" accept="text/plain,.csv" /> + </label>{{selectedFile}} + <div *ngIf="selectedUploadDropdown.value === 'functions'" class="upload-instructions">File must be .csv or .txt + and one entry per line with this format: + <pre>Function Type, Function Instance, Function Action, Function Name</pre> + </div> + <div *ngIf="selectedUploadDropdown.value === 'roles'" class="upload-instructions">File must be .csv or .txt and + one entry per line with this format: + <pre>Role Name, Priority (Optional)</pre> + </div> + <div *ngIf="selectedUploadDropdown.value === 'roleFunctions'" class="upload-instructions">File must be .csv or + .txt and one entry per line with this format: + <pre>Role Name, Function Type, Function Instance, Function Action, Function Name</pre> + </div> + <div *ngIf="selectedUploadDropdown.value === 'globalRoleFunctions'" class="upload-instructions">File must be .csv + or .txt and one entry per line with this format: + <pre>Global Role Name, Function Type, Function Instance, Function Action, Function Name</pre> + </div> + </div> + <div class="bulk-upload" *ngIf="dialogState === 3"> + + <div *ngIf="selectedUploadDropdown.value === 'roles' && !isProcessing">Click OK to upload the valid + roles. Invalid or existing roles will be ignored. + <p style="font-size: 80%;"> + <span id="required" style="color: Red;" visible="false">*</span>Name can only contain alphanumeric + characters, dots(.), forward slashes(/), and underscores(_) + </p> + </div> + + <div *ngIf="selectedUploadDropdown.value === 'functions' && !isProcessing">Click OK to upload the valid + functions. Invalid or existing functions will be ignored. + <p style="font-size: 80%;"> + <span id="required" style="color: Red; font-size: 180%;" visible="false">*</span>Type can only contain + alphanumeric + characters, dots(.) and underscores(_) + </p> + <p style="font-size: 80%;"> + <span id="required" style="color: Red; font-size: 180%;" visible="false">*</span>Action can only contain + alphanumeric + characters, hyphens(-), dots(.) and underscores(_) and single + asterisk character(*) + </p> + <p style="font-size: 80%;"> + <span id="required" style="color: Red; font-size: 180%;" visible="false">*</span>Instance/Code can only + contain alphanumeric + characters, hyphens(-), dots(.), colons(:), forwardSlash(/) , + asterisk(*) and underscores(_) + </p> + <p style="font-size: 80%;"> + <span id="required" style="color: Red; font-size: 180%;" visible="false">*</span>Name can only contain + alphanumeric + characters, spaces, hyphens(-), dots(.) and underscores(_) + </p> + </div> + + <div *ngIf="selectedUploadDropdown.value === 'roleFunctions' && !isProcessing">Click OK to upload the valid + role functions. Invalid or existing functions will be ignored. + </div> + <div *ngIf="selectedUploadDropdown.value === 'globalRoleFunctions' && !isProcessing">Click OK to upload the valid + global role functions. Invalid or existing functions will be ignored. + </div> + <!-- progress indicator --> + <div class="upload-instructions" [hidden]="!isProcessing"> + {{progressMsg}} + <br> + <br> + <span class="onap-spinner"></span> + </div> + + <!-- progress indicator --> + <div class="upload-instructions" [hidden]="!isProcessedRecords"> + {{conformMsg}} + </div> + <div [hidden]="isProcessing || isProcessedRecords"> + + <table [hidden]="selectedUploadDropdown.value !== 'functions'" mat-table + [dataSource]="uploadFunctionsDataSource"> + + <ng-container matColumnDef="line"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Line + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;">{{element.line}} + </td> + </ng-container> + <ng-container matColumnDef="type"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Type + <td id="rowheader_t1_{{i}}-roles" mat-cell *matCellDef="let element; let i=index;"> + {{element.type}} + </td> + </ng-container> + <ng-container matColumnDef="instance"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Instance/Code + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.instance }} + </td> + </ng-container> + <ng-container matColumnDef="action"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Action + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.action}} + </td> + </ng-container> + <ng-container matColumnDef="name"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Name + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.name}} + </td> + </ng-container> + <ng-container matColumnDef="status"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Status + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.status}} + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedFunctionColumns; sticky: true"></tr> + <tr mat-row id="table-row-{{i}}" *matRowDef="let row; columns: displayedFunctionColumns; let i = index;"></tr> + </table> + + <table [hidden]="selectedUploadDropdown.value !== 'roles'" mat-table [dataSource]="uploadRolesDataSource"> + + <ng-container matColumnDef="line"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Line + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;">{{element.line}} + </td> + </ng-container> + <ng-container matColumnDef="name"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Name + <td id="rowheader_t1_{{i}}-roles" mat-cell *matCellDef="let element; let i=index;"> + {{element.name}} + </td> + </ng-container> + <ng-container matColumnDef="priority"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Priority + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.priority}} + </td> + </ng-container> + <ng-container matColumnDef="status"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Status + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.status}} + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedRoleColumns; sticky: true"></tr> + <tr mat-row id="table-row-{{i}}" *matRowDef="let row; columns: displayedRoleColumns; let i = index;"></tr> + </table> + + + <table [hidden]="selectedUploadDropdown.value !== 'roleFunctions'" mat-table + [dataSource]="uploadRoleFunctionsDataSource"> + + <ng-container matColumnDef="line"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Line + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;">{{element.line}} + </td> + </ng-container> + <ng-container matColumnDef="role"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Role Name + <td id="rowheader_t1_{{i}}-roles" mat-cell *matCellDef="let element; let i=index;"> + {{element.role}} + </td> + </ng-container> + <ng-container matColumnDef="type"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef>Function Type + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.type}} + </td> + </ng-container> + <ng-container matColumnDef="instance"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef>Function Instance + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.instance}} + </td> + </ng-container> + <ng-container matColumnDef="action"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef>Function Action + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.action}} + </td> + </ng-container> + <ng-container matColumnDef="name"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef>Function Name + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.name}} + </td> + </ng-container> + <ng-container matColumnDef="status"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Status + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.status}} + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedRoleFunctionColumns; sticky: true"></tr> + <tr mat-row id="table-row-{{i}}" *matRowDef="let row; columns: displayedRoleFunctionColumns; let i = index;"> + </tr> + </table> + + <table [hidden]="selectedUploadDropdown.value !== 'globalRoleFunctions'" mat-table + [dataSource]="uploadGlobalRoleFunctionsDataSource"> + + <ng-container matColumnDef="line"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Line + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;">{{element.line}} + </td> + </ng-container> + <ng-container matColumnDef="role"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Global Role Name + <td id="rowheader_t1_{{i}}-roles" mat-cell *matCellDef="let element; let i=index;"> + {{element.role}} + </td> + </ng-container> + <ng-container matColumnDef="type"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef>Function Type + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.type}} + </td> + </ng-container> + <ng-container matColumnDef="instance"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef>Function Instance + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.instance}} + </td> + </ng-container> + <ng-container matColumnDef="action"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef>Function Action + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.action}} + </td> + </ng-container> + <ng-container matColumnDef="name"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef>Function Name + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.name}} + </td> + </ng-container> + <ng-container matColumnDef="status"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Status + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.status}} + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedGlobalRoleFunctionColumns; sticky: true"></tr> + <tr mat-row id="table-row-{{i}}" + *matRowDef="let row; columns: displayedGlobalRoleFunctionColumns; let i = index;"></tr> + </table> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="submit" class="btn btn-primary" [hidden]="dialogState !== 1" + (click)="navigateUploadScreen()">Next</button> + <button type="submit" class="btn btn-primary" [hidden]="dialogState !== 2" + (click)="navigateSelectTypeUpload()">Back</button> + <button type="submit" class="btn btn-primary" [hidden]="dialogState !== 3" (click)="updateInDB()">Ok</button> + <button type="submit" class="btn btn-primary" [hidden]="dialogState !== 3" + (click)="navigateUploadScreen()">Cancel</button> + <button type="button" class="btn btn-primary" [hidden]="dialogState !== 1" + (click)="activeModal.close('Close')">Cancel</button> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.scss b/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.scss new file mode 100644 index 00000000..6ed732b7 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.scss @@ -0,0 +1,41 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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============================================ + * + * + */ +.container-bulk-upload { + overflow-y: auto; + height: 250px; + }
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.spec.ts b/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.spec.ts new file mode 100644 index 00000000..d4006a08 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.spec.ts @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { BulkUploadRoleComponent } from './bulk-upload-role.component'; + +describe('BulkUploadRoleComponent', () => { + let component: BulkUploadRoleComponent; + let fixture: ComponentFixture<BulkUploadRoleComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ BulkUploadRoleComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BulkUploadRoleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.ts b/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.ts new file mode 100644 index 00000000..698889b7 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/bulk-upload-role/bulk-upload-role.component.ts @@ -0,0 +1,978 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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, Input } from '@angular/core'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { RoleService, FunctionalMenuService } from 'src/app/shared/services'; +import { MatTableDataSource } from '@angular/material'; + +@Component({ + selector: 'app-bulk-upload-role', + templateUrl: './bulk-upload-role.component.html', + styleUrls: ['./bulk-upload-role.component.scss'] +}) +export class BulkUploadRoleComponent implements OnInit { + + + @Input() title: string; + closeResult: string; + @Input() appId: string; + @Input() dialogState: number; + ngRepeatBulkUploadOptions = [ + { id: '1', title: 'Functions', value: 'functions' }, + { id: '2', title: 'Roles', value: 'roles' }, + { id: '3', title: 'Role Functions', value: 'roleFunctions' }, + { id: '4', title: 'Global Role Functions', value: 'globalRoleFunctions' } + ]; + selectedUploadDropdown: any; + uploadTypeInstruction = "Function Type, Function Instance, Function Action, Function Name"; + uploadCheck: boolean; + isProcessing: boolean; + conformMsg: string; + progressMsg: string; + uploadFile: any; + // Roles fetched from Role service + appRoleFuncsResult = []; + // Functions fetched from Role service + appFunctionsResult = []; + // Global roles fetched from Role service + appGlobalRolesResult = []; + changeUploadTypeInstruction(typeInstrc) { + switch (typeInstrc) { + case 'functions': + this.uploadTypeInstruction = "Function Type, Function Instance, Function Action, Function Name"; + break; + case 'roles': + this.uploadTypeInstruction = "Role Name, Priority (Optional)"; + break; + case 'roleFunctions': + this.uploadTypeInstruction = "Role Name, Function Type, Function Instance, Function Action, Function Name"; + break; + default: + this.uploadTypeInstruction = "Global Role Name, Function Type, Function Instance, Function Action, Function Name"; + } + }; + + fileModel: {}; + step1: boolean; + fileSelected: boolean; + isProcessedRecords: boolean; + displayedFunctionColumns: string[] = ['line', 'type', 'instance', 'action', 'name', 'status']; + uploadFunctionsDataSource = new MatTableDataSource(this.uploadFile); + displayedRoleColumns: string[] = ['line', 'name', 'priority', 'status']; + uploadRolesDataSource = new MatTableDataSource(this.uploadFile); + displayedRoleFunctionColumns: string[] = ['line', 'role', 'type', 'instance', 'action', 'name', 'status']; + uploadRoleFunctionsDataSource = new MatTableDataSource(this.uploadFile); + displayedGlobalRoleFunctionColumns: string[] = ['line', 'role', 'type', 'instance', 'action', 'name', 'status']; + uploadGlobalRoleFunctionsDataSource = new MatTableDataSource(this.uploadFile); + + constructor(public activeModal: NgbActiveModal, public ngbModal: NgbModal, private roleService: RoleService, private functionalMenuService: FunctionalMenuService) { } + + ngOnInit() { + this.selectedUploadDropdown = this.ngRepeatBulkUploadOptions[0]; + this.fileModel = {}; + // Enable modal controls + this.step1 = true; + + this.fileSelected = false; + + this.isProcessedRecords = false; + } + + navigateUploadScreen() { + if (this.selectedUploadDropdown.value === 'functions') { + this.title = 'Bulk Upload Functions Confirmation'; + } else if (this.selectedUploadDropdown.value === 'roles') { + this.title = 'Bulk Upload Roles Confirmation'; + } else if (this.selectedUploadDropdown.value === 'roleFunctions') { + this.title = 'Bulk Upload Role-Functions Confirmation'; + } else { + this.title = 'Bulk Upload Global-Role-Functions Confirmation'; + } + this.dialogState = 2; + } + + navigateSelectTypeUpload() { + this.title = 'Bulk Upload Role-Function'; + this.dialogState = 1; + } + + updateInDB() { + if (this.selectedUploadDropdown.value === 'functions') { + this.updateFunctionsInDB(); + } else if (this.selectedUploadDropdown.value === 'roles') { + this.updateRolesInDB(); + } else if (this.selectedUploadDropdown.value === 'roleFunctions') { + this.updateRoleFunctionsInDB(); + } else { + this.updateGlobalRoleFunctionsInDB(); + } + } + // Answers a function that compares properties with the specified name. + getSortOrder = (prop, foldCase) => { + return function (a, b) { + let aProp = foldCase ? a[prop].toLowerCase() : a[prop]; + let bProp = foldCase ? b[prop].toLowerCase() : b[prop]; + if (aProp > bProp) + return 1; + else if (aProp < bProp) + return -1; + else + return 0; + } + } + + onFileLoad(fileLoadedEvent) { + const textFromFileLoaded = fileLoadedEvent.target.result; + let lines = textFromFileLoaded.split('\n'); + var result = []; + var len, i, line, o; + switch (this.selectedUploadDropdown.value) { + case 'functions': + for (len = lines.length, i = 1; i <= len; ++i) { + line = lines[i - 1].trim(); + + if (line.length == 0) { + // console.log("Skipping blank line"); + result.push({ + line: i, + type: '', + instance: '', + action: '', + name: '', + status: 'Blank line' + }); + continue; + } + o = line.split(','); + if (o.length !== 4) { + // other lengths not valid for upload + result.push({ + line: i, + type: o[0], + instance: o[1], + action: o[2], + name: '', + status: 'Failed to find 4 comma-separated values' + }); + } + else { + // console.log("Valid line: ", val); + let entry = { + line: i, + type: o[0], + instance: o[1], + action: o[2], + name: o[3] + // leave status undefined, this + // could be valid. + }; + if (o[0].toLowerCase() === 'type') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].toLowerCase() === 'instance') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].toLowerCase() === 'action') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].toLowerCase() === 'name') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].trim() == '' || o[1].trim() == '' || o[2].trim() == '' || o[3].trim() == '') { + // defend against line with only a + // single comma etc. + entry['status'] = 'Failed to find non-empty values'; + } + result.push(entry); + } // len 2 + }// for + break; + case 'roles': + for (len = lines.length, i = 1; i <= len; ++i) { + line = lines[i - 1].trim(); + if (line.length == 0) { + // console.log("Skipping blank line"); + result.push({ + line: i, + role: '', + priority: '', + status: 'Blank line' + }); + continue; + } + o = line.split(','); + if (o.length === 0 && line.length !== 0) { + // other lengths not valid for upload + result.push({ + line: i, + role: o[0], + priority: null + }); + } + else { + // console.log("Valid line: ", val); + let entry = { + line: i, + role: o[0], + priority: o[1] + // leave status undefined, this + // could be valid. + }; + if (o[0].toLowerCase() === 'role') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + if (o[0].toLowerCase() === 'priority') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].trim() == '') { + // defend against line with only a + // single comma etc. + entry['status'] = 'Failed to find non-empty values'; + } + result.push(entry); + } // len 2 + } + break; + case 'roleFunctions': + case 'globalRoleFunctions': + for (len = lines.length, i = 1; i <= len; ++i) { + line = lines[i - 1].trim(); + if (line.length == 0) { + // console.log("Skipping blank line"); + result.push({ + line: i, + role: '', + type: '', + instance: '', + action: '', + name: '', + status: 'Blank line' + }); + continue; + } + o = line.split(','); + if (o.length !== 5) { + // other lengths not valid for upload + result.push({ + line: i, + role: o[0], + type: o[1], + instance: o[2], + action: o[3], + name: '', + status: 'Failed to find 4 comma-separated values' + }); + } + else { + // console.log("Valid line: ", val); + let entry = { + line: i, + role: o[0], + type: o[1], + instance: o[2], + action: o[3], + name: o[4] + // leave status undefined, this + // could be valid. + }; + if (o[0].toLowerCase() === 'role') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } else if (o[0].toLowerCase() === 'type') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].toLowerCase() === 'instance') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].toLowerCase() === 'action') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].toLowerCase() === 'name') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].trim() == '' || o[1].trim() == '' || o[2].trim() == '' || o[3].trim() == '' || o[4].trim() == '') { + // defend against line with only a + // single comma etc. + entry['status'] = 'Failed to find non-empty values'; + } + result.push(entry); + } // len 2 + } + break; + default: + result = []; + break; + } + return result; + } + + + onFileSelect(input: HTMLInputElement) { + var validExts = new Array(".csv", ".txt"); + var fileExt = input.value; + fileExt = fileExt.substring(fileExt.lastIndexOf('.')); + if (validExts.indexOf(fileExt) < 0) { + const modalFileErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalFileErrorRef.componentInstance.title = 'Confirmation'; + modalFileErrorRef.componentInstance.message = 'Invalid file selected, valid files are of ' + + validExts.toString() + ' types.' + this.uploadCheck = false; + return false; + } + else { + const files = input.files; + this.isProcessing = true; + this.conformMsg = ''; + this.isProcessedRecords = true; + this.progressMsg = 'Reading upload file..'; + if (files && files.length) { + this.uploadCheck = true; + const fileToRead = files[0]; + const fileReader = new FileReader(); + fileReader.readAsText(fileToRead, "UTF-8"); + fileReader.onloadend = (e) => { + this.uploadFile = this.onFileLoad(e); + if (this.selectedUploadDropdown.value === 'roles') { + // if (debug){ + // $log.debug('BulkRoleAndFunctionsModalCtrl::readValidateFile onload: data length is ' + this.uploadFile.length); + // } + this.progressMsg = 'Fetching & validating application roles...'; + // fetch app roles + this.roleService.getRoles(this.appId).toPromise().then((appRoles: any) => { + // if (debug){ + // $log.debug("BulkRoleAndFunctionsModalCtrl::readValidateFile: getRoles returned " + JSON.stringify(appFunctions.data)); + // } + let availableRolesList = JSON.parse(appRoles.data); + this.appRoleFuncsResult = availableRolesList.availableRoles; + this.evalAppRolesCheckResults(); + // Re sort by line for the confirmation dialog + this.uploadFile.sort(this.getSortOrder('line', false)); + // We're done, confirm box may show the table + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::readValidateFile inner-then ends'); + this.progressMsg = 'Done.'; + this.isProcessing = false; + this.isProcessedRecords = false; + }, (error) => { + // $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app roles info'); + this.isProcessing = false; + this.isProcessedRecords = false; + }); + + this.uploadRolesDataSource = new MatTableDataSource(this.uploadFile); + this.dialogState = 3; + } else if (this.selectedUploadDropdown.value === 'roleFunctions') { + // if (debug) { + // $log.debug('BulkRoleAndFunctionsModalCtrl::readValidateFile onload: data length is ' + this.uploadFile.length); + // } + this.progressMsg = 'Fetching & validating application role functions...'; + //fetch app functions + this.roleService.getRoleFunctionList(this.appId).toPromise().then((appFunctions: any) => { + // if (debug) + // $log.debug("BulkRoleAndFunctionsModalCtrl::readValidateFile: getRoleFunctionList returned " + JSON.stringify(appFunctions.data)); + let availableRoleFunctionsList = JSON.parse(appFunctions.data); + this.appFunctionsResult = availableRoleFunctionsList.availableRoleFunctions; + // fetch app roles + this.roleService.getRoles(this.appId).toPromise().then((appRoles: any) => { + // if (debug) { + // $log.debug("BulkRoleAndFunctionsModalCtrl::readValidateFile: getRoles returned " + JSON.stringify(appFunctions.data)); + // } + let availableRolesList = JSON.parse(appRoles.data); + this.appRoleFuncsResult = availableRolesList.availableRoles; + this.evalAppRoleFuncsCheckResults(this.selectedUploadDropdown.value); + // Re sort by line for the confirmation dialog + this.uploadFile.sort(this.getSortOrder('line', false)); + // We're done, confirm box may show the table + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::readValidateFile inner-then ends'); + this.progressMsg = 'Done.'; + this.isProcessing = false; + this.isProcessedRecords = false; + }, (error) => { + // $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app roles info'); + this.isProcessing = false; + this.isProcessedRecords = false; + }); + }, (error) => { + // $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app functions info'); + this.isProcessing = false; + } + ); + this.uploadRoleFunctionsDataSource = new MatTableDataSource(this.uploadFile); + this.dialogState = 3; + } else if (this.selectedUploadDropdown.value === 'functions') { + // if (debug) { + // $log.debug('BulkRoleAndFunctionsModalCtrl::readValidateFile onload: data length is ' + this.uploadFile.length); + // } + this.progressMsg = 'Fetching & validating the application functions...'; + // fetch app functions + this.roleService.getRoleFunctionList(this.appId).toPromise().then((appFunctions: any) => { + // if (debug) + // $log.debug("BulkRoleAndFunctionsModalCtrl::readValidateFile: getRoleFunctionList returned " + JSON.stringify(appFunctions.data)); + let availableRoleFunctionsList = JSON.parse(appFunctions.data); + this.appFunctionsResult = availableRoleFunctionsList.availableRoleFunctions; + this.verifyFunctions(); + this.evalAppFunctionsCheckResults(); + // Re sort by line for the confirmation dialog + this.uploadFile.sort(this.getSortOrder('line', false)); + // We're done, confirm box may show the table + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::readValidateFile inner-then ends'); + this.progressMsg = 'Done.'; + this.isProcessing = false; + this.isProcessedRecords = false; + }, (error) => { + // $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app functions info'); + this.isProcessing = false; + this.isProcessedRecords = false; + } + ); + this.uploadFunctionsDataSource = new MatTableDataSource(this.uploadFile); + this.dialogState = 3; + } else if (this.selectedUploadDropdown.value === 'globalRoleFunctions') { + // if (debug) { + // $log.debug('BulkRoleAndFunctionsModalCtrl::readValidateFile onload: data length is ' + this.uploadFile.length); + // } + this.progressMsg = 'Fetching application global role functions...'; + //fetch app functions + this.roleService.getRoleFunctionList(this.appId).toPromise().then((appFunctions: any) => { + // if (debug) + // $log.debug("BulkRoleAndFunctionsModalCtrl::readValidateFile: getRoleFunctionList returned " + JSON.stringify(appFunctions.data)); + let availableRoleFunctionsList = JSON.parse(appFunctions.data); + this.appFunctionsResult = availableRoleFunctionsList.availableRoleFunctions; + // fetch app roles + this.roleService.getRoles(this.appId).toPromise().then((appRoles: any) => { + // if (debug) { + // $log.debug("BulkRoleAndFunctionsModalCtrl::readValidateFile: getRoles returned " + JSON.stringify(appFunctions.data)); + // } + let availableRolesList = JSON.parse(appRoles.data); + this.appRoleFuncsResult = availableRolesList.availableRoles; + this.appRoleFuncsResult.forEach((appRole) => { + if (appRole.name.toLowerCase().startsWith("global_")) { + this.appGlobalRolesResult.push(appRole); + } + }); + this.evalAppRoleFuncsCheckResults(this.selectedUploadDropdown.value); + // Re sort by line for the confirmation dialog + this.uploadFile.sort(this.getSortOrder('line', false)); + // We're done, confirm box may show the table + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::readValidateFile inner-then ends'); + this.progressMsg = 'Done.'; + this.isProcessing = false; + this.isProcessedRecords = false; + }, (error) => { + // $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app roles info'); + this.isProcessing = false; + this.isProcessedRecords = false; + }); + this.uploadGlobalRoleFunctionsDataSource = new MatTableDataSource(this.uploadFile); + this.dialogState = 3; + }, (error) => { + // $log.error('BulkUserModalCtrl::readValidateFile: failed retrieving app functions info'); + this.isProcessing = false; + } + ); + } + } + } + } + } + + + /** + * Evaluates the result set returned by the role service. + * Sets an uploadFile array element status if a functions is not + * defined. Reads and writes scope variable uploadFile. Reads + * closure variable appFunctionsResult. + */ + verifyFunctions() { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::verifyFunctions: appFunctions is ' + JSON.stringify(appFunctionsResult)); + // check functions in upload file against defined app functions + this.uploadFile.forEach((uploadRow) => { + // skip rows that already have a defined status: headers etc. + if (uploadRow.status) { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::verifyFunctions: skip row ' + uploadRow.line); + return; + } + for (var i = 0; i < this.appFunctionsResult.length; i++) { + if (uploadRow.type.toUpperCase() === this.appFunctionsResult[i].type.toUpperCase() + && uploadRow.instance.toUpperCase() === this.appFunctionsResult[i].code.toUpperCase() + && uploadRow.action.toUpperCase() === this.appFunctionsResult[i].action.toUpperCase()) { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::verifyFunctions: match on function ' + uploadRow.type, + // uploadRow.instance, uploadRow.type, uploadRow.type); + break; + } + } + }); // foreach + }; // verifyFunctions + + /** + * Evaluates the result set of existing functions returned by + * the Roleservice and list of functions found in the upload file. + * Reads and writes scope variable uploadFile. + * Reads closure variable appFunctionsResult. + */ + evalAppFunctionsCheckResults() { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::evalAppFunctionsCheckResults: uploadFile length is ' + $scope.uploadFile.length); + this.uploadFile.forEach((uploadRow) => { + if (uploadRow.status) { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::evalAppFunctionsCheckResults: skip row ' + uploadRow.line); + return; + } + // Search for the match in the app-functions + // array + let isFunctionExist = false; + this.appFunctionsResult.forEach((exixtingFuncObj) => { + if (uploadRow.type.toUpperCase() === exixtingFuncObj.type.toUpperCase() + && uploadRow.instance.toUpperCase() === exixtingFuncObj.code.toUpperCase() + && uploadRow.action.toUpperCase() === exixtingFuncObj.action.toUpperCase()) { + uploadRow.status = 'Function exits!'; + uploadRow.isCreate = false; + isFunctionExist = true; + } + }); // for each result + if (!isFunctionExist) { + if (/[^a-zA-Z0-9\-\.\_]/.test(uploadRow.type) + || (uploadRow.action !== '*' + && /[^a-zA-Z0-9\-\.\_]/.test(uploadRow.action)) + || /[^a-zA-Z0-9\-\:\_\./*]/.test(uploadRow.instance) + || /[^a-zA-Z0-9\-\_ \.]/.test(uploadRow.name)) { + uploadRow.status = 'Invalid function'; + uploadRow.isCreate = false; + } else { + // if (debug){ + // $log.debug('BulkRoleAndFunctionsModalCtrl::evalAppFunctionsCheckResults: new function ' + // + uploadRow); + // } + // After much back-and-forth I decided a clear indicator is better than blank in the table status column. + uploadRow.status = 'Create'; + uploadRow.isCreate = true; + } + } + }); // for each row + }; // evalAppFunctionsCheckResults + + + /** + * Evaluates the result set of existing roles returned by + * the Roleservice and list of roles found in the upload file. + * Reads and writes scope variable uploadFile. + * Reads closure variable appRolesResult. + */ + evalAppRolesCheckResults() { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::evalAppRolesCheckResults: uploadFile length is ' + this.uploadFile.length); + this.uploadFile.forEach((uploadRow) => { + if (uploadRow.status) { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::evalAppRolesCheckResults: skip row ' + uploadRow.line); + return; + } + // Search for the match in the app-roles + // array + let isRoleExist = false; + this.appRoleFuncsResult.forEach((existingRoleObj) => { + if (uploadRow.role.toUpperCase() === existingRoleObj.name.toUpperCase()) { + uploadRow.status = 'Role exits!'; + uploadRow.isCreate = false; + isRoleExist = true; + } + }); // for each result + if (!isRoleExist) { + if (/[^a-zA-Z0-9\-\_ \.\/]/.test(uploadRow.role) || + uploadRow.role.toLowerCase().startsWith("global_")) { + uploadRow.status = 'Invalid role!'; + uploadRow.isCreate = false; + } else { + // if (debug){ + // $log.debug('BulkRoleAndFunctionsModalCtrl::evalAppRolesCheckResults: new function ' + // + uploadRow); + // } + // After much back-and-forth I decided a clear indicator is better than blank in the table status column. + uploadRow.status = 'Create'; + uploadRow.isCreate = true; + } + } + }); // for each row + }; // evalAppRolesCheckResults + + /** + * Evaluates the result set of existing roles returned by + * the Roleservice and list of roles found in the upload file. + * Reads and writes scope variable uploadFile. + * Reads closure variable appRolesResult. + */ + evalAppRoleFuncsCheckResults(typeUpload) { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::evalAppRoleFuncsCheckResults: uploadFile length is ' + this.uploadFile.length); + this.uploadFile.forEach((uploadRow) => { + if (uploadRow.status) { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::evalAppRoleFuncsCheckResults: skip row ' + uploadRow.line); + return; + } + // Search for the match in the app-functions array + let isValidFunc = false; + this.appFunctionsResult.forEach((existingFuncObj) => { + if (uploadRow.type.toUpperCase() === existingFuncObj.type.toUpperCase() + && uploadRow.instance.toUpperCase() === existingFuncObj.code.toUpperCase() + && uploadRow.action.toUpperCase() === existingFuncObj.action.toUpperCase() + && uploadRow.name.toUpperCase() === existingFuncObj.name.toUpperCase()) { + isValidFunc = true; + } + }); + + let isValidRole = false; + let isRoleFuncExist = false; + if (typeUpload === 'globalRoleFunctions') { + // Search for the match in the app-role array + this.appGlobalRolesResult.forEach((existingRoleObj) => { + if (uploadRow.role.toUpperCase() === existingRoleObj.name.toUpperCase()) { + isValidRole = true; + if (isValidFunc) { + existingRoleObj.roleFunctions.forEach((existingRoleFuncObj) => { + if (uploadRow.type.toUpperCase() === existingRoleFuncObj.type.toUpperCase() + && uploadRow.instance.toUpperCase() === existingRoleFuncObj.code.toUpperCase() + && uploadRow.action.toUpperCase() === existingRoleFuncObj.action.toUpperCase()) { + isRoleFuncExist = true; + } + }); + } + } + }); // for each result + } else { + // Search for the match in the app-role array + this.appRoleFuncsResult.forEach((existingRoleObj) => { + if (uploadRow.role.toUpperCase() === existingRoleObj.name.toUpperCase()) { + isValidRole = true; + if (isValidFunc) { + existingRoleObj.roleFunctions.forEach((existingRoleFuncObj) => { + if (uploadRow.type.toUpperCase() === existingRoleFuncObj.type.toUpperCase() + && uploadRow.instance.toUpperCase() === existingRoleFuncObj.code.toUpperCase() + && uploadRow.action.toUpperCase() === existingRoleFuncObj.action.toUpperCase()) { + isRoleFuncExist = true; + } + }); + } + } + }); // for each result + } + + uploadRow.isCreate = false; + if (typeUpload === 'globalRoleFunctions' && (!isValidRole || !isValidFunc)) { + uploadRow.status = 'Invalid global role function!'; + } else if (typeUpload !== 'globalRoleFunctions' && (!isValidRole || !isValidFunc)) { + uploadRow.status = 'Invalid role function!'; + } else if (typeUpload === 'globalRoleFunctions' && !isRoleFuncExist) { + uploadRow.status = 'Add global role function!'; + uploadRow.isCreate = true; + } else if (typeUpload !== 'globalRoleFunctions' && !isRoleFuncExist) { + uploadRow.status = 'Add role function!'; + uploadRow.isCreate = true; + } else if (typeUpload === 'globalRoleFunctions') { + uploadRow.status = 'Global role function exists!'; + } else { + uploadRow.status = 'Role function exists!'; + } + + }); // for each row + }; // evalAppRolesCheckResults + + + /** + * Sends requests to Portal BE requesting application functions assignment. + * That endpoint handles creation of the application functions in the + * external auth system if necessary. Reads closure variable appFunctionsResult. + * Invoked by the Next button on the confirmation dialog. + */ + updateFunctionsInDB() { + this.isProcessing = true; + this.conformMsg = ''; + this.isProcessedRecords = true; + this.progressMsg = 'Sending requests to application..'; + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateFunctionsInDB: request length is ' + appUserRolesRequest.length); + var numberFunctionsSucceeded = 0; + let promises = []; + this.uploadFile.forEach((appFuncPostData) => { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateFunctionsInDB: appFuncPostData is ' + JSON.stringify(appFuncPostData)); + let updateFunctionsFinalPostData = { + type: appFuncPostData.type, + code: appFuncPostData.instance, + action: appFuncPostData.action, + name: appFuncPostData.name + }; + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateFunctionsInDB: updateFunctionsFinalPostData is ' + JSON.stringify(updateFunctionsFinalPostData)); + let updatePromise = {}; + if (appFuncPostData.isCreate) { + updatePromise = this.functionalMenuService.saveBulkFunction(this.appId, updateFunctionsFinalPostData).toPromise().then(res => { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateFunctionsInDB: updated successfully: ' + JSON.stringify(res)); + numberFunctionsSucceeded++; + }).catch(err => { + // What to do if one of many fails?? + // $log.error('BulkRoleAndFunctionsModalCtrl::updateFunctionsInDB failed: ', err); + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Failed to update the application functions.' + 'Error: ' + err.status; + }).finally(() => { + }); + } + promises.push(updatePromise); + }); // for each + + // Run all the promises + Promise.all(promises).then(() => { + this.conformMsg = 'Processed ' + numberFunctionsSucceeded + ' records.'; + this.isProcessing = false; + this.isProcessedRecords = true; + this.uploadFile = []; + this.dialogState = 2; + }); + }; // updateFunctionsInDB + + /** + * Sends requests to Portal BE requesting application functions assignment. +* That endpoint handles creation of the application role in the +* external auth system if necessary. Reads closure variable appRoleFuncResult. +* Invoked by the Next button on the confirmation dialog. +*/ + updateRolesInDB() { + this.isProcessing = true; + this.conformMsg = ''; + this.isProcessedRecords = true; + this.progressMsg = 'Sending requests to application..'; + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateRolesInDB: request length is ' + appUserRolesRequest.length); + var numberRolesSucceeded = 0; + let promises = []; + this.uploadFile.forEach((appRolePostData) => { + let priority = parseInt(appRolePostData.priority); + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateRolesInDB: appRolePostData is ' + JSON.stringify(appFuncPostData)); + let uplaodRolePostData = {}; + if (isNaN(priority)) { + uplaodRolePostData = { + name: appRolePostData.role, + active: true, + } + } else { + uplaodRolePostData = { + name: appRolePostData.role, + priority: appRolePostData.priority, + active: true, + } + } + var postData = { + role: uplaodRolePostData, + roleFunctions: [], + childRoles: [] + } + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateRolesInDB: uplaodRoleFinalPostData is ' + JSON.stringify(uplaodRoleFinalPostData)); + let updatePromise = {}; + if (appRolePostData.isCreate) { + updatePromise = this.functionalMenuService.saveBulkRole(this.appId, JSON.stringify(postData)).toPromise().then(res => { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateRolesInDB: updated successfully: ' + JSON.stringify(res)); + numberRolesSucceeded++; + }).catch(err => { + // What to do if one of many fails?? + // $log.error('BulkRoleAndFunctionsModalCtrl::updateRolesInDB failed: ', err); + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Failed to update the application role. ' + + 'Error: ' + err.status; + }).finally(() => { + }); + } + promises.push(updatePromise); + }); // for each + + // Run all the promises + Promise.all(promises).then(() => { + if (numberRolesSucceeded == 0) { + this.conformMsg = 'Processed ' + numberRolesSucceeded + ' records'; + } else { + this.conformMsg = 'Processed ' + numberRolesSucceeded + ' records. Please sync roles to reflect in portal'; + } this.isProcessing = false; + this.isProcessedRecords = true; + this.uploadFile = []; + this.dialogState = 2; + }); + }; // updateRolesInDB + + /** + * Sends requests to Portal BE requesting role function assignment. + * That endpoint handles adding role function in the external auth system + * if necessary.Invoked by the Next button on the confirmation dialog. + */ + updateRoleFunctionsInDB() { + this.isProcessing = true; + this.conformMsg = ''; + this.isProcessedRecords = true; + this.progressMsg = 'Sending requests to application..'; + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateRoleFunctionsInDB: request length is ' + appUserRolesRequest.length); + var numberRoleFunctionSucceeded = 0; + let promises = []; + this.uploadFile.forEach((appRoleFuncPostData) => { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateRoleFunctionsInDB: appRoleFuncPostData is ' + JSON.stringify(appFuncPostData)); + let updateRoleFunctionFinalPostData = { + roleName: appRoleFuncPostData.role, + type: appRoleFuncPostData.type, + instance: appRoleFuncPostData.instance, + action: appRoleFuncPostData.action, + name: appRoleFuncPostData.name, + isGlobalRolePartnerFunc: false + }; + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateRoleFunctionsInDB: updateRoleFunctionFinalPostData is ' + JSON.stringify(updateFunctionsFinalPostData)); + let updatePromise = {}; + if (appRoleFuncPostData.isCreate) { + updatePromise = this.functionalMenuService.updateBulkRoleFunction(this.appId, updateRoleFunctionFinalPostData).toPromise().then(res => { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateRoleFunctionsInDB: updated successfully: ' + JSON.stringify(res)); + numberRoleFunctionSucceeded++; + }).catch(err => { + // What to do if one of many fails?? + // $log.error('BulkRoleAndFunctionsModalCtrl::updateRoleFunctionsInDB failed: ', err); + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Failed to update the application role function. ' + + 'Error: ' + err.status; + }).finally(() => { + }); + } + promises.push(updatePromise); + }); // for each + + // Run all the promises + Promise.all(promises).then(() => { + if (numberRoleFunctionSucceeded == 0) { + this.conformMsg = 'Processed ' + numberRoleFunctionSucceeded + ' records'; + } else { + this.conformMsg = 'Processed ' + numberRoleFunctionSucceeded + ' records. Please sync roles to reflect in portal'; + } this.isProcessing = false; + this.isProcessedRecords = true; + this.uploadFile = []; + this.dialogState = 2; + }); + }; // updateRoleFunctionsInDB + + /** + * Sends requests to Portal requesting global role functions assignment. + * That endpoint handles updating global role functions in the external auth system + * if necessary. Invoked by the Next button on the confirmation dialog. + */ + updateGlobalRoleFunctionsInDB() { + this.isProcessing = true; + this.conformMsg = ''; + this.isProcessedRecords = true; + this.progressMsg = 'Sending requests to application..'; + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateGlobalRoleFunctionsInDB: request length is ' + appUserRolesRequest.length); + var numberGlobalRoleFunctionSucceeded = 0; + let promises = []; + this.uploadFile.forEach((appRoleFuncPostData) => { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateGlobalRoleFunctionsInDB: appRoleFuncPostData is ' + JSON.stringify(appFuncPostData)); + let updateGlobalRoleFunctionFinalPostData = { + roleName: appRoleFuncPostData.role, + type: appRoleFuncPostData.type, + instance: appRoleFuncPostData.instance, + action: appRoleFuncPostData.action, + name: appRoleFuncPostData.name, + isGlobalRolePartnerFunc: true + }; + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateGlobalRoleFunctionsInDB: updateRoleFunctionFinalPostData is ' + JSON.stringify(updateFunctionsFinalPostData)); + let updatePromise = {}; + if (appRoleFuncPostData.isCreate) { + updatePromise = this.functionalMenuService.updateBulkRoleFunction(this.appId, updateGlobalRoleFunctionFinalPostData).toPromise().then(res => { + // if (debug) + // $log.debug('BulkRoleAndFunctionsModalCtrl::updateGlobalRoleFunctionsInDB: updated successfully: ' + JSON.stringify(res)); + numberGlobalRoleFunctionSucceeded++; + }).catch(err => { + // What to do if one of many fails?? + // $log.error('BulkRoleAndFunctionsModalCtrl::updateGlobalRoleFunctionsInDB failed: ', err); + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Failed to update the global role partner function. ' + + 'Error: ' + err.status; + }).finally(() => { + }); + } + promises.push(updatePromise); + }); // for each + + // Run all the promises + Promise.all(promises).then(() => { + if (numberGlobalRoleFunctionSucceeded == 0) { + this.conformMsg = 'Processed ' + numberGlobalRoleFunctionSucceeded + ' records'; + } else { + this.conformMsg = 'Processed ' + numberGlobalRoleFunctionSucceeded + ' records. Please sync roles to reflect in portal'; + } + this.isProcessing = false; + this.isProcessedRecords = true; + this.uploadFile = []; + this.dialogState = 2; + }); + }; // updateGlobalRoleFunctionsInDB + +} diff --git a/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.html b/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.html new file mode 100644 index 00000000..be7dc2a1 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.html @@ -0,0 +1,100 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <div class="modal-header"> + <h4 class="modal-title">{{title}}</h4> + <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross')"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <span class="onap-spinner" *ngIf="showSpinner"></span> + <form> + <fieldset class="form-group"> + <div class="row"> + <legend class="col-form-label col-sm-2 pt-0">Type</legend> + <div class="col-sm-10"> + <div class="form-check"> + <mat-radio-group aria-labelledby="type-radio-group-label" class="type-radio-group" + [(ngModel)]="selectedType" name="selectedType"> + <mat-radio-button class="type-radio-button" [disabled]="editDisable" *ngFor="let type of typeOptions" [value]="type"> + {{type}} + </mat-radio-button> + </mat-radio-group> + </div> + </div> + </div> + </fieldset> + <div *ngIf="selectedType === 'other'" class="form-group row"> + <label for="inputOtherType" class="col-sm-2 col-form-label"></label> + <div class="col-sm-10"> + <input type="text" class="form-control" [disabled]="editDisable" [(ngModel)]="otherTypeValue" name="type2" id="inputOtherType" + placeholder="Type"> + </div> + </div> + <div class="form-group row"> + <label for="inputInstance" class="col-sm-2 col-form-label">Instance</label> + <div class="col-sm-10"> + <input type="text" class="form-control" [disabled]="editDisable" [(ngModel)]="roleFunction.code" name="code" id="inputInstance" + placeholder="Instance"> + </div> + </div> + <div class="form-group row"> + <label for="inputAction" class="col-sm-2 col-form-label">Action</label> + <div class="col-sm-10"> + <input type="text" class="form-control" [disabled]="editDisable" [(ngModel)]="roleFunction.action" name="action" id="inputAction" + placeholder="Action"> + </div> + </div> + <div class="form-group row"> + <label for="inputName" class="col-sm-2 col-form-label">Name</label> + <div class="col-sm-10"> + <input type="text" class="form-control" [(ngModel)]="roleFunction.name" name="name" id="inputName" + placeholder="Name"> + </div> + </div> + </form> + </div> + <div class="modal-footer"> + <button type="submit" class="btn btn-primary" + [disabled]="(selectedType === 'other' && otherTypeValue.length === 0 ) || (roleFunction.code.length === 0 || roleFunction.action.length === 0 || roleFunction.name.length === 0)" + (click)="saveRoleFunction()">Save</button> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Cancel</button> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.scss b/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.scss new file mode 100644 index 00000000..fff036cd --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.scss @@ -0,0 +1,40 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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============================================ + * + * + */ +.onap-spinner{ + z-index: 9999; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.spec.ts b/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.spec.ts new file mode 100644 index 00000000..2c5ef631 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.spec.ts @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { RoleFunctionModalComponent } from './role-function-modal.component'; + +describe('RoleFunctionModalComponent', () => { + let component: RoleFunctionModalComponent; + let fixture: ComponentFixture<RoleFunctionModalComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ RoleFunctionModalComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RoleFunctionModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.ts b/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.ts new file mode 100644 index 00000000..d54cd02c --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role-functions/role-function-modal/role-function-modal.component.ts @@ -0,0 +1,165 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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, Input, Output, EventEmitter } from '@angular/core'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { RoleFunction, Role } from 'src/app/shared/model'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { environment } from 'src/environments/environment'; +import { HttpClient } from '@angular/common/http'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; + +@Component({ + selector: 'app-role-function-modal', + templateUrl: './role-function-modal.component.html', + styleUrls: ['./role-function-modal.component.scss'] +}) +export class RoleFunctionModalComponent implements OnInit { + + @Input() title: string; + @Input() appId: any; + @Input() dialogState: number; + @Input() currentRoleFunctions: any; + @Input() editRoleFunction: RoleFunction; + @Output() passBackRoleFunctionPopup: EventEmitter<any> = new EventEmitter(); + roleFunction: RoleFunction; + otherTypeValue: string; + typeOptions: string[] = ['menu', 'url', 'other']; + api = environment.api; + isEditing: any; + editDisable: boolean; + showSpinner: boolean; + selectedType: string; + createOrUpdate: string; + constructor(public activeModal: NgbActiveModal, public ngbModal: NgbModal, public http: HttpClient) { } + + ngOnInit() { + this.createOrUpdate = 'create'; + this.selectedType = 'menu'; + this.roleFunction = new RoleFunction(this.selectedType, '', '*', ''); + this.otherTypeValue = ''; + if (this.editRoleFunction) { + this.createOrUpdate = 'update'; + this.editDisable = true; + this.selectedType = this.editRoleFunction.type; + if (this.editRoleFunction.type !== 'menu' && this.editRoleFunction.type !== 'url') { + this.selectedType = 'other'; + this.otherTypeValue = this.editRoleFunction.type; + } + this.roleFunction = new RoleFunction(this.editRoleFunction.type, this.editRoleFunction.code, this.editRoleFunction.action, this.editRoleFunction.name); + } + } + + saveRoleFunction() { + if (/[^a-zA-Z0-9\-\.\_]/.test(this.roleFunction.type) && this.selectedType === 'other') { + this.openConfirmationModal('Confirmation', 'Type can only contain alphanumeric characters, dots(.) and underscores(_)'); + return; + } else { + this.roleFunction.type = this.selectedType; + } + if (this.roleFunction.action !== '*' && /[^a-zA-Z0-9\-\.\_]/.test(this.roleFunction.action)) { + this.openConfirmationModal('Confirmation', 'Action can only contain alphanumeric characters, hyphens(-), dots(.) and underscores(_) and single asterisk character(*)'); + return; + } + if (/[^a-zA-Z0-9\-\:\_\./*]/.test(this.roleFunction.code)) { + this.openConfirmationModal('Confirmation', 'Instance can only contain alphanumeric characters, hyphens(-), dots(.), colons(:), forwardSlash(/) , asterisk(*) and underscores(_)'); + return; + } + const modalInfoRef = this.ngbModal.open(InformationModalComponent); + modalInfoRef.componentInstance.title = 'Confirmation'; + modalInfoRef.componentInstance.message = 'You are about to ' + this.createOrUpdate + ' the role function ' + this.roleFunction.name + '. Do you want to continue?'; + modalInfoRef.result.then((_res) => { + if (_res === 'Ok') { + this.showSpinner = true; + var uuu = this.api.saveRoleFunction.replace(':appId', this.appId); + var postData = this.roleFunction; + var exists = false, x; + for (x in this.currentRoleFunctions) { + if (this.currentRoleFunctions[x].type == this.roleFunction.type + && this.currentRoleFunctions[x].code == this.roleFunction.code + && this.currentRoleFunctions[x].action == this.roleFunction.action + && this.currentRoleFunctions[x].name == this.roleFunction.name) { + this.openConfirmationModal('Confirmation', "Role Function already exist."); + exists = true; + this.showSpinner = false; + break; + } + if (!this.editDisable) { + if (this.currentRoleFunctions[x].type == this.roleFunction.type + && this.currentRoleFunctions[x].code == this.roleFunction.code + && this.currentRoleFunctions[x].action == this.roleFunction.action + ) { + this.openConfirmationModal('Confirmation', "Please make sure code, type and action is unique. Please create a role function with a different code or type or action to proceed."); + exists = true; + this.showSpinner = false; + break; + } + } + } + if (this.selectedType === 'other') + this.roleFunction.type = this.otherTypeValue; + if (!exists && this.roleFunction.name.trim() != '' && this.roleFunction.code.trim() != '') { + this.http.post(uuu, JSON.stringify(postData)).toPromise().then((res: any) => { + if (res.status == 'OK') { + this.showSpinner = false; + if (this.editRoleFunction) { + this.editRoleFunction.name = this.roleFunction.name; + this.passBackRoleFunctionPopup.emit(this.editRoleFunction); + } else{ + this.passBackRoleFunctionPopup.emit(this.roleFunction); + } + this.openConfirmationModal('Success', res.message); + } else { + this.showSpinner = false; + this.openConfirmationModal('Error', res.message); + } + }); + + } + } + }, (_dismiss) => { + + }) + } + + + openConfirmationModal(_title: string, _message: string) { + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = _title; + modalInfoRef.componentInstance.message = _message; + } +} diff --git a/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.html b/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.html new file mode 100644 index 00000000..046d0a07 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.html @@ -0,0 +1,113 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <div class="onap-main-view-title"> + <h1 class="heading-page">Role Functions</h1> + </div> + <mat-form-field> + <mat-label> Select Application </mat-label> + <mat-select [disabled]='centralizedApps.length === 0' [(ngModel)]="selectedCentralizedApp"> + <mat-option *ngFor="let app of centralizedApps" [value]="app.appId" + (click)="getRoleFunctions(selectedCentralizedApp)"> + {{app.appName}}</mat-option> + </mat-select> + </mat-form-field> + + <button type="button" class="btn btn-primary" [disabled]='centralizedApps.length === 0' + (click)="syncRolesFromExternalAuthSystem()"><i class="icon ion-md-sync"></i> + Sync Role Functions </button> + <button type="button" class="btn btn-primary" [disabled]='centralizedApps.length === 0' + (click)="addRoleFunctionModalPopup()"><i class="icon ion-md-add-circle-outline"></i> + Create </button> + <span class="onap-spinner" *ngIf="showSpinner"></span> + <table mat-table [dataSource]="roleFunctionsDataSource" matSort> + <!-- Type Column --> + <ng-container matColumnDef="type"> + <th id="col1" mat-header-cell *matHeaderCellDef mat-sort-header> Type </th> + <td id="rowheader_t1_{{i}}-type" mat-cell *matCellDef="let element; let i = index;"> {{element.type}} + </td> + </ng-container> + + <!-- Instance Column --> + <ng-container matColumnDef="instance"> + <th id="col2" mat-header-cell *matHeaderCellDef mat-sort-header> Instance </th> + <td id="rowheader_t1_{{i}}-instance" mat-cell *matCellDef="let element; let i=index;"> {{element.code}} + </td> + </ng-container> + + <!-- Action Column --> + <ng-container matColumnDef="action"> + <th id="col3" mat-header-cell *matHeaderCellDef mat-sort-header> Action </th> + <td id="rowheader_t1_{{i}}-action" mat-cell *matCellDef="let element; let i=index;"> {{element.action}} + </td> + </ng-container> + + <!-- Name Column --> + <ng-container matColumnDef="name"> + <th id="col3" mat-header-cell *matHeaderCellDef mat-sort-header> Name </th> + <td id="rowheader_t1_{{i}}-name" mat-cell *matCellDef="let element; let i=index;"> {{element.name}} + </td> + </ng-container> + + <!-- Edit Column --> + <ng-container matColumnDef="edit"> + <th id="col3" mat-header-cell *matHeaderCellDef mat-sort-header> Edit </th> + <td id="rowheader_t1_{{i}}-userId" mat-cell *matCellDef="let element; let i=index;"> + <span class="icon-trash" id="{{i}}-button-edit" (click)="editRoleFunctionModalPopup(element)"> + <i class="icon ion-md-create"></i> + </span> + </td> + </ng-container> + + <!-- Delete Column --> + <ng-container matColumnDef="delete"> + <th id="col4" mat-header-cell *matHeaderCellDef> Delete </th> + <td id="rowheader_t1_{{i}}-delete" mat-cell *matCellDef="let element; let i=index;"> + <span class="icon-trash" id="{{i}}-button-portal-admin-remove" (click)="removeRoleFunction(element)"> + <i class="icon ion-md-trash"></i> + </span> + </td> + </ng-container> + + <tr [hidden]="availableRoleFunctions.length === 0" mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> + </table> + <mat-paginator [hidden]="availableRoleFunctions.length === 0" [pageSizeOptions]="[10, 20]" showFirstLastButtons> + </mat-paginator> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.scss b/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.scss new file mode 100644 index 00000000..944e9c8c --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.scss @@ -0,0 +1,47 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 "../../pages.component"; + +.icon-trash{ + cursor: pointer; + font-size: 20px; +} + +.onap-spinner{ + z-index: 9999; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.spec.ts b/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.spec.ts new file mode 100644 index 00000000..ccefb8d2 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.spec.ts @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { RoleFunctionsComponent } from './role-functions.component'; + +describe('RoleFunctionsComponent', () => { + let component: RoleFunctionsComponent; + let fixture: ComponentFixture<RoleFunctionsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ RoleFunctionsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RoleFunctionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.ts b/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.ts new file mode 100644 index 00000000..1b68526d --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role-functions/role-functions.component.ts @@ -0,0 +1,200 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { RoleService, ApplicationsService } from 'src/app/shared/services'; +import { MatTableDataSource, MatPaginator, MatSort } from '@angular/material'; +import { RoleFunctionModalComponent } from './role-function-modal/role-function-modal.component'; +import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { HttpClient } from '@angular/common/http'; +import { environment } from 'src/environments/environment'; + +@Component({ + selector: 'app-role-functions', + templateUrl: './role-functions.component.html', + styleUrls: ['./role-functions.component.scss'] +}) +export class RoleFunctionsComponent implements OnInit { + api = environment.api + centralizedApps: any; + selectedCentralizedApp: any; + availableRoleFunctions: any; + displayedColumns: string[] = ['type', 'instance', 'action', 'name', 'edit', 'delete']; + roleFunctionsDataSource = new MatTableDataSource(this.availableRoleFunctions); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + showSpinner: boolean; + + constructor(public ngbModal: NgbModal,private roleService: RoleService, private applicationsService: ApplicationsService, public http: HttpClient) {} + + ngOnInit() { + this.availableRoleFunctions = []; + this.centralizedApps = []; + this.getCentralizedApps(sessionStorage.userId); + } + + syncRolesFromExternalAuthSystem() { + this.applicationsService.syncRolesEcompFromExtAuthSystem(this.selectedCentralizedApp).toPromise().then((res: any) => { + if (res.status == 'OK') { + const modalInfoRef = this.ngbModal.open(InformationModalComponent); + modalInfoRef.componentInstance.title = 'Success'; + modalInfoRef.componentInstance.message = 'Sync role functions completed successfully!'; + modalInfoRef.result.then((_res) => { + if (_res === 'Ok') + this.getRoleFunctions(this.selectedCentralizedApp); + }, (result) => { + + }) + } else { + this.openConfirmationModal('Error', 'Sync failed ' + res.message); + } + }).catch(err => { + this.openConfirmationModal('Error', 'Sync failed' + err); + }); + }; + + + // getCentalizedApps + getCentralizedApps(userId) { + this.roleService.getCentralizedApps(userId).toPromise().then((res: any) => { + if (res.length > 0) { + this.centralizedApps = res; + this.selectedCentralizedApp = this.centralizedApps[0].appId; + this.getRoleFunctions(this.centralizedApps[0].appId); + } + }).catch(err => { + // $log.error('RoleListCtrl::centralizedApps retrieval error: ', err); + }).finally(() => { + // this.isLoadingTable = false; + }); + } + + getRoleFunctions(id) { + this.showSpinner = true; + this.roleService.getRoleFunctionList(id).subscribe((data: any) => { + this.showSpinner = false; + var j = data; + var roleFunctions = JSON.parse(j.data); + this.availableRoleFunctions = roleFunctions.availableRoleFunctions; + this.roleFunctionsDataSource = new MatTableDataSource(this.availableRoleFunctions); + this.roleFunctionsDataSource.sort = this.sort; + this.roleFunctionsDataSource.paginator = this.paginator; + }, (error) => { + this.showSpinner = false; + this.openConfirmationModal('Error', 'Failed to get role functions. Please try again!' + error.message); + }) + }; + + addRoleFunctionModalPopup(){ + const modalInfoRef = this.ngbModal.open(RoleFunctionModalComponent); + modalInfoRef.componentInstance.title = 'Add Role Function'; + modalInfoRef.componentInstance.appId = this.selectedCentralizedApp; + modalInfoRef.componentInstance.currentRoleFunctions = this.availableRoleFunctions; + modalInfoRef.componentInstance.passBackRoleFunctionPopup.subscribe((_result: any) => { + if(_result){ + modalInfoRef.close(); + this.availableRoleFunctions.push(_result); + this.roleFunctionsDataSource = new MatTableDataSource(this.availableRoleFunctions); + this.roleFunctionsDataSource.sort = this.sort; + this.roleFunctionsDataSource.paginator = this.paginator; + } + }, (_reason: any) => { + return; + }); + + } + + editRoleFunctionModalPopup(_element){ + const modalInfoRef = this.ngbModal.open(RoleFunctionModalComponent); + modalInfoRef.componentInstance.title = 'Edit Role Function'; + modalInfoRef.componentInstance.appId = this.selectedCentralizedApp; + modalInfoRef.componentInstance.editRoleFunction = _element; + modalInfoRef.componentInstance.currentRoleFunctions = this.availableRoleFunctions; + modalInfoRef.componentInstance.passBackRoleFunctionPopup.subscribe((_result: any) => { + if(_result){ + modalInfoRef.close(); + this.availableRoleFunctions.splice(this.availableRoleFunctions.indexOf(_element), 1); + this.availableRoleFunctions.push(_result); + this.roleFunctionsDataSource = new MatTableDataSource(this.availableRoleFunctions); + this.roleFunctionsDataSource.sort = this.sort; + this.roleFunctionsDataSource.paginator = this.paginator; + } + }, (_reason: any) => { + return; + }); + } + + removeRoleFunction(_element: any){ + const ngbInfoModal = this.ngbModal.open(InformationModalComponent); + ngbInfoModal.componentInstance.title = 'Confirmation'; + ngbInfoModal.componentInstance.message = 'You are about to delete the role function ' + _element.name + '. Do you want to continue?'; + ngbInfoModal.result.then(_res =>{ + if(_res === 'Ok'){ + this.showSpinner = true; + var uuu = this.api.removeRoleFunction.replace(':appId', this.selectedCentralizedApp); + var postData = _element; + this.http.post(uuu, postData).subscribe((response: any) => { + this.showSpinner = false; + if(response.status == 'OK'){ + this.openConfirmationModal('Success', response.message); + this.availableRoleFunctions.splice(this.availableRoleFunctions.indexOf(_element), 1); + this.roleFunctionsDataSource = new MatTableDataSource(this.availableRoleFunctions); + this.roleFunctionsDataSource.sort = this.sort; + this.roleFunctionsDataSource.paginator = this.paginator; + } else{ + this.showSpinner = false; + this.openConfirmationModal('Error', "Error while deleting: " + response.message); + } + }, (err) => { + this.showSpinner = false; + this.openConfirmationModal('Error', err.message); + }); + } + }, (_reason: any) => { + return; + }); + } + + openConfirmationModal(_title: string, _message: string) { + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = _title; + modalInfoRef.componentInstance.message = _message; + } + +} diff --git a/portal-FE-common/src/app/pages/role/role.component.html b/portal-FE-common/src/app/pages/role/role.component.html new file mode 100644 index 00000000..9f57aa47 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role.component.html @@ -0,0 +1,115 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <div class="onap-main-view-title"> + <h1 class="heading-page">Roles</h1> + </div> + <mat-form-field> + <mat-label> Select Application </mat-label> + <mat-select [disabled]='centralizedApps.length === 0' [(ngModel)]="selectedCentralizedApp"> + <mat-option *ngFor="let app of centralizedApps" [value]="app.appId" + (click)="getRolesForSelectedCentralizedApp(selectedCentralizedApp)"> + {{app.appName}}</mat-option> + </mat-select> + </mat-form-field> + + <button type="button" class="btn btn-primary" [disabled]='centralizedApps.length === 0' + (click)="openBulkUploadRolesAndFunctionsModal()"><i class="icon ion-md-cloud-upload"></i> + Bulk Upload</button> + <button type="button" class="btn btn-primary" [disabled]='centralizedApps.length === 0' [hidden]="!syncRolesApplied" + (click)="syncRolesFromExternalAuthSystem()"><i class="icon ion-md-sync"></i> + Sync Roles </button> + <button type="button" class="btn btn-primary" [disabled]='centralizedApps.length === 0' + (click)="addRoleModalPopup()"><i class="icon ion-md-add-circle-outline"></i> + Create </button> + <span class="onap-spinner" *ngIf="showSpinner"></span> + <table mat-table [dataSource]="roleDataSource" matSort> + <!-- Name Column --> + <ng-container matColumnDef="name"> + <th id="col1" mat-header-cell *matHeaderCellDef mat-sort-header> Name </th> + <td id="rowheader_t1_{{i}}-firstName" mat-cell *matCellDef="let element; let i = index;"> {{element.name}} + </td> + </ng-container> + + <!-- Priority Column --> + <ng-container matColumnDef="priority"> + <th id="col2" mat-header-cell *matHeaderCellDef mat-sort-header> Priority </th> + <td id="rowheader_t1_{{i}}-lastName" mat-cell *matCellDef="let element; let i=index;"> {{element.priority}} + </td> + </ng-container> + + <!-- Active Column --> + <ng-container matColumnDef="active"> + <th id="col3" mat-header-cell *matHeaderCellDef mat-sort-header> Active </th> + <td id="rowheader_t1_{{i}}-userId" mat-cell *matCellDef="let element; let i=index;"> + <mat-slide-toggle + [disabled]="(element.id === 1 || element.id === 999) || ((this.selectedCentralizedApp !== 1) && (element.name.indexOf('global_') !== -1))" + [(ngModel)]="element.active" (change)="toggleRole(element)"></mat-slide-toggle> + </td> + </ng-container> + + <!-- Edit Column --> + <ng-container matColumnDef="edit"> + <th id="col3" mat-header-cell *matHeaderCellDef mat-sort-header> Edit </th> + <td id="rowheader_t1_{{i}}-userId" mat-cell *matCellDef="let element; let i=index;"> + <span class="icon-trash" id="{{i}}-button-edit" (click)="editRoleModalPopup(element)"> + <i class="icon ion-md-create"></i> + </span> + </td> + </ng-container> + + <!-- Delete Column --> + <ng-container matColumnDef="delete"> + <th [hidden]="selectedCentralizedApp === 1" id="col4" mat-header-cell *matHeaderCellDef> Delete </th> + <td [hidden]="selectedCentralizedApp === 1" id="rowheader_t1_{{i}}-applications" mat-cell + *matCellDef="let element; let i=index;"> + <span class="icon-trash" id="{{i}}-button-portal-admin-remove" (click)="removeRole(element)"> + <i class="icon ion-md-trash"></i> + </span> + </td> + </ng-container> + + <tr [hidden]="availableRoles.length === 0" mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> + </table> + <mat-paginator [hidden]="availableRoles.length === 0" [pageSizeOptions]="[10, 20]" showFirstLastButtons> + </mat-paginator> + <a id="manage-role" [hidden]="availableRoles.length === 0" [routerLink]="['/roleFunctions']">Manage Role + Functions</a><br><br> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/role/role.component.scss b/portal-FE-common/src/app/pages/role/role.component.scss new file mode 100644 index 00000000..6fc77258 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role.component.scss @@ -0,0 +1,43 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 "../pages.component"; + +.icon-trash{ + cursor: pointer; + font-size: 20px; +} diff --git a/portal-FE-common/src/app/pages/role/role.component.spec.ts b/portal-FE-common/src/app/pages/role/role.component.spec.ts new file mode 100644 index 00000000..2dc104d8 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role.component.spec.ts @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { RoleComponent } from './role.component'; + +describe('RoleComponent', () => { + let component: RoleComponent; + let fixture: ComponentFixture<RoleComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ RoleComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RoleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/role/role.component.ts b/portal-FE-common/src/app/pages/role/role.component.ts new file mode 100644 index 00000000..0ed39569 --- /dev/null +++ b/portal-FE-common/src/app/pages/role/role.component.ts @@ -0,0 +1,279 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { RoleService, ApplicationsService } from 'src/app/shared/services'; +import { HttpErrorResponse, HttpClient } from '@angular/common/http'; +import { MatTableDataSource, MatSort, MatPaginator } from '@angular/material'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; +import { environment } from 'src/environments/environment'; +import { BulkUploadRoleComponent } from './bulk-upload-role/bulk-upload-role.component'; +import { AddRoleComponent } from './add-role/add-role.component'; + +@Component({ + selector: 'app-role', + templateUrl: './role.component.html', + styleUrls: ['./role.component.scss'] +}) +export class RoleComponent implements OnInit { + + selectedCentralizedApp: any; + centralizedApps: any; + showSpinner: boolean; + availableRoles: any[]; + syncRolesApplied: boolean; + displayedColumns: string[] = ['name', 'priority', 'active', 'edit', 'delete']; + roleDataSource = new MatTableDataSource(this.availableRoles); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + appName: any; + api = environment.api; + availableRoleFunctions: any; + constructor(private roleService: RoleService, private applicationsService: ApplicationsService, public ngbModal: NgbModal, public http: HttpClient) { } + + ngOnInit() { + this.centralizedApps = []; + this.availableRoles = []; + this.appName = ''; + this.selectedCentralizedApp = ''; + this.getCentralizedApps(sessionStorage.userId); + } + + toggleRole(_element) { + let activeOrInactive = (_element.active) ? 'activate' : 'inactivate'; + const modalInfoRef = this.ngbModal.open(InformationModalComponent); + modalInfoRef.componentInstance.title = 'Confirmation'; + modalInfoRef.componentInstance.message = 'You are about to ' + activeOrInactive + ' the role ' + _element.name + '. Do you want to continue?'; + modalInfoRef.result.then((_res) => { + if (_res === 'Ok') { + var uuu = this.api.toggleRole + '/' + this.selectedCentralizedApp + '/' + _element.id; + var postData = { + appId: this.selectedCentralizedApp, + role: _element + }; + this.http.post(uuu, postData).toPromise().then((data: any) => { + if (typeof data === 'object' && data.restcallStatus == 'Success') { + this.availableRoles = data.availableRoles; + this.roleDataSource = new MatTableDataSource(this.availableRoles); + this.roleDataSource.sort = this.sort; + this.roleDataSource.paginator = this.paginator; + // $log.debug('role::availableRoles:'+$scope.availableRoles); + } else { + _element.active = !_element.active; + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Error while saving. ' + data.restCallStatus; + } + + }, (response) => { + // debug.log('response:'+response); + _element.active = !_element.active; + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Error while saving. ' + response.restCallStatus; + }); + } else { + _element.active = !_element.active; + } + + }, (result) => { + + }) + } + + openBulkUploadRolesAndFunctionsModal() { + const modalBulkUploadRole = this.ngbModal.open(BulkUploadRoleComponent); + modalBulkUploadRole.componentInstance.title = 'Bulk Upload Role-Function'; + modalBulkUploadRole.componentInstance.dialogState = 1; + modalBulkUploadRole.componentInstance.appId = this.selectedCentralizedApp; + } + + editRoleModalPopup(_element) { + this.showSpinner = true; + this.roleService.getRole(this.selectedCentralizedApp, _element.id).toPromise().then((data: any) => { + this.showSpinner = false; + var response = JSON.parse(data.data); + var availableRoleFunctions = JSON.parse(response.availableRoleFunctions); + const ngbModalCreateRole = this.ngbModal.open(AddRoleComponent); + ngbModalCreateRole.componentInstance.title = 'Role'; + ngbModalCreateRole.componentInstance.dialogState = 2; + ngbModalCreateRole.componentInstance.availableRole = _element; + ngbModalCreateRole.componentInstance.appRoleFunctions = availableRoleFunctions; + ngbModalCreateRole.componentInstance.appId = this.selectedCentralizedApp; + ngbModalCreateRole.componentInstance.passBackAddRolePopup.subscribe((_result: any) => { + this.showSpinner = true; + this.getAppRoles(_result); + }, (_reason: any) => { + return; + }); + }, (error) => { + this.showSpinner = false; + }); + } + + addRoleModalPopup() { + const ngbModalCreateRole = this.ngbModal.open(AddRoleComponent); + ngbModalCreateRole.componentInstance.title = 'Role'; + ngbModalCreateRole.componentInstance.appId = this.selectedCentralizedApp; + ngbModalCreateRole.componentInstance.passBackAddRolePopup.subscribe((_result: any) => { + this.showSpinner = true; + this.getAppRoles(_result); + }, (_reason: any) => { + return; + }); + + } + + removeRole(_element) { + if ((this.selectedCentralizedApp !== 1) && (_element.name.indexOf('global_') !== -1)) { + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = 'Confirmation'; + modalInfoRef.componentInstance.message = 'Global role cannot be deleted.'; + } + else { + const modalInfoRef = this.ngbModal.open(InformationModalComponent); + modalInfoRef.componentInstance.title = 'Confirmation'; + modalInfoRef.componentInstance.message = 'You are about to delete the role ' + _element.name + ' . Do you want to continue?'; + modalInfoRef.result.then((_res) => { + if (_res === 'Ok') { + var uuu = this.api.removeRole + '/' + this.selectedCentralizedApp + '/' + _element.id; + var postData = { + appId: this.selectedCentralizedApp, + availableRoleId: _element.id + }; + this.http.post(uuu, postData).toPromise().then((data: any) => { + if (typeof data === 'object' && data.restCallStatus == 'Success') { + this.availableRoles = data.availableRoles; + this.roleDataSource = new MatTableDataSource(this.availableRoles); + this.roleDataSource.sort = this.sort; + this.roleDataSource.paginator = this.paginator; + } else { + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Failed to remove role. ' + data.error; + } + }, (_err) => { + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Error while deleting: ' + _err.error; + }) + } + }, (_dismiss) => { + + }); + } + } + + // getCentalizedApps + getCentralizedApps(userId) { + this.showSpinner = true; + this.roleService.getCentralizedApps(userId).toPromise().then((res: any) => { + if (res.length > 0) { + this.centralizedApps = res; + this.selectedCentralizedApp = this.centralizedApps[0].appId; + this.getRolesForSelectedCentralizedApp(this.centralizedApps[0].appId); + } + }).catch(err => { + this.showSpinner = false; + // $log.error('RoleListCtrl::centralizedApps retrieval error: ', err); + }) + } + + syncRolesFromExternalAuthSystem() { + this.applicationsService.syncRolesEcompFromExtAuthSystem(this.selectedCentralizedApp).toPromise().then((res: any) => { + if (res.status == 'OK') { + const modalInfoRef = this.ngbModal.open(InformationModalComponent); + modalInfoRef.componentInstance.title = 'Success'; + modalInfoRef.componentInstance.message = 'Sync operation completed successfully!'; + modalInfoRef.result.then((_res) => { + if (_res === 'Ok') + this.getRolesForSelectedCentralizedApp(this.selectedCentralizedApp); + }, (result) => { + + }) + } else { + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Sync operation failed for ' + this.appName + 'res.message'; + } + }).catch(err => { + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Sync operation failed for ' + this.appName + 'err.message'; + }); + }; + + getRolesForSelectedCentralizedApp(val) { + this.showSpinner = true; + this.availableRoles = []; + this.roleDataSource = new MatTableDataSource(this.availableRoles); + this.applicationsService.getSingleAppInfoById(val).subscribe((res: any) => { + this.appName = res.name; + if (res.centralAuth == true) { + this.syncRolesApplied = true; + } + }); + this.getAppRoles(val); + } + + private getAppRoles(val: any) { + this.roleService.getRoles(val).subscribe((data: any) => { + if (data) { + var j = data; + j = JSON.parse(j.data); + this.availableRoles = j.availableRoles; + this.roleDataSource = new MatTableDataSource(this.availableRoles); + this.roleDataSource.sort = this.sort; + this.roleDataSource.paginator = this.paginator; + this.showSpinner = false; + } + else { + this.showSpinner = false; + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Failed to get ' + this.appName + ' roles. Please try again later!'; + } + }, (error: HttpErrorResponse) => { + this.showSpinner = false; + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = 'Error'; + modalErrorRef.componentInstance.message = 'Failed to get ' + this.appName + ' roles. Please try again later!'; + }); + } +} diff --git a/portal-FE-common/src/app/pages/scheduler/Scheduler.ts b/portal-FE-common/src/app/pages/scheduler/Scheduler.ts new file mode 100644 index 00000000..424e14bb --- /dev/null +++ b/portal-FE-common/src/app/pages/scheduler/Scheduler.ts @@ -0,0 +1,49 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ============LICENSE_END============================================ + * + * + */ + +export interface IScheduler { + range ?: any; + fromDate ?: any; + endDate ?: any; + durationType ?: any; + duration ?: any; + fallBackDuration ?: any; + concurrencyLimit ?: any; + policy ?: any; + +} diff --git a/portal-FE-common/src/app/pages/scheduler/scheduler.component.html b/portal-FE-common/src/app/pages/scheduler/scheduler.component.html new file mode 100644 index 00000000..edf8bcc2 --- /dev/null +++ b/portal-FE-common/src/app/pages/scheduler/scheduler.component.html @@ -0,0 +1,116 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <!--Modal Headers--> + <div class="modal-header"> + <h4 class="modal-title">Scheduler Change</h4> + <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross')"> + <span aria-hidden="true">×</span> + </button> + </div> + <!--Modal Body goes here--> + <div class="modal-body"> + <div class="application-details-modal"> + <div class="app-properties-main" scroll-top="appDetails.scrollApi"> + <form name="appForm" novalidate autocomplete="off"> + <div id="app-left-container" class="left-container"> + <div class="property-label checkbox-label"> + <mat-radio-group aria-label="Select an option" [(ngModel)]="schedulerForm.checkboxSelection" name="range"> + <mat-radio-button *ngFor="let opt of ranges" [value]="opt.value">{{opt.labelValue }} + </mat-radio-button> + </mat-radio-group> + </div> + + <div class="property required"> + <mat-form-field> + <input id="startDate" name="startDate" matInput [matDatepicker]="startDatepicker" [(ngModel)]="schedulerForm.fromDate" placeholder="Choose start date"> + <mat-datepicker-toggle matSuffix [for]="startDatepicker"></mat-datepicker-toggle> + <mat-datepicker #startDatepicker></mat-datepicker> + </mat-form-field> + + <mat-form-field> + <input id="endDate" name="endDate" matInput [matDatepicker]="endDatepicker" [(ngModel)]="schedulerForm.toDate" placeholder="Choose end date"> + <mat-datepicker-toggle name="endDatePickToggle" matSuffix [for]="endDatepicker"></mat-datepicker-toggle> + <mat-datepicker #endDatepicker></mat-datepicker> + </mat-form-field> + + </div> + <mat-form-field> + <div id="duration-Type-label" class="property-label"> + Please Select Option For Duration and FallBack</div> + <!--<mat-label> select </mat-label>--> + <mat-select name="dType" [(ngModel)]="schedulerForm.durationType"> + <mat-option *ngFor="let dType of timeUnit" [value]="dType"> + {{dType.text}}</mat-option> + </mat-select> + </mat-form-field> + + <div class="property"> + <div id="duration-label" class="property-label">Duration</div> + <input id="duration" name="duration" [(ngModel)]="schedulerForm.duration" maxlength="256" /> + + <div id="duration-label" class="property-label">Fallback Duration</div> + <input id="fallBackDuration" name="fallBackDuration" [(ngModel)]="schedulerForm.fallbackDuration" maxlength="256" /> + + <div id="duration-label" class="property-label">Concurrency Limit</div> + <input id="concurrencyLimit" name="concurrencyLimit" [(ngModel)]="schedulerForm.concurrencyLimit" maxlength="256" /> + + </div> + + <mat-form-field> + <mat-label> Select policy </mat-label> + <mat-select name="policy" [(ngModel)]="schedulerForm.policyName" style="width:500px;"> + + <mat-option *ngFor="let policy of policys" [value]="policy.policyName" style="width:500px;"> + {{policy.policyName}} + </mat-option> + </mat-select> + </mat-form-field> + + </div> + + </form> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" (click)="sendSchedulerReq()">Schedule</button> + <!-- <button type="button" class="btn btn-primary" (click)="submit()">Schedule</button> --> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Cancel</button> + </div> +</div> diff --git a/portal-FE-common/src/app/pages/scheduler/scheduler.component.scss b/portal-FE-common/src/app/pages/scheduler/scheduler.component.scss new file mode 100644 index 00000000..46d3d8a8 --- /dev/null +++ b/portal-FE-common/src/app/pages/scheduler/scheduler.component.scss @@ -0,0 +1,37 @@ +/* + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright © 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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============================================ + * + * + */
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/scheduler/scheduler.component.spec.ts b/portal-FE-common/src/app/pages/scheduler/scheduler.component.spec.ts new file mode 100644 index 00000000..35f9b50d --- /dev/null +++ b/portal-FE-common/src/app/pages/scheduler/scheduler.component.spec.ts @@ -0,0 +1,62 @@ +/* + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright © 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { SchedulerComponent } from './scheduler.component'; + +describe('SchedulerComponent', () => { + let component: SchedulerComponent; + let fixture: ComponentFixture<SchedulerComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SchedulerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SchedulerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/scheduler/scheduler.component.ts b/portal-FE-common/src/app/pages/scheduler/scheduler.component.ts new file mode 100644 index 00000000..d1bcd984 --- /dev/null +++ b/portal-FE-common/src/app/pages/scheduler/scheduler.component.ts @@ -0,0 +1,782 @@ +/* + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright © 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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, Input } from '@angular/core'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { SchedulerService } from 'src/app/shared/services/scheduler/scheduler.service'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; +import { UserProfileService } from 'src/app/shared/services'; +import { analyzeAndValidateNgModules } from '@angular/compiler'; + +@Component({ + selector: 'app-scheduler', + templateUrl: './scheduler.component.html', + styleUrls: ['./scheduler.component.scss'] +}) +export class SchedulerComponent implements OnInit { + + pollpromise; + /*Assign the data that's passed to scheduler UI*/ + hasParentData: boolean = true; + schedulerID = ''; + orgUserId = ""; + policys = []; + @Input() payload: any; + + ranges = [ + { id: 'now', value: 'true', labelValue: 'Now' }, + { id: 'range', value: 'false', labelValue: 'Range' } + ]; + + range = this.ranges[0].labelValue; + + selectedPolicy={ + policyName:"", + policyConfig:"" + }; + + scheduler = {}; + schedulingInfo = {}; + timeSlots = []; + changeManagement = {}; + + schedulerForm = { + checkboxSelection : 'false', + fromDate: null, + toDate: null, + duration:'', + durationType:'', + fallbackDuration:'', + concurrencyLimit:'', + policyName: '' + }; + + schedulerObjConst= { + domain: null, + scheduleName : '', + WorkflowName : '', + CallbackUrl : '', + approvalType : '', + approvalSubmitStatus : '', + approvalRejectStatus : '', + getTimeslotRate :0, + policyName : '', + groupId : '', + } + + vnfNames = []; + vnfTypes = []; + schedulerObj = { + domain: null, + scheduleId: null, + schedulingInfo: null, + domainData: [], + scheduleName: null, + userId:'' + }; + + vnfObject = { + workflow: null, + vnfNames:'' + }; + + minDate: any = null; + tomorrow: any = null + + /*form validation*/ + durationEmpty: boolean = false; + concurrencyLimitEmpty: boolean = false; + fallBackDurationEmpty: boolean = false; + fromDateEmpty: boolean = false; + toDateEmpty: boolean = false; + + /*interval values for getting time slots*/ + hasvaluereturnd: boolean = true; + hasthresholdreached: boolean = false; + thresholdvalue =10; // interval threshold value + selectedTimeUint: any; + showSpinner: boolean = false; + selectedOption: any; + fromDateGreater: any; + + timeUnit= [ + {text: 'HOURS'}, + {text: 'MINUTES'}, + {text: 'SECONDS'} + ]; + + constructor(public schedulerService : SchedulerService, public userProfileService: UserProfileService, public activeModal: NgbActiveModal, + public ngbModal: NgbModal) { } + + ngOnInit() { + this.tomorrow = new Date(); + this.tomorrow.setDate(this.tomorrow.getDate() + 1); + this.minDate = this.tomorrow.toISOString().substring(0, 10); + this.init(); + } + + /***** Functions for modal popup ******/ + radioSelections(){ + if( this.schedulerForm.checkboxSelection=="true"){ + this.schedulerForm.fromDate=''; + this.schedulerForm.toDate='' + } + } + + /*Dropdown update: everytime values in dropdown chagnes, update the selected value*/ + onChangeUpdatePolicyName(newVal, oldVal){ + for (var i = 0; i < this.policys.length; i++){ + if (this.policys[i].policyName == newVal) { + this.selectedPolicy = this.policys[i]; + } + } + } + + onChangeUpdateTimeUnit(newVal, oldVal){ + for (var i = 0; i < this.timeUnit.length; i++){ + if (this.timeUnit[i].text == newVal){ + this.selectedTimeUint = this.timeUnit[i]; + } + } + } + + /** + * This function is to validate and check if the input is a valid date. + * There are two checkers in this function: + * Check 1: the input is a valid date object,return true, return false otherwise. + * Check 2: check if the input has the format of MM/DD/YYYY or M/D/YYYY and is a valid date value. + * @param dateInput + * @return true/false + */ + isDateValid(dateInput){ + /*Check 1: see if the input is able to convert into date object*/ + if ( Object.prototype.toString.call(dateInput) === "[object Date]" ) + return true; + /*Check 2: see if the input is the date format MM/DD/YYYY */ + var isDateStrFormat = false; + try{ + /*check the format of MM/DD/YYYY or M/D/YYYY */ + let startDateformat = dateInput.split('/'); + if (startDateformat.length != 3) + return false; + let day = startDateformat[1]; + let month = parseInt(startDateformat[0])-1; + let year = startDateformat[2]; + if (year.length != 4) + return false; + /*check the input value and see if it's a valid date*/ + let composedDate = new Date(year, month, day); + if(composedDate.getDate() == day && composedDate.getMonth() == month && composedDate.getFullYear() == year) + isDateStrFormat = true + else + isDateStrFormat =false; + } catch(err){ + return false; + } + return isDateStrFormat; + } + + /** + * This function is to check whether the input date is greater than current date or not. + * @param date + * @return true/false + */ + isStartDateValidFromToday(date){ + if(!this.isDateValid(date)) + return false; + let startDate = new Date(date); + let currentDate = new Date(); + if(startDate<=currentDate) + return false; + + return true; + } + + /** + * This function is to check whether the input to date is greater than input from date. + * @param fromDate , toDate + * @return true/false + */ + isToDateGreaterFromDate(fromDate,toDate){ + if(!this.isDateValid(fromDate) || !this.isDateValid(toDate)) + return false; + var fromDateObj = new Date(fromDate); + var toDateObj = new Date(toDate); + if(toDateObj<=fromDateObj) + return false; + return true; + } + + /** + * This function is to get error message from the input json response object. + * @param response , method + * @return errorMsg + */ + + parseErrorMsg(response, method){ + var errorMsg = ''; + if(response.entity){ + try{ + var entityJson = JSON.parse(response.entity); + if(entityJson){ + errorMsg = entityJson.requestError.text; + } + }catch(err){ + console.log('SchedulerCtrl::' + method +' error: ' + err); + } + } + return errorMsg; + } + + /***** Scheduler UI functions *****/ + + /* This function is to send scheduler task approval to scheduler microservice. */ + + submit(){ + this.showSpinner =true; + let approvalDateTime = new Date(this.timeSlots[0].startTime); + this.schedulingInfo={ + scheduleId: this.schedulerID, + approvalDateTime:approvalDateTime.toISOString(), + approvalUserId:this.orgUserId, + approvalStatus:this.schedulerObjConst.approvalSubmitStatus, + approvalType: this.schedulerObjConst.approvalType + } + let approvalObj= JSON.stringify(this.schedulingInfo) + + this.schedulerService.postSubmitForApprovedTimeslots(approvalObj) + .subscribe( _data => { + let response = _data; + if(response.status>=200 && response.status<=204){ + this.openConfirmationModal("Successfully Sent for Approval",''); + }else{ + var errorMsg = this.parseErrorMsg(response, 'postSubmitForApprovedTimeslots'); + this.openConfirmationModal("Failed to Send for Approval ", errorMsg); + } + this.showSpinner = false; + }, error => { + console.log('SchedulerCtrl::postSubmitForApprovedTimeslots error: ' + error); + var errorMsg = ''; + if(error.data) + errorMsg = this.parseErrorMsg(error.data, 'postSubmitForApprovedTimeslots'); + else + errorMsg = error; + + this.openConfirmationModal("There was a problem sending Schedule request.", errorMsg); + this.showSpinner = false; + }); + } + + /* This function is to send scheduler task rejection to scheduler microservice. */ + + reject(){ + this.showSpinner =true; + let approvalDateTime = new Date(this.timeSlots[0].startTime); + this.schedulingInfo={ + scheduleId: this.schedulerID, + approvalDateTime:approvalDateTime.toISOString(), + approvalUserId: this.orgUserId, + approvalStatus: this.schedulerObjConst.approvalRejectStatus, + approvalType: this.schedulerObjConst.approvalType + } + let approvalObj= JSON.stringify(this.schedulingInfo); + this.schedulerService.postSubmitForApprovedTimeslots(approvalObj) + .subscribe( _data => { + let response = _data; + if(response.status>=200 && response.status<=299){ + this.openConfirmationModal("Successfully Sent for Reject",''); + }else{ + var errorMsg = this.parseErrorMsg(response, 'postSubmitForApprovedTimeslots'); + this.openConfirmationModal("Failed to Send for Approval ", errorMsg); + } + this.showSpinner = false; + }, error => { + console.log('SchedulerCtrl::postSubmitForApprovedTimeslots error: ' + error); + var errorMsg = ''; + if(error.data) + errorMsg = this.parseErrorMsg(error.data, 'postSubmitForApprovedTimeslots'); + else + errorMsg = error; + + this.openConfirmationModal("There was a problem rejecting Schedule request. .", errorMsg); + this.showSpinner = false; + }); + } + + /* This function is to send policy config and receive scheduler Id. */ + + sendSchedulerReq(){ + this.timeSlots=[]; + this.timeSlots.length=0; + this.schedulerObj.userId = this.orgUserId; + this.schedulerObj.domainData[0].WorkflowName= this.vnfObject.workflow; + this.schedulerObj.schedulingInfo.normalDurationInSeconds= this.convertToSecs(this.schedulerForm.duration); + this.schedulerObj.schedulingInfo.additionalDurationInSeconds= this.convertToSecs(this.schedulerForm.fallbackDuration); + this.schedulerObj.schedulingInfo.concurrencyLimit=parseInt(this.schedulerForm.concurrencyLimit); + this.schedulerObj.schedulingInfo['vnfDetails'][0].groupId=this.schedulerObjConst.groupId; + this.schedulerObj.schedulingInfo['vnfDetails'][0].node = this.getVnfData(this.vnfObject.vnfNames); + + for(var i=0;i<this.policys.length;i++){ + if(this.policys[i].policyName == this.schedulerForm.policyName){ + try{ + var config = this.policys[i].config; + var configJson = JSON.parse(config); + this.selectedPolicy.policyConfig = configJson.policyName; + }catch(err){ + this.openConfirmationModal("There was a problem setting Policy config. Please try again later.", err); + return; + } + } + } + + + this.schedulerObj.schedulingInfo.policyId = this.selectedPolicy.policyConfig; + let changeWindow=[{ + startTime: new Date(this.schedulerForm.fromDate), + endTime: new Date(this.schedulerForm.toDate) + //startTime: $filter('date')(new Date(this.schedulerForm.fromDate), "yyyy-MM-ddTHH:mmZ", "UTC"), + //endTime: $filter('date')(new Date(this.schedulerForm.toDate), "yyyy-MM-ddTHH:mmZ", "UTC") + }]; + this.schedulerObj.schedulingInfo['vnfDetails'][0].changeWindow=changeWindow; + if(this.schedulerForm.checkboxSelection=="true"){//When Scheduled now we remove the changeWindow + delete this.schedulerObj.schedulingInfo['vnfDetails'][0].changeWindow; + } + let requestScheduler= JSON.stringify(this.schedulerObj) + this.showSpinner = true; + this.schedulerService.getStatusSchedulerId(requestScheduler) + .subscribe( _data => { + let response = _data; + let errorMsg = ''; + if(response && response.entity!=null){ + this.openConfirmationModal("There was a problem retrieving scheduler ID. Please try again later. ",''); + }else{ + if(response && response.uuid){ + this.schedulerID = response.uuid; + let scheduledID= JSON.stringify({scheduleId:this.schedulerID}); + this.seviceCallToGetTimeSlots(); + }else{ + this.openConfirmationModal("There was a problem retrieving scheduler ID. Please try again later ", errorMsg); + } + } + this.showSpinner = false; + }, error => { + this.showSpinner = false; + console.log('SchedulerCtrl::getStatusSchedulerId error: ' + error); + let errorMsg = ''; + if(error.data) + errorMsg = this.parseErrorMsg(error.data, 'postSubmitForApprovedTimeslots'); + else + errorMsg = error; + this.openConfirmationModal("There was a problem retrieving scheduler ID. Please try again later.", errorMsg); + }); + } + + seviceCallToGetTimeSlots(){ + this.showSpinner = true; + this.schedulerService.getTimeslotsForScheduler(this.schedulerID) + .subscribe( _data => { + let response = _data; + if(this.schedulerForm.checkboxSelection=="false"){ + if(response.entity && JSON.parse(response.entity).schedule){ //received the timeslots + let entityJson = JSON.parse(response.entity); + let scheduleColl=JSON.parse(entityJson.schedule); + if(scheduleColl.length>0){ + this.timeSlots =scheduleColl; + let hasvaluereturnd = false; + this.showSpinner = false; + this.stopPoll(); + this.openConfirmationModal(entityJson.scheduleId +" Successfully Returned TimeSlots.",''); + }else{ + this.openConfirmationModal("No time slot available",''); + } + }else{ // do polling + if(this.timeSlots.length==0 && this.hasthresholdreached==false){ + let polltime = this.schedulerObjConst.getTimeslotRate*1000; + let pollpromise= this.poll(polltime, function () { + if(this.timeSlots.length==0){ + this.hasvaluereturnd = true; + this.seviceCallToGetTimeSlots() + }else + this.hasvaluereturnd = false; + }); + } else { + if(this.showSpinner === true){ + this.showSpinner = false; + this.hasthresholdreached = false; + this.openConfirmationModal("Failed to get time slot - Timeout error. Please try again later",''); + } + } + } + }else{ + if(response.entity){ + this.showSpinner = false; + if(this.schedulerForm.checkboxSelection=="false"){ + this.openConfirmationModal("Schedule ID :" + response.entity.scheduleId +" is ready to schedule.",''); + }else{ + var entityObj = JSON.parse(response.entity); + this.openConfirmationModal("ID :" + entityObj.scheduleId +" is successfully sent for Approval",''); + } + } + } + }, error => { + this.showSpinner = false; + console.log('SchedulerCtrl::seviceCallToGetTimeSlots error: ' + error); + this.openConfirmationModal("There was a problem retrieving time slows. Please try again later.", ''); + }); + } + + + getPolicy(){ + this.schedulerService.getPolicyInfo() + .subscribe( _data => { + let res = _data; + if(res==null || res=='' || res.status==null || !(res.status>=200 && res.status<=299)){ + console.log('SchedulerWidgetCtrl::getPolicyInfo caught error', res); + var errorMsg = this.parseErrorMsg(res, 'getPolicy'); + this.openConfirmationModal("There was a problem retrieving ploicy. Please try again later. ", errorMsg); + }else{ + this.policys = res.entity; + } + }, error => { + console.log('SchedulerCtrl::getStatusSchedulerId error: ' + error); + }); + } + + removeXMLExtension(str:any){ + return str.replace(".xml",""); + } + + /* Find Button */ + schedule() { + if(this.formValidation()){ + this.sendSchedulerReq(); + } + } + + extractChangeManagementCallbackDataStr(changeManagement) { + console.log(changeManagement); + let result = { + requestType: null, + requestDetails: null + }; + result.requestType = changeManagement.workflow; + var workflowType = changeManagement.workflow; + result.requestDetails = []; + changeManagement.vnfNames.forEach(function (vnf: any) { + + try{ + var requestInfoData ={}; + var requestParametersData ={ + + }; + if (vnf.availableVersions && vnf.availableVersions.length!=0){ + + requestInfoData ={ + source: vnf.availableVersions[0].requestInfo.source, + suppressRollback: vnf.availableVersions[0].requestInfo.suppressRollback, + requestorId: vnf.availableVersions[0].requestInfo.requestorId + } + + if(workflowType=='Update'){ + requestParametersData = { + usePreload: vnf.availableVersions[0].requestParameters.usePreload + } + }else if(workflowType=="Replace"){ + requestParametersData = { + rebuildVolumeGroups: vnf.availableVersions[0].requestParameters.usePreload + } + }else if(workflowType=="VNF In Place Software Update"){ + var payloadObj = { + 'existing_software_version':changeManagement.existingSoftwareVersion, + 'new_software_version':changeManagement.newSoftwareVersion, + 'operations_timeout':changeManagement.operationTimeout + }; + requestParametersData = { + payload: JSON.stringify(payloadObj) + } + }else if(workflowType=="VNF Config Update"){ + requestParametersData = { + payload: changeManagement.configUpdateFile + } + } + }else if(workflowType=="VNF In Place Software Update"){ + var payloadObj = { + 'existing_software_version':changeManagement.existingSoftwareVersion, + 'new_software_version':changeManagement.newSoftwareVersion, + 'operations_timeout':changeManagement.operationTimeout + }; + requestParametersData = { + payload: JSON.stringify(payloadObj) + } + }else if(workflowType=="VNF Config Update"){ + requestParametersData = { + payload: changeManagement.configUpdateFile + } + } + + if(changeManagement.testApi){ + requestParametersData['testApi'] = changeManagement.testApi; + } + + + + var data = { + vnfName: vnf.name, + vnfInstanceId: vnf.id, + modelInfo: { + modelType: 'vnf', + modelInvariantId: vnf.properties['model-invariant-id'], + modelVersionId: vnf.modelVersionId, + modelName: vnf.properties['vnf-name'], + modelVersion: vnf.version, + modelCustomizationName: vnf.properties['model-customization-name'], + modelCustomizationId: vnf.properties['model-customization-id'] + }, + cloudConfiguration: vnf.cloudConfiguration, + requestInfo: requestInfoData, + relatedInstanceList: [], + requestParameters:requestParametersData + }; + + var serviceInstanceId = ''; + vnf['service-instance-node'].forEach( function (instanceNode:any) { + if(instanceNode['node-type'] === 'service-instance'){ + serviceInstanceId = instanceNode.properties['service-instance-id']; + } + }); + + if (vnf.availableVersions && vnf.availableVersions.length!=0){ + vnf.availableVersions[0].relatedInstanceList.forEach( function (related:any) { + var rel = related.relatedInstance; + var relatedInstance = { + instanceId: serviceInstanceId, + modelInfo: { + modelType: rel.modelInfo.modelType, + modelInvariantId: rel.modelInfo.modelInvariantId, + modelVersionId: rel.modelInfo.modelVersionId, + modelName: rel.modelInfo.modelName, + modelVersion: rel.modelInfo.modelVersion, + modelCustomizationName: rel.modelInfo.modelCustomizationName, + modelCustomizationId: rel.modelInfo.modelCustomizationId + } + }; + if (rel.vnfInstanceId) + relatedInstance.instanceId = rel.vnfInstanceId; + + data.relatedInstanceList.push({relatedInstance: relatedInstance}); + }); + } + }catch(err){ + console.log('SchedulerCtrl::extractChangeManagementCallbackDataStr error: ' + err); + } + + result.requestDetails.push(data); + }); + return JSON.stringify(result); + } + + constructScheduleInfo(){ + let callbackData = this.extractChangeManagementCallbackDataStr(this.vnfObject); + this.schedulerObj = { + domain: this.schedulerObjConst.domain, + scheduleId: '', + scheduleName: this.schedulerObjConst.scheduleName, + userId: '', + domainData: [{ + 'WorkflowName': this.schedulerObjConst.WorkflowName, + 'CallbackUrl': this.schedulerObjConst.CallbackUrl, + 'CallbackData': callbackData + }], + schedulingInfo: { + normalDurationInSeconds: '', + additionalDurationInSeconds: '', + concurrencyLimit: '', + policyId: '', + vnfDetails: [ + { + groupId: "", + node: [], + changeWindow: [{ + startTime: '', + endTime: '' + }] + } + ] + }, + } + } + + formValidation(){ + this.durationEmpty=false; + this.concurrencyLimitEmpty = false; + this.fallBackDurationEmpty=false; + this.fromDateGreater=false; + this.fromDateEmpty=false; + this.toDateEmpty=false; + if(this.schedulerForm.duration=='') + this.durationEmpty=true; + if(this.schedulerForm.fallbackDuration=='') + this.fallBackDurationEmpty=true; + if(this.schedulerForm.concurrencyLimit=='') + this.concurrencyLimitEmpty = true; + if(!(this.schedulerForm.fromDate instanceof Date)) + this.fromDateEmpty=true; + if(!(this.schedulerForm.toDate instanceof Date )) + this.toDateEmpty=true; + var fromDateObj = new Date(this.schedulerForm.fromDate); + var toDateObj = new Date(this.schedulerForm.toDate); + if(fromDateObj>toDateObj) + this.fromDateGreater = true; + if(this.durationEmpty|| this.fallBackDurationEmpty || this.concurrencyLimitEmpty || ((this.fromDateEmpty || + this.toDateEmpty) && this.schedulerForm.checkboxSelection=='false' ) ||this.fromDateGreater) + return false; + if(this.schedulerForm.checkboxSelection == 'false' && (!this.isDateValid(this.schedulerForm.toDate) + || !this.isDateValid(this.schedulerForm.fromDate))) + return false; + if(this.selectedPolicy.policyName=='' || this.selectedPolicy.policyName=='Select Policy'){ + this.openConfirmationModal("Policy is required", ''); + return false; + } + return true; + } + + /*************utility functions**************/ + + convertToSecs(number){ + this.selectedTimeUint = this.schedulerForm.durationType; + var totalSecs; + if(this.selectedTimeUint.text === 'HOURS'){ + totalSecs=number * 3600; + } else if(this.selectedOption === 'MINUTES') { + totalSecs=number * 60; + } else { + totalSecs=number; + } + return totalSecs; + } + + poll(interval, callback) { + return setTimeout(function () { + if (this.hasvaluereturnd) //check flag before start new call + callback(this.hasvaluereturnd); + this.thresholdvalue = this.thresholdvalue - 1; //Decrease threshold value + if (this.thresholdvalue == 0) + this.stopPoll(); // Stop $interval if it reaches to threshold + }, interval) + } + + stopPoll() { + //this.interval.cancel(pollpromise); + this.thresholdvalue = 0; //reset all flags. + this.hasvaluereturnd = false; + this.hasthresholdreached=true; + this.showSpinner = false; + } + + getVnfData(arrColl){ + var vnfcolletion=[]; + for(var i=0;i<arrColl.length;i++) + vnfcolletion.push(arrColl[i].name); + return vnfcolletion + } + + getScheduleConstant(){ + this.schedulerService.getSchedulerConstants() + .subscribe( _data => { + let res = _data; + if(res==null || res=='' || res.status==null || res.status!="OK"){ + console.log('SchedulerWidgetCtrl::getSchedulerConstants caught error', res); + this.openConfirmationModal('There is a problem about the Scheduler UI. Please try again later.', ''); + }else{ + let response = res.response; + this.schedulerObjConst= { + domain: response.domainName, + scheduleName : response.scheduleName, + WorkflowName : response.workflowName, + CallbackUrl : response.callbackUrl, + approvalType : response.approvalType, + approvalSubmitStatus : response.approvalSubmitStatus, + approvalRejectStatus : response.approvalRejectStatus, + getTimeslotRate : response.intervalRate, + policyName : response.policyName, + groupId : response.groupId + } + this.constructScheduleInfo(); + this.getPolicy() + } + }, error => { + console.log('SchedulerCtrl::getStatusSchedulerId error: ' + error); + }); + } + + /*This function is to get the current logged in user id*/ + getUserId(){ + this.showSpinner = true; + this.userProfileService.getUserProfile() + .subscribe( _data => { + let res = _data; + this.orgUserId = res['orgUserId']; + this.showSpinner = false; + }, error => { + this.showSpinner = false; + console.log('SchedulerCtrl::getStatusSchedulerId error: ' + error); + }); + } + + init(){ + this.showSpinner = false; + this.selectedTimeUint=this.timeUnit[0].text; + this.vnfObject = this.payload['widgetData']; + this.getUserId(); + this.getScheduleConstant(); + } + + openConfirmationModal(_title: string, _message: string) { + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = _title; + modalInfoRef.componentInstance.message = _message; + } + + openInformationModal(_title: string, _message: string){ + const modalInfoRef = this.ngbModal.open(InformationModalComponent); + modalInfoRef.componentInstance.title = _title; + modalInfoRef.componentInstance.message = _message; + return modalInfoRef; + } + +} diff --git a/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.html b/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.html new file mode 100644 index 00000000..b4325dbd --- /dev/null +++ b/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.html @@ -0,0 +1,168 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <!--Modal Headers--> + <div class="modal-header"> + <h4 class="modal-title">Add a New Notification</h4> + <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross')"> + <span aria-hidden="true">×</span> + </button> + </div> + + <!--Modal Body goes here--> + <div class="modal-body" [ngClass]="{'disable-div': (isEditMode)}"> + <div class="user-notification-details-contents"> + <!-- Left Side container--> + <div class="left-container"> + <div id="add-user-notif-broadcast" class="add-widget-field"> + <div class="user-notif-label" style="padding-bottom: 7px">Broadcast to All Categories</div> + <mat-radio-group [(ngModel)]="notification.isForAllRoles" #radioGroup="matRadioGroup" aria-label="Select an option"> + <span style="padding-left: 12px;"><mat-radio-button value="Y">Yes</mat-radio-button></span><br/> + <span style="padding-left: 12px;"><mat-radio-button value="N">No</mat-radio-button></span> + </mat-radio-group> + <div style="padding-bottom: 12px;"></div> + + <div class="categories-selection" [ngClass]="{'disable-category-selection': (!notification.isForAllRoles ||notification.isForAllRoles=='Y')}"> + <div [ngClass]="{'required': (notification.isForAllRoles=='N')}" style="padding-bottom: 7px"> + Categories + </div> + <mat-radio-group [(ngModel)]="notification.isFunctionalMenu" #radioGroup="matRadioGroup" aria-label="Select an option"> + <span style="padding-left: 12px;"><mat-radio-button (click)="getFunctionalMenu()" value="Y">Functional Menu</mat-radio-button></span><br/> + <span style="padding-left: 12px;"><mat-radio-button (click)="getAppRoleIds()" value="N">Application Roles</mat-radio-button></span> + </mat-radio-group> + <br/> + <div id="user-notif-tree-div" class="tree-div" > + <!-- Mat Tree code will goes here..--> + <mat-tree [dataSource]="dataSource" [treeControl]="treeControl"> + <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding> + <button mat-icon-button disabled></button> + <mat-checkbox class="checklist-leaf-node" + [checked]="checklistSelection.isSelected(node)" + (change)="checklistSelection.toggle(node);">{{node.item}}</mat-checkbox> + </mat-tree-node> + + <mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" matTreeNodePadding> + <button mat-icon-button disabled></button> + </mat-tree-node> + + <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding> + <button mat-icon-button matTreeNodeToggle + [attr.aria-label]="'toggle ' + node.filename"> + <mat-icon class="mat-icon-rtl-mirror"> + {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}} + </mat-icon> + </button> + <mat-checkbox + [indeterminate]="descendantsPartiallySelected(node)" + (change)="todoItemSelectionToggle(node)">{{node.item}} + </mat-checkbox> + </mat-tree-node> + </mat-tree> + </div> + </div> + </div> + </div> + + <!-- Right Side container--> + <div id="app-conatiner-right" class="right-container"> + <div id="add-user-notif-priority" class="add-widget-field"> + <div id="user-notification-priority-label" class="user-notif-label">Priority</div> + <mat-radio-group [(ngModel)]="notification.priority" #radioGroup="matRadioGroup" aria-label="Select an option"> + <span><mat-radio-button value="1">Normal</mat-radio-button></span><br/> + <span><mat-radio-button value="2">Important</mat-radio-button></span> + </mat-radio-group> + </div> + <div id="add-user-notif-startdate" class="add-widget-field" style="padding-bottom: 12px; width: 301px !important;"> + <div id="user-notification-startdate-label" class="user-notif-label"> + <div class="user-notif-label"> + <span runat="server" ID="required" class="notifcation-label-required">*</span>Start Date (Local Time) + </div> + <mat-form-field> + <input [(ngModel)]="notification.startTime" name="startTime" matInput [matDatepicker]="startdate" + placeholder="MM/dd/yyyy"> + <mat-datepicker-toggle matSuffix [for]="startdate"></mat-datepicker-toggle> + <mat-datepicker #startdate></mat-datepicker> + </mat-form-field> + <div class="user-notif-label"> + <span runat="server" ID="required" class="notifcation-label-required">*</span>End Date (Local Time) + </div> + <mat-form-field> + <input [(ngModel)]="notification.endTime" name="endTime" matInput [matDatepicker]="enddate" + placeholder="MM/dd/yyyy"> + <mat-datepicker-toggle matSuffix [for]="enddate"></mat-datepicker-toggle> + <mat-datepicker #enddate></mat-datepicker> + </mat-form-field> + </div> + </div> + + <div id="add-user-notif-title" class="add-widget-field" style="padding-bottom: 12px;"> + <div id="user-notification-title-label" class="user-notif-label"> + <span runat="server" ID="required" class="notifcation-label-required" visible="false"> *</span> Title + </div> + <textarea id="add-notification-input-title" + [(ngModel)]="notification.msgHeader" name="content" class="add-notification-input-title-ht"> + </textarea> + <div id="user-title-required"> + <small *ngIf="notification.msgHeader ==''" class="mandatory-categories">Title is Required</small> + </div> + </div> + + <div id="add-user-notif-message" class="add-widget-field" + style="padding-bottom: 12px;"> + <div id="user-notif-message-label" class="user-notif-label"> + <span runat="server" ID="required" class="notifcation-label-required" + visible="false"> *</span> Message + </div> + <textarea id="user-notif-input-message" class="add-notification-input-title-ht" + [(ngModel)]="notification.msgDescription" name="content"> + </textarea> + <div id="user-notif-message-required"> + <small *ngIf="notification.msgDescription ==''" class="mandatory-categories">Message is Required</small> + </div> + </div> + </div> + </div> + </div> + + <!--Modal Footer goes Here--> + <div class="modal-footer"> + <button type="button" [hidden]="isEditMode" class="btn btn-primary" (click)="addUserNotification('')">Save</button> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Cancel</button> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.scss b/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.scss new file mode 100644 index 00000000..0389b7da --- /dev/null +++ b/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.scss @@ -0,0 +1,116 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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============================================ + * + * + */ + +.container .modal-body .user-notification-details-contents { + padding-left: 0; + padding-top: 16px; + padding-bottom: 16px; + height: 630px; + //overflow-y: auto; + margin-left: 10px; + padding-right: 100px; + margin-bottom: 5px; + color: #5a5a5a; + font-family: Omnes-ECOMP-W02,Arial; + font-size: 14px; + font-weight: 700; +} + +.container .modal-body .user-notification-details-contents .left-container { + display: inline-block; + width: 48%; +} + +.container .modal-body .user-notification-details-contents .right-container { + display: inline-block; + width: 39%; + float: right; +} +.mat-radio-button ~ .mat-radio-button { + margin-left: 16px; +} + +.add-widget-field { + width: 250px; + display: inline-table; + margin: 10px; +} + +.container .modal-body .user-notification-details-contents .right-container .add-notification-input-title-ht{ + width: 284px; + height: 76px; +} + +::ng-deep .modal-dialog { + max-width: 800px; + width: 800px; +} + + +::ng-deep .mat-form-field { + width: 279px; +} + +.tree-div { + height: 308px; + width: 320px; + overflow: auto; +} + +.disable-category-selection{ + opacity: 0.6; + pointer-events: none; +} + +.disable-div{ + opacity: 0.6; + pointer-events: none; +} + +.required::before { + color: #cf2a2a; + margin-right: 2px; + content: "* "; + position: absolute; + top: 141px; + left: 27px; +} + +.notifcation-label-required{ + color: #cf2a2a; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.spec.ts b/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.spec.ts new file mode 100644 index 00000000..142a0ba0 --- /dev/null +++ b/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.spec.ts @@ -0,0 +1,63 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { NewNotificationModalComponent } from './new-notification-modal.component'; + +describe('NewNotificationModalComponent', () => { + let component: NewNotificationModalComponent; + let fixture: ComponentFixture<NewNotificationModalComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NewNotificationModalComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NewNotificationModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.ts b/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.ts new file mode 100644 index 00000000..44ad18f5 --- /dev/null +++ b/portal-FE-common/src/app/pages/user-notification-admin/new-notification-modal/new-notification-modal.component.ts @@ -0,0 +1,943 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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, Input, Output, EventEmitter, Injectable} from '@angular/core'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { NotificationService, FunctionalMenuService } from 'src/app/shared/services'; +import { SelectionModel } from '@angular/cdk/collections'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree'; +import { BehaviorSubject } from 'rxjs'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; + +@Component({ + selector: 'app-new-notification-modal', + templateUrl: './new-notification-modal.component.html', + styleUrls: ['./new-notification-modal.component.scss'] +}) +export class NewNotificationModalComponent implements OnInit { + + notification = { + 'notificationId': null, + 'isOnlineUsersOnly': null, + 'isForAllRoles': null, + 'priority': null, + 'isActive': null, + 'startTime': null, + 'endTime': null, + 'msgHeader': null, + 'msgDescription': null, + 'roleIds': null, + 'isFunctionalMenu': null, + 'treeTitle': null, + 'anyTreeItemSelected': false, + 'notifObj':null, + 'roleObj': { + notificationRoleIds: null + }, + 'notificationRoleIds': null + }; + + /** + * The Json object for to-do list data. + */ + MAT_TREE_DATA = {}; + notificationId = null; + selectedCat = null; + selectedEcompFunc = null; + YN_index_mapping = { + "Y": 0, + "N": 1 + } + + onlineAllUsersOptions = [{ + "index": 0, + "value": "Y", + "title": "Online Users Only" + }, + { + "index": 1, + "value": "N", + "title": "Online & Offline Users" + } + ]; + + isForAllRoles = [{ + "index": 0, + "value": "Y", + "title": "Yes" + }, + { + "index": 1, + "value": "N", + "title": "No" + } + ]; + + priorityOptions = [{ + "index": 0, + "value": "1", + "title": "Normal" + }, + { + "index": 1, + "value": "2", + "title": "Important" + } + ]; + + isActiveOptions = [{ + "index": 0, + "value": "Y", + "title": "Yes" + }, + { + "index": 1, + "value": "N", + "title": "No" + } + ]; + + @Input() selectedNotification: any; + @Output() passEntry: EventEmitter<any> = new EventEmitter(); + result: any; + isEditMode: any; + functionalMenuRes = {}; + treedata = []; + checkBoxObj: any; + + dataChange = new BehaviorSubject<TodoItemNode[]>([]); + get data(): TodoItemNode[] { return this.dataChange.value; } + + constructor(public ngbModal: NgbModal, + public activeModal: NgbActiveModal, + private notificationService: NotificationService, + public functionalMenuService: FunctionalMenuService) { } + + ngOnInit() { + this.notification.isFunctionalMenu='Y'; + console.log("selectedNotification ::::",this.selectedNotification); + if(this.selectedNotification && this.selectedNotification.msgSource !=''){ + + this.isEditMode = true; + this.notification = Object.assign({}, this.selectedNotification); + if(this.selectedNotification && this.selectedNotification.priority ==1 ){ + this.notification.priority = this.priorityOptions[0].value; + }else{ + this.notification.priority = this.priorityOptions[1].value; + } + if(this.selectedNotification && this.selectedNotification.startTime){ + this.notification.startTime = new Date(this.selectedNotification.startTime); + } + if(this.selectedNotification && this.selectedNotification.endTime ){ + this.notification.endTime = new Date(this.selectedNotification.endTime); + } + this.notification.isFunctionalMenu='Y'; + }else{ + this.isEditMode = false; + this.notification.isActive = this.isActiveOptions[0]; + this.notification.isOnlineUsersOnly = this.onlineAllUsersOptions[1]; + this.notification.isForAllRoles = this.isForAllRoles[0].value; + this.notification.priority = this.priorityOptions[0].value; + this.notification.isFunctionalMenu = "Y"; + this.notification.msgHeader = ''; + this.notification.msgDescription = ''; + this.notification.treeTitle = "Functional Menu"; + this.notification.notifObj = { + isCategoriesFunctionalMenu: true + } + } + this.getFunctionalMenu(); + this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren); + this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable); + this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + const data = this.buildFileTree(this.MAT_TREE_DATA, 0); + this.dataChange.next(data); + this.dataChange.subscribe(data => { + this.dataSource.data = data; + }); + } + + addUserNotification(){ + let notificationRoleIds = [] + let endDate = new Date(this.notification.endTime); + let selectedTreeNode: Array<TodoItemFlatNode> = this.checklistSelection.selected; + selectedTreeNode.forEach(element => { + if(element && element.roleIds && element.roleIds.length >0){ + element.roleIds.forEach(id =>{ + if(id && id !='undefined'){ + notificationRoleIds.push(id); + } + }); + } + }); + console.log("notificationRoleIds >>>>>>>>",notificationRoleIds); + if(notificationRoleIds && notificationRoleIds.length >0){ + notificationRoleIds.sort(); + } + + let newUserNotification = { + 'notificationId': (this.notification && this.notification.notificationId ? this.notification.notificationId: null), + 'isForOnlineUsers': (this.notification && this.notification.isOnlineUsersOnly ? this.notification.isOnlineUsersOnly.value : null), + 'isForAllRoles': this.notification.isForAllRoles, + 'priority': this.notification.priority, + 'activeYn': (this.notification && this.notification.isActive ? this.notification.isActive.value :'Y'), + 'startTime': new Date(this.notification.startTime), + 'endTime': new Date(this.notification.endTime), + 'msgHeader': this.notification.msgHeader, + 'msgDescription': this.notification.msgDescription, + 'roleIds': notificationRoleIds, + 'createdDate': new Date() + }; + + if((newUserNotification.isForAllRoles) && (newUserNotification.priority) && (newUserNotification.activeYn) + && (newUserNotification.startTime) && (newUserNotification.endTime) + && (newUserNotification.msgHeader != '') && (newUserNotification.msgDescription != '')){ + + console.log("newUserNotification >>> ",newUserNotification); + // POST ajax call here; + if (this.isEditMode) { + this.notificationService.updateAdminNotification(newUserNotification) + .subscribe(_data => { + this.result = _data; + if(this.result && this.result.status ==='ERROR'){ + this.openConfirmationModal('Error', this.result.response); + }else{ + this.passEntry.emit(this.result); + this.ngbModal.dismissAll(); + } + }, error =>{ + console.log(error); + this.openConfirmationModal('Error', error); + return; + }); + }else{ + this.notificationService.addAdminNotification(newUserNotification) + .subscribe(_data => { + this.result = _data; + if(this.result && this.result.status ==='ERROR'){ + this.openConfirmationModal('Error', this.result.response); + return; + }else{ + this.passEntry.emit(this.result); + this.ngbModal.dismissAll(); + } + }, error =>{ + console.log(error); + this.openConfirmationModal('Error', error); + return; + }); + } + }else{ + this.openConfirmationModal("","Please fill in all required(*) fields."); + return; + } + } + + getFunctionalMenu(){ + let menu_role_dict = {}; + /**First Rest Call */ + this.functionalMenuService.getFunctionalMenuRole() + .subscribe(role_res => { + this.result = role_res; + if (this.result == null || this.result == 'undefined') { + console.log('FunctionalMenuService::getFunctionalMenu Failed: Result or result.data is null'); + }else { + //console.log('First Rest Call Data ::',this.result); + for (var i in role_res) { + // if first time appear in menu_role_dict + if (!(role_res[i].menuId in menu_role_dict)) { + menu_role_dict[role_res[i].menuId] = [role_res[i].roleId]; + } else { + menu_role_dict[role_res[i].menuId].push(role_res[i].roleId); + } + } + + /** 2nd Rest Call */ + this.functionalMenuService.getManagedFunctionalMenuForNotificationTree() + .subscribe(res => { + this.result = res; + //console.log("2nd REST CALL RESPONSE :: ",this.result); + var exclude_list = ['Favorites']; + let actualData = []; + //Adding children and label attribute to all objects in res + for (let i = 0; i < this.result.length; i++) { + res[i].child = []; + res[i].name = res[i].text; + res[i].id = res[i].text; + res[i].displayCheckbox = true; + let checkBoxObj = { + isAnyRoleSelected: false + }; + res[i].roleId = menu_role_dict[res[i].menuId]; + res[i].onSelect = function() { + this.notification.anyTreeItemSelected = this.checkTreeSelect(); + }; + + let intersectionObj: any; + intersectionObj = res[i].roleId & this.notification.notificationRoleIds; + if (res[i].roleId && res[i].roleId.length == intersectionObj.length) { + res[i].isSelected = true; + res[i].selected = true; + res[i].indeterminate = false; + } else { + /*default*/ + res[i].isSelected = false; + res[i].selected = false; + res[i].indeterminate = false; + } + } + + // Adding actual child items to children array in res + // objects + let parentChildDict = {}; + let parentChildRoleIdDict = {}; + for (let i = 0; i < this.result.length; i++) { + let parentId = this.result[i].menuId; + parentChildDict[parentId] = []; + parentChildRoleIdDict[parentId] = []; + for (let j = 0; j < this.result.length; j++) { + let childId = res[j].parentMenuId; + if (parentId === childId) { + res[i].child.push(res[j]); + parentChildDict[parentId].push(res[j].menuId); + //if res[j].roleId is defined + if (res[j].roleId) { + for (let k in res[j].roleId) { + parentChildRoleIdDict[parentId].push(res[j].roleId[k]); + } + + } + } + } + } + + //check if grand children exist + for (var key in parentChildDict) { + var child = parentChildDict[key]; + var isGrandParent = false; + if (child.length > 0) { + for (var i in child) { + if (parentChildDict[child[i]].length > 0) { + isGrandParent = true; + break; + } + } + } + if (isGrandParent) { + for (var i in child) { + // if the child has children + if (parentChildDict[child[i]].length > 0) { + for (var j in parentChildRoleIdDict[child[i]]) { + if (parentChildRoleIdDict[key].indexOf(parentChildRoleIdDict[child[i]][j]) === -1) { + parentChildRoleIdDict[key].push(parentChildRoleIdDict[child[i]][j]); + } + } + } + } + } + + }; + + // Sort the top-level menu items in order based on the column + this.result.sort(function(a, b) { + return a.column - b.column; + }); + + // Sort all the child in order based on the column + for (let i = 0; i < this.result.length; i++) { + res[i].child.sort(function(a, b) { + return a.column - b.column; + }); + } + + //Forming actual parent items + for (let i = 0; i < this.result.length; i++) { + let pmid = res[i].parentMenuId; + if (pmid === null) { + actualData.push(res[i]); + } + } + var treedata = actualData[0].child; + + this.treedata = []; + + /*Remove favorite from the list */ + for (var i in treedata) { + if (!(treedata[i].name.indexOf(exclude_list) > -1)) { + this.treedata.push(treedata[i]) + } + } + //setting b2b tree parameter + this.settingTreeParam(); + this.populateTreeDataSourceByFunctionalMenu(this.treedata); + + }, error =>{ + console.log(error); + }); + } + }, error =>{ + console.log(error); + }); + } + + settingTreeParam() { + /**************first level****************/ + for (var fi = 0; fi < this.treedata.length; fi++) { + var fLevel = this.treedata[fi]; + var sLevel = this.treedata[fi].child; + var sLevelSelectedCount = 0; + var sLevelChildNumber = 0 + if (fLevel.child.length == 0 && fLevel.roleId == null) { + delete fLevel.child; + } else if (sLevel) { + /**************Second level****************/ + var sLevelDelArray = []; + for (var si = 0; si < sLevel.length; si++) { + var deletThisSLev = false; + if (sLevel[si].child.length == 0 && sLevel[si].roleId == null) { + sLevel[si].displayCheckbox = false; + sLevelDelArray.push(sLevel[si].name); + sLevel[si].name = ''; + sLevel[si].active = false; + delete sLevel[si].child; + } else if (sLevel[si].child.length == 0) { + delete sLevel[si].child; + } else { + /**************Third level****************/ + var tLevel = sLevel[si].child; + var tLevelSelectedCount = 0; + var tLevelChildNumber = 0; + if (tLevel) { + var tLevelDelArray = []; + var tLevelLen = tLevel.length; + var tLevelRoleIdUndefined = 0; + for (var ti = 0; ti < tLevel.length; ti++) { + delete tLevel[ti].child; + if (tLevel[ti].roleId == null) { + tLevel[ti].displayCheckbox = false; + tLevelDelArray.push(tLevel[ti].name); + tLevel[ti].name = ''; + tLevel[ti].active = false; + tLevelRoleIdUndefined++ + } else { + if (tLevel[ti].isSelected) + tLevelSelectedCount++; + + if (tLevel[ti].displayCheckbox) + tLevelChildNumber++; + } + } + if (tLevelRoleIdUndefined == tLevelLen) + deletThisSLev = true; + if (tLevelSelectedCount == tLevelChildNumber) { + sLevel[si].isSelected = true; + sLevel[si].indeterminate = false; + sLevel[si].active = true; + } else if (tLevelSelectedCount > 0) { + sLevel[si].indeterminate = true; + sLevel[si].active = true; + } + + /*Cleanup unused third level items*/ + for (var i = 0; i < tLevelDelArray.length; i++) { + var name = tLevelDelArray[i]; + for (var ti = 0; ti < tLevel.length; ti++) { + if (name == tLevel[ti].text) { + tLevel.splice(ti, 1); + break; + } + } + } + } + } + if (deletThisSLev) { //remove the whole second level item if all it's child has no roleId + sLevel[si].displayCheckbox = false; + sLevelDelArray.push(sLevel[si].name); + sLevel[si].name = ''; + sLevel[si].active = false; + } else { + if (sLevel[si].isSelected) + sLevelSelectedCount++; + if (sLevel[si].displayCheckbox) + sLevelChildNumber++; + } + } + if (sLevelSelectedCount == sLevelChildNumber && sLevelChildNumber != 0) { + fLevel.isSelected = true; + fLevel.indeterminate = false; + fLevel.active = true; + } else if (sLevelSelectedCount > 0) { + fLevel.indeterminate = true; + fLevel.active = true; + } else { + //fLevel.active=false; + fLevel.indeterminate = false; + } + /*Cleanup unused second level items*/ + for (var i = 0; i < sLevelDelArray.length; i++) { + var name = sLevelDelArray[i]; + for (var si = 0; si < sLevel.length; si++) { + if (name == sLevel[si].text) { + sLevel.splice(si, 1); + break; + } + } + } + } + } +} + + +checkTreeSelect() { + if (this.treedata) { + for (var fi = 0; fi < this.treedata.length; fi++) { + var fLevel = this.treedata[fi]; + if (fLevel.isSelected) { + return true; + } + var sLevel = fLevel.child; + if (sLevel) { + for (var si = 0; si < sLevel.length; si++) { + if (sLevel[si].isSelected) { + return true; + } + var tLevel = sLevel[si].child; + if (tLevel) { + for (var ti = 0; ti < tLevel.length; ti++) { + if (tLevel[ti].isSelected) { + return true; + } + } + } + } + } + } + } + return false; + } + + getAppRoleIds() { + this.notification.notifObj = { + isCategoriesFunctionalMenu: false + }; + this.notificationService.getAppRoleIds() + .subscribe(_data => { + this.result = _data; + let actualData = []; + var app_id_name_list = {}; + this.checkBoxObj = { + isAnyRoleSelected: false + }; + for (let i = 0; i < this.result.length; i++) { + if (!(this.result[i].appId in app_id_name_list)) { + app_id_name_list[this.result[i].appId] = this.result[i].appName; + } + this.result[i].child = []; + this.result[i].name = this.result[i].roleName; + this.result[i].displayCheckbox = true; + this.result[i].id = this.result[i].roleId; + this.result[i].menuId = this.result[i].roleId; + this.result[i].parentMenuId = this.result[i].appId; + this.result[i].can_check = true; + this.result[i].roleId = [this.result[i].roleId]; + this.result[i].onSelect = function() { + this.notification.anyTreeItemSelected = this.checkTreeSelect(); + }; + /*assigning selected value*/ + let intersectionObj: any; + intersectionObj = this.result[i].roleId & this.notification.notificationRoleIds; + if (this.result[i].roleId && this.result[i].roleId.length == intersectionObj.length) { + this.result[i].isSelected = true; + this.result[i].selected = true; + this.result[i].indeterminate = false; + } else { + /*default*/ + this.result[i].isSelected = false; + this.result[i].selected = false; + this.result[i].indeterminate = false; + } + } + + for (var app_id in app_id_name_list) { + + let new_res = { + 'child': null, + 'name': null, + 'id': null, + 'displayCheckbox': null, + 'menuId': null, + 'parentMenuId': '', + 'appId': '', + 'can_check': null, + 'msgDescription': null, + 'roleIds': null, + 'roleId': null, + 'onSelect': null, + }; + + new_res.child = []; + new_res.name = app_id_name_list[app_id]; + new_res.id = app_id; + new_res.displayCheckbox = true; + new_res.menuId = app_id; + new_res.parentMenuId = null; + new_res.appId = null; + new_res.can_check = true; + new_res.roleId = null; + new_res.onSelect = function() { + this.notification.anyTreeItemSelected = this.checkTreeSelect(); + }; + this.result.push(new_res); + } + + let parentChildRoleIdDict = {}; + //Adding actual child items to child array in res objects + for (let i = 0; i < this.result.length; i++) { + let parentId = this.result[i].menuId; + parentChildRoleIdDict[parentId] = []; + for (let j = 0; j < this.result.length; j++) { + let childId = this.result[j].parentMenuId; + if (parentId == childId) { + this.result[i].child.push(this.result[j]); + if (this.result[j].roleId) { + for (let k in this.result[j].roleId) { + parentChildRoleIdDict[parentId].push(this.result[j].roleId[k]); + } + } + } + } + } + + //Forming actual parent items + for (let i = 0; i < this.result.length; i++) { + let parentId = this.result[i].parentMenuId; + if (parentId === null) { + actualData.push(this.result[i]); + } + } + + this.treedata = actualData; + //setting correct parameters for b2b tree + this.settingTreeParam(); + this.makeFinalTreeDataByProcessingAppRoleIds(this.treedata); + + }, error =>{ + console.log(error); + }); + } + + makeFinalTreeDataByProcessingAppRoleIds(treeData_approleids){ + let datamap: any = new Map(); + let data = treeData_approleids; + + if(data && data.length > 0){ + data.forEach(element => { + let iskeyPresent: boolean = false; + try{ + if(datamap.get(element.id)){ + iskeyPresent = true; + } + }catch(e){// + } + if(iskeyPresent){ + let temp = datamap.get(element.id); + temp.push(element); + datamap.set(element.id,temp) + }else{ + datamap.set(element.id,element); + } + }); + } + + if(datamap && datamap.size > 0){ + let treeNode = {}; + datamap.forEach((value, key) => { + if(value.child){ + let keyname = ""; + let childArray = []; + for(let item of value.child){ + keyname = item.appName+"~"+'undefined'; + let childItem = item.roleName+"~"+item.roleId; + childArray.push(childItem); + } + treeNode[keyname] = childArray; + } + }); + console.log("makeFinalTreeDataByProcessingAppRoleIds >>",treeNode); + + this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren); + this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable); + this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + const data = this.buildFileTree(treeNode, 0); + this.dataChange.next(data); + this.dataChange.subscribe(data => { + this.dataSource.data = data; + }); + } + } + + + /** + * Prepare Tree datasoure using functional Menu Data. + * @param functionMenuData + */ + populateTreeDataSourceByFunctionalMenu(functionMenuData: any){ + console.log("populateTreeDataSourceByFunctionalMenu :: ",functionMenuData); + if(functionMenuData){ + let treeNode = {}; + functionMenuData.forEach(element => { + let name = element.text+"~"+element.roleId; + treeNode[name] = { }; + + if(element.child && element.child.length >0){ + let childArray = []; + element.child.forEach(element => { + let childItem = element.text+"~"+element.roleId; + childArray.push(childItem); + }); + treeNode[name] = childArray; + } + + }); + console.log("treeNode>>",treeNode); + + this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren); + this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable); + this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + const data = this.buildFileTree(treeNode, 0); + this.dataChange.next(data); + this.dataChange.subscribe(data => { + this.dataSource.data = data; + }); + } + } + + openConfirmationModal(_title: string, _message: string) { + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = _title; + modalInfoRef.componentInstance.message = _message; + } + + openInformationModal(_title: string, _message: string){ + const modalInfoRef = this.ngbModal.open(InformationModalComponent); + modalInfoRef.componentInstance.title = _title; + modalInfoRef.componentInstance.message = _message; + return modalInfoRef; + } + + + /*************************************** mat-tree code start here ******************************/ + + /** Map from flat node to nested node. This helps us finding the nested node to be modified */ + flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>(); + /** Map from nested node to flattened node. This helps us to keep the same object for selection */ + nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>(); + /** A selected parent node to be inserted */ + selectedParent: TodoItemFlatNode | null = null; + /** The new item's name */ + newItemName = ''; + treeControl: FlatTreeControl<TodoItemFlatNode>; + treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>; + dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>; + /** The selection for checklist */ + checklistSelection = new SelectionModel<TodoItemFlatNode>(true /* multiple */); + getLevel = (node: TodoItemFlatNode) => node.level; + isExpandable = (node: TodoItemFlatNode) => node.expandable; + getChildren = (node: TodoItemNode): TodoItemNode[] => node.children; + hasChild = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.expandable; + hasNoContent = (_: number, _nodeData: TodoItemFlatNode) => _nodeData.item === ''; + + /** + * Transformer to convert nested node to flat node. Record the nodes in maps for later use. + */ + transformer = (node: TodoItemNode, level: number) => { + const existingNode = this.nestedNodeMap.get(node); + const flatNode = existingNode && existingNode.item === node.item + ? existingNode + : new TodoItemFlatNode(); + flatNode.item = node.item; + flatNode.level = level; + flatNode.expandable = !!node.children; + flatNode.roleIds = node.roleIds; + this.flatNodeMap.set(flatNode, node); + this.nestedNodeMap.set(node, flatNode); + return flatNode; + } + + /** Whether all the descendants of the node are selected. */ + descendantsAllSelected(node: TodoItemFlatNode): boolean { + const descendants = this.treeControl.getDescendants(node); + const descAllSelected = descendants.every(child => + this.checklistSelection.isSelected(child) + ); + return descAllSelected; + } + + /** Whether part of the descendants are selected */ + descendantsPartiallySelected(node: TodoItemFlatNode): boolean { + const descendants = this.treeControl.getDescendants(node); + const result = descendants.some(child => this.checklistSelection.isSelected(child)); + return result && !this.descendantsAllSelected(node); + } + + /** Toggle the to-do item selection. Select/deselect all the descendants node */ + todoItemSelectionToggle(node: TodoItemFlatNode): void { + this.checklistSelection.toggle(node); + const descendants = this.treeControl.getDescendants(node); + this.checklistSelection.isSelected(node) + ? this.checklistSelection.select(...descendants) + : this.checklistSelection.deselect(...descendants); + + // Force update for the parent + descendants.every(child => + this.checklistSelection.isSelected(child) + ); + this.checkAllParentsSelection(node); + } + + /** Toggle a leaf to-do item selection. Check all the parents to see if they changed */ + todoLeafItemSelectionToggle(node: TodoItemFlatNode): void { + this.checklistSelection.toggle(node); + this.checkAllParentsSelection(node); + } + + /* Checks all the parents when a leaf node is selected/unselected */ + checkAllParentsSelection(node: TodoItemFlatNode): void { + let parent: TodoItemFlatNode | null = this.getParentNode(node); + while (parent) { + this.checkRootNodeSelection(parent); + parent = this.getParentNode(parent); + } + } + + /** Check root node checked state and change it accordingly */ + checkRootNodeSelection(node: TodoItemFlatNode): void { + const nodeSelected = this.checklistSelection.isSelected(node); + const descendants = this.treeControl.getDescendants(node); + const descAllSelected = descendants.every(child => + this.checklistSelection.isSelected(child) + ); + if (nodeSelected && !descAllSelected) { + this.checklistSelection.deselect(node); + } else if (!nodeSelected && descAllSelected) { + this.checklistSelection.select(node); + } + } + + /* Get the parent node of a node */ + getParentNode(node: TodoItemFlatNode): TodoItemFlatNode | null { + const currentLevel = this.getLevel(node); + + if (currentLevel < 1) { + return null; + } + + const startIndex = this.treeControl.dataNodes.indexOf(node) - 1; + + for (let i = startIndex; i >= 0; i--) { + const currentNode = this.treeControl.dataNodes[i]; + + if (this.getLevel(currentNode) < currentLevel) { + return currentNode; + } + } + return null; + } + + + /** + * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object. + * The return value is the list of `TodoItemNode`. + */ + buildFileTree(obj: {[key: string]: any}, level: number): TodoItemNode[] { + return Object.keys(obj).reduce<TodoItemNode[]>((accumulator, key) => { + const value = obj[key]; + const node = new TodoItemNode(); + node.item = key; + + if(key.indexOf("~") !== -1){ + let nodeDetails = key.split("~"); + node.item = nodeDetails[0]; + if(nodeDetails[1]){ + node.roleIds = nodeDetails[1].split(","); + }else{ + node.roleIds = []; + } + } + + if (value != null) { + if (typeof value === 'object') { + node.children = this.buildFileTree(value, level + 1); + } else { + node.item = value; + if(value.indexOf("~") !== -1){ + let nodeDetails = value.split("~"); + node.item = nodeDetails[0]; + if(nodeDetails[1]){ + node.roleIds = nodeDetails[1].split(","); + }else{ + node.roleIds = []; + } + } + } + } + + return accumulator.concat(node); + }, []); + } + /*************************************** mat-tree code end here ******************************/ +} + +/** + * Node for to-do item + */ +export class TodoItemNode { + children: TodoItemNode[]; + item: string; + roleIds: string[]; +} + +/** Flat to-do item node with expandable and level information */ +export class TodoItemFlatNode { + item: string; + level: number; + expandable: boolean; + roleIds: string[]; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.html b/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.html new file mode 100644 index 00000000..b9e63d4a --- /dev/null +++ b/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.html @@ -0,0 +1,137 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <!-- Heading --> + <div class="onap-main-view-title"> + <h1 class="heading-page">User Notifications</h1> + </div> + + + <!--Filter Search Box --> + <mat-form-field> + <input matInput type="text" (keyup)="applyFilter($event.target.value)" placeholder="Search in entire table"> + </mat-form-field> + + <!--Add Notifications--> + <button type="button" class="btn btn-primary" (click)="openAddNewNotificationModal('')"> + <i class="icon ion-md-person-add"></i> Add Notification + </button> + + <!-- Recent Notification History table --> + <table mat-table [dataSource]="notificationsDataSource" matSort> + <!-- Message Source Column --> + <ng-container matColumnDef="messageSource"> + <th id="col1" mat-header-cell *matHeaderCellDef> Message Source </th> + <td (click)="openAddNewNotificationModal(element)" + id="rowheader_t1_{{i}}-messageSource" mat-cell *matCellDef="let element; let i = index;"> {{element.msgSource}} + </td> + </ng-container> + + <!-- Message Column --> + <ng-container matColumnDef="message"> + <th id="col2" mat-header-cell *matHeaderCellDef> Message </th> + <td (click)="openAddNewNotificationModal(element)" + id="rowheader_t1_{{i}}-message" mat-cell *matCellDef="let element; let i=index;"> {{element.msgDescription}} + </td> + </ng-container> + + <!-- Start Date (Local Time) Column --> + <ng-container matColumnDef="startDateLocalTime"> + <th id="col3" mat-header-cell *matHeaderCellDef> Start Date (Local Time) </th> + <td (click)="openAddNewNotificationModal(element)" + id="rowheader_t1_{{i}}-startDateLocalTime" mat-cell *matCellDef="let element; let i=index;"> {{element.startTime | date:'dd/MM/yyyy'}} + </td> + </ng-container> + + <!-- End Date (Local Time) Column --> + <ng-container matColumnDef="endDateLocalTime"> + <th id="col4" mat-header-cell *matHeaderCellDef> End Date (Local Time) </th> + <td (click)="openAddNewNotificationModal(element)" + id="rowheader_t1_{{i}}-endDateLocalTime" mat-cell *matCellDef="let element; let i=index;">{{element.endTime | date:'dd/MM/yyyy'}} + </td> + </ng-container> + + <!-- Priority Column --> + <ng-container matColumnDef="priority"> + <th id="col4" mat-header-cell *matHeaderCellDef> Priority </th> + <td (click)="openAddNewNotificationModal(element)" + id="rowheader_t1_{{i}}-priority" mat-cell *matCellDef="let element; let i=index;"> {{element.priority}} + </td> + </ng-container> + + <!-- Created By Column --> + <ng-container matColumnDef="createdBy"> + <th id="col4" mat-header-cell *matHeaderCellDef> Created By </th> + <td (click)="openAddNewNotificationModal(element)" + id="rowheader_t1_{{i}}-createdBy" mat-cell *matCellDef="let element; let i=index;"> {{element.loginId}} + </td> + </ng-container> + + <!-- Created Time Column --> + <ng-container matColumnDef="createdTime"> + <th id="col4" mat-header-cell *matHeaderCellDef> Created Time </th> + <td (click)="openAddNewNotificationModal(element)" + id="rowheader_t1_{{i}}-createdTime" mat-cell *matCellDef="let element; let i=index;"> {{element.createdDate}} + </td> + </ng-container> + + <!-- All Users (Roles)? Column --> + <ng-container matColumnDef="allUsersRoles"> + <th id="col4" mat-header-cell *matHeaderCellDef> All Users (Roles)? </th> + <td (click)="openAddNewNotificationModal(element)" + id="rowheader_t1_{{i}}-allUsersRoles" mat-cell *matCellDef="let element; let i=index;"> {{element.isForAllRoles}} + </td> + </ng-container> + + <!-- view/Delete Column --> + <ng-container matColumnDef="viewOrDelete"> + <th id="col4" mat-header-cell *matHeaderCellDef> Delete </th> + <td id="rowheader_t1_{{i}}" mat-cell *matCellDef="let element; let i=index;"> + <span class="icon-trash" id="{{i}}-button-portal-admin-remove" (click)="removeUserNotification(element)"> + <i class="icon ion-md-trash"></i> + </span> + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> + </table> + <mat-paginator [pageSizeOptions]="[10, 20]" showFirstLastButtons></mat-paginator> + + </div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.scss b/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.scss new file mode 100644 index 00000000..7a773398 --- /dev/null +++ b/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.scss @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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============================================ + * + * + */
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.spec.ts b/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.spec.ts new file mode 100644 index 00000000..82b4e129 --- /dev/null +++ b/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.spec.ts @@ -0,0 +1,63 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { UserNotificationAdminComponent } from './user-notification-admin.component'; + +describe('UserNotificationAdminComponent', () => { + let component: UserNotificationAdminComponent; + let fixture: ComponentFixture<UserNotificationAdminComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UserNotificationAdminComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserNotificationAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.ts b/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.ts new file mode 100644 index 00000000..bf4a39f7 --- /dev/null +++ b/portal-FE-common/src/app/pages/user-notification-admin/user-notification-admin.component.ts @@ -0,0 +1,149 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { MatTableDataSource } from '@angular/material'; +import { MatSort, MatPaginator } from '@angular/material'; +import { NotificationService } from '../../shared/services/index'; +import { NewNotificationModalComponent } from './new-notification-modal/new-notification-modal.component'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; + +@Component({ + selector: 'app-user-notification-admin', + templateUrl: './user-notification-admin.component.html', + styleUrls: ['./user-notification-admin.component.scss'] +}) +export class UserNotificationAdminComponent implements OnInit { + + isEditMode: any; + result: any; + tableAdminNotifItems: any = []; + displayedColumns: string[] = ['messageSource', 'message', 'startDateLocalTime','endDateLocalTime', 'priority', 'createdBy', 'createdTime', 'allUsersRoles', 'viewOrDelete']; + notificationsDataSource = new MatTableDataSource(this.tableAdminNotifItems); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + constructor(public notificationService: NotificationService, public ngbModal: NgbModal) { } + + ngOnInit() { + this.getAdminNotifications(); + } + + getAdminNotifications(){ + this.notificationService.getAdminNotification() + .subscribe(_data => { + this.result = _data; + if (this.result == null || this.result == 'undefined') { + console.log('NotificationService::getAdminNotifications Failed:::: Result or result.data is null'); + }else { + this.tableAdminNotifItems = this.result; + this.populateTableData(this.tableAdminNotifItems); + } + }, error =>{ + console.log(error); + this.openConfirmationModal('Error', error); + return; + }); + } + + removeUserNotification(selectedAdminNotification: any){ + let confirmationMsg = 'You are about to delete this Notification : ' + selectedAdminNotification.msgHeader+ '. Click OK to continue.'; + this.openInformationModal("Confirmation",confirmationMsg).result.then((result) => { + if (result === 'Ok') { + selectedAdminNotification.activeYn = 'N'; + this.notificationService.updateAdminNotification(selectedAdminNotification) + .subscribe(_data => { + this.result = _data; + this.tableAdminNotifItems = []; + this.getAdminNotifications(); + }, error =>{ + console.log(error); + this.openConfirmationModal('Error', error); + return; + }); + } + }, (resut) => { + this.openConfirmationModal('Error', resut); + return; + }) + } + + + openAddNewNotificationModal(rowData: any){ + const modalRef = this.ngbModal.open(NewNotificationModalComponent, { windowClass: 'add-notification-modal'}); + modalRef.componentInstance.title = 'Add a New Notification'; + if(rowData != 'undefined' && rowData){ + modalRef.componentInstance.selectedNotification = rowData; + this.isEditMode = true; + }else{ + modalRef.componentInstance.notification = {}; + this.isEditMode = false; + } + modalRef.componentInstance.passEntry.subscribe((receivedEntry: any) => { + if(receivedEntry){ + this.tableAdminNotifItems = []; + this.getAdminNotifications(); + } + }); + } + + populateTableData(notificationHistory: Array<Object>){ + this.notificationsDataSource = new MatTableDataSource(notificationHistory); + this.notificationsDataSource.sort = this.sort; + this.notificationsDataSource.paginator = this.paginator; + } + + applyFilter(filterValue: string) { + this.notificationsDataSource.filter = filterValue.trim().toLowerCase(); + } + + openConfirmationModal(_title: string, _message: string) { + const modalInfoRef = this.ngbModal.open(ConfirmationModalComponent); + modalInfoRef.componentInstance.title = _title; + modalInfoRef.componentInstance.message = _message; + } + + openInformationModal(_title: string, _message: string){ + const modalInfoRef = this.ngbModal.open(InformationModalComponent); + modalInfoRef.componentInstance.title = _title; + modalInfoRef.componentInstance.message = _message; + return modalInfoRef; + } +} diff --git a/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.html b/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.html new file mode 100644 index 00000000..e988c317 --- /dev/null +++ b/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.html @@ -0,0 +1,130 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <div class="modal-header"> + <h4 class="modal-title">{{title}}</h4> + <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross')"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <div *ngIf="dialogState===1"> + <mat-form-field> + <mat-label> Select Application </mat-label> + <mat-select [disabled]='adminsAppsData.length === 0'> + <mat-option [value]="select-application" (click)="changeSelectApp('select-application')">Select Application + </mat-option> + <mat-option *ngFor="let app of adminsAppsData" (click)="changeSelectApp(app)" [value]="app.value"> + {{app.title}}</mat-option> + </mat-select> + </mat-form-field> + <span class="onap-spinner" *ngIf="adminsAppsData.length === 0"></span> + </div> + <div *ngIf="dialogState===2"> + <div class="upload-instructions">Select Upload File:</div> + <!-- input type=file is difficult to style. + Instead use a label styled as a button. --> + <label class="file-label"> + <input type="file" (change)="onFileSelect($event.target)" accept="text/plain,.csv" /> + </label>{{selectedFile}} + <div class="upload-instructions">File must be .csv or .txt and have one entry per line with this format: + <pre>orgUserId, role name</pre> + </div> + </div> + <div class="bulk-upload" *ngIf="dialogState===3"> + <!-- progress indicator --> + <div class="upload-instructions" [hidden]="!isProcessing"> + {{progressMsg}} + <br> + <br> + <span class="onap-spinner"></span> + </div> + + <!-- progress indicator --> + <div class="upload-instructions" [hidden]="!isProcessedRecords"> + {{conformMsg}} + </div> + <div [hidden]="isProcessing || isProcessedRecords"> + <div class="upload-instructions"> + Click OK to upload the valid requests. + Invalid requests will be ignored.</div> + + <table mat-table [dataSource]="uploadFileDataSource"> + <!-- Search Result Column--> + <ng-container matColumnDef="line"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Line + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;">{{element.line}} + </td> + </ng-container> + <ng-container matColumnDef="orgUserId"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> OrgUserID + <td id="rowheader_t1_{{i}}-roles" mat-cell *matCellDef="let element; let i=index;"> + {{element.orgUserId}} + </td> + </ng-container> + <ng-container matColumnDef="appRole"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> App Role + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.role}} + </td> + </ng-container> + <ng-container matColumnDef="status"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Status + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.status}} + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> + <tr mat-row id="table-row-{{i}}" *matRowDef="let row; columns: displayedColumns; let i = index;"></tr> + </table> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" *ngIf="dialogState === 2" (click)="navigateBack()">Back</button> + + <button type="submit" id="dialog1Button" class="btn btn-primary" [disabled]="selectApp" *ngIf="dialogState === 1" + (click)="uploadFileDialog()">Next</button> + <button type="button" class="btn btn-primary" *ngIf="dialogState !== 3" + (click)="activeModal.close('Close')">Close</button> + <button type="submit" class="btn btn-primary" *ngIf="dialogState === 3" (click)="updateDB()">Ok</button> + <button type="button" class="btn btn-primary" *ngIf="dialogState === 3" (click)="navigateDialog2()">Cancel</button> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.scss b/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.scss new file mode 100644 index 00000000..3c8cd756 --- /dev/null +++ b/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.scss @@ -0,0 +1,45 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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-column-orgUserId { + padding: 10px; +} + +.container.bulk-upload { + overflow-y: auto; + height: 250px; +} diff --git a/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.spec.ts b/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.spec.ts new file mode 100644 index 00000000..05b04a96 --- /dev/null +++ b/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.spec.ts @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { BulkUserComponent } from './bulk-user.component'; + +describe('BulkUserComponent', () => { + let component: BulkUserComponent; + let fixture: ComponentFixture<BulkUserComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ BulkUserComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BulkUserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.ts b/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.ts new file mode 100644 index 00000000..70072a68 --- /dev/null +++ b/portal-FE-common/src/app/pages/users/bulk-user/bulk-user.component.ts @@ -0,0 +1,497 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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, Input, Output, EventEmitter } from '@angular/core'; +import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { UsersService, ApplicationsService, FunctionalMenuService } from 'src/app/shared/services'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { MatTableDataSource } from '@angular/material'; + +@Component({ + selector: 'app-bulk-user', + templateUrl: './bulk-user.component.html', + styleUrls: ['./bulk-user.component.scss'] +}) +export class BulkUserComponent implements OnInit { + + @Input() title: string; + @Input() adminsAppsData: any; + @Output() passBackBulkUserPopup: EventEmitter<any> = new EventEmitter(); + adminApps: any; + // Roles fetched from app service + appRolesResult: any; + // Users fetched from user service + userCheckResult: any; + // Requests for user-role assignment built by validator + appUserRolesRequest: any; + fileSelected: boolean; + isProcessing: boolean; + isProcessedRecords: boolean; + dialogState: number; + selectedFile: any; + fileModel: any; + selectApp: boolean; + fileToRead: any; + selectedAppValue: any; + progressMsg: string; + conformMsg: string; + uploadFile: any; + uploadCheck: boolean; + displayedColumns: string[] = ['line', 'orgUserId', 'appRole', 'status']; + uploadFileDataSource = new MatTableDataSource(this.uploadFile); + constructor(public ngbModal: NgbModal, public activeModal: NgbActiveModal, private applicationsService: ApplicationsService, private usersService: UsersService, private functionalMenuService: FunctionalMenuService) { } + + ngOnInit() { + this.selectApp = true; + this.fileSelected = false; + this.uploadCheck = false; + // Flag that indicates background work is proceeding + this.isProcessing = true; + this.isProcessedRecords = false; + this.dialogState = 1; + } + + changeSelectApp(val: any) { + if (val === 'select-application') + this.selectApp = true; + else + this.selectApp = false; + this.selectedAppValue = val; + } + + // Answers a function that compares properties with the specified name. + getSortOrder = (prop, foldCase) => { + return function (a, b) { + let aProp = foldCase ? a[prop].toLowerCase() : a[prop]; + let bProp = foldCase ? b[prop].toLowerCase() : b[prop]; + if (aProp > bProp) + return 1; + else if (aProp < bProp) + return -1; + else + return 0; + } + } + + onFileLoad(fileLoadedEvent) { + const textFromFileLoaded = fileLoadedEvent.target.result; + let lines = textFromFileLoaded.split('\n'); + // this.uploadFile = lines; + let result = []; + var len, i, line, o; + + // Need 1-based index below + for (len = lines.length, i = 1; i <= len; ++i) { + // Use 0-based index for array + line = lines[i - 1].trim(); + if (line.length == 0) { + result.push({ + line: i, + orgUserId: '', + role: '', + status: 'Blank line' + }); + continue; + } + o = line.split(','); + if (o.length !== 2) { + // other lengths not valid for upload + result.push({ + line: i, + orgUserId: line, + role: '', + status: 'Failed to find 2 comma-separated values' + }); + } + else { + let entry = { + line: i, + orgUserId: o[0], + role: o[1] + // leave status undefined, this could be valid. + }; + if (o[0].toLowerCase() === 'orgUserId') { + // not valid for upload, so set status + entry['status'] = 'Header'; + } + else if (o[0].trim() == '' || o[1].trim() == '') { + // defend against line with only a single comma etc. + entry['status'] = 'Failed to find 2 non-empty values'; + } + result.push(entry); + } // len 2 + } // for + return result; + } + + onFileSelect(input: HTMLInputElement) { + var validExts = new Array(".csv", ".txt"); + var fileExt = input.value; + fileExt = fileExt.substring(fileExt.lastIndexOf('.')); + if (validExts.indexOf(fileExt) < 0) { + const modalFileErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalFileErrorRef.componentInstance.title = 'Confirmation'; + modalFileErrorRef.componentInstance.message = 'Invalid file selected, valid files are of ' + + validExts.toString() + ' types.' + this.uploadCheck = false; + return false; + } + else { + const files = input.files; + this.isProcessing = true; + this.conformMsg = ''; + this.isProcessedRecords = true; + this.progressMsg = 'Reading upload file..'; + if (files && files.length) { + this.uploadCheck = true; + const fileToRead = files[0]; + const fileReader = new FileReader(); + fileReader.readAsText(fileToRead, "UTF-8"); + fileReader.onloadend = (e) => { + this.uploadFile = this.onFileLoad(e); + this.uploadFile.sort(this.getSortOrder('orgUserId', true)); + let appId = this.selectedAppValue.id; + this.progressMsg = 'Fetching application roles..'; + this.functionalMenuService.getManagedRolesMenu(appId).toPromise().then((rolesObj) => { + this.appRolesResult = rolesObj; + this.progressMsg = 'Validating application roles..'; + this.verifyAppRoles(this.appRolesResult); + this.progressMsg = 'Validating Org Users..'; + let userPromises = this.buildUserChecks(); + Promise.all(userPromises).then(userPromise => { + this.evalUserCheckResults(); + let appPromises = this.buildAppRoleChecks(); + this.progressMsg = 'Querying application for user roles..'; + Promise.all(appPromises).then(() => { + this.evalAppRoleCheckResults(); + // Re sort by line for the confirmation dialog + this.uploadFile.sort(this.getSortOrder('line', false)); + // We're done, confirm box may show the table + this.progressMsg = 'Done.'; + this.isProcessing = false; + this.isProcessedRecords = false; + }, + function (error) { + this.isProcessing = false; + this.isProcessedRecords = false; + } + ); // then of app promises + }, + function (_error) { + this.isProcessing = false; + this.isProcessedRecords = false; + } + ); // then of user promises + }, + function (error) { + this.isProcessing = false; + this.isProcessedRecords = false; + } + ); + this.uploadFileDataSource = new MatTableDataSource(this.uploadFile); + this.dialogState = 3; + }; + } + } + } + + /** + * Evaluates the result set returned by the app role service. + * Sets an uploadFile array element status if a role is not defined. + * Reads and writes scope variable uploadFile. + * Reads closure variable appRolesResult. + */ + verifyAppRoles(appRolesResult: any) { + // check roles in upload file against defined app roles + this.uploadFile.forEach(function (uploadRow) { + // skip rows that already have a defined status: headers etc. + if (uploadRow.status) { + return; + } + uploadRow.role = uploadRow.role.trim(); + var foundRole = false; + for (var i = 0; i < appRolesResult.length; i++) { + if (uploadRow.role.toUpperCase() === appRolesResult[i].rolename.trim().toUpperCase()) { + foundRole = true; + break; + } + }; + if (!foundRole) { + uploadRow.status = 'Invalid role'; + }; + }); // foreach + }; // verifyRoles + + /** + * Builds and returns an array of promises to invoke the + * searchUsers service for each unique Org User UID in the input. + * Reads and writes scope variable uploadFile, which must be sorted by Org User UID. + * The promise function writes to closure variable userCheckResult + */ + buildUserChecks() { + // if (debug) + // $log.debug('BulkUserModalCtrl::buildUserChecks: uploadFile length is ' + $scope.uploadFile.length); + this.userCheckResult = []; + let promises = []; + let prevRow = null; + this.uploadFile.forEach((uploadRow) => { + if (uploadRow.status) { + // if (debug) + // $log.debug('BulkUserModalCtrl::buildUserChecks: skip row ' + uploadRow.line); + return; + }; + // detect repeated UIDs + if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) { + // if (debug) + // $log.debug('BulkUserModalCtrl::buildUserChecks: create request for orgUserId ' + uploadRow.orgUserId); + let userPromise = this.usersService.searchUsers(uploadRow.orgUserId).toPromise().then((usersList) => { + if (typeof usersList[0] !== "undefined") { + this.userCheckResult.push({ + orgUserId: usersList[0].orgUserId, + firstName: usersList[0].firstName, + lastName: usersList[0].lastName, + jobTitle: usersList[0].jobTitle + }); + } + else { + // User not found. + // if (debug) + // $log.debug('BulkUserModalCtrl::buildUserChecks: searchUsers returned null'); + } + }, function (error) { + // $log.error('BulkUserModalCtrl::buildUserChecks: searchUsers failed ' + JSON.stringify(error)); + }); + promises.push(userPromise); + } + else { + // if (debug) + // $log.debug('BulkUserModalCtrl::buildUserChecks: skip repeated orgUserId ' + uploadRow.orgUserId); + } + prevRow = uploadRow; + }); // foreach + return promises; + }; // buildUserChecks + + /** + * Evaluates the result set returned by the user service to set + * the uploadFile array element status if the user was not found. + * Reads and writes scope variable uploadFile. + * Reads closure variable userCheckResult. + */ + evalUserCheckResults = () => { + // if (debug) + // $log.debug('BulkUserModalCtrl::evalUserCheckResult: uploadFile length is ' + $scope.uploadFile.length); + this.uploadFile.forEach((uploadRow) => { + if (uploadRow.status) { + // if (debug) + // $log.debug('BulkUserModalCtrl::evalUserCheckResults: skip row ' + uploadRow.line); + return; + }; + let foundorgUserId = false; + this.userCheckResult.forEach(function (userItem) { + if (uploadRow.orgUserId.toLowerCase() === userItem.orgUserId.toLowerCase()) { + // if (debug) + // $log.debug('BulkUserModalCtrl::evalUserCheckResults: found orgUserId ' + uploadRow.orgUserId); + foundorgUserId = true; + }; + }); + if (!foundorgUserId) { + // if (debug) + // $log.debug('BulkUserModalCtrl::evalUserCheckResults: NO match on orgUserId ' + uploadRow.orgUserId); + uploadRow.status = 'Invalid orgUserId'; + } + }); // foreach + }; // evalUserCheckResults + + /** + * Builds and returns an array of promises to invoke the getUserAppRoles + * service for each unique Org User in the input file. + * Each promise creates an update to be sent to the remote application + * with all role names. + * Reads scope variable uploadFile, which must be sorted by Org User. + * The promise function writes to closure variable appUserRolesRequest + */ + buildAppRoleChecks() { + this.appUserRolesRequest = []; + let appId = this.selectedAppValue.id; + let promises = []; + let prevRow = null; + this.uploadFile.forEach((uploadRow) => { + if (uploadRow.status) { + return; + } + // Because the input is sorted, generate only one request for each Org User + if (prevRow == null || prevRow.orgUserId.toLowerCase() !== uploadRow.orgUserId.toLowerCase()) { + let appPromise = this.usersService.getUserAppRoles(appId, uploadRow.orgUserId, true, false).toPromise().then((userAppRolesResult) => { + // Reply for unknown user has all defined roles with isApplied=false on each. + if (typeof userAppRolesResult[0] !== "undefined") { + this.appUserRolesRequest.push({ + orgUserId: uploadRow.orgUserId, + userAppRoles: userAppRolesResult + }); + } else { + // $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles returned ' + JSON.stringify(userAppRolesResult)); + }; + }, function (error) { + // $log.error('BulkUserModalCtrl::buildAppRoleChecks: getUserAppRoles failed ', error); + }); + promises.push(appPromise); + } else { + // if (debug) + // $log.debug('BulkUserModalCtrl::buildAppRoleChecks: duplicate orgUserId, skip: '+ uploadRow.orgUserId); + } + prevRow = uploadRow; + }); // foreach + return promises; + }; // buildAppRoleChecks + + /** + * Evaluates the result set returned by the app service and adjusts + * the list of updates to be sent to the remote application by setting + * isApplied=true for each role name found in the upload file. + * Reads and writes scope variable uploadFile. + * Reads closure variable appUserRolesRequest. + */ + evalAppRoleCheckResults() { + this.uploadFile.forEach((uploadRow) => { + if (uploadRow.status) { + return; + } + // Search for the match in the app-user-roles array + this.appUserRolesRequest.forEach((appUserRoleObj) => { + if (uploadRow.orgUserId.toLowerCase() === appUserRoleObj.orgUserId.toLowerCase()) { + let roles = appUserRoleObj.userAppRoles; + roles.forEach(function (appRoleItem) { + //if (debug) + // $log.debug('BulkUserModalCtrl::evalAppRoleCheckResults: checking uploadRow.role=' + // + uploadRow.role + ', appRoleItem.roleName= ' + appRoleItem.roleName); + if (uploadRow.role === appRoleItem.roleName) { + if (appRoleItem.isApplied) { + uploadRow.status = 'Role exists'; + } + else { + // After much back-and-forth I decided a clear indicator + // is better than blank in the table status column. + uploadRow.status = 'OK'; + appRoleItem.isApplied = true; + } + // This count is not especially interesting. + // numberUserRolesSucceeded++; + } + }); // for each role + } + }); // for each result + }); // for each row + }; // evalAppRoleCheckResults + + // Sets the variable that hides/reveals the user controls + uploadFileDialog() { + this.fileSelected = false; + this.selectedFile = null; + this.fileModel = null; + this.dialogState = 2; + } + + // Navigate between dialog screens using number: 1,2,3 + navigateBack() { + this.selectApp = true; + this.dialogState = 1; + this.fileSelected = false; + }; + + // Navigate between dialog screens using number: 1,2,3 + navigateDialog2() { + this.dialogState = 2; + }; + + /** + * Sends requests to Portal requesting user role assignment. + * That endpoint handles creation of the user at the remote app if necessary. + * Reads closure variable appUserRolesRequest. + * Invoked by the Next button on the confirmation dialog. + */ + updateDB() { + this.isProcessing = true; + this.conformMsg = ''; + this.isProcessedRecords = true; + this.progressMsg = 'Sending requests to application..'; + // if (debug) + // $log.debug('BulkUserModalCtrl::updateDB: request length is ' + appUserRolesRequest.length); + var numberUsersSucceeded = 0; + let promises = []; + this.appUserRolesRequest.forEach(appUserRoleObj => { + // if (debug) + // $log.debug('BulkUserModalCtrl::updateDB: appUserRoleObj is ' + JSON.stringify(appUserRoleObj)); + let updateRequest = { + orgUserId: appUserRoleObj.orgUserId, + appId: this.selectedAppValue.id, + appRoles: appUserRoleObj.userAppRoles + }; + // if (debug) + // $log.debug('BulkUserModalCtrl::updateDB: updateRequest is ' + JSON.stringify(updateRequest)); + let updatePromise = this.usersService.updateUserAppRoles(updateRequest).toPromise().then(res => { + // if (debug) + // $log.debug('BulkUserModalCtrl::updateDB: updated successfully: ' + JSON.stringify(res)); + numberUsersSucceeded++; + }).catch(err => { + // What to do if one of many fails?? + // $log.error('BulkUserModalCtrl::updateDB failed: ', err); + const modelErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modelErrorRef.componentInstance.title = 'Error'; + modelErrorRef.componentInstance.message = 'Failed to update the user application roles. ' + + 'Error: ' + err.status; + }).finally(() => { + // $log.debug('BulkUserModalCtrl::updateDB: finally()'); + }); + promises.push(updatePromise); + }); // for each + + // Run all the promises + Promise.all(promises).then(() => { + + this.conformMsg = 'Processed ' + numberUsersSucceeded + ' users.'; + const modelRef = this.ngbModal.open(ConfirmationModalComponent); + modelRef.componentInstance.title = 'Confirmation'; + modelRef.componentInstance.message = this.conformMsg + this.isProcessing = false; + this.isProcessedRecords = true; + this.uploadFile = []; + this.dialogState = 2; + }); + }; // updateDb + +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/users/users.component.html b/portal-FE-common/src/app/pages/users/users.component.html new file mode 100644 index 00000000..8f01deab --- /dev/null +++ b/portal-FE-common/src/app/pages/users/users.component.html @@ -0,0 +1,121 @@ +<!-- + ============LICENSE_START========================================== + ONAP Portal + =================================================================== + Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + =================================================================== + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "License"); + you may not use this software 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. + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + you may not use this documentation except in compliance with the License. + You may obtain a copy of the License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + 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="container"> + <div class="onap-main-view-title"> + <h1 class="heading-page">Users</h1> + </div> + <span *ngIf="showSpinner" class="onap-spinner"></span> + <mat-form-field> + <mat-label> Select Application </mat-label> + <mat-select [disabled]='adminApps.length === 0'> + <mat-option [value]="select-application" (click)="applyDropdownFilter('select-application')">Select Application + </mat-option> + <mat-option *ngFor="let app of adminApps" [value]="app.value" (click)="applyDropdownFilter(app)"> + {{app.title}}</mat-option> + </mat-select> + </mat-form-field> + + <mat-form-field> + <input matInput type="text" (keyup)="applyFilter($event.target.value)" placeholder="Search in entire table"> + </mat-form-field> + <button type="button" class="btn btn-primary" (click)="openBulkUserUploadModal()"><i + class="icon ion-md-cloud-upload"></i> + Bulk Upload</button> + <button type="button" class="btn btn-primary" (click)="openAddNewUserModal()"><i class="icon ion-md-person-add"></i> + Add </button> + <div class="error-text" id="div-select-app" [hidden]="!noAppSelected || adminApps.length === 0"> + <p class="error-help">Use the 'Select application' dropdown to see users.</p> + </div> + <div class="error-text" id="div-error-no-users" [hidden]="!noUsersInApp"> + <p> </p> + <p class="error-help"> + No users found. Select "Add User" to add a User to the application. + </p> + </div> + <div class="error-text" id="div-error-app-down" [hidden]="!appsIsDown"> + <p> </p> + <p class="error-help"> + Failed to communicate with the application. + Please try again later or contact a system administrator. + </p> + </div> + <div class="error-text" id="div-error-403" [hidden]="!adminAppsIsNull"> + <h1>Attention:</h1> + <p> </p> + <p class="error-help">It appears that you have not been added as an admin yet to an application.</p> + <p> </p> + <p class="error-help">Click on the Admins link to the left and check and see if you are listed as an admin for an + application. + If not, you can add yourself to the appropriate application.</p> + </div> + <table mat-table [dataSource]="adminsDataSource" matSort> + <!-- First Name Column --> + <ng-container matColumnDef="firstName"> + <th id="col1" mat-header-cell *matHeaderCellDef mat-sort-header> First Name </th> + <td id="rowheader_t1_{{i}}-firstName" mat-cell *matCellDef="let element; let i = index;"> {{element.firstName}} + </td> + </ng-container> + + <!-- Last Name Column --> + <ng-container matColumnDef="lastName"> + <th id="col2" mat-header-cell *matHeaderCellDef mat-sort-header> Last Name </th> + <td id="rowheader_t1_{{i}}-lastName" mat-cell *matCellDef="let element; let i=index;"> {{element.lastName}} + </td> + </ng-container> + + <!-- User ID Column --> + <ng-container matColumnDef="userId"> + <th id="col3" mat-header-cell *matHeaderCellDef mat-sort-header> User ID </th> + <td id="rowheader_t1_{{i}}-userId" mat-cell *matCellDef="let element; let i=index;"> {{element.orgUserId}} + </td> + </ng-container> + + <!-- Roles Column --> + <ng-container matColumnDef="roles"> + <th id="col4" mat-header-cell *matHeaderCellDef> Roles </th> + <td id="rowheader_t1_{{i}}-applications" mat-cell *matCellDef="let element; let i=index;"> + <div *ngFor="let element of element.roles; let i=index;"> {{element.name}} </div> + </td> + </ng-container> + + <tr [hidden]="accountUsers.length === 0" mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="openExistingUserModal(row)"></tr> + </table> + <mat-paginator [hidden]="accountUsers.length === 0" [pageSizeOptions]="[10, 20]" showFirstLastButtons></mat-paginator> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/users/users.component.scss b/portal-FE-common/src/app/pages/users/users.component.scss new file mode 100644 index 00000000..eebe72f4 --- /dev/null +++ b/portal-FE-common/src/app/pages/users/users.component.scss @@ -0,0 +1,66 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 "../pages.component"; + +.mat-row { + cursor: pointer; +} + +.onap-spinner{ + z-index: 99999; +} + +.error-text { + width: 1170px; + margin: auto; + padding: 10px; + left: 20px; + font-weight: bold; + font-size: 16px; + text-align: left; + color: red; + .error-help { + color: grey; //@portalDGray; + font-weight: normal; + } + + .error-help-bold { + color: grey; //@portalDGray; + font-weight: bold; + } +} diff --git a/portal-FE-common/src/app/pages/users/users.component.spec.ts b/portal-FE-common/src/app/pages/users/users.component.spec.ts new file mode 100644 index 00000000..60d024ba --- /dev/null +++ b/portal-FE-common/src/app/pages/users/users.component.spec.ts @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { UsersComponent } from './users.component'; + +describe('UsersComponent', () => { + let component: UsersComponent; + let fixture: ComponentFixture<UsersComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ UsersComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UsersComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/users/users.component.ts b/portal-FE-common/src/app/pages/users/users.component.ts new file mode 100644 index 00000000..23538b5f --- /dev/null +++ b/portal-FE-common/src/app/pages/users/users.component.ts @@ -0,0 +1,234 @@ +/*- + * ============LICENSE_START========================================== + * ONAP Portal + * =================================================================== + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * =================================================================== + * + * Unless otherwise specified, all software contained herein is licensed + * under the Apache License, Version 2.0 (the "License"); + * you may not use this software 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. + * + * Unless otherwise specified, all documentation contained herein is licensed + * under the Creative Commons License, Attribution 4.0 Intl. (the "License"); + * you may not use this documentation except in compliance with the License. + * You may obtain a copy of the License at + * + * https://creativecommons.org/licenses/by/4.0/ + * + * Unless required by applicable law or agreed to in writing, documentation + * 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 { MatTableDataSource, MatSort, MatPaginator } from '@angular/material'; +import { ApplicationsService, UsersService } from 'src/app/shared/services'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { UserAdminApps } from 'src/app/shared/model'; +import { HttpErrorResponse } from '@angular/common/http'; +import { NewUserModalComponent } from './new-user-modal/new-user-modal.component'; +import { BulkUserComponent } from './bulk-user/bulk-user.component'; + +@Component({ + selector: 'app-users', + templateUrl: './users.component.html', + styleUrls: ['./users.component.scss'] +}) +export class UsersComponent implements OnInit { + multiAppAdmin: boolean; + adminApps: any; + selectApp = 'select-application'; + selectedApp: any; + appsIsDown: boolean; + noUsersInApp: boolean; + searchString: string; + isAppSelectDisabled: boolean; + accountUsers: any; + noAppSelected: boolean; + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + displayedColumns: string[] = ['firstName', 'lastName', 'userId', 'roles']; + adminsDataSource = new MatTableDataSource(this.accountUsers); + adminsData: []; + showSpinner: boolean; + adminAppsIsNull: any; + + constructor(private applicationsService: ApplicationsService, public ngbModal: NgbModal, + private usersService: UsersService) { } + + ngOnInit() { + this.adminApps = []; + this.accountUsers = []; + this.getAdminApps(); + } + + openAddNewUserModal() { + const modalRef = this.ngbModal.open(NewUserModalComponent); + modalRef.componentInstance.title = 'New User'; + modalRef.componentInstance.dialogState = 1; + modalRef.componentInstance.disableBack = false; + modalRef.componentInstance.passBackNewUserPopup.subscribe((_result: any) => { + this.showSpinner = true; + this.updateUsersList(); + }, (_reason: any) => { + return; + }); + } + + openExistingUserModal(userData: any) { + const modalRef = this.ngbModal.open(NewUserModalComponent); + modalRef.componentInstance.userTitle = `${userData.firstName}, ${userData.lastName} ` + '(' + `${userData.orgUserId}` + ')'; + modalRef.componentInstance.dialogState = 2; + modalRef.componentInstance.userModalData = userData; + modalRef.componentInstance.disableBack = true; + modalRef.componentInstance.passBackNewUserPopup.subscribe((_result: any) => { + this.showSpinner = true; + this.updateUsersList(); + }, (_reason: any) => { + return; + }); + } + + openBulkUserUploadModal() { + const modalRef = this.ngbModal.open(BulkUserComponent); + modalRef.componentInstance.title = 'Bulk User Upload'; + modalRef.componentInstance.adminsAppsData = this.adminApps; + modalRef.componentInstance.passBackBulkUserPopup.subscribe((_result: any) => { + this.showSpinner = true; + this.updateUsersList(); + }, (_reason: any) => { + return; + }); + } + + applyDropdownFilter(_appValue: any) { + if (_appValue !== 'select-application') { + this.selectedApp = _appValue; + this.selectApp = this.selectedApp.value; + this.updateUsersList(); + } else { + this.showSpinner = false; + this.noAppSelected = true; + this.accountUsers = []; + this.adminsDataSource = new MatTableDataSource(this.accountUsers); + } + } + + applyFilter(filterValue: string) { + this.adminsDataSource.filter = filterValue.trim().toLowerCase(); + } + + getAdminApps() { + this.showSpinner = true; + this.applicationsService.getAdminApps().subscribe((apps: Array<UserAdminApps>) => { + this.showSpinner = false; + if (!apps) { + return null; + } + + if (apps.length >= 2) { + this.multiAppAdmin = true; + } else { + this.adminApps = []; + } + + let sortedApps = apps.sort(this.getSortOrder("name")); + let realAppIndex = 1; + for (let i = 1; i <= sortedApps.length; i++) { + this.adminApps.push({ + index: realAppIndex, + id: sortedApps[i - 1].id, + value: sortedApps[i - 1].name, + title: sortedApps[i - 1].name + }); + realAppIndex = realAppIndex + 1; + } + this.selectApp = this.adminApps[0]; + this.adminAppsIsNull = false; + if (this.selectApp != 'select-application') { + this.isAppSelectDisabled = false; + this.noUsersInApp = false; + this.noAppSelected = true; + } + }, (_err: HttpErrorResponse) => { + this.showSpinner = false; + if (_err.status === 403) { + this.adminAppsIsNull = true; + } else { + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = "Error"; + if (_err.status) { //Conflict + modalErrorRef.componentInstance.message = 'Error Status: ' + _err.status + ' There was a unknown problem adding the portal admin.' + 'Please try again later.'; + } + } + }); + } + + updateUsersList() { + this.appsIsDown = false; + this.noUsersInApp = false; + // $log.debug('UsersCtrl::updateUsersList: Starting updateUsersList'); + //reset search string + this.searchString = ''; + //should i disable this too in case of moving between tabs? + this.isAppSelectDisabled = true; + //activate spinner + this.showSpinner = true; + this.accountUsers = []; + this.adminsDataSource = new MatTableDataSource(this.accountUsers); + if (this.selectApp != 'select-application' && this.selectedApp) { // 'Select Application' + this.noAppSelected = false; + this.usersService.getAccountUsers(this.selectedApp.id) + .subscribe((accountUsers: []) => { + this.isAppSelectDisabled = false; + this.accountUsers = accountUsers; + if (!accountUsers || accountUsers.length === 0) { + this.noUsersInApp = true; + } + this.showSpinner = false; + this.adminsDataSource = new MatTableDataSource(this.accountUsers); + this.adminsDataSource.paginator = this.paginator; + this.adminsDataSource.sort = this.sort; + }, (_err: HttpErrorResponse) => { + this.isAppSelectDisabled = false; + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = "Error"; + modalErrorRef.componentInstance.message = 'Error Status: ' + _err.status + ' There was a problem updating the users List.' + 'Please try again later.'; + this.appsIsDown = true; + this.showSpinner = false; + }) + } else { + this.isAppSelectDisabled = false; + this.showSpinner = false; + this.noUsersInApp = false; + this.noAppSelected = true; + } + }; + + getSortOrder = (prop) => { + return function (a, b) { + if (a[prop] > b[prop]) { + return 1; + } else if (a[prop] < b[prop]) { + return -1; + } + return 0; + } + } +} diff --git a/portal-FE-common/src/app/pages/widgets/widgets.component.html b/portal-FE-common/src/app/pages/widgets/widgets.component.html new file mode 100644 index 00000000..fee3afbb --- /dev/null +++ b/portal-FE-common/src/app/pages/widgets/widgets.component.html @@ -0,0 +1,3 @@ +<p> + widgets works! +</p> diff --git a/portal-FE-common/src/app/pages/widgets/widgets.component.scss b/portal-FE-common/src/app/pages/widgets/widgets.component.scss new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/portal-FE-common/src/app/pages/widgets/widgets.component.scss diff --git a/portal-FE-common/src/app/pages/widgets/widgets.component.spec.ts b/portal-FE-common/src/app/pages/widgets/widgets.component.spec.ts new file mode 100644 index 00000000..72982a1c --- /dev/null +++ b/portal-FE-common/src/app/pages/widgets/widgets.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WidgetsComponent } from './widgets.component'; + +describe('WidgetsComponent', () => { + let component: WidgetsComponent; + let fixture: ComponentFixture<WidgetsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WidgetsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WidgetsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/widgets/widgets.component.ts b/portal-FE-common/src/app/pages/widgets/widgets.component.ts new file mode 100644 index 00000000..325ca42d --- /dev/null +++ b/portal-FE-common/src/app/pages/widgets/widgets.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-widgets', + templateUrl: './widgets.component.html', + styleUrls: ['./widgets.component.scss'] +}) +export class WidgetsComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} |