flare / flare-ui /src /app /components /activity-log /activity-log.component.ts
ciyidogan's picture
Update flare-ui/src/app/components/activity-log/activity-log.component.ts
ce589a9 verified
raw
history blame
9.79 kB
import { Component, EventEmitter, Output, inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
import { MatListModule } from '@angular/material/list';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
interface ActivityLog {
id: number;
timestamp: string;
user: string;
action: string;
entity_type: string;
entity_id: any;
entity_name: string;
details?: string;
}
interface ActivityLogResponse {
items: ActivityLog[];
total: number;
page: number;
limit: number;
pages: number;
}
@Component({
selector: 'app-activity-log',
standalone: true,
imports: [
CommonModule,
MatProgressSpinnerModule,
MatButtonModule,
MatIconModule,
MatPaginatorModule,
MatListModule,
MatCardModule,
MatDividerModule
],
template: `
<mat-card class="activity-log-dropdown" (click)="$event.stopPropagation()">
<mat-card-header>
<mat-card-title>
<mat-icon>notifications</mat-icon>
Recent Activities
</mat-card-title>
<button mat-icon-button (click)="close.emit()">
<mat-icon>close</mat-icon>
</button>
</mat-card-header>
<mat-card-content>
@if (loading && activities.length === 0) {
<div class="loading">
<mat-spinner diameter="30"></mat-spinner>
</div>
} @else if (activities.length === 0) {
<div class="empty">
<mat-icon>inbox</mat-icon>
<p>No activities found</p>
</div>
} @else {
<mat-list class="activity-list">
@for (activity of activities; track activity.id) {
<mat-list-item>
<mat-icon matListItemIcon>{{ getActivityIcon(activity.action) }}</mat-icon>
<div matListItemTitle>
<span class="activity-time">{{ getRelativeTime(activity.timestamp) }}</span>
</div>
<div matListItemLine>
<strong>{{ activity.user }}</strong> {{ getActionText(activity) }}
<em>{{ activity.entity_name }}</em>
@if (activity.details) {
<span class="details">• {{ activity.details }}</span>
}
</div>
</mat-list-item>
@if (!$last) {
<mat-divider></mat-divider>
}
}
</mat-list>
}
</mat-card-content>
<mat-card-actions *ngIf="totalItems > pageSize">
<mat-paginator
[length]="totalItems"
[pageSize]="pageSize"
[pageIndex]="currentPage - 1"
[pageSizeOptions]="[10, 25, 50]"
(page)="onPageChange($event)"
showFirstLastButtons>
</mat-paginator>
</mat-card-actions>
<mat-card-actions *ngIf="totalItems <= pageSize && activities.length > 0">
<button mat-button (click)="openFullView()" class="full-width">
<mat-icon>open_in_new</mat-icon>
View All Activities
</button>
</mat-card-actions>
</mat-card>
`,
styles: [`
.activity-log-dropdown {
width: 450px;
max-height: 600px;
display: flex;
flex-direction: column;
overflow: hidden;
}
mat-card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background-color: #424242; // Koyu gri (önceden #f5f5f5 idi)
color: white; // Yazıları beyaz yap
mat-card-title {
margin: 0;
display: flex;
align-items: center;
gap: 8px;
font-size: 18px;
color: white; // Title'ı da beyaz yap
mat-icon {
font-size: 24px;
width: 24px;
height: 24px;
color: white; // Icon'u da beyaz yap
}
}
button {
color: white; // Close button'u beyaz yap
}
}
mat-card-content {
flex: 1;
overflow-y: auto;
padding: 0;
min-height: 200px;
max-height: 400px;
}
.activity-list {
padding: 0;
mat-list-item {
height: auto;
min-height: 72px;
padding: 12px 16px;
&:hover {
background-color: #f5f5f5;
}
.activity-time {
font-size: 12px;
color: #666;
}
strong {
color: #1976d2;
margin-right: 4px;
}
em {
color: #673ab7;
font-style: normal;
font-weight: 500;
margin: 0 4px;
}
.details {
color: #666;
font-size: 12px;
margin-left: 4px;
}
}
}
mat-card-actions {
padding: 0;
margin: 0;
mat-paginator {
background: transparent;
}
.full-width {
width: 100%;
margin: 0;
}
}
.loading, .empty {
padding: 60px 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #666;
mat-icon {
font-size: 48px;
width: 48px;
height: 48px;
color: #e0e0e0;
margin-bottom: 16px;
}
p {
margin: 0;
font-size: 14px;
}
}
::ng-deep {
.mat-mdc-list-item-unscoped-content {
display: block;
}
.mat-mdc-paginator {
.mat-mdc-paginator-container {
padding: 8px;
justify-content: center;
}
}
}
`]
})
export class ActivityLogComponent implements OnInit {
@Output() close = new EventEmitter<void>();
private http = inject(HttpClient);
activities: ActivityLog[] = [];
loading = false;
currentPage = 1;
pageSize = 10;
totalItems = 0;
totalPages = 0;
ngOnInit() {
this.loadActivities();
}
loadActivities(page: number = 1) {
this.loading = true;
this.currentPage = page;
// Backend sadece limit parametresi alıyor, page almıyor
const limit = this.pageSize * page; // Toplam kaç kayıt istediğimizi hesapla
this.http.get<ActivityLog[]>(
`/api/activity-log?limit=${limit}`
).subscribe({
next: (response) => {
// Response direkt array olarak geliyor
const allActivities = response || [];
// Manuel pagination yap
const startIndex = (page - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
this.activities = allActivities.slice(startIndex, endIndex);
this.totalItems = allActivities.length;
this.totalPages = Math.ceil(allActivities.length / this.pageSize);
this.loading = false;
},
error: (error) => {
console.error('Failed to load activities:', error);
this.activities = []; // Hata durumunda boş array
this.loading = false;
}
});
}
onPageChange(event: PageEvent) {
this.pageSize = event.pageSize;
this.loadActivities(event.pageIndex + 1);
}
openFullView() {
// TODO: Implement full activity log view
console.log('Open full activity log view');
this.close.emit();
}
getRelativeTime(timestamp: string): string {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'just now';
if (diffMins < 60) return `${diffMins} min ago`;
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
return date.toLocaleDateString();
}
getActionText(activity: ActivityLog): string {
const actions: Record<string, string> = {
'CREATE_PROJECT': 'created project',
'UPDATE_PROJECT': 'updated project',
'DELETE_PROJECT': 'deleted project',
'ENABLE_PROJECT': 'enabled project',
'DISABLE_PROJECT': 'disabled project',
'PUBLISH_VERSION': 'published version of',
'CREATE_VERSION': 'created version for',
'UPDATE_VERSION': 'updated version of',
'DELETE_VERSION': 'deleted version from',
'CREATE_API': 'created API',
'UPDATE_API': 'updated API',
'DELETE_API': 'deleted API',
'UPDATE_ENVIRONMENT': 'updated environment',
'IMPORT_PROJECT': 'imported project',
'CHANGE_PASSWORD': 'changed password'
};
return actions[activity.action] || activity.action.toLowerCase().replace(/_/g, ' ');
}
getActivityIcon(action: string): string {
if (action.includes('CREATE')) return 'add_circle';
if (action.includes('UPDATE')) return 'edit';
if (action.includes('DELETE')) return 'delete';
if (action.includes('ENABLE')) return 'check_circle';
if (action.includes('DISABLE')) return 'cancel';
if (action.includes('PUBLISH')) return 'publish';
if (action.includes('IMPORT')) return 'cloud_upload';
if (action.includes('PASSWORD')) return 'lock';
return 'info';
}
trackByActivityId(index: number, activity: ActivityLog): number {
return activity.id;
}
isLast(activity: ActivityLog): boolean {
return this.activities.indexOf(activity) === this.activities.length - 1;
}
}