mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-08 05:39:06 +00:00
Initial commit
This commit is contained in:
121
lib/stores/audit-events.ts
Normal file
121
lib/stores/audit-events.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
|
||||
export interface AuditLogEntry {
|
||||
id: number;
|
||||
user_id: number | null;
|
||||
username: string;
|
||||
action: string;
|
||||
entity_type: string;
|
||||
entity_id: string | null;
|
||||
entity_name: string | null;
|
||||
environment_id: number | null;
|
||||
description: string | null;
|
||||
details: any | null;
|
||||
ip_address: string | null;
|
||||
user_agent: string | null;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export type AuditEventCallback = (event: AuditLogEntry) => void;
|
||||
|
||||
// Connection state
|
||||
export const auditSseConnected = writable<boolean>(false);
|
||||
export const auditSseError = writable<string | null>(null);
|
||||
export const lastAuditEvent = writable<AuditLogEntry | null>(null);
|
||||
|
||||
// Event listeners
|
||||
const listeners: Set<AuditEventCallback> = new Set();
|
||||
|
||||
let eventSource: EventSource | null = null;
|
||||
let reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
let reconnectAttempts = 0;
|
||||
const MAX_RECONNECT_ATTEMPTS = 5;
|
||||
const RECONNECT_DELAY = 3000;
|
||||
|
||||
// Subscribe to audit events
|
||||
export function onAuditEvent(callback: AuditEventCallback): () => void {
|
||||
listeners.add(callback);
|
||||
return () => listeners.delete(callback);
|
||||
}
|
||||
|
||||
// Notify all listeners
|
||||
function notifyListeners(event: AuditLogEntry) {
|
||||
lastAuditEvent.set(event);
|
||||
listeners.forEach(callback => {
|
||||
try {
|
||||
callback(event);
|
||||
} catch (e) {
|
||||
console.error('Audit event listener error:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Connect to SSE endpoint
|
||||
export function connectAuditSSE() {
|
||||
// Close existing connection
|
||||
disconnectAuditSSE();
|
||||
|
||||
try {
|
||||
eventSource = new EventSource('/api/audit/events');
|
||||
|
||||
eventSource.addEventListener('connected', (e) => {
|
||||
console.log('Audit SSE connected');
|
||||
auditSseConnected.set(true);
|
||||
auditSseError.set(null);
|
||||
reconnectAttempts = 0;
|
||||
});
|
||||
|
||||
eventSource.addEventListener('audit', (e) => {
|
||||
try {
|
||||
const event: AuditLogEntry = JSON.parse(e.data);
|
||||
notifyListeners(event);
|
||||
} catch (err) {
|
||||
console.error('Failed to parse audit event:', err);
|
||||
}
|
||||
});
|
||||
|
||||
eventSource.addEventListener('heartbeat', () => {
|
||||
// Connection is alive
|
||||
});
|
||||
|
||||
eventSource.addEventListener('error', (e) => {
|
||||
console.error('Audit SSE error:', e);
|
||||
auditSseConnected.set(false);
|
||||
|
||||
// Attempt reconnection
|
||||
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
||||
reconnectAttempts++;
|
||||
auditSseError.set(`Connection lost. Reconnecting (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`);
|
||||
reconnectTimeout = setTimeout(() => {
|
||||
connectAuditSSE();
|
||||
}, RECONNECT_DELAY);
|
||||
} else {
|
||||
auditSseError.set('Connection failed. Refresh the page to retry.');
|
||||
}
|
||||
});
|
||||
|
||||
eventSource.onerror = () => {
|
||||
// Handled by error event listener
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Failed to create Audit EventSource:', error);
|
||||
auditSseError.set(error.message || 'Failed to connect');
|
||||
auditSseConnected.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect from SSE
|
||||
export function disconnectAuditSSE() {
|
||||
if (reconnectTimeout) {
|
||||
clearTimeout(reconnectTimeout);
|
||||
reconnectTimeout = null;
|
||||
}
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
eventSource = null;
|
||||
}
|
||||
auditSseConnected.set(false);
|
||||
auditSseError.set(null);
|
||||
reconnectAttempts = 0;
|
||||
}
|
||||
Reference in New Issue
Block a user