- Add VERSION file (1.1.0) at repo root
- core/constants.py: expose __version__ read from VERSION file
- routes/admin.py: GET /api/version endpoint returns {version}
- Settings → About: display "v1.1.0" next to app name via /api/version fetch
- CHANGELOG.md: full rewrite following Keep a Changelog + Semantic Versioning
- [Unreleased] staging section at top
- [1.1.0] 2026-05-29 — security, perf, refactor, UX changes from this session
- [1.0.0] 2026-05-28 — all pre-session features documented
- Compare links at bottom pointing to GitHub
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
58 lines
1.5 KiB
Python
58 lines
1.5 KiB
Python
"""Admin routes: index, favicon, browse-dirs, robots."""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from fastapi.responses import FileResponse, PlainTextResponse, Response
|
|
|
|
from core.constants import STATIC_DIR, __version__
|
|
|
|
router = APIRouter()
|
|
|
|
_BROWSE_BLOCKED: frozenset[str] = frozenset({
|
|
"/proc", "/sys", "/dev", "/run", "/boot",
|
|
})
|
|
|
|
|
|
@router.get("/")
|
|
async def index():
|
|
return FileResponse(
|
|
STATIC_DIR / "index.html",
|
|
headers={"Cache-Control": "no-store, max-age=0"},
|
|
)
|
|
|
|
|
|
@router.get("/favicon.ico")
|
|
async def favicon():
|
|
return Response(status_code=204)
|
|
|
|
|
|
@router.get("/api/browse-dirs")
|
|
async def browse_dirs(path: str = "/"):
|
|
p = Path(path).resolve()
|
|
p_str = str(p)
|
|
if any(p_str == b or p_str.startswith(b + "/") for b in _BROWSE_BLOCKED):
|
|
raise HTTPException(403, "Access to this path is not permitted")
|
|
if not p.is_dir():
|
|
raise HTTPException(404, "Not a directory")
|
|
try:
|
|
entries = sorted(
|
|
[d.name for d in p.iterdir() if d.is_dir() and not d.name.startswith(".")],
|
|
key=str.lower,
|
|
)
|
|
except PermissionError:
|
|
raise HTTPException(403, "Permission denied")
|
|
parent = str(p.parent) if p.parent != p else None
|
|
return {"path": str(p), "parent": parent, "dirs": entries}
|
|
|
|
|
|
@router.get("/api/version")
|
|
async def get_version():
|
|
return {"version": __version__}
|
|
|
|
|
|
@router.get("/robots.txt", response_class=PlainTextResponse)
|
|
async def robots_txt():
|
|
return "User-agent: *\nDisallow: /"
|