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.
1079 lines
42 KiB
Markdown
1079 lines
42 KiB
Markdown
# 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
|
||
|
||
---
|
||
|
||
<role>
|
||
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.
|
||
</role>
|
||
|
||
<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)** :
|
||
```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)
|
||
```
|
||
</project_context>
|
||
|
||
<mission>
|
||
## 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<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 :
|
||
```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=<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 :
|
||
```typescript
|
||
// 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 :
|
||
```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<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 :
|
||
```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<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 :
|
||
```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.
|
||
```
|
||
</mission>
|
||
|
||
<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
|
||
```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
|
||
</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>
|