Spaces:
Paused
Paused
| 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; | |
| } | |
| ({ | |
| 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 { | |
| () 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; | |
| } | |
| } |