From c779f12e548700c60c81129c6e262319cb7d24ec Mon Sep 17 00:00:00 2001 From: Sebastian-Ion TINCU <Sebastian-Ion.TINCU@ext.ec.europa.eu> Date: Thu, 7 Dec 2023 17:55:07 +0100 Subject: [PATCH] EDELIVERY-11258 Access token enhancements Add table having rows containing collapsible panels and paginator. --- smp-angular/src/app/app.module.ts | 3 +- .../credential-dialog.component.html | 11 +- .../credential-dialog.component.ts | 22 +-- .../access-token-panel.component.html | 149 +++++++++--------- .../access-token-panel.component.ts | 5 +- .../user-access-tokens.component.html | 26 ++- .../user-access-tokens.component.ts | 30 ++-- .../user-certificates.component.ts | 4 +- 8 files changed, 145 insertions(+), 105 deletions(-) diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts index 0cbb4aa4d..c430ffba4 100644 --- a/smp-angular/src/app/app.module.ts +++ b/smp-angular/src/app/app.module.ts @@ -140,7 +140,7 @@ import {ManageMembersDialogComponent} from "./common/dialogs/manage-members-dial import {HttpErrorHandlerService} from "./common/error/http-error-handler.service"; import {SmpTitledLabelComponent} from "./common/components/smp-titled-label/smp-titled-label.component"; import {ServiceGroupSearchComponent} from "./service-group-search/service-group-search.component"; - +import { ClipboardModule } from '@angular/cdk/clipboard'; @NgModule({ declarations: [ @@ -264,6 +264,7 @@ import {ServiceGroupSearchComponent} from "./service-group-search/service-group- routing, MatAutocompleteModule, CodemirrorModule, + ClipboardModule, ], providers: [ AdminDomainService, diff --git a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.html b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.html index 181cc605c..2931841c7 100644 --- a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.html +++ b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.html @@ -82,6 +82,7 @@ </div> </mat-dialog-content> + <mat-dialog-actions> <button id="generatedAccessTokenButton" *ngIf="isAccessTokenType && !isReadOnly" [disabled]="!credentialForm.valid " mat-raised-button color="primary" (click)="generatedAccessToken()"> @@ -99,8 +100,14 @@ <button id="closeDialogButton" mat-raised-button color="primary" (click)="closeDialog()"> - <mat-icon>cancel</mat-icon> - <span>Cancel</span> + <mat-icon>close</mat-icon> + <span>Close</span> + </button> + + <button id="copyButton" *ngIf="isAccessTokenType && isReadOnly" [cdkCopyToClipboard]="accessTokenValue" mat-raised-button> + <mat-icon>content_copy</mat-icon> + <span>Copy</span> </button> + </mat-dialog-actions> diff --git a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts index 031cd48f5..9b7cb1b35 100644 --- a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts +++ b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts @@ -9,7 +9,6 @@ import {CertificateRo} from "../../../system-settings/user/certificate-ro.model" import {CertificateService} from "../../../system-settings/user/certificate.service"; import {HttpErrorHandlerService} from "../../error/http-error-handler.service"; - @Component({ templateUrl: './credential-dialog.component.html', styleUrls: ['./credential-dialog.component.css'] @@ -19,7 +18,7 @@ export class CredentialDialogComponent { public static ACCESS_TOKEN_TYPE: string = "ACCESS_TOKEN"; dateTimeFormat: string = SmpConstants.DATE_TIME_FORMAT; - formTitle = "Access token generation dialog"; + formTitle: string = "New Access token created"; credentialForm: FormGroup; certificateForm: FormGroup; @@ -31,7 +30,7 @@ export class CredentialDialogComponent { // certificate specific data newCertFile: File = null; enableCertificateImport: boolean = true; - + accessTokenValue: string; constructor(@Inject(MAT_DIALOG_DATA) public data: any, private userService: UserService, @@ -157,7 +156,7 @@ export class CredentialDialogComponent { }, err => { this.clearCertificateData() - if (this.httpErrorHandlerService.logoutOnInvalidSessionError(err)){ + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(err)) { this.closeDialog(); return; } @@ -174,15 +173,17 @@ export class CredentialDialogComponent { generatedAccessToken() { - this.clearAlert(); this.userService.generateUserAccessTokenCredential(this.initCredential).subscribe((response: AccessTokenRo) => { - this.showSuccessMessage("Token with ID: \"" + response.identifier + "\" and value: \"" + response.value + "\" was generated!" + - "<br \><br \>Copy the access token's value and save it in a safe space. <br \><b>You won't be able to see your token's value once you click Close.</b>") + this.accessTokenValue = response.value; + this.showSuccessMessage( + `Token with ID: "${response.identifier}" and value: "${response.value}" was generated! + <br/><br/>Copy the access token's value and save it in a safe space. + You won't be able to see your token's value once you click <b>Close.</b>`) this.userService.notifyAccessTokenUpdated(response.credential); this.setDisabled(true); }, (err) => { - if (this.httpErrorHandlerService.logoutOnInvalidSessionError(err)){ + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(err)) { this.closeDialog(); return; } @@ -229,9 +230,9 @@ export class CredentialDialogComponent { this.messageType = "success"; } - showErrorMessage(value: string, errorLevel:boolean) { + showErrorMessage(value: string, errorLevel: boolean) { this.message = value; - this.messageType =errorLevel?"error":"warning"; + this.messageType = errorLevel ? "error" : "warning"; } clearAlert() { @@ -239,7 +240,6 @@ export class CredentialDialogComponent { this.messageType = null; } - closeDialog() { this.dialogRef.close() } diff --git a/smp-angular/src/app/user-settings/user-access-tokens/access-token-panel/access-token-panel.component.html b/smp-angular/src/app/user-settings/user-access-tokens/access-token-panel/access-token-panel.component.html index aeec59959..f2576aaa3 100644 --- a/smp-angular/src/app/user-settings/user-access-tokens/access-token-panel/access-token-panel.component.html +++ b/smp-angular/src/app/user-settings/user-access-tokens/access-token-panel/access-token-panel.component.html @@ -1,78 +1,83 @@ -<div class="panel smp-data-panel" [formGroup]="credentialForm" (ngSubmit)="onSaveButtonClicked()"> - <div style="display: flex;flex-flow: row wrap;"> - <mat-form-field style="flex-grow: 2"> - <mat-label>Access token ID</mat-label> +<mat-expansion-panel [expanded]="_active"> + <mat-expansion-panel-header> + {{ credential?.name }} + </mat-expansion-panel-header> + <div class="panel smp-data-panel" [formGroup]="credentialForm" (ngSubmit)="onSaveButtonClicked()"> + <div style="display: flex;flex-flow: row wrap;"> + <mat-form-field style="flex-grow: 2"> + <mat-label>Access token ID</mat-label> + <input matInput + [value]="credential?.name" + maxlength="255" readonly> + </mat-form-field> + <div style="display: inline"> + <button id="deleteButton" mat-raised-button + (click)="onDeleteButtonClicked()" + color="primary" > + <mat-icon>delete</mat-icon> + <span>Delete</span> + </button> + <button id="saveButton" mat-raised-button + (click)="onSaveButtonClicked()" + color="primary" + [disabled]="!submitButtonEnabled" > + <mat-icon>save</mat-icon> + <span>Save</span> + </button> + </div> + </div> + <mat-form-field style="width: 100%"> + <mat-label>Description</mat-label> <input matInput - [value]="credential?.name" - maxlength="255" readonly> + formControlName="description" + maxlength="255"> </mat-form-field> - <div style="display: inline"> - <button id="deleteButton" mat-raised-button - (click)="onDeleteButtonClicked()" - color="primary" > - <mat-icon>delete</mat-icon> - <span>Delete</span> - </button> - <button id="saveButton" mat-raised-button - (click)="onSaveButtonClicked()" - color="primary" - [disabled]="!submitButtonEnabled" > - <mat-icon>save</mat-icon> - <span>Save</span> - </button> - </div> - </div> - <mat-form-field style="width: 100%"> - <mat-label>Description</mat-label> - <input matInput - formControlName="description" - maxlength="255"> - </mat-form-field> - <div style="display: flex;flex-flow: row wrap;"> + <div style="display: flex;flex-flow: row wrap;"> - <mat-checkbox formControlName="active" style="align-self: center; padding-bottom: 1em;padding-right: 2em"> - Active - </mat-checkbox> + <mat-checkbox formControlName="active" style="align-self: center; padding-bottom: 1em;padding-right: 2em"> + Active + </mat-checkbox> - <mat-form-field appearance="fill" style="flex-grow: 1"> - <mat-label>Enter a valid date range</mat-label> - <mat-date-range-input [rangePicker]="picker" [min]="minSelectableDate" > - <input matStartDate formControlName="activeFrom" placeholder="Start date"> - <input matEndDate formControlName="expireOn" placeholder="End date"> - </mat-date-range-input> - <mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle> - <mat-date-range-picker #picker></mat-date-range-picker> - <smp-field-error *ngIf="credentialForm.controls.activeFrom.hasError('matStartDateInvalid')">Invalid active from date</smp-field-error > - <smp-field-error *ngIf="credentialForm.controls.expireOn.hasError('matEndDateInvalid')">Invalid expire on date</smp-field-error > - </mat-form-field> - </div> - <div style="display: flex;flex-flow: row;"> - <mat-form-field style="flex-grow: 1"> - <mat-label>Seq. failed attempts</mat-label> - <input matInput - [value]="sequentialLoginFailureCount" - id="sequentialTokenLoginFailureCount_id" maxlength="255" disabled readonly> - </mat-form-field> - <mat-form-field style="flex-grow:2 " floatLabel="always"> - <mat-label>Last failed attempt</mat-label> - <input id="LastFailedAttempt_id" matInput [ngxMatDatetimePicker]="LastFailedAttemptPicker" - [value]="lastFailedLoginAttempt" - placeholder="---" - readonly> - <mat-datepicker-toggle matSuffix [for]="LastFailedAttemptPicker" style="visibility: hidden"></mat-datepicker-toggle> - <ngx-mat-datetime-picker #LastFailedAttemptPicker [showSpinners]="true" [showSeconds]="false" - [hideTime]="false"></ngx-mat-datetime-picker> - </mat-form-field> - <mat-form-field style="flex-grow: 2" floatLabel="always"> - <mat-label>Suspended until</mat-label> - <input id="SuspendedUtil_id" matInput [ngxMatDatetimePicker]="suspendedUtilPicker" - [value]="suspendedUtil" - placeholder="---" - readonly> - <mat-datepicker-toggle matSuffix [for]="suspendedUtilPicker" style="visibility: hidden"></mat-datepicker-toggle> - <ngx-mat-datetime-picker #suspendedUtilPicker [showSpinners]="true" [showSeconds]="false" - [hideTime]="false"></ngx-mat-datetime-picker> + <mat-form-field appearance="fill" style="flex-grow: 1"> + <mat-label>Enter a valid date range</mat-label> + <mat-date-range-input [rangePicker]="picker" [min]="minSelectableDate" > + <input matStartDate formControlName="activeFrom" placeholder="Start date"> + <input matEndDate formControlName="expireOn" placeholder="End date"> + </mat-date-range-input> + <mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle> + <mat-date-range-picker #picker></mat-date-range-picker> + <smp-field-error *ngIf="credentialForm.controls.activeFrom.hasError('matStartDateInvalid')">Invalid active from date</smp-field-error > + <smp-field-error *ngIf="credentialForm.controls.expireOn.hasError('matEndDateInvalid')">Invalid expire on date</smp-field-error > + </mat-form-field> + </div> + <div style="display: flex;flex-flow: row;"> + <mat-form-field style="flex-grow: 1"> + <mat-label>Seq. failed attempts</mat-label> + <input matInput + [value]="sequentialLoginFailureCount" + id="sequentialTokenLoginFailureCount_id" maxlength="255" disabled readonly> + </mat-form-field> + <mat-form-field style="flex-grow:2 " floatLabel="always"> + <mat-label>Last failed attempt</mat-label> + <input id="LastFailedAttempt_id" matInput [ngxMatDatetimePicker]="LastFailedAttemptPicker" + [value]="lastFailedLoginAttempt" + placeholder="---" + readonly> + <mat-datepicker-toggle matSuffix [for]="LastFailedAttemptPicker" style="visibility: hidden"></mat-datepicker-toggle> + <ngx-mat-datetime-picker #LastFailedAttemptPicker [showSpinners]="true" [showSeconds]="false" + [hideTime]="false"></ngx-mat-datetime-picker> + </mat-form-field> + <mat-form-field style="flex-grow: 2" floatLabel="always"> + <mat-label>Suspended until</mat-label> + <input id="SuspendedUtil_id" matInput [ngxMatDatetimePicker]="suspendedUtilPicker" + [value]="suspendedUtil" + placeholder="---" + readonly> + <mat-datepicker-toggle matSuffix [for]="suspendedUtilPicker" style="visibility: hidden"></mat-datepicker-toggle> + <ngx-mat-datetime-picker #suspendedUtilPicker [showSpinners]="true" [showSeconds]="false" + [hideTime]="false"></ngx-mat-datetime-picker> - </mat-form-field> + </mat-form-field> + </div> </div> -</div> +<mat-expansion-panel> diff --git a/smp-angular/src/app/user-settings/user-access-tokens/access-token-panel/access-token-panel.component.ts b/smp-angular/src/app/user-settings/user-access-tokens/access-token-panel/access-token-panel.component.ts index 34028ee83..a3fc41d92 100644 --- a/smp-angular/src/app/user-settings/user-access-tokens/access-token-panel/access-token-panel.component.ts +++ b/smp-angular/src/app/user-settings/user-access-tokens/access-token-panel/access-token-panel.component.ts @@ -19,7 +19,7 @@ export class AccessTokenPanelComponent implements BeforeLeaveGuard { _credential: CredentialRo; credentialForm: FormGroup; - + _active: boolean; constructor(private formBuilder: FormBuilder) { this.credentialForm = formBuilder.group({ @@ -37,6 +37,7 @@ export class AccessTokenPanelComponent implements BeforeLeaveGuard { @Input() set credential(value: CredentialRo) { this._credential = value; + this._active = value.active; if (this._credential) { this.credentialForm.controls['active'].setValue(this._credential.active); this.credentialForm.controls['description'].setValue(this._credential.description); @@ -54,12 +55,10 @@ export class AccessTokenPanelComponent implements BeforeLeaveGuard { } onDeleteButtonClicked() { - this.onDeleteEvent.emit(this.credential); } onSaveButtonClicked() { - this._credential.active = this.credentialForm.controls['active'].value this._credential.description = this.credentialForm.controls['description'].value this._credential.activeFrom = this.credentialForm.controls['activeFrom'].value diff --git a/smp-angular/src/app/user-settings/user-access-tokens/user-access-tokens.component.html b/smp-angular/src/app/user-settings/user-access-tokens/user-access-tokens.component.html index 86b5f8e81..182091a34 100644 --- a/smp-angular/src/app/user-settings/user-access-tokens/user-access-tokens.component.html +++ b/smp-angular/src/app/user-settings/user-access-tokens/user-access-tokens.component.html @@ -2,11 +2,29 @@ <data-panel title="Access token" text="You can generate a access token for each application you use that needs access to the DomiSMP API." [labelColumnContent]="commonToolbar"> - <access-token-panel *ngFor="let item of accessTokens; trackBy: trackListItem" - (onDeleteEvent)="onDeleteItemClicked(item)" - (onSaveEvent)="onSaveItemClicked(item)" - [credential]="item"></access-token-panel> + + <table mat-table id="access-token-table" [dataSource]="dataSource" class="panel smp-data-panel" > + <ng-container matColumnDef="accessTokens"> + <th mat-header-cell *matHeaderCellDef> Access Tokens </th> + <td mat-cell *matCellDef="let token" + [ngClass]="{'datatable-row-error': token.invalid}" + [matTooltip]="token.certificateId"> + <access-token-panel (onDeleteEvent)="onDeleteItemClicked(token)" + (onSaveEvent)="onSaveItemClicked(token)" + [credential]="token"></access-token-panel> + + </td> + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let myRowData; columns: displayedColumns"></tr> + </ng-container> + </table> + + <mat-paginator class="mat-elevation-z2" id="tokens-paginator" + [hidePageSize]="true" + [showFirstLastButtons]="true" + [pageSize]="5" aria-label="Select page"></mat-paginator> </data-panel> + </div> <ng-template #commonToolbar> diff --git a/smp-angular/src/app/user-settings/user-access-tokens/user-access-tokens.component.ts b/smp-angular/src/app/user-settings/user-access-tokens/user-access-tokens.component.ts index facf09b10..7995edeb0 100644 --- a/smp-angular/src/app/user-settings/user-access-tokens/user-access-tokens.component.ts +++ b/smp-angular/src/app/user-settings/user-access-tokens/user-access-tokens.component.ts @@ -1,5 +1,4 @@ -import {Component, QueryList, ViewChildren,} from '@angular/core'; -import {SecurityService} from "../../security/security.service"; +import {AfterViewInit, Component, QueryList, ViewChild, ViewChildren,} from '@angular/core'; import {UserService} from "../../system-settings/user/user.service"; import {CredentialRo} from "../../security/credential.model"; import {ConfirmationDialogComponent} from "../../common/dialogs/confirmation-dialog/confirmation-dialog.component"; @@ -8,22 +7,26 @@ import {EntityStatus} from "../../common/enums/entity-status.enum"; import {CredentialDialogComponent} from "../../common/dialogs/credential-dialog/credential-dialog.component"; import {BeforeLeaveGuard} from "../../window/sidenav/navigation-on-leave-guard"; import {AccessTokenPanelComponent} from "./access-token-panel/access-token-panel.component"; - +import {MatPaginator} from "@angular/material/paginator"; +import {MatTableDataSource} from "@angular/material/table"; @Component({ templateUrl: './user-access-tokens.component.html', styleUrls: ['./user-access-tokens.component.scss'] }) -export class UserAccessTokensComponent implements BeforeLeaveGuard { +export class UserAccessTokensComponent implements AfterViewInit, BeforeLeaveGuard { + displayedColumns: string[] = ['accessTokens']; + dataSource: MatTableDataSource<CredentialRo> = new MatTableDataSource(); accessTokens: CredentialRo[] = []; + @ViewChildren(AccessTokenPanelComponent) userTokenCredentialComponents: QueryList<AccessTokenPanelComponent>; - constructor(private securityService: SecurityService, - private userService: UserService, - public dialog: MatDialog) { - + @ViewChild(MatPaginator) + paginator: MatPaginator; + constructor(private userService: UserService, + public dialog: MatDialog) { this.userService.onAccessTokenCredentialsUpdateSubject().subscribe((credentials: CredentialRo[]) => { this.updateAccessTokenCredentials(credentials); }); @@ -37,6 +40,11 @@ export class UserAccessTokensComponent implements BeforeLeaveGuard { public updateAccessTokenCredentials(userAccessTokens: CredentialRo[]) { this.accessTokens = userAccessTokens; + this.dataSource.data = this.accessTokens; + } + + ngAfterViewInit() { + this.dataSource.paginator = this.paginator; } public updateAccessTokenCredential(userAccessToken: CredentialRo) { @@ -57,6 +65,9 @@ export class UserAccessTokensComponent implements BeforeLeaveGuard { userAccessToken]; } + this.dataSource.data = this.accessTokens; + // show the last page + this.paginator.lastPage(); } public trackListItem(index: number, credential: CredentialRo) { @@ -80,13 +91,12 @@ export class UserAccessTokensComponent implements BeforeLeaveGuard { this.dialog.open(CredentialDialogComponent, { data: { credentialType: CredentialDialogComponent.ACCESS_TOKEN_TYPE, - formTitle: "Access token generation dialog" + formTitle: "New Access token created" } }).afterClosed(); } public onSaveItemClicked(credential: CredentialRo) { - this.dialog.open(ConfirmationDialogComponent, { data: { title: "Update Access token", diff --git a/smp-angular/src/app/user-settings/user-certificates/user-certificates.component.ts b/smp-angular/src/app/user-settings/user-certificates/user-certificates.component.ts index 18f4b6911..5fcaa9436 100644 --- a/smp-angular/src/app/user-settings/user-certificates/user-certificates.component.ts +++ b/smp-angular/src/app/user-settings/user-certificates/user-certificates.component.ts @@ -83,7 +83,7 @@ export class UserCertificatesComponent implements BeforeLeaveGuard { this.dialog.open(CredentialDialogComponent, { data: { credentialType: CredentialDialogComponent.CERTIFICATE_TYPE, - formTitle: "Import certificate dialog" + formTitle: "Import Certificate" } }).afterClosed(); @@ -108,7 +108,7 @@ export class UserCertificatesComponent implements BeforeLeaveGuard { this.dialog.open(ConfirmationDialogComponent, { data: { - title: "Update Certificate data", + title: "Update Certificate", description: "Action will update Certificate data:<br />" + credential.name + "!<br /><br />Do you wish to continue?" } }).afterClosed().subscribe(result => { -- GitLab