mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-07-06 18:01:17 +02:00
perf(web): cancel in-flight requests on navigation; consolidate dashboard stats
Fix dashboard pages stalling under rapid navigation, plus reduce the cost
of the heaviest dashboard endpoint.
SPA request cancellation: apiGet never passed an AbortSignal, so navigating
away left a page's in-flight requests running — the homepage alone fires
three (/stats + two charts), the slowest being /stats. Under rapid
navigation these piled up, holding browser connections and API threadpool
threads, so the page actually wanted queued behind stale work; a late
resolver could also clobber the new page's DOM.
- api.js: apiGet accepts an optional { signal } and forwards it to fetch;
export isAbortError().
- router.js: each navigation gets an AbortController; the previous one is
aborted at the start of _handleRoute and its signal is passed to the page
handler. A navigation-generation guard stops a superseded route from
hiding the loader for the page that replaced it.
- app.js: pageHandler swallows AbortError (an intentional cancel is not an
error).
- all 11 page modules: thread params.signal into on-load apiGet calls and
guard their catch blocks with isAbortError.
dashboard/stats consolidation: collapse the 11 sequential COUNT(*) queries
into 4 using portable conditional aggregation (func.sum(case(...))) for
nodes, messages, advertisements, and user profiles. Responses are
unchanged.
Docs: extend the v0.12 "Read-Path Query Optimisations" note and add a
"Dashboard Navigation Responsiveness" note (front-end only, no action
required).
Tests: add test_stats_time_bucket_counts asserting the active/today/24h/7d
buckets. SPA bundles are gitignored and rebuilt by the Docker/CI build, so
only committed source changed; the esbuild build was run locally to
validate the JS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,72 @@ class TestDashboardStats:
|
||||
assert data["total_messages"] == 1
|
||||
assert data["total_advertisements"] == 1
|
||||
|
||||
def test_stats_time_bucket_counts(self, client_no_auth, api_db_session):
|
||||
"""Conditional-aggregation buckets (active/today/24h/7d) count the
|
||||
correct rows across recent and older records."""
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# Nodes: one active (seen now), one stale (seen 2 days ago).
|
||||
api_db_session.add_all(
|
||||
[
|
||||
Node(public_key="a" * 64, name="Active", last_seen=now),
|
||||
Node(
|
||||
public_key="b" * 64,
|
||||
name="Stale",
|
||||
last_seen=now - timedelta(days=2),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
# Messages (contact type → always channel-visible): today, 3 days ago,
|
||||
# 10 days ago.
|
||||
api_db_session.add_all(
|
||||
[
|
||||
Message(message_type="contact", text="now", received_at=now),
|
||||
Message(
|
||||
message_type="contact",
|
||||
text="3d",
|
||||
received_at=now - timedelta(days=3),
|
||||
),
|
||||
Message(
|
||||
message_type="contact",
|
||||
text="10d",
|
||||
received_at=now - timedelta(days=10),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
# Flood advertisements: now (24h), 3 days ago (7d), 10 days ago (older).
|
||||
api_db_session.add_all(
|
||||
[
|
||||
Advertisement(public_key="c" * 64, route_type="flood", received_at=now),
|
||||
Advertisement(
|
||||
public_key="d" * 64,
|
||||
route_type="flood",
|
||||
received_at=now - timedelta(days=3),
|
||||
),
|
||||
Advertisement(
|
||||
public_key="e" * 64,
|
||||
route_type="flood",
|
||||
received_at=now - timedelta(days=10),
|
||||
),
|
||||
]
|
||||
)
|
||||
api_db_session.commit()
|
||||
|
||||
data = client_no_auth.get("/api/v1/dashboard/stats").json()
|
||||
|
||||
assert data["total_nodes"] == 2
|
||||
assert data["active_nodes"] == 1
|
||||
|
||||
assert data["total_messages"] == 3
|
||||
assert data["messages_today"] == 1
|
||||
assert data["messages_7d"] == 2 # now + 3d (10d excluded)
|
||||
|
||||
assert data["total_advertisements"] == 3
|
||||
assert data["advertisements_24h"] == 1
|
||||
assert data["advertisements_7d"] == 2 # now + 3d (10d excluded)
|
||||
|
||||
|
||||
class TestDashboardHtmlRemoved:
|
||||
"""Tests that legacy HTML dashboard endpoint has been removed."""
|
||||
|
||||
Reference in New Issue
Block a user