ciyidogan commited on
Commit
c519511
·
verified ·
1 Parent(s): 9ea1f78

Update flare-ui/src/app/components/activity-log/activity-log.component.ts

Browse files
flare-ui/src/app/components/activity-log/activity-log.component.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Component, EventEmitter, Output, inject, OnInit } from '@angular/core';
2
  import { CommonModule } from '@angular/common';
3
  import { HttpClient } from '@angular/common/http';
4
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@@ -8,6 +8,8 @@ import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
8
  import { MatListModule } from '@angular/material/list';
9
  import { MatCardModule } from '@angular/material/card';
10
  import { MatDividerModule } from '@angular/material/divider';
 
 
11
 
12
  interface ActivityLog {
13
  id: number;
@@ -39,7 +41,8 @@ interface ActivityLogResponse {
39
  MatPaginatorModule,
40
  MatListModule,
41
  MatCardModule,
42
- MatDividerModule
 
43
  ],
44
  template: `
45
  <mat-card class="activity-log-dropdown" (click)="$event.stopPropagation()">
@@ -48,7 +51,7 @@ interface ActivityLogResponse {
48
  <mat-icon>notifications</mat-icon>
49
  Recent Activities
50
  </mat-card-title>
51
- <button mat-icon-button (click)="close.emit()">
52
  <mat-icon>close</mat-icon>
53
  </button>
54
  </mat-card-header>
@@ -58,6 +61,15 @@ interface ActivityLogResponse {
58
  <div class="loading">
59
  <mat-spinner diameter="30"></mat-spinner>
60
  </div>
 
 
 
 
 
 
 
 
 
61
  } @else if (activities.length === 0) {
62
  <div class="empty">
63
  <mat-icon>inbox</mat-icon>
@@ -120,8 +132,8 @@ interface ActivityLogResponse {
120
  justify-content: space-between;
121
  align-items: center;
122
  padding: 16px;
123
- background-color: #424242; // Koyu gri (önceden #f5f5f5 idi)
124
- color: white; // Yazıları beyaz yap
125
 
126
  mat-card-title {
127
  margin: 0;
@@ -129,18 +141,18 @@ interface ActivityLogResponse {
129
  align-items: center;
130
  gap: 8px;
131
  font-size: 18px;
132
- color: white; // Title'ı da beyaz yap
133
 
134
  mat-icon {
135
  font-size: 24px;
136
  width: 24px;
137
  height: 24px;
138
- color: white; // Icon'u da beyaz yap
139
  }
140
  }
141
 
142
  button {
143
- color: white; // Close button'u beyaz yap
144
  }
145
  }
146
 
@@ -203,7 +215,7 @@ interface ActivityLogResponse {
203
  }
204
  }
205
 
206
- .loading, .empty {
207
  padding: 60px 20px;
208
  display: flex;
209
  flex-direction: column;
@@ -220,11 +232,17 @@ interface ActivityLogResponse {
220
  }
221
 
222
  p {
223
- margin: 0;
224
  font-size: 14px;
225
  }
226
  }
227
 
 
 
 
 
 
 
228
  ::ng-deep {
229
  .mat-mdc-list-item-unscoped-content {
230
  display: block;
@@ -239,13 +257,16 @@ interface ActivityLogResponse {
239
  }
240
  `]
241
  })
