Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions app/core/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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()
Expand All @@ -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")))
Expand Down
12 changes: 12 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions static/css/daw.css
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
25 changes: 25 additions & 0 deletions static/js/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -2063,6 +2075,7 @@ function openLibraryEditor() {
<button class="settings-tab active" type="button" data-tab="general" role="tab">General</button>
<button class="settings-tab" type="button" data-tab="network" role="tab">Network</button>
<button class="settings-tab" type="button" data-tab="export" role="tab">Export</button>
<button class="settings-tab" type="button" data-tab="registry" role="tab">Registry</button>
</div>
<div class="settings-pane" data-pane="general">
<div class="settings-section">
Expand Down Expand Up @@ -2142,6 +2155,16 @@ function openLibraryEditor() {
</div>
</div>
</div>
<div class="settings-pane hidden" data-pane="registry">
<div class="settings-row">
<div class="settings-row-text">
<div class="settings-row-title">Job registry</div>
<div class="settings-row-desc">Read-only view of <code>registry.json</code> — the persisted list of completed jobs on disk.</div>
</div>
<button class="settings-registry-refresh" type="button">Refresh</button>
</div>
<textarea class="settings-registry-view" readonly spellcheck="false" aria-label="Job registry (read only)">Loading…</textarea>
</div>
<div class="settings-foot">
<button class="settings-done" type="button">Done</button>
</div>
Expand All @@ -2155,8 +2178,10 @@ function openLibraryEditor() {
const name = tab.dataset.tab;
overlay.querySelectorAll(".settings-tab").forEach((t) => t.classList.toggle("active", t === tab));
overlay.querySelectorAll(".settings-pane").forEach((p) => p.classList.toggle("hidden", p.dataset.pane !== name));
if (name === "registry") loadRegistryView(overlay);
});
});
overlay.querySelector(".settings-registry-refresh")?.addEventListener("click", () => loadRegistryView(overlay));

overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) closeLibraryEditor(); });
// (status summary is filled in after the overlay is in the DOM, below)
Expand Down