import React, { useEffect, useState } from 'react'; import api from '../utils/api'; import './Dashboard.css'; import { Shield, Users, Activity, Clock } from 'lucide-react'; interface Stats { total_logs: number; unique_attackers: number; active_deckies: number; deployed_deckies: number; } interface LogEntry { id: number; timestamp: string; decky: string; service: string; event_type: string | null; attacker_ip: string; raw_line: string; fields: string | null; msg: string | null; } interface DashboardProps { searchQuery: string; } const Dashboard: React.FC = ({ searchQuery }) => { const [stats, setStats] = useState(null); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const fetchData = async () => { try { const [statsRes, logsRes] = await Promise.all([ api.get('/stats'), api.get('/logs', { params: { limit: 50, search: searchQuery } }) ]); setStats(statsRes.data); setLogs(logsRes.data.data); } catch (err) { console.error('Failed to fetch dashboard data', err); } finally { setLoading(false); } }; useEffect(() => { // Initial fetch to populate UI immediately fetchData(); // Setup SSE connection const token = localStorage.getItem('token'); const baseUrl = 'http://localhost:8000/api/v1'; // Or extract from api.defaults.baseURL let url = `${baseUrl}/stream?token=${token}`; if (searchQuery) { url += `&search=${encodeURIComponent(searchQuery)}`; } const eventSource = new EventSource(url); eventSource.onmessage = (event) => { try { const payload = JSON.parse(event.data); if (payload.type === 'logs') { setLogs(prev => { const newLogs = payload.data; // Prepend new logs, keep up to 100 in UI to prevent infinite DOM growth return [...newLogs, ...prev].slice(0, 100); }); } else if (payload.type === 'stats') { setStats(payload.data); } } catch (err) { console.error('Failed to parse SSE payload', err); } }; eventSource.onerror = (err) => { console.error('SSE connection error, attempting to reconnect...', err); }; return () => { eventSource.close(); }; }, [searchQuery]); if (loading && !stats) return
INITIALIZING SENSORS...
; return (
} label="TOTAL INTERACTIONS" value={stats?.total_logs || 0} /> } label="UNIQUE ATTACKERS" value={stats?.unique_attackers || 0} /> } label="ACTIVE DECKIES" value={`${stats?.active_deckies || 0} / ${stats?.deployed_deckies || 0}`} />

LIVE INTERACTION LOG

{logs.length > 0 ? logs.map(log => { let parsedFields: Record = {}; if (log.fields) { try { parsedFields = JSON.parse(log.fields); } catch (e) { // Ignore parsing errors } } return ( ); }) : ( )}
TIMESTAMP DECKY SERVICE ATTACKER IP EVENT
{new Date(log.timestamp).toLocaleString()} {log.decky} {log.service} {log.attacker_ip}
{log.event_type} {log.msg && log.msg !== '-' && — {log.msg}}
{Object.keys(parsedFields).length > 0 && (
{Object.entries(parsedFields).map(([k, v]) => ( {k}: {v} ))}
)}
NO INTERACTION DETECTED
); }; interface StatCardProps { icon: React.ReactNode; label: string; value: string | number; } const StatCard: React.FC = ({ icon, label, value }) => (
{icon}
{label} {value.toLocaleString()}
); export default Dashboard;