diff options
Diffstat (limited to 'portal-FE-common/src/app/pages')
146 files changed, 17488 insertions, 0 deletions
diff --git a/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.html b/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.html new file mode 100644 index 00000000..31f28615 --- /dev/null +++ b/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.html @@ -0,0 +1,132 @@ +<!-- + ============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">Account Details</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"> + <!-- Add Account Popup code goes here--> + <div class="account-properties-main"> + <form id="account-details-form" name="accountDetailsForm" novalidate autocomplete="off" method="post"> + <div class="item required"> + <div class="item-label">Account Name</div> + <input id="account-onboarding-details-input-name" class="table-search-field" + type="text" ng-change="accountAddDetails.updateAccountName()" name="accountname" + [(ngModel)]="accountOnboarding.applicationName" + ng-pattern="/^[\w -]*$/" maxlength="100" required /> + </div> + + <div class="item required"> + <div class="item-label">Username</div> + <input id="account-onboarding-details-input-username" class="table-search-field" + type="text" + ng-change="accountAddDetails.updateUsername()" name="username" + [(ngModel)]="accountOnboarding.username"/> + </div> + + <div class="item"> + <div class="item-label">Password</div> + <input id="account-onboarding-details-input-password" class="table-search-field" + type="password" name="password" [(ngModel)]="accountOnboarding.password" + maxlength="100" autocomplete="off"/> + </div> + + <div class="item"> + <div class="item-label">Retype Password</div> + <input id="account-onboarding-details-input-repassword" class="table-search-field" + type="password" name="repassword" + [(ngModel)]="accountOnboarding.repassword" maxlength="100"/> + </div> + + <div class="add-endpoint-item"> + <div class="item-label add-label-left">Add Endpoint</div> + <div class="add-label-right" id="accountAddDetails-button-accordion" + (click)="addEndpoint()"><i class="icon ion-ios-add-circle-outline"></i> + </div> + </div> + + <div class="item"> + <div class="item-label" *ngIf="accountOnboarding.endpointList && accountOnboarding.endpointList.length > 0"> + Account endpoint + </div> + + <div id="account-details-user-paramters" *ngFor="let endpoint of accountOnboarding.endpointList"> + <div class="endpoint-item-left"> + <input id="account-details-input-endpoint-name" + type="text" name="endpointName" + maxlength="200" ng-model="endpoint.name" + ng-change="accountAddDetails.updateAccountEndpoint(endpoint)"/> + </div> + + <div class="icon-circle-action-remove endpoint-item-middle" + (click)="removeEndpointItem(endpoint)"><span><i class="icon ion-ios-remove-circle-outline"></i></span> + </div> + + <div class="error-container endpoint-item-right" + *ngIf="endpoint.valid == false"> + <small id="accounts-details-input-invalid-endpoint" + class="err-message">Invalid end point format + </small> + </div> + </div> + + <div class="account-property"> + <div class="property-label checkbox-label"> + <mat-checkbox name="isActive" type="checkbox" [(ngModel)]="accountOnboarding.isActive" + id="accounts-checkbox-app-is-enabled" [checked]="accountOnboarding.isActive"> + Active + </mat-checkbox> + </div> + </div> + </div> + </form> + </div> + </div> + <!--Modal Footer goes Here--> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" (click)="saveChanges()">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/account-onboarding/account-add-details/account-add-details.component.scss b/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.scss new file mode 100644 index 00000000..4a490b1c --- /dev/null +++ b/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.scss @@ -0,0 +1,77 @@ +/*- + * ============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============================================ + * + * + */ + +.account-properties-main { + padding-left: 20px; + padding-right: 20px; + margin-bottom: 50px; + height: 320px; + width: 100%; + overflow-x: auto; +} + +.required:before { + color: #cf2a2a; + margin-right: 2px; + content: "* "; + position: absolute; + top: 45px; + left: 28px; +} + +.account-properties-main input[type="text"] { + width: 20em; + margin-bottom: 10px; +} + +.account-properties-main input[type="password"] { + width: 20em; + margin-bottom: 10px; +} + +.add-endpoint-item .add-label-right{ + margin-left: 13em; +} + +.add-endpoint-item{ + display: inline-flex; +} + +.account-property{ + margin-top: 8px; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.spec.ts b/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.spec.ts new file mode 100644 index 00000000..fd568194 --- /dev/null +++ b/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.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 { AccountAddDetailsComponent } from './account-add-details.component'; + +describe('AccountAddDetailsComponent', () => { + let component: AccountAddDetailsComponent; + let fixture: ComponentFixture<AccountAddDetailsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AccountAddDetailsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AccountAddDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.ts b/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.ts new file mode 100644 index 00000000..e2c408a1 --- /dev/null +++ b/portal-FE-common/src/app/pages/account-onboarding/account-add-details/account-add-details.component.ts @@ -0,0 +1,159 @@ +/*- + * ============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 { BasicAuthAccountService } from 'src/app/shared/services'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; + +@Component({ + selector: 'app-account-add-details', + templateUrl: './account-add-details.component.html', + styleUrls: ['./account-add-details.component.scss'] +}) +export class AccountAddDetailsComponent implements OnInit { + + result: any; + isEditMode: boolean = false; + dupliateName: boolean = false; + emptyAccountName: boolean = false; + emptyAccountUsername: boolean = false; + passwordMatched: boolean = false; + + @Input() accountOnboarding: any; + @Output() passEntry: EventEmitter<any> = new EventEmitter(); + + constructor(public basicAuthAccountService: BasicAuthAccountService, public activeModal: NgbActiveModal, public ngbModal: NgbModal) { } + + ngOnInit() { + this.passwordMatched = true; + this.dupliateName = false; + this.emptyAccountName = false; + this.emptyAccountUsername = false; + + if(this.accountOnboarding.applicationName){ + this.isEditMode = true; + }else{ + this.isEditMode = false; + this.accountOnboarding.isActive = true; + this.accountOnboarding.endpointList = []; + } + //console.log("IsEditMode in Add account Dialog :: ",this.isEditMode) + + } + + addEndpoint(){ + const modalRef = this.ngbModal.open(ConfirmationModalComponent); + modalRef.componentInstance.title = ""; + modalRef.componentInstance.message = 'Please add the roles to this Username/MechId through AAF Screen'; + modalRef.result.then((result) => { }, (resut) => {return;}); + + /*this.accountOnboarding.endpointList.push({ + valid: true + });*/ + } + + //Add Or Update Account. + saveChanges(){ + var isValid = true; + //console.log("saveChanges called Account Onboarding"); + + if(this.accountOnboarding.applicationName == '' + || this.accountOnboarding.applicationName == undefined){ + this.emptyAccountName = true; + isValid = false; + } + + if(this.accountOnboarding.username == '' + || this.accountOnboarding.username == undefined){ + this.emptyAccountUsername = true; + isValid = false; + } + + if(this.dupliateName == true){ + isValid = false; + } + + if(this.dupliateName == true){ + isValid = false; + } + + if(this.accountOnboarding.password != this.accountOnboarding.repassword){ + this.passwordMatched = false; + isValid = false; + } + //console.log("isValid....",isValid) + if(!isValid) + return; + + var active = 'N'; + if(this.accountOnboarding.isActive == true) + active = 'Y'; + + var newAccount = { + applicationName: this.accountOnboarding.applicationName, + username: this.accountOnboarding.username, + password: this.accountOnboarding.password, + endpoints: this.accountOnboarding.endpoints, + isActive: active + }; + + if(this.isEditMode){ + var message = "Are you sure you want to change '" + this.accountOnboarding.applicationName + "'?" + this.basicAuthAccountService.updateAccount(this.accountOnboarding.id, newAccount) + .subscribe( _data => { + this.result = _data; + //console.log("updateAccount response :: ",this.result); + this.passEntry.emit(this.result); + this.ngbModal.dismissAll(); + }, error => { + console.log(error); + }); + }else{ + this.basicAuthAccountService.createAccount(newAccount) + .subscribe( _data => { + this.result = _data; + //console.log("createAccount response :: ",this.result); + this.passEntry.emit(this.result); + this.ngbModal.dismissAll(); + }, error => { + console.log(error); + }); + } + } +} diff --git a/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.html b/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.html new file mode 100644 index 00000000..88d2ab94 --- /dev/null +++ b/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.html @@ -0,0 +1,78 @@ +<!-- + ============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">App Account Management</h1> + </div> + <button type="button" style="float: right;" class="btn btn-primary" (click)="openAddNewAccountModal('')"> + <i class="icon ion-md-person-add"></i>Add Account + </button> + <div> + <table mat-table [dataSource]="dataSource" matSort> + <!-- Account Name Column --> + <ng-container matColumnDef="accountName"> + <th id="col1" mat-header-cell *matHeaderCellDef> Account Name </th> + <td (click)="openAddNewAccountModal(element)" id="rowheader_t1_{{i}}-accountName" + mat-cell *matCellDef="let element; let i = index;"> {{element.applicationName}} + </td> + </ng-container> + + <!-- Username Column --> + <ng-container matColumnDef="userName"> + <th id="col2" mat-header-cell *matHeaderCellDef> Username </th> + <td (click)="openAddNewAccountModal(element)" id="rowheader_t1_{{i}}-userName" + mat-cell *matCellDef="let element; let i=index;"> {{element.username}} </td> + </ng-container> + + <!-- Delete Column --> + <ng-container matColumnDef="delete"> + <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)="deleteAccount(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> +</div> diff --git a/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.scss b/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.scss new file mode 100644 index 00000000..05d0cd22 --- /dev/null +++ b/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.scss @@ -0,0 +1,46 @@ +/*- + * ============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 th{ + padding-bottom: 15px; + font-weight: bold; +} + +.ion-md-trash{ + cursor: pointer; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.spec.ts b/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.spec.ts new file mode 100644 index 00000000..8e6e79ea --- /dev/null +++ b/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.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 { AccountOnboardingComponent } from './account-onboarding.component'; + +describe('AccountOnboardingComponent', () => { + let component: AccountOnboardingComponent; + let fixture: ComponentFixture<AccountOnboardingComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AccountOnboardingComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AccountOnboardingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.ts b/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.ts new file mode 100644 index 00000000..9ed4f9da --- /dev/null +++ b/portal-FE-common/src/app/pages/account-onboarding/account-onboarding.component.ts @@ -0,0 +1,153 @@ +/*- + * ============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, Input} from '@angular/core'; +import { MatTableDataSource } from '@angular/material'; +import { MatSort, MatPaginator } from '@angular/material'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { BasicAuthAccountService } from '../../shared/services/index'; +import { IAccountOnboarding } from 'src/app/shared/model/account-onboarding/accountOnboarding'; +import { AccountAddDetailsComponent } from './account-add-details/account-add-details.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-account-onboarding', + templateUrl: './account-onboarding.component.html', + styleUrls: ['./account-onboarding.component.scss'] +}) +export class AccountOnboardingComponent implements OnInit { + + accountList: Array<IAccountOnboarding> = []; + result: any; + isEditMode: boolean = false; + displayedColumns: string[] = ['accountName', 'userName','delete']; + dataSource = new MatTableDataSource(this.accountList); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + constructor(public basicAuthAccountService: BasicAuthAccountService, public ngbModal: NgbModal) { } + + ngOnInit() { + this.getOnboardingAccounts(); + } + + populateTableData(wigetList: Array<IAccountOnboarding>){ + this.dataSource = new MatTableDataSource(wigetList); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + }; + + getOnboardingAccounts(){ + //console.log("getOnboardingAccounts called"); + this.basicAuthAccountService.getAccountList() + .subscribe(_data => { + this.result = _data; + //console.log("getOnboardingAccounts Data :: ", _data); + if (this.result == null || this.result == 'undefined') { + //console.log('BasicAuthAccountService::getOnboardingAccounts Failed: Result or result.data is null'); + }else { + this.accountList = this.result.response; + this.populateTableData(this.accountList); + } + }, error =>{ + console.log(error); + }); + + }; + + openAddNewAccountModal(rowData: any){ + //console.log("openAddNewAccountModal getting called..."); + const modalRef = this.ngbModal.open(AccountAddDetailsComponent, { size: 'lg' }); + modalRef.componentInstance.title = 'Account Details'; + if(rowData != 'undefined' && rowData){ + rowData.repassword = rowData.password; + modalRef.componentInstance.accountOnboarding = rowData; + this.isEditMode = true; + }else{ + modalRef.componentInstance.accountOnboarding = {}; + this.isEditMode = false; + } + modalRef.componentInstance.passEntry.subscribe((receivedEntry: any) => { + //console.log("receivedEntry >>> ",receivedEntry); + if(receivedEntry){ + this.accountList = []; + this.getOnboardingAccounts(); + } + }); + } + + deleteAccount(selectedAccount : any){ + let confirmationMsg = 'You are about to delete this account : ' + selectedAccount.applicationName + '. Click OK to continue.'; + this.openInformationModal("Confirmation",confirmationMsg).result.then((result) => { + if (result === 'Ok') { + //console.log("deleteAccount called Account Onboarding"); + if(!selectedAccount || selectedAccount == null || selectedAccount =='undefined'){ + console.log('AccountOnboardingCtrl::deleteAccount: No Account or ID... cannot delete'); + return; + } + //console.log("deleteAccount>>id",selectedAccount.id) + this.basicAuthAccountService.deleteAccount(selectedAccount.id) + .subscribe( _data => { + this.result = _data; + //console.log("deleteAccount response :: ",this.result); + this.accountList.splice(this.accountList.indexOf(selectedAccount), 1); + this.getOnboardingAccounts(); + }, error => { + console.log(error); + }); + } + }, (resut) => { + return; + }) + } + + 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/admins/admins.component.html b/portal-FE-common/src/app/pages/admins/admins.component.html new file mode 100644 index 00000000..a47a6207 --- /dev/null +++ b/portal-FE-common/src/app/pages/admins/admins.component.html @@ -0,0 +1,92 @@ +<!-- + ============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">Admins</h1> + </div> + + <mat-form-field> + <mat-label>All Applications</mat-label> + <mat-select> + <mat-option>All Applications</mat-option> + <mat-option *ngFor="let app of availableApps" [value]="app.value" (click)="applyFilterByAppName(app.value)">{{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)="openAddNewAdminModal()"><i class="icon ion-md-person-add"></i> + Add Admin</button> + <span class="onap-spinner" *ngIf="showSpinner"></span> + <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> + + <!-- Applications Column --> + <ng-container matColumnDef="appName"> + <th id="col4" mat-header-cell *matHeaderCellDef> Applications </th> + <td id="rowheader_t1_{{i}}-applications" mat-cell *matCellDef="let element; let i=index;"> + <div *ngFor="let element of element.apps; let i=index;"> {{element.appName}} </div> + </td> + </ng-container> + + <tr [hidden]="adminsData.length === 0" mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="openExistingAdminModal(row)"></tr> + </table> + <mat-paginator [hidden]="adminsData.length === 0" [pageSizeOptions]="[10, 20]" showFirstLastButtons></mat-paginator> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/admins/admins.component.scss b/portal-FE-common/src/app/pages/admins/admins.component.scss new file mode 100644 index 00000000..a7f4d385 --- /dev/null +++ b/portal-FE-common/src/app/pages/admins/admins.component.scss @@ -0,0 +1,42 @@ +/*- + * ============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; +} diff --git a/portal-FE-common/src/app/pages/admins/admins.component.spec.ts b/portal-FE-common/src/app/pages/admins/admins.component.spec.ts new file mode 100644 index 00000000..563f80ff --- /dev/null +++ b/portal-FE-common/src/app/pages/admins/admins.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 { AdminsComponent } from './admins.component'; + +describe('AdminsComponent', () => { + let component: AdminsComponent; + let fixture: ComponentFixture<AdminsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AdminsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/admins/admins.component.ts b/portal-FE-common/src/app/pages/admins/admins.component.ts new file mode 100644 index 00000000..d7a839b5 --- /dev/null +++ b/portal-FE-common/src/app/pages/admins/admins.component.ts @@ -0,0 +1,133 @@ +/*- + * ============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 { AdminsService, ApplicationsService } from 'src/app/shared/services'; +import { Admins, AllApps } from 'src/app/shared/model'; +import { MatTableDataSource, MatSort, MatPaginator } from '@angular/material'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { NewAdminComponent } from './new-admin/new-admin.component'; + +@Component({ + selector: 'app-admins', + templateUrl: './admins.component.html', + styleUrls: ['./admins.component.scss'] +}) +export class AdminsComponent implements OnInit { + availableApps: Array<{ index: number, title: string, value: string }> = []; + + constructor(private adminsService: AdminsService, private applicationService: ApplicationsService, + public ngModal: NgbModal) { } + + showSpinner = true; + displayedColumns: string[] = ['firstName', 'lastName', 'userId', 'appName']; + adminsData: Admins[] = []; + adminsDataSource = new MatTableDataSource(this.adminsData); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + ngOnInit() { + this.adminsData = []; + this.getAccoutAdminsData(); + this.getAllApps(); + } + + openAddNewAdminModal() { + const modalRef = this.ngModal.open(NewAdminComponent); + modalRef.componentInstance.title = 'New Admin'; + modalRef.componentInstance.dialogState = 1; + modalRef.componentInstance.disableBack = false; + modalRef.componentInstance.passBackNewAdminPopup.subscribe((_result: any) => { + modalRef.close(); + this.showSpinner = true; + this.getAccoutAdminsData(); + }, (_reason: any) => { + return; + }); + } + + openExistingAdminModal(_adminData: Admins) { + const modalRef = this.ngModal.open(NewAdminComponent); + modalRef.componentInstance.userTitle = `${_adminData.firstName}, ${_adminData.lastName} `+'('+`${_adminData.orgUserId}`+')'; + modalRef.componentInstance.adminModalData = _adminData; + modalRef.componentInstance.dialogState = 2; + modalRef.componentInstance.disableBack = true; + modalRef.componentInstance.passBackNewAdminPopup.subscribe((_result: any) => { + modalRef.close(); + this.showSpinner = true; + this.getAccoutAdminsData(); + }, (_reason: any) => { + return; + }); + } + + applyFilterByAppName(filterValue: string) { + + } + + applyFilter(filterValue: string) { + this.adminsDataSource.filter = filterValue.trim().toLowerCase(); + } + + + getAccoutAdminsData() { + this.adminsService.getAccountAdmins().subscribe((_res: Admins[]) => { + this.showSpinner = false; + this.adminsData = _res; + this.adminsDataSource = new MatTableDataSource(this.adminsData); + this.adminsDataSource.sort = this.sort; + this.adminsDataSource.paginator = this.paginator; + }); + } + + getAllApps() { + this.applicationService.getAvailableApps().subscribe((_res: AllApps[]) => { + var realAppIndex = 1; + for (let i = 1; i <= _res.length; i++) { + if (!_res[i - 1].restrictedApp) { + this.availableApps.push({ + index: realAppIndex, + title: _res[i - 1].title, + value: _res[i - 1].value + }); + realAppIndex = realAppIndex + 1; + } else { + } + } + }); + } + +} 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 new file mode 100644 index 00000000..ce721c46 --- /dev/null +++ b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.html @@ -0,0 +1,97 @@ +<!-- + ============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" *ngIf="dialogState===1"> + <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"> + <app-search-users [searchTitle]="searchTitleText" [placeHolder]="placeholderText" (passBackSelectedUser)='changeSelectedUser($event)'></app-search-users> + <span class="onap-spinner" *ngIf="isLoading"></span> + </div> + <div class="modal-footer"> + <button type="submit" class="btn btn-primary" [disabled]='!changedSelectedUser' + (click)="getAdminAppsRoles()">Next</button> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Close</button> + </div> +</div> +<div class="container" *ngIf="dialogState===2"> + <div class="modal-header"> + <h4 class="modal-title">{{userTitle}}</h4> + <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross')"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body adminApps"> + <div ngbDropdown class="d-inline-block"> + <h4>Administrates:</h4> + <button class="btn btn-outline-primary" id="dropdownBasic1" ngbDropdownToggle>Select Application</button> + <div ngbDropdownMenu aria-labelledby="dropdownBasic1"> + <button *ngFor="let app of adminDropdownApps" (click)="updateDropdown(app, true)" + 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"> + <!-- Search Result Column--> + <ng-container matColumnDef="applications"> + <th id="rowheader-result" mat-header-cell *matHeaderCellDef> Applications <span id="i-delete-application" + class="span-remove-title"> Delete </span></th> + <td id="table-data-{{i}}" mat-cell *matCellDef="let element; let i = index;"> + {{element.appName}} <span id="i-delete-application" class="span-remove-admin" + (click)="removeAdminApp(element)"><i class="icon ion-md-trash"></i></span> + </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 class="modal-footer"> + <button [hidden]="disableBack" type="submit" class="btn btn-primary" [disabled]='!changedSelectedUser' + (click)="navigateBack()">Back</button> + <button type="submit" class="btn btn-primary" [disabled]='!newAppSelected' + (click)="updateAdminAppsRoles(adminAppsScreen)">Save</button> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Close</button> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.scss b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.scss new file mode 100644 index 00000000..eb6db14c --- /dev/null +++ b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.scss @@ -0,0 +1,58 @@ +/*- + * ============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.adminApps { + overflow-y: auto; + height: 250px; +} + +.dropdown-menu.show { + overflow-y: auto !important; + height: 250px !important; +} +.span-remove-title { + float: right; +} +.span-remove-admin { + float: right; + 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/admins/new-admin/new-admin.component.spec.ts b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.spec.ts new file mode 100644 index 00000000..4040b0d3 --- /dev/null +++ b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.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 { NewAdminComponent } from './new-admin.component'; + +describe('NewAdminComponent', () => { + let component: NewAdminComponent; + let fixture: ComponentFixture<NewAdminComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NewAdminComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NewAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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 new file mode 100644 index 00000000..46a86d7d --- /dev/null +++ b/portal-FE-common/src/app/pages/admins/new-admin/new-admin.component.ts @@ -0,0 +1,233 @@ +/*- + * ============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 { AdminsService } from 'src/app/shared/services'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { PortalAdmin } from 'src/app/shared/model'; +import { MatTableDataSource } from '@angular/material'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-new-admin', + templateUrl: './new-admin.component.html', + styleUrls: ['./new-admin.component.scss'] +}) +export class NewAdminComponent implements OnInit { + + @Input() dialogState: number; + @Input() userTitle: string; + @Input() disableBack: boolean; + @Input() adminModalData: any; + @Output() passBackNewAdminPopup: EventEmitter<any> = new EventEmitter(); + searchTitleText = 'Enter First Name, Last Name or Org User Id'; + placeholderText = 'Search'; + changedSelectedUser: PortalAdmin; + adminAppsRoles: any; + adminDropdownApps: any; + isLoading: boolean; + newAppSelected: boolean; + adminAppSelectAndUnselectData: any; + displayedColumns: string[] = ['applications']; + adminsAppsSource = new MatTableDataSource(this.adminAppsRoles); + constructor(public router: Router, private adminsService: AdminsService, public ngModal: NgbModal, public activeModal: NgbActiveModal) { } + + ngOnInit() { + this.adminAppsRoles = []; + this.changedSelectedUser = null; + if (this.disableBack){ + this.changedSelectedUser = this.adminModalData; + this.getAdminAppsRoles(); + } + this.adminDropdownApps = []; + this.adminAppSelectAndUnselectData = []; + } + + changeSelectedUser(user: PortalAdmin) { + this.changedSelectedUser = user; + this.userTitle = `${this.changedSelectedUser.firstName}, ` + ` ${this.changedSelectedUser.lastName} ` + ` (${this.changedSelectedUser.orgUserId})`; + } + + getAdminAppsRoles() { + this.isLoading = true; + this.adminsService.getAdminAppsRoles(this.changedSelectedUser.orgUserId).subscribe((_res: any) => { + JSON.stringify(_res); + if (!_res.appsRoles) { + return; + } + this.adminAppsRoles = []; + for (var i = 0; i < _res.appsRoles.length; i++) { + if (!_res.appsRoles[i].restrictedApp && _res.appsRoles[i].isAdmin) { + this.adminAppsRoles.push({ + id: _res.appsRoles[i].id, + appName: _res.appsRoles[i].appName, + isAdmin: _res.appsRoles[i].isAdmin, + restrictedApp: _res.appsRoles[i].restrictedApp + }); + } else if (!_res.appsRoles[i].restrictedApp) { + this.adminDropdownApps.push({ + id: _res.appsRoles[i].id, + appName: _res.appsRoles[i].appName, + isAdmin: _res.appsRoles[i].isAdmin, + restrictedApp: _res.appsRoles[i].restrictedApp + }); + } + } + this.isLoading = false; + this.newAppSelected = false; + this.dialogState = 2; + this.adminsAppsSource = new MatTableDataSource(this.adminAppsRoles); + }); + } + + navigateBack() { + this.dialogState = 1; + this.changedSelectedUser = null; + } + + removeAdminApp(app: any) { + const modalRef = this.ngModal.open(InformationModalComponent); + modalRef.componentInstance.title = "Confirmation"; + modalRef.componentInstance.message = `Are you sure you want to delete ${app.appName}?`; + modalRef.result.then((result) => { + if (result === 'Ok') { + this.adminAppsRoles.forEach((item: any, index: any) => { + if (item === app) this.adminAppsRoles.splice(index, 1); + }); + //call from delete admin app + this.updateDropdown(app, false); + this.adminsAppsSource = new MatTableDataSource(this.adminAppsRoles); + } + }, (resut) => { + return; + }) + } + + updateDropdown(_newValue: any, isDropdownCall: boolean) { + // app is selected from dropdown + if (isDropdownCall) { + this.adminDropdownApps.forEach((item: any, index: any) => { + if (item === _newValue) this.adminDropdownApps.splice(index, 1); + }); + this.getadminAppSelectAndUnselectedData(_newValue); + _newValue.isAdmin = true; + this.adminAppsRoles.push(_newValue); + this.adminsAppsSource = new MatTableDataSource(this.adminAppsRoles); + } else { // app is removed from the admin list + this.getadminAppSelectAndUnselectedData(_newValue); + _newValue.isAdmin = false; + this.adminDropdownApps.push(_newValue); + } + + // disable save button if nothing new in the admin list + if (this.adminAppSelectAndUnselectData.length > 0) + this.newAppSelected = true; + else + this.newAppSelected = false; + + } + + private getadminAppSelectAndUnselectedData(_newValue: any) { + const index: number = this.adminAppSelectAndUnselectData.indexOf(_newValue); + if (index !== -1) { + this.adminAppSelectAndUnselectData.splice(index, 1); // if found, remove selected app from dropdown in the list + } + else { + this.adminAppSelectAndUnselectData.push(_newValue); + } + } + + remindToAddUserIfNecessary() { + let adminAddedToNewApp = true; + if ((this.adminAppsRoles != null) && (this.adminAppsRoles.length > 0)) { + for (var i = 0; i < this.adminAppSelectAndUnselectData.length; i++) { + var foundApp = false; + for (var j = 0; j < this.adminAppsRoles.length; j++) { + if (this.adminAppsRoles[j] === this.adminAppSelectAndUnselectData[i]) { + foundApp = true; + } + } + if (foundApp === false) { + adminAddedToNewApp = true; + break; + } + } + } else { + adminAddedToNewApp = true; + } + if (adminAddedToNewApp === true) { + const modalRef = this.ngModal.open(InformationModalComponent); + modalRef.componentInstance.title = "Confirmation"; + modalRef.componentInstance.message = 'Add this person as an application user? This allows them to access the application from ONAP Portal. Press OK to go to the Add Users page.'; + modalRef.result.then((_res) => { + if (_res === 'Ok') { + this.router.navigate(['/users']); + } + }); + } + } + + 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?'; + modalRef.result.then((result) => { + if (result === 'Ok') { + 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"; + if (_err.status) { + modalErrorRef.componentInstance.message = "There was a unknown problem while adding admin to selected application(s)." + "Please try again later. Error Status: " + _err.status; + } + }); + } + this.isLoading = false; + }, (reason) => { + return; + }); + } +} diff --git a/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.html b/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.html new file mode 100644 index 00000000..af54d276 --- /dev/null +++ b/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.html @@ -0,0 +1,143 @@ +<!-- + ============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> + <div id="page-content"> + <div id="title"> + <h1>{{'Application Catalog'}}</h1> + </div> + <div> + <div> + <div id="container" class="simulateCatGridHeader"> + <span class="simulateCatGridHeaderDetails"> Click the check + boxes below to choose which applications are shown on the <a [routerLink]="['/applicationsHome']">home + page</a>. + </span> <br> <br> + <div> + <div class="catalog-radio-div"> + <label class="radio"> + <span>{{'Filter'}}: </span> + </label> + </div> + <div class="catalog-radio-div"> + <label class="radio"> + <input type="radio" [(ngModel)]="radioValue" id="radio-button-all" name="All" value="All"> + <i class="skin"></i> + <span>{{'All'}}</span> + </label> + </div> + <div class="catalog-radio-div"> + <label class="radio"> + <input type="radio" [(ngModel)]="radioValue" id="radio-button-accessible" name="Accessible" + value="Accessible"> + <i class="skin"></i> + <span>{{'Accessible'}}</span> + </label> + </div> + <div class="catalog-radio-div"> + <label class="radio"> + <input type="radio" [(ngModel)]="radioValue" id="radio-button-homepage" name="Selected" + value="Selected"> + <i class="skin"></i> + <span>{{'On Home Page'}}</span> + </label> + </div> + + </div> + </div> + <div> + <div> + <gridster [options]="options" class="appCatalogue-boarder"> + + + <div *ngFor="let item of layout"> + <gridster-item [item]="item" *ngIf="(item.select && radioValue=='Selected') || (item.access && radioValue=='Accessible') || (radioValue == 'All')"> + <div class="gridster-box" [ngStyle]="{'color': !item.access ? 'lightgray':''}"> + <div class="gridster-box-header"> + + <mat-icon class="icon-content-gridguide">drag_handle</mat-icon> + <h3>{{ item.name | elipsis: 13}}</h3> + <span style="position: absolute;right:1em" *ngIf="item.access || isUserSuperAdmin"> + <label id="widget-checkbox-label" class="checkbox"> + + <input type="checkbox" id="{{item.name.split(' ').join('-')}}-checkbox" + [(ngModel)]="item.select" (ngModelChange)="storeSelection(item)" /> + + + <i class="skin"></i> + </label> + </span> + + + </div> + <div class="gridster-box-content" [ngStyle]="{'cursor':'pointer', + 'background-image': 'url('+(item.imageLink)+')', + 'order': item.order, + 'background-color':'white', + 'background-repeat': 'no-repeat', + 'background-size': '168px 118px', + 'height': '80%'}" (click)="openAddRoleModal(item)"> + </div> + + </div> + </gridster-item> + </div> + + + + </gridster> + </div> + </div> + + </div> + + <div id="widgets-disclaimer" class="w-ecomp-main-disclaimer"> + To request access to an application widget, please visit the <a>Get Access</a> page. + </div> + </div> + </div> +</div> + +<div> + <div> + + </div> + <div> + <div></div> + <div></div> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.scss b/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.scss new file mode 100644 index 00000000..29198a52 --- /dev/null +++ b/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.scss @@ -0,0 +1,174 @@ +/* + * ============LICENSE_START========================================== + * ONAP Portal SDK + * =================================================================== + * 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============================================ + * + * + */ + + + + :host { .toolbar { + height: 100px; + display: flex; +} gridster { + display: flex; + height: calc(100vh - 115px); + flex-direction: column; +}} +.icon-content-gridguide{ + cursor:move; + font-size: 14px; + } + .form-row { + margin-top: -14px; +} +.griditem-header{ + + + border-bottom: 1px; + border-bottom-color: solid gray; + + + +} + +.checkbox, .radio { +min-height: 10px; +padding-left: 0px; +} + +.simulateCatGridHeader{ + position: relative; + height: 70px !important; + border: 1px solid #d3d3d3; + border-bottom: 0; + background-color: #E5E5E5; + white-space: nowrap; + text-overflow: ellipsis; + z-index: 1; +} + +.simulateCatGridHeaderTitle{ + line-height: 20px; + margin-top: 10px; + margin-left: 26px; + font-family: "Omnes-ECOMP-W02", Arial; + font-size: 18px; + color: #444444; + float: left; +} + +.simulateCatGridHeaderRadio{ + line-height: 20px; + margin-top: 10px; + margin-left: 10px; + font-family: "Omnes-ECOMP-W02", Arial; + font-size: 12px; + color: #444444; + float: left; +} + +.simulateCatGridHeaderDetails{ + line-height: 20px; + margin-left: 10px; + font-family: "Omnes-ECOMP-W02", Arial; + font-size: 12px; + color: #444444; + float: left; +} + +.simulateGridHeaderHandle{ + cursor: move; + margin: 12px; + position: absolute; + top: 0; + left: 0; + border: 0; + vertical-align: middle; + -ms-interpolation-mode: bicubic; + display: block; +} + +.catalog-radio-div{ + display:inline-block; + margin-right:5px; +} + +.appCatalogue-boarder{ + background-color: #eee; + border: 1px dashed white; + + overflow-y: auto; + overflow-x: hidden; + } + + .gridster-box { + height: 100%; + border: 1px solid #ccc; + background-color: #fff; + transition: transform 0.5s ease-out; +} +.gridster-box-header { + background-color: #fff; + padding: 0 0px 0 10px; + border-bottom: 1px solid #ccc; + position: relative; + height: 50px !important; +} +.gridster-box-header h3 { + margin-top: 15px; + display: inline-block; + font-size: 70%; + font-family: "Omnes-ECOMP-W02", Arial; +} +.gridster-box-content { + padding: 59px; +} +.gridster-box:hover{ + transform: scale(1.1); +} +.gridster-box-header-btns { + top: 15px; + right: 10px; + position: absolute; +} +.checkbox input{ + margin: 6px; + left: 112px; + top: -20px; + } +.checkbox .skin { + left: 125px; + top: -18px; +} diff --git a/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.spec.ts b/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.spec.ts new file mode 100644 index 00000000..1967d254 --- /dev/null +++ b/portal-FE-common/src/app/pages/application-catalog/application-catalog.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 { ApplicationCatalogComponent } from './application-catalog.component'; + +describe('ApplicationCatalogComponent', () => { + let component: ApplicationCatalogComponent; + let fixture: ComponentFixture<ApplicationCatalogComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ApplicationCatalogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ApplicationCatalogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.ts b/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.ts new file mode 100644 index 00000000..f571dcaa --- /dev/null +++ b/portal-FE-common/src/app/pages/application-catalog/application-catalog.component.ts @@ -0,0 +1,179 @@ +/*- + * ============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 { GridsterConfig, GridsterItem } from 'angular-gridster2'; +import { ApplicationCatalogService } from '../../shared/services/application-catalog/application-catalog.service'; +import { IApplicationCatalog } from '../../shared/model/application-catalog.model'; +import { IWidgetCatalog } from '../../shared/model/widget-catalog.model'; +import { environment } from 'src/environments/environment'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { CatalogModalComponent } from '../catalog-modal/catalog-modal.component'; +import { ExternalRequestAccessService } from 'src/app/shared/services/external-request-access-service/external-request-access.service'; +import { UsersService } from 'src/app/shared/services/users/users.service'; + +@Component({ + selector: 'app-application-catalog', + templateUrl: './application-catalog.component.html', + styleUrls: ['./application-catalog.component.scss'] +}) +export class ApplicationCatalogComponent implements OnInit { + widgetCatalogData: IWidgetCatalog[]; + appCatalogData: IApplicationCatalog[]; + resultAccessValue: string; + orgUserId: string; + firstName: string; + lastName: string; + radioValue: any; + isUserSuperAdmin: boolean; + + get options(): GridsterConfig { + return this.applicationCatalogService.options; + } get layout(): GridsterItem[] { + return this.applicationCatalogService.layout; + } constructor(private applicationCatalogService: ApplicationCatalogService, private externalRequestAccessService: ExternalRequestAccessService, private userService: UsersService, private modal: NgbModal) { } + + ngOnInit() { + this.applicationCatalogService.clearCatalog(); + this.radioValue = 'All'; + this.callAppCatalogExecutor(); + } + + callAppCatalogExecutor() { + //Check whether Admin is Super Admin + this.checkAdminIsSuperAdmin(); + //To fetch ML value + this.getExternalAccess(); + + //Call user profile service + this.getUserProfile(); + + //Call Application Catalog services + this.getAppCatalogService(); + } + + checkAdminIsSuperAdmin() { + this.applicationCatalogService.checkIfUserIsSuperAdmin().subscribe(data => { + this.isUserSuperAdmin = data; + }, error => { + console.log('checkAdminIsSuperAdmin Error Object' + error); + }); + } + + getAppCatalogService() { + //console.log("getAppCatalogServices called"); + this.applicationCatalogService.getAppCatalog().subscribe(data => { + //console.log("Response data" + data); + this.appCatalogData = data; + for (let entry of this.appCatalogData) { + //console.log("Check the URL" + environment.api.appThumbnail); + var appCatalog = { + x: -1, + y: -1, + id: entry.id, + name: entry.name, + mlAppName: entry.mlAppName, + imageLink: environment.api.appThumbnail.replace(':appId', <string><any>entry.id), + restricted: entry.restricted, + select: entry.select, + access: entry.access, + pending: entry.pending, + mlproperty: this.resultAccessValue + }; + this.applicationCatalogService.addItem(appCatalog); + } + }, error => { + console.log('getAppCatalogServices Error Object' + error); + }); + }; + + storeSelection(appCatalogData: any) { + //console.log("Store selection called " + appCatalogData.name); + var pendingFlag: boolean = false; + if (appCatalogData.access) + pendingFlag = false; + else + pendingFlag = appCatalogData.pending; + + var appData = { + appId: appCatalogData.id, + select: appCatalogData.select, + pending: pendingFlag + }; + this.applicationCatalogService.updateManualAppSort(appData).subscribe(data => { + //console.log("Update App sort data" + data); + }, error => { + console.log('Update App sort error' + error); + }); + + this.applicationCatalogService.updateAppCatalog(appData).subscribe(data => { + //console.log("Update App Catalog data" + data); + }, error => { + console.log('Update App Catalog error' + error); + }); + }; + openAddRoleModal(item: any) { + //console.log("OpenModal check" + item.id); + if ((!item.restricted) && (item.mlproperty)) { + this.modal.open(CatalogModalComponent); + } + } + + getExternalAccess() { + //console.log("getExternalAccess service called"); + this.externalRequestAccessService.getExternalRequestAccessServiceInfo().subscribe(data => { + //console.log("Response data" + data); + if (data) + this.resultAccessValue = data.accessValue; + }, error => { + console.log('getExternalAccess Error object' + error); + }); + } + + getUserProfile() { + const userProfileObservable = this.userService.getUserProfile(); + userProfileObservable.subscribe((userProfile: any) => { + //console.log('UserProfile is ' + userProfile); + if (userProfile) { + this.orgUserId = userProfile.orgUserId; + this.firstName = userProfile.firstName; + this.lastName = userProfile.lastName; + } + }); + + } +} 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/contact-us/contact-us-manage/contact-us-manage.component.html b/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.html new file mode 100644 index 00000000..02dd70cb --- /dev/null +++ b/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.html @@ -0,0 +1,156 @@ +<!-- + ============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">Manage Contact Us</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="c-onap-portal-abs-table contactus-manage-table"> + <table b2b-table id="table-main" table-data="contactUsList" current-page="ignoredCurrentPage"> + <thead b2b-table-row type="header"> + <tr> + <th id="th-users-0" b2b-table-header key="onap_function" default-sort="a">App Name</th> + <th id="th-users-1" b2b-table-header key="app_name" sortable="true">Contact Name</th> + <th id="th-users-2" b2b-table-header key="app_name" sortable="true">Contact Email</th> + <th id="th-users-3" b2b-table-header key="role_name" sortable="true">Contact URL</th> + <th id="th-users-4" b2b-table-header key="role_name" sortable="true">Description</th> + <th id="th-users-5" b2b-table-header key="role_name" sortable="true">Edit</th> + <th id="th-users-6" b2b-table-header key="role_name" sortable="true">Delete</th> + </tr> + </thead> + <!-- Use track-by="UNIQUE KEY HERE" or leave out if no unique keys in data --> + <tbody b2b-table-row type="body" class="table-body" track-by="$index" *ngFor="let rowData of contactUsList; index as i"> + <tr id="tr-rowData" ng-click=""> + <td b2b-table-body> + <div id="users-page-td-appName" [innerHtml]="rowData.appName"></div> + </td> + <td b2b-table-body> + <div id="users-page-td-name" [hidden]="showEdit" [innerHtml]="rowData.contactName"></div> + <input class="input-inline-edit-text" type="text" *ngIf="showEdit" [(ngModel)]="rowData.contactName" /> + </td> + <td b2b-table-body> + <div id="users-page-td-email" [hidden]="showEdit" [innerHtml]="rowData.contactEmail"></div> + <input class="input-inline-edit-text" type="text" *ngIf="showEdit" [(ngModel)]="rowData.contactEmail" /> + </td> + <td b2b-table-body> + <div id="users-page-td-url" [hidden]="showEdit" [innerHtml]="rowData.url"></div> + <input class="input-inline-edit-text" type="text" *ngIf="showEdit" [(ngModel)]="rowData.url" /> + </td> + <td b2b-table-body> + <div id="users-page-td-descr" [hidden]="showEdit" [innerHtml]=" rowData.description"></div> + <input class="input-inline-edit-text" type="text" *ngIf="showEdit" [(ngModel)]="rowData.description" /> + </td> + <td b2b-table-body> + <div class="edit-contact-us" [hidden]="showEdit"> + <span class="icon-edit" (click)="showEdit=true"> + <i class="icon ion-md-create"></i> + </span> + </div> + <span *ngIf="showEdit" (click)="editContactUs(rowData)"> + <i class="icon ion-md-save"></i> + </span> + </td> + <td b2b-table-body> + <div class="delete-contact-us"> + <span class="icon-trash" (click)="delContactUs(rowData)"> + <i class="icon ion-md-trash"></i> + </span> + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div class="divider-line"></div> + + <div class="contactus-addWidgetHeader"> + <div id="addWidgetHeader" class="contact-us-margin"> + <p class="edit-contactus-title">Add Application Contact Information</p> + </div> + <div id="addWidget" class="contact-us-margin"> + <div> + <form name="contactForm" novalidate autocomplete="off"> + <div id="add-contact-us-field-appname" class="add-contact-us-field"> + <mat-form-field class="contact-us-applications-select"> + <mat-label> <span id="required" class="mots-property-label-spn" visible="false"> *</span>Application Name </mat-label> + <mat-select name="contact-us-applications-select" id="application" + name="applicationName" [(ngModel)]="selectedApp" [(value)]="selected"> + <mat-option *ngFor="let d of contactUsAllAppList" [value]="d" >{{d.title}}</mat-option> + </mat-select> + </mat-form-field> + <div id="mots-property-label-required" *ngIf="selectedApp==null || selectedApp==''"> + <small class="mandatory-categories">App Name is Required</small> + </div> + </div> + <br> + <div class="addApplicationContactInfo"> + <div id="add-contact-us-field-contactname" class="add-contact-us-field"> + <div id="property-label-name" class="property-label">Contact Name</div> + <input id="property-input-name" name="name" class="input-text-area" type="text" [(ngModel)]="newContactUs.contactName" /> + </div> + <div id="add-contact-us-field-email" class="add-contact-us-field"> + <div id="property-label-email" class="property-label">Contact Email</div> + <input id="property-input-email" name="email" class="input-text-area" type="text" [(ngModel)]="newContactUs.contactEmail" /> + </div> + <div id="add-contact-us-field-url" class="add-contact-us-field"> + <div id="property-label-url" class="property-label">Contact URL</div> + <input id="property-input-url" name="url" class="input-text-area" type="text" [(ngModel)]="newContactUs.url" /> + </div> + </div> + <div id="add-contact-us-field-desc" class="add-contact-us-field-des"> + <div id="property-label-desc" class="property-label">Description</div> + <textarea id="property-input-desc" name="description" class="property-label-desc-txtarea" [(ngModel)]="newContactUs.description"></textarea> + </div> + </form> + </div> + </div> + </div> + </div> + <!--Modal Footer goes Here--> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Close</button> + <button type="button" class="btn btn-primary" (click)="addNewContactUs()">Add New</button> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.scss b/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.scss new file mode 100644 index 00000000..bff240e2 --- /dev/null +++ b/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.scss @@ -0,0 +1,79 @@ +/*- + * ============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============================================ + * + * + */ + ::ng-deep .modal-dialog { + max-width: 1200px; + width: 1200px; + overflow-x: auto; + overflow-y: auto; +} + +.addApplicationContactInfo{ + display: inline-flex; +} + +.addApplicationContactInfo input[type="text"] { + width: 22em; + margin-right: 8px; +} + +.property-label-desc-txtarea { + overflow: auto; + resize: vertical; + width: 67em; +} + +.divider-line{ + border-bottom-style: solid; + border-bottom-width: 1px; + padding-bottom: 50px; +} + +.edit-contactus-title { + margin-top: 28px; + margin-bottom: 1rem; + font-size: 23px; +} + +.required:before { + color: #cf2a2a; + margin-right: 2px; + content: "* "; + position: absolute; + top: 45px; + left: 28px; +} diff --git a/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.spec.ts b/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.spec.ts new file mode 100644 index 00000000..06b6995a --- /dev/null +++ b/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.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 { ContactUsManageComponent } from './contact-us-manage.component'; + +describe('ContactUsManageComponent', () => { + let component: ContactUsManageComponent; + let fixture: ComponentFixture<ContactUsManageComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ContactUsManageComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContactUsManageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.ts b/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.ts new file mode 100644 index 00000000..e04ccb77 --- /dev/null +++ b/portal-FE-common/src/app/pages/contact-us/contact-us-manage/contact-us-manage.component.ts @@ -0,0 +1,182 @@ +/*- + * ============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} from '@angular/core'; +import { ContactUsService } from '../../../shared/services/index'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-contact-us-manage', + templateUrl: './contact-us-manage.component.html', + styleUrls: ['./contact-us-manage.component.scss'] +}) +export class ContactUsManageComponent implements OnInit { + + contactUsList = []; + contactUsAllAppList = []; + result: any; + selectedApp: any; + showEdit: boolean = false; + newContactUs ={ + appId:'', + appName:'', + description:'', + contactName:'', + contactEmail:'', + url:'' , + activeYN:'' + }; + + + constructor(public activeModal: NgbActiveModal, public ngbModal: NgbModal, public contactUsService: ContactUsService) { } + + ngOnInit() { + + this.getContactUsList(); + this.getListOfApp(); + } + + getContactUsList(){ + console.log("getContactUsList called..."); + this.contactUsService.getContactUs() + .subscribe( _data => { + this.result = _data; + console.log("getContactUsList Data :: ", _data); + if (this.result.response == null || this.result.response == 'undefined') { + console.log('ContactUsService::getContactUsList Failed: Result or result.data is null'); + }else{ + for(var i=0; i<this.result.response.length;i++){ + if(this.result.response[i].appId!=1) + this.contactUsList.push(this.result.response[i]); + } + } + },error =>{ + console.log(error); + }); + } + + getListOfApp(){ + console.log("getListOfApp called..."); + this.contactUsService.getListOfApp() + .subscribe( _data => { + this.result = _data; + console.log("getListOfApp Data :: ", _data); + if (this.result == null || this.result == 'undefined') { + console.log('ContactUsService::getListOfApp Failed: Result or result.data is null'); + }else{ + let res1 = this.result; + let realAppIndex = 0; + this.contactUsAllAppList.length=0; + console.log("this.contactUsList ",this.contactUsList) + for (var i = 1; i <= res1.length; i++) { + if (!res1[i - 1].restrictedApp) { + var okToAdd = true; + for(var j =0; j<this.contactUsList.length;j++){ + if(res1[i - 1].title == this.contactUsList[j].appName){ + okToAdd=false; + console.log("okToAdd=false res1[i - 1].title ",res1[i - 1].title); + } + } + // not allowed to add(duplicate) another entry if the app is already available in the table + if(okToAdd){ + if(res1[i - 1].title){ + this.contactUsAllAppList.push({ + index: realAppIndex, + title: res1[i - 1].title, + value: res1[i - 1].index + }); + } + realAppIndex = realAppIndex + 1; + } + } + } + } + },error =>{ + console.log(error); + }); + } + + addNewContactUs(){ + console.log("Calling addNewContactUs"); + let selectedApplication = this.selectedApp; + this.newContactUs.appId = selectedApplication.value; + this.newContactUs.appName = selectedApplication.title; + console.log("newContactUsObj ",this.newContactUs); + this.contactUsService.addContactUs(this.newContactUs) + .subscribe( _data => { + this.result = _data; + console.log("addContactUs response :: ", _data); + this.contactUsList.push(this.newContactUs); + },error =>{ + console.log(error); + }); + } + + editContactUs(contactObj: any){ + + var contactUsObj={ + appId:contactObj.appId, + appName:contactObj.appName, + description:contactObj.description, + contactName:contactObj.contactName, + contactEmail:contactObj.contactEmail, + url:contactObj.url, + }; + + this.contactUsService.modifyContactUs(contactUsObj) + .subscribe( _data => { + this.result = _data; + console.log("editContactUsFun response :: ", _data); + this.showEdit=false; + },error =>{ + console.log(error); + }); + } + + delContactUs(appObj: any){ + this.contactUsService.removeContactUs(appObj.appId) + .subscribe( _data => { + this.result = _data; + console.log("delContactUsFun response :: ", _data); + this.contactUsList.splice(appObj, 1); + },error =>{ + console.log(error); + }); + } + +} diff --git a/portal-FE-common/src/app/pages/contact-us/contact-us.component.html b/portal-FE-common/src/app/pages/contact-us/contact-us.component.html new file mode 100644 index 00000000..f7f56463 --- /dev/null +++ b/portal-FE-common/src/app/pages/contact-us/contact-us.component.html @@ -0,0 +1,163 @@ +<!-- + ============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"> + <div id="contact-us-title" class="w-onap-main-view-title"> + <h1 class="heading-page">Contact Us</h1> + </div> + <button type="button" style="float: right;" class="btn btn-primary" (click)="editContactUsModal()"> + <i class="icon ion-md-person-add"></i>Edit Contact Us + </button> + <div class="w-onap-contactUs-home"> + <div class="contactUs-home-container" id="page-content"> + <div class="admins-page-main"> + <div class="contactUs-general-div"> + <span class="contactUs-txt"> + To report an issue with ONAP Portal, open a ticket at the + <a href="{{ush_TicketInfoUrl}}" target="_blank">User Self Help system</a>. + </span><br /> + </div> + + <div class="contactUs-general-div"> + <span class="contactUs-txt"> + To give feedback to the ONAP Portal team, send email to + <a href="mailto:{{portalInfo_Address}}" target="_top">{{portalInfo_Address}}</a>. + </span><br /> + </div> + + <div class="contactUs-general-div"> + <span class="contactUs-txt"> + To learn more about ONAP Portal, visit the + <a href="{{feedback_Url}}" target="_blank">ONAP Portal wiki</a> (may need to request access). + </span><br /> + </div> + + <!-- Function Categories --> + <div id="appFunctionInfo" class="w-onap-main-view-title"> Application Functions + <br /> + <div b2b-table table-data="contact.functionalTableData" class="b2b-table-div"> + <table mat-table [dataSource]="dataSource" matSort> + <!-- Category --> + <ng-container matColumnDef="category"> + <th id="col1" mat-header-cell *matHeaderCellDef> Category </th> + <td id="rowheader_t1_{{i}}-category" + mat-cell *matCellDef="let element; let i = index;"> {{element.category}} + </td> + </ng-container> + + <!-- Application Name Column --> + <ng-container matColumnDef="onapFunctions"> + <th id="col2" mat-header-cell *matHeaderCellDef> ONAP Functions </th> + <td id="rowheader_t1_{{i}}-onapFunctions" + mat-cell *matCellDef="let element; let i=index;"> {{element.functions}} </td> + </ng-container> + + <!-- Application Name Column --> + <ng-container matColumnDef="onapApplications"> + <th id="col2" mat-header-cell *matHeaderCellDef> ONAP Applications </th> + <td id="rowheader_t1_{{i}}-onapApplications" + mat-cell *matCellDef="let element; let i=index;"> + <a href="javascript:void(0)" (click)="showApplicationInfo(element.app_Id)"> + {{element.app_Name}} + </a> + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> + </table> + </div> + </div><br /> + + <!-- Display applicationInfo panels --> + <div id="allAppInfo"> + <div class="w-onap-main-view-title"> All Applications</div> <br /> + <div class="contactUs-general-div"> + <div class="contact-us-table"> + <div class="c-onap-portal-abs-table default contactUs-table-ht"> + <div *ngFor="let rowData of appTable; index as i" class="contactUs-collapsible-panel"> + <div id="{{rowData.app_Id}}" class="contactUs-table-ht" class="contactUs-panel-header" (click)="showApplicationInfo(rowData.app_Id)"> + <span id="appId{{i}}" class="contactUs-table-spn">{{rowData.app_name}}</span> + <span class="contactUs-table-title-sp"> + <span id="arrowup{{rowData.app_Id}}" [hidden]="showUp" title="Collapse app contact section" class="icon-controls-upPRIMARY"> + <i class="fa fa-2x fa-angle-up" aria-hidden="true"></i> + </span> + + <span id="arrowdown{{rowData.app_Id}}" [hidden]="showDown" title="Expand app contact section" class="icon-controls-down"> + <i class="fa fa-2x fa-angle-down" aria-hidden="true"></i> + </span> + </span> + </div> + <div id="collapse{{rowData.app_Id}}" [hidden]="true" class="contactUs-collapsible-panel"> + <div class="contactUs-txt" [hidden]="rowData.contact_name || rowData.contact_email || rowData.url_Info || rowData.desc"> + <span class="contactUs-collapsible-panel-spn"> + No application information is available. Please use the links above to contact the ONAP Portal team. + </span> + </div> + <div class="contactUs-txt" *ngIf="rowData.contact_name || rowData.contact_email || rowData.url_Info || rowData.desc"> + <table class="contactUs-main-table"> + <tr> + <td class="contactUs-panel-labels contactUs-main-panel-labels">Contact:</td> + <td class="contactUs-panel-labels contactUs-main-panel-value">{{rowData.contact_name}}</td> + </tr> + <tr> + <td class="contactUs-panel-labels">Email:</td> + <td class="contactUs-panel-labels"><a href="mailto:{{rowData.contact_email}}" target="_top">{{rowData.contact_email}}</a></td> + </tr> + <tr> + <td class="contactUs-panel-labels">Info URL:</td> + <td class="contactUs-panel-labels"><a href="{{rowData.url_Info}}" target="_blank">{{rowData.url_Info}}</a></td> + </tr> + <tr> + <td class="contactUs-panel-labels">Description:</td> + <td class="contactUs-panel-labels">{{rowData.desc}}</td> + </tr> + <tr> + <td class="contactUs-panel-labels">Get access:</td> + <td class="contactUs-panel-labels"><a href="javascript:void(0)" (click)="goGetAccess(rowData.app_name);">Click for application and role information</a></td> + </tr> + </table> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/contact-us/contact-us.component.scss b/portal-FE-common/src/app/pages/contact-us/contact-us.component.scss new file mode 100644 index 00000000..5c7a74eb --- /dev/null +++ b/portal-FE-common/src/app/pages/contact-us/contact-us.component.scss @@ -0,0 +1,74 @@ + +/*- + * ============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============================================ + * + * + */ +.w-onap-contactUs-home .contactUs-txt { + font-weight: 400; + color: #666; + font-size: 15px; + font-family: Omnes-ECOMP-W02-Medium,Arial; + margin: 10px; +} + +.w-onap-contactUs-home .contactUs-home-container .contactUs-general-div { + margin: auto!important; + padding-top: 15px; + padding-bottom: 15px; + margin-top: 0; + margin-right: 0; + margin-left: 0; + margin-bottom: 0; + width: 1170px; +} + +.w-onap-contactUs-home .w-onap-main-view-title { + font-family: Omnes-ECOMP-W02,Arial; + font-size: 20px; +} + +button#edit-button-contact-us { + float: right; +} + +.container th{ + padding-bottom: 15px; + font-weight: bold; +} + +.w-onap-contactUs-home .contactUs-collapsible-panel { + margin: auto; +} diff --git a/portal-FE-common/src/app/pages/contact-us/contact-us.component.spec.ts b/portal-FE-common/src/app/pages/contact-us/contact-us.component.spec.ts new file mode 100644 index 00000000..db77e078 --- /dev/null +++ b/portal-FE-common/src/app/pages/contact-us/contact-us.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 { ContactUsComponent } from './contact-us.component'; + +describe('ContactUsComponent', () => { + let component: ContactUsComponent; + let fixture: ComponentFixture<ContactUsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ContactUsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ContactUsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/contact-us/contact-us.component.ts b/portal-FE-common/src/app/pages/contact-us/contact-us.component.ts new file mode 100644 index 00000000..ec5d44ef --- /dev/null +++ b/portal-FE-common/src/app/pages/contact-us/contact-us.component.ts @@ -0,0 +1,196 @@ +/*- + * ============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, Input } from '@angular/core'; +import { MatTableDataSource } from '@angular/material'; +import { MatSort, MatPaginator } from '@angular/material'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ContactUsService } from '../../shared/services/index'; +import { ContactUsManageComponent } from './contact-us-manage/contact-us-manage.component'; + +@Component({ + selector: 'app-contact-us', + templateUrl: './contact-us.component.html', + styleUrls: ['./contact-us.component.scss'] +}) +export class ContactUsComponent implements OnInit { + + contactUsList: Array<Object> = []; + appTable: Array<Object> = []; + functionalTableData: Array<Object> = []; + + ush_TicketInfoUrl: string; + portalInfo_Address: string; + feedback_Url: string; + showUp: boolean = true; + showDown: boolean = false; + + result: any; + isEditMode: boolean = false; + displayedColumns: string[] = ['category', 'onapFunctions','onapApplications']; + dataSource = new MatTableDataSource(this.functionalTableData); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + constructor(public contactUsService: ContactUsService, public ngbModal: NgbModal) { } + + ngOnInit() { + this.appTable=[]; + this.functionalTableData=[]; + + this.getContactUSPortalDetails(); + this.updateContactUsTable(); + this.getAppCategoryFunctions() + } + + getContactUSPortalDetails(){ + console.log("getContactUSPortalDetails called..."); + this.contactUsService.getContactUSPortalDetails() + .subscribe( _data => { + this.result = _data; + console.log("getContactUSPortalDetails Data :: ", _data); + if (this.result.response == null || this.result.response == 'undefined') { + console.log('ContactUsService::getContactUSPortalDetails Failed: Result or result.data is null'); + }else{ + var source = JSON.parse(this.result.response); + this.ush_TicketInfoUrl = source.ush_ticket_url; + this.portalInfo_Address = source.feedback_email_address; + this.feedback_Url = source.portal_info_url; + } + },error =>{ + console.log(error); + }); + } + + updateContactUsTable(){ + console.log("updateContactUsTable called..."); + this.contactUsService.getAppsAndContacts() + .subscribe( _data => { + this.result = _data; + console.log("getAppsAndContacts Data :: ", _data); + if (this.result.response == null || this.result.response == 'undefined') { + console.log('ContactUsService::getAppsAndContacts Failed: Result or result.data is null'); + }else{ + var tableData=[]; + var source = this.result.response; + for(var i=0; i<source.length; i++) { + var dataArr = source[i]; + if ( !dataArr.appName || dataArr.appId == 1) { + continue; + } + + var dataTemp={ + app_name: dataArr.appName, + contact_name: dataArr.contactName, + contact_email: dataArr.contactEmail, + desc: dataArr.description, + url_Info: dataArr.url, + app_Id: dataArr.appId, + } + + tableData.push(dataTemp); + } + + this.appTable=tableData; + } + },error =>{ + console.log(error); + }); + } + + getAppCategoryFunctions(){ + console.log("getAppCategoryFunctions called"); + this.contactUsService.getAppCategoryFunctions() + .subscribe( _data => { + this.result = _data; + console.log("getAppCategoryFunctions Data :: ", _data); + if (this.result.response == null || this.result.response == 'undefined') { + console.log('ContactUsService::getAppCategoryFunctions Failed: Result or result.data is null'); + }else{ + var tablefunctionalData=[]; + var source = this.result.response; + for(var i=0;i<source.length; i++) { + var datafunctionalArr = source[i]; + var datafuntionalTemp={ + category: datafunctionalArr.category, + app_Name: datafunctionalArr.application, + functions: datafunctionalArr.functions, + app_Id: datafunctionalArr.appId, + } + tablefunctionalData.push(datafuntionalTemp); + } + this.functionalTableData = tablefunctionalData; + this.populateTableData(this.functionalTableData); + } + },error =>{ + console.log(error); + }); + } + + populateTableData(functionalTableData: Array<Object>){ + this.dataSource = new MatTableDataSource(functionalTableData); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + } + + editContactUsModal() { + const modalRef = this.ngbModal.open(ContactUsManageComponent, { size: 'lg' }); + } + + showApplicationInfo(appId: any){ + console.log("AppId Contact US...",appId); + let appInfoDiv = document.getElementById('collapse'+appId); + let uparrowDiv = document.getElementById('arrowup'+appId); + let downarrowDiv = document.getElementById('arrowdown'+appId); + + if(!appInfoDiv.getAttribute('hidden')){ + appInfoDiv.setAttribute("hidden","true"); + uparrowDiv.setAttribute("hidden", "true"); + downarrowDiv.removeAttribute("hidden"); + }else if(appInfoDiv.getAttribute('hidden')){ + appInfoDiv.removeAttribute("hidden"); + uparrowDiv.removeAttribute("hidden"); + downarrowDiv.setAttribute("hidden","true"); + } + } + + goGetAccess(app_name: any){ + console.log("Get Access :: goGetAccess method implemetation is pending... appName : ",app_name); + } + +} diff --git a/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.html b/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.html new file mode 100644 index 00000000..3169fa01 --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.html @@ -0,0 +1,87 @@ +<!-- + ============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 style="height: 100%;"> + <div class="simulateGridHeader"> + <span class="simulateGridHeaderTitle">{{'Applications'}}<span class="dashboardSortHeader">{{'Sort by'}}:</span> + <div class="dashboard-dropdown" id="sort-by-dropdownid-{{selectedSortType.value}}"> + <mat-form-field> + <mat-select [(ngModel)]="this.selectedSortType.value"> + <mat-option *ngFor="let option of sortOptions" [value]="option.value" + (click)="sortTypeChanged(option.value)">{{option.title}}</mat-option> + </mat-select> + </mat-form-field> + </div> + </span> + + </div> + + <div style="height: 100%;"> + <gridster [options]="options" class="appCatalogue-boarder" > + + + <div *ngFor="let item of layout"> + <gridster-item [item]="item"> + <div class="gridster-box"> + <div class="gridster-box-header"> + + <mat-icon class="icon-content-gridguide">drag_handle</mat-icon> + <h3>{{ item.name | elipsis: 13}}</h3> + + + </div> + <div class="gridster-box-content" [ngStyle]="{'cursor':'pointer', + 'background-image': 'url('+(item.imageLink)+')', + 'order': item.order, + 'background-color':'white', + 'background-repeat': 'no-repeat', + 'background-size': '168px 118px', + 'height': '80%'}" (click)="openAddRoleModal(item)"> + </div> + + </div> + </gridster-item> + </div> + + + + + </gridster> + </div> + + +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.scss b/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.scss new file mode 100644 index 00000000..2ab9d6b0 --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.scss @@ -0,0 +1,130 @@ +/* + * ============LICENSE_START========================================== + * ONAP Portal SDK + * =================================================================== + * 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============================================ + * + * + */ +.simulateGridHeader{ + position: relative; + height: 50px !important; + border: 1px solid #d3d3d3; + border-bottom: 0; + background-color: #E5E5E5; + white-space: nowrap; + text-overflow: ellipsis; + z-index: 1; +} + +.simulateGridHeaderTitle{ + line-height: 44px; + margin-left: 26px; + font-family: "Omnes-ECOMP-W02", Arial; + font-size: 18px; + color: #444444; + float: left; +} + +.simulateGridHeaderHandle{ + cursor: move; + margin: 12px; + position: absolute; + top: 0; + left: 0; + border: 0; + vertical-align: middle; + -ms-interpolation-mode: bicubic; + display: block; +} + +/* apps gridsters */ +ul { + list-style: none; +} +.gridster-box { + height: 100%; + border: 1px solid #ccc; + background-color: #fff; + transition: transform 0.5s ease-out; +} +.gridster-box-header { + background-color: #fff; + padding: 0 0px 0 10px; + border-bottom: 1px solid #ccc; + position: relative; + height: 50px !important; +} +.gridster-box-header h3 { + margin-top: 15px; + display: inline-block; + font-size: 70%; + font-family: "Omnes-ECOMP-W02", Arial; +} + +.gridster-box-header i { +font-size: 22px; +} + +.gridster-box-content { + padding: 59px; +} +.gridster-box:hover{ + transform: scale(1.1); +} +.gridster-box-header-btns { + top: 15px; + right: 10px; + position: absolute; +} +.dashboardSortHeader{ + margin-top: -44px; + margin-left: 700px; + } + + .dashboard-dropdown + { + position: absolute; + margin-top: -50px; + left: 82%; + top: 42px; + } + .appCatalogue-boarder{ + background-color: #eee; + border: 1px dashed white; + height: 80%; + } + + .icon-content-gridguide{ + cursor:move; + font-size: 14px; + }
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.spec.ts b/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.spec.ts new file mode 100644 index 00000000..48c91804 --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.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 { DashboardApplicationCatalogComponent } from './dashboard-application-catalog.component'; + +describe('DashboardApplicationCatalogComponent', () => { + let component: DashboardApplicationCatalogComponent; + let fixture: ComponentFixture<DashboardApplicationCatalogComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardApplicationCatalogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DashboardApplicationCatalogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.ts b/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.ts new file mode 100644 index 00000000..baaa4c49 --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard-application-catalog/dashboard-application-catalog.component.ts @@ -0,0 +1,205 @@ +/*- + * ============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 { GridsterConfig, GridsterItem } from 'angular-gridster2'; +import { ApplicationCatalogService } from '../../shared/services/application-catalog/application-catalog.service'; +import { IApplicationCatalog } from '../../shared/model/application-catalog.model'; +import { IWidgetCatalog } from '../../shared/model/widget-catalog.model'; +import { environment } from 'src/environments/environment'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { CatalogModalComponent } from '../catalog-modal/catalog-modal.component'; +import { ExternalRequestAccessService } from 'src/app/shared/services/external-request-access-service/external-request-access.service'; +import { UsersService } from 'src/app/shared/services/users/users.service'; +import { AddTabFunctionService } from 'src/app/shared/services/tab/add-tab-function.service'; + +@Component({ + selector: 'app-dashboard-application-catalog', + templateUrl: './dashboard-application-catalog.component.html', + styleUrls: ['./dashboard-application-catalog.component.scss'] +}) +export class DashboardApplicationCatalogComponent implements OnInit { + + widgetCatalogData: IWidgetCatalog[]; + appCatalogData: IApplicationCatalog[]; + resultAccessValue: string; + orgUserId: string; + firstName: string; + lastName: string; + selectedSortType: any; + sortOptions: Array<any>; + + get options(): GridsterConfig { + return this.applicationCatalogService.options; + } get layout(): GridsterItem[] { + return this.applicationCatalogService.layout; + } constructor(private applicationCatalogService: ApplicationCatalogService, private externalRequestAccessService: ExternalRequestAccessService, private userService: UsersService,private addTabFuntionService: AddTabFunctionService) { + this.sortOptions = [{ + index: 0, + value: 'N', + title: 'Name' + }, + { + index: 1, + value: 'L', + title: 'Last used' + }, + { + index: 2, + value: 'F', + title: 'Most used' + }, + { + index: 3, + value: 'M', + title: 'Manual' + } + ]; + this.selectedSortType = {}; + } + + ngOnInit() { + this.applicationCatalogService.clearCatalog(); + //this.selectedSortType = this.sortOptions[0]; + this.getUserAppsSortTypePreference(); + // + // //this.getUserAppsSortTypePreference(); + //this.getAppCatalogService('N'); + } + + getUserAppsSortTypePreference() { + this.applicationCatalogService.getUserAppsSortTypePreference().subscribe(data => { + //console.log("getUserAppsSortTypePreference data"+data); + if (data) { + var resJson: any = {}; + resJson.value = data; + if (resJson.value === "N" || resJson.value === "") { + resJson.index = 0; + + } else if (resJson.value === "L") { + resJson.index = 1; + + } else if (resJson.value === "F") { + resJson.index = 2; + + } else { + resJson.index = 3; + + } + + this.selectedSortType = this.sortOptions[resJson.index]; + //console.log(this.selectedSortType); + this.getAppCatalogService(data); + } + }, error => { + console.log('getUserAppsSortTypePreference Error Object' + error.message); + }); + + } + + sortTypeChanged(userAppSortTypePref: string) { + //console.log("check whether get into the method"); + if (!userAppSortTypePref) { + this.selectedSortType = this.sortOptions[0]; + } + else { + this.sortOptions.forEach(obj => { + if (obj.value == userAppSortTypePref) { + this.selectedSortType = obj; + } + }) + + } + + this.getAppCatalogService(userAppSortTypePref); + this.saveAppsSortTypePreference(this.selectedSortType); + + } + + getAppCatalogService(userAppSortTypePref: string) { + //console.log("getAppCatalogServices called"); + this.applicationCatalogService.getAppsOrderBySortPref(userAppSortTypePref).subscribe(data => { + //console.log("Response data" + data); + this.appCatalogData = data; + if (data) { + this.applicationCatalogService.layout = []; + for (let entry of data) { + //console.log("Check the URL" + environment.api.appThumbnail); + var appCatalog = { + x: -1, + y: -1, + id: entry.id, + name: entry.name, + subHeaderText: entry.notes, + imageLink: environment.api.appThumbnail.replace(':appId', <string><any>entry.id), + restrictedApp: entry.restrictedApp, + select: entry.select, + access: entry.access, + pending: entry.pending, + order: entry.order, + url: entry.url, + appid: entry.id + }; + this.applicationCatalogService.addItem(appCatalog); + } + } + }, error => { + console.log('getAppCatalogServices Error Object' + error); + }); + }; + + saveAppsSortTypePreference(selectedSortType: any) { + this.applicationCatalogService.saveAppsSortTypePreference(selectedSortType).subscribe(); + + } + + openAddRoleModal(item: any) { + //console.log("OpenModal check" + item.id+" "+item.url); + if (item.restrictedApp) { + // Link-based apps open in their own browser tab + window.open(item.url, '_blank'); + } else{ + var tabContent = { + id: new Date(), + title: item.name, + url: item.url, + appId: item.appid + }; + this.addTabFuntionService.filter(tabContent); + } + } +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.html b/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.html new file mode 100644 index 00000000..9d3ffc7f --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.html @@ -0,0 +1,77 @@ +<!-- + ============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 style="height: 100%;"> + + <div class="simulateGridHeader"> + <span class="simulateGridHeaderTitle">{{'Widgets'}} + </span> + + </div> + + + <div style="height: 100%"> + <gridster [options]="options" class="appCatalogue-boarder"> + + + <div *ngFor="let item of layout"> + <gridster-item [item]="item"> + <div class="gridster-box"> + <div class="gridster-box-header"> + + <mat-icon class="icon-content-gridguide">drag_handle</mat-icon> + <h3>{{ item.name | elipsis: 13}}</h3> + + + + </div> + <div class="gridster-box-content"> + <app-dynamic-widget [widgetType]="item.name"></app-dynamic-widget> + </div> + + </div> + </gridster-item> + </div> + + + + </gridster> + </div> + + + +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.scss b/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.scss new file mode 100644 index 00000000..553f8b56 --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.scss @@ -0,0 +1,139 @@ +/* + * ============LICENSE_START========================================== + * ONAP Portal SDK + * =================================================================== + * 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============================================ + * + * + */ + + + + :host { .toolbar { + height: 100px; + display: flex; + } gridster { + display: flex; + height: calc(50vh - 115px); + flex-direction: column; + }} + .icon-content-gridguide{ + cursor:move; + font-size: 22px; + } + .form-row { + margin-top: -14px; + } + .griditem-header{ + + + border-bottom: 1px; + border-bottom-color: solid gray; + + + + } + +.checkbox, .radio { + min-height: 10px; + padding-left: 0px; +} + +.appCatalogue-boarder{ + background-color: #eee; + border: 1px dashed white; + height: 100%; + + overflow-y: auto; + overflow-x: hidden; + } + + .gridster-box { + height: 100%; + border: 1px solid #ccc; + background-color: #fff; + transition: transform 0.5s ease-out; +} +.gridster-box-header { + background-color: #fff; + padding: 0 0px 0 10px; + border-bottom: 1px solid #ccc; + position: relative; + height: 50px !important; +} +.gridster-box-header h3 { + margin-top: 15px; + display: inline-block; + font-size: 70%; + font-family: "Omnes-ECOMP-W02", Arial; +} +.gridster-box-content { + //padding: 59px; + overflow-y: scroll; + height: 100%; + +} + +.gridster-box-header-btns { + top: 15px; + right: 10px; + position: absolute; +} +.checkbox input{ + margin: 6px; + left: 112px; + top: -20px; + } +.checkbox .skin { + left: 125px; + top: -18px; +} + +.simulateGridHeader{ + position: relative; + height: 50px !important; + border: 1px solid #d3d3d3; + border-bottom: 0; + background-color: #E5E5E5; + white-space: nowrap; + text-overflow: ellipsis; + z-index: 1; +} + +.simulateGridHeaderTitle{ + line-height: 44px; + margin-left: 26px; + font-family: "Omnes-ECOMP-W02", Arial; + font-size: 18px; + color: #444444; + float: left; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.spec.ts b/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.spec.ts new file mode 100644 index 00000000..7ae5f770 --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.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 { DashboardWidgetCatalogComponent } from './dashboard-widget-catalog.component'; + +describe('DashboardWidgetCatalogComponent', () => { + let component: DashboardWidgetCatalogComponent; + let fixture: ComponentFixture<DashboardWidgetCatalogComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardWidgetCatalogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DashboardWidgetCatalogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.ts b/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.ts new file mode 100644 index 00000000..dbdd2928 --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard-widget-catalog/dashboard-widget-catalog.component.ts @@ -0,0 +1,126 @@ +/*- + * ============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 { GridsterConfig, GridsterItem } from 'angular-gridster2'; +import { WidgetCatalogService } from '../../shared/services/widget-catalog/widget-catalog.service'; +import { IWidgetCatalog } from '../../shared/model/widget-catalog.model'; +import { Observable } from 'rxjs'; +import { inflateRaw } from 'zlib'; +import { UsersService } from 'src/app/shared/services/users/users.service'; + +@Component({ + selector: 'app-dashboard-widget-catalog', + templateUrl: './dashboard-widget-catalog.component.html', + styleUrls: ['./dashboard-widget-catalog.component.scss'] +}) +export class DashboardWidgetCatalogComponent implements OnInit { + + + widgetCatalogData: IWidgetCatalog[]; + isCommonError: boolean = false; + isApiRunning: boolean = true; + userName: string; + + + get options(): GridsterConfig { + return this.widgetCatalogService.options; + } get layout(): GridsterItem[] { + return this.widgetCatalogService.layout; + } constructor(private widgetCatalogService: WidgetCatalogService, private userService: UsersService) { + + } + + ngOnInit() { + const widgetCatalogObservable = this.widgetCatalogService.getWidgetCatalog(); + this.widgetCatalogService.layout = []; + this.getUserWidgets(this.userName); + } + + + getUserWidgets(loginName: string) { + const widgetCatalogUserObservable = this.userService.getUserProfile(); + widgetCatalogUserObservable.subscribe((userProfile: any) => { + //console.log('UserProfile is ' + userProfile); + if (userProfile) { + const widgetCatalogObservable = this.widgetCatalogService.getUserWidgets(userProfile.orgUserId); + widgetCatalogObservable.subscribe(data => { + //console.log("What is coming from backend" + JSON.parse(data)); + this.widgetCatalogData = data; + console.log(this.widgetCatalogData); + for (let entry of this.widgetCatalogData) { + if (entry[1] == 'Events' || entry[1] == 'News' || entry[1] == 'Resources') { + if(entry[4] === 'S' || entry[4] === null) + { + var appCatalog = { + id: entry[0], + name: entry[1], + headerName: entry[2], + //select: (entry[4] === 'S' || entry[4] === null) ? true : false + }; + this.widgetCatalogService.addItem(appCatalog); + } + } + } + }); + } + }); + + } + + getUserProfile(): Observable<any> { + const widgetCatalogObservable = this.userService.getUserProfile(); + return widgetCatalogObservable; + } + + storeSelection(widgetCatalogData: any) { + console.log("Store selection called " + widgetCatalogData.select); + if (widgetCatalogData && widgetCatalogData.select) { + var appData = { + widgetId: widgetCatalogData.id, + select: widgetCatalogData.select, + pending:false + }; + this.widgetCatalogService.updateWidgetCatalog(appData).subscribe(data => { + //console.log("Update App sort data" + data); + }, error => { + console.log('updateWidgetCatalog error' + error); + }); + } + + } +} diff --git a/portal-FE-common/src/app/pages/dashboard/dashboard.component.html b/portal-FE-common/src/app/pages/dashboard/dashboard.component.html new file mode 100644 index 00000000..c5c21efb --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard/dashboard.component.html @@ -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============================================ + + + --> +<div> + + <div style="height:calc(50vh );"> + <app-dashboard-application-catalog></app-dashboard-application-catalog> + </div> + <div style="height: calc(50vh );"> + <app-dashboard-widget-catalog></app-dashboard-widget-catalog> + </div> + + + <!-- + <div class="gridster-container override_background" style="height: calc(30vh - 20px );"> + + <div id="dashboardAddWidgetPreference" [ngStyle]="{'cursor':'pointer'}" + (click)="dashboard.goToWidgetCatLog()"> + <i class="icon-controls-add-maximize"></i>{{'Add Widget'}} + </div> + + <div id="dashboardDefaultPreference" [ngStyle]="{'cursor':'pointer'}" (click)="restoreSortSelected()"> + <i class="icon-arrows-replay-restart"></i> {{'Reset Widget Layout'}} + </div> + </div> + --> + +</div> diff --git a/portal-FE-common/src/app/pages/dashboard/dashboard.component.scss b/portal-FE-common/src/app/pages/dashboard/dashboard.component.scss new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard/dashboard.component.scss diff --git a/portal-FE-common/src/app/pages/dashboard/dashboard.component.spec.ts b/portal-FE-common/src/app/pages/dashboard/dashboard.component.spec.ts new file mode 100644 index 00000000..9c996c37 --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard/dashboard.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DashboardComponent } from './dashboard.component'; + +describe('DashboardComponent', () => { + let component: DashboardComponent; + let fixture: ComponentFixture<DashboardComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/dashboard/dashboard.component.ts b/portal-FE-common/src/app/pages/dashboard/dashboard.component.ts new file mode 100644 index 00000000..6f25e745 --- /dev/null +++ b/portal-FE-common/src/app/pages/dashboard/dashboard.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; +import { UserProfileService } from 'src/app/shared/services'; +import { PluginComponent} from 'src/app/shared/plugin/plugin.component'; + +@Component({ + selector: 'app-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.scss'] +}) +export class DashboardComponent implements OnInit { + + + constructor(private userProfile: UserProfileService) { } + + ngOnInit() { + // remove this after portal upgrade is done + this.userProfile.getFunctionalMenuStaticInfo(); + } + + + +} diff --git a/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.html b/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.html new file mode 100644 index 00000000..5ea72090 --- /dev/null +++ b/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.html @@ -0,0 +1,38 @@ +<!-- + ============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 #vc style="overflow-y: scroll;"></div> diff --git a/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.scss b/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.scss new file mode 100644 index 00000000..570475d7 --- /dev/null +++ b/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.scss @@ -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============================================ + * + * + */ + + + +:host { .toolbar { + height: 100px; + display: flex; + } gridster { + display: flex; + height: calc(100vh - 115px); + flex-direction: column; + }} + .icon-content-gridguide{ + cursor:move; + font-size: 22px; + } + .form-row { + margin-top: -14px; + } + .griditem-header{ + + + border-bottom: 1px; + border-bottom-color: solid gray; + + + + } + +.checkbox, .radio { + min-height: 10px; + padding-left: 0px; +} + +.appCatalogue-boarder{ + background-color: #eee; + border: 1px dashed white; + height: 100%; + + overflow-y: auto; + overflow-x: hidden; + } + + .gridster-box { + height: 100%; + border: 1px solid #ccc; + background-color: #fff; + transition: transform 0.5s ease-out; +} +.gridster-box-header { + background-color: #fff; + padding: 0 0px 0 10px; + border-bottom: 1px solid #ccc; + position: relative; + height: 50px !important; +} +.gridster-box-header h3 { + margin-top: 15px; + display: inline-block; + font-size: 70%; + font-family: "Omnes-ECOMP-W02", Arial; +} +.gridster-box-content { + //padding: 59px; + overflow-y: scroll; + height: 100%; + +} +.gridster-box-content:hover{ + //transform: scale(1.1); +} +.gridster-box-header-btns { + top: 15px; + right: 10px; + position: absolute; +} +.checkbox input{ + margin: 6px; + left: 112px; + top: -20px; + } +.checkbox .skin { + left: 125px; + top: -18px; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.spec.ts b/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.spec.ts new file mode 100644 index 00000000..bae69386 --- /dev/null +++ b/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.spec.ts @@ -0,0 +1,38 @@ +/*- + * ============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============================================ + * + * + */ + diff --git a/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.ts b/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.ts new file mode 100644 index 00000000..21aab3a7 --- /dev/null +++ b/portal-FE-common/src/app/pages/dynamic-widget/dynamic-widget.component.ts @@ -0,0 +1,80 @@ +/*- + * ============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 { Compiler, Component, Injector, NgModule, NgModuleRef, ViewChild, ViewContainerRef, OnInit, Input } from '@angular/core'; +import { PluginLoaderService } from 'src/app/shared/plugin/plugin-loader/plugin-loader.service'; + +@Component({ + selector: 'app-dynamic-widget', + templateUrl: './dynamic-widget.component.html', + styleUrls: ['./dynamic-widget.component.scss'] +}) +export class DynamicWidgetComponent implements OnInit { + + @ViewChild('vc', { read: ViewContainerRef }) _container: ViewContainerRef; + @Input() widgetType:string; + + constructor(private injector: Injector, + private pluginLoader: PluginLoaderService) { + } + + ngOnInit() { + console.log('this.widgetType'+ this.widgetType); + this.loadPlugin(this.widgetType); + } + + + loadPlugin(pluginName: string) { + + setTimeout(() => { + this.executePlugin(pluginName); + + }, 2000); + + } + + executePlugin(pluginName: string) { + this.pluginLoader.load(pluginName).then(moduleFactory => { + const moduleRef = moduleFactory.create(this.injector); + const entryComponent = (moduleFactory.moduleType as any).entry; + const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory( + entryComponent + ); + this._container.createComponent(compFactory); + }); + } +} diff --git a/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.html b/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.html new file mode 100644 index 00000000..5e10b78f --- /dev/null +++ b/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.html @@ -0,0 +1,100 @@ +<!-- + ============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"> + <div class="functionalMenu-details-modal"> + <!--Modal Headers--> + <div class="modal-header"> + <h4 class="modal-title">Functional Menu</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="parent"> + <div class="item-label">Parent</div> + <input id="input-parent" class="functionalMenu-height" + [(ngModel)]="nodedetails.menuLocation" [disabled]="isParentDisable" + type="text" name="parent" readonly="readonly" /> + </div> + <div class="title" > + <div class="item-label">Title</div> + <input id="input-title" placeholder="Enter text" class="functionalMenu-height" + [(ngModel)]="nodedetails.name" type="text" autocomplete="off" name="title" maxlength="100" [disabled]="isViewMode"/> + </div> + <div class="url" > + <div class="item-label">URL</div> + <input id="input-title" placeholder="http://" class="functionalMenu-height" + [(ngModel)]="nodedetails.url" type="text" autocomplete="off" name="url" maxlength="100" [disabled]="isViewMode"/> + </div> + <div class="application-select"> + <mat-form-field> + <mat-label>App</mat-label> + <mat-select name="functional-menu-application-select" [disabled]="isViewMode" + [(ngModel)]="nodedetails.selectedAppIndex" + (ngModelChange)="updateSelectedApp(nodedetails.selectedAppIndex)" + [(value)]="selectedApp" + > + <mat-option *ngFor="let d of availableApps" [value]="d.index" >{{d.title}}</mat-option> + </mat-select> + </mat-form-field> + </div> + <div class="role-select" [hidden]="hideRoleField"> + <mat-form-field> + <mat-label>Role</mat-label> + <mat-select name="functional-menu-role-select" [disabled]="isViewMode" + [(ngModel)]="nodedetails.selectedRole" [(value)]="selectedRole" + > + <mat-option *ngFor="let d of availableRoles" [value]="d.roleId" >{{d.rolename}}</mat-option> + </mat-select> + </mat-form-field> + </div> + </div> + + <!--Modal Footer goes Here--> + <div class="modal-footer"> + <button type="button" class="btn btn-primary" (click)="switchToAddMode()">Add</button> + <button type="button" class="btn btn-primary" (click)="switchToEditMode()">Edit</button> + <button type="button" class="btn btn-primary" *ngIf="(isEditMode)" (click)="saveChanges()">Save</button> + <button type="button" class="btn btn-primary" (click)="deleteMenuItem()">Delete</button> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Cancel</button> + </div> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.scss b/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.scss new file mode 100644 index 00000000..ba9a1e37 --- /dev/null +++ b/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.scss @@ -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============================================ + * + * + */ + +::ng-deep .modal-dialog { + max-width: 550px; + width: 550px; + overflow-x: auto; + overflow-y: auto; +} + +::ng-deep .mat-form-field-infix { + display: block; + position: relative; + flex: auto; + min-width: 0; + width: 448px !important; +} + +.container .functionalMenu-details-modal { + padding: 16px; + height: 537px; + overflow: auto; +} + +.functionalMenu-details-modal input[type="text"] { + width: 28em; + margin-bottom: 10px; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.spec.ts b/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.spec.ts new file mode 100644 index 00000000..de79b9d8 --- /dev/null +++ b/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.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 { FunctionalMenuDialogComponent } from './functional-menu-dialog.component'; + +describe('FunctionalMenuDialogComponent', () => { + let component: FunctionalMenuDialogComponent; + let fixture: ComponentFixture<FunctionalMenuDialogComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FunctionalMenuDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FunctionalMenuDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.ts b/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.ts new file mode 100644 index 00000000..a1839732 --- /dev/null +++ b/portal-FE-common/src/app/pages/functional-menu/functional-menu-dialog/functional-menu-dialog.component.ts @@ -0,0 +1,446 @@ +/*- + * ============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 { FunctionalMenuService } from 'src/app/shared/services'; +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-functional-menu-dialog', + templateUrl: './functional-menu-dialog.component.html', + styleUrls: ['./functional-menu-dialog.component.scss'] +}) +export class FunctionalMenuDialogComponent implements OnInit { + + @Input() nodedata: any; + @Input() operationName: string; + @Output() passEntry: EventEmitter<any> = new EventEmitter(); + isEditMode: boolean = false; + isViewMode: boolean = false; + isAddItemMode: boolean = false; + selectedItem: any; + result: any; + availableRoles: any; + preSelectedRoles: any; + isAllApplications: boolean = false; + availableApps: any + selectedRole: any = []; + selectedApp={ + index:null, + isDisabled: null + }; + selectedAppIndex: any; + menutitle:string; + menuLocation:string; + nodedetails: any; + isParentDisable:boolean =true; + hideRoleField:boolean = true; + conflictMessages = {}; + functionalMenuForm = {}; + + constructor(public functionalMenuService : FunctionalMenuService, public ngbModal: NgbModal, public activeModal: NgbActiveModal) { } + + ngOnInit() { + //console.log("nodedata in dialog ",this.nodedata); + this.nodedetails = Object.assign({}, this.nodedata); + this.isViewMode = true; + this.selectedItem = this.nodedata; + this.selectedRole = []; + this.availableRoles = []; + if(this.nodedata && (this.isViewMode || this.isEditMode) && this.isLeafMenuItem(this.nodedetails)){ + this.selectedApp.index = this.nodedetails.appid; + this.selectedAppIndex=this.nodedetails.appid; + this.getAvailableRoles(this.selectedAppIndex); + } + + if(this.isViewMode || this.isEditMode){ + this.nodedetails.menutitle = this.nodedetails.name; + this.nodedetails.menuLocation = this.isParentMenuItem(this.nodedata) ? this.nodedata.name : this.nodedata.parent.name; + }else{ + this.nodedetails.menutitle = ''; + this.nodedetails.menuLocation = this.nodedata.name; + } + this.nodedetails.selectedAppIndex = (this.selectedItem.appid) ? this.selectedItem.appid : 0; + this.getAvailableApps(); + if(this.selectedItem.appid && this.selectedItem.appid >0){ + this.getAvailableRoles(this.selectedItem.appid); + } + } + + switchToEditMode(){ + //console.log("switchToEditMode :: ",this.nodedata); + this.isViewMode = false; + this.isEditMode = true; + this.isParentDisable =true; + this.nodedetails.name = this.selectedItem.name; + this.nodedetails.url = this.selectedItem.url; + this.nodedetails.selectedAppIndex = (this.selectedItem.appid) ? this.selectedItem.appid : 0; + this.nodedetails.selectedRole = (this.selectedItem.roles) ? this.selectedItem.roles : 0; + } + + switchToAddMode(){ + //console.log("switchToAddMode :: ",this.nodedata); + if(this.selectedItem != null && this.selectedItem.getLevel() >= 4){ + this.openConfirmationModal("","You are not allowed to have a menu item at a level greater than 4."); + return ; + } + //this.isViewMode = false; + this.isViewMode = false; + this.isEditMode = true; + this.isAddItemMode = true; + this.nodedetails.name = ""; + this.nodedetails.url = ""; + this.nodedetails.selectedAppIndex = 0; + this.nodedetails.selectedRole = 0; + } + + /** + * deleteMenuItem + * @param selectedItem + */ + deleteMenuItem(){ + if(this.selectedItem.children!=null && this.selectedItem.children.length>0){ + const modalRef = this.ngbModal.open(ConfirmationModalComponent); + modalRef.componentInstance.title = ""; + modalRef.componentInstance.message = 'You are not allowed to delete a menu item that has children. You can only delete leaf menu items.'; + modalRef.result.then((result) => { }, (resut) => {return;}); + }else{ + const modalRef = this.ngbModal.open(InformationModalComponent); + modalRef.componentInstance.title = "Confirmation"; + modalRef.componentInstance.message = 'Are you sure you want to delete '+ this.selectedItem.name+' ?'; + modalRef.result.then((result) => { + if (result === 'Ok') { + this.functionalMenuService.deleteMenuItem(this.selectedItem.menuId) + .subscribe(_data => { + this.result = _data + this.passEntry.emit(this.result); + let successMsg = "Item Deleted Successfully"; + this.openConfirmationModal("Success",successMsg); + this.ngbModal.dismissAll(); + }, error =>{ + console.log(error); + let deleteErrorMsg = 'There was an error while deleting the item.'+error.message; + this.openConfirmationModal("Error",deleteErrorMsg); + return; + }); + } + }, (resut) => { + + }) + } + } + + updateSelectedApp(appid){ + //console.log("updateSelectedApp called with appId :: ",appid); + if (!appid) { + return; + } + this.getAvailableRoles(appid); + } + + getAvailableRoles(appid){ + //console.log("getAvailableRoles called with appId :: ",appid); + if (appid != null && appid >0) { + this.functionalMenuService.getManagedRolesMenu(appid) + .subscribe(rolesObj => { + this.availableRoles = rolesObj; + if(this.availableRoles && this.availableRoles.length >0){ + this.hideRoleField = false; + } + this.preSelectedRoles = {roles:[]}; + this.preSelectedRoles = {roles:[]}; + + if((this.isEditMode) && this.isMidLevelMenuItem(this.nodedata)){ + // in Edit flow , for Midlevel menu item no need to preSelect. + this.preSelectedRoles = {roles:[]}; + }else if(this.nodedata && this.isEditMode && this.isLeafMenuItem(this.nodedata) && this.nodedata.appid!=appid) { + // in Edit flow , for LeafMenuItem, if appid changed then no need to preSelect. + this.preSelectedRoles = {roles:[]}; + }else{ + if(this.nodedata && this.nodedata.roles){ + for(var i=0; i< this.nodedata.roles.length; i++){ + var role = {"roleId": this.nodedata.roles[i]}; + this.preSelectedRoles.roles.push(role); + } + } + } + + if(this.nodedata.rolesObj){ + for(var i=0; i< this.nodedata.rolesObj.length;i++){ + //this.availableRoles[i].isApplied = false; + for(var j=0;j<this.preSelectedRoles.roles.length;j++){ + if(this.preSelectedRoles.roles[j].roleId==this.availableRoles[i].roleId){ + this.availableRoles[i].isApplied=true; + this.nodedetails.selectedRole = (this.preSelectedRoles.roles[j]) ? this.preSelectedRoles.roles[j] : 0; + break; + } + } + } + } + }, error =>{ + console.log(error); + let errorMsg = 'There was an error while gettting available roles. ' + error.message; + this.openConfirmationModal("",errorMsg); + return; + }); + //console.log("this.availableRoles >>>>>",this.availableRoles); + }else{ + console.log("FunctionalMenuDialogComponent::getAvailableRoles: appid was null or -1"); + } + } + + getAvailableApps(){ + this.isAllApplications = true; + this.functionalMenuService.getAvailableApplications() + .subscribe(apps => { + this.availableApps = apps; + if (this.nodedetails && this.nodedetails.index) { + for (var i = 0; i < this.availableApps.length; i++) { + if (apps[i].index === this.nodedetails.index) { + //console.log("MenuDetailsModalCtrl::getAvailableApps: found app with index: " + this.nodedetails.index); + //console.log("MenuDetailsModalCtrl::getAvailableApps: setting isDisabled to: " + !apps[i].enabled); + this.nodedetails.isDisabled = !apps[i].enabled; + break; + } + } + //console.log("didn't find index: " + this.nodedetails.index); + } + }, error =>{ + console.log(error); + this.isAllApplications = false; + let errorMsg = 'There was a problem retrieving the Applications. '+error.message; + this.openConfirmationModal("Error",errorMsg); + }); + } + + isLeafMenuItem(menu: any){ + return menu.children.length>0 ? false : true; + } + + isMidLevelMenuItem(menu: any){ + return menu.parentMenuId!=null && menu.children.length>0 ? true : false; + } + + isParentMenuItem(menu: any){ + return menu.parentMenuId!=null ? false : true; + }; + + isRoleSelected(){ + var selectedRoleIds=[]; + for(var i=0;i<this.availableRoles.length;i++){ + if(this.availableRoles[i].isApplied){ + selectedRoleIds.push(this.availableRoles[i].roleId); + return true; + } + } + return false; + } + + getDialogTitle = (source) => { + switch (source) { + case 'edit': + return "Functional Menu - Edit"; + case 'view': + return "Functional Menu - View"; + case 'add': + return "Functional Menu - Add"; + default: + return "Functional Menu"; + }; + } + + saveChanges(){ + if(!this.nodedetails.menuLocation || !this.nodedetails.name + || !this.nodedetails.url || !this.nodedetails.selectedAppIndex + || !this.nodedetails.selectedAppIndex || !this.nodedetails.selectedRole){ + this.openConfirmationModal("","All fields are mandatory, please provide inputs for all the fields."); + return; + } + /* + if(!!this.nodedetails.url && (!this.selectedApp || this.selectedAppIndex <=0)) { + this.openConfirmationModal("","Please select the appropriate app, or remove the url"); + return; + }else if(!this.nodedetails.url && (this.selectedApp) && this.selectedApp.index>0){ + this.openConfirmationModal("","Please enter url, or select No Application"); + return; + }else if(!this.nodedetails.menutitle){ + this.openConfirmationModal("","Please enter the Menu title"); + return; + } + */ + + if(this.isAddItemMode){ + if(this.selectedItem != null && this.selectedItem.getLevel() >= 4){ + this.openConfirmationModal("","You are not allowed to have a menu item at a level greater than 4."); + return ; + }else{ + let data = null; + let selectedMenuDetails = null; + this.functionalMenuService.getFunctionalMenu(this.selectedItem.menuId) + .subscribe(_data => { + selectedMenuDetails = _data + if((this.selectedItem.children===null || this.selectedItem.children.length == 0) && (!!selectedMenuDetails.url + || !!selectedMenuDetails.appid || !!selectedMenuDetails.roles)){ + let warning_message = 'Warning: the child menu item "' + + this.selectedItem.name + '" is already configured with an application. You can create a new mid-level menu item.'; + this.openConfirmationModal("",warning_message); + return; + }else{ + if(this.selectedItem){ + var selectedRoleIds=[]; + //console.log("Selected Role ID ; ",this.nodedetails.selectedRole); + if(this.nodedetails.selectedRole){ + selectedRoleIds.push(this.nodedetails.selectedRole); + } + + let applicationid: any = null; + if(!this.nodedata){ + applicationid = null; + }else{ + applicationid = this.nodedata.appid; + } + var newMenuItem = { + menuId:null, // this is a new menu item + column:this.nodedata.column, + text:this.nodedetails.name, + // We are creating this new menu item under the menu item that was clicked on. + parentMenuId:this.nodedata.menuId, + url:(this.nodedetails.url) ? this.nodedetails.url : null, + appid:(this.nodedetails.selectedAppIndex) ? this.nodedetails.selectedAppIndex: null, + roles:selectedRoleIds + }; + //console.log("Add menu Item newMenuItem :: ",newMenuItem) + this.functionalMenuService.saveMenuItem(newMenuItem) + .subscribe(_data => { + this.result = _data + //console.log("add menu item response :: ",_data); + this.passEntry.emit(this.result); + let successMsg = "Item added successfully"; + this.openConfirmationModal("Success",successMsg); + }, error =>{ + console.log(error); + if(error.status === 409){//Conflict + this.handleConflictErrors(error); + } else { + let errorMsg = "There was a problem saving your menu. Please try again later. Error Status: "+error.status + this.openConfirmationModal("",errorMsg); + return; + } + }); + } + } + }, error =>{ + //console.log(error); + let errorMsg = "There was a problem saving your menu. Please try again later. Error Status: "+error.status + this.openConfirmationModal("",errorMsg ); + return; + }); + } + }else{ + //edit mode.. + //console.log('MenuDetailsModalCtrl::saveChanges: Will be saving an edit menu item'); + var selectedRoleIds=[]; + //console.log("Selected Role ID ---->>>> ",this.nodedetails.selectedRole); + if(this.nodedetails.selectedRole){ + selectedRoleIds.push(this.nodedetails.selectedRole); + } + let activeMenuItem = { + menuId:this.nodedata.menuId, + column:this.nodedata.column, + text:this.nodedetails.name, + parentMenuId:this.nodedata.parentMenuId, + url:(this.nodedetails.url) ? this.nodedetails.url : null, + appid: (this.nodedetails.selectedAppIndex) ? this.nodedetails.selectedAppIndex: null, + roles:selectedRoleIds + }; + if ((activeMenuItem.appid==null && activeMenuItem.url=="") || (activeMenuItem.appid==null && activeMenuItem.url=="undefined")) { + activeMenuItem.roles = null; + } + //console.log("Update menu Item activeMenuItem :: ",activeMenuItem); + this.functionalMenuService.saveEditedMenuItem(activeMenuItem) + .subscribe(_data => { + this.result = _data + //console.log("Edit menu item response :: ",_data); + this.passEntry.emit(this.result); + let successMsg = "Item updated successfully"; + this.openConfirmationModal("Success",successMsg); + //this.ngbModal.dismissAll(); + }, error =>{ + //console.log(error); + let errorMsg = "There was a problem updating your menu. Please try again later. Error Status: "+error.status; + this.openConfirmationModal("",errorMsg); + return; + }); + } + } + + //This part handles conflict errors (409) + handleConflictErrors(error){ + if(!error.data){ + return; + } + if(!error.data.length){ //support objects + error.data = [error.data] + } + //console.log('MenuDetailsModalCtrl::handleConflictErrors: err.data = ' + JSON.stringify(error.data)); + if(error.data){ + error.data.forEach(item => { + //set conflict message + this.conflictMessages[item.field.name] = item.errorCode; + //set field as invalid + //console.log('MenuDetailsModalCtrl::handleConflictErrors: fieldName = ' + item.field.name); + this.functionalMenuForm[item.field.name].$setValidity('conflict', false); + }); + } + } + + 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/functional-menu/functional-menu.component.html b/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.html new file mode 100644 index 00000000..d898563b --- /dev/null +++ b/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.html @@ -0,0 +1,52 @@ +<!-- + ============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 id="title" class="w-onap-main-view-title"> + <h1 class="heading-page">Edit Functional Menu</h1> + </div> + <div id="jqTree"></div> + <div class="functional-admin-button-container"> + <button id="regenrate-functionalmenu-btn" + class="btn btn-alt btn-small" + (click)="regenerateFunctionalMenuAncestors()">Regenerate Menu + </button> + <div class="regenerate-functionalmenu-btn-txt"> + <span class="n16r">Click when you are done with your changes.</span> + </div> + </div> +</div> diff --git a/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.scss b/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.scss new file mode 100644 index 00000000..cb1c7422 --- /dev/null +++ b/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.scss @@ -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============================================ + * + * + */ + +.functional-menu-main { + background-color: #fff; + position: fixed; + top: 105px; + left: 0; + right: 0; + bottom: 75px; + padding-top: 10px; + overflow-y: scroll; + padding-left: 0; +} + +.functional-menu-main .functional-menu-container { + position: relative; + padding-right: 0; + padding-left: 0; + padding-bottom: 32px; + background-color: #fff; +} + +.w-onap-main-view-title { + color: #191919; + font-family: Omnes-ECOMP-W02-Medium,Arial; + font-size: 24px; + width: 1170px; + padding-bottom: 15px; + margin: auto; +} + +.functional-menu-main .functional-menu-container .tree { + margin: auto; + width: 1170px; + font-size: 16px; +} + +.functional-admin-button-container { + padding-top: 10px; + width: 1170px; + margin: auto; +} + +.btn-small { + padding: 10px 19px 9px 18px; + font-size: 1.5rem; + border-radius: 8px; +} + +.btn-alt { + border-color: #087ac2 transparent #0568ae; + background-color: #0568ae; + background: linear-gradient(to bottom, #087ac2 0%, #0568ae 100%); + color: #ffffff; +} + +.functional-menu-main .regenerate-functionalmenu-btn-txt { + color: #000; +} diff --git a/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.spec.ts b/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.spec.ts new file mode 100644 index 00000000..c5c562ed --- /dev/null +++ b/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FunctionalMenuComponent } from './functional-menu.component'; + +describe('FunctionalMenuComponent', () => { + let component: FunctionalMenuComponent; + let fixture: ComponentFixture<FunctionalMenuComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FunctionalMenuComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FunctionalMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.ts b/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.ts new file mode 100644 index 00000000..655b4cb4 --- /dev/null +++ b/portal-FE-common/src/app/pages/functional-menu/functional-menu.component.ts @@ -0,0 +1,192 @@ +/*- + * ============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 { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { FunctionalMenuService } from 'src/app/shared/services'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { FunctionalMenuDialogComponent } from './functional-menu-dialog/functional-menu-dialog.component'; + + +@Component({ + selector: 'app-functional-menu', + templateUrl: './functional-menu.component.html', + styleUrls: ['./functional-menu.component.scss'] +}) +export class FunctionalMenuComponent implements OnInit { + + result: any; + functionalMenu: any = []; + treedata = []; + isEditMode: boolean; + operationName: string; + self: any; + constructor(public functionalMenuService : FunctionalMenuService, public ngbModal: NgbModal) { } + + ngOnInit() { + this.self = this; + this.functionalMenu = []; + this.getFunctionalMenu(); + + } + + /** + * regenerateFunctionalMenuAncestors + */ + regenerateFunctionalMenuAncestors(){ + this.functionalMenuService.regenerateFunctionalMenuAncestors() + .subscribe(_data => { + this.result = _data + if(this.result){ + const modalRef = this.ngbModal.open(ConfirmationModalComponent); + modalRef.componentInstance.title = ""; + modalRef.componentInstance.message = 'You have successfully regenerated the menu.'; + modalRef.result.then((result) => { }, (resut) => {return;}); + this.getFunctionalMenu(); + } + }, error =>{ + console.log(error); + const modalRef = this.ngbModal.open(ConfirmationModalComponent); + modalRef.componentInstance.title = ""; + modalRef.componentInstance.message = 'There was an error while regenerating the menu.'; + modalRef.result.then((result) => { }, (resut) => {return;}); + }); + } + + /** + * getFunctionalMenu + */ + getFunctionalMenu(){ + let actualData=[]; + this.functionalMenuService.getManagedFunctionalMenu() + .subscribe(_data => { + this.result = _data; + if(this.result){ + for(let i = 0; i < this.result.length; i++){ + this.result[i].children=[]; + this.result[i].label= this.result[i].text; + this.result[i].id= this.result[i].text; + } + //Adding actual child items to children array in res objects + for(let i = 0; i < this.result.length; i++){ + let parentId=this.result[i].menuId; + for(let j = 0; j < this.result.length; j++){ + let childId=this.result[j].parentMenuId; + if(parentId===childId){ + this.result[i].children.push(this.result[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 children in order based on the column + for(let i = 0; i < this.result.length; i++){ + this.result[i].children.sort(function(a, b){ + return a.column-b.column; + }); + } + + //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; + //console.log("this.treedata :: ",this.treedata); + + if(this.treedata){ + this.buildTree(this.treedata,this.ngbModal, this.self); + } + + } + }, error =>{ + console.log(error); + }); + + } + + /** + * buildTree + * @param treedataarray + * @param ngbModal + */ + buildTree(treedataarray,ngbModal: NgbModal , _self){ + //console.log("treedataarray>>>>",treedataarray); + $(function() { + $('#jqTree').tree('loadData', treedataarray); + $('#jqTree').tree({ + data: treedataarray, + autoOpen: false, + dragAndDrop: true, + onCreateLi: function(node, $li) { + ////console.log("node >>",node); + } + }).on( + 'tree.contextmenu', + function(event:any) { + // The clicked node is 'event.node' + var node = event.node; + openMenuDetailsModal(node, "view"); + } + ); + + var openMenuDetailsModal = function(node: any, actionName: string ){ + const modalRef = ngbModal.open(FunctionalMenuDialogComponent, { size: 'lg' }); + modalRef.componentInstance.title = 'Functional Menu ',actionName; + if(node != 'undefined' && node){ + modalRef.componentInstance.nodedata = node; + modalRef.componentInstance.operationName = actionName; + }else{ + modalRef.componentInstance.nodedata = {}; + } + modalRef.componentInstance.passEntry.subscribe((receivedEntry: any) => { + //console.log("receivedEntry>>>>",receivedEntry); + ngbModal.dismissAll(); + if(receivedEntry.httpStatusCode===200){ + _self.getFunctionalMenu(); + } + }); + } + }); + } +} diff --git a/portal-FE-common/src/app/pages/functional-menu/jqTreeContextMenu.js b/portal-FE-common/src/app/pages/functional-menu/jqTreeContextMenu.js new file mode 100644 index 00000000..fa240187 --- /dev/null +++ b/portal-FE-common/src/app/pages/functional-menu/jqTreeContextMenu.js @@ -0,0 +1,195 @@ +(function ($) { + if (!$.fn.tree) { + throw "Error jqTree is not loaded."; + } + + $.fn.jqTreeContextMenu = function (menuElement, callbacks) { + // + // TODO: + // * Make sure the useContextMenu option is set in jqTree, either complain or set it automatically + // * Make menu fade in/out + // + var self = this; + var $el = this; + + // The jQuery object of the menu div. + var $menuEl = menuElement; + + // This hash holds all menu items that should be disabled for a specific node. + var nodeToDisabledMenuItems = {}; + + // Hide the menu div. + $menuEl.hide(); + + // Disable system context menu from beeing displayed. + $el.bind("contextmenu", function (e) { + e.preventDefault(); + return false; + }); + + // Handle the contextmenu event sent from jqTree when user clicks right mouse button. + $el.bind('tree.contextmenu', function (event) { + var x = event.click_event.pageX; + var y = event.click_event.pageY; + var yPadding = 5; + var xPadding = 5; + var menuHeight = $menuEl.height(); + var menuWidth = $menuEl.width(); + var windowHeight = $(window).height(); + var windowWidth = $(window).width(); + + if (menuHeight + y + yPadding > windowHeight) { + // Make sure the whole menu is rendered within the viewport. + y = y - menuHeight; + } + if (menuWidth + x + xPadding > windowWidth) { + // Make sure the whole menu is rendered within the viewport. + x = x - menuWidth; + } + + // Handle disabling and enabling of menu items on specific nodes. + if (Object.keys(nodeToDisabledMenuItems).length > 0) { + if (event.node.name in nodeToDisabledMenuItems) { + var nodeName = event.node.name; + var items = nodeToDisabledMenuItems[nodeName]; + if (items.length === 0) { + $menuEl.find('li').addClass('disabled'); + $menuEl.find('li > a').unbind('click'); + } else { + $menuEl.find('li > a').each(function () { + $(this).closest('li').removeClass('disabled'); + var hrefValue = $(this).attr('href'); + var value = hrefValue.slice(hrefValue.indexOf("#") + 1, hrefValue.length) + if ($.inArray(value, items) > -1) { + $(this).closest('li').addClass('disabled'); + $(this).unbind('click'); + } + }); + } + } else { + $menuEl.find('li.disabled').removeClass('disabled'); + } + } + + // Must call show before we set the offset (offset can not be set on display: none elements). + $menuEl.show(); + + $menuEl.offset({ left: x, top: y }); + + var dismissContextMenu = function () { + $(document).unbind('click.jqtreecontextmenu'); + $el.unbind('tree.click.jqtreecontextmenu'); + $menuEl.hide(); + } + // Make it possible to dismiss context menu by clicking somewhere in the document. + $(document).bind('click.jqtreecontextmenu', function () { + dismissContextMenu(); + }); + + // Dismiss context menu if another node in the tree is clicked. + $el.bind('tree.click.jqtreecontextmenu', function (e) { + dismissContextMenu(); + }); + + // Make selection follow the node that was right clicked on. + var selectedNode = $el.tree('getSelectedNode'); + if (selectedNode !== event.node) { + $el.tree('selectNode', event.node); + } + + // Handle click on menu items, if it's not disabled. + var menuItems = $menuEl.find('li:not(.disabled) a'); + if (menuItems.length !== 0) { + menuItems.unbind('click'); + menuItems.click(function (e) { + e.stopImmediatePropagation(); + dismissContextMenu(); + var hrefAnchor = e.currentTarget.attributes.href.nodeValue; + var funcKey = hrefAnchor.slice(hrefAnchor.indexOf("#") + 1, hrefAnchor.length) + var callbackFn = callbacks[funcKey]; + if (callbackFn) { + callbackFn(event.node); + } + return false; + }); + } + }); + + this.disable = function () { + if (arguments.length === 0) { + // Called as: api.disable() + $menuEl.find('li:not(.disabled)').addClass('disabled'); + $menuEl.find('li a').unbind('click'); + nodeToDisabledMenuItems = {}; + } else if (arguments.length === 1) { + // Called as: api.disable(['edit','remove']) + var items = arguments[0]; + if (typeof items !== 'object') { + return; + } + $menuEl.find('li > a').each(function () { + var hrefValue = $(this).attr('href'); + var value = hrefValue.slice(hrefValue.indexOf("#") + 1, hrefValue.length) + if ($.inArray(value, items) > -1) { + $(this).closest('li').addClass('disabled'); + $(this).unbind('click'); + } + }); + nodeToDisabledMenuItems = {}; + } else if (arguments.length === 2) { + // Called as: api.disable(nodeName, ['edit','remove']) + var nodeName = arguments[0]; + var items = arguments[1]; + nodeToDisabledMenuItems[nodeName] = items; + } + }; + + this.enable = function () { + if (arguments.length === 0) { + // Called as: api.enable() + $menuEl.find('li.disabled').removeClass('disabled'); + nodeToDisabledMenuItems = {}; + } else if (arguments.length === 1) { + // Called as: api.enable(['edit','remove']) + var items = arguments[0]; + if (typeof items !== 'object') { + return; + } + + $menuEl.find('li > a').each(function () { + var hrefValue = $(this).attr('href'); + var value = hrefValue.slice(hrefValue.indexOf("#") + 1, hrefValue.length) + if ($.inArray(value, items) > -1) { + $(this).closest('li').removeClass('disabled'); + } + }); + + nodeToDisabledMenuItems = {}; + } else if (arguments.length === 2) { + // Called as: api.enable(nodeName, ['edit','remove']) + var nodeName = arguments[0]; + var items = arguments[1]; + if (items.length === 0) { + delete nodeToDisabledMenuItems[nodeName]; + } else { + var disabledItems = nodeToDisabledMenuItems[nodeName]; + for (var i = 0; i < items.length; i++) { + var idx = disabledItems.indexOf(items[i]); + if (idx > -1) { + disabledItems.splice(idx, 1); + } + } + if (disabledItems.length === 0) { + delete nodeToDisabledMenuItems[nodeName]; + } else { + nodeToDisabledMenuItems[nodeName] = disabledItems; + } + } + if (Object.keys(nodeToDisabledMenuItems).length === 0) { + $menuEl.find('li.disabled').removeClass('disabled'); + } + } + }; + return this; + }; +} (jQuery)); diff --git a/portal-FE-common/src/app/pages/get-access/get-access.component.html b/portal-FE-common/src/app/pages/get-access/get-access.component.html new file mode 100644 index 00000000..8719ea41 --- /dev/null +++ b/portal-FE-common/src/app/pages/get-access/get-access.component.html @@ -0,0 +1,98 @@ +<!-- + ============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="ecomp-main-view-title"> + <h1 class="heading-page">Get Access</h1> + <br> + <span> Visit <a id="url-access" [href]="getAccessUrl" target="_new">{{getAccessName}}</a> + {{getAccessInfo}} + </span> + </div> + <mat-form-field> + <input matInput type="text" (keyup)="applyFilter($event.target.value)" placeholder="Search in entire table"> + </mat-form-field> + <span class="ecomp-spinner" *ngIf="showSpinner"></span> + <table mat-table [dataSource]="getAccessDataSource"> + <!-- Function Column --> + <ng-container matColumnDef="function"> + <th id="col1" mat-header-cell *matHeaderCellDef> Function </th> + <td id="rowheader_t1_{{i}}-function" mat-cell *matCellDef="let element; let i = index;"> + <div id="access-page-function" *ngIf="element.ecomp_function !== 'Onap Function Not Available'"> + {{element.ecomp_function}}</div> + <div id="access-page-function" *ngIf="element.ecomp_function === 'Onap Function Not Available'"> + {{element.ecomp_function}}</div> + </td> + </ng-container> + + <!-- Application Name Column --> + <ng-container matColumnDef="applicationName"> + <th id="col2" mat-header-cell *matHeaderCellDef> Application Name</th> + <td id="rowheader_t1_{{i}}-applicationName" mat-cell *matCellDef="let element; let i=index;"> {{element.app_name}} + </td> + </ng-container> + + <!-- Role Name Column --> + <ng-container matColumnDef="roleName"> + <th id="col3" mat-header-cell *matHeaderCellDef> Role Name </th> + <td id="rowheader_t1_{{i}}-roleName" mat-cell *matCellDef="let element; let i=index;"> {{element.role_name}} + </td> + </ng-container> + + <!-- Current Role Column --> + <ng-container matColumnDef="currentRole"> + <th id="col4" mat-header-cell *matHeaderCellDef> Current Role </th> + <td id="rowheader_t1_{{i}}-currentRole" mat-cell *matCellDef="let element; let i=index;"> + <div id="access-page-currentRole" *ngIf="element.current_role === 'Y'"> + <i class="icon ion-md-checkmark"></i> + </div> + </td> + </ng-container> + + <!-- Request Status Column --> + <ng-container matColumnDef="requestStatus"> + <th id="col4" mat-header-cell *matHeaderCellDef> Request Status </th> + <td id="rowheader_t1_{{i}}-requestStatus" mat-cell *matCellDef="let element; let i=index;"> + <div id="access-page-RequestAccess" *ngIf="element.request_type !== null">{{element.request_type}}</div> + </td> + </ng-container> + + <tr [hidden]="appTable.length === 0" mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="openExistingAdminModal(row)"></tr> + </table> + <mat-paginator [hidden]="appTable.length === 0" [pageSizeOptions]="[10, 20]" showFirstLastButtons></mat-paginator> + </div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/get-access/get-access.component.scss b/portal-FE-common/src/app/pages/get-access/get-access.component.scss new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/portal-FE-common/src/app/pages/get-access/get-access.component.scss diff --git a/portal-FE-common/src/app/pages/get-access/get-access.component.spec.ts b/portal-FE-common/src/app/pages/get-access/get-access.component.spec.ts new file mode 100644 index 00000000..ae61740d --- /dev/null +++ b/portal-FE-common/src/app/pages/get-access/get-access.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GetAccessComponent } from './get-access.component'; + +describe('GetAccessComponent', () => { + let component: GetAccessComponent; + let fixture: ComponentFixture<GetAccessComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ GetAccessComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(GetAccessComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/get-access/get-access.component.ts b/portal-FE-common/src/app/pages/get-access/get-access.component.ts new file mode 100644 index 00000000..a7454894 --- /dev/null +++ b/portal-FE-common/src/app/pages/get-access/get-access.component.ts @@ -0,0 +1,136 @@ +/*- + * ============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 { GetAccessService } from 'src/app/shared/services/get-access/get-access.service'; +import { MatTableDataSource, MatPaginator } from '@angular/material'; +import { environment } from '../../../environments/environment'; + +@Component({ + selector: 'app-get-access', + templateUrl: './get-access.component.html', + styleUrls: ['./get-access.component.scss'] +}) +export class GetAccessComponent implements OnInit { + + isLoadingTable: boolean; + appTable: any[]; + displayedColumns: string[] = ['function', 'applicationName', 'roleName', 'currentRole', 'requestStatus']; + getAccessDataSource = new MatTableDataSource(this.appTable); + @ViewChild(MatPaginator) paginator: MatPaginator; + showSpinner: boolean; + getAccessUrl = environment.getAccessUrl; + getAccessName = environment.getAccessName; + getAccessInfo = environment.getAccessInfo; + + constructor(private getAccessService: GetAccessService) { } + + ngOnInit() { + this.appTable = []; + this.getAccessAppsList(); + } + + applyFilter(filterValue: string) { + this.getAccessDataSource.filter = filterValue.trim().toLowerCase(); + } + + // Convert this code to typescript after mylogins feature is back again + // var resultAccessValue = null; + + // $scope.openAppRoleModal = (itemData) => { + // if(resultAccessValue){ + // let data = null; + // data = { + // dialogState: 2, + // selectedUser:{ + // orgUserId: $scope.orgUserId, + // firstName: $scope.firstName, + // lastName: $scope.lastName, + // headerText: itemData.app_name, + // } + // } + // ngDialog.open({ + // templateUrl: 'app/views/catalog/request-access-catalog-dialogs/request-access-catalog.modal.html', + // controller: 'ExternalRequestAccessCtrl', + // controllerAs: 'userInfo', + // data: data + // }); + // } + // } + + // userProfileService.getUserProfile().then( + // function(profile) { + // $scope.orgUserId = profile.orgUserId; + // $scope.firstName = profile.firstName; + // $scope.lastName = profile.lastName; + // }); + + getAccessAppsList() { + this.showSpinner = true; + this.getAccessService.getListOfApp().subscribe((_res: any) => { + var tableData = []; + // $log.info('GetAccessCtrl::updateAppsList: getting res'); + var result = (typeof (_res) != "undefined" && _res != null) ? _res : null; + this.showSpinner = false; + // $log.info('GetAccessCtrl::updateAppsList: result',result); + // $log.info('GetAccessCtrl::updateAppsList: done'); + var source = result; + // $log.info('GetAccessCtrl::updateAppsList source: ', source); + for (var i = 0; i < source.length; i++) { + var dataArr = source[i]; + var checkEcompFuncAvail = 'Function Not Available'; + var reqStatus = 'Pending'; + dataArr.ecompFunction = (dataArr.ecompFunction === null) ? checkEcompFuncAvail : dataArr.ecompFunction; + dataArr.reqType = (dataArr.reqType === 'P') ? reqStatus : dataArr.reqType; + var dataTemp = { + ecomp_function: dataArr.ecompFunction, + app_name: dataArr.appName, + role_name: dataArr.roleName, + current_role: dataArr.roleActive, + request_type: dataArr.reqType + } + tableData.push(dataTemp); + } + this.appTable = tableData; + this.getAccessDataSource = new MatTableDataSource(this.appTable); + this.getAccessDataSource.paginator = this.paginator; + }, (_err) => { + this.isLoadingTable = false; + }) + } +} diff --git a/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.html b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.html new file mode 100644 index 00000000..4ec8ae3d --- /dev/null +++ b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.html @@ -0,0 +1,214 @@ +<!-- + ============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"> + <form name="form" (ngSubmit)="f.form.valid && saveChanges()" #f="ngForm" novalidate autocomplete="off"> + + <!--Modal Headers--> + <div class="modal-header"> + <h4 class="modal-title">Microservice Onboarding</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 id="microservices" class="microservice-add-details-model"> + <div class="microservice-properties-main"> + <div class="item required"> + <div class="item-label">Microservice Name</div> + <input id="microservice-details-input-name" class="table-search-field" + type="text" name="name" #name="ngModel" ng-pattern="/^[\w -]*$/" maxlength="100" + [(ngModel)]="ms.name" + ng-change="microserviceAddDetails.updateServiceName()" + [ngClass]="{'is-invalid': f.submitted && name.invalid}" required /> + + <div class="error-container" *ngIf="f.submitted && name.invalid"> + <div class="error-container"> + <small id="microservices-details-input-name-required" class="err-message"> + Microservice Name is required + </small> + </div> + </div> + </div> + + + <div class="item required"> + <div class="item-label">Microservice Description</div> + <textarea b2b-reset b2b-reset-textarea id="widgets-details-input-desc" + name="desc" [(ngModel)]="ms.desc" #desc="ngModel" class="microservice-desc" + ng-change="microserviceAddDetails.updateDesc()" + [ngClass]="{'is-invalid': f.submitted && desc.invalid}" required> + </textarea> + + <div class="error-container" *ngIf="f.submitted && desc.invalid"> + <div class="error-container"> + <small id="microservices-details-input-desc-required" class="err-message"> + Microservice Description is required + </small> + </div> + </div> + </div> + + <div class="item required"> + <div class="service-select"> + <mat-form-field> + <mat-label>Applications Name</mat-label> + <mat-select name="microservice-details-application-select" + [(ngModel)]="ms.appId" #appId="ngModel" [(value)]="selected" + [ngClass]="{'is-invalid': f.submitted && appId.invalid}" required> + <mat-option *ngFor="let d of applicationList" [value]="d.value" >{{d.title}}</mat-option> + </mat-select> + </mat-form-field> + </div> + + <div class="error-container" *ngIf="f.submitted && appId.invalid"> + <div class="error-container"> + <small id="microservices-details-input-appid-required" class="err-message"> + Please select microservice Application + </small> + </div> + </div> + </div> + + <div class="item required"> + <div class="item-label">Microservice Endpoint URL</div> + <input id="microservice-details-input-endpoint-url" + [(ngModel)]="ms.url" #url="ngModel" ng-change="microserviceAddDetails.updateURL()" type="text" + name="url" maxlength="200" [ngClass]="{'is-invalid': f.submitted && url.invalid}" required /> + + <div class="error-container" *ngIf="f.submitted && url.invalid"> + <div class="error-container"> + <small id="microservices-details-input-url-required" class="err-message"> + Microservice Endpoint URL is required + </small> + </div> + </div> + </div> + + <div class="item"> + <div class="service-select"> + <mat-form-field> + <mat-label>Security Type</mat-label> + <mat-select name="microservice-details-input-security-type" [(ngModel)]="ms.securityType" [(value)]="selected"> + <mat-option *ngFor="let d of availableSecurityTypes" [value]="d.name" >{{d.name}}</mat-option> + </mat-select> + </mat-form-field> + </div> + </div> + + <div class="item" *ngIf="ms.securityType == 'Basic Authentication'"> + <div class="item-label">Username</div> + <input id="microservice-details-input-username" class="table-search-field" + type="text" name="username" maxlength="100" + [(ngModel)]="ms.username" /> + </div> + + <div class="item" *ngIf="ms.securityType == 'Basic Authentication'"> + <div class="item-label">Password</div> + <input id="microservice-details-input-password" class="table-search-field" + type="password" name="password" maxlength="100" + [(ngModel)]="ms.password" /> + </div> + + <div class="item" *ngIf="isEditMode"> + <div class="left-test-item"> + <div class="item-label">Test Microservice</div> + </div> + <div class="right-test-item"> + <div id="microservice-details-test-button" class="test-button" + (click)="testServiceURL()">Test</div> + </div> + </div> + + <div class="item" *ngIf="isEditMode"> + <div class="item-label">JSON output</div> + <textarea id="microservice-details-input-json" class="json-field" + name="json"></textarea> + </div> + + <div class="add-para-item"> + <div class="item-label add-label-left">Add User Parameter</div> + <div class="icon-primary-accordion-plus" (click)="addParameter()"> + <i class="icon ion-ios-add-circle-outline"></i> + </div> + </div> + + <div class="item"> + <div class="param-label-item-left" + *ngIf="ms.parameterList.length > 0"> + Parameter Key + </div> + <div class="para-label-item-right" + *ngIf="ms.parameterList.length > 0"> + Parameter Default Value + </div> + + <div id="microservice-details-user-paramters" *ngFor="let parameter of ms.parameterList; let i=index"> + <div class="param-item-left"> + <input id="microservice-details-input-user-parameter-key" + class="table-search-field" type="text" name="param-key" maxlength="200" + [(ngModel)]="parameter.para_key" /> + <input id="microservice-details-input-user-parameter-value" + class="table-search-fields" type="text" name="param-value" + maxlength="200" [(ngModel)]="parameter.para_value" /> + </div> + + <div (click)="removeParamItem(parameter)"> + <i class="icon ion-ios-remove-circle-outline"></i> + </div> + </div> + + <div class="microservice-property"> + <mat-checkbox name="active" type="checkbox" [(ngModel)]="ms.active" + id="microservices-checkbox-app-is-enabled" [checked]="ms.active"> + Active + </mat-checkbox> + </div> + </div> + </div> + </div> + </div> + <!--Modal Footer goes Here--> + <div class="modal-footer"> + <button type="submit" class="btn btn-primary">Save</button> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Cancel</button> + </div> + </form> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.scss b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.scss new file mode 100644 index 00000000..1ea39095 --- /dev/null +++ b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.scss @@ -0,0 +1,211 @@ +/*- + * ============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============================================ + * + * + */ + +::ng-deep .modal-dialog { + max-width: 600px; + width: 600px; + overflow-x: auto; + overflow-y: auto; +} + +::ng-deep .modal-body { + padding: -1rem; +} + +::ng-deep .mat-form-field-infix { + width: 400px; +} + +.microservice-add-details-model .microservice-properties-main { + padding-left: 20px; + padding-right: 20px; + margin-bottom: 50px; + height: 430px; + width: 100%; + overflow-x: auto; +} + +.microservice-add-details-model .microservice-properties-main .item { + position: relative; + margin-bottom: 15px; + width: 400px; +} + +.microservice-add-details-model .microservice-properties-main .item .error-container { + position: absolute; + width: 280px; + display: block; + height: 12px; + line-height: 12px; +} + +.microservice-add-details-model .microservice-properties-main .item .error-container .err-message { + color: #cf2a2a; + font-size: 10px; +} + +.microservice-add-details-model .microservice-properties-main .item .item-label { + color: #5a5a5a; + font-family: Omnes-ECOMP-W02,Arial; + font-size: 14px; +} + +.microservice-add-details-model .microservice-properties-main .item .left-test-item { + display: inline-block; + width: 85%; + background: #fff; +} + +.microservice-add-details-model .microservice-properties-main .item .right-test-item { + position: relative; + display: inline-block; + width: 15%; + float: right; +} + +.microservice-add-details-model .microservice-properties-main .item .test-button { + color: #fff; + font-family: Omnes-ECOMP-W02-Medium,Arial; + font-size: 14px; + border-style: solid; + border-width: 1px; + border-radius: 6px; + box-shadow: 0 1px 0.99px 0.01px rgba(2,60,89,.004); + height: 29px; + line-height: 29px; + cursor: pointer; + text-align: center; + background: #067ab4; +} + +.microservice-add-details-model .microservice-properties-main .item { + position: relative; + margin-bottom: 15px; + width: 400px; +} + +.microservice-add-details-model .microservice-properties-main .add-para-item { + position: relative; + display: inline-flex; +} + +.microservice-add-details-model .microservice-properties-main .add-para-item .add-label-left { + line-height: 25px; + height: 30px; + vertical-align: middle; + display: inline-block; + margin-right: 10px; + background: #fff; + color: #5a5a5a; + font-family: Omnes-ECOMP-W02,Arial; + font-size: 14px; +} + +.microservice-add-details-model .microservice-properties-main .microservice-property { + margin-top: 10px; + position: relative; +} + +.icon-primary-accordion-minus, .icon-primary-expanded, .icon-primary-accordion-plus, .icon-primary-collapsed { + font-size: 20px !important; +} + +#microservice-details-input-name{ + width: 16em; +} + +#widgets-details-input-desc{ + width: 25em; +} + +.microservice-desc { + overflow: auto; + resize: vertical; + width: 29em; +} + +.json-field { + overflow: auto; + resize: vertical; + width: 25em; +} + +#microservice-details-input-app{ + width: 25em; + height: 32px; +} + +#microservice-details-input-endpoint-url{ + width: 25em; +} + +#microservice-details-input-security-type{ + width: 25em; + height: 32px; +} + +.microservice-add-details-model .microservice-properties-main .item .para-label-item-right { + margin-left: 201px!important; + position: absolute!important; + margin-top: -19px!important; + width: 50%!important; +} + +.microservice-add-details-model .microservice-properties-main .item .para-item-right { + margin-top: -27px; + position: absolute; + margin-left: 500px; +} + +.table-search-field { + width: 48% !important; + margin-bottom: 5px; +} + +#microservice-details-user-paramters{ + display: inline-flex; +} + +.required:before { + color: #cf2a2a; + margin-right: 2px; + content: "* "; + position: absolute; + top: 28px; + left: -10px; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.spec.ts b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.spec.ts new file mode 100644 index 00000000..5589854c --- /dev/null +++ b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.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 { MicroserviceAddDetailsComponent } from './microservice-add-details.component'; + +describe('MicroserviceAddDetailsComponent', () => { + let component: MicroserviceAddDetailsComponent; + let fixture: ComponentFixture<MicroserviceAddDetailsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MicroserviceAddDetailsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MicroserviceAddDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.ts b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.ts new file mode 100644 index 00000000..5e359dd4 --- /dev/null +++ b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-add-details/microservice-add-details.component.ts @@ -0,0 +1,303 @@ +/*- + * ============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 { IMircroservies } from 'src/app/shared/model/microservice-onboarding/microservices'; +import { MicroserviceService, WidgetOnboardingService } from '../../../shared/services/index'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'app-microservice-add-details', + templateUrl: './microservice-add-details.component.html', + styleUrls: ['./microservice-add-details.component.scss'] +}) +export class MicroserviceAddDetailsComponent implements OnInit { + + @Input() public ms: IMircroservies; + @Output() passEntry: EventEmitter<any> = new EventEmitter(); + result: any; + selected: any; + isEditMode: any; + originalName: string; + dupliateName = false; + emptyServiceName = false; + emptyServiceDesc = false; + emptyServiceURL = false; + emptyServiceApp = false; + availableSecurityTypes = []; + availableWidgets = []; + applicationList: Array<Object> = []; + + constructor(public microservice: MicroserviceService, public widgetOnboardingService: WidgetOnboardingService, + public activeModal: NgbActiveModal, public ngbModal: NgbModal) { } + + ngOnInit() { + if(this.ms.name){ + this.isEditMode = true; + }else{ + this.isEditMode = false; + } + this.ms.parameterList = []; + this.ms.active = true; + this.populateAvailableApps(); + this.getAvailableSecurityTypes(); + } + + getAvailableWidgets(serviceId){ + console.log("getAvailableWidgets called"); + this.microservice.getWidgetListByService(serviceId) + .subscribe(data => { + this.result = data; + if (this.result == null || this.result) { + console.log('MicroserviceService::getServiceList Failed: Result or result.data is null'); + }else { + this.availableWidgets = []; + for(var i = 0; i < data.length; i++){ + this.availableWidgets.push({ + name: data[i] + }) + } + } + }, error =>{ + console.log(error); + }); + }; + + getAvailableSecurityTypes(): Array<String> { + this.availableSecurityTypes = []; + this.availableSecurityTypes.push({ + id: 0, + name: 'No Authentication' + }); + this.availableSecurityTypes.push({ + id: 1, + name: 'Basic Authentication' + }); + this.availableSecurityTypes.push({ + id: 2, + name: 'Cookie based Authentication' + }); + + return this.availableSecurityTypes; + }; + + populateAvailableApps(){ + this.widgetOnboardingService.populateAvailableApps() + .subscribe( _data => { + let allPortalsFilterObject = {index: 0, title: 'Select Application', value: ''}; + this.applicationList = [allPortalsFilterObject]; + var realAppIndex = 1; + for (let i = 1; i <= _data.length; i++) { + if (!_data[i-1].restrictedApp) { + this.applicationList.push({ + index: realAppIndex, + title: _data[i - 1].name, + value: _data[i - 1].id + }) + realAppIndex = realAppIndex + 1; + } + } + }, error => { + console.log(error); + }); + }; + + addParameter() { + this.ms.parameterList.push({}); + } + + testServiceURL(){ + console.log("testServiceURL called id is :: ",this.ms.id) + this.microservice.getServiceJSON(this.ms.id) + .subscribe( _data => { + this.result = _data; + console.log("testServiceURL response :: ",this.result); + document.getElementById("microservice-details-input-json").innerHTML = (JSON.stringify(this.result)); + }, error => { + document.getElementById("microservice-details-input-json").innerHTML = "Something went wrong. Please go back to the previous page or try again later."; + console.log(error); + }); + } + + removeParamItem(parameter: any){ + console.log("removeParamItem called", parameter); + this.ms.parameterList.splice(parameter.para_key, 1); + this.microservice.getUserParameterById(parameter) + .subscribe(data => { + this.result = data; + if (this.result == null || this.result) { + console.log('MicroserviceService::removeParamItem Failed: Result or result.data is null'); + }else if(this.result && this.result.length > 0) { + this.microservice.deleteUserParameterById(parameter.id) + .subscribe(__data => { + for(var i = 0; i < this.ms.parameterList.length; i++){ + if(this.ms.parameterList[i].para_key == parameter.para_key + && this.ms.parameterList[i].para_value == parameter.para_value){ + this.ms.parameterList.splice(i, 1); + return; + } + } + + }, error =>{ + console.log(error); + }); + }else{ + for(var i = 0; i < this.ms.parameterList.length; i++){ + if(this.ms.parameterList[i].para_key == parameter.para_key + && this.ms.parameterList[i].para_value == parameter.para_value){ + this.ms.parameterList.splice(i, 1); + return; + } + } + } + }, error =>{ + console.log(error); + }); + } + + //Add Or Update Microservices. + saveChanges(){ + console.log("saveChanges..",this.ms); + if(this.ms && this.ms.id && this.ms.id !='undefined'){ + this.isEditMode = true; + } + var isValid = true; + + if(this.ms.name == '' + || this.ms.name == undefined){ + this.emptyServiceName = true; + isValid = false; + } + console.log("a >",isValid); + if(this.dupliateName == true){ + isValid = false; + } + console.log("b> ",isValid); + if(this.ms.desc == '' + || this.ms.desc == undefined){ + this.emptyServiceDesc = true; + isValid = false; + } + console.log("c> ",isValid); + + if(this.ms.url == '' + || this.ms.url == undefined){ + this.emptyServiceURL = true; + isValid = false; + } + console.log("d> ",isValid); + + if(this.ms.appId == undefined + || this.ms.appId == null){ + this.emptyServiceApp = true; + isValid = false; + } + + console.log("IsValid flag add/update microservices ",isValid ) + + if(!isValid) + return; + /* + * Check the parameter list, delete those parameters that don't + * have key + */ + if(this.ms && this.ms.parameterList){ + for(var i = 0; i < this.ms.parameterList.length; i++){ + if(this.ms.parameterList[i].para_key == undefined + || this.ms.parameterList[i].para_key == null + || this.ms.parameterList[i].para_key == ""){ + this.ms.parameterList.splice(i, 1); + i--; + } + } + } + if(this.ms.securityType == undefined || + this.ms.securityType == null) + this.ms.securityType = "No Authentication"; + else{ + this.ms.securityType = this.ms.securityType; + this.ms.username = this.ms.username; + this.ms.password = this.ms.password; + } + + var active = 'N'; + if(this.ms.active == true) + active = 'Y'; + + let paramList = []; + if(this.ms.parameterList && this.ms.parameterList.length >0){ + paramList = this.ms.parameterList; + } + var newService = { + name: this.ms.name, + desc: this.ms.desc, + appId: this.ms.appId, + url: this.ms.url, + securityType: this.ms.securityType, + username: this.ms.username, + password: this.ms.password, + active: active, + parameterList: paramList + }; + + if(this.isEditMode){ + console.log("Edit microservice mode called"); + this.microservice.updateService(this.ms.id , newService) + .subscribe( data => { + this.result = data; + console.log("update microservice response :: ",this.result); + this.passEntry.emit(this.result); + this.ngbModal.dismissAll(); + }, error => { + console.log(error); + this.ngbModal.dismissAll(); + }); + }else{ + console.log("Add microservice mode called") + this.microservice.createService(newService) + .subscribe( data => { + this.result = data; + console.log("add microservice response :: ",this.result); + this.passEntry.emit(this.result); + this.ngbModal.dismissAll(); + }, error => { + this.ngbModal.dismissAll(); + console.log(error); + }); + } + } +} diff --git a/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.html b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.html new file mode 100644 index 00000000..45a68650 --- /dev/null +++ b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.html @@ -0,0 +1,89 @@ +<!-- + ============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 id="microservice-onboarding-title" class="w-ecomp-main-view-title"> + <h1 class="heading-page"> Microservice Onboarding</h1> + </div> + + <mat-form-field> + <input matInput type="text" (keyup)="applyFilter($event.target.value)" placeholder="Search in entire table"> + </mat-form-field> + + <button type="button" style="float: right;" class="btn btn-primary" (click)="openAddNewMicroserviceModal('')"> + <i class="icon ion-md-person-add"></i>Add Microservice + </button> + + <table mat-table [dataSource]="dataSource" matSort> + <!-- Microservice Name Column --> + <ng-container matColumnDef="microserviceName"> + <th id="col1" mat-header-cell *matHeaderCellDef mat-sort-header> Microservice Name </th> + <td (click)="openAddNewMicroserviceModal(element)" id="rowheader_t1_{{i}}-microserviceName" + mat-cell *matCellDef="let element; let i = index;"> {{element.name}} + </td> + </ng-container> + + <!-- Microservice serviceEndPointURL Column --> + <ng-container matColumnDef="serviceEndPointURL"> + <th id="col2" mat-header-cell *matHeaderCellDef> Service End Point URL </th> + <td (click)="openAddNewMicroserviceModal(element)" id="rowheader_t1_{{i}}-serviceEndPointURL" + mat-cell *matCellDef="let element; let i=index;"> {{element.url}} </td> + </ng-container> + + <!-- Microservice securityType Column --> + <ng-container matColumnDef="securityType"> + <th id="col2" mat-header-cell *matHeaderCellDef> Security Type </th> + <td (click)="openAddNewMicroserviceModal(element)" id="rowheader_t1_{{i}}-securityType" + mat-cell *matCellDef="let element; let i=index;"> {{element.securityType}} </td> + </ng-container> + + <!-- Delete Column --> + <ng-container matColumnDef="delete"> + <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)="deleteService(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/microservice-onboarding/microservice-onboarding.component.scss b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.scss new file mode 100644 index 00000000..135fbda2 --- /dev/null +++ b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.scss @@ -0,0 +1,46 @@ +/*- + * ============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 th{ + padding-bottom: 15px; + font-weight: bold; +} + +.ion-md-trash{ + cursor: pointer; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.spec.ts b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.spec.ts new file mode 100644 index 00000000..09d30a14 --- /dev/null +++ b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.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 { MicroserviceOnboardingComponent } from './microservice-onboarding.component'; + +describe('MicroserviceOnboardingComponent', () => { + let component: MicroserviceOnboardingComponent; + let fixture: ComponentFixture<MicroserviceOnboardingComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ MicroserviceOnboardingComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MicroserviceOnboardingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.ts b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.ts new file mode 100644 index 00000000..d2d66974 --- /dev/null +++ b/portal-FE-common/src/app/pages/microservice-onboarding/microservice-onboarding.component.ts @@ -0,0 +1,156 @@ +/*- + * ============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, Input } from '@angular/core'; +import { MicroserviceService, WidgetOnboardingService } from '../../shared/services/index' +import { IMircroservies } from 'src/app/shared/model/microservice-onboarding/microservices'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { MatTableDataSource } from '@angular/material'; +import { MatSort, MatPaginator } from '@angular/material'; +import { MicroserviceAddDetailsComponent } from './microservice-add-details/microservice-add-details.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-microservice-onboarding', + templateUrl: './microservice-onboarding.component.html', + styleUrls: ['./microservice-onboarding.component.scss'] +}) +export class MicroserviceOnboardingComponent implements OnInit { + + showSpinner = true; + microServiceList: Array<IMircroservies> = []; + result: any; + isEditMode: boolean = false; + + displayedColumns: string[] = ['microserviceName', 'serviceEndPointURL', 'securityType','delete']; + dataSource = new MatTableDataSource(this.microServiceList); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + constructor(public microservice: MicroserviceService, public ngbModal: NgbModal ) { } + + ngOnInit() { + this.getOnboardingServices(); + } + + getOnboardingServices(){ + //console.log("getOnboardingServices called"); + this.microservice.getServiceList() + .subscribe(_data => { + this.result = _data; + if (this.result == null || this.result == 'undefined') { + console.log('MicroserviceService::getServiceList Failed: Result or result.data is null'); + }else { + this.microServiceList = this.result; + this.populateTableData(this.microServiceList); + } + }, error =>{ + console.log(error); + }); + } + + deleteService(microserviceObj: IMircroservies, isConfirmed: boolean): void { + let confirmationMsg = 'You are about to delete this Microservice : ' + microserviceObj.name+ '. Click OK to continue.'; + this.openInformationModal("Confirmation",confirmationMsg).result.then((result) => { + if (result === 'Ok') { + if(!microserviceObj || microserviceObj == null){ + console.log('MicroserviceOnboardingCtrl::deleteService: No service or ID... cannot delete'); + return; + } + //console.log("service id to delete", microserviceObj.id) + this.microServiceList.splice(this.microServiceList.indexOf(microserviceObj), 1); + this.populateTableData(this.microServiceList); + this.microservice.deleteService(microserviceObj.id) + .subscribe( data => { + this.result = data; + this.microServiceList = []; + this.getOnboardingServices(); + }, error => { + console.log(error); + }); + } + }, (resut) => { + return; + }) + } + + + openAddNewMicroserviceModal(rowData: any){ + //console.log("openAddNewMicroserviceModal getting called..."); + const modalRef = this.ngbModal.open(MicroserviceAddDetailsComponent, { size: 'lg' }); + modalRef.componentInstance.title = 'Microservice Onboarding'; + if(rowData != 'undefined' && rowData){ + modalRef.componentInstance.ms = rowData; + this.isEditMode = true; + }else{ + modalRef.componentInstance.ms = {}; + this.isEditMode = false; + } + modalRef.componentInstance.passEntry.subscribe((receivedEntry: any) => { + //console.log("receivedEntry >>> ",receivedEntry); + if(receivedEntry){ + this.microServiceList = []; + this.getOnboardingServices(); + } + }); + } + + populateTableData(microServiceList: Array<IMircroservies>){ + this.dataSource = new MatTableDataSource(microServiceList); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + } + + applyFilter(filterValue: string) { + this.dataSource.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/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/portal-admins/new-portal-admin/new-portal-admin.component.html b/portal-FE-common/src/app/pages/portal-admins/new-portal-admin/new-portal-admin.component.html new file mode 100644 index 00000000..bdd99730 --- /dev/null +++ b/portal-FE-common/src/app/pages/portal-admins/new-portal-admin/new-portal-admin.component.html @@ -0,0 +1,54 @@ +<!-- + ============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"> + <app-search-users [searchTitle]="searchTitleText" [placeHolder]="placeholderText" (passBackSelectedUser)='changeSelectedUser($event)'></app-search-users> + </div> + <div class="modal-footer"> + <button type="submit" class="btn btn-primary" [disabled]='!changedSelectedUser' + (click)="addNewPortalAdmin(changedSelectedUser)">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/portal-admins/new-portal-admin/new-portal-admin.component.scss b/portal-FE-common/src/app/pages/portal-admins/new-portal-admin/new-portal-admin.component.scss new file mode 100644 index 00000000..7a773398 --- /dev/null +++ b/portal-FE-common/src/app/pages/portal-admins/new-portal-admin/new-portal-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/portal-admins/new-portal-admin/new-portal-admin.component.spec.ts b/portal-FE-common/src/app/pages/portal-admins/new-portal-admin/new-portal-admin.component.spec.ts new file mode 100644 index 00000000..cd85fca1 --- /dev/null +++ b/portal-FE-common/src/app/pages/portal-admins/new-portal-admin/new-portal-admin.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 { NewPortalAdminComponent } from './new-portal-admin.component'; + +describe('NewPortalAdminComponent', () => { + let component: NewPortalAdminComponent; + let fixture: ComponentFixture<NewPortalAdminComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NewPortalAdminComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NewPortalAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/portal-admins/new-portal-admin/new-portal-admin.component.ts b/portal-FE-common/src/app/pages/portal-admins/new-portal-admin/new-portal-admin.component.ts new file mode 100644 index 00000000..d5cde04b --- /dev/null +++ b/portal-FE-common/src/app/pages/portal-admins/new-portal-admin/new-portal-admin.component.ts @@ -0,0 +1,94 @@ +/*- + * ============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, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap'; +import { PortalAdmin } from 'src/app/shared/model/PortalAdmin'; +import { PortalAdminsService } from 'src/app/shared/services'; +import { HttpErrorResponse } from '@angular/common/http'; +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-portal-admin', + templateUrl: './new-portal-admin.component.html', + styleUrls: ['./new-portal-admin.component.scss'] +}) +export class NewPortalAdminComponent implements OnInit { + + constructor(public activeModal: NgbActiveModal, private portalAdminsService: PortalAdminsService, + public ngbModal: NgbModal) { } + @Input() title: string; + @Input() id: number; + changedSelectedUser: PortalAdmin; + closeResult: string; + searchTitleText = 'Enter First Name, Last Name or Org User Id'; + placeholderText = 'Search'; + @Output() passBackNewPortalAdmin: EventEmitter<any> = new EventEmitter(); + + ngOnInit() { + } + + changeSelectedUser(user: PortalAdmin) { + this.changedSelectedUser = user; + } + + addNewPortalAdmin(changedSelectedUser: PortalAdmin) { + const modalRef = this.ngbModal.open(InformationModalComponent); + modalRef.componentInstance.title = "Admin Update"; + modalRef.componentInstance.message = `Are you sure you want to add ${changedSelectedUser.firstName} ${changedSelectedUser.lastName} as a Portal Admin?`; + modalRef.result.then((result) => { + if (result === 'Ok') { + this.portalAdminsService.addPortalAdmin(this.changedSelectedUser.orgUserId).subscribe(_data => { + this.passBackNewPortalAdmin.emit(_data); + }, (_err: HttpErrorResponse) => { + this.passBackNewPortalAdmin.emit(_err); + const modalErrorRef = this.ngbModal.open(ConfirmationModalComponent); + modalErrorRef.componentInstance.title = "Error"; + if (_err.status === 409) { //Conflict + modalErrorRef.componentInstance.message = "This user already exists as a portal admin!"; + } else { + modalErrorRef.componentInstance.message = "There was a unknown problem adding the portal admin." + "Please try again later. Error Status: " + _err.status; + } + }); + } + }, (reason) => { + return; + }); + + } +} diff --git a/portal-FE-common/src/app/pages/portal-admins/portal-admins.component.html b/portal-FE-common/src/app/pages/portal-admins/portal-admins.component.html new file mode 100644 index 00000000..26e59e45 --- /dev/null +++ b/portal-FE-common/src/app/pages/portal-admins/portal-admins.component.html @@ -0,0 +1,87 @@ +<!-- + ============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">Portal Admins</h1> + </div> + <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)="addPortalAdminEntry()"><i class="icon ion-md-person-add"></i> + Add Portal Admin</button> + + <span class="onap-spinner" *ngIf="showSpinner"></span> + <table mat-table [dataSource]="dataSource" 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="loginId"> + <th id="col3" mat-header-cell *matHeaderCellDef mat-sort-header> User ID </th> + <td id="rowheader_t1_{{$index}}-loginId" mat-cell *matCellDef="let element; let i=index;"> {{element.loginId}} + </td> + </ng-container> + + <!-- Delete Column --> + <ng-container matColumnDef="delete"> + <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)="removePortalAdmin(element)"> + <i class="icon ion-md-trash"></i> + </span> + </td> + </ng-container> + + <tr [hidden]="portalAdmins.length === 0" mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> + </table> + <mat-paginator [hidden]="portalAdmins.length === 0" [pageSizeOptions]="[10, 20]" showFirstLastButtons></mat-paginator> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/portal-admins/portal-admins.component.scss b/portal-FE-common/src/app/pages/portal-admins/portal-admins.component.scss new file mode 100644 index 00000000..4c65595a --- /dev/null +++ b/portal-FE-common/src/app/pages/portal-admins/portal-admins.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/portal-admins/portal-admins.component.spec.ts b/portal-FE-common/src/app/pages/portal-admins/portal-admins.component.spec.ts new file mode 100644 index 00000000..0e3c969f --- /dev/null +++ b/portal-FE-common/src/app/pages/portal-admins/portal-admins.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 { PortalAdminsComponent } from './portal-admins.component'; + +describe('PortalAdminsComponent', () => { + let component: PortalAdminsComponent; + let fixture: ComponentFixture<PortalAdminsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PortalAdminsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PortalAdminsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/portal-admins/portal-admins.component.ts b/portal-FE-common/src/app/pages/portal-admins/portal-admins.component.ts new file mode 100644 index 00000000..5dfe026d --- /dev/null +++ b/portal-FE-common/src/app/pages/portal-admins/portal-admins.component.ts @@ -0,0 +1,124 @@ +/*- + * ============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 { OnInit, Component, ViewChild, NgModuleRef } from '@angular/core'; +import { MatSort, MatPaginator } from '@angular/material'; +import { MatTableDataSource } from '@angular/material'; +import { PortalAdminsService } from 'src/app/shared/services'; +import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap'; +import { NewPortalAdminComponent } from './new-portal-admin/new-portal-admin.component'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; +import { PortalAdmin } from 'src/app/shared/model/PortalAdmin'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; + +@Component({ + selector: 'app-portal-admins', + templateUrl: './portal-admins.component.html', + styleUrls: ['./portal-admins.component.scss'] +}) +export class PortalAdminsComponent implements OnInit { + portalAdmins: PortalAdmin[] = []; + showSpinner = true; + closeResult: string; + constructor(private portalAdminsService: PortalAdminsService, private ngbModal: NgbModal) { } + displayedColumns: string[] = ['firstName', 'lastName', 'loginId', 'delete']; + dataSource = new MatTableDataSource(this.portalAdmins); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + ngOnInit() { + this.getAllPortalAdmins(); + } + + getAllPortalAdmins() { + this.portalAdminsService.getPortalAdmins().subscribe((_data: PortalAdmin[]) => { + this.showSpinner = false; + // _data is the array of data that you getting from the db. + this.portalAdmins = _data; + this.dataSource = new MatTableDataSource(this.portalAdmins); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + }, (_err: HttpErrorResponse) =>{ + 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.'; + } + }) + } + + applyFilter(filterValue: string) { + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + addPortalAdminEntry() { + const modalRef = this.ngbModal.open(NewPortalAdminComponent); + modalRef.componentInstance.title = 'Add Portal Admin'; + modalRef.componentInstance.id = 1; + modalRef.componentInstance.passBackNewPortalAdmin.subscribe((_result: any) => { + modalRef.close(); + this.showSpinner = true; + this.getAllPortalAdmins(); + }) + } + + removePortalAdmin(deletePortalAdmin: any) { + const modalRef = this.ngbModal.open(InformationModalComponent); + modalRef.componentInstance.title = 'Confirmation'; + modalRef.componentInstance.message = `Are you sure you want to delete ${deletePortalAdmin.firstName} ${deletePortalAdmin.lastName} ?`; + modalRef.result.then((result) => { + if (result === 'Ok') { + this.portalAdminsService.removePortalAdmin(deletePortalAdmin.userId, deletePortalAdmin.loginId).subscribe(_data => { + this.showSpinner = true; + this.getAllPortalAdmins(); + }) + } + }, (reason) => { + this.closeResult = `Dismissed ${this.getDismissReason(reason)}`; + }); + } + + private getDismissReason(reason: any): string { + if (reason === ModalDismissReasons.ESC) { + return 'by pressing ESC'; + } else if (reason === ModalDismissReasons.BACKDROP_CLICK) { + return 'by clicking on a backdrop'; + } else { + return `with: ${reason}`; + } + } +}
\ No newline at end of file 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/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.html b/portal-FE-common/src/app/pages/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.html new file mode 100644 index 00000000..01e27a99 --- /dev/null +++ b/portal-FE-common/src/app/pages/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.html @@ -0,0 +1,117 @@ +<!-- + ============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">Web Analytics Report Details</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="app-properties-main"> + <div [hidden]="isEditMode" style="width: 75%;"> + <!--<div id='ecomp-small-spinner' class="ecomp-small-spinner" *ngIf="isAllApplications"></div>--> + <div *ngIf="isAllApplications"> + <span runat="server" ID="required" style="color: Red;" + visible="false">*</span> + <mat-form-field class="web-analytics-applications-select"> + <mat-label> Application Name </mat-label> + <mat-select name="web-analytics-application-select" id="application" + name="applicationName" [(ngModel)]="userTableAppReport.appName" [(value)]="selected"> + <mat-option *ngFor="let d of allApplications" [value]="d" >{{d.appName}}</mat-option> + </mat-select> + </mat-form-field> + </div> + <div id="web-analytics-message-required" + *ngIf="!userTableAppReport.appName && userTableAppReport.appName === ''"> + <small class="mandatory-categories" + style="color: Red; font-size: 11px;">Application Name is Required</small> + </div> + </div> + + <div class="property-label" *ngIf="isEditMode"> + <div class="property-label"> + <span runat="server" ID="required" style="color: Red;" visible="false"> *</span>Application Name : + </div> + <input id="input-Application-name" type="text" ddh-reset + [(ngModel)]="userTableAppReport.appName" style="width: 75%;" + name="appId" [disabled]="isEditMode" /> + <div id="web-analytics-message-required" + ng-show="(userTableAppReport.appName && (userTableAppReport.appName.length == 0 ||! userTableAppReport.appName))"> + <small class="mandatory-categories" style="color: Red; font-size: 11px;">Application ID is Required</small> + </div> + </div> + + <div class="property-label"> + <div class="property-label"> + <span runat="server" ID="required" style="color: Red;" visible="false"> *</span>Report Name : + </div> + <input id="input-report-name" type="text" ddh-reset [(ngModel)]="userTableAppReport.reportName" style="width: 75%;" /> + <div id="web-analytics-message-required" + [hidden]="(userTableAppReport.reportName && (userTableAppReport.reportName|| userTableAppReport.reportName.length == 0))"> + <small class="mandatory-categories" + style="color: Red; font-size: 11px;">Report Name is Required</small> + </div> + </div> + + <div class="property-label"> + <div class="property-label"> + <span runat="server" ID="required" style="color: Red;" visible="false"> *</span>Report URL : + </div> + <input id="input-report-url" type="text" ddh-reset [(ngModel)]="userTableAppReport.reportSrc" style="width: 75%;" /> + <div id="web-analytics-message-required" + [hidden]="(userTableAppReport.reportSrc && (userTableAppReport.reportSrc || userTableAppReport.reportSrc.length == 0))"> + <small class="mandatory-categories" style="color: Red; font-size: 11px;">Report URL is Required</small> + </div> + </div> + </div> + </div> + + <!--Modal Footer goes Here--> + <div class="modal-footer"> + <button type="submit" class="btn btn-primary" + [disabled]="(userTableAppReport.appName && userTableAppReport.appName === '')||(userTableAppReport.reportName && (!userTableAppReport.reportName + ||userTableAppReport.reportName.length == 0))||(userTableAppReport.reportSrc && (userTableAppReport.reportSrc.length == 0 + || !userTableAppReport.reportSrc))"(click)="saveChanges()">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/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.scss b/portal-FE-common/src/app/pages/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.scss new file mode 100644 index 00000000..7a773398 --- /dev/null +++ b/portal-FE-common/src/app/pages/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.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/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.spec.ts b/portal-FE-common/src/app/pages/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.spec.ts new file mode 100644 index 00000000..4dc85724 --- /dev/null +++ b/portal-FE-common/src/app/pages/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.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 { WebAnalyticsDetailsDialogComponent } from './web-analytics-details-dialog.component'; + +describe('WebAnalyticsDetailsDialogComponent', () => { + let component: WebAnalyticsDetailsDialogComponent; + let fixture: ComponentFixture<WebAnalyticsDetailsDialogComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WebAnalyticsDetailsDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WebAnalyticsDetailsDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.ts b/portal-FE-common/src/app/pages/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.ts new file mode 100644 index 00000000..3b5a23ef --- /dev/null +++ b/portal-FE-common/src/app/pages/web-analytics/web-analytics-details-dialog/web-analytics-details-dialog.component.ts @@ -0,0 +1,156 @@ +/*- + * ============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 { WebAnalyticsService } from 'src/app/shared/services'; + +@Component({ + selector: 'app-web-analytics-details-dialog', + templateUrl: './web-analytics-details-dialog.component.html', + styleUrls: ['./web-analytics-details-dialog.component.scss'] +}) +export class WebAnalyticsDetailsDialogComponent implements OnInit { + + result: any; + isEditMode: boolean = false; + isAllApplications: boolean = true; + emptyImg = null; + allApplications: any = []; + allApps: any = []; + + newAppModel = { + 'appId': null, + 'appName':null, + 'reportName': null, + 'reportSrc': null, + 'resourceId': null + }; + + newApp={ + 'appId': '', + 'appName':'' + } + + constructor(public activeModal: NgbActiveModal, public ngbModal: NgbModal, + public webAnalyticsService : WebAnalyticsService) { } + + @Input() userTableAppReport: any; + @Output() passEntry: EventEmitter<any> = new EventEmitter(); + + ngOnInit() { + this.isAllApplications = true; + if(this.userTableAppReport.appName){ + this.isEditMode = true; + }else{ + this.isEditMode = false; + } + //console.log("IsEditMode in Web analytycs Dialog :: ",this.isEditMode) + this.getAllApplications(); + } + + getAllApplications(){ + this.isAllApplications = true; + this.webAnalyticsService.getAllApplications() + .subscribe(_data => { + this.result = _data; + if (this.result == null || this.result == 'undefined') { + //console.log('WebAnalyticsService::getAllApplications Failed: Result or result.data is null'); + }else { + for (let i = 0; i < this.result.length; i++) { + var application = { + appId : this.result[i].id, + appName: this.result[i].name, + enabled : this.result[i].enabled, + restrictedApp :this.result[i].restrictedApp, + }; + this.allApps.push(application); + } + for (let i = 0; i < this.allApps.length; i++) { + if((this.allApps[i].enabled == true && this.allApps[i].restrictedApp == false) || (this.allApps[i].appId == 1) ) { + var validApplication = { + appId : this.allApps[i].appId, + appName: this.allApps[i].appName, + }; + this.allApplications.push(validApplication); + } + } + } + }, error =>{ + this.isAllApplications = false; + console.log(error); + }); + } + + saveChanges(){ + //console.log("Save Changes Called."); + let selectedApplication = this.userTableAppReport.appName; + this.newAppModel.appId = selectedApplication.appId; + this.newAppModel.appName = selectedApplication.appName; + this.newAppModel.reportName = this.userTableAppReport.reportName; + this.newAppModel.reportSrc = this.userTableAppReport.reportSrc; + this.newAppModel.resourceId = this.userTableAppReport.resourceId; + + if (this.isEditMode) { + this.newAppModel.appId = this.userTableAppReport.appId; + //console.log("Update Analytics..newAppModel :: ",this.userTableAppReport); + this.webAnalyticsService.updateWebAnalyticsReport(this.newAppModel) + .subscribe(_data => { + this.result = _data; + //console.log("Update Analytics Response:: ",this.result); + this.passEntry.emit(this.result); + this.ngbModal.dismissAll(); + }, error =>{ + this.isAllApplications = false; + console.log(error); + }); + }else{ + //console.log("Save Analytics.newAppModel :: ",this.userTableAppReport); + this.webAnalyticsService.save(this.newAppModel) + .subscribe(_data => { + this.result = _data; + //console.log("Save Analytics Response:: ",this.result); + this.passEntry.emit(this.result); + this.ngbModal.dismissAll(); + }, error =>{ + this.isAllApplications = false; + console.log(error); + }); + } + } +} diff --git a/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.html b/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.html new file mode 100644 index 00000000..4266718b --- /dev/null +++ b/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.html @@ -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============================================ + + + --> + <div class="container"> + <div class="ecomp-main-view-title"> + <h1 class="heading-page">Web Analytics Onboarding</h1> + </div> + + <mat-form-field> + <input matInput type="text" (keyup)="applyFilter($event.target.value)" placeholder="Search in entire table"> + </mat-form-field> + + <button type="button" style="float: right;" class="btn btn-primary" (click)="openWebAnalyticsModal('')"> + <i class="icon ion-md-person-add"></i>Add Web Analytics Report + </button> + + <div class="webanalytics-table"> + <table mat-table [dataSource]="dataSource" matSort> + <!-- Account Name Column --> + <ng-container matColumnDef="applicationName"> + <th id="col1" mat-header-cell *matHeaderCellDef> Application Name </th> + <td (click)="openWebAnalyticsModal(element)" id="rowheader_t1_{{i}}-applicationName" + mat-cell *matCellDef="let element; let i = index;"> {{element.appName}} + </td> + </ng-container> + + <!-- Report Name Column --> + <ng-container matColumnDef="reportName"> + <th id="col2" mat-header-cell *matHeaderCellDef> Report Name </th> + <td (click)="openWebAnalyticsModal(element)" id="rowheader_t1_{{i}}-reportName" + mat-cell *matCellDef="let element; let i=index;"> {{element.reportName}} </td> + </ng-container> + + <!-- Report URL Column --> + <ng-container matColumnDef="reportURL"> + <th id="col2" mat-header-cell *matHeaderCellDef> Report URL </th> + <td (click)="openWebAnalyticsModal(element)" id="rowheader_t1_{{i}}-reportURL" + mat-cell *matCellDef="let element; let i=index;"> {{element.reportSrc}} </td> + </ng-container> + + <!-- Delete Column --> + <ng-container matColumnDef="delete"> + <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)="deleteWebAnalyticsReport(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> +</div> +
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.scss b/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.scss new file mode 100644 index 00000000..c72d11e5 --- /dev/null +++ b/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.scss @@ -0,0 +1,51 @@ +/*- + * ============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{ + overflow-y: auto; +} + +.container .webanalytics-table th{ + padding-bottom: 15px; + padding-right: 40px; + font-weight: bold; +} + +.ion-md-trash{ + cursor: pointer; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.spec.ts b/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.spec.ts new file mode 100644 index 00000000..76d44cc0 --- /dev/null +++ b/portal-FE-common/src/app/pages/web-analytics/web-analytics.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 { WebAnalyticsComponent } from './web-analytics.component'; + +describe('WebAnalyticsComponent', () => { + let component: WebAnalyticsComponent; + let fixture: ComponentFixture<WebAnalyticsComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WebAnalyticsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WebAnalyticsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.ts b/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.ts new file mode 100644 index 00000000..eb260911 --- /dev/null +++ b/portal-FE-common/src/app/pages/web-analytics/web-analytics.component.ts @@ -0,0 +1,240 @@ +/*- + * ============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, Input} from '@angular/core'; +import { WebAnalyticsService } from '../../shared/services/index'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { MatTableDataSource } from '@angular/material'; +import { MatSort, MatPaginator } from '@angular/material'; +import { WebAnalyticsDetailsDialogComponent } from './web-analytics-details-dialog/web-analytics-details-dialog.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-web-analytics', + templateUrl: './web-analytics.component.html', + styleUrls: ['./web-analytics.component.scss'] +}) +export class WebAnalyticsComponent implements OnInit { + + application: any; + isAppSelectDisabled: boolean; + selectApp: any; + sortedApps: any = []; + userAppReports: any = []; + userTableAppReports: any= []; + userJourneyAnalytics: any = []; + userApps: any = []; + result: any; + isEditMode: boolean = false; + + displayedColumns: string[] = ['applicationName', 'reportName', 'reportURL','delete']; + dataSource = new MatTableDataSource(this.userTableAppReports); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + + constructor(public webAnalyticsService: WebAnalyticsService, public ngbModal: NgbModal) { } + + ngOnInit() { + + this.application = null; + this.isAppSelectDisabled = false; + this.selectApp = 'All Applications'; + this.sortedApps = [{ + index: 0, + appName: 'All Applications', + value: 'All Applications' + }]; + + this.getAllWebAnalyticsReport(); + //this.getUserApps(); + + } + + getAllWebAnalyticsReport(){ + //console.log("getAllWebAnalyticsReport called"); + this.webAnalyticsService.getAllWebAnalyticsReport() + .subscribe(_data => { + this.result = _data; + if (this.result == null || this.result == 'undefined') { + console.log('WebAnalyticsService::getAllWebAnalyticsReport Failed: Result or result.data is null'); + }else { + //console.log('WebAnalyticsService::getAllWebAnalyticsReport', this.result); + for (let i = 0; i < this.result.length; i++) { + var userTableAppReport = { + reportName: this.result[i].reportName, + reportSrc: this.result[i].reportSrc, + appName: this.result[i].appName, + appId : this.result[i].appId, + resourceId : this.result[i].resourceId + }; + this.userTableAppReports.push(userTableAppReport); + } + this.populateTableData(this.userTableAppReports); + } + }, error =>{ + console.log(error); + }); + } + + deleteWebAnalyticsReport(deleteObj: any){ + let confirmationMsg = 'You are about to delete this Web Analytics : ' + deleteObj.reportName+ '. Click OK to continue.'; + this.openInformationModal("Confirmation",confirmationMsg).result.then((result) => { + if (result === 'Ok') { + this.userTableAppReports.splice(this.userTableAppReports.indexOf(deleteObj), 1); + this.webAnalyticsService.deleteWebAnalyticsReport(deleteObj) + .subscribe(_data => { + this.userTableAppReports = []; + this.getAllWebAnalyticsReport(); + }, error =>{ + console.log(error); + }); + } + }, (resut) => { + return; + }) + } + + getUserApps(){ + //console.log("getUserApps called"); + this.webAnalyticsService.getWebAnalyticsAppReports() + .subscribe(_data => { + this.result = _data; + if (this.result == null || this.result == 'undefined') { + console.log('WebAnalyticsService::getServiceList Failed: Result or result.data is null'); + }else { + for (let i = 0; i < this.result.length; i++) { + var userAppReport = { + sizeX: 3, + sizeY: 2, + reportName: this.result[i].reportName, + reportSrc: this.result[i].reportSrc, + appName: this.result[i].appName, + }; + + if(this.result[i].reportSrc.includes("appName")){ + let appName = this.result[i].reportSrc.split("appName=").splice(-1)[0]; + this.webAnalyticsService.getUserJourneyList(appName) + .subscribe(_data => { + let userJourneyReports = { + sizeX: 6, + sizeY: 3, + title: appName+" User journey", + analyticsList: this.result + }; + this.userJourneyAnalytics.push(userJourneyReports); + }, error =>{ + console.log(error); + }); + + } + this.userAppReports.push(userAppReport); + } + + for (var i = 0; i < this.result.length; i++) { + var userApp = { + appName: this.result[i].appName, + }; + + this.userApps.push(userApp); + } + /*angular.forEach($scope.userApps, function(value, key) { + var index = $scope.uniqueUserApps.indexOf(value.appName); + + if (index === -1) { + $scope.uniqueUserApps.push(value.appName); + } + }); + + + for (let i = 1; i <= $scope.uniqueUserApps.length; i++) { + this.sortedApps.push({ + index: i, + appName: $scope.uniqueUserApps[i - 1], + value: $scope.uniqueUserApps[i - 1] + }); + }*/ + } + }, error =>{ + //console.log(error); + }); + } + + openWebAnalyticsModal(rowData: any) { + const modalRef = this.ngbModal.open(WebAnalyticsDetailsDialogComponent, { size: 'lg' }); + //console.log("selectedData in parent",rowData); + if(rowData != 'undefined' && rowData){ + modalRef.componentInstance.userTableAppReport = rowData; + this.isEditMode = true; + }else{ + modalRef.componentInstance.userTableAppReport = {}; + this.isEditMode = false; + } + modalRef.componentInstance.passEntry.subscribe((receivedEntry: any) => { + if(receivedEntry.httpStatusCode && receivedEntry.httpStatusCode ==='200' || + receivedEntry.status && receivedEntry.status === 'OK'){ + this.userTableAppReports = []; + this.getAllWebAnalyticsReport(); + } + }); + } + + + populateTableData(userTableAppReports: Array<Object>){ + this.dataSource = new MatTableDataSource(userTableAppReports); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + } + + applyFilter(filterValue: string) { + this.dataSource.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/widget-catalog/widget-catalog.component.html b/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.html new file mode 100644 index 00000000..927e264c --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.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 id="page-content"> + <div id="title"> + <h1>{{'Widget Catalog'}}</h1> + </div> + <div> + <div> + <div id="widgets"> + + + <div style="height: 100%"> + <gridster [options]="options" class="appCatalogue-boarder"> + + + <div *ngFor="let item of layout"> + <gridster-item [item]="item" > + <div class="gridster-box"> + <div class="gridster-box-header"> + + <mat-icon class="icon-content-gridguide">drag_handle</mat-icon> + <h3>{{ item.name | elipsis: 13}}</h3> + <span style="position: absolute;right:1em"> + <label id="widget-checkbox-label" class="checkbox"> + + <input type="checkbox" id="{{item.name.split(' ').join('-')}}-checkbox" + [(ngModel)]="item.select" (ngModelChange)="storeSelection(item)" /> + + + <i class="skin"></i> + </label> + </span> + + + </div> + <div class="gridster-box-content" > + <app-dynamic-widget [widgetType] = "item.name"></app-dynamic-widget> + </div> + + </div> + </gridster-item> + </div> + + + + </gridster> + </div> + </div> + </div> + <div id="widgets-disclaimer" class="w-ecomp-main-disclaimer"> + To request access to an application widget, please visit the <a>Get Access</a> page. + </div> + </div> + </div> +</div> +<div> + <div> + + </div> + <div> + <div></div> + <div></div> + </div> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.scss b/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.scss new file mode 100644 index 00000000..cbdfd12b --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.scss @@ -0,0 +1,119 @@ +/* + * ============LICENSE_START========================================== + * ONAP Portal SDK + * =================================================================== + * 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============================================ + * + * + */ + + + +:host { .toolbar { + height: 100px; + display: flex; + } gridster { + display: flex; + height: calc(100vh - 115px); + flex-direction: column; + }} + .icon-content-gridguide{ + cursor:move; + font-size: 22px; + } + .form-row { + margin-top: -14px; + } + .griditem-header{ + + + border-bottom: 1px; + border-bottom-color: solid gray; + + + + } + +.checkbox, .radio { + min-height: 10px; + padding-left: 0px; +} + +.appCatalogue-boarder{ + background-color: #eee; + border: 1px dashed white; + height: 100%; + + overflow-y: auto; + overflow-x: hidden; + } + + .gridster-box { + height: 100%; + border: 1px solid #ccc; + background-color: #fff; + transition: transform 0.5s ease-out; +} +.gridster-box-header { + background-color: #fff; + padding: 0 0px 0 10px; + border-bottom: 1px solid #ccc; + position: relative; + height: 50px !important; +} +.gridster-box-header h3 { + margin-top: 15px; + display: inline-block; + font-size: 70%; + font-family: "Omnes-ECOMP-W02", Arial; +} +.gridster-box-content { + //padding: 59px; + overflow-y: scroll; + height: 100%; + +} + +.gridster-box-header-btns { + top: 15px; + right: 10px; + position: absolute; +} +.checkbox input{ + margin: 6px; + left: 112px; + top: -20px; + } +.checkbox .skin { + left: 125px; + top: -18px; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.spec.ts b/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.spec.ts new file mode 100644 index 00000000..83de0040 --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WidgetCatalogComponent } from './widget-catalog.component'; + +describe('WidgetCatalogComponent', () => { + let component: WidgetCatalogComponent; + let fixture: ComponentFixture<WidgetCatalogComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WidgetCatalogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WidgetCatalogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.ts b/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.ts new file mode 100644 index 00000000..c36f9e11 --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-catalog/widget-catalog.component.ts @@ -0,0 +1,85 @@ +import { Component, OnInit } from '@angular/core'; +import { GridsterConfig, GridsterItem } from 'angular-gridster2'; +import { WidgetCatalogService } from '../../shared/services/widget-catalog/widget-catalog.service'; +import { IWidgetCatalog } from '../../shared/model/widget-catalog.model'; +import { Observable } from 'rxjs'; +import { inflateRaw } from 'zlib'; +import { UsersService } from 'src/app/shared/services/users/users.service'; + + +@Component({ + selector: 'app-widget-catalog', + templateUrl: './widget-catalog.component.html', + styleUrls: ['./widget-catalog.component.scss'] +}) +export class WidgetCatalogComponent implements OnInit { + widgetCatalogData: IWidgetCatalog[]; + isCommonError: boolean = false; + isApiRunning: boolean = true; + userName: string; + + + get options(): GridsterConfig { + return this.widgetCatalogService.options; + } get layout(): GridsterItem[] { + return this.widgetCatalogService.layout; + } constructor(private widgetCatalogService: WidgetCatalogService, private userService: UsersService) { + + } + + ngOnInit() { + const widgetCatalogObservable = this.widgetCatalogService.getWidgetCatalog(); + this.widgetCatalogService.layout = []; + this.getUserWidgets(this.userName); + } + + + getUserWidgets(loginName: string) { + const widgetCatalogUserObservable = this.userService.getUserProfile(); + widgetCatalogUserObservable.subscribe((userProfile: any) => { + //console.log('UserProfile is ' + userProfile); + if (userProfile) { + const widgetCatalogObservable = this.widgetCatalogService.getUserWidgets(userProfile.orgUserId); + widgetCatalogObservable.subscribe(data => { + //console.log("What is coming from backend" + JSON.parse(data)); + this.widgetCatalogData = data; + console.log(this.widgetCatalogData); + for (let entry of this.widgetCatalogData) { + if (entry[1] == 'Events' || entry[1] == 'News' || entry[1] == 'Resources') { + var appCatalog = { + id: entry[0], + name: entry[1], + headerName: entry[2], + select: (entry[4] == 'S' || entry[4] === null) ? true : false + }; + this.widgetCatalogService.addItem(appCatalog); + } + } + }); + } + }); + + } + + getUserProfile(): Observable<any> { + const widgetCatalogObservable = this.userService.getUserProfile(); + return widgetCatalogObservable; + } + + storeSelection(widgetCatalogData: any) { + console.log("Store selection called " + widgetCatalogData.select); + if (widgetCatalogData) { + var appData = { + widgetId: widgetCatalogData.id, + select: widgetCatalogData.select, + pending:false + }; + this.widgetCatalogService.updateWidgetCatalog(appData).subscribe(data => { + //console.log("Update App sort data" + data); + }, error => { + console.log('updateWidgetCatalog error' + error); + }); + } + + } +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.html b/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.html new file mode 100644 index 00000000..bd87e699 --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.html @@ -0,0 +1,120 @@ +<!-- + ============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">Widget Details</h4> + <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross')"> + <span aria-hidden="true">×</span> + </button> + </div> + + <!--Modal Body goes here--> + <form id="widgets-details-form" name="widgetForm" [formGroup] = "uploadForm" (ngSubmit)="saveChanges()" + enctype="multipart/form-data" novalidate autocomplete="off"> + <div class="modal-body"> + <div class="widget-model-body"> + <div class="item-label">Widget Name</div> + <input id="widgets-details-input-name" class="widget-name-field" + type="text" formControlName="widgetName" [(ngModel)]="widget.name" + ng-pattern="/^[\w -]*$/" maxlength="100" + ng-disabled="widgetOnboardingDetails.isEditMode" required /> + + <div class="item-label" style="padding-top: 20px">Widget Description</div> + + <textarea b2b-reset b2b-reset-textarea class="widgets-details-input-desc" + formControlName="description" [(ngModel)]="widget.desc" type="text" maxlength="200"> + </textarea> + + <div class="table-dropdown"> + <mat-form-field class="widget-service-select"> + <mat-label> Service Endpoint </mat-label> + <mat-select name="widget-service-select" id="serviceEndPoint" + formControlName="serviceEndPoint" [(ngModel)]="widget.serviceURL" [disabled]="isEditMode"> + <mat-option *ngFor="let rowData of availableMicroServices" [value]="rowData.id" >{{rowData.option}}</mat-option> + </mat-select> + </mat-form-field> + </div> + + <div class="property-label checkbox-label" style="padding-top: 20px"> + <mat-checkbox formControlName="allowAllUser" type="checkbox" [(ngModel)]="widget.allUser" + id="allow-all-user"> + Allow all user access + </mat-checkbox> + </div> + + <div [hidden]="widget.allUser"> + <div class="table-dropdown"> + <mat-form-field class="widget-applications-select"> + <mat-label> Application Name </mat-label> + <mat-select multiple name="widget-application-select" id="application" + formControlName="applicationName" [(ngModel)]="widget.appName"> + <mat-option *ngFor="let rowData of availableApps" [value]="rowData.id" >{{rowData.name}}</mat-option> + </mat-select> + </mat-form-field> + </div> + </div> + + <div [hidden]="widget.allUser"> + <div class="table-dropdown"> + <mat-form-field class="widget-roles-select"> + <mat-label> User Role Name </mat-label> + <mat-select multiple name="widget-role-select" id="roles" + formControlName="applicationRole" [(ngModel)]="widget.widgetRoles"> + <mat-option *ngFor="let appRole of availableApps.roles" [value]="appRole.id" >{{appRole.name}}</mat-option> + </mat-select> + </mat-form-field> + </div> + </div> + + <div class="item-label widget-upload">Upload Widget</div> + <div> + <input id="widget-onboarding-details-upload-file" + name="profile" type="file" + class="widget-onboarding-details-upload-file ht" + (change)="onFileSelect($event)"/> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="submit" class="btn btn-primary"[disabled]="(isFileNotSelected && !isEditMode)">Save</button> + <button type="button" class="btn btn-primary" (click)="activeModal.close('Close')">Cancel</button> + </div> + </form> +</div>
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.scss b/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.scss new file mode 100644 index 00000000..7f5b871e --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.scss @@ -0,0 +1,82 @@ +/*- + * ============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============================================ + * + * + */ + +::ng-deep .modal-dialog { + max-width: 700px; + width: 619px; + overflow-x: auto; + overflow-y: auto; +} + +::ng-deep .widget-applications-select { + width: 34em; +} + +::ng-deep .widget-roles-select { + width: 34em; + } + +::ng-deep .widget-service-select { + width: 34em; + } + +.widget-name-field{ + width: 22em; + margin-top: 5px; +} + +.widgets-details-input-desc { + overflow: auto; + resize: vertical; + width: 34em; + margin-top: 5px; +} + +.widget-upload{ + margin-top: 16px; +} + +#applicationName{ + width: 100%; + margin-top: 5px; +} + +#serviceEndPoint{ + width: 100%; + margin-top: 5px; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.spec.ts b/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.spec.ts new file mode 100644 index 00000000..411da1e9 --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.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 { WidgetDetailsDialogComponent } from './widget-details-dialog.component'; + +describe('WidgetDetailsDialogComponent', () => { + let component: WidgetDetailsDialogComponent; + let fixture: ComponentFixture<WidgetDetailsDialogComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WidgetDetailsDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WidgetDetailsDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.ts b/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.ts new file mode 100644 index 00000000..a6d6115f --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-onboarding/widget-details-dialog/widget-details-dialog.component.ts @@ -0,0 +1,429 @@ +/*- + * ============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 { IWidget } from 'src/app/shared/model/widget-onboarding/widget'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { WidgetOnboardingService, MicroserviceService, AdminsService, ApplicationsService } from 'src/app/shared/services'; +import { FormBuilder, FormGroup, FormControl } from '@angular/forms'; +import { IMircroservies } from 'src/app/shared/model/microservice-onboarding/microservices'; +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-widget-details-dialog', + templateUrl: './widget-details-dialog.component.html', + styleUrls: ['./widget-details-dialog.component.scss'] +}) +export class WidgetDetailsDialogComponent implements OnInit { + @Input() public widget: IWidget; + @Input() public availableMicroServices: Array<IMircroservies>; + @Input() public applicationList: Array<Object>; + + @Output() passEntry: EventEmitter<any> = new EventEmitter(); + + widgetsList: any; + uploadForm: FormGroup; + result: any; + selected: any; + isEditMode: boolean = false; + hasSelectedApp: boolean = false; + availableApps = []; + availableRoles = []; + allRoleSelected: boolean = false; + appCounter = 0; + duplicatedName:boolean = true; + widgetFileTypeError:boolean = false; + isFileNotSelected:boolean = true; + + constructor(public widgetOnboardingService: WidgetOnboardingService, + public microservice: MicroserviceService, public applicationsService: ApplicationsService, + public formBuilder: FormBuilder, public activeModal: NgbActiveModal, + public ngbModal: NgbModal, public adminsService: AdminsService) { } + + ngOnInit() { + this.widget.allUser = true; + this.getOnboardingWidgets(); + this.getAvailableApps(); + this.duplicatedName = true; + this.allRoleSelected = false; + this.appCounter = 0; + this.uploadForm = this.formBuilder.group({ + profile: [''], + widgetName:[''], + description:[''], + serviceEndPoint:[''], + allowAllUser:[''], + applicationName:[''], + applicationRole:[''] + }); + + if(this.widget && this.widget.name){ + this.isEditMode = true; + } + if(this.isEditMode && this.widget && this.widget.allowAllUser == "Y"){ + this.widget.allUser = true; + }else if(this.isEditMode && this.widget && this.widget.allowAllUser == "N"){ + this.widget.allUser = false; + } + if(this.widget && this.widget.serviceId != null){ + this.widget.serviceURL = this.widget.serviceId; + } + } + + //Add Or Update Widget. + saveChanges(){ + if(this.widget.name == '' || this.widget.name == undefined){ + this.openConfirmationModal("Error",'Widget Name is required.'); + return; + } + + if(!this.isEditMode){ + this.updateWidgetName(); + } + + if(this.duplicatedName == false){ + this.openConfirmationModal("Error",'Name not available - please choose different name.'); + return; + } + + if(this.widgetFileTypeError == true){ + this.openConfirmationModal("Error",'File must be .zip'); + return; + } + + let widgetName = this.widget.name; + let description = this.widget.desc + let file_loc = widgetName + ".zip"; + const formData = new FormData(); + formData.append('file', this.uploadForm.get('profile').value); + //console.log("FormData >>>>::> ",formData.get('file')); + + /*if((formData == undefined && !this.isEditMode) || + (!this.widget.allUser && this.appCounter == 0) || + this.widget.name == null || + (!this.widget.allUser && !this.allRoleSelected)){ + console.log("return from 2nd check"); + return; + }*/ + + let selectedRoles = []; + + if(!this.widget.allUser){ + for(var i = 0; i < this.availableApps.length; i++){ + if(this.availableApps[i].isSelected){ + for(var n = 0; n < this.availableApps[i].roles.length; n++) { + if(this.availableApps[i].roles[n].isSelected){ + var role = { + app: {appId: this.availableApps[i].id}, + roleId: this.availableApps[i].roles[n].id, + roleName: this.availableApps[i].roles[n].name, + }; + selectedRoles.push(role); + } + } + } + } + } + + let allowAllUser = 0; + if(this.widget.allUser){ + allowAllUser = 1; + } + + let serviceId = null; + if(this.widget.serviceURL != null && this.widget.serviceURL != undefined){ + serviceId = parseInt(this.widget.serviceURL); + } + var newWidget = { + name: widgetName, + desc: description, + widgetRoles: selectedRoles, + fileLocation: file_loc, + allowAllUser: allowAllUser, + serviceId: serviceId + }; + + if(this.isEditMode){ + //console.log("widget in updateWidget :::: >>> ",newWidget); + if(formData && formData.get('file')){ + this.widgetOnboardingService.updateWidgetWithFile(formData, this.widget.id, newWidget) + .subscribe( _data => { + this.result = 'updated'; + this.passEntry.emit(this.result); + }, error => { + console.log(error); + this.openConfirmationModal("Error",'Could not update. Please retry.'); + }); + }else{ + this.widgetOnboardingService.updateWidget(this.widget.id, newWidget) + .subscribe( _data => { + this.result = 'updated'; + this.passEntry.emit(this.result); + }, error => { + this.openConfirmationModal("Error",'Could not update. Please retry.'); + console.log(error); + }); + } + }else{ + //console.log("newWidget in createWidget :::: >>> ",newWidget); + this.widgetOnboardingService.createWidget(newWidget, formData) + .subscribe( _data => { + this.result = 'created'; + this.passEntry.emit(this.result); + }, error => { + this.openConfirmationModal("Error",'Could not save. Please retry.'); + console.log(error); + }); + } + this.ngbModal.dismissAll(); + } + + onFileSelect(event) { + this.widgetFileTypeError = false; + this.isFileNotSelected = false; + if (event.target.files.length > 0) { + const file = event.target.files[0]; + //console.log("onFileSelect called.. ",file); + var fileName = file.name; + var validFormats = ['zip']; + var ext = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(); + if(validFormats.indexOf(ext) == -1){ + this.widgetFileTypeError = true; + } + this.uploadForm.get('profile').setValue(file); + } + } + + appUpdate(){ + this.hasSelectedApp = false; + this.appCounter = 0; + for(var i = 0; i < this.availableApps.length; i++){ + if(this.availableApps[i].isSelected){ + this.appCounter++; + if(!this.hasSelectedApp) + this.hasSelectedApp = true; + } + if(this.availableApps[i].isSelected + && this.availableApps[i].roles.length == 0){ + var index = i; + this.availableRoles = []; + this.adminsService.getRolesByApp(this.availableApps[i].id) + .subscribe( roles => { + if(roles && roles.length >0){ + for(var i = 0; i < roles.length; i++){ + this.availableRoles.push({ + id: roles[i].id, + name: roles[i].name, + isSelected: false, + }); + } + } + this.availableApps[index].roles = this.availableRoles; + }, error => { + console.log(error); + }); + } + } + this.allRoleSelected = true; + this.checkRoleSelected(); + } + + roleUpdate(app){ + this.allRoleSelected = true; + for(var i = 0; i < app.roles.length; i++){ + if(app.roles[i].isSelected){ + app.roleSelected = true; + this.checkRoleSelected(); + return; + } + } + app.roleSelected = false; + this.checkRoleSelected(); + } + + checkRoleSelected(){ + for(var i = 0; i < this.availableApps.length; i++){ + if(this.availableApps[i].isSelected + && !this.availableApps[i].roleSelected){ + this.allRoleSelected = false; + return; + } + } + } + + getAppName = function(appId){ + for(var i = 0; i < this.availableApps.length; i++){ + if(this.availableApps[i].id == appId){ + return this.availableApps[i].name; + } + } + } + + updateWidgetName(){ + for(var i = 0; i < this.widgetsList.length; i++){ + if(this.widget.name && this.widget.name.toUpperCase() == this.widgetsList[i].name.toUpperCase()){ + this.duplicatedName = false; + return; + } + } + this.duplicatedName = true; + } + + getOnboardingWidgets(){ + this.widgetOnboardingService.getManagedWidgets() + .subscribe(_data => { + this.result = _data + if(!(_data instanceof Array)){ + return; + } + if (this.result == null || this.result == 'undefined') { + console.log('WidgetOnboardingService::getOnboardingWidgets Failed: Result or result.data is null'); + }else { + this.widgetsList = _data; + } + }, error =>{ + console.log(error); + }); + } + + getAvailableApps(){ + if(this.isEditMode == false){ + this.availableApps=[]; + this.applicationsService.getAppsForSuperAdminAndAccountAdmin() + .subscribe(apps => { + this.availableApps=[]; + for(var i=0;i<apps.length;i++) { + if (!apps[i].restrictedApp) { + this.availableApps.push({ + id: apps[i].id, + name: apps[i].name, + roles: [], + roleSelected: false, + isSelected: false, + }); + } + } + }, error =>{ + console.log(error); + }); + }else if(this.isEditMode == true){ + if(this.widget.allowAllUser == "Y"){ + this.widget.allUser = true; + } + this.applicationsService.getAppsForSuperAdminAndAccountAdmin() + .subscribe(apps => { + this.availableApps=[]; + let selectedApps = {}; + var availableApps = this.availableApps; + this.allRoleSelected = true; + for(var i=0; i < this.widget.widgetRoles.length; i++){ + if(selectedApps[this.widget.widgetRoles[i].app.appId] != undefined) + selectedApps[this.widget.widgetRoles[i].app.appId] += this.widget.widgetRoles[i].roleId + ";" + this.widget.widgetRoles[i].roleName + ";"; + else{ + selectedApps[this.widget.widgetRoles[i].app.appId] = this.widget.widgetRoles[i].roleId + ";" + this.widget.widgetRoles[i].roleName + ";"; + this.appCounter++; + } + } + apps.forEach(function(app, index){ + availableApps.push({ + id: app.id, + name: app.name, + roles: [], + roleSelected: false, + isSelected: false, + }); + if(selectedApps[app.id] != undefined){ + this.adminsService.getRolesByApp(app.id) + .subscribe(roles => { + var role = selectedApps[app.id].split(';'); + var selectedRoles = []; + var n = 0; + while((n+1) < role.length){ + selectedRoles.push({ + id: role[n++], + name: role[n++], + isSelected: true, + }); + } + for(var m = 0; m < roles.length; m++){ + var hasSelected = true; + for(var n = 0; n < selectedRoles.length; n++){ + if(selectedRoles[n].id == roles[m].id){ + hasSelected = false; + break; + } + } + if(hasSelected){ + selectedRoles.push({ + id: roles[m].id, + name: roles[m].name, + isSelected: false, + }); + } + } + availableApps[index].roleSelected = true; + availableApps[index].isSelected = true; + availableApps[index].roles = selectedRoles; + }, error =>{ + console.log(error); + }); + } + }) + + }, error =>{ + console.log(error); + }); + } + //console.log("this.availableApps :: ",this.availableApps); + } + + 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/widget-onboarding/widget-onboarding.component.html b/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.component.html new file mode 100644 index 00000000..54070926 --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.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="onap-main-view-title"> + <h1>Widget Onboarding</h1> + </div> + + <div class="c-onap-abs-select default"> + <div class="table-dropdown"> + <mat-form-field class="widget-application-select"> + <mat-label> Select Application </mat-label> + <mat-select name="widget-application-select" [(ngModel)]="filterByApp" #appId="ngModel" (ngModelChange)="applyAppFilter($event)"> + <mat-option *ngFor="let rowData of applicationList" [value]="rowData">{{rowData.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)="openAddWigetModal('')"> + <i class="icon ion-md-person-add"></i>Add Widget + </button> + </div> + </div> + + <table mat-table [dataSource]="dataSource" matSort> + <!-- Wiget Name Column --> + <ng-container matColumnDef="widgetName"> + <th id="col1" mat-header-cell *matHeaderCellDef> Widget Name </th> + <td (click)="openAddWigetModal(element)" id="rowheader_t1_{{i}}-widgetName" + mat-cell *matCellDef="let element; let i = index;"> {{element.name}} + </td> + </ng-container> + + <!-- Application Name Column --> + <ng-container matColumnDef="application"> + <th id="col2" mat-header-cell *matHeaderCellDef> Application </th> + <td (click)="openAddWigetModal(element)" id="rowheader_t1_{{i}}-application" + mat-cell *matCellDef="let element; let i=index;"> {{element.appContent}} </td> + </ng-container> + + <!-- Download URL --> + <ng-container matColumnDef="download"> + <th id="col3" mat-header-cell *matHeaderCellDef> Download </th> + <td id="rowheader_t1_{{$index}}-download" mat-cell *matCellDef="let element; let i=index;"> + <i class="icon ion-md-download" (click)="downloadWidget(element)"></i> + </td> + </ng-container> + + <!-- Delete Column --> + <ng-container matColumnDef="delete"> + <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)="removeWidget(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> diff --git a/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.component.scss b/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.component.scss new file mode 100644 index 00000000..b87b7abf --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.component.scss @@ -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============================================ + * + * + */ + +.container th{ + padding-bottom: 15px; + font-weight: bold; +} + +::ng-deep .mat-form-field-infix { + width: 200px; +} + +::ng-deep .widget-application-select { + display: inline-block; + position: relative; + text-align: left; + margin-left: 10px; + margin-right: 110px; +} + +.ion-md-download{ + cursor: pointer; +} + +.ion-md-trash{ + cursor: pointer; +}
\ No newline at end of file diff --git a/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.component.spec.ts b/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.component.spec.ts new file mode 100644 index 00000000..2fd7ef27 --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.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 { WidgetOnboardingComponent } from './widget-onboarding.component'; + +describe('WidgetOnboardingComponent', () => { + let component: WidgetOnboardingComponent; + let fixture: ComponentFixture<WidgetOnboardingComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ WidgetOnboardingComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(WidgetOnboardingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.component.ts b/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.component.ts new file mode 100644 index 00000000..d87791f6 --- /dev/null +++ b/portal-FE-common/src/app/pages/widget-onboarding/widget-onboarding.component.ts @@ -0,0 +1,311 @@ +/*- + * ============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, Input, ChangeDetectionStrategy } from '@angular/core'; +import { MatTableDataSource } from '@angular/material'; +import { MatSort, MatPaginator } from '@angular/material'; +import { WidgetOnboardingService, MicroserviceService } from '../../shared/services/index'; +import { IMircroservies } from 'src/app/shared/model/microservice-onboarding/microservices'; +import { IWidget } from 'src/app/shared/model/widget-onboarding/widget'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { HttpClient } from '@angular/common/http'; +import { WidgetDetailsDialogComponent } from './widget-details-dialog/widget-details-dialog.component'; +import { ConfirmationModalComponent } from 'src/app/modals/confirmation-modal/confirmation-modal.component'; +import { InformationModalComponent } from 'src/app/modals/information-modal/information-modal.component'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'app-widget-onboarding', + templateUrl: './widget-onboarding.component.html', + styleUrls: ['./widget-onboarding.component.scss'] +}) +export class WidgetOnboardingComponent implements OnInit { + + widgetsList: Array<IWidget> = []; + applicationList: Array<Object> = []; + availableMicroServices: Array<IMircroservies> = []; + displayedColumns: string[] = ['widgetName', 'application', 'download','delete']; + isCommError: boolean = false; + dataSource = new MatTableDataSource(this.widgetsList); + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + isEditMode: boolean = false; + result: any; + + + constructor( public widgetOnboardingService: WidgetOnboardingService, + public microservice: MicroserviceService,public ngbModal: NgbModal) { } + + ngOnInit() { + this.prepareApplicationRoleName(); + this.getOnboardingWidgets(); + this.populateAvailableApps(); + this.getAvailableMicroServices(); + } + + getOnboardingWidgets(){ + this.isCommError = false; + this.widgetOnboardingService.getManagedWidgets() + .subscribe(_data => { + this.result = _data + if(!(_data instanceof Array)){ + this.isCommError = true; + return; + } + //console.log("getOnboardingWidgets Data :: ", _data); + if (this.result == null || this.result == 'undefined') { + console.log('WidgetOnboardingService::getOnboardingWidgets Failed: Result or result.data is null'); + }else { + let reSortedWidget = _data.sort(this.getSortOrder("name")); + this.widgetsList = reSortedWidget; + this.prepareApplicationRoleName(); + this.populateTableData(this.widgetsList); + } + }, error =>{ + console.log(error); + }); + } + + //Refactor this into a directive + getSortOrder(prop){ + return function(a, b) { + if (a[prop].toLowerCase() > b[prop].toLowerCase()) { + return 1; + } else if (a[prop].toLowerCase() < b[prop].toLowerCase()) { + return -1; + } + return 0; + } + } + + removeWidget(widget: IWidget) { + let confirmationMsg = 'You are about to delete this Widget : ' + widget.name+ '. Click OK to continue.'; + this.openInformationModal("Confirmation",confirmationMsg).result.then((result) => { + if (result === 'Ok') { + if(!widget || widget == null){ + console.log('WidgetOnboardingCtrl::deleteService: No widget or ID... cannot delete'); + return; + } + + this.widgetsList.splice(this.widgetsList.indexOf(widget), 1); + + this.widgetOnboardingService.deleteWidget(widget.id) + .subscribe( _data => { + this.result = _data; + this.openConfirmationModal("Success",'Widget deleted successfully'); + }, error => { + console.log(error); + }); + + this.populateTableData(this.widgetsList); + } + }, (resut) => { + this.openConfirmationModal('Error', resut); + return; + }) + } + + + openAddWigetModal(rowData:any){ + //console.log("openAddWigetModal getting called..."); + const modalRef = this.ngbModal.open(WidgetDetailsDialogComponent, { size: 'lg' }); + modalRef.componentInstance.widget = rowData; + modalRef.componentInstance.availableMicroServices = this.availableMicroServices; + modalRef.componentInstance.applicationList = this.applicationList; + modalRef.componentInstance.widgetsList = this.widgetsList; + if(rowData != 'undefined' && rowData){ + this.isEditMode = true; + }else{ + modalRef.componentInstance.widget = {}; + this.isEditMode = false; + } + modalRef.componentInstance.passEntry.subscribe((receivedEntry: any) => { + //console.log("receivedEntry >>> ",receivedEntry); + if(receivedEntry){ + this.widgetsList = []; + this.getOnboardingWidgets(); + } + }); + } + + applyFilter(filterValue: string) { + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + applyAppFilter(event) { + let filterByApp = event.title; + if(filterByApp == 'All Applications'){ + this.getOnboardingWidgets(); + }else{ + this.dataSource.filter = filterByApp.trim().toLowerCase(); + } + } + + downloadWidget(widget){ + this.widgetOnboardingService.downloadWidgetFile(widget.id) + .subscribe(res => { + var data = res; + //console.log("downloadWidgetFile response :: ",data); + var filename = widget.name + ".zip"; + if (data == undefined || data == null){ + this.openConfirmationModal("Could not download. Please retry.", ''); + return; + } + var a = document.createElement('a'); + var blob = new Blob([data], {type: 'application/octet-stream'}); + var url = window.URL.createObjectURL(blob); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + + setTimeout(function(){ + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 100); + + }, error =>{ + console.log(error); + this.openConfirmationModal("Could not download. Please retry.", error.message); + }); + } + + + populateTableData(wigetList: Array<IWidget>){ + this.dataSource = new MatTableDataSource(wigetList); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + }; + + prepareApplicationRoleName(){ + if(this.widgetsList && this.widgetsList.length > 0){ + for(var i = 0; i < this.widgetsList.length; i++){ + let set = new Set(); + var info = ""; + var appContent = []; + var appName = []; + if(this.widgetsList[i].widgetRoles && this.widgetsList[i].widgetRoles.length >0){ + for(var n = 0; n < this.widgetsList[i].widgetRoles.length; n++){ + if(this.widgetsList[i].widgetRoles[n].app) + set.add(this.widgetsList[i].widgetRoles[n].app.appName); + } + set.forEach(function (item) { + info = item.toString() + " - "; + for(var n = 0; n < this.widgetsList[i].widgetRoles.length; n++){ + if(this.widgetsList[i].widgetRoles[n].app && item.toString() == this.widgetsList[i].widgetRoles[n].app.appName){ + info += this.widgetsList[i].widgetRoles[n].roleName + "; "; + } + } + appContent.push(info); + appName.push(item.toString()); + }.bind(this)); + } + if(this.widgetsList[i].allowAllUser == "Y"){ + info = "All Applications"; + appContent.push("All Applications"); + appName.push("All Applications"); + } + this.widgetsList[i].appContent = appContent; + this.widgetsList[i].appName = appName; + } + } + } + + populateAvailableApps(){ + this.widgetOnboardingService.populateAvailableApps() + .subscribe( _data => { + this.applicationList.push({ + index: 0, + title: 'All Applications', + value: '' + }) + var reSortedApp = _data.sort(this.getSortOrder("name")); + var realAppIndex = 1; + for (let i = 1; i <= reSortedApp.length; i++) { + if (!reSortedApp[i-1].restrictedApp) { + if(_data[i - 1].name && _data[i - 1].name!=""){ + this.applicationList.push({ + index: realAppIndex, + title: _data[i - 1].name, + value: _data[i - 1].id + }) + } + realAppIndex = realAppIndex + 1; + } + } + }, error => { + console.log(error); + }); + } + + getAvailableMicroServices = () =>{ + this.availableMicroServices = []; + this.microservice.getServiceList() + .subscribe(_data => { + this.result = _data; + if (this.result == null || this.result == 'undefined') { + console.log('MicroserviceService::getAvailableMicroServices Failed: Result or result.data is null'); + }else { + for(var i = 0; i < _data.length; i++){ + this.availableMicroServices.push({ + id: _data[i].id, + name: _data[i].name, + option: _data[i].name + ": " + _data[i].url + }); + } + } + }, error =>{ + console.log(error); + }); + } + + 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/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() { + } + +} |