feat(scheduler): each row shows its last three runs, an interrupted run as such, and the ad-hoc runs beside the jobs
A row's one dot said how the LAST run went, and an interrupted run fell through to "idle" — a grey dot that read as "no run recorded". The row now leads with its last three runs, newest first, each named for a screen reader and in its tooltip (how it went, when, who started it); an interrupted run is a ring, set apart from a skipped run and a failure, and its line says the server restarted under it. A channel's full-history backfill gets its own "Ad-hoc runs" block: its runs, its channel and what it fetched, no interval and no run button — it needs its target. AD_HOC_JOBS names every such job; the backend-id contract holds their names in both languages, and run_ad_hoc refuses any other id.
This commit is contained in:
@@ -10,7 +10,7 @@ import { vocabKey } from "../lib/vocab";
|
||||
// the registries straight from the backend sources:
|
||||
// - `sysconfig.SPECS` → `config.groups.<group>`, `config.fields.<key>.label` + `.hint`, and for a
|
||||
// "choice" key `config.choices.<key>.<option>` — the options read from its Literal in config.py
|
||||
// - `scheduler.JOB_INTERVALS` → `scheduler.jobs.<id>` + `scheduler.jobDesc.<id>`
|
||||
// - `scheduler.JOB_INTERVALS` and `scheduler.AD_HOC_JOBS` → `scheduler.jobs.<id>` + `scheduler.jobDesc.<id>`
|
||||
// - the `audit` action enum → `auditActions.<id>`; the `quota` action enum → `quotaActions.<id>`
|
||||
// and the other way round: a name left behind for an id the backend dropped is dead weight that reads
|
||||
// as a live setting to the next person (`plex_sync_interval_min` moved to the scheduler long ago).
|
||||
@@ -56,11 +56,18 @@ const choicesOf = (attr: string): string[] => {
|
||||
const choiceIds = specs
|
||||
.filter((s) => s.type === "choice")
|
||||
.flatMap((s) => choicesOf(s.attr).map((c) => `config.choices.${s.key}.${c}`));
|
||||
const schedulerPy = backend("scheduler.py");
|
||||
// The interval jobs, and the ad-hoc ones (`AD_HOC_JOBS`, a tuple of constants named beside it).
|
||||
const jobs = [
|
||||
...(backend("scheduler.py").split("JOB_INTERVALS")[1] ?? "")
|
||||
.split("}")[0]!
|
||||
.matchAll(/"(\w+)": settings\./g),
|
||||
].map((m) => m[1]!);
|
||||
...[
|
||||
...(schedulerPy.split("JOB_INTERVALS")[1] ?? "")
|
||||
.split("}")[0]!
|
||||
.matchAll(/"(\w+)": settings\./g),
|
||||
].map((m) => m[1]!),
|
||||
...[...(/^AD_HOC_JOBS = \(([^)]*)\)/m.exec(schedulerPy)?.[1] ?? "").matchAll(/\b[A-Z_]+\b/g)].map(
|
||||
(m) => new RegExp(`^${m[0]} = "(\\w+)"$`, "m").exec(schedulerPy)?.[1] ?? m[0],
|
||||
),
|
||||
];
|
||||
const enumValues = (file: string) =>
|
||||
[...backend(file).matchAll(/^\s+[A-Z_]+ = "(\w+)"$/gm)].map((m) => m[1]!);
|
||||
const auditActions = enumValues("audit.py");
|
||||
|
||||
@@ -56,10 +56,11 @@
|
||||
},
|
||||
"dot": {
|
||||
"running": "running now",
|
||||
"ok": "last run OK",
|
||||
"idle": "idle (no run recorded yet)",
|
||||
"error": "last run failed",
|
||||
"skipped": "skipped (sync paused)"
|
||||
"ok": "OK",
|
||||
"idle": "no run recorded yet",
|
||||
"error": "failed",
|
||||
"skipped": "skipped (sync paused)",
|
||||
"interrupted": "interrupted (the server restarted)"
|
||||
},
|
||||
"jobDesc": {
|
||||
"rss_poll": "Checks each channel's RSS feed for new uploads (free, no quota). If it stops, new videos only appear later when a backfill pass catches them, so your Feed lags behind YouTube.",
|
||||
@@ -78,7 +79,8 @@
|
||||
"audit_gc": "Prunes admin audit-log entries and YouTube quota history older than their retention windows (Configuration → Audit log, Quota). If it stops, both just keep growing — harmless but unbounded.",
|
||||
"log_gc": "Deletes stored log events older than the retention window (Configuration → Diagnostics). Runs hourly so nothing outlives the promised limit by more than an hour.",
|
||||
"tmdb_enrich": "Enriches the mirrored Plex catalogue from TMDB (keywords, crew, budget/revenue, TMDb score, recommendations, artwork), matched by each title's canonical id and served from the app's own database. Optional — a no-op until a TMDB API key is set. If it stops, new Plex titles just miss their TMDB extras until it runs again.",
|
||||
"people_enrich": "Makes the people in the Plex library local: fetches each person's biography from TMDB and caches their portrait, resized through Plex, the most-credited people first. A person page and a cast strip then open from the app's own data. The TMDB half does nothing without a TMDB API key, the portrait half nothing without Plex. If it stops, a person page fetches its own bio when opened, a moment later."
|
||||
"people_enrich": "Makes the people in the Plex library local: fetches each person's biography from TMDB and caches their portrait, resized through Plex, the most-credited people first. A person page and a cast strip then open from the app's own data. The TMDB half does nothing without a TMDB API key, the portrait half nothing without Plex. If it stops, a person page fetches its own bio when opened, a moment later.",
|
||||
"channel_backfill": "Turning on full history for a channel fetches its whole back-catalogue at once, as its own run. The Backfill job finishes what this one could not (a very large channel, low quota, or two of these already running)."
|
||||
},
|
||||
"jobs": {
|
||||
"rss_poll": "RSS poll (new uploads)",
|
||||
@@ -97,7 +99,8 @@
|
||||
"tmdb_enrich": "TMDB enrichment (Plex)",
|
||||
"audit_gc": "Audit-log and quota-history cleanup",
|
||||
"log_gc": "Log cleanup",
|
||||
"people_enrich": "People details and portraits (Plex)"
|
||||
"people_enrich": "People details and portraits (Plex)",
|
||||
"channel_backfill": "A channel's full history"
|
||||
},
|
||||
"queue": {
|
||||
"recentPending": "Channels to sync",
|
||||
@@ -199,5 +202,14 @@
|
||||
"transport": "unreachable",
|
||||
"apply": "could not be saved",
|
||||
"error": "unexpected error"
|
||||
}
|
||||
},
|
||||
"dotRun": "{{state}} · {{time}} · {{trigger}}",
|
||||
"trigger": {
|
||||
"scheduled": "on schedule",
|
||||
"manual": "started by an admin",
|
||||
"ad_hoc": "started by a user"
|
||||
},
|
||||
"interrupted": "interrupted: the server restarted during the run",
|
||||
"adHocTitle": "Ad-hoc runs",
|
||||
"adHocStarted": "started by a user"
|
||||
}
|
||||
|
||||
@@ -56,10 +56,11 @@
|
||||
},
|
||||
"dot": {
|
||||
"running": "most fut",
|
||||
"ok": "utolsó futás OK",
|
||||
"idle": "tétlen (még nincs rögzített futás)",
|
||||
"error": "utolsó futás hibázott",
|
||||
"skipped": "kihagyva (szinkron szüneteltetve)"
|
||||
"ok": "rendben",
|
||||
"idle": "még nincs rögzített futás",
|
||||
"error": "hibával ért véget",
|
||||
"skipped": "kihagyva (szinkron szüneteltetve)",
|
||||
"interrupted": "megszakadt (újraindult a szerver)"
|
||||
},
|
||||
"jobDesc": {
|
||||
"rss_poll": "Az egyes csatornák RSS-feedjét nézi új feltöltésekért (ingyenes, nem fogyaszt kvótát). Ha leáll, az új videók csak később jelennek meg, amikor a videók behúzása eléri őket — így a Feed lemarad a YouTube mögött.",
|
||||
@@ -78,7 +79,8 @@
|
||||
"audit_gc": "Törli a megőrzési időnél régebbi admin-naplóbejegyzéseket és YouTube-kvótaelőzményeket (Konfiguráció → Eseménynapló, Kvóta). Ha leáll, mindkettő csak nő — ártalmatlan, de korlátlanul.",
|
||||
"log_gc": "Törli a megőrzési időnél (Konfiguráció → Diagnosztika) régebbi tárolt naplóeseményeket. Óránként fut, így semmi sem marad meg egy óránál tovább az ígért határidőn túl.",
|
||||
"tmdb_enrich": "A tükrözött Plex-katalógust dúsítja a TMDB-ből (kulcsszavak, stáb, költségvetés/bevétel, TMDb-pontszám, ajánlások, artwork), a címek kanonikus azonosítója alapján párosítva, és az app saját adatbázisából szolgálja ki. Opcionális — TMDB API-kulcs nélkül nem csinál semmit. Ha leáll, az új Plex-címek csak a következő futásig maradnak TMDB-extrák nélkül.",
|
||||
"people_enrich": "Helyivé teszi a Plex-könyvtár szereplőit: a TMDB-ből letölti az életrajzukat, a portréjukat pedig a Plexszel kicsinyítve elmenti, a legtöbbet szereplőkkel kezdve. A személyoldal és a szereplősáv így az app saját adataiból nyílik meg. A TMDB-rész TMDB API-kulcs nélkül nem csinál semmit, a portrérész Plex nélkül. Ha leáll, a személyoldal megnyitáskor maga kéri le az életrajzot, egy pillanattal később."
|
||||
"people_enrich": "Helyivé teszi a Plex-könyvtár szereplőit: a TMDB-ből letölti az életrajzukat, a portréjukat pedig a Plexszel kicsinyítve elmenti, a legtöbbet szereplőkkel kezdve. A személyoldal és a szereplősáv így az app saját adataiból nyílik meg. A TMDB-rész TMDB API-kulcs nélkül nem csinál semmit, a portrérész Plex nélkül. Ha leáll, a személyoldal megnyitáskor maga kéri le az életrajzot, egy pillanattal később.",
|
||||
"channel_backfill": "Ha egy csatornánál bekapcsolod a teljes előzményt, a teljes korábbi kínálata egyszerre, saját futásként töltődik le. Amit ez nem tud befejezni (egy nagyon nagy csatorna, kevés kvóta, vagy ha már két ilyen fut), azt a „Videók behúzása” feladat fejezi be."
|
||||
},
|
||||
"jobs": {
|
||||
"rss_poll": "RSS-lekérdezés (új feltöltések)",
|
||||
@@ -97,7 +99,8 @@
|
||||
"tmdb_enrich": "TMDB-dúsítás (Plex)",
|
||||
"audit_gc": "Eseménynapló- és kvótaelőzmény-takarítás",
|
||||
"log_gc": "Naplótakarítás",
|
||||
"people_enrich": "Személyadatok és portrék (Plex)"
|
||||
"people_enrich": "Személyadatok és portrék (Plex)",
|
||||
"channel_backfill": "Egy csatorna teljes előzménye"
|
||||
},
|
||||
"queue": {
|
||||
"recentPending": "Szinkronra váró csatorna",
|
||||
@@ -199,5 +202,14 @@
|
||||
"transport": "nem érhető el",
|
||||
"apply": "nem sikerült menteni",
|
||||
"error": "váratlan hiba"
|
||||
}
|
||||
},
|
||||
"dotRun": "{{state}} · {{time}} · {{trigger}}",
|
||||
"trigger": {
|
||||
"scheduled": "ütemezve",
|
||||
"manual": "admin indította",
|
||||
"ad_hoc": "felhasználó indította"
|
||||
},
|
||||
"interrupted": "megszakadt: a futás közben újraindult a szerver",
|
||||
"adHocTitle": "Alkalmi futások",
|
||||
"adHocStarted": "felhasználó indítja"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user