Audit screen

This commit is contained in:
Adina Țeudan 2021-03-13 19:35:19 +02:00
parent 9eb7c5fe83
commit 5eb79a03e4
14 changed files with 347 additions and 4 deletions

View File

@ -88,7 +88,7 @@ import { ColorPickerModule } from 'ngx-color-picker';
import { AceEditorModule } from 'ng2-ace-editor';
import { TeamMembersComponent } from './components/team-members/team-members.component';
import { AdminBreadcrumbsComponent } from './components/admin-page-header/admin-breadcrumbs.component';
import { UserListingScreenComponent } from './screens/admin/users/user-listing-screen.component';
import { UserListingScreenComponent } from './screens/admin/user-listing-screen/user-listing-screen.component';
import { NotificationsComponent } from './components/notifications/notifications.component';
import { RulesScreenComponent } from './screens/admin/rules-screen/rules-screen.component';
import { WatermarkScreenComponent } from './screens/admin/watermark-screen/watermark-screen.component';
@ -116,6 +116,8 @@ import { NgxChartsModule } from '@swimlane/ngx-charts';
import { ComboChartComponent, ComboSeriesVerticalComponent } from './screens/admin/license-information-screen/combo-chart';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { ForceRedactionDialogComponent } from './dialogs/force-redaction-dialog/force-redaction-dialog.component';
import { AuditScreenComponent } from './screens/admin/audit-screen/audit-screen.component';
import { PaginationComponent } from './components/pagination/pagination.component';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -278,6 +280,14 @@ const routes = [
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: 'audit',
component: AuditScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
}
]
}
@ -378,7 +388,9 @@ const matImports = [
DigitalSignatureScreenComponent,
RemoveAnnotationsDialogComponent,
ComboChartComponent,
ComboSeriesVerticalComponent
ComboSeriesVerticalComponent,
AuditScreenComponent,
PaginationComponent
],
imports: [
BrowserModule,

View File

@ -26,6 +26,15 @@
*ngIf="root && permissionService.isAdmin()"
></a>
<a
class="breadcrumb"
[routerLink]="'/ui/admin/audit'"
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active"
translate="audit"
*ngIf="root && permissionService.isAdmin()"
></a>
<a
class="breadcrumb"
[routerLink]="'/ui/admin/users'"

View File

@ -0,0 +1,7 @@
<div translate="pagination.previous" class="page" [class.disabled]="currentPage < 2" (click)="selectPage(currentPage - 1)"></div>
<span>|</span>
<div *ngFor="let page of displayedPages" class="page" [class.dots]="page === '...'" [class.active]="page === currentPage" (click)="selectPage(page)">
{{ displayValue(page) }}
</div>
<span>|</span>
<div translate="pagination.next" class="page" [class.disabled]="currentPage === totalPages - 1" (click)="selectPage(currentPage + 1)"></div>

View File

@ -0,0 +1,28 @@
@import '../../../assets/styles/red-variables';
:host {
display: flex;
> *:not(:last-child) {
margin-right: 12px;
}
.disabled,
span {
opacity: 0.5;
}
.page {
cursor: pointer;
&.disabled,
&.dots {
cursor: default;
}
&.active {
color: $primary;
font-weight: bold;
}
}
}

View File

@ -0,0 +1,68 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
const DISPLAYED_ITEMS = 5;
@Component({
selector: 'redaction-pagination',
templateUrl: './pagination.component.html',
styleUrls: ['./pagination.component.scss']
})
export class PaginationComponent implements OnInit {
private _currentPage: number;
private _totalPages: number;
public displayedPages: (number | string)[];
@Input()
public set settings(value: { currentPage: number; totalPages: number }) {
this._currentPage = value.currentPage;
this._totalPages = value.totalPages;
this._updatePagesArray();
}
public get currentPage() {
return this._currentPage;
}
public get totalPages() {
return this._totalPages;
}
@Output() pageChanged = new EventEmitter<number>();
public displayed;
constructor() {}
ngOnInit(): void {}
private _updatePagesArray() {
this.displayedPages = [0];
if (Math.max(1, this.currentPage - 1) > 1) {
this.displayedPages.push('...');
}
for (let page = Math.max(1, this.currentPage - 1); page <= Math.min(this.currentPage + 1, this.totalPages - 1); ++page) {
this.displayedPages.push(page);
}
if (Math.min(this.currentPage + 1, this.totalPages - 1) !== this.totalPages - 1) {
if (this.currentPage + 1 < this.totalPages - 2) {
this.displayedPages.push('...');
}
this.displayedPages.push(this.totalPages - 1);
}
}
public get allDisplayed(): boolean {
return this.totalPages > DISPLAYED_ITEMS;
}
public selectPage(page: number | string) {
if (page !== '...') {
this.pageChanged.emit(page as number);
}
}
public displayValue(page: number | string) {
return page === '...' ? page : (page as number) + 1;
}
}

View File

@ -0,0 +1,72 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1" [root]="true"></redaction-admin-breadcrumbs>
<div class="actions">
<redaction-circle-button
class="ml-6"
*ngIf="permissionsService.isUser()"
[routerLink]="['/ui/projects/']"
tooltip="common.close"
tooltipPosition="before"
icon="red:close"
></redaction-circle-button>
</div>
</div>
<div class="red-content-inner">
<div class="left-container">
<div class="header-item">
<span class="all-caps-label">
{{ 'audit-screen.table-header.title' | translate: { length: logs?.totalHits || 0 } }}
</span>
<div class="actions-wrapper">
<redaction-pagination
[settings]="{ currentPage: logs?.page || 0, totalPages: totalPages }"
(pageChanged)="pageChanged($event)"
></redaction-pagination>
<div class="separator">·</div>
<!-- <div class="small-label" translate="audit-screen.table-header.category"></div>-->
<form [formGroup]="categoryForm">
<div class="red-input-group w-150">
<mat-form-field class="no-label">
<mat-select formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
{{ category }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</form>
</div>
</div>
<div class="table-header" redactionSyncWidth="table-item">
<redaction-table-col-name label="audit-screen.table-col-names.message" column="message"></redaction-table-col-name>
<redaction-table-col-name label="audit-screen.table-col-names.date" column="date"></redaction-table-col-name>
<redaction-table-col-name label="audit-screen.table-col-names.user" column="user"></redaction-table-col-name>
<redaction-table-col-name label="audit-screen.table-col-names.category" column="category"></redaction-table-col-name>
<div class="scrollbar-placeholder"></div>
</div>
<cdk-virtual-scroll-viewport [itemSize]="100" redactionHasScrollbar>
<div class="table-item pointer" *cdkVirtualFor="let log of logs?.data">
<div>
{{ log.message }}
</div>
<div>
{{ log.recordDate }}
</div>
<div>
{{ log.userId }}
</div>
<div>
{{ log.category }}
</div>
<div class="scrollbar-placeholder"></div>
</div>
</cdk-virtual-scroll-viewport>
</div>
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>

View File

@ -0,0 +1,37 @@
.left-container {
width: 100vw;
.header-item {
justify-content: space-between;
}
.actions-wrapper {
display: flex;
align-items: center;
.separator {
margin: 0 20px;
font-weight: bold;
font-size: 16px;
opacity: 0.7;
}
}
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 1fr 1fr 1fr 1fr 11px;
.table-item {
> div {
padding: 0 24px;
}
}
}
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
}
}
}

