Files
manage/backend/routers/files.py
T
alex 3c432473e5 Add FastAPI backend and React frontend subprojects
Backend:
- FastAPI app with 17 REST endpoints covering dashboard, monitoring,
  media index, file browser, and jobs
- Reuses existing clients/domain/services unchanged
- pydantic-settings config, dependency injection, CORS setup
- Auto-generated OpenAPI docs at /docs

Frontend:
- Vite + React + TypeScript SPA
- @tanstack/react-query for data fetching with polling
- ag-grid-react for media table and file browser
- recharts for monitoring charts
- Tailwind CSS styling
- 4 pages: Dashboard, Monitoring, Media, File Browser
- Typed API client matching all backend endpoints

Also:
- docs/MIGRATION_PLAN.md with full architecture plan
- Updated .gitignore for both subprojects
- Streamlit app preserved for now (can coexist)
2026-04-30 21:40:18 +02:00

68 lines
2.1 KiB
Python

"""Files router — directory listing, ffprobe, stat, path resolution."""
from __future__ import annotations
import json
from typing import Any
from fastapi import APIRouter, Depends, Query, HTTPException
from dependencies import get_ssh_client
from clients.ssh import RemoteSSHClient
from config import get_settings
from path_utils import resolve_remote_media_path
router = APIRouter(prefix="/api/files", tags=["files"])
@router.get("/list")
def list_directory(
path: str = Query(..., description="Remote directory path to list"),
ssh: RemoteSSHClient = Depends(get_ssh_client),
) -> dict[str, Any]:
"""List a remote directory."""
result = ssh.list_dir(path)
if result.exit_status != 0:
raise HTTPException(status_code=400, detail=result.stderr or result.stdout or "Failed to list directory")
entries = json.loads(result.stdout)
return {
"path": path,
"entries": entries,
"count": len(entries),
}
@router.get("/ffprobe")
def get_ffprobe(
path: str = Query(..., description="Remote file path to probe"),
ssh: RemoteSSHClient = Depends(get_ssh_client),
) -> dict[str, Any]:
"""Run ffprobe on a remote file and return parsed JSON."""
try:
data = ssh.ffprobe_json(path)
except RuntimeError as exc:
raise HTTPException(status_code=400, detail=str(exc))
return data
@router.get("/stat")
def get_stat(
path: str = Query(..., description="Remote path to stat"),
ssh: RemoteSSHClient = Depends(get_ssh_client),
) -> dict[str, str]:
"""Run stat on a remote path."""
result = ssh.stat_path(path)
if result.exit_status != 0:
raise HTTPException(status_code=400, detail=result.stderr or result.stdout or "stat failed")
return {"path": path, "output": result.stdout}
@router.get("/resolve-path")
def resolve_path(
path: str = Query(..., description="Jellyfin path to resolve to SSH path"),
) -> dict[str, str]:
"""Resolve a Jellyfin path to its SSH-visible equivalent."""
settings = get_settings()
resolved = resolve_remote_media_path(path, settings.media_root, settings.path_prefix)
return {"original": path, "resolved": resolved}