diff --git a/app/core/registry.py b/app/core/registry.py index 39e981e..48d84c6 100644 --- a/app/core/registry.py +++ b/app/core/registry.py @@ -70,6 +70,10 @@ def _migrate(data: dict) -> dict: return data +def registry_path(jobs_dir: Path) -> Path: + return jobs_dir / _REGISTRY_FILE + + def persist(jobs_dir: Path) -> None: """Persist terminal jobs so completed library entries survive restarts. @@ -84,7 +88,7 @@ def persist(jobs_dir: Path) -> None: except OSError: logger.warning("cannot create jobs dir %s; skipping persist", jobs_dir, exc_info=True) return - path = jobs_dir / _REGISTRY_FILE + path = registry_path(jobs_dir) with _lock: records = [ job.to_record() @@ -105,7 +109,7 @@ def persist(jobs_dir: Path) -> None: def restore(jobs_dir: Path) -> None: """Load persisted jobs and recover completed orphan jobs from disk.""" jobs_dir.mkdir(parents=True, exist_ok=True) - path = jobs_dir / _REGISTRY_FILE + path = registry_path(jobs_dir) if path.is_file(): try: data = _migrate(json.loads(path.read_text(encoding="utf-8"))) diff --git a/app/main.py b/app/main.py index e0c9c4c..1371307 100644 --- a/app/main.py +++ b/app/main.py @@ -28,6 +28,7 @@ ensure_runtime_dirs, ) from app.core.logging_setup import configure_logging +from app.core.registry import registry_path from app.core.registry import restore as restore_registry from app.core.settings import ( get_allow_network, @@ -309,6 +310,17 @@ async def update_settings(request: Request) -> dict[str, object]: return _settings_payload() +@app.get("/api/registry", tags=["settings"]) +def get_registry_raw() -> PlainTextResponse: + """Read-only view of the persisted job registry (Settings -> Registry).""" + path = registry_path(JOBS_DIR) + if not path.is_file(): + return PlainTextResponse( + '{\n "version": 1,\n "jobs": []\n}\n', media_type="application/json" + ) + return PlainTextResponse(path.read_text(encoding="utf-8"), media_type="application/json") + + # Content-Security-Policy. Defense-in-depth so an injected string in the webview # can't run script (and, in the desktop app, reach the exposed Tauri IPC) — #171. # script-src has no 'unsafe-inline'/'eval': all JS is same-origin modules and the diff --git a/static/css/daw.css b/static/css/daw.css index c7d354d..52f62fe 100644 --- a/static/css/daw.css +++ b/static/css/daw.css @@ -835,6 +835,17 @@ input, textarea { font-family: inherit; } .settings-server-note { font-size: 10.5px; color: var(--muted); margin: 0 0 12px; line-height: 1.5; } .settings-subhead { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); font-weight: 600; margin: 4px 0 7px; } +/* Settings → registry (read-only viewer) */ +.settings-registry-refresh { flex-shrink: 0; min-height: 28px; border-radius: 6px; border: 1px solid var(--border-strong); background: rgba(10,17,24,0.6); color: var(--fg-2); font-family: var(--font-mono); font-size: 11px; padding: 0 12px; cursor: pointer; } +.settings-registry-refresh:hover { color: var(--fg); border-color: rgba(148,163,184,0.4); } +.settings-registry-view { + flex: 1; min-height: 0; margin-top: 10px; resize: none; + background: rgba(10,17,24,0.5); border: 1px solid var(--border); border-radius: 7px; + color: var(--fg-2); font-family: var(--font-mono); font-size: 11px; line-height: 1.5; + padding: 10px 12px; white-space: pre; overflow: auto; +} +.settings-registry-view:focus { outline: none; } + .library-editor-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 11px; } .library-editor-status { font-size: 10.5px; color: var(--muted); } .library-editor-status.out-of-sync { color: var(--danger); font-weight: 600; } diff --git a/static/js/catalog.js b/static/js/catalog.js index 8224963..ae714ee 100644 --- a/static/js/catalog.js +++ b/static/js/catalog.js @@ -2045,6 +2045,18 @@ async function wireNetworkSetting(overlay) { }); } +async function loadRegistryView(overlay) { + const view = overlay.querySelector(".settings-registry-view"); + if (!view) return; + view.value = "Loading…"; + try { + const r = await fetch("/api/registry", { cache: "no-store" }); + view.value = r.ok ? await r.text() : `Failed to load registry (status ${r.status}).`; + } catch { + view.value = "Failed to load registry — check your connection."; + } +} + function openLibraryEditor() { closeFolderEditor(); closeLibraryEditor(); @@ -2063,6 +2075,7 @@ function openLibraryEditor() { +