docs: add comprehensive project documentation
- Replace original Subgen README with TranscriptorIO documentation - Add docs/API.md with 45+ REST endpoint documentation - Add docs/ARCHITECTURE.md with backend component details - Add docs/FRONTEND.md with Vue 3 frontend structure - Add docs/CONFIGURATION.md with settings system documentation - Remove outdated backend/README.md
This commit is contained in:
666
docs/FRONTEND.md
Normal file
666
docs/FRONTEND.md
Normal file
@@ -0,0 +1,666 @@
|
||||
# TranscriptorIO Frontend
|
||||
|
||||
Technical documentation for the Vue 3 frontend application.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Technology Stack](#technology-stack)
|
||||
- [Directory Structure](#directory-structure)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Views](#views)
|
||||
- [Components](#components)
|
||||
- [State Management](#state-management)
|
||||
- [API Service](#api-service)
|
||||
- [Routing](#routing)
|
||||
- [Styling](#styling)
|
||||
- [Build and Deployment](#build-and-deployment)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The TranscriptorIO frontend is a Single Page Application (SPA) built with Vue 3, featuring:
|
||||
|
||||
- **6 Complete Views**: Dashboard, Queue, Scanner, Rules, Workers, Settings
|
||||
- **Real-time Updates**: Polling-based status updates
|
||||
- **Dark Theme**: Tdarr-inspired dark UI
|
||||
- **Type Safety**: Full TypeScript support
|
||||
- **State Management**: Pinia stores for shared state
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack
|
||||
|
||||
| Technology | Version | Purpose |
|
||||
|------------|---------|---------|
|
||||
| Vue.js | 3.4+ | UI Framework |
|
||||
| Vue Router | 4.2+ | Client-side routing |
|
||||
| Pinia | 2.1+ | State management |
|
||||
| Axios | 1.6+ | HTTP client |
|
||||
| TypeScript | 5.3+ | Type safety |
|
||||
| Vite | 5.0+ | Build tool / dev server |
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── public/ # Static assets (favicon, etc.)
|
||||
├── src/
|
||||
│ ├── main.ts # Application entry point
|
||||
│ ├── App.vue # Root component + navigation
|
||||
│ │
|
||||
│ ├── views/ # Page components (routed)
|
||||
│ │ ├── DashboardView.vue # System overview + resources
|
||||
│ │ ├── QueueView.vue # Job management
|
||||
│ │ ├── ScannerView.vue # Scanner control
|
||||
│ │ ├── RulesView.vue # Scan rules CRUD
|
||||
│ │ ├── WorkersView.vue # Worker pool management
|
||||
│ │ └── SettingsView.vue # Settings management
|
||||
│ │
|
||||
│ ├── components/ # Reusable components
|
||||
│ │ ├── ConnectionWarning.vue # Backend connection status
|
||||
│ │ ├── PathBrowser.vue # Filesystem browser modal
|
||||
│ │ └── SetupWizard.vue # First-run setup wizard
|
||||
│ │
|
||||
│ ├── stores/ # Pinia state stores
|
||||
│ │ ├── config.ts # Configuration store
|
||||
│ │ ├── system.ts # System status store
|
||||
│ │ ├── workers.ts # Workers store
|
||||
│ │ └── jobs.ts # Jobs store
|
||||
│ │
|
||||
│ ├── services/
|
||||
│ │ └── api.ts # Axios API client
|
||||
│ │
|
||||
│ ├── router/
|
||||
│ │ └── index.ts # Vue Router configuration
|
||||
│ │
|
||||
│ ├── types/
|
||||
│ │ └── api.ts # TypeScript interfaces
|
||||
│ │
|
||||
│ └── assets/
|
||||
│ └── css/
|
||||
│ └── main.css # Global styles (dark theme)
|
||||
│
|
||||
├── index.html # HTML template
|
||||
├── vite.config.ts # Vite configuration
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
└── package.json # Dependencies
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+ and npm
|
||||
- Backend server running on port 8000
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start development server (with proxy to backend)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Development URLs
|
||||
|
||||
| URL | Description |
|
||||
|-----|-------------|
|
||||
| http://localhost:3000 | Frontend dev server |
|
||||
| http://localhost:8000 | Backend API |
|
||||
| http://localhost:8000/docs | Swagger API docs |
|
||||
|
||||
### Scripts
|
||||
|
||||
```bash
|
||||
npm run dev # Start dev server with HMR
|
||||
npm run build # Build for production
|
||||
npm run preview # Preview production build
|
||||
npm run lint # Run ESLint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Views
|
||||
|
||||
### DashboardView
|
||||
|
||||
**Path**: `/`
|
||||
|
||||
System overview with real-time resource monitoring.
|
||||
|
||||
**Features**:
|
||||
- System status (running/stopped)
|
||||
- CPU usage gauge
|
||||
- RAM usage gauge
|
||||
- GPU usage gauges (per device)
|
||||
- Recent jobs list
|
||||
- Worker pool summary
|
||||
- Scanner status
|
||||
|
||||
**Data Sources**:
|
||||
- `GET /api/status`
|
||||
- `GET /api/system/resources`
|
||||
- `GET /api/jobs?page_size=10`
|
||||
|
||||
### QueueView
|
||||
|
||||
**Path**: `/queue`
|
||||
|
||||
Job queue management with filtering and pagination.
|
||||
|
||||
**Features**:
|
||||
- Job list with status icons
|
||||
- Status filter (All/Queued/Processing/Completed/Failed)
|
||||
- Pagination controls
|
||||
- Retry failed jobs
|
||||
- Cancel queued/processing jobs
|
||||
- Clear completed jobs
|
||||
- Job progress display
|
||||
- Processing time display
|
||||
|
||||
**Data Sources**:
|
||||
- `GET /api/jobs`
|
||||
- `GET /api/jobs/stats`
|
||||
- `POST /api/jobs/{id}/retry`
|
||||
- `DELETE /api/jobs/{id}`
|
||||
- `POST /api/jobs/queue/clear`
|
||||
|
||||
### ScannerView
|
||||
|
||||
**Path**: `/scanner`
|
||||
|
||||
Library scanner control and configuration.
|
||||
|
||||
**Features**:
|
||||
- Scanner status display
|
||||
- Start/stop scheduler
|
||||
- Start/stop file watcher
|
||||
- Manual scan trigger
|
||||
- Scan results display
|
||||
- Next scan time
|
||||
- Total files scanned counter
|
||||
|
||||
**Data Sources**:
|
||||
- `GET /api/scanner/status`
|
||||
- `POST /api/scanner/scan`
|
||||
- `POST /api/scanner/scheduler/start`
|
||||
- `POST /api/scanner/scheduler/stop`
|
||||
- `POST /api/scanner/watcher/start`
|
||||
- `POST /api/scanner/watcher/stop`
|
||||
|
||||
### RulesView
|
||||
|
||||
**Path**: `/rules`
|
||||
|
||||
Scan rules CRUD management.
|
||||
|
||||
**Features**:
|
||||
- Rules list with priority ordering
|
||||
- Create new rule (modal)
|
||||
- Edit existing rule (modal)
|
||||
- Delete rule (with confirmation)
|
||||
- Toggle rule enabled/disabled
|
||||
- Condition configuration
|
||||
- Action configuration
|
||||
|
||||
**Data Sources**:
|
||||
- `GET /api/scan-rules`
|
||||
- `POST /api/scan-rules`
|
||||
- `PUT /api/scan-rules/{id}`
|
||||
- `DELETE /api/scan-rules/{id}`
|
||||
- `POST /api/scan-rules/{id}/toggle`
|
||||
|
||||
### WorkersView
|
||||
|
||||
**Path**: `/workers`
|
||||
|
||||
Worker pool management.
|
||||
|
||||
**Features**:
|
||||
- Worker list with status
|
||||
- Add CPU worker
|
||||
- Add GPU worker (with device selection)
|
||||
- Remove worker
|
||||
- Start/stop pool
|
||||
- Worker statistics
|
||||
- Current job display per worker
|
||||
- Progress and ETA display
|
||||
|
||||
**Data Sources**:
|
||||
- `GET /api/workers`
|
||||
- `GET /api/workers/stats`
|
||||
- `POST /api/workers`
|
||||
- `DELETE /api/workers/{id}`
|
||||
- `POST /api/workers/pool/start`
|
||||
- `POST /api/workers/pool/stop`
|
||||
|
||||
### SettingsView
|
||||
|
||||
**Path**: `/settings`
|
||||
|
||||
Database-backed settings management.
|
||||
|
||||
**Features**:
|
||||
- Settings grouped by category
|
||||
- Category tabs (General, Workers, Transcription, Scanner, Bazarr)
|
||||
- Edit settings in-place
|
||||
- Save changes button
|
||||
- Change detection (unsaved changes warning)
|
||||
- Setting descriptions
|
||||
|
||||
**Data Sources**:
|
||||
- `GET /api/settings`
|
||||
- `PUT /api/settings/{key}`
|
||||
- `POST /api/settings/bulk-update`
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
### ConnectionWarning
|
||||
|
||||
Displays warning banner when backend is unreachable.
|
||||
|
||||
**Props**: None
|
||||
|
||||
**State**: Uses `systemStore.isConnected`
|
||||
|
||||
### PathBrowser
|
||||
|
||||
Modal component for browsing filesystem paths.
|
||||
|
||||
**Props**:
|
||||
- `show: boolean` - Show/hide modal
|
||||
- `initialPath: string` - Starting path
|
||||
|
||||
**Emits**:
|
||||
- `select(path: string)` - Path selected
|
||||
- `close()` - Modal closed
|
||||
|
||||
**API Calls**:
|
||||
- `GET /api/filesystem/browse?path={path}`
|
||||
- `GET /api/filesystem/common-paths`
|
||||
|
||||
### SetupWizard
|
||||
|
||||
First-run setup wizard component.
|
||||
|
||||
**Props**: None
|
||||
|
||||
**Features**:
|
||||
- Mode selection (Standalone/Bazarr)
|
||||
- Library path configuration
|
||||
- Scan rule creation
|
||||
- Worker configuration
|
||||
- Scanner interval setting
|
||||
|
||||
**API Calls**:
|
||||
- `GET /api/setup/status`
|
||||
- `POST /api/setup/standalone`
|
||||
- `POST /api/setup/bazarr-slave`
|
||||
- `POST /api/setup/skip`
|
||||
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
### Pinia Stores
|
||||
|
||||
#### systemStore (`stores/system.ts`)
|
||||
|
||||
Global system state.
|
||||
|
||||
```typescript
|
||||
interface SystemState {
|
||||
isConnected: boolean
|
||||
status: SystemStatus | null
|
||||
resources: SystemResources | null
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
// Actions
|
||||
fetchStatus() // Fetch /api/status
|
||||
fetchResources() // Fetch /api/system/resources
|
||||
startPolling() // Start auto-refresh
|
||||
stopPolling() // Stop auto-refresh
|
||||
```
|
||||
|
||||
#### workersStore (`stores/workers.ts`)
|
||||
|
||||
Worker pool state.
|
||||
|
||||
```typescript
|
||||
interface WorkersState {
|
||||
workers: Worker[]
|
||||
stats: WorkerStats | null
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
// Actions
|
||||
fetchWorkers() // Fetch all workers
|
||||
fetchStats() // Fetch pool stats
|
||||
addWorker(type, deviceId?) // Add worker
|
||||
removeWorker(id) // Remove worker
|
||||
startPool(cpuCount, gpuCount) // Start pool
|
||||
stopPool() // Stop pool
|
||||
```
|
||||
|
||||
#### jobsStore (`stores/jobs.ts`)
|
||||
|
||||
Job queue state.
|
||||
|
||||
```typescript
|
||||
interface JobsState {
|
||||
jobs: Job[]
|
||||
stats: QueueStats | null
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
statusFilter: string | null
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
// Actions
|
||||
fetchJobs() // Fetch with current filters
|
||||
fetchStats() // Fetch queue stats
|
||||
retryJob(id) // Retry failed job
|
||||
cancelJob(id) // Cancel job
|
||||
clearCompleted() // Clear completed jobs
|
||||
setStatusFilter(status) // Update filter
|
||||
setPage(page) // Change page
|
||||
```
|
||||
|
||||
#### configStore (`stores/config.ts`)
|
||||
|
||||
Settings configuration state.
|
||||
|
||||
```typescript
|
||||
interface ConfigState {
|
||||
settings: Setting[]
|
||||
loading: boolean
|
||||
error: string | null
|
||||
pendingChanges: Record<string, string>
|
||||
}
|
||||
|
||||
// Actions
|
||||
fetchSettings(category?) // Fetch settings
|
||||
updateSetting(key, value) // Queue update
|
||||
saveChanges() // Save all pending
|
||||
discardChanges() // Discard pending
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Service
|
||||
|
||||
### Configuration (`services/api.ts`)
|
||||
|
||||
```typescript
|
||||
import axios from 'axios'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// Response interceptor for error handling
|
||||
api.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
console.error('API Error:', error)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default api
|
||||
```
|
||||
|
||||
### Usage Example
|
||||
|
||||
```typescript
|
||||
import api from '@/services/api'
|
||||
|
||||
// GET request
|
||||
const response = await api.get('/jobs', {
|
||||
params: { status_filter: 'queued', page: 1 }
|
||||
})
|
||||
|
||||
// POST request
|
||||
const job = await api.post('/jobs', {
|
||||
file_path: '/media/video.mkv',
|
||||
target_lang: 'spa'
|
||||
})
|
||||
|
||||
// PUT request
|
||||
await api.put('/settings/worker_cpu_count', {
|
||||
value: '2'
|
||||
})
|
||||
|
||||
// DELETE request
|
||||
await api.delete(`/jobs/${jobId}`)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Routing
|
||||
|
||||
### Route Configuration
|
||||
|
||||
```typescript
|
||||
const routes = [
|
||||
{ path: '/', name: 'Dashboard', component: DashboardView },
|
||||
{ path: '/workers', name: 'Workers', component: WorkersView },
|
||||
{ path: '/queue', name: 'Queue', component: QueueView },
|
||||
{ path: '/scanner', name: 'Scanner', component: ScannerView },
|
||||
{ path: '/rules', name: 'Rules', component: RulesView },
|
||||
{ path: '/settings', name: 'Settings', component: SettingsView }
|
||||
]
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
Navigation is handled in `App.vue` with a sidebar menu.
|
||||
|
||||
```vue
|
||||
<nav class="sidebar">
|
||||
<router-link to="/">Dashboard</router-link>
|
||||
<router-link to="/workers">Workers</router-link>
|
||||
<router-link to="/queue">Queue</router-link>
|
||||
<router-link to="/scanner">Scanner</router-link>
|
||||
<router-link to="/rules">Rules</router-link>
|
||||
<router-link to="/settings">Settings</router-link>
|
||||
</nav>
|
||||
|
||||
<main class="content">
|
||||
<router-view />
|
||||
</main>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Styling
|
||||
|
||||
### Dark Theme
|
||||
|
||||
The application uses a Tdarr-inspired dark theme defined in `assets/css/main.css`.
|
||||
|
||||
**Color Palette**:
|
||||
|
||||
| Variable | Value | Usage |
|
||||
|----------|-------|-------|
|
||||
| --bg-primary | #1a1a2e | Main background |
|
||||
| --bg-secondary | #16213e | Card background |
|
||||
| --bg-tertiary | #0f3460 | Hover states |
|
||||
| --text-primary | #eaeaea | Primary text |
|
||||
| --text-secondary | #a0a0a0 | Secondary text |
|
||||
| --accent-primary | #e94560 | Buttons, links |
|
||||
| --accent-success | #4ade80 | Success states |
|
||||
| --accent-warning | #fbbf24 | Warning states |
|
||||
| --accent-error | #ef4444 | Error states |
|
||||
|
||||
### Component Styling
|
||||
|
||||
Components use scoped CSS with CSS variables:
|
||||
|
||||
```vue
|
||||
<style scoped>
|
||||
.card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build and Deployment
|
||||
|
||||
### Production Build
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
This creates a `dist/` folder with:
|
||||
- `index.html` - Entry HTML
|
||||
- `assets/` - JS, CSS bundles (hashed filenames)
|
||||
|
||||
### Deployment Options
|
||||
|
||||
#### Option 1: Served by Backend (Recommended)
|
||||
|
||||
The FastAPI backend automatically serves the frontend from `frontend/dist/`:
|
||||
|
||||
```python
|
||||
# backend/app.py
|
||||
frontend_path = Path(__file__).parent.parent / "frontend" / "dist"
|
||||
|
||||
if frontend_path.exists():
|
||||
app.mount("/assets", StaticFiles(directory=str(frontend_path / "assets")))
|
||||
|
||||
@app.get("/{full_path:path}")
|
||||
async def serve_frontend(full_path: str = ""):
|
||||
return FileResponse(str(frontend_path / "index.html"))
|
||||
```
|
||||
|
||||
**Access**: http://localhost:8000
|
||||
|
||||
#### Option 2: Nginx Reverse Proxy
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name transcriptorio.local;
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
root /var/www/transcriptorio/frontend/dist;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Backend API
|
||||
location /api {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Option 3: Docker
|
||||
|
||||
```dockerfile
|
||||
# Build frontend
|
||||
FROM node:18-alpine AS frontend-builder
|
||||
WORKDIR /app/frontend
|
||||
COPY frontend/package*.json ./
|
||||
RUN npm ci
|
||||
COPY frontend/ ./
|
||||
RUN npm run build
|
||||
|
||||
# Final image
|
||||
FROM python:3.12-slim
|
||||
COPY --from=frontend-builder /app/frontend/dist /app/frontend/dist
|
||||
# ... rest of backend setup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Interfaces
|
||||
|
||||
### Key Types (`types/api.ts`)
|
||||
|
||||
```typescript
|
||||
// Job
|
||||
interface Job {
|
||||
id: string
|
||||
file_path: string
|
||||
file_name: string
|
||||
status: 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled'
|
||||
priority: number
|
||||
progress: number
|
||||
// ... more fields
|
||||
}
|
||||
|
||||
// Worker
|
||||
interface Worker {
|
||||
worker_id: string
|
||||
worker_type: 'cpu' | 'gpu'
|
||||
device_id: number | null
|
||||
status: 'idle' | 'busy' | 'stopped' | 'error'
|
||||
current_job_id: string | null
|
||||
jobs_completed: number
|
||||
jobs_failed: number
|
||||
}
|
||||
|
||||
// Setting
|
||||
interface Setting {
|
||||
id: number
|
||||
key: string
|
||||
value: string | null
|
||||
description: string | null
|
||||
category: string | null
|
||||
value_type: string | null
|
||||
}
|
||||
|
||||
// ScanRule
|
||||
interface ScanRule {
|
||||
id: number
|
||||
name: string
|
||||
enabled: boolean
|
||||
priority: number
|
||||
conditions: ScanRuleConditions
|
||||
action: ScanRuleAction
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user