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)
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
# Migration Plan: Streamlit → FastAPI + React
|
||||
|
||||
## Overview
|
||||
|
||||
Split the current Streamlit monolith into:
|
||||
|
||||
- **Backend:** FastAPI (Python) serving a REST API
|
||||
- **Frontend:** React SPA (TypeScript) consuming that API
|
||||
|
||||
The existing `clients/`, `domain/`, `services/`, `jobs.py`, `utils.py`, and `config.py` are already UI-independent and transfer directly to the FastAPI backend with minimal changes.
|
||||
|
||||
---
|
||||
|
||||
## Current Architecture
|
||||
|
||||
```
|
||||
src/media_library_viewer/
|
||||
├── app.py # Streamlit orchestration + caching (DELETE)
|
||||
├── config.py # Env/dotenv config
|
||||
├── jobs.py # SSH job templates
|
||||
├── utils.py # Formatting helpers
|
||||
├── clients/
|
||||
│ ├── jellyfin.py # Jellyfin HTTP client
|
||||
│ ├── resources.py # Remote resource collector
|
||||
│ └── ssh.py # SSH command execution
|
||||
├── domain/
|
||||
│ └── media.py # Media normalization
|
||||
├── services/
|
||||
│ └── media_index.py # SQLite media index
|
||||
└── ui/ # Streamlit rendering (DELETE)
|
||||
├── dashboard.py
|
||||
├── file_browser.py
|
||||
├── media.py
|
||||
└── preview.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Target Architecture
|
||||
|
||||
```
|
||||
repo/
|
||||
├── backend/
|
||||
│ ├── main.py # FastAPI app + CORS + lifespan
|
||||
│ ├── config.py # pydantic-settings based config
|
||||
│ ├── dependencies.py # DI for SSH/Jellyfin clients
|
||||
│ ├── routers/
|
||||
│ │ ├── dashboard.py # /api/dashboard/*
|
||||
│ │ ├── monitoring.py # /api/monitoring/*
|
||||
│ │ ├── media.py # /api/media/*
|
||||
│ │ ├── files.py # /api/files/*
|
||||
│ │ └── jobs.py # /api/jobs/*
|
||||
│ ├── clients/ # Copied unchanged
|
||||
│ │ ├── jellyfin.py
|
||||
│ │ ├── resources.py
|
||||
│ │ └── ssh.py
|
||||
│ ├── domain/ # Copied unchanged
|
||||
│ │ └── media.py
|
||||
│ ├── services/ # Copied unchanged
|
||||
│ │ └── media_index.py
|
||||
│ ├── jobs.py # Copied unchanged
|
||||
│ ├── utils.py # Copied unchanged
|
||||
│ └── pyproject.toml
|
||||
│
|
||||
├── frontend/
|
||||
│ ├── package.json
|
||||
│ ├── vite.config.ts
|
||||
│ ├── tsconfig.json
|
||||
│ └── src/
|
||||
│ ├── main.tsx
|
||||
│ ├── App.tsx
|
||||
│ ├── api/
|
||||
│ │ └── client.ts # Typed fetch wrappers
|
||||
│ ├── hooks/
|
||||
│ │ ├── useDashboard.ts
|
||||
│ │ ├── useMonitoring.ts
|
||||
│ │ ├── useMedia.ts
|
||||
│ │ └── useFiles.ts
|
||||
│ ├── pages/
|
||||
│ │ ├── Dashboard.tsx
|
||||
│ │ ├── Monitoring.tsx
|
||||
│ │ ├── Media.tsx
|
||||
│ │ └── FileBrowser.tsx
|
||||
│ ├── components/
|
||||
│ │ ├── NowPlaying.tsx
|
||||
│ │ ├── MetricCard.tsx
|
||||
│ │ ├── LibraryOverview.tsx
|
||||
│ │ ├── MonitoringCharts.tsx
|
||||
│ │ ├── MediaTable.tsx
|
||||
│ │ ├── FileListing.tsx
|
||||
│ │ └── FfprobePreview.tsx
|
||||
│ └── types/
|
||||
│ └── index.ts
|
||||
│
|
||||
├── docker-compose.yml # Optional unified deployment
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FastAPI Endpoints
|
||||
|
||||
| Endpoint | Method | Source | Description |
|
||||
| ------------------------------- | ------ | ---------------------------------------- | -------------------------------- |
|
||||
| `/api/dashboard/counts` | GET | `jellyfin.media_counts()` | Movie/series/episode totals |
|
||||
| `/api/dashboard/libraries` | GET | `jellyfin.library_item_counts()` | Per-library breakdown |
|
||||
| `/api/dashboard/now-playing` | GET | `jellyfin.active_sessions()` | Active sessions + transcode info |
|
||||
| `/api/monitoring/status` | GET | `resources.resource_collector_status()` | Collector running? |
|
||||
| `/api/monitoring/metrics` | GET | `resources.read_resource_metrics()` | Last-hour JSONL samples |
|
||||
| `/api/monitoring/disk` | GET | `resources.disk_space()` | df for media root |
|
||||
| `/api/monitoring/start` | POST | `resources.start_resource_collector()` | Start collector |
|
||||
| `/api/monitoring/stop` | POST | `resources.stop_resource_collector()` | Stop collector |
|
||||
| `/api/monitoring/restart` | POST | `resources.restart_resource_collector()` | Restart collector |
|
||||
| `/api/media/status` | GET | `MediaIndex.status()` | Index exists/count/age |
|
||||
| `/api/media/build` | POST | `build_media_index()` | Rebuild index |
|
||||
| `/api/media/query` | GET | `MediaIndex.query()` | Filtered/sorted/paged query |
|
||||
| `/api/files/list?path=` | GET | `ssh.list_dir()` | Directory listing |
|
||||
| `/api/files/ffprobe?path=` | GET | `ssh.ffprobe_json()` | ffprobe JSON |
|
||||
| `/api/files/stat?path=` | GET | `ssh.stat_path()` | stat output |
|
||||
| `/api/files/resolve-path?path=` | GET | `resolve_remote_media_path()` | Jellyfin→SSH path mapping |
|
||||
| `/api/jobs/templates` | GET | `JOB_TEMPLATES` | Available job list |
|
||||
| `/api/jobs/run` | POST | `run_job()` | Execute a job |
|
||||
|
||||
---
|
||||
|
||||
## React Frontend
|
||||
|
||||
### Tech Stack
|
||||
|
||||
- **Vite** + React 18+ + TypeScript
|
||||
- **@tanstack/react-query** — data fetching with polling
|
||||
- **ag-grid-react** — media table and file browser (same grid lib)
|
||||
- **recharts** — monitoring line charts
|
||||
- **react-router** — page navigation
|
||||
- **tailwindcss** + **shadcn/ui** — styling
|
||||
|
||||
### Page → Component Mapping
|
||||
|
||||
| Page | Components | Polling |
|
||||
| ------------ | --------------------------------------------------------- | -------------------------------- |
|
||||
| Dashboard | NowPlaying, MetricCard (server overview), LibraryOverview | 15s (now-playing), 30s (metrics) |
|
||||
| Monitoring | MonitoringCharts, MetricCard, CollectorControls | 15s |
|
||||
| Media | MediaTable (AG Grid), filter/sort/page controls | on-demand |
|
||||
| File Browser | FileListing (AG Grid), FfprobePreview, JobRunner | on-demand |
|
||||
|
||||
### Key Interactions
|
||||
|
||||
- Media row click → updates client-side file browser path state (no API call)
|
||||
- File browser directory click → fetches `/api/files/list?path=...`
|
||||
- File browser file click → fetches `/api/files/ffprobe?path=...`
|
||||
- Path input Enter → navigates directory
|
||||
- Monitoring charts → recharts line chart from `/api/monitoring/metrics`
|
||||
|
||||
---
|
||||
|
||||
## What Transfers Unchanged (~1,350 lines)
|
||||
|
||||
| File | Lines | Notes |
|
||||
| ------------------------- | ----- | ---------------------------- |
|
||||
| `clients/jellyfin.py` | 167 | Remove unused methods if any |
|
||||
| `clients/ssh.py` | 146 | No changes |
|
||||
| `clients/resources.py` | 316 | No changes |
|
||||
| `domain/media.py` | 161 | No changes |
|
||||
| `services/media_index.py` | 278 | No changes |
|
||||
| `jobs.py` | 59 | No changes |
|
||||
| `utils.py` | 227 | No changes |
|
||||
|
||||
---
|
||||
|
||||
## What Gets Deleted
|
||||
|
||||
- `src/media_library_viewer/ui/` (all Streamlit rendering)
|
||||
- `src/media_library_viewer/app.py` (Streamlit orchestration)
|
||||
- Root `app.py` (Streamlit launcher)
|
||||
- `streamlit` and `streamlit-aggrid` dependencies
|
||||
|
||||
---
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### Step 1: Backend scaffold
|
||||
|
||||
- Create `backend/` with FastAPI, pydantic-settings config, DI
|
||||
- Copy `clients/`, `domain/`, `services/`, `jobs.py`, `utils.py`
|
||||
- Implement routers wrapping existing functions
|
||||
- Test with curl
|
||||
|
||||
### Step 2: Frontend scaffold
|
||||
|
||||
- Create `frontend/` with Vite + React + TypeScript
|
||||
- API client with typed wrappers
|
||||
- React Query provider + hooks
|
||||
- Router with 4 pages
|
||||
|
||||
### Step 3: Build pages (one at a time)
|
||||
|
||||
1. Dashboard (simplest — just fetches and displays)
|
||||
2. Monitoring (charts + controls)
|
||||
3. Media (AG Grid + filters)
|
||||
4. File Browser (AG Grid + ffprobe + jobs)
|
||||
|
||||
### Step 4: Validate feature parity
|
||||
|
||||
- Compare behavior side-by-side with Streamlit
|
||||
- Remove Streamlit code
|
||||
|
||||
---
|
||||
|
||||
## Effort Estimate
|
||||
|
||||
| Component | Effort |
|
||||
| ------------------------------------- | ------------- |
|
||||
| FastAPI backend | 1-2 days |
|
||||
| React scaffold + routing + API client | 0.5 day |
|
||||
| Dashboard page | 0.5 day |
|
||||
| Monitoring page + charts | 1 day |
|
||||
| Media page + AG Grid | 1 day |
|
||||
| File browser page | 1 day |
|
||||
| Polish + testing + deployment | 1-2 days |
|
||||
| **Total** | **~6-9 days** |
|
||||
|
||||
---
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- **Polling over WebSocket initially** — simpler, matches current behavior; upgrade path exists for later.
|
||||
- **Auth deferred** — rely on network access control for now; add API key or JWT later.
|
||||
- **CORS enabled** — for Vite dev server and production origin.
|
||||
- **Path resolution stays server-side** — frontend sends Jellyfin paths, backend resolves to SSH paths.
|
||||
- **Both can coexist** — Streamlit and FastAPI can run simultaneously during transition since they share the same clients/services.
|
||||
|
||||
---
|
||||
|
||||
## Optional Post-Migration Enhancements
|
||||
|
||||
- WebSocket push for real-time monitoring/now-playing
|
||||
- JWT authentication
|
||||
- Background index build with SSE progress
|
||||
- Dark/light theme
|
||||
- Persistent user preferences (localStorage + optional backend sync)
|
||||
- Docker Compose (backend + frontend + nginx reverse proxy)
|
||||
@@ -8,7 +8,7 @@ Build a compact Streamlit application for browsing a remote Jellyfin media libra
|
||||
|
||||
## Current Phase
|
||||
|
||||
Phase 1: Jellyfin library browser plus SSH-based remote filesystem inspection and safe job templates.
|
||||
Phase 1: Jellyfin media index, SSH-based remote filesystem inspection, server monitoring, and safe job templates.
|
||||
|
||||
## Core Requirements
|
||||
|
||||
@@ -91,14 +91,15 @@ Phase 1: Jellyfin library browser plus SSH-based remote filesystem inspection an
|
||||
- Keep a manual `ffprobe` action available for selected paths.
|
||||
- Support `stat` on selected paths.
|
||||
|
||||
### Dashboard / Server Resources
|
||||
### Dashboard / Server Monitoring
|
||||
|
||||
- Provide a dashboard tab with a compact Jellyfin media library overview and server resource overview.
|
||||
- Show Jellyfin media counts for movies, series, and series episodes on the dashboard.
|
||||
- Show currently playing Jellyfin sessions on the dashboard, including user, media title, playback state, and whether transcoding is active.
|
||||
- Provide a dashboard tab with a compact server resource overview over SSH.
|
||||
- Provide a separate Resources tab for detailed resource charts, collector controls, diagnostics, and raw samples.
|
||||
- Provide a separate Monitoring tab for detailed resource charts, collector controls, diagnostics, and raw samples.
|
||||
- Show CPU and RAM usage for the last hour.
|
||||
- Show IO wait percentage for the last hour.
|
||||
- Show average and spike/peak values for network throughput and disk I/O.
|
||||
- Show used, available, and total disk space for the configured media root, falling back to `/`.
|
||||
- Use a lightweight remote collector that reads Linux `/proc`, `/sys/block`, and `df` data into a JSONL file under `/tmp`.
|
||||
@@ -274,8 +275,8 @@ Local app dependencies are declared in `pyproject.toml`; `requirements.txt` inst
|
||||
- Changed network display units from bits per second to bytes per second to avoid Kbps/KB/s ambiguity; the collector still stores bit-rate compatibility fields for old/debug consumers.
|
||||
- Scaled network and disk throughput charts into readable units such as KB/s, MB/s, and GB/s instead of plotting raw base units.
|
||||
- Updated collector startup to remove old temporary metrics/log files when a new collector process is started after a schema/display change.
|
||||
- Moved detailed resource charts, raw samples, diagnostics, and collector controls into a dedicated Resources tab; the Dashboard now keeps a compact overview.
|
||||
- Removed the CPU/RAM chart from the Dashboard and kept detailed charts in the Resources tab.
|
||||
- Moved detailed resource charts, raw samples, diagnostics, and collector controls into a dedicated Monitoring tab; the Dashboard now keeps a compact overview.
|
||||
- Removed the CPU/RAM chart from the Dashboard and kept detailed charts in the Monitoring tab.
|
||||
- Renamed the remote files tab to File browser.
|
||||
- Added Jellyfin media counts for movies, series, and episodes to the Dashboard using lightweight count queries.
|
||||
- Added a Dashboard now-playing section sourced from Jellyfin sessions, showing who is currently playing what and whether each session is transcoding.
|
||||
@@ -297,6 +298,9 @@ Local app dependencies are declared in `pyproject.toml`; `requirements.txt` inst
|
||||
- Restored File browser table row selection with AG Grid (single-select), using a table interaction style consistent with the Media tab.
|
||||
- Reintroduced open-on-select behavior in File browser: selecting a directory row (including `[UP] ..`) opens it immediately, while file rows update selected target path.
|
||||
- Refined Media table column presentation with explicit user-friendly headers and null-safe display formatting to keep the grid readable and consistent.
|
||||
- Renamed Resources tab to Monitoring; added IO wait (iowait) percentage to the collector script, metrics, dashboard summary, and detailed charts.
|
||||
- Removed the Jellyfin library poster-grid tab and its associated cached API calls and UI module; the Media index tab now covers library browsing needs.
|
||||
- Simplified File browser navigation: removed Up/Go/Select folder buttons; pressing Enter in the path text input navigates directly.
|
||||
- Added broad inline/module documentation across clients, domain, services, and Streamlit adapter modules to make debugging and future frontend extraction easier.
|
||||
- Simplified the File browser by removing its interactive AG Grid and using a read-only listing with explicit Open/Select controls, reducing cross-tab state interactions with the Media grid.
|
||||
- Removed optional/compatibility code paths around the Media table grid and old file-browser state aliases to keep the interaction model easier to reason about during debugging.
|
||||
|
||||
Reference in New Issue
Block a user