feat(tasks,server,web): bridge outbox events to the SSE bus (U3)
OutboxRelay gains a `mirror` option: listed types are claimed even without a task route and, after the claim transaction commits, published in order to the realtime bus (publish failures are logged, delivery state unaffected). The worker mirrors feed.*, decision.*, download.*, organization.* and subscription.* (feed.due excluded) with their business-id payloads, so the home page, inbox, task stream and feed tables finally receive them. Feed tables refresh on feed.fetched / feed.health_changed; the task stream also listens to feed and decision events. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
04401309fb
commit
615917bb45
6 files changed
+136
-6
No files matched your search
@@ -1,8 +1,9 @@
|
||||
import type { FeedHealth, FeedSource } from "@umt/contracts";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { EventType, type FeedHealth, type FeedSource } from "@umt/contracts";
|
||||
import { RotateCcw, Rss, Zap } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { feedGapHint, useFeedAction, useFeeds, useFetchFeedNow } from "~/api/feeds.ts";
|
||||
import { feedGapHint, feedKeys, useFeedAction, useFeeds, useFetchFeedNow } from "~/api/feeds.ts";
|
||||
import { useSiteAccounts, useSites } from "~/api/sites.ts";
|
||||
import { type DataColumn, DataTable } from "~/components/console/data-table.tsx";
|
||||
import { DetailSection, DetailSheet } from "~/components/console/detail-sheet.tsx";
|
||||
@@ -12,6 +13,7 @@ import { StatusBadge, type StatusTone } from "~/components/console/status-badge.
|
||||
import { Button } from "~/components/ui/button.tsx";
|
||||
import { Switch } from "~/components/ui/switch.tsx";
|
||||
import { errorMessage } from "~/lib/api.ts";
|
||||
import { useSse } from "~/lib/sse.ts";
|
||||
import { formatDateTime } from "~/lib/utils.ts";
|
||||
import { FeedFormDialog, PURPOSE_LABEL } from "./feed-form.tsx";
|
||||
import { relTime } from "./release-fields.ts";
|
||||
@@ -78,6 +80,25 @@ function FeedActions({ f }: { f: FeedSource }) {
|
||||
/**
|
||||
* Feed 列表 + 详情抽屉 + 新建/编辑对话框。来源健康页列全部,站点详情按 siteId 过滤。
|
||||
*/
|
||||
/** 抓取完成 / 健康变化(outbox → SSE 桥)到达时节流刷新 Feed 列表 */
|
||||
function useLiveFeeds() {
|
||||
const qc = useQueryClient();
|
||||
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
useSse([EventType.feedFetched, EventType.feedHealthChanged], () => {
|
||||
if (timer.current) return;
|
||||
timer.current = setTimeout(() => {
|
||||
timer.current = null;
|
||||
void qc.invalidateQueries({ queryKey: feedKeys.all });
|
||||
}, 800);
|
||||
});
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (timer.current) clearTimeout(timer.current);
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
export function FeedTable({
|
||||
siteId,
|
||||
health,
|
||||
@@ -93,6 +114,7 @@ export function FeedTable({
|
||||
filtered?: boolean;
|
||||
}) {
|
||||
const feeds = useFeeds({ health, siteId });
|
||||
useLiveFeeds();
|
||||
const sites = useSites();
|
||||
const accounts = useSiteAccounts();
|
||||
const [editingFeed, setEditingFeed] = useState<FeedSource | null>(null);
|
||||
|
||||
Reference in new issue
Block a user