Universal-Sprint: Wizard, UI-Mounts, Encoder-Erkennung, Task-Split, README
Ampel / ampel (push) Successful in 29s
Ampel / ampel (push) Successful in 29s
Commander-Ziel: All-in-one, universell, weitergebbar.
- First-Run-Wizard: startet automatisch bei neuer Installation (Keys,
Verarbeitung, erkannte Hardware); /setup + /setup/complete
- Data-Mounts via UI: Einstellungen -> Speicherziele haengt NFS/SMB direkt
ein (mounts.py, CAP_SYS_ADMIN + rshared-Propagation, Auto-Remount beim
Start, CIFS-Creds via Datei statt Kommandozeile); nfs-common/cifs-utils
im api-Image
- Encoder-Erkennung: jeder Worker meldet beim Start ehrlich seine
Faehigkeiten (caps.py -> workers-Tabelle), GET /capabilities, Anzeige
in Wizard + Verarbeitung-Tab
- Task-Split: transcode_files als eigener Task auf Queue "transcode"
(Basis fuer optionale Remote-GPU-Worker, deploy/remote-transcode-worker.yml
EXPERIMENTELL) + POST /jobs/{id}/retry-transcode + UI-Knopf
"Neu komprimieren" bei fehlgeschlagenen Jobs
- API-Keys aus der DB: Settings-UI/Wizard ueberstimmen Env — vorher waren
die Key-Felder im UI reine Dekoration (Clients lasen nur Env)
- README komplett neu: generischer Schnellstart, Laufwerk-Override via
docker-compose.override.yml, Architektur, Env-Tabelle
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,63 @@ settings_table = Table(
|
||||
Column("value", Text),
|
||||
)
|
||||
|
||||
workers = Table(
|
||||
"workers",
|
||||
metadata,
|
||||
Column("name", String(128), primary_key=True),
|
||||
Column("encoders", Text),
|
||||
Column("last_seen", DateTime(timezone=True)),
|
||||
)
|
||||
|
||||
storage_mounts = Table(
|
||||
"storage_mounts",
|
||||
metadata,
|
||||
Column("name", String(64), primary_key=True),
|
||||
Column("typ", String(8)), # nfs | cifs
|
||||
Column("quelle", String(255)), # host:/export bzw. //host/share
|
||||
Column("optionen", String(255)),
|
||||
Column("username", String(128)),
|
||||
Column("passwort", String(255)), # Klartext — Heimnetz-Kompromiss, siehe README
|
||||
)
|
||||
|
||||
|
||||
def list_workers() -> list:
|
||||
import json
|
||||
|
||||
with engine.connect() as conn:
|
||||
zeilen = conn.execute(select(workers)).mappings().all()
|
||||
ergebnis = []
|
||||
for z in zeilen:
|
||||
eintrag = dict(z)
|
||||
try:
|
||||
eintrag["encoders"] = json.loads(eintrag.get("encoders") or "[]")
|
||||
except ValueError:
|
||||
eintrag["encoders"] = []
|
||||
if eintrag.get("last_seen"):
|
||||
eintrag["last_seen"] = eintrag["last_seen"].isoformat()
|
||||
ergebnis.append(eintrag)
|
||||
return ergebnis
|
||||
|
||||
|
||||
def list_mounts() -> list:
|
||||
with engine.connect() as conn:
|
||||
return [dict(z) for z in conn.execute(select(storage_mounts)).mappings().all()]
|
||||
|
||||
|
||||
def save_mount(name: str, typ: str, quelle: str, optionen: str, username: str, passwort: str) -> None:
|
||||
with engine.begin() as conn:
|
||||
conn.execute(
|
||||
storage_mounts.insert().values(
|
||||
name=name, typ=typ, quelle=quelle,
|
||||
optionen=optionen, username=username, passwort=passwort,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_mount(name: str) -> None:
|
||||
with engine.begin() as conn:
|
||||
conn.execute(storage_mounts.delete().where(storage_mounts.c.name == name))
|
||||
|
||||
|
||||
def utcnow() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
@@ -97,6 +154,14 @@ def insert_job(
|
||||
)
|
||||
|
||||
|
||||
def get_job(job_id: str) -> dict:
|
||||
with engine.connect() as conn:
|
||||
zeile = conn.execute(
|
||||
select(jobs).where(jobs.c.id == job_id)
|
||||
).mappings().first()
|
||||
return dict(zeile) if zeile else None
|
||||
|
||||
|
||||
def list_jobs(limit: int = 100) -> list:
|
||||
with engine.connect() as conn:
|
||||
zeilen = conn.execute(
|
||||
|
||||
Reference in New Issue
Block a user