diff --git a/src/frontend/app/administration/history/history-administration.component.html b/src/frontend/app/administration/history/history-administration.component.html index c925ddd0b7a262e93080cad12d51a511980103e8..44adb1a79a280dfc98e74b70f80c902efd32db96 100755 --- a/src/frontend/app/administration/history/history-administration.component.html +++ b/src/frontend/app/administration/history/history-administration.component.html @@ -29,15 +29,15 @@ <mat-form-field (click)="startPicker.open()" style="cursor:pointer;" class="dateFilter"> <mat-label style="color:white;">{{lang.since}} </mat-label> - <input [(ngModel)]="startDateFilter" matInput [matDatepicker]="startPicker" - [placeholder]="lang.since" [max]="endDateFilter" readonly style="cursor:pointer;" - (dateChange)="filterStartDate()"> - <mat-datepicker-toggle matSuffix [for]="startPicker" *ngIf="!startDateFilter"> + <input [(ngModel)]="appHistoryList.startDateFilter" matInput [matDatepicker]="startPicker" + [placeholder]="lang.since" [max]="appHistoryList.endDateFilter" readonly style="cursor:pointer;" + (dateChange)="appHistoryList.filterStartDate()"> + <mat-datepicker-toggle matSuffix [for]="startPicker" *ngIf="!appHistoryList.startDateFilter"> </mat-datepicker-toggle> <mat-datepicker [touchUi]="appService.getViewMode()" #startPicker> </mat-datepicker> - <button mat-button color="warn" matSuffix mat-icon-button *ngIf="startDateFilter" - (click)="$event.stopPropagation();startDateFilter = '';filterStartDate()" [title]="lang.eraseValue"> + <button mat-button color="warn" matSuffix mat-icon-button *ngIf="appHistoryList.startDateFilter" + (click)="$event.stopPropagation();appHistoryList.startDateFilter = '';appHistoryList.filterStartDate()" [title]="lang.eraseValue"> <mat-icon color="warn" class="fa fa-calendar-times"> </mat-icon> </button> @@ -45,15 +45,15 @@ <mat-form-field (click)="endPicker.open()" style="cursor:pointer;" class="dateFilter"> <mat-label style="color:white;">{{lang.until}} </mat-label> - <input [(ngModel)]="endDateFilter" matInput [matDatepicker]="endPicker" - [placeholder]="lang.until" [min]="startDateFilter" readonly style="cursor:pointer;" - (dateChange)="filterEndDate()"> - <mat-datepicker-toggle matSuffix [for]="endPicker" *ngIf="!endDateFilter"> + <input [(ngModel)]="appHistoryList.endDateFilter" matInput [matDatepicker]="endPicker" + [placeholder]="lang.until" [min]="appHistoryList.startDateFilter" readonly style="cursor:pointer;" + (dateChange)="appHistoryList.filterEndDate()"> + <mat-datepicker-toggle matSuffix [for]="endPicker" *ngIf="!appHistoryList.endDateFilter"> </mat-datepicker-toggle> <mat-datepicker [touchUi]="appService.getViewMode()" #endPicker> </mat-datepicker> - <button mat-button color="warn" matSuffix mat-icon-button *ngIf="endDateFilter" - (click)="$event.stopPropagation();endDateFilter = '';filterEndDate()" [title]="lang.eraseValue"> + <button mat-button color="warn" matSuffix mat-icon-button *ngIf="appHistoryList.endDateFilter" + (click)="$event.stopPropagation();appHistoryList.endDateFilter = '';appHistoryList.filterEndDate()" [title]="lang.eraseValue"> <mat-icon color="warn" class="fa fa-calendar-times"> </mat-icon> </button> @@ -63,93 +63,7 @@ </div> <div class="container" [class.fullContainer]="appService.getViewMode()"> <div class="container-content"> - <div class="example-loading-shade" *ngIf="isLoadingResults"> - <mat-spinner *ngIf="isLoadingResults"></mat-spinner> - </div> - <div class="table-head"> - <div class="table-head-result"> - <mat-form-field floatLabel="never" style="font-size: 13px;"> - <input type="text" #autoCompleteInput [matAutocomplete]="auto" [placeholder]="lang.filterBy" - matInput [formControl]="searchHistory" (click)="$event.stopPropagation()" - maxlength="128"> - <mat-autocomplete #auto="matAutocomplete" (optionSelected)="addItemFilter($event.option)" - (opened)="initFilterListHistory()"> - <mat-option disabled *ngIf="loadingFilters"> - <div style="display: flex;justify-content: center;"> - <mat-spinner diameter="35"></mat-spinner> - </div> - </mat-option> - <ng-container *ngIf="filterList!==null && !loadingFilters"> - <ng-container *ngFor="let keyVal of filterList | keyvalue"> - <mat-optgroup *ngIf="(filteredList[keyVal.key] | async)?.length > 0" - [label]="lang[keyVal.key]" class="filterList"> - <mat-option [id]="keyVal.key" - [style.color]="!filter.used ? filterColor[keyVal.key] : ''" - *ngFor="let filter of filteredList[keyVal.key] | async | sortBy : 'label'" - [value]="filter" [disabled]="filter.used"> - {{filter.label}} - </mat-option> - </mat-optgroup> - </ng-container> - </ng-container> - - </mat-autocomplete> - </mat-form-field> - </div> - <div class="table-head-tool"> - <mat-paginator #paginatorHistoryList [length]="resultsLength" [hidePageSize]="true" - [pageSize]="10" class="paginatorResultList"></mat-paginator> - </div> - </div> - <div style="height:90%;overflow:auto;position:absolute;width:100%;"> - <div class="filterBadges"> - <ng-container *ngFor="let keyVal of filterUsed | keyvalue"> - <ng-container *ngIf="['startDate','endDate'].indexOf(keyVal.key) === -1"> - <span *ngFor="let filter of filterUsed[keyVal.key]; let i=index;" class="label" - [style.background]="filterColor[keyVal.key]" [title]="lang[keyVal.key]" - (click)="removeItemFilter(filter,keyVal.key,i)">{{filter.label}} - <i class="fa fa-times-circle"></i></span> - </ng-container> - </ng-container> - </div> - <mat-table id="history-list" #tableHistoryListSort="matSort" [dataSource]="data" matSort - matSortActive="event_date" matSortDirection="desc" style="width:100%;"> - <ng-container matColumnDef="event_date"> - <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.event}}</mat-header-cell> - <mat-cell mat-cell *matCellDef="let element" - [title]="element.event_date | fullDate"> - {{element.event_date | timeAgo : 'full' | ucfirst}} </mat-cell> - </ng-container> - <ng-container matColumnDef="userLabel"> - <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.user | ucfirst}}</mat-header-cell> - <mat-cell *matCellDef="let element"> - {{element.userLabel}} </mat-cell> - </ng-container> - <ng-container matColumnDef="info"> - <mat-header-cell *matHeaderCellDef mat-sort-header - style="flex: 2;">{{lang.information}} - </mat-header-cell> - <mat-cell *matCellDef="let element" - style="flex: 2;"> - {{element.info}} </mat-cell> - </ng-container> - <ng-container matColumnDef="remote_ip"> - <mat-header-cell *matHeaderCellDef mat-sort-header - >{{lang.ip}} - </mat-header-cell> - <mat-cell *matCellDef="let element"> - {{element.remote_ip}} </mat-cell> - </ng-container> - <mat-header-row *matHeaderRowDef="displayedColumnsHistory"></mat-header-row> - <mat-row *matRowDef="let row; columns: displayedColumnsHistory;"> - </mat-row> - </mat-table> - <div class="mat-paginator" - style="min-height:48px;min-height: 48px;display: flex;justify-content: end;align-items: center;padding-right: 20px;"> - {{resultsLength}} {{lang.elements}}</div> - </div> - <div class="table-head"> - </div> + <app-history-list #appHistoryList></app-history-list> </div> </div> </mat-sidenav-content> diff --git a/src/frontend/app/administration/history/history-administration.component.ts b/src/frontend/app/administration/history/history-administration.component.ts index b17a5ac2d5f21572394d4568479d71f6920c4d7b..d1f59031bee121832fad6a64f0f7247c6bbbd12f 100755 --- a/src/frontend/app/administration/history/history-administration.component.ts +++ b/src/frontend/app/administration/history/history-administration.component.ts @@ -1,17 +1,10 @@ -import { Component, OnInit, ViewChild, EventEmitter, ElementRef } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { LANG } from '../../translate.component'; -import { NotificationService } from '../../notification.service'; -import { HeaderService } from '../../../service/header.service'; import { MatSidenav } from '@angular/material/sidenav'; import { AppService } from '../../../service/app.service'; -import { Observable, merge, Subject, of as observableOf, of } from 'rxjs'; -import { MatPaginator, MatSort, MatDialog } from '@angular/material'; -import { takeUntil, startWith, switchMap, map, catchError, filter, exhaustMap, tap, debounceTime, distinctUntilChanged, finalize } from 'rxjs/operators'; -import { ConfirmComponent } from '../../../plugins/modal/confirm.component'; -import { FormControl } from '@angular/forms'; import { FunctionsService } from '../../../service/functions.service'; -import { LatinisePipe } from 'ngx-pipes'; +import { HistoryComponent } from '../../history/history.component'; @Component({ selector: 'contact-list', @@ -25,42 +18,11 @@ export class HistoryAdministrationComponent implements OnInit { @ViewChild('snav2', { static: true }) public sidenavRight: MatSidenav; lang: any = LANG; - loading: boolean = false; - filtersChange = new EventEmitter(); - - data: any; - - displayedColumnsHistory: string[] = ['event_date', 'userLabel', 'info', 'remote_ip']; - - isLoadingResults = true; - routeUrl: string = '../../rest/history'; - resultListDatabase: HistoryListHttpDao | null; - resultsLength = 0; - - searchHistory = new FormControl(); startDateFilter: any = ''; endDateFilter: any = ''; - filterUrl: string = ''; - filterList: any = null; - filteredList: any = {}; - filterUsed: any = {}; - - filterColor = { - startDate: '#b5cfd8', - endDate: '#7393a7', - actions: '#7d5ba6', - systemActions: '#d6716f', - users: '#009dc5', - }; - loadingFilters: boolean = true; - - @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; - @ViewChild('tableHistoryListSort', { static: true }) sort: MatSort; - @ViewChild('autoCompleteInput', { static: true }) autoCompleteInput: ElementRef; - - private destroy$ = new Subject<boolean>(); + @ViewChild('appHistoryList', { static: false }) appHistoryList: HistoryComponent; subMenus: any[] = [ { @@ -79,215 +41,8 @@ export class HistoryAdministrationComponent implements OnInit { constructor( public http: HttpClient, - private notify: NotificationService, - private headerService: HeaderService, public appService: AppService, - public dialog: MatDialog, - public functions: FunctionsService, - private latinisePipe: LatinisePipe) { } - - ngOnInit(): void { - this.loading = true; - this.initHistoryList(); - } - - initHistoryList() { - this.resultListDatabase = new HistoryListHttpDao(this.http); - this.paginator.pageIndex = 0; - this.sort.active = 'event_date'; - this.sort.direction = 'desc'; - this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); - - // When list is refresh (sort, page, filters) - merge(this.sort.sortChange, this.paginator.page, this.filtersChange) - .pipe( - takeUntil(this.destroy$), - startWith({}), - switchMap(() => { - this.isLoadingResults = true; - return this.resultListDatabase!.getRepoIssues( - this.sort.active, this.sort.direction, this.paginator.pageIndex, this.routeUrl, this.filterUrl); - }), - map(data => { - this.isLoadingResults = false; - data = this.processPostData(data); - this.resultsLength = data.count; - this.headerService.setHeader(this.lang.administration + ' ' + this.lang.history.toLowerCase(), '', ''); - return data.history; - }), - catchError((err: any) => { - this.notify.handleErrors(err); - this.isLoadingResults = false; - return observableOf([]); - }) - ).subscribe(data => this.data = data); - } - - processPostData(data: any) { - data.history = data.history.map((item: any) => { - return { - ...item, - userLabel : !this.functions.empty(item.userLabel) ? item.userLabel : this.lang.userDeleted - } - }) - return data; - } - - - refreshDao() { - this.paginator.pageIndex = 0; - this.filtersChange.emit(); - } - - initFilterListHistory() { - - if (this.filterList === null) { - this.filterList = {}; - this.loadingFilters = true; - this.http.get("../../rest/history/availableFilters").pipe( - map((data: any) => { - let deletedActions = data.actions.filter((action: any) => action.label === null).map((action: any) => action.id); - let deletedUser = data.users.filter((user: any) => user.label === null).map((user: any) => user.login); - - data.actions = data.actions.filter((action: any) => action.label !== null); - if (deletedActions.length > 0) { - data.actions.push({ - id: deletedActions, - label: this.lang.actionDeleted - }); - } - - data.users = data.users.filter((user: any) => user.label !== null); - - if (deletedUser.length > 0) { - data.users.push({ - id: deletedUser, - label: this.lang.userDeleted - }); - } - - data.systemActions = data.systemActions.map((syst: any) => { - return { - id: syst.id, - label: this.lang[syst.id] - } - }); - return data; - }), - tap((data: any) => { - Object.keys(data).forEach((filterType: any) => { - if (this.functions.empty(this.filterList[filterType])) { - this.filterList[filterType] = []; - this.filteredList[filterType] = []; - } - data[filterType].forEach((element: any) => { - this.filterList[filterType].push(element); - }); - - this.filteredList[filterType] = this.searchHistory.valueChanges - .pipe( - startWith(''), - map(element => element ? this.filter(element, filterType) : this.filterList[filterType].slice()) - ); - }); - - }), - finalize(() => this.loadingFilters = false), - catchError((err: any) => { - this.notify.handleSoftErrors(err); - return of(false); - }) - ).subscribe(); - - } - } - - filterStartDate() { - if (this.functions.empty(this.filterUsed['startDate'])) { - this.filterUsed['startDate'] = []; - } - this.filterUsed['startDate'][0] = { - id: this.functions.empty(this.startDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.startDateFilter), - label: this.functions.empty(this.startDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.startDateFilter) - }; - this.generateUrlFilter(); - this.refreshDao(); - } - - filterEndDate() { - if (this.functions.empty(this.filterUsed['endDate'])) { - this.filterUsed['endDate'] = []; - } - this.filterUsed['endDate'][0] = { - id: this.functions.empty(this.endDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.endDateFilter, true), - label: this.functions.empty(this.endDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.endDateFilter) - }; - this.generateUrlFilter(); - this.refreshDao(); - } - - addItemFilter(elem: any) { - elem.value.used = true; - if (this.functions.empty(this.filterUsed[elem.id])) { - this.filterUsed[elem.id] = []; - } - this.filterUsed[elem.id].push(elem.value); - this.generateUrlFilter(); - this.searchHistory.reset(); - this.autoCompleteInput.nativeElement.blur(); - this.refreshDao(); - } - - removeItemFilter(elem: any, type: string, index: number) { - elem.used = false; - this.filterUsed[type].splice(index, 1); - this.generateUrlFilter(); - this.refreshDao(); - } - - - generateUrlFilter() { - this.filterUrl = ''; - let arrTmpUrl: any[] = []; - Object.keys(this.filterUsed).forEach((type: any) => { - this.filterUsed[type].forEach((filter: any) => { - if (!this.functions.empty(filter.id)) { - if (['startDate', 'endDate'].indexOf(type) > -1) { - arrTmpUrl.push(`${type}=${filter.id}`); - } else { - arrTmpUrl.push(`${type}[]=${filter.id}`); - } - } - }); - }); - if (arrTmpUrl.length > 0) { - this.filterUrl = '&' + arrTmpUrl.join('&'); - } - } - - private filter(value: string, type: string): any[] { - if (typeof value === 'string') { - const filterValue = this.latinisePipe.transform(value.toLowerCase()); - return this.filterList[type].filter((elem: any) => this.latinisePipe.transform(elem.label.toLowerCase()).includes(filterValue)); - } else { - return this.filterList[type]; - } - } -} - -export interface HistoryList { - history: any[]; - count: number; -} -export class HistoryListHttpDao { - - constructor(private http: HttpClient) { } - - getRepoIssues(sort: string, order: string, page: number, href: string, search: string): Observable<HistoryList> { - - let offset = page * 10; - const requestUrl = `${href}?limit=10&offset=${offset}&order=${order}&orderBy=${sort}${search}`; + public functions: FunctionsService) { } - return this.http.get<HistoryList>(requestUrl); - } + ngOnInit(): void { } } \ No newline at end of file diff --git a/src/frontend/app/app-common.module.ts b/src/frontend/app/app-common.module.ts index 8abbcd4ed13ed04e04e3f437a65c399deb376160..7420d284b350507e22532fddc9d7831d41762e29 100755 --- a/src/frontend/app/app-common.module.ts +++ b/src/frontend/app/app-common.module.ts @@ -63,7 +63,7 @@ import { TagInputComponent } from '../app/tag/indexing/ta import { DragDropDirective } from '../app/viewer/upload-file-dnd.directive'; import { ContactAutocompleteComponent } from './contact/autocomplete/contact-autocomplete.component'; import { ContactsFormComponent } from './administration/contact/page/form/contacts-form.component'; - +import { HistoryComponent } from './history/history.component'; import { DiffusionsListComponent } from './diffusions/diffusions-list.component'; @@ -125,7 +125,8 @@ export class MyHammerConfig extends HammerGestureConfig { EcplOnlyofficeViewerComponent, ContactAutocompleteComponent, ContactsFormComponent, - CustomSnackbarComponent + CustomSnackbarComponent, + HistoryComponent, ], exports: [ CommonModule, @@ -167,7 +168,8 @@ export class MyHammerConfig extends HammerGestureConfig { DragDropDirective, EcplOnlyofficeViewerComponent, ContactAutocompleteComponent, - ContactsFormComponent + ContactsFormComponent, + HistoryComponent, ], providers: [ HeaderService, diff --git a/src/frontend/app/history/history.component.html b/src/frontend/app/history/history.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8d1daff3c812430b88adbd24eee6d8b782553181 --- /dev/null +++ b/src/frontend/app/history/history.component.html @@ -0,0 +1,91 @@ +<div class="example-loading-shade" *ngIf="isLoadingResults"> + <mat-spinner *ngIf="isLoadingResults"></mat-spinner> +</div> +<div class="table-head"> + <div class="table-head-result"> + <mat-form-field floatLabel="never" style="font-size: 13px;"> + <input type="text" #autoCompleteInput [matAutocomplete]="auto" [placeholder]="lang.filterBy" matInput + [formControl]="searchHistory" (click)="$event.stopPropagation()" maxlength="128"> + <mat-autocomplete #auto="matAutocomplete" (optionSelected)="addItemFilter($event.option)" + (opened)="initFilterListHistory()"> + <mat-option disabled *ngIf="loadingFilters"> + <div style="display: flex;justify-content: center;"> + <mat-spinner diameter="35"></mat-spinner> + </div> + </mat-option> + <ng-container *ngIf="filterList!==null && !loadingFilters"> + <ng-container *ngFor="let keyVal of filterList | keyvalue"> + <mat-optgroup *ngIf="(filteredList[keyVal.key] | async)?.length > 0" [label]="lang[keyVal.key]" + class="filterList"> + <mat-option [id]="keyVal.key" [style.color]="!filter.used ? filterColor[keyVal.key] : ''" + *ngFor="let filter of filteredList[keyVal.key] | async | sortBy : 'label'" + [value]="filter" [disabled]="filter.used"> + {{filter.label}} + </mat-option> + </mat-optgroup> + </ng-container> + </ng-container> + </mat-autocomplete> + </mat-form-field> + </div> + <button color="primary" mat-icon-button title="Afficher tout l'historique"> + <mat-icon class="fas fa-history"></mat-icon> + </button> + <div class="table-head-tool"> + <mat-paginator #paginatorHistoryList [length]="resultsLength" [hidePageSize]="true" [pageSize]="10" + class="paginatorResultList"></mat-paginator> + </div> +</div> +<div [class.table-admin]="resId === null"> + <div class="filterBadges"> + <ng-container *ngFor="let keyVal of filterUsed | keyvalue"> + <ng-container *ngIf="['startDate','endDate'].indexOf(keyVal.key) === -1"> + <span *ngFor="let filter of filterUsed[keyVal.key]; let i=index;" class="label" + [style.background]="filterColor[keyVal.key]" [title]="lang[keyVal.key]" + (click)="removeItemFilter(filter,keyVal.key,i)">{{filter.label}} + <i class="fa fa-times-circle"></i></span> + </ng-container> + </ng-container> + </div> + <mat-table id="history-list" #tableHistoryListSort="matSort" [dataSource]="data" matSort matSortActive="event_date" + matSortDirection="desc" style="width:100%;"> + <ng-container matColumnDef="event_date"> + <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.event}}</mat-header-cell> + <mat-cell mat-cell *matCellDef="let element" [title]="element.event_date | fullDate" + [class.smallText]="resId !== null"> + <div *ngIf="resId !== null" style="font-size: 10px;"> + {{element.userLabel}} + </div> + <div> + {{element.event_date | timeAgo : 'full' | ucfirst}} + </div> + </mat-cell> + </ng-container> + <ng-container matColumnDef="userLabel"> + <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.user | ucfirst}}</mat-header-cell> + <mat-cell *matCellDef="let element"> + {{element.userLabel}} </mat-cell> + </ng-container> + <ng-container matColumnDef="info"> + <mat-header-cell *matHeaderCellDef mat-sort-header style="flex: 2;">{{lang.information}} + </mat-header-cell> + <mat-cell *matCellDef="let element" style="flex: 2;"> + {{element.info}} + </mat-cell> + </ng-container> + <ng-container matColumnDef="remote_ip"> + <mat-header-cell *matHeaderCellDef mat-sort-header>{{lang.ip}} + </mat-header-cell> + <mat-cell *matCellDef="let element"> + {{element.remote_ip}} </mat-cell> + </ng-container> + <ng-container *ngIf="resId === null"> + <mat-header-row *matHeaderRowDef="displayedColumnsHistory"></mat-header-row> + </ng-container> + <mat-row *matRowDef="let row; columns: displayedColumnsHistory;"> + </mat-row> + </mat-table> + <div class="mat-paginator" + style="min-height:48px;min-height: 48px;display: flex;justify-content: end;align-items: center;padding-right: 20px;"> + {{resultsLength}} {{lang.elements}}</div> +</div> \ No newline at end of file diff --git a/src/frontend/app/history/history.component.scss b/src/frontend/app/history/history.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..6eede368ed46e5fce55ed657952eac607df2e5d4 --- /dev/null +++ b/src/frontend/app/history/history.component.scss @@ -0,0 +1,72 @@ +@import '../../css/vars.scss'; + +.active, +.active:hover, +.active:active, +.active:focus { + color: $primary; + border-left: solid 5px $primary; + background: rgba($primary, 0.14); +} + + +.paginatorResultList { + ::ng-deep.mat-paginator-range-label { + justify-content: flex-end; + display: flex; + } +} + +.filterList { + ::ng-deep.mat-optgroup-label { + color: $primary; + position: sticky; + top: 0px; + background: white !important; + z-index: 1; + } +} + +.label { + cursor: pointer; + margin: 5px; +} + +.bg-head-content { + ::ng-deep .mat-focused .mat-form-field-label { + /*change color of label*/ + color: white !important; + } + + ::ng-deep.mat-form-field-underline { + /*change color of underline*/ + background-color: white !important; + } + + ::ng-deep.mat-form-field-ripple { + /*change color of underline when focused*/ + background-color: white !important; + } + + .mat-icon, + .mat-datepicker-toggle { + color: white; + } +} + +.table-admin { + height: 90%; + overflow: auto; + position: absolute; + width: 100%; +} + +.smallText { + flex-direction: column; + align-items: center; + justify-content: center; + font-size: 10px; + opacity: 0.5; + width: 150px; + flex: initial; +} \ No newline at end of file diff --git a/src/frontend/app/history/history.component.ts b/src/frontend/app/history/history.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..de89a606fcae718b5a0acf8b4b70a3da8860c861 --- /dev/null +++ b/src/frontend/app/history/history.component.ts @@ -0,0 +1,279 @@ +import { Component, OnInit, ViewChild, EventEmitter, ElementRef, Input } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { LANG } from '../translate.component'; +import { NotificationService } from '../notification.service'; +import { HeaderService } from '../../service/header.service'; +import { Observable, merge, Subject, of as observableOf, of } from 'rxjs'; +import { MatPaginator, MatSort, MatDialog } from '@angular/material'; +import { takeUntil, startWith, switchMap, map, catchError, filter, exhaustMap, tap, debounceTime, distinctUntilChanged, finalize } from 'rxjs/operators'; +import { FormControl } from '@angular/forms'; +import { FunctionsService } from '../../service/functions.service'; +import { LatinisePipe } from 'ngx-pipes'; + +@Component({ + selector: 'app-history-list', + templateUrl: "history.component.html", + styleUrls: ['history.component.scss'], +}) +export class HistoryComponent implements OnInit { + + lang: any = LANG; + loading: boolean = false; + + filtersChange = new EventEmitter(); + + data: any; + + displayedColumnsHistory: string[] = ['event_date', 'userLabel', 'info', 'remote_ip']; + + isLoadingResults = true; + routeUrl: string = '../../rest/history'; + resultListDatabase: HistoryListHttpDao | null; + resultsLength = 0; + + searchHistory = new FormControl(); + startDateFilter: any = ''; + endDateFilter: any = ''; + filterUrl: string = ''; + filterList: any = null; + filteredList: any = {}; + filterUsed: any = {}; + + filterColor = { + startDate: '#b5cfd8', + endDate: '#7393a7', + actions: '#7d5ba6', + systemActions: '#d6716f', + users: '#009dc5', + }; + + loadingFilters: boolean = true; + + @Input('resId') resId: number = null; + + @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; + @ViewChild('tableHistoryListSort', { static: true }) sort: MatSort; + @ViewChild('autoCompleteInput', { static: true }) autoCompleteInput: ElementRef; + + private destroy$ = new Subject<boolean>(); + + constructor( + public http: HttpClient, + private notify: NotificationService, + private headerService: HeaderService, + public dialog: MatDialog, + public functions: FunctionsService, + private latinisePipe: LatinisePipe) { } + + ngOnInit(): void { + if (this.resId !== null) { + this.routeUrl = '../../rest/history'; + this.displayedColumnsHistory = ['event_date', 'info']; + } else { + this.routeUrl = '../../rest/history'; + this.displayedColumnsHistory = ['event_date', 'userLabel', 'info', 'remote_ip']; + } + this.loading = true; + this.initHistoryList(); + } + + initHistoryList() { + this.resultListDatabase = new HistoryListHttpDao(this.http); + this.paginator.pageIndex = 0; + this.sort.active = 'event_date'; + this.sort.direction = 'desc'; + this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); + + // When list is refresh (sort, page, filters) + merge(this.sort.sortChange, this.paginator.page, this.filtersChange) + .pipe( + takeUntil(this.destroy$), + startWith({}), + switchMap(() => { + this.isLoadingResults = true; + return this.resultListDatabase!.getRepoIssues( + this.sort.active, this.sort.direction, this.paginator.pageIndex, this.routeUrl, this.filterUrl); + }), + map(data => { + this.isLoadingResults = false; + data = this.processPostData(data); + this.resultsLength = data.count; + this.headerService.setHeader(this.lang.administration + ' ' + this.lang.history.toLowerCase(), '', ''); + return data.history; + }), + catchError((err: any) => { + this.notify.handleErrors(err); + this.isLoadingResults = false; + return observableOf([]); + }) + ).subscribe(data => this.data = data); + } + + processPostData(data: any) { + data.history = data.history.map((item: any) => { + return { + ...item, + userLabel : !this.functions.empty(item.userLabel) ? item.userLabel : this.lang.userDeleted + } + }) + return data; + } + + + refreshDao() { + this.paginator.pageIndex = 0; + this.filtersChange.emit(); + } + + initFilterListHistory() { + + if (this.filterList === null) { + this.filterList = {}; + this.loadingFilters = true; + this.http.get("../../rest/history/availableFilters").pipe( + map((data: any) => { + let deletedActions = data.actions.filter((action: any) => action.label === null).map((action: any) => action.id); + let deletedUser = data.users.filter((user: any) => user.label === null).map((user: any) => user.login); + + data.actions = data.actions.filter((action: any) => action.label !== null); + if (deletedActions.length > 0) { + data.actions.push({ + id: deletedActions, + label: this.lang.actionDeleted + }); + } + + data.users = data.users.filter((user: any) => user.label !== null); + + if (deletedUser.length > 0) { + data.users.push({ + id: deletedUser, + label: this.lang.userDeleted + }); + } + + data.systemActions = data.systemActions.map((syst: any) => { + return { + id: syst.id, + label: this.lang[syst.id] + } + }); + return data; + }), + tap((data: any) => { + Object.keys(data).forEach((filterType: any) => { + if (this.functions.empty(this.filterList[filterType])) { + this.filterList[filterType] = []; + this.filteredList[filterType] = []; + } + data[filterType].forEach((element: any) => { + this.filterList[filterType].push(element); + }); + + this.filteredList[filterType] = this.searchHistory.valueChanges + .pipe( + startWith(''), + map(element => element ? this.filter(element, filterType) : this.filterList[filterType].slice()) + ); + }); + + }), + finalize(() => this.loadingFilters = false), + catchError((err: any) => { + this.notify.handleSoftErrors(err); + return of(false); + }) + ).subscribe(); + + } + } + + filterStartDate() { + if (this.functions.empty(this.filterUsed['startDate'])) { + this.filterUsed['startDate'] = []; + } + this.filterUsed['startDate'][0] = { + id: this.functions.empty(this.startDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.startDateFilter), + label: this.functions.empty(this.startDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.startDateFilter) + }; + this.generateUrlFilter(); + this.refreshDao(); + } + + filterEndDate() { + if (this.functions.empty(this.filterUsed['endDate'])) { + this.filterUsed['endDate'] = []; + } + this.filterUsed['endDate'][0] = { + id: this.functions.empty(this.endDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.endDateFilter, true), + label: this.functions.empty(this.endDateFilter) ? '' : this.functions.formatDateObjectToDateString(this.endDateFilter) + }; + this.generateUrlFilter(); + this.refreshDao(); + } + + addItemFilter(elem: any) { + elem.value.used = true; + if (this.functions.empty(this.filterUsed[elem.id])) { + this.filterUsed[elem.id] = []; + } + this.filterUsed[elem.id].push(elem.value); + this.generateUrlFilter(); + this.searchHistory.reset(); + this.autoCompleteInput.nativeElement.blur(); + this.refreshDao(); + } + + removeItemFilter(elem: any, type: string, index: number) { + elem.used = false; + this.filterUsed[type].splice(index, 1); + this.generateUrlFilter(); + this.refreshDao(); + } + + + generateUrlFilter() { + this.filterUrl = ''; + let arrTmpUrl: any[] = []; + Object.keys(this.filterUsed).forEach((type: any) => { + this.filterUsed[type].forEach((filter: any) => { + if (!this.functions.empty(filter.id)) { + if (['startDate', 'endDate'].indexOf(type) > -1) { + arrTmpUrl.push(`${type}=${filter.id}`); + } else { + arrTmpUrl.push(`${type}[]=${filter.id}`); + } + } + }); + }); + if (arrTmpUrl.length > 0) { + this.filterUrl = '&' + arrTmpUrl.join('&'); + } + } + + private filter(value: string, type: string): any[] { + if (typeof value === 'string') { + const filterValue = this.latinisePipe.transform(value.toLowerCase()); + return this.filterList[type].filter((elem: any) => this.latinisePipe.transform(elem.label.toLowerCase()).includes(filterValue)); + } else { + return this.filterList[type]; + } + } +} + +export interface HistoryList { + history: any[]; + count: number; +} +export class HistoryListHttpDao { + + constructor(private http: HttpClient) { } + + getRepoIssues(sort: string, order: string, page: number, href: string, search: string): Observable<HistoryList> { + + let offset = page * 10; + const requestUrl = `${href}?limit=10&offset=${offset}&order=${order}&orderBy=${sort}${search}`; + + return this.http.get<HistoryList>(requestUrl); + } +} \ No newline at end of file diff --git a/src/frontend/app/process/process.component.html b/src/frontend/app/process/process.component.html index 91c824c2083bee28ae6b3062388208159a43f29c..5f699b615449ce47d5b43e52b92ce69a5ca4f287 100644 --- a/src/frontend/app/process/process.component.html +++ b/src/frontend/app/process/process.component.html @@ -70,6 +70,9 @@ </div> </ng-container> <ng-container *ngIf="!isModalOpen(); else elseTemplate"> + <app-history-list *ngIf="currentTool === 'history' && !loading" #appHistoryList + [resId]="currentResourceInformations.resId"> + </app-history-list> <app-notes-list *ngIf="currentTool === 'notes' && !loading" #appNotesList [editMode]="true" [resId]="currentResourceInformations.resId"> </app-notes-list> @@ -77,9 +80,11 @@ [adminMode]="false" [resId]="currentResourceInformations.resId" [expanded]="true"> </app-diffusions-list> <app-visa-workflow *ngIf="currentTool === 'visaCircuit' && !loading" #appVisaWorkflow - [resId]="currentResourceInformations.resId" [adminMode]="privilegeService.hasCurrentUserPrivilege('config_visa_workflow')"></app-visa-workflow> + [resId]="currentResourceInformations.resId" + [adminMode]="privilegeService.hasCurrentUserPrivilege('config_visa_workflow')"></app-visa-workflow> <app-avis-workflow *ngIf="currentTool === 'opinionCircuit' && !loading" #appAvisWorkflow - [resId]="currentResourceInformations.resId" [adminMode]="privilegeService.hasCurrentUserPrivilege('config_avis_workflow')"></app-avis-workflow> + [resId]="currentResourceInformations.resId" + [adminMode]="privilegeService.hasCurrentUserPrivilege('config_avis_workflow')"></app-avis-workflow> <app-attachments-list *ngIf="currentTool === 'attachments' && !loading" #appAttachmentsList [resId]="currentResourceInformations.resId" [target]="'process'" (reloadBadgeAttachments)="refreshBadge($event,'attachments')"> @@ -87,11 +92,11 @@ <app-indexing-form *ngIf="currentTool === 'info' && !loading" #indexingForm [groupId]="currentGroupId" [resId]="currentResourceInformations.resId" [indexingFormId]="currentResourceInformations.modelId" [mode]="'process'" [canEdit]="canEditData" [hideDiffusionList]="true" - (loadingFormEndEvent)="triggerProcessAction()" (retrieveDocumentEvent)="appDocumentViewer.saveDocService()"></app-indexing-form> + (loadingFormEndEvent)="triggerProcessAction()" + (retrieveDocumentEvent)="appDocumentViewer.saveDocService()"></app-indexing-form> <div style="position: sticky;bottom: 0px;text-align:right;"> - <button mat-fab [title]="lang.saveModifications" - *ngIf="isToolModified()" - (click)="saveTool()" color="accent"> + <button mat-fab [title]="lang.saveModifications" *ngIf="isToolModified()" (click)="saveTool()" + color="accent"> <mat-icon style="height:auto;font-size:20px;" class="fas fa-check"></mat-icon> </button> </div> @@ -238,17 +243,22 @@ </button> </div> <div class="modal-module-content"> + <app-history-list *ngIf="currentTool === 'history' && !loading" #appHistoryList + [resId]="currentResourceInformations.resId"> + </app-history-list> <app-notes-list *ngIf="modal.id === 'notes' && !loading" #appNotesList [editMode]="true" [resId]="currentResourceInformations.resId"> </app-notes-list> <app-diffusions-list *ngIf="modal.id === 'diffusionList' && !loading" #appDiffusionsList [adminMode]="false" [resId]="currentResourceInformations.resId" [expanded]="true"> </app-diffusions-list> - <app-visa-workflow *ngIf="modal.id === 'visaCircuit' && !loading" [adminMode]="privilegeService.hasCurrentUserPrivilege('config_visa_workflow')" #appVisaWorkflow - [resId]="currentResourceInformations.resId" > + <app-visa-workflow *ngIf="modal.id === 'visaCircuit' && !loading" + [adminMode]="privilegeService.hasCurrentUserPrivilege('config_visa_workflow')" #appVisaWorkflow + [resId]="currentResourceInformations.resId"> </app-visa-workflow> <app-avis-workflow *ngIf="modal.id === 'opinionCircuit' && !loading" #appAvisWorkflow - [resId]="currentResourceInformations.resId" [adminMode]="privilegeService.hasCurrentUserPrivilege('config_avis_workflow')"> + [resId]="currentResourceInformations.resId" + [adminMode]="privilegeService.hasCurrentUserPrivilege('config_avis_workflow')"> </app-avis-workflow> <app-attachments-list *ngIf="modal.id === 'attachments' && !loading" #appAttachmentsList [resId]="currentResourceInformations.resId" (reloadBadgeAttachments)="refreshBadge($event,'attachments')">