Skip to content
Open

Audit #1243

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 72 additions & 10 deletions frontend/src/app/components/audit/audit-data-source.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
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'
import { UserService } from 'src/app/services/user.service';

interface Column {
title: string,
Expand All @@ -28,7 +29,7 @@ export class AuditDataSource implements DataSource<Object> {
public loading$ = this.loadingSubject.asObservable();
public paginator: MatPaginator;

constructor(private _connections: ConnectionsService) {}
constructor(private _connections: ConnectionsService, private _userService: UserService) {}

connect(collectionViewer: CollectionViewer): Observable<Object[]> {
return this.rowsSubject.asObservable();
Expand All @@ -39,12 +40,40 @@ export class AuditDataSource implements DataSource<Object> {
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,
Expand All @@ -60,24 +89,57 @@ export class AuditDataSource implements DataSource<Object> {
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 => {
if (log.operationType === 'actionActivated') {
console.log('DEBUG: actionActivated log', log);
}
const date = new Date(log.createdAt);
const formattedDate = format(date, "P p")
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;
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],
['User']: name || email,
name: name,
email: email,
['Action']: actionName,
['Date']: formattedDate,
['Status']: log.operationStatusResult,
operationType: log.operationType,
actionIcon: actionIcon,
createdAt: log.createdAt,
prevValue: log.old_data,
currentValue: log.received_data,
Expand Down
93 changes: 90 additions & 3 deletions frontend/src/app/components/audit/audit.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,32 @@ header {

th.mat-header-cell, td.mat-cell {
padding-right: 20px;
height: 44px;
}

.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: #e8f5e9;
color: #1B5E20;
}

.status-badge.error {
background: #ffebee;
color: #B71C1C;
}

.table-wrapper {
Expand Down Expand Up @@ -110,4 +131,70 @@ 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: #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;
}
}
41 changes: 37 additions & 4 deletions frontend/src/app/components/audit/audit.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,53 @@ <h3 class='mat-subheading-2'>Rocketadmin can not find any tables</h3>
<ng-container [matColumnDef]="column" *ngFor="let column of dataColumns">
<th mat-header-cell *matHeaderCellDef> {{ column }} </th>
<td mat-cell *matCellDef="let element">
<div class="table-cell-content" >
{{element[column] || '—'}}
<div class="table-cell-content" [ngClass]="{'date-cell': column === 'Date'}" >
<ng-container *ngIf="column === 'Status'; else actionOrRegularCell">
<span class="status-badge" [ngClass]="{
'success': element[column] === 'successfully',
'error': element[column] === 'unsuccessfully'
}">
{{element[column] || '—'}}
</span>
</ng-container>
<ng-template #actionOrRegularCell>
<ng-container *ngIf="column === 'Action'; else userOrRegularCell">
<div class="action-cell">
<mat-icon class="action-icon">
{{getActionIcon(element.operationType, element.actionIcon)}}
</mat-icon>
<span>{{element[column] || '—'}}</span>
</div>
</ng-container>
</ng-template>
<ng-template #userOrRegularCell>
<ng-container *ngIf="column === 'User'; else regularCell">
<span style="display: flex; flex-direction: column; line-height: 1.2;">
<span>{{element.name}}</span>
<span style="color: #888; font-size: 12px;">{{element.email || element[column] || '—'}}</span>
</span>
</ng-container>
</ng-template>
<ng-template #regularCell>
<ng-container *ngIf="column === 'Date'; else regularCellDefault">
<span class="date-cell">{{(element[column] || '').split(',')[0]}} at {{(element[column] || '').split(',')[1]?.trim()}}</span>
</ng-container>
</ng-template>
<ng-template #regularCellDefault>
{{element[column] || '—'}}
</ng-template>
</div>
</td>
</ng-container>

<ng-container matColumnDef="Details">
<th mat-header-cell *matHeaderCellDef> Details </th>
<th mat-header-cell *matHeaderCellDef> Changes </th>
<td mat-cell *matCellDef="let element">
<button mat-button color="primary"
angulartics2On="click"
angularticsAction="Audit: view changes is clicked"
(click)="openInfoLogDialog(element)">
View changes
Details
</button>
</td>
</ng-container>
Expand Down
36 changes: 33 additions & 3 deletions frontend/src/app/components/audit/audit.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -28,6 +29,7 @@ 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 { UserService } from 'src/app/services/user.service';

@Component({
selector: 'app-audit',
Expand All @@ -42,6 +44,7 @@ import { normalizeTableName } from 'src/app/lib/normalize';
MatButtonModule,
MatTableModule,
MatPaginatorModule,
MatIconModule,
FormsModule,
RouterModule,
Angulartics2OnModule,
Expand Down Expand Up @@ -76,7 +79,8 @@ export class AuditComponent implements OnInit {
private _users: UsersService,
private _companyService: CompanyService,
public dialog: MatDialog,
private title: Title
private title: Title,
private _userService: UserService
) { }

ngAfterViewInit() {
Expand All @@ -99,7 +103,7 @@ export class AuditComponent implements OnInit {
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)
Expand Down Expand Up @@ -128,7 +132,8 @@ export class AuditComponent implements OnInit {
this.dataSource.fetchLogs({
connectionID: this.connectionID,
tableName: this.tableName,
userEmail: this.userEmail
userEmail: this.userEmail,
usersList: this.usersList
});
}

Expand All @@ -143,4 +148,29 @@ export class AuditComponent implements OnInit {
// @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 'visibility_outline';
case 'rowsReceived':
return 'visibility_outline';
case 'ruleAction':
return 'rule';
case 'actionActivated':
return 'rule';
default:
return 'info';
}
}
}