Fix recovery layout and interactive assets
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.recovery-panel {
|
||||||
|
margin-top: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.recovery-panel {
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-1
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user