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

14 KiB

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:

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:

<!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:

# 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:

<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:

# 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:

{% 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:

{% 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:

{% 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

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! 🎨