feat(crawler): live status via SSE instead of polling
Replace the dashboard's 5s polling with a Server-Sent Events stream: - StatusHandle gains a tokio `watch` version bumped on every mutation; GET /admin/crawler/stream subscribes and pushes a composed snapshot immediately on connect, then on every status change (instant, no lost-wakeup) plus a 5s backstop for DB queue counts / browser phase. - Non-status signals poke the notifier so they push immediately too: session-expired (worker), session update / clear-expired / browser restart (endpoints). - compose_status is shared by the one-shot GET and the stream; the stream tolerates transient DB errors with a keep-alive comment instead of tearing down. Frontend: the crawler page opens an EventSource on mount and closes it on destroy, so the subscription is scoped to the active page (no global subscription). A one-shot fetch still paints initial state / serves as a fallback if SSE is blocked; a live/reconnecting indicator reflects the connection. The existing reverse proxy already streams SSE (its abort timer is cleared once response headers arrive), so no proxy change needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
resyncManga,
|
||||
resyncChapter,
|
||||
getCrawlerStatus,
|
||||
crawlerStatusStreamUrl,
|
||||
runCrawlerPass,
|
||||
restartCrawlerBrowser,
|
||||
updateCrawlerSession,
|
||||
@@ -356,6 +357,10 @@ describe('admin crawler api client', () => {
|
||||
queue: { pending: 2, running: 1, dead: 4 }
|
||||
};
|
||||
|
||||
it('crawlerStatusStreamUrl points at the SSE endpoint under the API base', () => {
|
||||
expect(crawlerStatusStreamUrl()).toMatch(/\/v1\/admin\/crawler\/stream$/);
|
||||
});
|
||||
|
||||
it('getCrawlerStatus GETs /v1/admin/crawler', async () => {
|
||||
fetchSpy.mockResolvedValueOnce(ok(statusFixture));
|
||||
const s = await getCrawlerStatus();
|
||||
|
||||
Reference in New Issue
Block a user