242
- export class ActivityLogComponent implements OnInit {
243
  @Output() close = new EventEmitter<void>();
244
 
245
  private http = inject(HttpClient);
 
 
246
 
247
  activities: ActivityLog[] = [];
248
  loading = false;
 
249
  currentPage = 1;
250
  pageSize = 10;
251
  totalItems = 0;
@@ -255,8 +276,14 @@ export class ActivityLogComponent implements OnInit {
255
  this.loadActivities();
256
  }
257
 
 
 
 
 
 
258
  loadActivities(page: number = 1) {
259
  this.loading = true;
 
260
  this.currentPage = page;
261
 
262
  // Backend sadece limit parametresi alıyor, page almıyor
@@ -264,24 +291,40 @@ export class ActivityLogComponent implements OnInit {
264
 
265
  this.http.get<ActivityLog[]>(
266
  `/api/activity-log?limit=${limit}`
 
 
267
  ).subscribe({
268
  next: (response) => {
269
- // Response direkt array olarak geliyor
270
- const allActivities = response || [];
271
-
272
- // Manuel pagination yap
273
- const startIndex = (page - 1) * this.pageSize;
274
- const endIndex = startIndex + this.pageSize;
275
-
276
- this.activities = allActivities.slice(startIndex, endIndex);
277
- this.totalItems = allActivities.length;
278
- this.totalPages = Math.ceil(allActivities.length / this.pageSize);
279
- this.loading = false;
 
 
 
 
 
 
 
280
  },
281
  error: (error) => {
282
  console.error('Failed to load activities:', error);
283
- this.activities = []; // Hata durumunda boş array
 
284
  this.loading = false;
 
 
 
 
 
 
285
  }
286
  });
287
  }
@@ -297,20 +340,30 @@ export class ActivityLogComponent implements OnInit {
297
  this.close.emit();
298
  }
299
 
 
 
 
 
300
  getRelativeTime(timestamp: string): string {
301
- const date = new Date(timestamp);
302
- const now = new Date();
303
- const diffMs = now.getTime() - date.getTime();
304
- const diffMins = Math.floor(diffMs / 60000);
305
- const diffHours = Math.floor(diffMs / 3600000);
306
- const diffDays = Math.floor(diffMs / 86400000);
307
-
308
- if (diffMins < 1) return 'just now';
309
- if (diffMins < 60) return `${diffMins} min ago`;
310
- if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
311
- if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
312
-
313
- return date.toLocaleDateString();
 
 
 
 
 
 
314
  }
315
 
316
  getActionText(activity: ActivityLog): string {
@@ -329,7 +382,10 @@ export class ActivityLogComponent implements OnInit {
329
  'DELETE_API': 'deleted API',
330
  'UPDATE_ENVIRONMENT': 'updated environment',
331
  'IMPORT_PROJECT': 'imported project',
332
- 'CHANGE_PASSWORD': 'changed password'
 
 
 
333
  };
334
 
335
  return actions[activity.action] || activity.action.toLowerCase().replace(/_/g, ' ');
@@ -344,6 +400,8 @@ export class ActivityLogComponent implements OnInit {
344
  if (action.includes('PUBLISH')) return 'publish';
345
  if (action.includes('IMPORT')) return 'cloud_upload';
346
  if (action.includes('PASSWORD')) return 'lock';
 
 
347
  return 'info';
348
  }
349
 
@@ -354,4 +412,19 @@ export class ActivityLogComponent implements OnInit {
354
  isLast(activity: ActivityLog): boolean {
355
  return this.activities.indexOf(activity) === this.activities.length - 1;
356
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  }
 
1
+ import { Component, EventEmitter, Output, inject, OnInit, OnDestroy } from '@angular/core';
2
  import { CommonModule } from '@angular/common';
3
  import { HttpClient } from '@angular/common/http';
4
  import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
 
8
  import { MatListModule } from '@angular/material/list';
9
  import { MatCardModule } from '@angular/material/card';
10
  import { MatDividerModule } from '@angular/material/divider';
11
+ import { Subject, takeUntil } from 'rxjs';
12
+ import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
13
 
14
  interface ActivityLog {
15
  id: number;
 
41
  MatPaginatorModule,
42
  MatListModule,
43
  MatCardModule,
44
+ MatDividerModule,
45
+ MatSnackBarModule
46
  ],
47
  template: `
48
  <mat-card class="activity-log-dropdown" (click)="$event.stopPropagation()">
 
51
  <mat-icon>notifications</mat-icon>
52
  Recent Activities
53
  </mat-card-title>
54
+ <button mat-icon-button (click)="close.emit(); $event.stopPropagation()">
55
  <mat-icon>close</mat-icon>
56
  </button>
57
  </mat-card-header>
 
61
  <div class="loading">
62
  <mat-spinner diameter="30"></mat-spinner>
63
  </div>
64
+ } @else if (error && activities.length === 0) {
65
+ <div class="error-state">
66
+ <mat-icon>error_outline</mat-icon>
67
+ <p>{{ error }}</p>
68
+ <button mat-button (click)="retry()">
69
+ <mat-icon>refresh</mat-icon>
70
+ Retry
71
+ </button>
72
+ </div>
73
  } @else if (activities.length === 0) {
74
  <div class="empty">
75
  <mat-icon>inbox</mat-icon>
 
132
  justify-content: space-between;
133
  align-items: center;
134
  padding: 16px;
135
+ background-color: #424242;
136
+ color: white;
137
 
138
  mat-card-title {
139
  margin: 0;
 
141
  align-items: center;
142
  gap: 8px;
143
  font-size: 18px;
144
+ color: white;
145
 
146
  mat-icon {
147
  font-size: 24px;
148
  width: 24px;
149
  height: 24px;
150
+ color: white;
151
  }
152
  }
153
 
154
  button {
155
+ color: white;
156
  }
157
  }
158
 
 
215
  }
216
  }
217
 
218
+ .loading, .empty, .error-state {
219
  padding: 60px 20px;
220
  display: flex;
221
  flex-direction: column;
 
232
  }
233
 
234
  p {
235
+ margin: 0 0 16px;
236
  font-size: 14px;
237
  }
238
  }
239
 
240
+ .error-state {
241
+ mat-icon {
242
+ color: #f44336;
243
+ }
244
+ }
245
+
246
  ::ng-deep {
247
  .mat-mdc-list-item-unscoped-content {
248
  display: block;
 
257
  }
258
  `]
259
  })
260
+ export class ActivityLogComponent implements OnInit, OnDestroy {
261
  @Output() close = new EventEmitter<void>();
262
 
263
  private http = inject(HttpClient);
264
+ private snackBar = inject(MatSnackBar);
265
+ private destroyed$ = new Subject<void>();
266
 
267
  activities: ActivityLog[] = [];
268
  loading = false;
269
+ error = '';
270
  currentPage = 1;
271
  pageSize = 10;
272
  totalItems = 0;
 
276
  this.loadActivities();
277
  }
278
 
279
+ ngOnDestroy() {
280
+ this.destroyed$.next();
281
+ this.destroyed$.complete();
282
+ }
283
+
284
  loadActivities(page: number = 1) {
285
  this.loading = true;
286
+ this.error = '';
287
  this.currentPage = page;
288
 
289
  // Backend sadece limit parametresi alıyor, page almıyor
 
291
 
292
  this.http.get<ActivityLog[]>(
293
  `/api/activity-log?limit=${limit}`
294
+ ).pipe(
295
+ takeUntil(this.destroyed$)
296
  ).subscribe({
297
  next: (response) => {
298
+ try {
299
+ // Response direkt array olarak geliyor
300
+ const allActivities = response || [];
301
+
302
+ // Manual pagination yap
303
+ const startIndex = (page - 1) * this.pageSize;
304
+ const endIndex = startIndex + this.pageSize;
305
+
306
+ this.activities = allActivities.slice(startIndex, endIndex);
307
+ this.totalItems = allActivities.length;
308
+ this.totalPages = Math.ceil(allActivities.length / this.pageSize);
309
+ this.loading = false;
310
+ } catch (err) {
311
+ console.error('Failed to process activities:', err);
312
+ this.error = 'Failed to process activity data';
313
+ this.activities = [];
314
+ this.loading = false;
315
+ }
316
  },
317
  error: (error) => {
318
  console.error('Failed to load activities:', error);
319
+ this.error = this.getErrorMessage(error);
320
+ this.activities = [];
321
  this.loading = false;
322
+
323
+ // Show error in snackbar
324
+ this.snackBar.open(this.error, 'Close', {
325
+ duration: 5000,
326
+ panelClass: 'error-snackbar'
327
+ });
328
  }
329
  });
330
  }
 
340
  this.close.emit();
341
  }
