From 15f343c198abf33afb87b5463f9d1fcf5b889051 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Tue, 8 Apr 2025 17:00:15 +0300 Subject: [PATCH 1/3] update Autid view --- .../app/components/audit/audit-data-source.ts | 26 ++++++++++--- .../app/components/audit/audit.component.css | 39 +++++++++++++++++-- .../app/components/audit/audit.component.html | 28 +++++++++++-- .../app/components/audit/audit.component.ts | 27 +++++++++++++ 4 files changed, 108 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/components/audit/audit-data-source.ts b/frontend/src/app/components/audit/audit-data-source.ts index 0ef494a76..c3b5a44dd 100644 --- a/frontend/src/app/components/audit/audit-data-source.ts +++ b/frontend/src/app/components/audit/audit-data-source.ts @@ -1,11 +1,11 @@ import { BehaviorSubject, Observable, of } from 'rxjs'; import { catchError, finalize } from 'rxjs/operators'; +import { format, isToday } from 'date-fns'; import { CollectionViewer } from '@angular/cdk/collections'; import { ConnectionsService } from 'src/app/services/connections.service'; import { DataSource } from '@angular/cdk/table'; import { MatPaginator } from '@angular/material/paginator'; -import { format } from 'date-fns' interface Column { title: string, @@ -60,24 +60,40 @@ export class AuditDataSource implements DataSource { finalize(() => this.loadingSubject.next(false)) ) .subscribe((res: any) => { - const actions = { addRow: 'added row', deleteRow: 'deleted row', updateRow: 'edit row', rowReceived: 'received row', - rowsReceived: 'received rows' + rowsReceived: 'received rows', + ruleAction: (actionName: string) => actionName } const formattedLogs = res.logs.map(log => { const date = new Date(log.createdAt); - const formattedDate = format(date, "P p") + const formattedDate = format(date, "d MMM yyyy, p"); + + // Handle action name + let actionName = log.operationType; + let actionIcon = null; + + if (log.operationType === 'actionActivated' && log.actionName) { + actionName = log.actionName; + actionIcon = log.icon; + } else if (log.operationType === 'ruleAction' && log.actionName) { + actionName = actions.ruleAction(log.actionName); + actionIcon = log.icon || 'rule'; + } else { + actionName = actions[log.operationType] || log.operationType; + } + return { ['Table']: log.table_name, ['User']: log.email, - ['Action']: actions[log.operationType], + ['Action']: actionName, ['Date']: formattedDate, ['Status']: log.operationStatusResult, operationType: log.operationType, + actionIcon: actionIcon, createdAt: log.createdAt, prevValue: log.old_data, currentValue: log.received_data, diff --git a/frontend/src/app/components/audit/audit.component.css b/frontend/src/app/components/audit/audit.component.css index 303a0b436..a545034f4 100644 --- a/frontend/src/app/components/audit/audit.component.css +++ b/frontend/src/app/components/audit/audit.component.css @@ -50,9 +50,29 @@ th.mat-header-cell, td.mat-cell { padding-right: 20px; } -.table-cell-content_break-words { - padding: 16px 0; - word-break: break-all; +.table-cell-content { + padding: 4px 0; +} + +.date-cell { + color: rgba(0, 0, 0, 0.6); +} + +.status-badge { + display: inline-block; + padding: 4px 8px; + border-radius: 4px; + font-size: 14px; +} + +.status-badge.success { + background-color: #e6f4ea; + color: #1e4620; +} + +.status-badge.error { + background-color: #fdeded; + color: #5f2120; } .table-wrapper { @@ -98,4 +118,17 @@ th.mat-header-cell, td.mat-cell { .hidden { display: none; +} + +.action-cell { + display: flex; + align-items: center; + gap: 8px; +} + +.action-icon { + font-size: 18px; + width: 18px; + height: 18px; + color: rgba(0, 0, 0, 0.54); } \ No newline at end of file diff --git a/frontend/src/app/components/audit/audit.component.html b/frontend/src/app/components/audit/audit.component.html index 37b4601e2..e987fc018 100644 --- a/frontend/src/app/components/audit/audit.component.html +++ b/frontend/src/app/components/audit/audit.component.html @@ -72,20 +72,40 @@

Rocketadmin can not find any tables

{{ column }} -
- {{element[column] || '—'}} +
+ + + {{element[column] || '—'}} + + + + +
+ + {{getActionIcon(element.operationType, element.actionIcon)}} + + {{element[column] || '—'}} +
+
+
+ + {{element[column] || '—'}} +
- Details + Changes diff --git a/frontend/src/app/components/audit/audit.component.ts b/frontend/src/app/components/audit/audit.component.ts index 8a1303028..b108e53d2 100644 --- a/frontend/src/app/components/audit/audit.component.ts +++ b/frontend/src/app/components/audit/audit.component.ts @@ -27,6 +27,7 @@ import { normalizeTableName } from 'src/app/lib/normalize'; import { tap } from 'rxjs/operators'; import { BannerComponent } from '../ui-components/banner/banner.component'; import { PlaceholderTableDataComponent } from '../skeletons/placeholder-table-data/placeholder-table-data.component'; +import { MatIconModule } from '@angular/material/icon'; @Component({ selector: 'app-audit', @@ -41,6 +42,7 @@ import { PlaceholderTableDataComponent } from '../skeletons/placeholder-table-da MatButtonModule, MatTableModule, MatPaginatorModule, + MatIconModule, FormsModule, RouterModule, Angulartics2OnModule, @@ -143,4 +145,29 @@ export class AuditComponent implements OnInit, OnDestroy { // @ts-ignore Intercom('show'); } + + getActionIcon(operationType: string, actionIcon?: string): string { + if ((operationType === 'ruleAction' || operationType === 'actionActivated') && actionIcon) { + return actionIcon; + } + + switch (operationType) { + case 'addRow': + return 'add'; + case 'deleteRow': + return 'delete'; + case 'updateRow': + return 'edit'; + case 'rowReceived': + return 'download'; + case 'rowsReceived': + return 'download'; + case 'ruleAction': + return 'rule'; + case 'actionActivated': + return 'rule'; + default: + return 'info'; + } + } } From c322f6c4c1cc3f1fec0e4c9b8d45258387d35152 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Thu, 3 Jul 2025 14:01:06 +0300 Subject: [PATCH 2/3] Enhance Audit component functionality by integrating UserService for user data retrieval and improving log fetching logic. Update styles for better UI consistency and add user name display in the audit logs. --- .../app/components/audit/audit-data-source.ts | 56 ++++++++++++++-- .../app/components/audit/audit.component.css | 64 +++++++++++++++++-- .../app/components/audit/audit.component.html | 15 ++++- .../app/components/audit/audit.component.ts | 13 ++-- 4 files changed, 132 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/components/audit/audit-data-source.ts b/frontend/src/app/components/audit/audit-data-source.ts index c3b5a44dd..63a5cd5b9 100644 --- a/frontend/src/app/components/audit/audit-data-source.ts +++ b/frontend/src/app/components/audit/audit-data-source.ts @@ -6,6 +6,7 @@ import { CollectionViewer } from '@angular/cdk/collections'; import { ConnectionsService } from 'src/app/services/connections.service'; import { DataSource } from '@angular/cdk/table'; import { MatPaginator } from '@angular/material/paginator'; +import { UserService } from 'src/app/services/user.service'; interface Column { title: string, @@ -28,7 +29,7 @@ export class AuditDataSource implements DataSource { public loading$ = this.loadingSubject.asObservable(); public paginator: MatPaginator; - constructor(private _connections: ConnectionsService) {} + constructor(private _connections: ConnectionsService, private _userService: UserService) {} connect(collectionViewer: CollectionViewer): Observable { return this.rowsSubject.asObservable(); @@ -39,12 +40,40 @@ export class AuditDataSource implements DataSource { this.loadingSubject.complete(); } - fetchLogs({ + async fetchLogs({ connectionID, tableName, - userEmail - }: RowsParams) { + userEmail, + usersList + }: RowsParams & { usersList?: any[] }) { this.loadingSubject.next(true); + let effectiveUsersList = usersList; + if (!effectiveUsersList || !Array.isArray(effectiveUsersList) || effectiveUsersList.length === 0) { + try { + const stored = localStorage.getItem('usersList'); + if (stored) { + effectiveUsersList = JSON.parse(stored); + } + } catch (e) {} + if ((!effectiveUsersList || !Array.isArray(effectiveUsersList) || effectiveUsersList.length === 0) && (window as any).usersList) { + effectiveUsersList = (window as any).usersList; + } + } + // Always refresh user info before using + await this._userService.fetchUser().toPromise(); + let currentUserName = ''; + let currentUserEmail = ''; + const userValue = this._userService['user']?.getValue?.(); + console.log('DEBUG: current user from UserService', userValue); + if (userValue) { + currentUserName = userValue.name || ''; + currentUserEmail = userValue.email || ''; + } + // DEBUG LOGGING + console.log('DEBUG: effectiveUsersList', effectiveUsersList); + if (effectiveUsersList && Array.isArray(effectiveUsersList)) { + effectiveUsersList.forEach(u => console.log('DEBUG: user in usersList', u)); + } const fetchedLogs = this._connections.fetchAuditLog({ connectionID, tableName, @@ -69,8 +98,23 @@ export class AuditDataSource implements DataSource { ruleAction: (actionName: string) => actionName } const formattedLogs = res.logs.map(log => { + if (log.operationType === 'actionActivated') { + console.log('DEBUG: actionActivated log', log); + } const date = new Date(log.createdAt); const formattedDate = format(date, "d MMM yyyy, p"); + let name = ''; + let email = log.email || ''; + // Use current user name if email matches + if (currentUserEmail && email === currentUserEmail) { + name = currentUserName; + } else if (effectiveUsersList && Array.isArray(effectiveUsersList)) { + const userProfile = effectiveUsersList.find(u => u.email === email); + if (userProfile && userProfile.name) { + name = userProfile.name; + } + } + if (!name && log.name) name = log.name; // Handle action name let actionName = log.operationType; @@ -88,7 +132,9 @@ export class AuditDataSource implements DataSource { return { ['Table']: log.table_name, - ['User']: log.email, + ['User']: name || email, + name: name, + email: email, ['Action']: actionName, ['Date']: formattedDate, ['Status']: log.operationStatusResult, diff --git a/frontend/src/app/components/audit/audit.component.css b/frontend/src/app/components/audit/audit.component.css index a545034f4..9d602dad5 100644 --- a/frontend/src/app/components/audit/audit.component.css +++ b/frontend/src/app/components/audit/audit.component.css @@ -48,6 +48,7 @@ header { th.mat-header-cell, td.mat-cell { padding-right: 20px; + height: 44px; } .table-cell-content { @@ -66,13 +67,13 @@ th.mat-header-cell, td.mat-cell { } .status-badge.success { - background-color: #e6f4ea; - color: #1e4620; + background: #e8f5e9; + color: #1B5E20; } .status-badge.error { - background-color: #fdeded; - color: #5f2120; + background: #ffebee; + color: #B71C1C; } .table-wrapper { @@ -130,5 +131,58 @@ th.mat-header-cell, td.mat-cell { font-size: 18px; width: 18px; height: 18px; - color: rgba(0, 0, 0, 0.54); + color: #888; +} + +tr.mat-mdc-row, tr.mat-mdc-header-row { + height: 44px !important; + max-height: 44px !important; +} + +td.mat-mdc-cell, th.mat-mdc-header-cell { + height: 44px !important; + max-height: 44px !important; + padding-top: 0 !important; + padding-bottom: 0 !important; + vertical-align: middle !important; + line-height: normal !important; +} + +tr.mat-mdc-row:hover { + background: #f5f5f5 !important; + cursor: default !important; +} + +th.mat-header-cell { + background: #fff; +} + +@media (prefers-color-scheme: dark) { + th.mat-header-cell { + background: #202020 !important; + box-shadow: none !important; + border-bottom: none !important; + filter: none !important; + } + .mat-table, .table-wrapper, tr.mat-mdc-row, td.mat-mdc-cell { + background: #202020 !important; + } + tr.mat-mdc-row:hover { + background: var(--color-primaryPalette-800) !important; + cursor: default !important; + } + td.mat-mdc-cell.date-cell, .date-cell { + color: #888 !important; + } + .action-icon { + color: #888 !important; + } + .status-badge.success { + background: rgba(76, 175, 80, 0.15); + color: #4CAF50; + } + .status-badge.error { + background: rgba(229, 57, 53, 0.15); + color: #E53935; + } } \ No newline at end of file diff --git a/frontend/src/app/components/audit/audit.component.html b/frontend/src/app/components/audit/audit.component.html index e987fc018..b21d16303 100644 --- a/frontend/src/app/components/audit/audit.component.html +++ b/frontend/src/app/components/audit/audit.component.html @@ -82,7 +82,7 @@

Rocketadmin can not find any tables

- +
{{getActionIcon(element.operationType, element.actionIcon)}} @@ -91,7 +91,20 @@

Rocketadmin can not find any tables

+ + + + {{element.name}} + {{element.email || element[column] || '—'}} + + + + + {{(element[column] || '').split(',')[0]}} at {{(element[column] || '').split(',')[1]?.trim()}} + + + {{element[column] || '—'}} diff --git a/frontend/src/app/components/audit/audit.component.ts b/frontend/src/app/components/audit/audit.component.ts index b108e53d2..565cee2b9 100644 --- a/frontend/src/app/components/audit/audit.component.ts +++ b/frontend/src/app/components/audit/audit.component.ts @@ -28,6 +28,7 @@ import { tap } from 'rxjs/operators'; import { BannerComponent } from '../ui-components/banner/banner.component'; import { PlaceholderTableDataComponent } from '../skeletons/placeholder-table-data/placeholder-table-data.component'; import { MatIconModule } from '@angular/material/icon'; +import { UserService } from 'src/app/services/user.service'; @Component({ selector: 'app-audit', @@ -76,7 +77,8 @@ export class AuditComponent implements OnInit, OnDestroy { private _tables: TablesService, private _users: UsersService, public dialog: MatDialog, - private title: Title + private title: Title, + private _userService: UserService ) { } ngAfterViewInit() { @@ -97,7 +99,7 @@ export class AuditComponent implements OnInit, OnDestroy { this.accesLevel = this._connections.currentConnectionAccessLevel; this.columns = ['Table', 'User', 'Action', 'Date', 'Status', 'Details']; this.dataColumns = ['Table', 'User', 'Action', 'Date', 'Status']; - this.dataSource = new AuditDataSource(this._connections); + this.dataSource = new AuditDataSource(this._connections, this._userService); this.loadLogsPage(); this._tables.fetchTables(this.connectionID) @@ -130,7 +132,8 @@ export class AuditComponent implements OnInit, OnDestroy { this.dataSource.fetchLogs({ connectionID: this.connectionID, tableName: this.tableName, - userEmail: this.userEmail + userEmail: this.userEmail, + usersList: this.usersList }); } @@ -159,9 +162,9 @@ export class AuditComponent implements OnInit, OnDestroy { case 'updateRow': return 'edit'; case 'rowReceived': - return 'download'; + return 'visibility_outline'; case 'rowsReceived': - return 'download'; + return 'visibility_outline'; case 'ruleAction': return 'rule'; case 'actionActivated': From d0b8f09cde0eec2352be33915d48f5f46613c699 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Thu, 3 Jul 2025 14:17:57 +0300 Subject: [PATCH 3/3] fix: remove duplicate import of PlaceholderTableDataComponent and clean up unused imports in Audit component --- frontend/src/app/components/audit/audit.component.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/src/app/components/audit/audit.component.ts b/frontend/src/app/components/audit/audit.component.ts index 9a1a5e113..983c140f0 100644 --- a/frontend/src/app/components/audit/audit.component.ts +++ b/frontend/src/app/components/audit/audit.component.ts @@ -18,6 +18,7 @@ import { MatPaginator } from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatSelectModule } from '@angular/material/select'; import { MatTableModule } from '@angular/material/table'; +import { MatIconModule } from '@angular/material/icon'; import { PlaceholderTableDataComponent } from '../skeletons/placeholder-table-data/placeholder-table-data.component'; import { RouterModule } from '@angular/router'; import { ServerError } from 'src/app/models/alert'; @@ -28,10 +29,6 @@ import { User } from '@sentry/angular-ivy'; import { UsersService } from 'src/app/services/users.service'; import { environment } from 'src/environments/environment'; import { normalizeTableName } from 'src/app/lib/normalize'; -import { tap } from 'rxjs/operators'; -import { BannerComponent } from '../ui-components/banner/banner.component'; -import { PlaceholderTableDataComponent } from '../skeletons/placeholder-table-data/placeholder-table-data.component'; -import { MatIconModule } from '@angular/material/icon'; import { UserService } from 'src/app/services/user.service'; @Component({