Files
openrouter-watcher/prompt/prompt-ingaggio-frontend.md
Luca Sacchi Ricciardi c1f47c897f feat(frontend): T44 setup FastAPI static files and templates
- Mount static files on /static endpoint
- Configure Jinja2Templates with directory structure
- Create base template with Pico.css, HTMX, Chart.js
- Create all template subdirectories (auth, dashboard, keys, tokens, profile, components)
- Create initial CSS and JS files
- Add tests for static files and templates configuration

Tests: 12 passing
Coverage: 100% on new configuration code
2026-04-07 17:58:03 +02:00

548 lines
14 KiB
Markdown

# Prompt di Ingaggio: Frontend Web (T44-T54)
## 🎯 MISSIONE
Implementare il **Frontend Web** per OpenRouter API Key Monitor usando HTML, Jinja2 templates e HTMX per un'interfaccia utente moderna e reattiva.
**Task da completare:** T44, T45, T46, T47, T48, T49, T50, T51, T52, T53, T54
---
## 📋 CONTESTO
**AGENTE:** @tdd-developer
**Repository:** `/home/google/Sources/LucaSacchiNet/openrouter-watcher`
**Stato Attuale:**
- ✅ MVP Backend completato: 51/74 task (69%)
- ✅ 444+ test passanti, ~98% coverage
- ✅ Tutte le API REST implementate
- ✅ Background Tasks per sincronizzazione automatica
- ✅ Docker support pronto
- 🎯 **Manca:** Interfaccia web per gli utenti
**Perché questa fase è importante:**
Attualmente l'applicazione espone solo API REST. Gli utenti devono usare strumenti come curl o Postman per interagire. Con il frontend web, gli utenti potranno:
- Registrarsi e fare login via browser
- Visualizzare dashboard con grafici
- Gestire API keys tramite interfaccia grafica
- Generare e revocare token API
- Vedere statistiche in tempo reale
**Stack Frontend:**
- **FastAPI** - Serve static files e templates
- **Jinja2** - Template engine
- **HTMX** - AJAX moderno senza JavaScript complesso
- **Pico.css** - CSS framework minimalista (o Bootstrap/Tailwind)
- **Chart.js** - Grafici per dashboard
**Backend Pronto:**
- Tutti i router REST funzionanti
- Autenticazione JWT via cookie
- API documentate su `/docs`
---
## 🔧 TASK DA IMPLEMENTARE
### T44: Configurare FastAPI per Static Files e Templates
**File:** `src/openrouter_monitor/main.py`
**Requisiti:**
- Mount directory `/static` per CSS, JS, immagini
- Configurare Jinja2 templates
- Creare struttura directory `templates/` e `static/`
- Aggiungere context processor per variabili globali
**Implementazione:**
```python
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pathlib import Path
# Mount static files
app.mount("/static", StaticFiles(directory="static"), name="static")
# Configure templates
templates = Jinja2Templates(directory="templates")
# Context processor
def get_context(request: Request, **kwargs):
return {
"request": request,
"app_name": "OpenRouter Monitor",
"user": getattr(request.state, 'user', None),
**kwargs
}
```
**File da creare:**
```
static/
├── css/
│ └── style.css
├── js/
│ └── main.js
└── img/
└── favicon.ico
templates/
├── base.html
├── components/
│ ├── navbar.html
│ ├── footer.html
│ └── alert.html
├── auth/
│ ├── login.html
│ └── register.html
├── dashboard/
│ └── index.html
├── keys/
│ └── index.html
├── tokens/
│ └── index.html
└── profile/
└── index.html
```
**Test:** Verifica che `/static/css/style.css` sia accessibile
---
### T45: Creare Base Template HTML
**File:** `templates/base.html`, `templates/components/navbar.html`, `templates/components/footer.html`
**Requisiti:**
- Layout base responsive
- Include Pico.css (o altro framework) da CDN
- Meta tags SEO-friendly
- Favicon
- Navbar con menu dinamico (login/logout)
- Footer con info app
- Block content per pagine figlie
**Implementazione base.html:**
```html
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Monitora l'utilizzo delle tue API key OpenRouter">
<title>{% block title %}{{ app_name }}{% endblock %}</title>
<!-- Pico.css -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
<!-- HTMX -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Custom CSS -->
<link rel="stylesheet" href="/static/css/style.css">
{% block extra_css %}{% endblock %}
</head>
<body>
{% include 'components/navbar.html' %}
<main class="container">
{% include 'components/alert.html' %}
{% block content %}{% endblock %}
</main>
{% include 'components/footer.html' %}
{% block extra_js %}{% endblock %}
</body>
</html>
```
**Test:** Verifica rendering base template
---
### T46: Configurare HTMX e CSRF
**File:** `templates/base.html` (aggiorna), `src/openrouter_monitor/middleware/csrf.py`
**Requisiti:**
- Aggiungere CSRF token in meta tag
- Middleware CSRF per protezione form
- HTMX configurato per inviare CSRF header
**Implementazione:**
```python
# middleware/csrf.py
from fastapi import Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
import secrets
class CSRFMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# Generate or get CSRF token
if 'csrf_token' not in request.session:
request.session['csrf_token'] = secrets.token_urlsafe(32)
# Validate on POST/PUT/DELETE
if request.method in ['POST', 'PUT', 'DELETE']:
token = request.headers.get('X-CSRF-Token') or request.form().get('_csrf_token')
if token != request.session.get('csrf_token'):
raise HTTPException(status_code=403, detail="Invalid CSRF token")
response = await call_next(request)
return response
```
**Template aggiornamento:**
```html
<meta name="csrf-token" content="{{ csrf_token }}">
<script>
// HTMX default headers
document.body.addEventListener('htmx:configRequest', function(evt) {
evt.detail.headers['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').content;
});
</script>
```
---
### T47: Pagina Login (/login)
**File:** `templates/auth/login.html`, `src/openrouter_monitor/routers/web_auth.py`
**Requisiti:**
- Form email/password
- Validazione client-side (HTML5)
- HTMX per submit AJAX
- Messaggi errore (flash messages)
- Redirect a dashboard dopo login
- Link a registrazione
**Implementazione:**
```python
# routers/web_auth.py
from fastapi import APIRouter, Request, Form, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse
router = APIRouter()
@router.get("/login", response_class=HTMLResponse)
async def login_page(request: Request):
return templates.TemplateResponse(
"auth/login.html",
get_context(request)
)
@router.post("/login")
async def login_submit(
request: Request,
email: str = Form(...),
password: str = Form(...)
):
# Call auth service
try:
token = await authenticate_user(email, password)
response = RedirectResponse(url="/dashboard", status_code=302)
response.set_cookie(key="access_token", value=token, httponly=True)
return response
except AuthenticationError:
return templates.TemplateResponse(
"auth/login.html",
get_context(request, error="Invalid credentials")
)
```
**Template:**
```html
{% extends "base.html" %}
{% block title %}Login - {{ app_name }}{% endblock %}
{% block content %}
<article class="grid">
<div>
<h1>Login</h1>
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
<form method="post" action="/login" hx-post="/login" hx-target="body">
<input type="hidden" name="_csrf_token" value="{{ csrf_token }}">
<label for="email">Email</label>
<input type="email" id="email" name="email" required
placeholder="your@email.com" autocomplete="email">
<label for="password">Password</label>
<input type="password" id="password" name="password" required
placeholder="••••••••" autocomplete="current-password">
<button type="submit">Login</button>
</form>
<p>Don't have an account? <a href="/register">Register</a></p>
</div>
</article>
{% endblock %}
```
**Test:** Test login form, validazione, redirect
---
### T48: Pagina Registrazione (/register)
**File:** `templates/auth/register.html`
**Requisiti:**
- Form completo: email, password, password_confirm
- Validazione password strength (client-side)
- Check password match
- Conferma registrazione
- Redirect a login
**Template:**
```html
{% extends "base.html" %}
{% block content %}
<h1>Register</h1>
<form method="post" action="/register" hx-post="/register" hx-target="body">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<label for="password">Password</label>
<input type="password" id="password" name="password" required
minlength="12" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*])">
<small>Min 12 chars, uppercase, lowercase, number, special char</small>
<label for="password_confirm">Confirm Password</label>
<input type="password" id="password_confirm" name="password_confirm" required>
<button type="submit">Register</button>
</form>
{% endblock %}
```
---
### T49: Pagina Logout
**File:** Gestito via endpoint POST con redirect
**Requisiti:**
- Bottone logout in navbar
- Conferma opzionale
- Redirect a login
- Cancella cookie JWT
---
### T50: Dashboard (/dashboard)
**File:** `templates/dashboard/index.html`
**Requisiti:**
- Card riepilogative (totale richieste, costo, token)
- Grafico andamento temporale (Chart.js)
- Tabella modelli più usati
- Link rapidi a gestione keys e tokens
- Dati caricati via API interna
**Implementazione:**
```html
{% extends "base.html" %}
{% block content %}
<h1>Dashboard</h1>
<div class="grid">
<article>
<h3>Total Requests</h3>
<p><strong>{{ stats.total_requests }}</strong></p>
</article>
<article>
<h3>Total Cost</h3>
<p><strong>${{ stats.total_cost }}</strong></p>
</article>
<article>
<h3>API Keys</h3>
<p><strong>{{ api_keys_count }}</strong></p>
</article>
</div>
<article>
<h3>Usage Over Time</h3>
<canvas id="usageChart"></canvas>
</article>
<script>
const ctx = document.getElementById('usageChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: {{ chart_labels | tojson }},
datasets: [{
label: 'Requests',
data: {{ chart_data | tojson }}
}]
}
});
</script>
{% endblock %}
```
---
### T51-T54: Altre Pagine
Seguire lo stesso pattern per:
- **T51**: Gestione API Keys (`/keys`) - Tabella con CRUD via HTMX
- **T52**: Statistiche (`/stats`) - Filtri e paginazione
- **T53**: Token API (`/tokens`) - Generazione e revoca
- **T54**: Profilo (`/profile`) - Cambio password
---
## 🔄 WORKFLOW TDD
Per **OGNI** task:
1. **RED**: Scrivi test che verifica rendering template
2. **GREEN**: Implementa template e route
3. **REFACTOR**: Estrai componenti riutilizzabili
---
## 📁 STRUTTURA FILE DA CREARE
```
templates/
├── base.html
├── components/
│ ├── navbar.html
│ ├── footer.html
│ └── alert.html
├── auth/
│ ├── login.html
│ └── register.html
├── dashboard/
│ └── index.html
├── keys/
│ └── index.html
├── tokens/
│ └── index.html
└── profile/
└── index.html
static/
├── css/
│ └── style.css
└── js/
└── main.js
src/openrouter_monitor/
├── routers/
│ ├── web.py # T44, T47-T54
│ └── web_auth.py # T47-T49
└── middleware/
└── csrf.py # T46
```
---
## ✅ CRITERI DI ACCETTAZIONE
- [ ] T44: Static files e templates configurati
- [ ] T45: Base template con layout responsive
- [ ] T46: CSRF protection e HTMX configurati
- [ ] T47: Pagina login funzionante
- [ ] T48: Pagina registrazione funzionante
- [ ] T49: Logout funzionante
- [ ] T50: Dashboard con grafici
- [ ] T51: Gestione API keys via web
- [ ] T52: Statistiche con filtri
- [ ] T53: Gestione token via web
- [ ] T54: Profilo utente
- [ ] Tutte le pagine responsive (mobile-friendly)
- [ ] Test completi per router web
- [ ] 11 commit atomici con conventional commits
---
## 📝 COMMIT MESSAGES
```
feat(frontend): T44 setup FastAPI static files and templates
feat(frontend): T45 create base HTML template with layout
feat(frontend): T46 configure HTMX and CSRF protection
feat(frontend): T47 implement login page
feat(frontend): T48 implement registration page
feat(frontend): T49 implement logout functionality
feat(frontend): T50 implement dashboard with charts
feat(frontend): T51 implement API keys management page
feat(frontend): T52 implement detailed stats page
feat(frontend): T53 implement API tokens management page
feat(frontend): T54 implement user profile page
```
---
## 🚀 VERIFICA FINALE
```bash
cd /home/google/Sources/LucaSacchiNet/openrouter-watcher
# Avvia app
uvicorn src.openrouter_monitor.main:app --reload
# Test manuali:
# 1. Visita http://localhost:8000/login
# 2. Registra nuovo utente
# 3. Login
# 4. Visualizza dashboard con grafici
# 5. Aggiungi API key
# 6. Genera token API
# 7. Logout
# Test automatici
pytest tests/unit/routers/test_web.py -v
```
---
## 🎨 DESIGN CONSIGLIATO
- **Framework CSS**: Pico.css (leggero, moderno, semantic HTML)
- **Colori**: Blu primario, grigio chiaro sfondo
- **Layout**: Container centrato, max-width 1200px
- **Mobile**: Responsive con breakpoint 768px
- **Grafici**: Chart.js con tema coordinato
---
**AGENTE:** @tdd-developer
**INIZIA CON:** T44 - Setup FastAPI static files e templates
**QUANDO FINITO:** L'applicazione avrà un'interfaccia web completa e user-friendly! 🎨