Files
Imago/doc/prompts/PROMPT_ADMIN_PORTAL_claude-opus.md
T
bruno 2f2a5e9b4a
CI / Lint & Format (push) Failing after 28s
CI / Tests (push) Has been skipped
CI / Security Scan (push) Failing after 8s
CI / Docker Build (push) Has been skipped
Add admin panel, WebSocket support, and API versioning
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.
2026-06-22 11:25:22 -04:00

42 KiB
Raw Blame History

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, premium avec 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 Hub

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

  1. Section Administration — Réservée aux opérateurs. Supervision complète du hub, gestion des clients, métriques, santé des composants, configuration.
  2. 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/clients avec 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ées
  • ImageCard.test.tsx — Rendu avec et sans thumbnail, badge client
  • QuotaBar.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

  1. Implémenter le code TypeScript/React complet — zéro // TODO ni fonctions vides
  2. Vérifier que npm run typecheck passe sans erreur avant de continuer
  3. Tester manuellement dans le navigateur après chaque page

Règles absolues de sécurité

  • Jamais de localStorage pour stocker la clé API — uniquement en mémoire Zustand
  • Jamais de sessionStorage non 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: true dans tsconfig.json, zéro any implicite
  • Props typées : chaque composant a une interface Props explicite
  • Pas de useEffect pour le fetching : utiliser TanStack Query exclusivement
  • Accessibilité : attributs aria-label, role, alt sur 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 → composant EmptyState avec 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>