diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index bcd985e8a..46ebf642c 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -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, diff --git a/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.html b/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.html index ddc442bab..b2280db94 100644 --- a/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.html +++ b/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.html @@ -26,6 +26,15 @@ *ngIf="root && permissionService.isAdmin()" > + + +| +
+ {{ displayValue(page) }} +
+| +
diff --git a/apps/red-ui/src/app/components/pagination/pagination.component.scss b/apps/red-ui/src/app/components/pagination/pagination.component.scss new file mode 100644 index 000000000..e2061e12a --- /dev/null +++ b/apps/red-ui/src/app/components/pagination/pagination.component.scss @@ -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; + } + } +} diff --git a/apps/red-ui/src/app/components/pagination/pagination.component.ts b/apps/red-ui/src/app/components/pagination/pagination.component.ts new file mode 100644 index 000000000..e4472a8c0 --- /dev/null +++ b/apps/red-ui/src/app/components/pagination/pagination.component.ts @@ -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(); + + 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; + } +} diff --git a/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.html b/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.html new file mode 100644 index 000000000..651646ca9 --- /dev/null +++ b/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.html @@ -0,0 +1,72 @@ +
+ +
+
+
+ + {{ 'audit-screen.table-header.title' | translate: { length: logs?.totalHits || 0 } }} + +
+ +
ยท
+ +
+
+ + + + {{ category }} + + + +
+
+
+
+ +
+ + + + +
+
+ + +
+
+ {{ log.message }} +
+
+ {{ log.recordDate }} +
+
+ {{ log.userId }} +
+
+ {{ log.category }} +
+
+
+
+
+
+
+ + diff --git a/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.scss b/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.scss new file mode 100644 index 000000000..03177cc29 --- /dev/null +++ b/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.scss @@ -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; + } + } + } +} diff --git a/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.ts b/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.ts new file mode 100644 index 000000000..524a52280 --- /dev/null +++ b/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.ts @@ -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 { + 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; + }); + } +} diff --git a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts index 07005bcac..d9824c4d5 100644 --- a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts @@ -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', diff --git a/apps/red-ui/src/app/screens/admin/users/user-listing-screen.component.html b/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.html similarity index 100% rename from apps/red-ui/src/app/screens/admin/users/user-listing-screen.component.html rename to apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.html diff --git a/apps/red-ui/src/app/screens/admin/users/user-listing-screen.component.scss b/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/admin/users/user-listing-screen.component.scss rename to apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.scss diff --git a/apps/red-ui/src/app/screens/admin/users/user-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts similarity index 100% rename from apps/red-ui/src/app/screens/admin/users/user-listing-screen.component.ts rename to apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 01530fb08..47543d156 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -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": { diff --git a/apps/red-ui/src/assets/styles/red-select.scss b/apps/red-ui/src/assets/styles/red-select.scss index 64853a80a..b43b61ba5 100644 --- a/apps/red-ui/src/assets/styles/red-select.scss +++ b/apps/red-ui/src/assets/styles/red-select.scss @@ -13,3 +13,8 @@ color: $grey-1; } } + +.mat-form-field.no-label .mat-form-field-infix { + padding: 0 !important; + border-top: 0 !important; +}