342
 
343
+ retry() {
344
+ this.loadActivities(this.currentPage);
345
+ }
346
+
347
  getRelativeTime(timestamp: string): string {
348
+ try {
349
+ const date = new Date(timestamp);
350
+ const now = new Date();
351
+ const diffMs = now.getTime() - date.getTime();
352
+ const diffMins = Math.floor(diffMs / 60000);
353
+ const diffHours = Math.floor(diffMs / 3600000);
354
+ const diffDays = Math.floor(diffMs / 86400000);
355
+
356
+ if (diffMs < 0) return 'just now'; // Future dates
357
+ if (diffMins < 1) return 'just now';
358
+ if (diffMins < 60) return `${diffMins} min ago`;
359
+ if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
360
+ if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
361
+
362
+ return date.toLocaleDateString();
363
+ } catch (err) {
364
+ console.error('Invalid timestamp:', timestamp, err);
365
+ return 'Unknown';
366
+ }
367
  }
368
 
369
  getActionText(activity: ActivityLog): string {
 
382
  'DELETE_API': 'deleted API',
383
  'UPDATE_ENVIRONMENT': 'updated environment',
384
  'IMPORT_PROJECT': 'imported project',
385
+ 'CHANGE_PASSWORD': 'changed password',
386
+ 'LOGIN': 'logged in',
387
+ 'LOGOUT': 'logged out',
388
+ 'FAILED_LOGIN': 'failed login attempt'
389
  };
390
 
391
  return actions[activity.action] || activity.action.toLowerCase().replace(/_/g, ' ');
 
400
  if (action.includes('PUBLISH')) return 'publish';
401
  if (action.includes('IMPORT')) return 'cloud_upload';
402
  if (action.includes('PASSWORD')) return 'lock';
403
+ if (action.includes('LOGIN')) return 'login';
404
+ if (action.includes('LOGOUT')) return 'logout';
405
  return 'info';
406
  }
407
 
 
412
  isLast(activity: ActivityLog): boolean {
413
  return this.activities.indexOf(activity) === this.activities.length - 1;
414
  }
415
+
416
+ private getErrorMessage(error: any): string {
417
+ if (error.status === 0) {
418
+ return 'Unable to connect to server. Please check your connection.';
419
+ } else if (error.status === 401) {
420
+ return 'Session expired. Please login again.';
421
+ } else if (error.status === 403) {
422
+ return 'You do not have permission to view activity logs.';
423
+ } else if (error.error?.detail) {
424
+ return error.error.detail;
425
+ } else if (error.message) {
426
+ return error.message;
427
+ }
428
+ return 'Failed to load activities. Please try again.';
429
+ }
430
  }