flare / flare-ui /src /app /components /activity-log /activity-log.component.ts
ciyidogan's picture
Upload 18 files
3b93905 verified
raw
history blame
5.03 kB
import { Component, EventEmitter, Output, inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
interface ActivityLog {
id: number;
timestamp: string;
user: string;
action: string;
entity_type: string;
entity_id: any;
entity_name: string;
details?: string;
}
@Component({
selector: 'app-activity-log',
standalone: true,
imports: [CommonModule],
template: `
<div class="activity-log-dropdown" (click)="$event.stopPropagation()">
<div class="activity-header">
<h3>πŸ”” Recent Activities</h3>
<button class="close-btn" (click)="close.emit()">Γ—</button>
</div>
<div class="activity-list">
@if (loading) {
<div class="loading">Loading...</div>
} @else if (activities.length === 0) {
<div class="empty">No recent activities</div>
} @else {
@for (activity of activities; track activity.id) {
<div class="activity-item">
<div class="activity-time">{{ getRelativeTime(activity.timestamp) }}</div>
<div class="activity-content">
<strong>{{ activity.user }}</strong> {{ getActionText(activity) }}
<em>{{ activity.entity_name }}</em>
</div>
</div>
}
}
</div>
<div class="activity-footer">
<button class="btn btn-secondary" (click)="loadMore()">View All Activities</button>
</div>
</div>
`,
styles: [`
.activity-log-dropdown {
position: absolute;
top: 100%;
right: 0;
width: 350px;
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
margin-top: 0.5rem;
}
.activity-header {
padding: 1rem;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
align-items: center;
h3 {
margin: 0;
font-size: 1.1rem;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #6c757d;
&:hover {
color: #333;
}
}
}
.activity-list {
max-height: 300px;
overflow-y: auto;
}
.activity-item {
padding: 0.75rem 1rem;
border-bottom: 1px solid #f0f0f0;
&:hover {
background-color: #f8f9fa;
}
.activity-time {
font-size: 0.85rem;
color: #6c757d;
margin-bottom: 0.25rem;
}
.activity-content {
font-size: 0.9rem;
em {
color: #007bff;
font-style: normal;
}
}
}
.activity-footer {
padding: 0.75rem;
border-top: 1px solid #dee2e6;
text-align: center;
}
.loading, .empty {
padding: 2rem;
text-align: center;
color: #6c757d;
}
`]
})
export class ActivityLogComponent implements OnInit {
@Output() close = new EventEmitter<void>();
private http = inject(HttpClient);
activities: ActivityLog[] = [];
loading = true;
ngOnInit() {
this.loadActivities();
}
loadActivities() {
this.loading = true;
this.http.get<ActivityLog[]>('/api/activity-log?limit=10')
.subscribe({
next: (data) => {
this.activities = data;
this.loading = false;
},
error: () => {
this.loading = false;
}
});
}
loadMore() {
// TODO: Implement full activity log view
console.log('Load more activities');
}
getRelativeTime(timestamp: string): string {
const date = new Date(timestamp);
const now = new Date();
const diff = now.getTime() - date.getTime();
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (minutes < 1) return 'just now';
if (minutes < 60) return `${minutes} min ago`;
if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
return `${days} day${days > 1 ? 's' : ''} ago`;
}
getActionText(activity: ActivityLog): string {
const actions: Record<string, string> = {
'CREATE_PROJECT': 'created project',
'UPDATE_PROJECT': 'updated project',
'DELETE_PROJECT': 'deleted project',
'PUBLISH_VERSION': 'published version',
'CREATE_VERSION': 'created version',
'UPDATE_VERSION': 'updated version',
'CREATE_API': 'created API',
'UPDATE_API': 'updated API',
'DELETE_API': 'deleted API',
'UPDATE_ENVIRONMENT': 'updated environment',
'IMPORT_PROJECT': 'imported project'
};
return actions[activity.action] || activity.action.toLowerCase().replace('_', ' ');
}
}