Introduce an admin portal (React + Nginx), WebSocket routing, and API versioning middleware with `/api/v1/` prefix deprecation. Add master API key authentication, new Prometheus metrics for AI token consumption and active WebSockets, and extend S3 config with a public endpoint URL. Update test paths and fixtures to align with the new routing structure.
42 KiB
Prompt Claude Code — Portail d'administration et de surveillance
Application : Imago Hub (backend FastAPI multi-clients)
Modèle : claude-opus-4-6
Usage : claude --model claude-opus-4-6 -p "$(cat PROMPT_ADMIN_PORTAL_claude-opus.md)"
ou coller directement dans une session Claude Code interactive
Tu es un ingénieur fullstack senior spécialisé en React, TypeScript, visualisation de données et architecture de portails d'administration. Tu construis un portail web complet, indépendant et conteneurisé pour surveiller et administrer Imago Hub — un backend FastAPI centralisé de gestion d'images avec pipeline AI multi-clients.
Ce portail est un projet entièrement séparé du backend. Il communique exclusivement via l'API REST et les WebSockets exposés par le hub. Il tourne dans son propre container Docker et n'a aucune dépendance de code avec le backend Python.
<project_context>
Imago Hub — Backend à superviser
Ce qu'est le backend
Imago Hub est un backend FastAPI centralisé servant plusieurs applications clientes simultanément. Chaque client (application ou utilisateur) s'authentifie via une API Key et possède son propre espace isolé de données.
Fonctionnalités du backend
- Upload et stockage d'images avec génération de thumbnails automatique
- Pipeline AI automatique à 3 étapes : EXIF → OCR → Vision AI (Claude)
- Extraction EXIF : appareil photo, GPS, ISO, ouverture, date de prise de vue
- OCR : extraction de texte via Tesseract + détection de langue
- Vision AI : description en langage naturel + tags automatiques + score de confiance
- Multi-clients : isolation totale des données par client (
client_id) - Plans :
free,standard,premiumavec quotas différents - File de tâches ARQ + Redis : pipeline persistant avec retry automatique
- WebSocket : notifications temps réel de l'avancement du pipeline
APIs disponibles sur le backend (base URL : http://backend:8000)
Authentification :
POST /api/v1/auth/clients Créer un client
GET /api/v1/auth/clients Lister tous les clients (scope: admin)
GET /api/v1/auth/clients/{id} Détail d'un client
PATCH /api/v1/auth/clients/{id} Modifier un client
POST /api/v1/auth/clients/{id}/rotate-key Régénérer la clé API
DELETE /api/v1/auth/clients/{id} Désactiver un client
Images :
POST /api/v1/images/upload Upload une image
GET /api/v1/images Lister les images (filtrées par client)
GET /api/v1/images/{id} Détail complet d'une image
GET /api/v1/images/{id}/status Statut du pipeline
GET /api/v1/images/{id}/download-url URL signée de téléchargement
GET /api/v1/images/{id}/thumbnail-url URL signée du thumbnail
GET /api/v1/images/{id}/exif Métadonnées EXIF
GET /api/v1/images/{id}/ocr Résultat OCR
GET /api/v1/images/{id}/ai Résultat Vision AI
GET /api/v1/images/tags/all Tous les tags distincts
POST /api/v1/images/{id}/reprocess Relancer le pipeline AI
DELETE /api/v1/images/{id} Supprimer une image
Intelligence Artificielle :
POST /api/v1/ai/summarize Résumé d'une URL web
POST /api/v1/ai/draft-task Génération de tâche structurée
Infrastructure :
GET /health Statut de base
GET /health/detailed Santé détaillée (DB, Redis, ARQ, Tesseract, Anthropic, Disque)
GET /metrics Métriques Prometheus
WS /ws/pipeline/{image_id}?token=… Suivi temps réel du pipeline
Modèle de données clés
Client (APIClient) :
{
id: string (UUID)
name: string
plan: "free" | "standard" | "premium"
scopes: string[] // ["images:read", "images:write", "ai:use", "admin"]
is_active: boolean
quota_images: number
quota_storage_mb: number
quota_ai_tokens: number
storage_used_bytes: number // dénormalisé, mis à jour dynamiquement
created_at: string (ISO 8601)
updated_at: string (ISO 8601)
}
Image :
{
id: number
uuid: string
client_id: string (UUID) // ← application/utilisateur source
client_name: string // ← dénormalisé pour l'affichage
original_name: string
filename: string
mime_type: string
file_size: number (bytes)
width: number | null
height: number | null
uploaded_at: string (ISO 8601)
// Pipeline
processing_status: "pending" | "processing" | "done" | "error"
processing_error: string | null
processing_started_at: string | null
processing_done_at: string | null
// EXIF
exif_make: string | null // marque de l'appareil
exif_model: string | null // modèle de l'appareil
exif_lens: string | null
exif_taken_at: string | null // date de prise de vue originale
exif_gps_lat: number | null
exif_gps_lon: number | null
exif_altitude: number | null
exif_iso: number | null
exif_aperture: string | null
exif_shutter: string | null
exif_focal: number | null
// OCR
ocr_text: string | null
ocr_language: string | null
ocr_has_text: boolean
ocr_confidence: number | null
// Vision AI
ai_description: string | null
ai_tags: string[]
ai_confidence: number | null
ai_model_used: string | null
ai_processed_at: string | null
ai_prompt_tokens: number | null
ai_output_tokens: number | null
}
Health Check détaillé (GET /health/detailed) :
{
status: "healthy" | "degraded"
version: string
checked_at: string
checks: {
database: { status: "ok"|"error"|"warning", latency_ms?: number, detail?: string }
redis: { status: "ok"|"error", detail?: string }
queue: { status: "ok"|"error", pending_jobs: number, detail?: string }
tesseract: { status: "ok"|"error", detail?: string }
anthropic: { status: "ok"|"error", configured: boolean }
disk: { status: "ok"|"warning"|"error", used_pct: number, free_gb: number }
}
}
Métriques Prometheus (GET /metrics) — format texte Prometheus :
hub_images_uploaded_total{client_id, plan} Counter
hub_pipeline_duration_seconds{step} Histogram (p50/p95/p99)
hub_ai_tokens_consumed_total{client_id, type} Counter (prompt/output)
hub_pipeline_errors_total{step} Counter
hub_storage_used_bytes{client_id} Gauge
hub_active_websockets Gauge
http_requests_total{method, path, status} Counter (auto FastAPI)
http_request_duration_seconds{path} Histogram (auto FastAPI)
</project_context>
## Mission : créer le portail d'administration Imago HubNom du projet : imago-admin
Le portail est un projet React + TypeScript entièrement indépendant, servi dans son propre container Docker (Nginx), qui communique avec le backend via ses APIs publiques. Il comprend deux grandes sections :
- Section Administration — Réservée aux opérateurs. Supervision complète du hub, gestion des clients, métriques, santé des composants, configuration.
- Section Galerie — Interface de consultation et de recherche des images, accessible aux utilisateurs authentifiés avec affichage de la provenance par application/client.
Architecture globale du projet
imago-admin/
├── src/
│ ├── main.tsx # Point d'entrée React
│ ├── App.tsx # Router principal + layout
│ ├── api/
│ │ ├── client.ts # Client HTTP (fetch + intercepteurs auth)
│ │ ├── auth.ts # Endpoints auth clients
│ │ ├── images.ts # Endpoints images
│ │ ├── metrics.ts # Parser métriques Prometheus
│ │ └── health.ts # Health check
│ ├── store/
│ │ ├── index.ts # Zustand store global
│ │ ├── auth.store.ts # Session admin (API key stockée en mémoire)
│ │ ├── clients.store.ts # Clients/tenants
│ │ └── images.store.ts # Cache images + filtres
│ ├── pages/
│ │ ├── Login.tsx # Page de connexion (saisie de l'API Key admin)
│ │ ├── admin/
│ │ │ ├── Dashboard.tsx # Vue d'ensemble (KPIs + santé + activité)
│ │ │ ├── Clients.tsx # Liste et gestion des clients
│ │ │ ├── ClientDetail.tsx # Détail d'un client (quotas, images, tokens)
│ │ │ ├── Queue.tsx # État de la file ARQ
│ │ │ ├── Metrics.tsx # Métriques Prometheus (graphiques)
│ │ │ ├── Health.tsx # Santé des composants en temps réel
│ │ │ ├── Config.tsx # Configuration du hub (lecture + modification)
│ │ │ └── Logs.tsx # Flux de logs temps réel (WebSocket)
│ │ └── gallery/
│ │ ├── Gallery.tsx # Galerie principale avec recherche
│ │ ├── ImageDetail.tsx # Détail d'une image (EXIF, OCR, AI, carte GPS)
│ │ └── ClientGallery.tsx # Galerie filtrée par client/application
│ ├── components/
│ │ ├── layout/
│ │ │ ├── Sidebar.tsx # Navigation latérale collapsable
│ │ │ ├── TopBar.tsx # Barre supérieure (breadcrumb, profil, alertes)
│ │ │ └── PageLayout.tsx # Wrapper page avec header/content
│ │ ├── shared/
│ │ │ ├── StatusBadge.tsx # Badge coloré (ok/warning/error/pending)
│ │ │ ├── PlanBadge.tsx # Badge plan (free/standard/premium)
│ │ │ ├── QuotaBar.tsx # Barre de progression quota
│ │ │ ├── MetricCard.tsx # Carte KPI (valeur + tendance + icône)
│ │ │ ├── HealthDot.tsx # Point de santé animé (vert/orange/rouge)
│ │ │ ├── Pagination.tsx # Pagination universelle
│ │ │ ├── SearchInput.tsx # Champ de recherche avec debounce
│ │ │ ├── ConfirmDialog.tsx # Dialogue de confirmation (actions destructives)
│ │ │ ├── CopyButton.tsx # Copier dans le presse-papier
│ │ │ └── EmptyState.tsx # État vide illustré
│ │ ├── charts/
│ │ │ ├── UploadsChart.tsx # Graphique uploads par jour (line chart)
│ │ │ ├── PipelineChart.tsx # Durées pipeline par étape (bar chart)
│ │ │ ├── TokensChart.tsx # Consommation tokens AI par client (area chart)
│ │ │ ├── StorageChart.tsx # Répartition stockage par client (donut chart)
│ │ │ └── ErrorRateChart.tsx # Taux d'erreur pipeline dans le temps
│ │ ├── images/
│ │ │ ├── ImageGrid.tsx # Grille d'images responsive avec lazy loading
│ │ │ ├── ImageCard.tsx # Carte image (thumbnail + métadonnées + client)
│ │ │ ├── ImageFilters.tsx # Panneau de filtres (client, tags, status, date)
│ │ │ ├── ImageSearch.tsx # Recherche fulltext (description AI + OCR + tags)
│ │ │ ├── ExifPanel.tsx # Panneau EXIF avec formatage lisible
│ │ │ ├── OcrPanel.tsx # Panneau OCR avec mise en évidence du texte
│ │ │ ├── AiPanel.tsx # Panneau résultats Vision AI
│ │ │ ├── GpsMap.tsx # Carte Leaflet pour coordonnées GPS EXIF
│ │ │ └── ClientBadge.tsx # Badge indiquant l'application/client source
│ │ └── clients/
│ │ ├── ClientCard.tsx # Carte résumé d'un client
│ │ ├── ClientForm.tsx # Formulaire création/modification client
│ │ ├── ApiKeyModal.tsx # Modal affichage clé API (une seule fois)
│ │ └── QuotaEditor.tsx # Éditeur de quotas avec sliders
│ ├── hooks/
│ │ ├── useApi.ts # Hook générique fetch avec loading/error
│ │ ├── useWebSocket.ts # Hook WebSocket avec reconnexion automatique
│ │ ├── useHealth.ts # Polling santé toutes les 30s
│ │ ├── useMetrics.ts # Fetch + parse métriques Prometheus
│ │ ├── useImages.ts # Recherche + pagination images
│ │ └── useClients.ts # CRUD clients
│ ├── utils/
│ │ ├── formatters.ts # Formatage bytes, durées, dates, tokens
│ │ ├── prometheus.ts # Parser format texte Prometheus → objets JS
│ │ ├── colors.ts # Palette cohérente (plans, statuts, clients)
│ │ └── constants.ts # URLs, limites, valeurs par défaut
│ └── types/
│ ├── api.types.ts # Types TypeScript miroir des modèles backend
│ └── metrics.types.ts # Types pour les métriques parsées
├── public/
│ └── favicon.svg # Logo Imago Hub
├── nginx/
│ └── default.conf # Config Nginx (SPA fallback + proxy API)
├── Dockerfile # Build multi-stage : node build → nginx serve
├── docker-compose.yml # Stack complète : admin + backend + redis + minio
├── .env.example # VITE_API_BASE_URL, VITE_APP_TITLE
├── vite.config.ts
├── tsconfig.json
├── tailwind.config.ts
└── package.json
Livrable A — Infrastructure et setup du projet
Stack technique :
- React 18 + TypeScript 5 — framework UI
- Vite 5 — bundler (dev rapide, build optimisé)
- Tailwind CSS 3 — styles utilitaires
- React Router v6 — routing SPA
- Zustand — état global léger (pas de Redux)
- TanStack Query (React Query) v5 — cache serveur + synchronisation
- Recharts — graphiques (léger, composable)
- Leaflet + react-leaflet — carte GPS EXIF
- Lucide React — icônes cohérentes
- date-fns — formatage dates
- clsx + tailwind-merge — gestion des classes conditionnelles
package.json — scripts :
{
"scripts": {
"dev": "vite --host",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
"typecheck": "tsc --noEmit"
}
}
Dockerfile — build multi-stage :
# Stage 1 : Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG VITE_API_BASE_URL=http://localhost:8000
ARG VITE_APP_TITLE="Imago Hub Admin"
RUN npm run build
# Stage 2 : Serve
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 3000
CMD ["nginx", "-g", "daemon off;"]
nginx/default.conf :
server {
listen 3000;
root /usr/share/nginx/html;
index index.html;
# SPA fallback — toutes les routes vers index.html
location / {
try_files $uri $uri/ /index.html;
}
# Proxy vers le backend pour éviter les problèmes CORS en production
location /api/ {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /ws/ {
proxy_pass http://backend:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /health {
proxy_pass http://backend:8000;
}
location /metrics {
proxy_pass http://backend:8000;
}
# Cache agressif pour les assets buildés (hash dans le nom)
location ~* \.(js|css|png|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
docker-compose.yml (stack complète) :
version: "3.9"
services:
# Portail admin (ce projet)
admin:
build:
context: .
args:
VITE_API_BASE_URL: http://backend:8000
ports:
- "3000:3000"
depends_on:
- backend
restart: unless-stopped
# Backend Imago Hub (projet séparé)
backend:
image: imago-hub:latest # image buildée depuis le projet backend
ports:
- "8000:8000"
env_file: .env.backend
depends_on:
- redis
restart: unless-stopped
# Worker ARQ
worker:
image: imago-hub:latest
command: python worker.py
env_file: .env.backend
depends_on:
- redis
restart: unless-stopped
# Redis (file ARQ + Pub/Sub)
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
restart: unless-stopped
# MinIO (stockage S3 compatible — développement)
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
restart: unless-stopped
volumes:
redis_data:
minio_data:
Livrable B — Authentification et navigation
src/pages/Login.tsx — Page de connexion :
- Champ de saisie de la clé API admin (masquée, type password)
- Bouton "Se connecter" qui tente
GET /api/v1/auth/clientsavec la clé saisie - Si succès (HTTP 200) → stocker la clé en mémoire dans Zustand (jamais dans localStorage)
- Si échec (HTTP 401/403) → message d'erreur inline "Clé API invalide ou permissions insuffisantes"
- Afficher le logo Imago Hub + titre + version de l'API (depuis
GET /health) - Fond sombre avec formulaire centré — design professionnel et sobre
src/store/auth.store.ts — Store Zustand d'authentification :
interface AuthStore {
apiKey: string | null // en mémoire uniquement — jamais persisted
isAuthenticated: boolean
login: (key: string) => Promise<boolean>
logout: () => void
}
⚠️ Ne jamais utiliser localStorage ou sessionStorage pour stocker la clé API. Uniquement en mémoire Zustand. À la fermeture/reload de l'onglet, l'utilisateur devra se reconnecter.
src/App.tsx — Router principal :
// Routes protégées (nécessitent authentification)
/login
/admin/dashboard → Dashboard.tsx
/admin/clients → Clients.tsx
/admin/clients/:id → ClientDetail.tsx
/admin/queue → Queue.tsx
/admin/metrics → Metrics.tsx
/admin/health → Health.tsx
/admin/config → Config.tsx
/admin/logs → Logs.tsx
/gallery → Gallery.tsx
/gallery/client/:clientId → ClientGallery.tsx
/gallery/image/:id → ImageDetail.tsx
src/components/layout/Sidebar.tsx — Navigation :
Section Administration :
- 🏠 Tableau de bord
- 👥 Clients & Applications
- ⚙️ File de tâches (ARQ)
- 📊 Métriques
- 💚 Santé des composants
- 🔧 Configuration
- 📋 Logs temps réel
Section Galerie :
- 🖼️ Toutes les images
- 📁 Par application (liste des clients cliquables)
Footer sidebar : version du portail + statut de connexion backend (point vert/rouge animé)
Livrable C — Section Administration
C.1 — src/pages/admin/Dashboard.tsx — Tableau de bord
Rangée KPIs (MetricCard ×6) :
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Total images │ │ Clients actifs │ │ Tokens AI/mois │
│ 12 483 │ │ 8 / 12 │ │ 2.4M tokens │
│ ↑ +142 (24h) │ │ │ │ ≈ 48$ coût │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Stockage total │ │ Jobs en attente │ │ Taux succès AI │
│ 48.2 GB │ │ 3 │ │ 97.4 % │
│ de 500 GB │ │ premium: 1 │ │ ↓ -0.3% │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Graphiques (rangée 2 colonnes) :
- Gauche : uploads par jour — 30 derniers jours, line chart, couleur par plan client
- Droite : répartition stockage — donut chart par client/application
Santé des composants (rangée 3 colonnes) :
- Chaque composant : icône + nom + point coloré + latence ms
- Clic sur un composant → page Health complète
- Rafraîchissement automatique toutes les 30 secondes
Activité récente (tableau) :
- 10 derniers uploads avec : thumbnail miniature, nom fichier, client source (badge coloré), statut pipeline, il y a X minutes
- Lien vers le détail de chaque image
Alertes actives (si applicable) :
- Bannière rouge si un composant est en erreur
- Bannière orange si espace disque > 80% ou si plus de 10 jobs en échec
C.2 — src/pages/admin/Clients.tsx — Gestion des clients
Tableau des clients avec les colonnes :
Nom Plan Images Stockage Tokens AI Statut Actions
Shaarli Premium 4 821 12.4/50 GB ████ 1.2M/5M tokens Actif [⋮]
App Mobile Standard 891 2.1/20 GB ██ 342k/2M tokens Actif [⋮]
Script CI Free 23 0.1/2 GB ▏ 8k/100k tokens Actif [⋮]
Old App Free 0 0/2 GB 0/100k tokens Inactif [⋮]
- Barre de progression colorée pour chaque quota (vert → orange → rouge selon %)
- Menu Actions [⋮] : Voir détail | Voir galerie | Modifier | Régénérer clé | Désactiver
- Bouton "Nouveau client" → ouvre ClientForm dans un drawer latéral
- Filtres : plan, statut actif/inactif, recherche par nom
- Tri par : nom, images, stockage, date création, tokens AI
src/components/clients/ClientForm.tsx — Formulaire création/édition :
- Champs : Nom (requis), Plan (select), Scopes (checkboxes), Quotas personnalisés (sliders)
- Quotas avec valeurs par défaut selon le plan sélectionné (mise à jour automatique)
- Validation temps réel (nom unique, quotas dans les limites)
src/components/clients/ApiKeyModal.tsx — Modal affichage de la clé :
- S'affiche une seule fois après la création d'un client
- Clé affichée en monospace avec bouton CopyButton
- Avertissement en rouge : "Cette clé ne sera plus jamais affichée. Copiez-la maintenant."
- Case à cocher "J'ai bien copié la clé" avant de pouvoir fermer
C.3 — src/pages/admin/ClientDetail.tsx — Détail d'un client
En-tête : nom, plan badge, date de création, statut actif/inactif, boutons d'actions
3 jauges de quota :
Images ████████░░░░ 4821 / 10 000 (48.2%)
Stockage █████░░░░░░░ 12.4 / 50 GB (24.8%)
Tokens AI ███░░░░░░░░░ 1.2M / 5M (24.0%)
Graphiques d'activité du client (30 derniers jours) :
- Uploads par jour (bar chart)
- Tokens AI consommés par jour (area chart)
Scopes accordés : liste des permissions avec badges
20 dernières images du client :
- Grille de thumbnails cliquables
- Chaque image : thumbnail + statut pipeline + date + tags AI (max 3)
Actions : modifier quotas, régénérer clé, désactiver, voir galerie complète
C.4 — src/pages/admin/Queue.tsx — File de tâches ARQ
Compteurs en temps réel (polling toutes les 5s) :
En attente En cours Réussis (24h) Échoués (24h) Temps moyen
3 1 1 284 12 8.4s
Tableau des jobs en cours :
| Image ID | Client | Étape en cours | Durée | Tentative |
|---|---|---|---|---|
| #4821 | Shaarli | Vision AI | 12s | 1/3 |
Tableau des jobs récents en échec :
| Image ID | Client | Étape | Erreur | Tentatives | Heure |
|---|---|---|---|---|---|
| #4819 | App Mobile | Vision AI | API timeout | 3/3 | il y a 2h |
- Bouton "Relancer" sur chaque job échoué (appelle
POST /api/v1/images/{id}/reprocess) - Bouton "Relancer tous les échoués" (batch)
- Indicateur du nombre de jobs par queue (premium vs standard)
C.5 — src/pages/admin/Metrics.tsx — Métriques Prometheus
Parse GET /metrics (format texte Prometheus) et affiche des graphiques :
Section Performance :
- Latence HTTP p50/p95/p99 par endpoint (tableau + sparklines)
- Throughput requêtes/seconde (line chart)
- Durée pipeline par étape EXIF/OCR/AI (grouped bar chart)
Section Usage :
- Uploads totaux par client (stacked bar chart, 30j)
- Tokens AI consommés par client et par type prompt/output (stacked area chart)
- Taux d'erreur pipeline par étape (line chart)
Section Infrastructure :
- Connexions WebSocket actives (gauge)
- Stockage utilisé par client (bar chart horizontal)
- Jobs ARQ par statut (donut chart)
Rafraîchissement : bouton manuel + auto toutes les 60s Sélecteur de période : 1h, 6h, 24h, 7j (appliqué sur les graphiques qui supportent la temporalité)
C.6 — src/pages/admin/Health.tsx — Santé des composants
Vue en temps réel (polling toutes les 15s) :
Pour chaque composant, une carte avec :
┌──────────────────────────────────────────┐
│ ● Base de données [OK] │
│ Latence : 8ms │
│ Dernière vérification : il y a 12s │
└──────────────────────────────────────────┘
Composants surveillés :
- Base de données : statut + latence en ms
- Redis : statut + ping
- File ARQ : statut + nombre de jobs en attente
- Tesseract : statut + version détectée
- Anthropic API : statut + clé configurée (masquée)
- Espace disque : statut + % utilisé + Go libres + barre de progression
Historique : graphique timeline des statuts des 2 dernières heures (vert/orange/rouge) pour chaque composant
Alertes configurables :
- Seuil alerte espace disque : slider (défaut 80%)
- Seuil alerte jobs en attente : input (défaut 50)
C.7 — src/pages/admin/Config.tsx — Configuration du hub
Lecture de la configuration via GET /health/detailed et les variables exposées.
Sections :
Général :
Nom de l'application Imago Hub [lecture seule]
Version API 1.0.0 [lecture seule]
Environnement production [lecture seule]
URL de base http://backend:8000 [lecture seule]
Stockage :
Backend de stockage S3 (MinIO) [lecture seule]
Bucket imago-hub [lecture seule]
Endpoint http://minio:9000 [lecture seule]
Taille max upload 50 MB [modifiable]
Pipeline AI :
Modèle Claude claude-opus-4-6 [lecture seule]
OCR activé ✅ Oui [modifiable toggle]
Langues OCR fra+eng [modifiable]
Timeout Vision AI 120s [modifiable]
Retry max 3 [modifiable]
Plans et quotas par défaut :
Plan Free Images: 100 Stockage: 2 GB Tokens AI: 100k/mois
Plan Standard Images: 1000 Stockage: 20 GB Tokens AI: 2M/mois
Plan Premium Images: ∞ Stockage: 500 GB Tokens AI: 20M/mois
(Éditable via des inputs inline avec bouton "Sauvegarder")
Rate Limiting :
Free Uploads: 20/h Requêtes AI: 50/h
Standard Uploads: 100/h Requêtes AI: 200/h
Premium Uploads: 500/h Requêtes AI: 1000/h
⚠️ Les modifications de configuration envoient une requête PATCH /api/v1/admin/config (à implémenter dans le backend si pas encore présent, ou afficher en lecture seule avec note "Modifier dans le fichier .env").
C.8 — src/pages/admin/Logs.tsx — Logs en temps réel
Connexion WebSocket vers WS /ws/logs?token=<api_key> (à implémenter dans le backend) ou polling GET /api/v1/admin/logs/stream toutes les 2s.
Interface :
- Fenêtre de terminal scrollable (fond sombre, police monospace)
- Chaque ligne de log avec : timestamp | niveau coloré | event | données JSON inline
- Niveau INFO = gris, WARNING = orange, ERROR = rouge
- Nouveau log → scroll automatique vers le bas (désactivable)
Filtres :
- Niveau minimum (DEBUG / INFO / WARNING / ERROR)
- Client source (dropdown tous les clients)
- Étape pipeline (exif / ocr / ai / upload / auth)
- Recherche texte dans le message
Actions :
- Pause/Resume du défilement automatique
- Vider l'affichage
- Exporter les logs affichés en JSON ou CSV
Livrable D — Section Galerie
D.1 — src/pages/gallery/Gallery.tsx — Galerie principale
Barre de recherche et filtres (panneau collapsable sur mobile) :
[🔍 Rechercher dans description, OCR, tags... ] [Filtres ▾]
Filtres actifs : Application: Shaarli × | Tag: paris × | Effacer tout
[par Application ▾] [par Plan ▾] [par Statut ▾] [par Date ▾] [par Tags ▾]
Filtres disponibles :
- Application/Client : dropdown avec tous les clients, badge coloré par plan
- Tags AI : saisie avec autocomplete depuis
GET /api/v1/images/tags/all - Statut pipeline : done / processing / error / pending (checkboxes)
- Date d'upload : date-range picker (du … au …)
- Date EXIF : date-range picker sur la date de prise de vue réelle
- Appareil photo : input texte (recherche dans exif_make + exif_model)
- A du texte (OCR) : toggle oui/non
- A des coordonnées GPS : toggle oui/non
- Taille : slider min/max en Mo
Affichage des résultats :
- Toggle vue grille / vue liste
- Tri : date upload, date EXIF, taille, nom, client, confiance AI
- Pagination : 20/50/100 images par page
Vue grille — ImageCard.tsx pour chaque image :
┌────────────────────────┐
│ [thumbnail 300×200] │
│ ████ pipeline: done │
├────────────────────────┤
│ photo_vacances.jpg │
│ 📱 Shaarli • Premium │ ← badge application source (couleur unique par client)
│ 🏷️ paris, café, été │
│ 📅 23 fév 2026 │
│ 📷 Canon EOS R5 │
└────────────────────────┘
Vue liste — une ligne par image avec toutes les métadonnées condensées
Indicateur de provenance : chaque image porte un badge coloré affichant le nom de l'application/client source. La couleur est générée de manière déterministe depuis le client_id (toujours la même couleur pour le même client).
D.2 — src/pages/gallery/ImageDetail.tsx — Détail d'une image
En-tête :
- Nom du fichier + badge application source + date d'upload
- Boutons : Télécharger | Reprocesser | Supprimer | ← Retour galerie
Zone principale (layout 2 colonnes) :
Colonne gauche — Image :
- Affichage de l'image en taille réelle (via URL signée)
- Contrôles zoom + plein écran
Colonne droite — Onglets :
Onglet "Vision AI" :
- Description en langage naturel (paragraphe)
- Tags : chaque tag = badge cliquable (filtre la galerie)
- Score de confiance : barre colorée + pourcentage
- Modèle utilisé + tokens consommés (prompt / output)
- Date d'analyse AI
Onglet "EXIF" :
📷 Appareil Canon EOS R5
🔭 Objectif EF 50mm f/1.4 USM
📅 Prise de vue 23 fév 2026 à 14h32
⚙️ Paramètres ISO 400 · f/2.8 · 1/200s · 50mm
📍 Localisation 48.8566°N, 2.3522°E [Voir sur carte]
🧭 Altitude 32m
- Carte Leaflet avec marqueur si coordonnées GPS présentes
- Lien "Voir sur Google Maps" + "Voir sur OpenStreetMap"
Onglet "OCR" :
- Texte extrait en zone défilante (monospace)
- Langue détectée + niveau de confiance
- Si pas de texte : "Aucun texte détecté dans cette image"
Onglet "Technique" :
- Dimensions (largeur × hauteur px)
- Taille du fichier (formatée : KB / MB)
- Format (JPEG, PNG, WebP...)
- UUID de l'image
- Statut et durée du pipeline (EXIF Xms / OCR Xms / AI Xms)
- Lien vers le thumbnail
Onglet "Provenance" :
- Application/Client source avec toutes ses infos (nom, plan, date de création)
- Lien "Voir toutes les images de cette application"
- Quota utilisé par ce client au moment de l'upload (si disponible)
D.3 — src/pages/gallery/ClientGallery.tsx — Galerie par application
Identique à la galerie principale mais pré-filtrée sur un client spécifique.
En-tête de page :
Images de "Shaarli"
Plan Premium • 4 821 images • 12.4 GB utilisés
[Voir les détails du client] [Voir toutes les images]
Mêmes filtres et fonctionnalités que la galerie principale, avec le filtre client verrouillé sur ce client.
Livrable E — Hooks et couche API
src/api/client.ts — Client HTTP :
// Intercepteur automatique qui ajoute Authorization: Bearer <apiKey>
// depuis le store Zustand
// Gestion des erreurs : 401 → logout automatique, 429 → toast "Rate limit atteint"
async function apiGet<T>(path: string): Promise<T>
async function apiPost<T>(path: string, body?: unknown): Promise<T>
async function apiPatch<T>(path: string, body: unknown): Promise<T>
async function apiDelete(path: string): Promise<void>
src/utils/prometheus.ts — Parser Prometheus :
// Parse le format texte Prometheus vers des objets exploitables
// Exemple : hub_images_uploaded_total{client_id="abc",plan="premium"} 4821
interface PrometheusMetric {
name: string
labels: Record<string, string>
value: number
timestamp?: number
}
function parsePrometheus(text: string): PrometheusMetric[]
function getMetricValues(metrics: PrometheusMetric[], name: string): PrometheusMetric[]
function getMetricByLabels(metrics: PrometheusMetric[], name: string, labels: Record<string, string>): number | null
src/hooks/useWebSocket.ts — Hook WebSocket :
// Connexion WebSocket avec reconnexion automatique (backoff exponentiel)
// Authentification via ?token= en query param
function useWebSocket(url: string | null, options?: {
onMessage: (data: unknown) => void
onOpen?: () => void
onClose?: () => void
reconnectDelay?: number // défaut 2000ms
maxReconnectAttempts?: number // défaut 5
})
src/hooks/useImages.ts — Hook recherche images :
function useImages(filters: ImageFilters): {
images: Image[]
total: number
isLoading: boolean
error: Error | null
page: number
setPage: (n: number) => void
setFilters: (f: Partial<ImageFilters>) => void
refetch: () => void
}
interface ImageFilters {
clientId?: string
tags?: string[]
status?: string[]
search?: string // fulltext
dateFrom?: string
dateTo?: string
exifDateFrom?: string
exifDateTo?: string
hasGps?: boolean
hasOcr?: boolean
sortBy?: string
sortOrder?: "asc" | "desc"
pageSize?: number
}
Livrable F — Design et UX
Palette de couleurs :
Fond principal #0F172A (slate-900)
Fond sidebar #1E293B (slate-800)
Fond carte #1E293B (slate-800)
Bordure #334155 (slate-700)
Texte principal #F1F5F9 (slate-100)
Texte secondaire #94A3B8 (slate-400)
Accent primaire #3B82F6 (blue-500)
Accent hover #2563EB (blue-600)
Statuts :
ok / done #22C55E (green-500)
warning #F59E0B (amber-500)
error #EF4444 (red-500)
pending #6366F1 (indigo-500)
processing #3B82F6 (blue-500, animé)
Plans :
free #64748B (slate-500)
standard #3B82F6 (blue-500)
premium #F59E0B (amber-500)
Comportements UX attendus :
- Toutes les données se chargent avec un skeleton loader (pas de spinner global bloquant)
- Les actions destructives (suppression, désactivation) demandent confirmation via
ConfirmDialog - Les erreurs API s'affichent en toast non-bloquant (coin bas-droite, auto-dismiss 5s)
- Le rafraîchissement des métriques/santé est indiqué par un indicateur discret (point clignotant)
- Responsive : sidebar collapsable sur écrans < 768px, grille images adaptative
- Mode sombre uniquement (thème fixe, pas de toggle)
- Transitions douces entre les pages (100ms fade)
Livrable G — Tests et documentation
src/__tests__/ — Tests unitaires (Vitest + React Testing Library) :
prometheus.test.ts— Parser métriques (cas normaux + edge cases)formatters.test.ts— Formatage bytes, dates, duréesImageCard.test.tsx— Rendu avec et sans thumbnail, badge clientQuotaBar.test.tsx— Couleurs aux bons seuils (vert/orange/rouge)Login.test.tsx— Formulaire, validation, cas erreur 401
README.md — Documentation complète :
# Imago Admin Portal
Interface d'administration et de surveillance pour Imago Hub.
## Lancement rapide (Docker)
docker-compose up -d
# Portail disponible sur http://localhost:3000
# Se connecter avec la clé admin définie dans .env.backend (ADMIN_API_KEY)
## Développement local
npm install
VITE_API_BASE_URL=http://localhost:8000 npm run dev
# Portail disponible sur http://localhost:5173
## Variables d'environnement
VITE_API_BASE_URL URL du backend Imago Hub (défaut: http://localhost:8000)
VITE_APP_TITLE Titre affiché dans l'interface (défaut: "Imago Hub Admin")
## Architecture
[description de la structure du projet]
## Connexion
L'API Key admin est celle définie par ADMIN_API_KEY dans le backend.
Elle donne accès à la section administration complète.
<execution_rules>
Règles d'exécution
Ordre de réalisation
Implémenter dans l'ordre strict : A → B → C → D → E → F → G. Les livrables C et D peuvent être développés en parallèle une fois B terminé.
À chaque livrable
- Implémenter le code TypeScript/React complet — zéro
// TODOni fonctions vides - Vérifier que
npm run typecheckpasse sans erreur avant de continuer - Tester manuellement dans le navigateur après chaque page
Règles absolues de sécurité
- Jamais de
localStoragepour stocker la clé API — uniquement en mémoire Zustand - Jamais de
sessionStoragenon plus - Les clés API ne doivent jamais apparaître dans les logs de la console navigateur
- Les URLs signées doivent être demandées au backend à chaque affichage (ne pas les cacher longtemps)
Règles de qualité du code
- TypeScript strict :
strict: truedanstsconfig.json, zéroanyimplicite - Props typées : chaque composant a une interface
Propsexplicite - Pas de
useEffectpour le fetching : utiliser TanStack Query exclusivement - Accessibilité : attributs
aria-label,role,altsur toutes les images - Pas de clé hardcodée : toutes les URLs depuis
import.meta.env.VITE_API_BASE_URL
Gestion des états de chargement et d'erreur
Chaque composant qui fait des appels API doit gérer explicitement :
- État
isLoading→ skeleton loader ou spinner inline (jamais de spinner global) - État
error→ message d'erreur inline + bouton "Réessayer" - État
empty→ composantEmptyStateavec illustration et action suggérée
Critère de succès final
# Build Docker sans erreur
docker-compose build
# Lancer la stack complète
docker-compose up -d
# Le portail répond
curl -I http://localhost:3000
# → HTTP/1.1 200 OK
# Build de production sans erreur TypeScript
npm run typecheck && npm run build
Fonctionnellement :
- Se connecter avec une clé API admin → accéder au dashboard
- Créer un nouveau client → clé affichée une seule fois dans le modal
- Voir la galerie → toutes les images avec badge application source
- Rechercher "café" → résultats filtrés depuis description AI + tags + OCR
- Cliquer une image → voir EXIF + carte GPS + OCR + résultats AI
- Voir la page Santé → statuts de tous les composants en temps réel </execution_rules>
<deliverable_summary>
Résumé des livrables
| # | Contenu | Fichiers principaux |
|---|---|---|
| A | Projet Vite+React+TS, Dockerfile multi-stage, Nginx SPA, docker-compose complet | Dockerfile, nginx/default.conf, docker-compose.yml, package.json, vite.config.ts |
| B | Authentification (mémoire uniquement), routing, layout sidebar/topbar | Login.tsx, App.tsx, Sidebar.tsx, auth.store.ts |
| C | 8 pages admin : Dashboard, Clients, ClientDetail, Queue, Metrics, Health, Config, Logs | src/pages/admin/*.tsx + composants associés |
| D | 3 pages galerie : Gallery (search+filtres), ImageDetail (EXIF+carte+AI), ClientGallery | src/pages/gallery/*.tsx + ImageCard.tsx, GpsMap.tsx, ImageFilters.tsx |
| E | Client HTTP, parser Prometheus, hooks réutilisables | src/api/*.ts, src/hooks/*.ts, src/utils/prometheus.ts |
| F | Design tokens Tailwind, composants partagés, animations, responsive | tailwind.config.ts, src/components/shared/*.tsx, src/components/charts/*.tsx |
| G | Tests unitaires Vitest, README complet | src/__tests__/*.test.ts(x), README.md |
Résultat final : portail autonome dockerisé, accessible sur http://localhost:3000, permettant de superviser l'intégralité d'Imago Hub — clients, quotas, pipeline, métriques, santé des composants — et de consulter/rechercher toutes les images avec identification claire de leur application source.
</deliverable_summary>