mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-02 21:19:05 +00:00
122 lines
3.0 KiB
TypeScript
122 lines
3.0 KiB
TypeScript
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;
|
|
}
|