3c432473e5
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)
68 lines
2.1 KiB
Python
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}
|