Files
office_translator/_bmad-output/implementation-artifacts/5-6-admin-manual-file-cleanup.md
Sepehr Ramezani 26bd096a06 feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure:
- Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud)
- Admin panel: user management, pricing, settings
- Glossary system with CSV import/export
- Subscription and tier quota management
- Security hardening (rate limiting, API key auth, path traversal fixes)
- Docker compose for dev, prod, and IONOS deployment
- Alembic migrations for new tables
- Frontend: dashboard, pricing page, landing page, i18n (en/fr)
- Test suite and verification scripts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-25 15:01:47 +02:00

352 lines
13 KiB
Markdown

# Story 5.6: admin-manual-file-cleanup
Status: done
## Story
En tant qu'**Admin**,
Je veux **déclencher le nettoyage manuel des fichiers temporaires orphelins**,
Afin de **libérer de l'espace disque sur le serveur**.
## Acceptance Criteria
1. **Page dédiée**: Le bouton de cleanup se trouve sur `/admin/system` (pas sur le dashboard principal)
2. **Bouton Cleanup**: Un bouton "Cleanup orphaned files" est visible et cliquable
3. **Exécution immédiate**: Au clic, le job de cleanup s'exécute immédiatement via `POST /api/v1/admin/cleanup/trigger`
4. **Résultat affiché**: Après exécution, afficher "X fichiers supprimés" (ex: "5 fichiers supprimés")
5. **Bouton désactivé**: Le bouton est désactivé pendant l'exécution du cleanup (état loading)
6. **Gestion d'erreurs**: En cas d'erreur, afficher un message utilisateur clair en français
7. **Feedback visuel**: Loader/spinner pendant l'exécution
8. **Refresh automatique**: Les stats du dashboard sont rafraîchies après un cleanup réussi
## Tasks / Subtasks
- [x] **Task 1: Créer le hook useSystemPage** (AC: #6, #8)
- [x] 1.1 Créer `frontend/src/app/admin/system/useSystemPage.ts`
- [x] 1.2 Combiner `useAdminDashboard` et `useCleanup` en un seul hook
- [x] 1.3 Exposer: data, isLoading, isPurging, triggerCleanup, purgeResult, error
- [x] 1.4 Gérer le refresh automatique après cleanup
- [x] **Task 2: Créer le composant CleanupSection** (AC: #2, #3, #4, #5, #7)
- [x] 2.1 Créer `frontend/src/app/admin/system/CleanupSection.tsx`
- [x] 2.2 Card avec icône Trash2, titre "Fichiers Temporaires"
- [x] 2.3 Afficher le nombre de fichiers trackés (tracked_files_count)
- [x] 2.4 Bouton "Nettoyer les fichiers orphelins" avec état loading
- [x] 2.5 Afficher le résultat après cleanup: "X fichiers supprimés"
- [x] 2.6 Désactiver le bouton pendant l'exécution (isPurging)
- [x] 2.7 Désactiver si tracked_files_count === 0
- [x] **Task 3: Créer le composant DiskSpaceCard** (AC: #1)
- [x] 3.1 Créer `frontend/src/app/admin/system/DiskSpaceCard.tsx`
- [x] 3.2 Afficher l'espace disque utilisé (used_percent)
- [x] 3.3 Afficher l'espace total et libre (total_gb, free_gb)
- [x] 3.4 Progress bar pour visualiser l'utilisation
- [x] 3.5 Réutiliser le pattern de SystemHealthCards.tsx
- [x] **Task 4: Modifier la page system/page.tsx** (AC: #1, #6)
- [x] 4.1 Importer useSystemPage hook
- [x] 4.2 Remplacer le placeholder par le contenu réel
- [x] 4.3 Header avec titre "Système" et description
- [x] 4.4 Layout en grille: DiskSpaceCard + CleanupSection
- [x] 4.5 Section ProviderStatus (réutiliser depuis admin/page.tsx)
- [x] 4.6 Gestion d'erreur globale avec message en français
- [x] **Task 5: Tests et validation** (AC: Tous)
- [x] 5.1 `npm run build` → 0 erreurs TypeScript
- [x] 5.2 Tester avec backend actif → cleanup fonctionne
- [x] 5.3 Tester états: loading, success, error
- [x] 5.4 Vérifier que le bouton est désactivé pendant l'exécution
- [x] 5.5 Vérifier le refresh des stats après cleanup
## Dev Notes
### 🏗️ Stack Technique
| Technologie | Version |
|-------------|---------|
| Next.js | 16.0.6 (App Router) |
| React | 19.2.0 |
| TanStack Query | v5 |
| Tailwind CSS | configuré |
| shadcn/ui | Card, Button, Progress |
| Lucide React | Trash2, HardDrive, Loader2, AlertCircle |
### 📁 Structure Cible (Colocation Pattern)
```
frontend/src/app/admin/system/
├── page.tsx # ⭐ Page principale (modifier)
├── useSystemPage.ts # ⭐ Nouveau: Hook combiné
├── CleanupSection.tsx # ⭐ Nouveau: Section cleanup
├── DiskSpaceCard.tsx # ⭐ Nouveau: Card espace disque
└── types.ts # Optionnel: Types spécifiques
```
**⚠️ Règle absolue (architecture.md):**
```
🚨 FICHIERS SPÉCIAUX: page.tsx, layout.tsx → TOUJOURS minuscules
🚨 COLOCATION: Components/hooks/types dans le dossier de leur page
```
### 🔗 API Endpoints
| Endpoint | Méthode | Description |
|----------|---------|-------------|
| `/api/v1/admin/dashboard` | GET | Stats système (disk, cleanup, providers) |
| `/api/v1/admin/cleanup/trigger` | POST | Déclencher le cleanup manuel |
### 📊 Response Format: POST /api/v1/admin/cleanup/trigger
```json
{
"status": "success",
"files_cleaned": 5,
"message": "Cleaned up 5 expired files"
}
```
**⚠️ Note:** Le backend ne retourne pas actuellement les MB libérés. L'UI affichera uniquement "X fichiers supprimés". Une amélioration backend future pourrait ajouter `bytes_freed` à la réponse.
### 📊 Response Format: GET /api/v1/admin/dashboard (section cleanup)
```json
{
"cleanup": {
"files_cleaned": 42,
"tracked_files_count": 5
},
"system": {
"disk": {
"used_percent": 45.2,
"total_gb": 500,
"free_gb": 274
}
}
}
```
### 🎨 Patterns UI à Réutiliser
**CleanupSection.tsx** - Section de cleanup:
```tsx
<Card className="py-0">
<CardContent className="flex items-center gap-3 px-4 py-3">
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-red-500/10">
<Trash2 className="size-4 text-red-500" />
</div>
<div className="flex flex-1 flex-col gap-0.5">
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
Fichiers Temporaires
</span>
<span className="text-sm font-semibold tabular-nums text-foreground">
{trackedFilesCount} fichiers orphelins
</span>
{purgeResult && (
<span className="text-[10px] text-green-500">
{purgeResult.files_cleaned} fichiers supprimés
</span>
)}
</div>
<Button
variant="outline"
size="sm"
className="h-7 shrink-0 gap-1.5 border-red-200/30 text-red-500 hover:bg-red-500/10"
onClick={onCleanup}
disabled={isPurging || trackedFilesCount === 0}
>
{isPurging ? (
<Loader2 className="size-3 animate-spin" />
) : (
<Trash2 className="size-3" />
)}
{isPurging ? "Nettoyage..." : trackedFilesCount === 0 ? "Propre" : "Nettoyer"}
</Button>
</CardContent>
</Card>
```
**useSystemPage.ts** - Hook combiné:
```tsx
"use client";
import { useAdminDashboard } from "../useAdminDashboard";
import { useCleanup } from "../useCleanup";
export function useSystemPage() {
const { data, isLoading, error, refetch } = useAdminDashboard();
const { isPurging, purgeResult, triggerCleanup, error: cleanupError } = useCleanup();
const handleCleanup = async () => {
await triggerCleanup();
refetch(); // Refresh stats after cleanup
};
return {
data,
isLoading,
error: error || cleanupError,
isPurging,
purgeResult,
handleCleanup,
};
}
```
### 🔄 Flux de données
```
User click "Nettoyer"
handleCleanup()
triggerCleanup() → POST /api/v1/admin/cleanup/trigger
↓ (isPurging = true, button disabled)
Response: { files_cleaned: 5 }
refetch() → GET /api/v1/admin/dashboard
↓ (isPurging = false, button enabled)
UI: "5 fichiers supprimés"
```
### ⚠️ Points d'Attention Critiques
1. **Authentification**: Tous les appels API doivent inclure le header `Authorization: Bearer {adminToken}`. Le token est stocké dans `useTranslationStore().settings.adminToken`.
2. **Réutiliser les hooks existants**:
- `useCleanup` existe déjà dans `admin/useCleanup.ts`
- `useAdminDashboard` existe déjà dans `admin/useAdminDashboard.ts`
- Ne PAS recréer ces hooks, les réutiliser
3. **Ne PAS dupliquer SystemHealthCards**: Le composant `SystemHealthCards.tsx` est utilisé sur le dashboard principal. Pour `/admin/system`, créer des composants dédiés plus détaillés.
4. **Gestion des erreurs**: Mapper les erreurs API vers des messages utilisateur en français:
- `AUTH_REQUIRED` → "Veuillez vous connecter"
- `UNAUTHORIZED` → "Session expirée"
- `HTTP_ERROR_403` → "Accès refusé"
5. **État du bouton**: Le bouton doit être désactivé dans deux cas:
- `isPurging === true` (cleanup en cours)
- `trackedFilesCount === 0` (pas de fichiers à nettoyer)
6. **ProviderStatus**: Réutiliser le composant `ProviderStatus.tsx` existant pour afficher le statut des providers.
### 📋 Checklist de Validation Avant Dev
- [x] `useCleanup.ts` et `useAdminDashboard.ts` existent et fonctionnent
- [x] Composants shadcn/ui disponibles (Card, Button, Progress)
- [x] Backend endpoint `/api/v1/admin/cleanup/trigger` opérationnel
- [x] Pattern d'authentification admin identifié (même que Story 5.3)
### 🚀 Integration Steps
1. **Créer useSystemPage.ts** - Hook combinant dashboard et cleanup
2. **Créer CleanupSection.tsx** - Composant de cleanup dédié
3. **Créer DiskSpaceCard.tsx** - Card espace disque détaillée
4. **Modifier system/page.tsx** - Intégrer tous les composants
5. **Tester** avec backend actif
### 📚 Previous Story Intelligence (Story 5.3, 5.4, 5.5)
**Learnings from previous Admin stories:**
- Utiliser TanStack Query `useQuery` pour le data fetching
- Le token admin est dans `useTranslationStore().settings.adminToken`
- Utiliser le pattern de colocation (components/hooks/types dans le dossier admin/system/)
- Les composants shadcn/ui sont disponibles: Card, Button, Progress, Badge, Tooltip
- Mapper les erreurs API vers des messages utilisateur en français
- Structure des fichiers: page.tsx (minuscule!), composants PascalCase dans le même dossier
**Files created in Story 5.5 for reference:**
- `frontend/src/app/admin/useTranslationStats.ts` - pattern useQuery à suivre
- `frontend/src/app/admin/StatsOverview.tsx` - pattern cards stats à suivre
- `frontend/src/app/admin/types.ts` - interfaces TypeScript
**Existing files to reuse:**
- `frontend/src/app/admin/useCleanup.ts` - hook mutation cleanup
- `frontend/src/app/admin/useAdminDashboard.ts` - hook data fetching
- `frontend/src/app/admin/ProviderStatus.tsx` - composant statut providers
- `frontend/src/app/admin/types.ts` - interfaces CleanupResponse, AdminDashboardData
### 🔍 Git Intelligence (Derniers commits)
```
3d37ce4 feat: Update Docker and Kubernetes for database infrastructure
550f351 feat: Add PostgreSQL database infrastructure
c4d6cae Production-ready improvements: security hardening, Redis sessions
```
**Insights:**
- Infrastructure base de données récemment mise à jour
- Redis configuré pour les sessions
- Focus sur la sécurité et la robustesse
### References
- [Source: _bmad-output/planning-artifacts/epics.md#Story-5.6] — Story requirements
- [Source: _bmad-output/planning-artifacts/architecture.md#Frontend-Architecture] — Colocation pattern
- [Source: _bmad-output/planning-artifacts/architecture.md#API-Response-Formats] — Response format {data, meta}
- [Source: routes/admin_routes.py#L396-L411] — Backend cleanup endpoint
- [Source: frontend/src/app/admin/useCleanup.ts] — Hook cleanup existant
- [Source: frontend/src/app/admin/SystemHealthCards.tsx] — Pattern UI cards existant
- [Source: frontend/src/app/admin/page.tsx] — Dashboard principal (référence)
## Dev Agent Record
### Agent Model Used
GLM-5 (zai-coding-plan/glm-5)
### Debug Log References
Aucun problème rencontré lors de l'implémentation.
### Completion Notes List
-**Task 1**: Hook `useSystemPage` créé - combine `useAdminDashboard` et `useCleanup` avec refresh automatique
-**Task 2**: Composant `CleanupSection` créé avec bouton, états loading, affichage résultat
-**Task 3**: Composant `DiskSpaceCard` créé avec progress bar et infos disque
-**Task 4**: Page `/admin/system` mise à jour avec layout grille et gestion d'erreurs
-**Task 5**: Build TypeScript réussi - 0 erreurs
**Implementation Summary:**
- Page `/admin/system` maintenant fonctionnelle avec:
- Affichage espace disque (used%, total, free)
- Section cleanup avec bouton "Nettoyer"
- États loading/success/error gérés
- Refresh automatique après cleanup
- Réutilisation du composant ProviderStatus existant
### File List
**Nouveaux fichiers:**
- `frontend/src/app/admin/system/useSystemPage.ts`
- `frontend/src/app/admin/system/CleanupSection.tsx`
- `frontend/src/app/admin/system/DiskSpaceCard.tsx`
- `frontend/src/app/admin/system/page.tsx`
## Senior Developer Review (AI)
**Date:** 2026-02-24
**Reviewer:** GLM-5 (Code Review Workflow)
### Issues Found: 2 HIGH, 3 MEDIUM, 2 LOW
| # | Severity | Issue | File | Status |
|---|----------|-------|------|--------|
| 1 | HIGH | Double-fetch inutile - `refetch()` redondant car `useCleanup` invalide déjà le cache | useSystemPage.ts:10-12 | ✅ Fixed |
| 2 | HIGH | Race condition - `refetch()` s'exécute même si `triggerCleanup()` échoue | useSystemPage.ts:10-13 | ✅ Fixed |
| 3 | MEDIUM | File List imprécis - `page.tsx` est un nouveau fichier, pas modifié | Story file | ✅ Fixed |
| 4 | MEDIUM | Export inutilisé - `refetch` exporté mais jamais utilisé | useSystemPage.ts:22 | ✅ Fixed |
| 5 | MEDIUM | Console warnings - `baseline-browser-mapping` outdated | package.json | ⚪ Skipped (minor) |
| 6 | LOW | Hardcoded strings sans i18n | page.tsx | ⚪ Skipped (future) |
| 7 | LOW | Type import inutilisé `AdminDashboardData` | CleanupSection.tsx:6 | ✅ Fixed |
### Fixes Applied
1. **useSystemPage.ts**: Simplifié `handleCleanup()` - suppression du `refetch()` redondant et de l'export inutilisé
2. **CleanupSection.tsx**: Supprimé l'import `AdminDashboardData` inutilisé
3. **Story File List**: Corrigé - `page.tsx` marqué comme nouveau fichier
### Verdict
**APPROVED** - Toutes les issues HIGH et MEDIUM critiques ont été corrigées. Code review terminé.