feat(config): job intervals live in sysconfig, every setting says when it applies, the unread SponsorBlock default goes

An interval override was a row of its own table beside the one store every other admin knob uses; it
is now the `job_interval_<job>` sysconfig key (migration 0110 copies them over and drops the table).
The Scheduler page stays its one editor: the Configuration page and the setup wizard neither list nor
write those keys, since a write there would skip the reschedule.

Each ConfigSpec now states `applies` (required, no default). Every key is `live`, held by a scan that
finds no read at import time; a `restart` key is refused until the page can say so. A second scan
requires a reader for every key on the Configuration page — `sponsorblock_default` had none since it
was added, so it leaves the registry, the translations and the store.
This commit is contained in:
2026-10-18 02:23:15 +02:00
parent 3bab2e7dc7
commit d13483a593
15 changed files with 529 additions and 136 deletions
@@ -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` and `scheduler.AD_HOC_JOBS` → `scheduler.jobs.<id>` + `scheduler.jobDesc.<id>`
// - `sysconfig.JOB_INTERVAL_DEFAULTS` 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).
@@ -57,12 +57,13 @@ 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).
// The interval jobs (sysconfig's `JOB_INTERVAL_DEFAULTS`: job → its default's setting), and the
// ad-hoc ones (`AD_HOC_JOBS` in scheduler.py, a tuple of constants named beside it).
const jobs = [
...[
...(schedulerPy.split("JOB_INTERVALS")[1] ?? "")
...(backend("sysconfig.py").split("JOB_INTERVAL_DEFAULTS: dict[str, str] = {")[1] ?? "")
.split("}")[0]!
.matchAll(/"(\w+)": settings\./g),
.matchAll(/"(\w+)": "\w+",/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],
-4
View File
@@ -162,10 +162,6 @@
"label": "Worker concurrency",
"hint": "How many downloads the worker processes in parallel."
},
"sponsorblock_default": {
"label": "SponsorBlock by default",
"hint": "Enable SponsorBlock (skip sponsor segments) for newly created custom download profiles."
},
"audit_retention_days": {
"label": "Retention (days)",
"hint": "How long admin audit-log entries are kept before the audit-log cleanup job prunes them. 0 = keep forever. Default 180."
-4
View File
@@ -162,10 +162,6 @@
"label": "Párhuzamos letöltések",
"hint": "Hány letöltést dolgoz fel egyszerre a letöltő folyamat."
},
"sponsorblock_default": {
"label": "SponsorBlock alapból",
"hint": "SponsorBlock (szponzorszakaszok átugrása) bekapcsolása az újonnan létrehozott egyéni letöltési profilokhoz."
},
"audit_retention_days": {
"label": "Megőrzés (nap)",
"hint": "Meddig maradnak meg az admin-naplóbejegyzések, mielőtt az eseménynapló-takarító feladat törli őket. 0 = örökre. Alapérték 180."