# 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. ## 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)** : ```typescript { 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** : ```typescript { 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`) : ```typescript { 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) ``` ## 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 : ```json { "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 : ```dockerfile # 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`** : ```nginx 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) : ```yaml 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 : ```typescript interface AuthStore { apiKey: string | null // en mémoire uniquement — jamais persisted isAuthenticated: boolean login: (key: string) => Promise 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 : ```typescript // 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=` (à 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 : ```typescript // Intercepteur automatique qui ajoute Authorization: Bearer // depuis le store Zustand // Gestion des erreurs : 401 → logout automatique, 429 → toast "Rate limit atteint" async function apiGet(path: string): Promise async function apiPost(path: string, body?: unknown): Promise async function apiPatch(path: string, body: unknown): Promise async function apiDelete(path: string): Promise ``` **`src/utils/prometheus.ts`** — Parser Prometheus : ```typescript // 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 value: number timestamp?: number } function parsePrometheus(text: string): PrometheusMetric[] function getMetricValues(metrics: PrometheusMetric[], name: string): PrometheusMetric[] function getMetricByLabels(metrics: PrometheusMetric[], name: string, labels: Record): number | null ``` **`src/hooks/useWebSocket.ts`** — Hook WebSocket : ```typescript // 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 : ```typescript function useImages(filters: ImageFilters): { images: Image[] total: number isLoading: boolean error: Error | null page: number setPage: (n: number) => void setFilters: (f: Partial) => 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 : ```markdown # 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. ``` ## 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 ```bash # 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 ## 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.