View File

@ -0,0 +1,90 @@
import { Component, OnInit } from '@angular/core';
import { PermissionsService } from '../../../common/service/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../utils/debounce';
import { AuditControllerService, AuditModel, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
import { TranslateService } from '@ngx-translate/core';
const PAGE_SIZE = 5;
let ALL_CATEGORIES: string;
@Component({
selector: 'redaction-audit-screen',
templateUrl: './audit-screen.component.html',
styleUrls: ['./audit-screen.component.scss']
})
export class AuditScreenComponent implements OnInit {
public categoryForm: FormGroup;
public viewReady = false;
public categories: string[] = [];
public logs: AuditResponse;
public currentPage = 1;
constructor(
public readonly permissionsService: PermissionsService,
private readonly _formBuilder: FormBuilder,
private readonly _auditControllerService: AuditControllerService,
private readonly _translateService: TranslateService
) {
ALL_CATEGORIES = this._translateService.instant('audit-screen.all-categories');
this.categoryForm = this._formBuilder.group({
category: [ALL_CATEGORIES]
});
this.categoryForm.valueChanges.subscribe((value) => this._filterByCategories(value));
}
ngOnInit(): void {
this._fetchAllData().then(() => {
this.viewReady = true;
});
}
private _getAuditLogRequestBody(config?: { page: number }) {
const category = this.categoryForm.get('category').value;
return { pageSize: PAGE_SIZE, withTotalHits: true, page: config?.page, category: category === ALL_CATEGORIES ? undefined : category };
}
private _fetchAllData(): Promise<void> {
const promises = [];
promises.push(this._auditControllerService.getAuditCategories().toPromise());
promises.push(this._auditControllerService.searchAuditLog(this._getAuditLogRequestBody()).toPromise());
return Promise.all(promises).then((data) => {
this.categories = data[0].map((c) => c.category);
this.categories.splice(0, 0, ALL_CATEGORIES);
this.logs = data[1];
});
}
private _filterByCategories(value: { category: string }) {
this.viewReady = false;
this._auditControllerService
.searchAuditLog(this._getAuditLogRequestBody())
.toPromise()
.then((data) => {
this.logs = data;
this.viewReady = true;
});
}
public get totalPages(): number {
if (!this.logs) {
return 0;
}
return Math.ceil(this.logs.totalHits / PAGE_SIZE);
}
public pageChanged(page: number) {
this.viewReady = false;
this._auditControllerService
.searchAuditLog(this._getAuditLogRequestBody({ page }))
.toPromise()
.then((data) => {
this.logs = data;
this.viewReady = true;
});
}
}

View File

@ -6,8 +6,6 @@ import { PermissionsService } from '../../../common/service/permissions.service'
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../utils/debounce';
import { RuleSetModel } from '@redaction/red-ui-http';
import { tap } from 'rxjs/operators';
import { forkJoin } from 'rxjs';
@Component({
selector: 'redaction-rule-sets-listing-screen',

View File

@ -790,6 +790,23 @@
"legend": "Legend"
}
},
"audit": "Audit",
"audit-screen": {
"table-header": {
"title": "{{length}} Logs"
},
"table-col-names": {
"message": "Message",
"user": "User",
"date": "Date",
"category": "Category"
},
"all-categories": "All Categories"
},
"pagination": {
"previous": "Prev",
"next": "Next"
},
"default-colors": "Default Colors",
"default-colors-screen": {
"table-header": {

View File

@ -13,3 +13,8 @@
color: $grey-1;
}
}
.mat-form-field.no-label .mat-form-field-infix {
padding: 0 !important;
border-top: 0 !important;
}