600 lines
17 KiB
TypeScript
600 lines
17 KiB
TypeScript
/** Angular Imports */
|
|
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
|
|
import { MatPaginator } from '@angular/material/paginator';
|
|
import { MatSort, MatSortHeader } from '@angular/material/sort';
|
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
|
import { UntypedFormControl, ReactiveFormsModule } from '@angular/forms';
|
|
|
|
/** Custom Data Source */
|
|
import { AuditTrailsDataSource } from './audit-trail.datasource';
|
|
|
|
/** Custom Services */
|
|
import { SystemService } from '../system.service';
|
|
import { SettingsService } from 'app/settings/settings.service';
|
|
|
|
/** rxjs Imports */
|
|
import { merge } from 'rxjs';
|
|
import { tap, debounceTime, distinctUntilChanged, startWith, map } from 'rxjs/operators';
|
|
import { Dates } from 'app/core/utils/dates';
|
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
import { NgFor, NgIf, AsyncPipe } from '@angular/common';
|
|
import { MatOption, MatAutocompleteTrigger, MatAutocomplete } from '@angular/material/autocomplete';
|
|
import { MatProgressBar } from '@angular/material/progress-bar';
|
|
import {
|
|
MatTable,
|
|
MatColumnDef,
|
|
MatHeaderCellDef,
|
|
MatHeaderCell,
|
|
MatCellDef,
|
|
MatCell,
|
|
MatHeaderRowDef,
|
|
MatHeaderRow,
|
|
MatRowDef,
|
|
MatRow
|
|
} from '@angular/material/table';
|
|
import { DatetimeFormatPipe } from '../../pipes/datetime-format.pipe';
|
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
|
|
/**
|
|
* Audit Trails Component.
|
|
*/
|
|
@Component({
|
|
selector: 'mifosx-audit-trails',
|
|
templateUrl: './audit-trails.component.html',
|
|
styleUrls: ['./audit-trails.component.scss'],
|
|
imports: [
|
|
...STANDALONE_SHARED_IMPORTS,
|
|
FaIconComponent,
|
|
MatAutocompleteTrigger,
|
|
MatAutocomplete,
|
|
MatProgressBar,
|
|
MatTable,
|
|
MatSort,
|
|
MatColumnDef,
|
|
MatHeaderCellDef,
|
|
MatHeaderCell,
|
|
MatSortHeader,
|
|
MatCellDef,
|
|
MatCell,
|
|
MatHeaderRowDef,
|
|
MatHeaderRow,
|
|
MatRowDef,
|
|
MatRow,
|
|
MatPaginator,
|
|
AsyncPipe,
|
|
DatetimeFormatPipe
|
|
]
|
|
})
|
|
export class AuditTrailsComponent implements OnInit, AfterViewInit {
|
|
/** Minimum date allowed. */
|
|
minDate = new Date(2000, 0, 1);
|
|
/** Maximum date allowed. */
|
|
maxDate = new Date();
|
|
/** Audit Trails Data */
|
|
auditTrailsData: any;
|
|
/** Filtered user data for autocomplete. */
|
|
filteredUserData: any;
|
|
/** Filtered action data for autocomplete. */
|
|
filteredActionData: any;
|
|
/** Filtered entity data for autocomplete. */
|
|
filteredEntityData: any;
|
|
/** Filtered checker data for autocomplete. */
|
|
filteredCheckerData: any;
|
|
/** Audit Trail Search Template Data. */
|
|
auditTrailSearchTemplateData: any;
|
|
/** Columns to be displayed in audit trails table. */
|
|
displayedColumns: string[] = [
|
|
'id',
|
|
'resourceId',
|
|
'processingResult',
|
|
'maker',
|
|
'actionName',
|
|
'entityName',
|
|
'officeName',
|
|
'madeOnDate',
|
|
'checker',
|
|
'checkedOnDate',
|
|
'clientIp'
|
|
];
|
|
/** Data source for audit trails table. */
|
|
dataSource: AuditTrailsDataSource;
|
|
/** Audit Trails filter. */
|
|
filterAuditTrailsBy = [
|
|
{
|
|
type: 'actionName',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'entityName',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'resourceId',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'makerId',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'makerDateTimeFrom',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'makerDateTimeTo',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'checkerDateTimeFrom',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'checkerDateTimeTo',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'checkerId',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'processingResult',
|
|
value: ''
|
|
},
|
|
{
|
|
type: 'dateFormat',
|
|
value: this.settingsService.dateFormat
|
|
},
|
|
{
|
|
type: 'locale',
|
|
value: this.settingsService.language.code
|
|
}
|
|
];
|
|
/** User form control. */
|
|
user = new UntypedFormControl('');
|
|
/** From date form control. */
|
|
fromDate = new UntypedFormControl();
|
|
/** From time form control. */
|
|
fromTime = new UntypedFormControl();
|
|
/** Checked from date form control. */
|
|
checkedFromDate = new UntypedFormControl();
|
|
/** Checked from time form control. */
|
|
checkedFromTime = new UntypedFormControl();
|
|
/** Processing result form control. */
|
|
processingResult = new UntypedFormControl();
|
|
/** Action name form control. */
|
|
actionName = new UntypedFormControl();
|
|
/** Resource ID form control. */
|
|
resourceId = new UntypedFormControl('');
|
|
/** To date form control. */
|
|
toDate = new UntypedFormControl();
|
|
/** To time form control. */
|
|
toTime = new UntypedFormControl();
|
|
/** Checked to date form control. */
|
|
checkedToDate = new UntypedFormControl();
|
|
/** Checked to time form control. */
|
|
checkedToTime = new UntypedFormControl();
|
|
/** Entity name form control. */
|
|
entityName = new UntypedFormControl();
|
|
/** Checker form control. */
|
|
checker = new UntypedFormControl();
|
|
|
|
isLoading = false;
|
|
|
|
/** Paginator for audit trails table. */
|
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
|
/** Sorter for audit trails table. */
|
|
@ViewChild(MatSort) sort: MatSort;
|
|
|
|
/**
|
|
* Retrieves the audit trail search template data from `resolve`.
|
|
* @param {ActivatedRoute} route Activated Route.
|
|
* @param {SystemService} systemService System Service.
|
|
* @param {Dates} dateUtils Dates utils
|
|
* @param {SettingsService} settingsService Settings Service
|
|
*/
|
|
constructor(
|
|
private route: ActivatedRoute,
|
|
private systemService: SystemService,
|
|
private dateUtils: Dates,
|
|
private settingsService: SettingsService
|
|
) {
|
|
this.route.data.subscribe((data: { auditTrailSearchTemplate: any }) => {
|
|
this.auditTrailSearchTemplateData = data.auditTrailSearchTemplate;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sets filtered users, actions and entities for autocomplete and audit trails table.
|
|
*/
|
|
ngOnInit() {
|
|
this.maxDate = this.settingsService.businessDate;
|
|
this.setFilteredUsers();
|
|
this.setFilteredActions();
|
|
this.setFilteredEntities();
|
|
this.setFilteredCheckers();
|
|
this.dataSource = new AuditTrailsDataSource(this.systemService);
|
|
this.getAuditTrails();
|
|
}
|
|
|
|
/**
|
|
* Subscribes to all search filters:
|
|
* User Name, From Date, To Date, Checked From Date, Checked To Date, Resource ID, Action Name, Entity Name, Checker
|
|
* sort change and page change.
|
|
*/
|
|
ngAfterViewInit() {
|
|
this.user.valueChanges
|
|
.pipe(
|
|
map((value) => (value.id ? value.id : '')),
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((filterValue) => {
|
|
this.applyFilter(filterValue, 'makerId');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.fromDate.valueChanges
|
|
.pipe(
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((filterValue) => {
|
|
this.applyFilter(this.getDateTime(filterValue, this.fromTime.value), 'makerDateTimeFrom');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.fromTime.valueChanges
|
|
.pipe(
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((timeValue) => {
|
|
this.applyFilter(this.getDateTime(this.fromDate.value, timeValue), 'makerDateTimeFrom');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.toDate.valueChanges
|
|
.pipe(
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((filterValue) => {
|
|
this.applyFilter(this.getDateTime(filterValue, this.toTime.value), 'makerDateTimeTo');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.toTime.valueChanges
|
|
.pipe(
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((timeValue) => {
|
|
this.applyFilter(this.getDateTime(this.toDate.value, timeValue), 'makerDateTimeTo');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.checkedFromDate.valueChanges
|
|
.pipe(
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((filterValue) => {
|
|
this.applyFilter(this.getDateTime(filterValue, this.checkedFromTime.value), 'checkerDateTimeFrom');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.checkedFromTime.valueChanges
|
|
.pipe(
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((timeValue) => {
|
|
this.applyFilter(this.getDateTime(this.checkedFromDate.value, timeValue), 'checkerDateTimeFrom');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.checkedToDate.valueChanges
|
|
.pipe(
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((filterValue) => {
|
|
this.applyFilter(this.getDateTime(filterValue, this.checkedToTime.value), 'checkerDateTimeTo');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.checkedToTime.valueChanges
|
|
.pipe(
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((timeValue) => {
|
|
this.applyFilter(this.getDateTime(this.checkedToDate.value, timeValue), 'checkerDateTimeTo');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.resourceId.valueChanges
|
|
.pipe(
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((filterValue) => {
|
|
this.applyFilter(filterValue, 'resourceId');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.actionName.valueChanges
|
|
.pipe(
|
|
map((value) => (value ? value : '')),
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((filterValue) => {
|
|
this.applyFilter(filterValue, 'actionName');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.entityName.valueChanges
|
|
.pipe(
|
|
map((value) => (value ? value : '')),
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((filterValue) => {
|
|
this.applyFilter(filterValue, 'entityName');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
this.checker.valueChanges
|
|
.pipe(
|
|
map((value) => (value ? value : '')),
|
|
debounceTime(500),
|
|
distinctUntilChanged(),
|
|
tap((filterValue) => {
|
|
this.applyFilter(filterValue.id, 'checkerId');
|
|
})
|
|
)
|
|
.subscribe();
|
|
|
|
//this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
|
|
|
|
merge(this.sort.sortChange, this.paginator.page)
|
|
.pipe(tap(() => this.loadAuditTrailsPage()))
|
|
.subscribe();
|
|
}
|
|
|
|
/**
|
|
* Initializes the data source for audit trails table and loads the first page.
|
|
*/
|
|
getAuditTrails() {
|
|
this.isLoading = true;
|
|
const isActive: string = this.sort ? this.sort.active : '';
|
|
const direction: string = this.sort ? this.sort.direction : '';
|
|
const pageIndex: number = this.paginator ? this.paginator.pageIndex : 0;
|
|
const pageSize: any = this.paginator ? this.paginator.pageSize : 20;
|
|
|
|
this.dataSource.getAuditTrails(this.filterAuditTrailsBy, isActive, direction, pageIndex, pageSize);
|
|
this.isLoading = false;
|
|
}
|
|
|
|
/**
|
|
* Loads a page of audit trails.
|
|
*/
|
|
loadAuditTrailsPage() {
|
|
if (!this.sort.direction) {
|
|
delete this.sort.active;
|
|
}
|
|
this.getAuditTrails();
|
|
}
|
|
|
|
/**
|
|
* Filters data in audit trails table based on passed value and poperty.
|
|
* @param {string} filterValue Value to filter data.
|
|
* @param {string} property Property to filter data by.
|
|
*/
|
|
applyFilter(filterValue: string, property: string) {
|
|
this.paginator.pageIndex = 0;
|
|
const findIndex = this.filterAuditTrailsBy.findIndex((filter) => filter.type === property);
|
|
this.filterAuditTrailsBy[findIndex].value = filterValue;
|
|
this.loadAuditTrailsPage();
|
|
}
|
|
|
|
/**
|
|
* Displays user name in form control input.
|
|
* @param {any} user User data.
|
|
* @returns {string} User name if valid otherwise undefined.
|
|
*/
|
|
displayUserName(user?: any): string | undefined {
|
|
return user ? user.name : undefined;
|
|
}
|
|
|
|
/**
|
|
* Displays action name in form control input.
|
|
* @param {any} action Action data.
|
|
* @returns {string} Action name if valid otherwise undefined.
|
|
*/
|
|
displayActionName(action?: any): string | undefined {
|
|
return action ? action : undefined;
|
|
}
|
|
|
|
/**
|
|
* Displays entity name in form control input.
|
|
* @param {any} entity Entity data.
|
|
* @returns {string} Entity name if valid otherwise undefined.
|
|
*/
|
|
displayEntityName(entity?: any): string | undefined {
|
|
return entity ? entity : undefined;
|
|
}
|
|
|
|
/**
|
|
* Sets filtered users for autocomplete.
|
|
*/
|
|
setFilteredUsers() {
|
|
this.filteredUserData = this.user.valueChanges.pipe(
|
|
startWith(''),
|
|
map((user: any) => (typeof user === 'string' ? user : user.name)),
|
|
map((userName: string) =>
|
|
userName ? this.filterUserAutocompleteData(userName) : this.auditTrailSearchTemplateData.appUsers
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sets filtered checkers for autocomplete.
|
|
*/
|
|
setFilteredCheckers() {
|
|
this.filteredCheckerData = this.checker.valueChanges.pipe(
|
|
startWith(''),
|
|
map((user: any) => (typeof user === 'string' ? user : user.name)),
|
|
map((userName: string) =>
|
|
userName ? this.filterUserAutocompleteData(userName) : this.auditTrailSearchTemplateData.appUsers
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sets filtered actions for autocomplete.
|
|
*/
|
|
setFilteredActions() {
|
|
this.filteredActionData = this.actionName.valueChanges.pipe(
|
|
startWith(''),
|
|
map((action: any) => (typeof action === 'string' ? action : '')),
|
|
map((actionName: string) =>
|
|
actionName ? this.filterActionAutocompleteData(actionName) : this.auditTrailSearchTemplateData.actionNames
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sets filtered entities for autocomplete.
|
|
*/
|
|
setFilteredEntities() {
|
|
this.filteredEntityData = this.entityName.valueChanges.pipe(
|
|
startWith(''),
|
|
map((entity: any) => (typeof entity === 'string' ? entity : '')),
|
|
map((entityName: string) =>
|
|
entityName ? this.filterEntityAutocompleteData(entityName) : this.auditTrailSearchTemplateData.entityNames
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Filters users.
|
|
* @param {string} userName User name to filter user by.
|
|
* @returns {any} Filtered users.
|
|
*/
|
|
private filterUserAutocompleteData(userName: string): any {
|
|
return this.auditTrailSearchTemplateData.appUsers.filter((user: any) =>
|
|
user.username.toLowerCase().includes(userName.toLowerCase())
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Filters actions.
|
|
* @param {string} actionName Action name to filter action by.
|
|
* @returns {any} Filtered actions.
|
|
*/
|
|
private filterActionAutocompleteData(actionName: string): any {
|
|
return this.auditTrailSearchTemplateData.actionNames.filter((action: any) =>
|
|
action.toLowerCase().includes(actionName.toLowerCase())
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Filters entities.
|
|
* @param {string} entityName Entity name to filter action by.
|
|
* @returns {any} Filtered entities.
|
|
*/
|
|
private filterEntityAutocompleteData(entityName: string): any {
|
|
return this.auditTrailSearchTemplateData.entityNames.filter((entity: any) =>
|
|
entity.toLowerCase().includes(entityName.toLowerCase())
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Generates the CSV file of Audit Trails Data.
|
|
*/
|
|
downloadCSV() {
|
|
const dateFormat = this.settingsService.dateFormat;
|
|
const replacer = (key: any, value: any) => (value === undefined ? '' : value);
|
|
const header = [
|
|
'ID',
|
|
'Resource ID',
|
|
'Status',
|
|
'Office',
|
|
'Made On',
|
|
'Maker',
|
|
'Checked On',
|
|
'Checker',
|
|
'Entity',
|
|
'Action',
|
|
'Client'
|
|
];
|
|
const headerCode = [
|
|
'id',
|
|
'resourceId',
|
|
'processingResult',
|
|
'officeName',
|
|
'madeOnDate',
|
|
'maker',
|
|
'checkedOnDate',
|
|
'checker',
|
|
'entityName',
|
|
'actionName',
|
|
'clientName'
|
|
];
|
|
this.systemService
|
|
.getAuditTrails(this.filterAuditTrailsBy, this.sort.active ? this.sort.active : '', this.sort.direction, 0, -1)
|
|
.subscribe((response: any) => {
|
|
if (response !== undefined) {
|
|
let csv = response.pageItems.map((row: any) =>
|
|
headerCode.map((fieldName) =>
|
|
(fieldName === 'madeOnDate' || fieldName === 'checkedOnDate') &&
|
|
row[fieldName] != null &&
|
|
row[fieldName] !== ''
|
|
? JSON.stringify(this.dateUtils.formatDate(row[fieldName], 'YYYY-MM-DDTHH:mm:ssZ'))
|
|
: JSON.stringify(row[fieldName], replacer)
|
|
)
|
|
);
|
|
csv.unshift(`data:text/csv;charset=utf-8,${header.join()}`);
|
|
csv = csv.join('\r\n');
|
|
const link = document.createElement('a');
|
|
link.setAttribute('href', encodeURI(csv));
|
|
link.setAttribute('download', 'Audit Trails.csv');
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Gets the date from the passed timestamp.
|
|
*
|
|
* TODO: Update once language and date settings are setup.
|
|
*
|
|
* @param {any} timestamp Timestamp from which date is to be extracted.
|
|
*/
|
|
private getDate(timestamp: any) {
|
|
const dateFormat = this.settingsService.dateFormat;
|
|
return this.dateUtils.formatDate(timestamp, dateFormat);
|
|
}
|
|
private getDateTime(date: Date, timeStr: string): string {
|
|
if (!date) {
|
|
return '';
|
|
}
|
|
const result = new Date(date);
|
|
if (timeStr) {
|
|
const [
|
|
hours,
|
|
minutes,
|
|
seconds
|
|
] = timeStr.split(':').map(Number);
|
|
result.setHours(hours || 0);
|
|
result.setMinutes(minutes || 0);
|
|
result.setSeconds(seconds || 0);
|
|
}
|
|
return this.dateUtils.formatDate(result, 'YYYY-MM-DDTHH:mm:ssZ');
|
|
}
|
|
}
|