Merge bug/scheduler-progress-visibility: live progress for any scheduler job + parallel RSS polling
This commit is contained in:
commit
7fd335aa90
9 changed files with 91 additions and 25 deletions
|
|
@ -21,6 +21,8 @@ import { notify } from "../lib/notifications";
|
|||
import Tooltip from "./Tooltip";
|
||||
|
||||
const POLL_MS = 4000;
|
||||
// While any job is running, poll faster so short scheduled runs aren't missed between ticks.
|
||||
const FAST_POLL_MS = 1500;
|
||||
|
||||
// Seconds until an ISO instant (negative = past).
|
||||
function secsUntil(iso: string | null): number | null {
|
||||
|
|
@ -73,19 +75,25 @@ function StatusLegend() {
|
|||
);
|
||||
}
|
||||
|
||||
function JobProgress({ p }: { p: NonNullable<SchedulerJob["progress"]> }) {
|
||||
// Shown for ANY running job. With reported counts it's a determinate bar; for a job that
|
||||
// runs but doesn't report progress (or hasn't yet) it falls back to a generic "working"
|
||||
// label and an indeterminate sliver — so every active run is visible, not just the ones
|
||||
// that report numbers.
|
||||
function JobProgress({ p }: { p: SchedulerJob["progress"] }) {
|
||||
const { t } = useTranslation();
|
||||
const phase = p.phase ? t(`scheduler.phase.${p.phase}`, p.phase) : null;
|
||||
const phase = p?.phase ? t(`scheduler.phase.${p.phase}`, p.phase) : t("scheduler.phase.working");
|
||||
const pct =
|
||||
p.total && p.total > 0 ? Math.min(100, Math.round((p.current / p.total) * 100)) : null;
|
||||
p?.total && p.total > 0 ? Math.min(100, Math.round((p.current / p.total) * 100)) : null;
|
||||
return (
|
||||
<div className="mt-1.5">
|
||||
<div className="flex items-center justify-between text-[11px] text-muted mb-0.5">
|
||||
<span className="truncate">{phase}</span>
|
||||
<span className="tabular-nums shrink-0">
|
||||
{p.total != null
|
||||
{p?.total != null
|
||||
? `${p.current.toLocaleString()} / ${p.total.toLocaleString()}`
|
||||
: p.current.toLocaleString()}
|
||||
: p
|
||||
? p.current.toLocaleString()
|
||||
: ""}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-1.5 rounded-full bg-border overflow-hidden">
|
||||
|
|
@ -184,7 +192,7 @@ function JobRow({
|
|||
<span> · {job.last_result}</span>
|
||||
) : null}
|
||||
</div>
|
||||
{job.running && job.progress && <JobProgress p={job.progress} />}
|
||||
{job.running && <JobProgress p={job.progress} />}
|
||||
</div>
|
||||
<div className="shrink-0 text-right text-[11px] text-muted tabular-nums">
|
||||
{job.running ? (
|
||||
|
|
@ -298,8 +306,10 @@ function Stat({
|
|||
export default function Scheduler() {
|
||||
const { t } = useTranslation();
|
||||
const qc = useQueryClient();
|
||||
// Poll faster while any job is running so live progress is smooth, then ease back when
|
||||
// idle. Derived from the freshest data by react-query itself (see useLiveQuery).
|
||||
const q = useLiveQuery<SchedulerStatus>(["scheduler"], api.schedulerStatus, {
|
||||
intervalMs: POLL_MS,
|
||||
intervalMs: (d) => (d?.jobs?.some((j) => j.running) ? FAST_POLL_MS : POLL_MS),
|
||||
});
|
||||
const data = q.data;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@
|
|||
"enrich": "Videos anreichern",
|
||||
"backfill_recent": "Neue Uploads nachladen",
|
||||
"backfill_deep": "Ganze Historie nachladen",
|
||||
"probe": "Shorts klassifizieren"
|
||||
"probe": "Shorts klassifizieren",
|
||||
"rss_poll": "Auf neue Uploads prüfen",
|
||||
"subscriptions": "Abos synchronisieren",
|
||||
"autotag": "Kanäle automatisch taggen",
|
||||
"playlist_sync": "Playlists synchronisieren",
|
||||
"working": "Arbeitet…"
|
||||
},
|
||||
"maintenance": {
|
||||
"title": "Wartung",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@
|
|||
"enrich": "Enriching videos",
|
||||
"backfill_recent": "Backfilling recent uploads",
|
||||
"backfill_deep": "Backfilling full history",
|
||||
"probe": "Classifying Shorts"
|
||||
"probe": "Classifying Shorts",
|
||||
"rss_poll": "Checking for new uploads",
|
||||
"subscriptions": "Syncing subscriptions",
|
||||
"autotag": "Auto-tagging channels",
|
||||
"playlist_sync": "Syncing playlists",
|
||||
"working": "Working…"
|
||||
},
|
||||
"maintenance": {
|
||||
"title": "Maintenance",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@
|
|||
"enrich": "Videók dúsítása",
|
||||
"backfill_recent": "Friss feltöltések behúzása",
|
||||
"backfill_deep": "Teljes előzmény behúzása",
|
||||
"probe": "Shorts besorolás"
|
||||
"probe": "Shorts besorolás",
|
||||
"rss_poll": "Új feltöltések keresése",
|
||||
"subscriptions": "Feliratkozások szinkronizálása",
|
||||
"autotag": "Csatornák automatikus címkézése",
|
||||
"playlist_sync": "Lejátszási listák szinkronizálása",
|
||||
"working": "Dolgozik…"
|
||||
},
|
||||
"maintenance": {
|
||||
"title": "Karbantartás",
|
||||
|
|
|
|||
|
|
@ -8,14 +8,22 @@ import { useQuery, type QueryKey } from "@tanstack/react-query";
|
|||
export function useLiveQuery<T>(
|
||||
key: QueryKey,
|
||||
queryFn: () => Promise<T>,
|
||||
opts: { intervalMs?: number; enabled?: boolean } = {}
|
||||
// intervalMs may be a function of the latest data, so the poll cadence can adapt to it
|
||||
// (e.g. poll faster while a job is running). react-query re-evaluates it after each fetch
|
||||
// against fresh data — the right place for this, vs. a React state toggle that can lag or
|
||||
// stall the live updates.
|
||||
opts: { intervalMs?: number | ((data: T | undefined) => number); enabled?: boolean } = {}
|
||||
) {
|
||||
const { intervalMs = 4000, enabled = true } = opts;
|
||||
return useQuery({
|
||||
queryKey: key,
|
||||
queryFn,
|
||||
enabled,
|
||||
refetchInterval: enabled ? intervalMs : false,
|
||||
refetchInterval: !enabled
|
||||
? false
|
||||
: typeof intervalMs === "function"
|
||||
? (query) => intervalMs(query.state.data as T | undefined)
|
||||
: intervalMs,
|
||||
refetchIntervalInBackground: false,
|
||||
staleTime: 0,
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue