Fix recovery layout and interactive assets

This commit is contained in:
Philip Guzman
2026-07-01 16:03:28 -07:00
parent da72419ce9
commit 139822f8a5
4 changed files with 31 additions and 12 deletions
+4 -1
View File
@@ -6,6 +6,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from importlib import resources from importlib import resources
from pathlib import Path from pathlib import Path
from typing import Iterable, Optional from typing import Iterable, Optional
from urllib.parse import urlsplit
from serato_doctor.crate_parser import load_library_crates from serato_doctor.crate_parser import load_library_crates
from serato_doctor.database_parser import parse_database from serato_doctor.database_parser import parse_database
@@ -31,6 +32,7 @@ STATIC_FILES = {
"/": ("index.html", "text/html; charset=utf-8"), "/": ("index.html", "text/html; charset=utf-8"),
"/app.css": ("app.css", "text/css; charset=utf-8"), "/app.css": ("app.css", "text/css; charset=utf-8"),
"/recovery.css": ("recovery.css", "text/css; charset=utf-8"), "/recovery.css": ("recovery.css", "text/css; charset=utf-8"),
"/layout-fixes.css": ("layout-fixes.css", "text/css; charset=utf-8"),
"/app.js": ("app.js", "text/javascript; charset=utf-8"), "/app.js": ("app.js", "text/javascript; charset=utf-8"),
} }
@@ -296,7 +298,7 @@ def backup_history(serato: Path) -> dict:
class SeratoDoctorHandler(BaseHTTPRequestHandler): class SeratoDoctorHandler(BaseHTTPRequestHandler):
def do_GET(self) -> None: def do_GET(self) -> None:
asset = STATIC_FILES.get(self.path) asset = STATIC_FILES.get(urlsplit(self.path).path)
if asset is None: if asset is None:
self._json_response(404, {"error": "Not found"}) self._json_response(404, {"error": "Not found"})
return return
@@ -308,6 +310,7 @@ class SeratoDoctorHandler(BaseHTTPRequestHandler):
) )
self.send_response(200) self.send_response(200)
self.send_header("Content-Type", content_type) self.send_header("Content-Type", content_type)
self.send_header("Cache-Control", "no-store")
self.send_header("Content-Length", str(len(content))) self.send_header("Content-Length", str(len(content)))
self.end_headers() self.end_headers()
self.wfile.write(content) self.wfile.write(content)
+11 -10
View File
@@ -5,8 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark"> <meta name="color-scheme" content="dark">
<title>Serato Doctor</title> <title>Serato Doctor</title>
<link rel="stylesheet" href="/app.css"> <link rel="stylesheet" href="/app.css?v=3">
<link rel="stylesheet" href="/recovery.css"> <link rel="stylesheet" href="/recovery.css?v=3">
<link rel="stylesheet" href="/layout-fixes.css?v=3">
</head> </head>
<body> <body>
<div class="ambient ambient-one"></div> <div class="ambient ambient-one"></div>
@@ -57,13 +58,6 @@
<div id="error-message" class="error" role="alert" hidden></div> <div id="error-message" class="error" role="alert" hidden></div>
</section> </section>
<section id="recovery" class="recovery-panel panel">
<div class="section-heading"><div><p class="eyebrow">Safety net</p><h2>Backup recovery</h2></div><button id="load-backups" type="button">Check backups</button></div>
<p>Review every repair snapshot saved for this Serato library. Older backups remain available after restarting Serato Doctor.</p>
<div id="backup-summary" class="backup-summary">Enter your Serato folder above, then check backups.</div>
<div id="backup-list" class="backup-list"></div>
</section>
<section id="dashboard" class="results" aria-live="polite" hidden> <section id="dashboard" class="results" aria-live="polite" hidden>
<div class="section-heading"><div><p class="eyebrow">Latest analysis</p><h2>Library health</h2></div><span id="analysis-time"></span></div> <div class="section-heading"><div><p class="eyebrow">Latest analysis</p><h2>Library health</h2></div><span id="analysis-time"></span></div>
<div class="hero-grid"> <div class="hero-grid">
@@ -115,8 +109,15 @@
<div id="repair-message" class="repair-message" role="status"></div> <div id="repair-message" class="repair-message" role="status"></div>
</div> </div>
</section> </section>
<section id="recovery" class="recovery-panel panel">
<div class="section-heading"><div><p class="eyebrow">Safety net</p><h2>Backup recovery</h2></div><button id="load-backups" type="button">Check backups</button></div>
<p>Repair backups appear here automatically before any duplicate is consolidated. Review saved snapshots, disk usage, and restore history.</p>
<div id="backup-summary" class="backup-summary">Analyze your library, then choose a duplicate to preview its automatic backup.</div>
<div id="backup-list" class="backup-list"></div>
</section>
</main> </main>
</div> </div>
<script src="/app.js" defer></script> <script src="/app.js?v=3" defer></script>
</body> </body>
</html> </html>
+9
View File
@@ -0,0 +1,9 @@
.recovery-panel {
margin-top: 42px;
}
@media (max-width: 680px) {
.recovery-panel {
margin-top: 28px;
}
}
+7 -1
View File
@@ -40,7 +40,13 @@ def test_web_analysis_rejects_missing_folders(tmp_path):
def test_web_static_assets_are_declared_and_packaged(): def test_web_static_assets_are_declared_and_packaged():
asset_root = Path(__file__).parents[1] / "serato_doctor" / "webui" asset_root = Path(__file__).parents[1] / "serato_doctor" / "webui"
assert set(STATIC_FILES) == {"/", "/app.css", "/recovery.css", "/app.js"} assert set(STATIC_FILES) == {
"/",
"/app.css",
"/recovery.css",
"/layout-fixes.css",
"/app.js",
}
assert all((asset_root / filename).is_file() for filename, _ in STATIC_FILES.values()) assert all((asset_root / filename).is_file() for filename, _ in STATIC_FILES.values())
html = (asset_root / "index.html").read_text(encoding="utf-8") html = (asset_root / "index.html").read_text(encoding="utf-8")
assert "Missing tracks in Serato" in html assert "Missing tracks in Serato" in html