ciyidogan commited on
Commit
66d5a73
·
verified ·
1 Parent(s): eb2b5d0

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,192 +1,307 @@
1
- import { Component, EventEmitter, Output, inject, OnInit } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
3
- import { HttpClient } from '@angular/common/http';
4
-
5
- interface ActivityLog {
6
- id: number;
7
- timestamp: string;
8
- user: string;
9
- action: string;
10
- entity_type: string;
11
- entity_id: any;
12
- entity_name: string;
13
- details?: string;
14
- }
15
-
16
- @Component({
17
- selector: 'app-activity-log',
18
- standalone: true,
19
- imports: [CommonModule],
20
- template: `
21
- <div class="activity-log-dropdown" (click)="$event.stopPropagation()">
22
- <div class="activity-header">
23
- <h3>🔔 Recent Activities</h3>
24
- <button class="close-btn" (click)="close.emit()">×</button>
25
- </div>
26
- <div class="activity-list">
27
- @if (loading) {
28
- <div class="loading">Loading...</div>
29
- } @else if (activities.length === 0) {
30
- <div class="empty">No recent activities</div>
31
- } @else {
32
- @for (activity of activities; track activity.id) {
33
- <div class="activity-item">
34
- <div class="activity-time">{{ getRelativeTime(activity.timestamp) }}</div>
35
- <div class="activity-content">
36
- <strong>{{ activity.user }}</strong> {{ getActionText(activity) }}
37
- <em>{{ activity.entity_name }}</em>
38
- </div>
39
- </div>
40
- }
41
- }
42
- </div>
43
- <div class="activity-footer">
44
- <button class="btn btn-secondary" (click)="loadMore()">View All Activities</button>
45
- </div>
46
- </div>
47
- `,
48
- styles: [`
49
- .activity-log-dropdown {
50
- position: absolute;
51
- top: 100%;
52
- right: 0;
53
- width: 350px;
54
- background: white;
55
- border: 1px solid #dee2e6;
56
- border-radius: 8px;
57
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
58
- z-index: 1000;
59
- margin-top: 0.5rem;
60
- }
61
-
62
- .activity-header {
63
- padding: 1rem;
64
- border-bottom: 1px solid #dee2e6;
65
- display: flex;
66
- justify-content: space-between;
67
- align-items: center;
68
-
69
- h3 {
70
- margin: 0;
71
- font-size: 1.1rem;
72
- }
73
-
74
- .close-btn {
75
- background: none;
76
- border: none;
77
- font-size: 1.5rem;
78
- cursor: pointer;
79
- color: #6c757d;
80
-
81
- &:hover {
82
- color: #333;
83
- }
84
- }
85
- }
86
-
87
- .activity-list {
88
- max-height: 300px;
89
- overflow-y: auto;
90
- }
91
-
92
- .activity-item {
93
- padding: 0.75rem 1rem;
94
- border-bottom: 1px solid #f0f0f0;
95
-
96
- &:hover {
97
- background-color: #f8f9fa;
98
- }
99
-
100
- .activity-time {
101
- font-size: 0.85rem;
102
- color: #6c757d;
103
- margin-bottom: 0.25rem;
104
- }
105
-
106
- .activity-content {
107
- font-size: 0.9rem;
108
-
109
- em {
110
- color: #007bff;
111
- font-style: normal;
112
- }
113
- }
114
- }
115
-
116
- .activity-footer {
117
- padding: 0.75rem;
118
- border-top: 1px solid #dee2e6;
119
- text-align: center;
120
- }
121
-
122
- .loading, .empty {
123
- padding: 2rem;
124
- text-align: center;
125
- color: #6c757d;
126
- }
127
- `]
128
- })
129
- export class ActivityLogComponent implements OnInit {
130
- @Output() close = new EventEmitter<void>();
131
-
132
- private http = inject(HttpClient);
133
-
134
- activities: ActivityLog[] = [];
135
- loading = true;
136
-
137
- ngOnInit() {
138
- this.loadActivities();
139
- }
140
-
141
- loadActivities() {
142
- this.loading = true;
143
- this.http.get<ActivityLog[]>('/api/activity-log?limit=10')
144
- .subscribe({
145
- next: (data) => {
146
- this.activities = data;
147
- this.loading = false;
148
- },
149
- error: () => {
150
- this.loading = false;
151
- }
152
- });
153
- }
154
-
155
- loadMore() {
156
- // TODO: Implement full activity log view
157
- console.log('Load more activities');
158
- }
159
-
160
- getRelativeTime(timestamp: string): string {
161
- const date = new Date(timestamp);
162
- const now = new Date();
163
- const diff = now.getTime() - date.getTime();
164
-
165
- const minutes = Math.floor(diff / 60000);
166
- const hours = Math.floor(diff / 3600000);
167
- const days = Math.floor(diff / 86400000);
168
-
169
- if (minutes < 1) return 'just now';
170
- if (minutes < 60) return `${minutes} min ago`;
171
- if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
172
- return `${days} day${days > 1 ? 's' : ''} ago`;
173
- }
174
-
175
- getActionText(activity: ActivityLog): string {
176
- const actions: Record<string, string> = {
177
- 'CREATE_PROJECT': 'created project',
178
- 'UPDATE_PROJECT': 'updated project',
179
- 'DELETE_PROJECT': 'deleted project',
180
- 'PUBLISH_VERSION': 'published version',
181
- 'CREATE_VERSION': 'created version',
182
- 'UPDATE_VERSION': 'updated version',
183
- 'CREATE_API': 'created API',
184
- 'UPDATE_API': 'updated API',
185
- 'DELETE_API': 'deleted API',
186
- 'UPDATE_ENVIRONMENT': 'updated environment',
187
- 'IMPORT_PROJECT': 'imported project'
188
- };
189
-
190
- return actions[activity.action] || activity.action.toLowerCase().replace('_', ' ');
191
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
 
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';
5
+ import { MatButtonModule } from '@angular/material/button';
6
+ import { MatIconModule } from '@angular/material/icon';
7
+ import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
8
+
9
+ interface ActivityLog {
10
+ id: number;
11
+ timestamp: string;
12
+ user: string;
13
+ action: string;
14
+ entity_type: string;
15
+ entity_id: any;
16
+ entity_name: string;
17
+ details?: string;
18
+ }
19
+
20
+ interface ActivityLogResponse {
21
+ items: ActivityLog[];
22
+ total: number;
23
+ page: number;
24
+ limit: number;
25
+ pages: number;
26
+ }
27
+
28
+ @Component({
29
+ selector: 'app-activity-log',
30
+ standalone: true,
31
+ imports: [
32
+ CommonModule,
33
+ MatProgressSpinnerModule,
34
+ MatButtonModule,
35
+ MatIconModule,
36
+ MatPaginatorModule
37
+ ],
38
+ template: `
39
+ <div class="activity-log-dropdown" (click)="$event.stopPropagation()">
40
+ <div class="activity-header">
41
+ <h3>🔔 Recent Activities</h3>
42
+ <button class="close-btn" (click)="close.emit()">×</button>
43
+ </div>
44
+
45
+ <div class="activity-content">
46
+ @if (loading && activities.length === 0) {
47
+ <div class="loading">
48
+ <mat-spinner diameter="30"></mat-spinner>
49
+ </div>
50
+ } @else if (activities.length === 0) {
51
+ <div class="empty">No activities found</div>
52
+ } @else {
53
+ <div class="activity-list">
54
+ @for (activity of activities; track activity.id) {
55
+ <div class="activity-item">
56
+ <div class="activity-time">{{ getRelativeTime(activity.timestamp) }}</div>
57
+ <div class="activity-content">
58
+ <strong>{{ activity.user }}</strong> {{ getActionText(activity) }}
59
+ <em>{{ activity.entity_name }}</em>
60
+ @if (activity.details) {
61
+ <span class="details">• {{ activity.details }}</span>
62
+ }
63
+ </div>
64
+ </div>
65
+ }
66
+ </div>
67
+ }
68
+ </div>
69
+
70
+ <div class="activity-footer">
71
+ @if (totalItems > pageSize) {
72
+ <mat-paginator
73
+ [length]="totalItems"
74
+ [pageSize]="pageSize"
75
+ [pageIndex]="currentPage - 1"
76
+ [pageSizeOptions]="[10, 25, 50]"
77
+ (page)="onPageChange($event)"
78
+ showFirstLastButtons>
79
+ </mat-paginator>
80
+ } @else {
81
+ <button mat-button (click)="openFullView()">
82
+ <mat-icon>open_in_new</mat-icon>
83
+ View All Activities
84
+ </button>
85
+ }
86
+ </div>
87
+ </div>
88
+ `,
89
+ styles: [`
90
+ .activity-log-dropdown {
91
+ position: absolute;
92
+ top: 100%;
93
+ right: 0;
94
+ width: 400px;
95
+ background: white;
96
+ border: 1px solid #dee2e6;
97
+ border-radius: 8px;
98
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
99
+ z-index: 1000;
100
+ margin-top: 0.5rem;
101
+ display: flex;
102
+ flex-direction: column;
103
+ max-height: 600px;
104
+ }
105
+
106
+ .activity-header {
107
+ padding: 1rem;
108
+ border-bottom: 1px solid #dee2e6;
109
+ display: flex;
110
+ justify-content: space-between;
111
+ align-items: center;
112
+
113
+ h3 {
114
+ margin: 0;
115
+ font-size: 1.1rem;
116
+ }
117
+
118
+ .close-btn {
119
+ background: none;
120
+ border: none;
121
+ font-size: 1.5rem;
122
+ cursor: pointer;
123
+ color: #6c757d;
124
+ line-height: 1;
125
+ padding: 0;
126
+ width: 24px;
127
+ height: 24px;
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+
132
+ &:hover {
133
+ color: #333;
134
+ }
135
+ }
136
+ }
137
+
138
+ .activity-content {
139
+ flex: 1;
140
+ overflow-y: auto;
141
+ min-height: 200px;
142
+ max-height: 400px;
143
+ }
144
+
145
+ .activity-list {
146
+ .activity-item {
147
+ padding: 0.75rem 1rem;
148
+ border-bottom: 1px solid #f0f0f0;
149
+ transition: background-color 0.2s;
150
+
151
+ &:hover {
152
+ background-color: #f8f9fa;
153
+ }
154
+
155
+ &:last-child {
156
+ border-bottom: none;
157
+ }
158
+
159
+ .activity-time {
160
+ font-size: 0.85rem;
161
+ color: #6c757d;
162
+ margin-bottom: 0.25rem;
163
+ }
164
+
165
+ .activity-content {
166
+ font-size: 0.9rem;
167
+
168
+ em {
169
+ color: #007bff;
170
+ font-style: normal;
171
+ font-weight: 500;
172
+ }
173
+
174
+ .details {
175
+ color: #6c757d;
176
+ font-size: 0.85rem;
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ .activity-footer {
183
+ padding: 0.5rem;
184
+ border-top: 1px solid #dee2e6;
185
+ display: flex;
186
+ justify-content: center;
187
+ align-items: center;
188
+
189
+ button {
190
+ width: 100%;
191
+ }
192
+
193
+ ::ng-deep .mat-paginator {
194
+ background: transparent;
195
+ width: 100%;
196
+
197
+ .mat-paginator-container {
198
+ padding: 0;
199
+ justify-content: center;
200
+ }
201
+
202
+ .mat-paginator-range-label {
203
+ margin: 0 8px;
204
+ }
205
+ }
206
+ }
207
+
208
+ .loading {
209
+ padding: 3rem;
210
+ display: flex;
211
+ justify-content: center;
212
+ align-items: center;
213
+ }
214
+
215
+ .empty {
216
+ padding: 3rem;
217
+ text-align: center;
218
+ color: #6c757d;
219
+ }
220
+ `]
221
+ })
222
+ export class ActivityLogComponent implements OnInit {
223
+ @Output() close = new EventEmitter<void>();
224
+
225
+ private http = inject(HttpClient);
226
+
227
+ activities: ActivityLog[] = [];
228
+ loading = false;
229
+ currentPage = 1;
230
+ pageSize = 10;
231
+ totalItems = 0;
232
+ totalPages = 0;
233
+
234
+ ngOnInit() {
235
+ this.loadActivities();
236
+ }
237
+
238
+ loadActivities(page: number = 1) {
239
+ this.loading = true;
240
+ this.currentPage = page;
241
+
242
+ this.http.get<ActivityLogResponse>(
243
+ `/api/activity-log?page=${page}&limit=${this.pageSize}`
244
+ ).subscribe({
245
+ next: (response) => {
246
+ this.activities = response.items;
247
+ this.totalItems = response.total;
248
+ this.totalPages = response.pages;
249
+ this.loading = false;
250
+ },
251
+ error: (error) => {
252
+ console.error('Failed to load activities:', error);
253
+ this.loading = false;
254
+ }
255
+ });
256
+ }
257
+
258
+ onPageChange(event: PageEvent) {
259
+ this.pageSize = event.pageSize;
260
+ this.loadActivities(event.pageIndex + 1);
261
+ }
262
+
263
+ openFullView() {
264
+ // TODO: Implement full activity log view
265
+ console.log('Open full activity log view');
266
+ this.close.emit();
267
+ }
268
+
269
+ getRelativeTime(timestamp: string): string {
270
+ const date = new Date(timestamp);
271
+ const now = new Date();
272
+ const diff = now.getTime() - date.getTime();
273
+
274
+ const minutes = Math.floor(diff / 60000);
275
+ const hours = Math.floor(diff / 3600000);
276
+ const days = Math.floor(diff / 86400000);
277
+
278
+ if (minutes < 1) return 'just now';
279
+ if (minutes < 60) return `${minutes} min ago`;
280
+ if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
281
+ if (days < 7) return `${days} day${days > 1 ? 's' : ''} ago`;
282
+
283
+ return date.toLocaleDateString();
284
+ }
285
+
286
+ getActionText(activity: ActivityLog): string {
287
+ const actions: Record<string, string> = {
288
+ 'CREATE_PROJECT': 'created project',
289
+ 'UPDATE_PROJECT': 'updated project',
290
+ 'DELETE_PROJECT': 'deleted project',
291
+ 'ENABLE_PROJECT': 'enabled project',
292
+ 'DISABLE_PROJECT': 'disabled project',
293
+ 'PUBLISH_VERSION': 'published version of',
294
+ 'CREATE_VERSION': 'created version for',
295
+ 'UPDATE_VERSION': 'updated version of',
296
+ 'DELETE_VERSION': 'deleted version from',
297
+ 'CREATE_API': 'created API',
298
+ 'UPDATE_API': 'updated API',
299
+ 'DELETE_API': 'deleted API',
300
+ 'UPDATE_ENVIRONMENT': 'updated environment',
301
+ 'IMPORT_PROJECT': 'imported project',
302
+ 'CHANGE_PASSWORD': 'changed password'
303
+ };
304
+
305
+ return actions[activity.action] || activity.action.toLowerCase().replace(/_/g, ' ');
306
+ }
307
  }