n8n anonyme

This commit is contained in:
nBiqoz
2025-06-13 12:57:08 +02:00
parent 386950b630
commit 7fe5523d80
7 changed files with 1726 additions and 132 deletions

View File

@@ -8,14 +8,19 @@ import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import {
Upload, FileText, ShieldCheck, Download, Trash2, AlertCircle, X, Zap, Lock, Shield, Clock,
User, AtSign, MapPin, Cake, Home as HomeIcon, Venus, Phone, Building, Fingerprint, CreditCard, Check
User, AtSign, MapPin, Cake, Home as HomeIcon, Venus, Phone, Building, Fingerprint, CreditCard, Check,
Eye // <-- Icône ajoutée
} from "lucide-react";
import MarkdownModal from "./components/MarkdownModal"; // <-- Composant de la modale ajouté
// Interfaces et Données
// === Interfaces et Données ===
// L'interface est mise à jour pour inclure le contenu texte pour l'aperçu
interface ProcessedFile {
id: string; name: string; status: 'processing' | 'completed' | 'error';
timestamp: Date; originalSize?: string; processedSize?: string;
piiCount?: number; errorMessage?: string; processedBlob?: Blob;
textContent?: string; // <-- Ajouté pour stocker le contenu du fichier
}
interface AnonymizationOptions { [key: string]: boolean; }
@@ -44,6 +49,8 @@ export default function Home() {
const [anonymizationOptions, setAnonymizationOptions] = useState<AnonymizationOptions>(
piiOptions.reduce((acc, option) => ({ ...acc, [option.id]: true }), {})
);
// Nouvel état pour gérer la modale
const [modalContent, setModalContent] = useState<string | null>(null);
// === Fonctions utilitaires et Handlers ===
const handleOptionChange = (id: string) => setAnonymizationOptions(prev => ({ ...prev, [id]: !prev[id] }));
@@ -55,7 +62,27 @@ export default function Home() {
const clearFile = () => { setFile(null); setError(null); };
const clearHistory = () => setHistory([]);
const removeFromHistory = (id: string) => setHistory(prev => prev.filter(item => item.id !== id));
const handleDownload = (id: string) => { const fileToDownload = history.find(item => item.id === id); if (!fileToDownload?.processedBlob) return; const url = URL.createObjectURL(fileToDownload.processedBlob); const a = document.createElement('a'); a.href = url; a.download = `anonymized_${fileToDownload.name.split('.')[0] || 'file'}.txt`; a.click(); URL.revokeObjectURL(url); a.remove(); };
const handleDownload = (id: string) => {
const fileToDownload = history.find(item => item.id === id);
if (!fileToDownload?.processedBlob) return;
const url = URL.createObjectURL(fileToDownload.processedBlob);
const a = document.createElement('a');
a.href = url;
a.download = `anonymized_${fileToDownload.name.split('.')[0] || 'file'}.txt`;
a.click();
URL.revokeObjectURL(url);
a.remove();
};
// Nouvelle fonction pour gérer l'ouverture de l'aperçu
const handlePreview = (id: string) => {
const fileToPreview = history.find((item) => item.id === id);
if (fileToPreview?.textContent) {
setModalContent(fileToPreview.textContent);
}
};
const getStatusInfo = (item: ProcessedFile) => { switch (item.status) { case 'completed': return { icon: <ShieldCheck className="h-4 w-4 text-white" />, color: 'bg-[#061717]' }; case 'error': return { icon: <AlertCircle className="h-4 w-4 text-white" />, color: 'bg-[#F7AB6E]' }; default: return { icon: <div className="h-2 w-2 rounded-full bg-[#F7AB6E] animate-pulse" />, color: 'bg-[#061717]' }; } };
// === Fonction principale pour l'appel à n8n ===
@@ -75,9 +102,7 @@ export default function Home() {
try {
const formData = new FormData();
// On attache le fichier binaire
formData.append('file', file);
// On attache les options cochées sous forme de chaîne JSON
formData.append('options', JSON.stringify(anonymizationOptions));
setProgress(30);
@@ -91,9 +116,13 @@ export default function Home() {
const processedBlob = await response.blob();
const piiCount = parseInt(response.headers.get('X-Pii-Count') || '0', 10);
// On lit le contenu du blob en texte pour pouvoir l'afficher
const textContent = await processedBlob.text();
setProgress(90);
setHistory(prev => prev.map(item => item.id === fileId ? { ...item, status: 'completed', processedSize: formatFileSize(processedBlob.size), piiCount, processedBlob } : item));
// On met à jour l'historique avec toutes les informations, y compris le contenu texte
setHistory(prev => prev.map(item => item.id === fileId ? { ...item, status: 'completed', processedSize: formatFileSize(processedBlob.size), piiCount, processedBlob, textContent } : item));
setProgress(100);
} catch (err) {
@@ -127,7 +156,16 @@ export default function Home() {
) : (
history.map((item) => {
const status = getStatusInfo(item);
return (<div key={item.id} className="bg-[#061717] border-2 border-white shadow-[4px_4px_0_0_black] p-3 transition-all"><div className="flex items-start justify-between"><div className="flex items-center gap-3 min-w-0"><div className={`flex-shrink-0 w-7 h-7 border-2 border-white shadow-[2px_2px_0_0_black] flex items-center justify-center ${status.color}`}>{status.icon}</div><div className="min-w-0"><p className="text-sm font-black text-white uppercase truncate" title={item.name}>{item.name}</p>{item.status === 'completed' && (<div className="flex items-center gap-2 mt-1"><span className="text-xs font-bold text-white/70">{item.originalSize} {item.processedSize}</span><Badge className="bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] font-black uppercase text-[10px] px-1.5 py-0">{item.piiCount} PII</Badge></div>)}{item.status === 'error' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Erreur</p>}{item.status === 'processing' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Traitement...</p>}</div></div><div className="flex items-center gap-1.5 ml-2">{item.status === 'completed' && (<Button onClick={() => handleDownload(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#061717] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><Download className="h-3 w-3" /></Button>)}<Button onClick={() => removeFromHistory(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><X className="h-3 w-3" /></Button></div></div></div>);
return (<div key={item.id} className="bg-[#061717] border-2 border-white shadow-[4px_4px_0_0_black] p-3 transition-all"><div className="flex items-start justify-between"><div className="flex items-center gap-3 min-w-0"><div className={`flex-shrink-0 w-7 h-7 border-2 border-white shadow-[2px_2px_0_0_black] flex items-center justify-center ${status.color}`}>{status.icon}</div><div className="min-w-0"><p className="text-sm font-black text-white uppercase truncate" title={item.name}>{item.name}</p>{item.status === 'completed' && (<div className="flex items-center gap-2 mt-1"><span className="text-xs font-bold text-white/70">{item.originalSize} {item.processedSize}</span><Badge className="bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] font-black uppercase text-[10px] px-1.5 py-0">{item.piiCount} PII</Badge></div>)}{item.status === 'error' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Erreur</p>}{item.status === 'processing' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Traitement...</p>}</div></div><div className="flex items-center gap-1.5 ml-2">
{/* Le nouveau bouton "Aperçu" est ajouté ici */}
{item.status === 'completed' && item.textContent && (
<Button onClick={() => handlePreview(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#061717] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5">
<Eye className="h-3 w-3" />
</Button>
)}
{item.status === 'completed' && (<Button onClick={() => handleDownload(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#061717] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><Download className="h-3 w-3" /></Button>)}
<Button onClick={() => removeFromHistory(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><X className="h-3 w-3" /></Button>
</div></div></div>);
})
)}
</div>
@@ -169,6 +207,12 @@ export default function Home() {
</div>
</div>
</main>
{/* Le composant de la modale est rendu ici, en dehors du flux principal */}
<MarkdownModal
content={modalContent}
onClose={() => setModalContent(null)}
/>
</div>
);
}