From 57d395d6d7b5cfd00de1aa465c2f08466895c5fb Mon Sep 17 00:00:00 2001 From: anti Date: Mon, 13 Apr 2026 18:33:32 -0400 Subject: [PATCH] fix: auth redirect, SSE reconnect, stats polling removal, active decky count, schemathesis health check --- decnet/web/db/sqlite/repository.py | 7 +-- decnet_web/src/components/Dashboard.tsx | 65 +++++++++++++++---------- decnet_web/src/components/Layout.tsx | 16 ++---- tests/api/test_schemathesis.py | 4 +- 4 files changed, 48 insertions(+), 44 deletions(-) diff --git a/decnet/web/db/sqlite/repository.py b/decnet/web/db/sqlite/repository.py index 9f28a33..b4768eb 100644 --- a/decnet/web/db/sqlite/repository.py +++ b/decnet/web/db/sqlite/repository.py @@ -226,11 +226,6 @@ class SQLiteRepository(BaseRepository): select(func.count(func.distinct(Log.attacker_ip))) ) ).scalar() or 0 - active_deckies = ( - await session.execute( - select(func.count(func.distinct(Log.decky))) - ) - ).scalar() or 0 _state = await asyncio.to_thread(load_state) deployed_deckies = len(_state[0].deckies) if _state else 0 @@ -238,7 +233,7 @@ class SQLiteRepository(BaseRepository): return { "total_logs": total_logs, "unique_attackers": unique_attackers, - "active_deckies": active_deckies, + "active_deckies": deployed_deckies, "deployed_deckies": deployed_deckies, } diff --git a/decnet_web/src/components/Dashboard.tsx b/decnet_web/src/components/Dashboard.tsx index c32717d..7f6c0f7 100644 --- a/decnet_web/src/components/Dashboard.tsx +++ b/decnet_web/src/components/Dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import './Dashboard.css'; import { Shield, Users, Activity, Clock } from 'lucide-react'; @@ -29,37 +29,52 @@ const Dashboard: React.FC = ({ searchQuery }) => { const [stats, setStats] = useState(null); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); + const eventSourceRef = useRef(null); + const reconnectTimerRef = useRef | null>(null); useEffect(() => { - const token = localStorage.getItem('token'); - const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000/api/v1'; - 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 => [...payload.data, ...prev].slice(0, 100)); - } else if (payload.type === 'stats') { - setStats(payload.data); - setLoading(false); - } - } catch (err) { - console.error('Failed to parse SSE payload', err); + const connect = () => { + if (eventSourceRef.current) { + eventSourceRef.current.close(); } + + const token = localStorage.getItem('token'); + const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8000/api/v1'; + let url = `${baseUrl}/stream?token=${token}`; + if (searchQuery) { + url += `&search=${encodeURIComponent(searchQuery)}`; + } + + const es = new EventSource(url); + eventSourceRef.current = es; + + es.onmessage = (event) => { + try { + const payload = JSON.parse(event.data); + if (payload.type === 'logs') { + setLogs(prev => [...payload.data, ...prev].slice(0, 100)); + } else if (payload.type === 'stats') { + setStats(payload.data); + setLoading(false); + window.dispatchEvent(new CustomEvent('decnet:stats', { detail: payload.data })); + } + } catch (err) { + console.error('Failed to parse SSE payload', err); + } + }; + + es.onerror = () => { + es.close(); + eventSourceRef.current = null; + reconnectTimerRef.current = setTimeout(connect, 3000); + }; }; - eventSource.onerror = (err) => { - console.error('SSE connection error, attempting to reconnect...', err); - }; + connect(); return () => { - eventSource.close(); + if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current); + if (eventSourceRef.current) eventSourceRef.current.close(); }; }, [searchQuery]); diff --git a/decnet_web/src/components/Layout.tsx b/decnet_web/src/components/Layout.tsx index 7645caa..20aa850 100644 --- a/decnet_web/src/components/Layout.tsx +++ b/decnet_web/src/components/Layout.tsx @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react'; import { NavLink } from 'react-router-dom'; import { Menu, X, Search, Activity, LayoutDashboard, Terminal, Settings, LogOut, Server, Archive } from 'lucide-react'; -import api from '../utils/api'; import './Layout.css'; interface LayoutProps { @@ -21,17 +20,12 @@ const Layout: React.FC = ({ children, onLogout, onSearch }) => { }; useEffect(() => { - const fetchStatus = async () => { - try { - const res = await api.get('/stats'); - setSystemActive(res.data.deployed_deckies > 0); - } catch (err) { - console.error('Failed to fetch system status', err); - } + const onStats = (e: Event) => { + const stats = (e as CustomEvent).detail; + setSystemActive(stats.deployed_deckies > 0); }; - fetchStatus(); - const interval = setInterval(fetchStatus, 10000); - return () => clearInterval(interval); + window.addEventListener('decnet:stats', onStats); + return () => window.removeEventListener('decnet:stats', onStats); }, []); return ( diff --git a/tests/api/test_schemathesis.py b/tests/api/test_schemathesis.py index 328b61a..9c000bd 100644 --- a/tests/api/test_schemathesis.py +++ b/tests/api/test_schemathesis.py @@ -12,7 +12,7 @@ Requires DECNET_DEVELOPER=true (set in tests/conftest.py) to expose /openapi.jso """ import pytest import schemathesis as st -from hypothesis import settings, Verbosity +from hypothesis import settings, Verbosity, HealthCheck from decnet.web.auth import create_access_token import subprocess @@ -102,6 +102,6 @@ schema = st.openapi.from_url(f"{LIVE_SERVER_URL}/openapi.json") @pytest.mark.fuzz @st.pytest.parametrize(api=schema) -@settings(max_examples=3000, deadline=None, verbosity=Verbosity.debug) +@settings(max_examples=3000, deadline=None, verbosity=Verbosity.debug, suppress_health_check=[HealthCheck.filter_too_much]) def test_schema_compliance(case): case.call_and_validate()