Merge improvement/header-activity-coherent: header reflects live sync activity, not pending work
This commit is contained in:
commit
72a26fb455
7 changed files with 48 additions and 18 deletions
|
|
@ -13,6 +13,7 @@ from app.sync.runner import (
|
||||||
run_rss_poll,
|
run_rss_poll,
|
||||||
)
|
)
|
||||||
from app.sync.subscriptions import import_subscriptions
|
from app.sync.subscriptions import import_subscriptions
|
||||||
|
from app.scheduler import running_job_ids
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/sync", tags=["sync"])
|
router = APIRouter(prefix="/api/sync", tags=["sync"])
|
||||||
|
|
||||||
|
|
@ -170,6 +171,9 @@ def my_status(
|
||||||
"quota_used_today": quota.units_used_today(db),
|
"quota_used_today": quota.units_used_today(db),
|
||||||
"quota_remaining_today": quota.remaining_today(db),
|
"quota_remaining_today": quota.remaining_today(db),
|
||||||
"paused": state.is_sync_paused(db),
|
"paused": state.is_sync_paused(db),
|
||||||
|
# True only while a job that advances this view (channel sync) is actually running,
|
||||||
|
# so the header shows live activity rather than a perpetual spinner over pending work.
|
||||||
|
"sync_active": bool({"backfill", "rss_poll"} & running_job_ids()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -272,6 +272,13 @@ def _is_running(job_id: str) -> bool:
|
||||||
return bool(_activity.get(job_id, {}).get("running"))
|
return bool(_activity.get(job_id, {}).get("running"))
|
||||||
|
|
||||||
|
|
||||||
|
def running_job_ids() -> set[str]:
|
||||||
|
"""Ids of jobs executing right now (any trigger). Lets non-admin surfaces — e.g. the
|
||||||
|
header's sync indicator — reflect real activity instead of just pending-work counts."""
|
||||||
|
with _activity_lock:
|
||||||
|
return {jid for jid, a in _activity.items() if a.get("running")}
|
||||||
|
|
||||||
|
|
||||||
def trigger_job(job_id: str, actor_id: int | None = None) -> str:
|
def trigger_job(job_id: str, actor_id: int | None = None) -> str:
|
||||||
"""Run a job immediately in a background thread, independent of its interval schedule.
|
"""Run a job immediately in a background thread, independent of its interval schedule.
|
||||||
The wrapper handles its own DB session, activity tracking and pause-skip, so the live
|
The wrapper handles its own DB session, activity tracking and pause-skip, so the live
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Database, History, Loader2, Pause, Play } from "lucide-react";
|
import { Clock, Database, History, Loader2, Pause, Play } from "lucide-react";
|
||||||
import { api } from "../lib/api";
|
import { api } from "../lib/api";
|
||||||
import { formatViews } from "../lib/format";
|
import { formatViews } from "../lib/format";
|
||||||
import Tooltip from "./Tooltip";
|
import Tooltip from "./Tooltip";
|
||||||
|
|
@ -38,6 +38,10 @@ export default function SyncStatus({
|
||||||
|
|
||||||
const syncing = data.channels_recent_pending;
|
const syncing = data.channels_recent_pending;
|
||||||
const notFull = data.channels_deep_pending; // your channels without full history yet
|
const notFull = data.channels_deep_pending; // your channels without full history yet
|
||||||
|
// Spin only while a sync job is actually running; otherwise pending work shows as a calm,
|
||||||
|
// static state (or, for deep history, just the "N without full history" link below).
|
||||||
|
const active = data.sync_active;
|
||||||
|
const showMain = data.paused || active || syncing > 0 || notFull === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hidden lg:flex items-center gap-2 text-xs text-muted">
|
<div className="hidden lg:flex items-center gap-2 text-xs text-muted">
|
||||||
|
|
@ -54,24 +58,32 @@ export default function SyncStatus({
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{showMain && (
|
||||||
|
<>
|
||||||
<span className="opacity-40">·</span>
|
<span className="opacity-40">·</span>
|
||||||
{data.paused ? (
|
{data.paused ? (
|
||||||
<span className="text-accent font-medium">{t("header.sync.paused")}</span>
|
<span className="text-accent font-medium">{t("header.sync.paused")}</span>
|
||||||
) : syncing > 0 ? (
|
) : active ? (
|
||||||
|
// A sync job is running right now — spin and name what it's doing.
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||||
{t("header.sync.syncing", { count: syncing })}
|
{syncing > 0
|
||||||
|
? t("header.sync.syncing", { count: syncing })
|
||||||
|
: notFull > 0
|
||||||
|
? t("header.sync.backfillingHistory")
|
||||||
|
: t("header.sync.working")}
|
||||||
</span>
|
</span>
|
||||||
) : notFull > 0 ? (
|
) : syncing > 0 ? (
|
||||||
// Recent uploads are in, but the scheduler is still backfilling full history —
|
// Recent uploads queued for the next run, but nothing running now — static.
|
||||||
// so "all synced" would be misleading. Reflect the deep work as active.
|
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
<Clock className="w-3.5 h-3.5" />
|
||||||
{t("header.sync.backfillingHistory")}
|
{t("header.sync.recentQueued", { count: syncing })}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span>{t("header.sync.allSynced")}</span>
|
<span>{t("header.sync.allSynced")}</span>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{notFull > 0 && (
|
{notFull > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="opacity-40">·</span>
|
<span className="opacity-40">·</span>
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
"paused": "pausiert",
|
"paused": "pausiert",
|
||||||
"syncing": "{{count}} werden synchronisiert",
|
"syncing": "{{count}} werden synchronisiert",
|
||||||
"backfillingHistory": "Verlauf wird geladen",
|
"backfillingHistory": "Verlauf wird geladen",
|
||||||
|
"working": "Synchronisierung…",
|
||||||
|
"recentQueued": "{{count}} in Warteschlange",
|
||||||
"allSynced": "alles synchronisiert",
|
"allSynced": "alles synchronisiert",
|
||||||
"withoutFullHistory": "{{count}} ohne vollständigen Verlauf",
|
"withoutFullHistory": "{{count}} ohne vollständigen Verlauf",
|
||||||
"fullHistoryTooltip": "Einige deiner Kanäle haben nur ihre neuesten Uploads. Klicke, um die Kanalverwaltung (auf diese gefiltert) zu öffnen und ihren vollständigen Katalog anzufordern.",
|
"fullHistoryTooltip": "Einige deiner Kanäle haben nur ihre neuesten Uploads. Klicke, um die Kanalverwaltung (auf diese gefiltert) zu öffnen und ihren vollständigen Katalog anzufordern.",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
"paused": "paused",
|
"paused": "paused",
|
||||||
"syncing": "{{count}} syncing",
|
"syncing": "{{count}} syncing",
|
||||||
"backfillingHistory": "fetching history",
|
"backfillingHistory": "fetching history",
|
||||||
|
"working": "syncing…",
|
||||||
|
"recentQueued": "{{count}} queued",
|
||||||
"allSynced": "all synced",
|
"allSynced": "all synced",
|
||||||
"withoutFullHistory": "{{count}} without full history",
|
"withoutFullHistory": "{{count}} without full history",
|
||||||
"fullHistoryTooltip": "Some of your channels only have their recent uploads. Click to open the channel manager (filtered to these) and request their full back-catalog.",
|
"fullHistoryTooltip": "Some of your channels only have their recent uploads. Click to open the channel manager (filtered to these) and request their full back-catalog.",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@
|
||||||
"paused": "szüneteltetve",
|
"paused": "szüneteltetve",
|
||||||
"syncing": "{{count}} szinkronizálás alatt",
|
"syncing": "{{count}} szinkronizálás alatt",
|
||||||
"backfillingHistory": "előzmény letöltése",
|
"backfillingHistory": "előzmény letöltése",
|
||||||
|
"working": "szinkronizálás…",
|
||||||
|
"recentQueued": "{{count}} sorban",
|
||||||
"allSynced": "minden szinkronban",
|
"allSynced": "minden szinkronban",
|
||||||
"withoutFullHistory": "{{count}} teljes előzmény nélkül",
|
"withoutFullHistory": "{{count}} teljes előzmény nélkül",
|
||||||
"fullHistoryTooltip": "Néhány csatornádnak csak a legutóbbi feltöltései vannak meg. Kattints a csatornakezelő megnyitásához (ezekre szűrve), és kérd le a teljes archívumukat.",
|
"fullHistoryTooltip": "Néhány csatornádnak csak a legutóbbi feltöltései vannak meg. Kattints a csatornakezelő megnyitásához (ezekre szűrve), és kérd le a teljes archívumukat.",
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,7 @@ export interface MyStatus {
|
||||||
quota_used_today: number;
|
quota_used_today: number;
|
||||||
quota_remaining_today: number;
|
quota_remaining_today: number;
|
||||||
paused: boolean;
|
paused: boolean;
|
||||||
|
sync_active: boolean; // a channel-sync job (backfill/rss) is running right now
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MyUsage {
|
export interface MyUsage {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue