- 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
548 lines
14 KiB
Markdown
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! 🎨
|