feat: Add dashboard charts with interactivity and basic authentication support

This commit is contained in:
2026-06-01 12:28:02 +02:00
parent cde181f343
commit 24f2b2ed88
8 changed files with 323 additions and 26 deletions
+33
View File
@@ -134,11 +134,15 @@ async def test_dashboard_page_and_fragment_and_sse(tmp_path) -> None:
overview = await client.get("/dashboard/fragment/overview")
overview_stream = await client.get("/dashboard/stream/overview")
controls = await client.get("/dashboard/fragment/controls")
charts = await client.get("/dashboard/fragment/charts")
assert page.status_code == 200
assert "EventSource" in page.text
assert "alpinejs" in page.text.lower()
assert "Chart.js" in page.text or "chart.umd.min.js" in page.text
assert 'hx-get="/dashboard/fragment/metrics"' in page.text
assert 'hx-get="/dashboard/fragment/controls"' in page.text
assert 'hx-get="/dashboard/fragment/charts"' in page.text
assert fragment.status_code == 200
assert "Realized P&L" in fragment.text
@@ -170,6 +174,11 @@ async def test_dashboard_page_and_fragment_and_sse(tmp_path) -> None:
assert "Paper trading mode" in controls.text
assert "Trade capital USD" in controls.text
assert charts.status_code == 200
assert "Opportunity Trend" in charts.text
assert "opportunity-chart" in charts.text
assert "Hide chart" in charts.text or "Show chart" in charts.text
async def test_dashboard_controls_update_runtime_state_and_config(tmp_path) -> None:
app = create_app(Settings(DUCKDB_PATH=tmp_path / "controls.duckdb"))
@@ -210,3 +219,27 @@ async def test_dashboard_controls_update_runtime_state_and_config(tmp_path) -> N
assert app.state.settings.max_trade_capital_usd == 300.0
assert app.state.settings.max_concurrent_trades == 4
assert app.state.settings.paper_trading_mode is True
async def test_dashboard_requires_basic_auth_when_configured(tmp_path) -> None:
app = create_app(
Settings(
DUCKDB_PATH=tmp_path / "auth.duckdb",
DASHBOARD_AUTH_USERNAME="admin",
DASHBOARD_AUTH_PASSWORD="secret",
)
)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
unauthenticated = await client.get("/dashboard/fragment/overview")
authenticated = await client.get(
"/dashboard/fragment/overview",
auth=("admin", "secret"),
)
health = await client.get("/health")
assert unauthenticated.status_code == 401
assert unauthenticated.headers["www-authenticate"] == 'Basic realm="Arbitrade Dashboard"'
assert authenticated.status_code == 200
assert health.status_code == 200