| <!DOCTYPE html>
|
| <html lang="fr" class="dark">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>VoxiAI 📊 | Analytics Dashboard</title>
|
| <link rel="icon" type="image/png" href="/static/logo.png">
|
| <script src="https://cdn.tailwindcss.com"></script>
|
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&family=Syne:wght@700;800&display=swap" rel="stylesheet">
|
| <script>
|
| tailwind.config = {
|
| theme: {
|
| extend: {
|
| colors: { brand: '#f59e0b', dark: '#050505' },
|
| fontFamily: { sans: ['Plus Jakarta Sans', 'sans-serif'], display: ['Syne', 'sans-serif'] }
|
| }
|
| }
|
| }
|
| </script>
|
| <style>
|
| body { background-color: #050505; color: #e5e5e5; font-family: 'Plus Jakarta Sans', sans-serif; }
|
| .glass { background: rgba(15, 15, 15, 0.7); backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.08); }
|
| .bg-mesh {
|
| position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1;
|
| background: radial-gradient(circle at 50% -20%, #2b1d03 0%, #050505 60%);
|
| }
|
| tr { transition: all 0.2s; }
|
| tr:hover { background: rgba(245, 158, 11, 0.05); }
|
|
|
| @keyframes glow {
|
| 0%, 100% { text-shadow: 0 0 5px rgba(245, 158, 11, 0.3); }
|
| 50% { text-shadow: 0 0 15px rgba(245, 158, 11, 0.6); }
|
| }
|
| .name-glow { animation: glow 3s infinite; }
|
| </style>
|
| </head>
|
| <body class="p-4 md:p-8 min-h-screen">
|
| <div class="bg-mesh"></div>
|
|
|
| <div class="max-w-7xl mx-auto">
|
|
|
| <header class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-12">
|
| <div class="flex items-center gap-3">
|
| <div class="w-10 h-10 overflow-hidden rounded-xl shadow-lg shadow-brand/20">
|
| <img src="/static/logo.png" alt="VoxiAI Logo" class="w-full h-full object-cover">
|
| </div>
|
| <h1 class="font-display text-2xl">Voxi<span class="text-brand">AI</span> <span class="text-gray-500 text-lg ml-2">Analytics</span></h1>
|
| </div>
|
| <div class="flex items-center gap-4">
|
| <button onclick="loadStats()" class="p-3 glass rounded-xl hover:bg-white/5 transition-all text-brand">
|
| <i class="fas fa-sync-alt"></i>
|
| </button>
|
| <a href="/" class="px-6 py-3 glass rounded-xl hover:bg-white/5 transition-all font-semibold">
|
| Retour au site
|
| </a>
|
| </div>
|
| </header>
|
|
|
|
|
| <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
| <div class="glass p-6 rounded-[2rem]">
|
| <p class="text-gray-500 text-sm uppercase tracking-widest mb-2">Utilisateurs Actifs</p>
|
| <div class="flex items-center justify-between">
|
| <h3 id="stat-active" class="text-3xl font-bold">--</h3>
|
| <div class="w-10 h-10 bg-blue-500/10 rounded-lg flex items-center justify-center text-blue-500">
|
| <i class="fas fa-users"></i>
|
| </div>
|
| </div>
|
| </div>
|
| <div class="glass p-6 rounded-[2rem]">
|
| <p class="text-gray-500 text-sm uppercase tracking-widest mb-2">Total Visiteurs</p>
|
| <div class="flex items-center justify-between">
|
| <h3 id="stat-visitors" class="text-3xl font-bold">--</h3>
|
| <div class="w-10 h-10 bg-brand/10 rounded-lg flex items-center justify-center text-brand">
|
| <i class="fas fa-eye"></i>
|
| </div>
|
| </div>
|
| </div>
|
| <div class="glass p-6 rounded-[2rem]">
|
| <p class="text-gray-500 text-sm uppercase tracking-widest mb-2">Vidéos Traitées</p>
|
| <div class="flex items-center justify-between">
|
| <h3 id="stat-tasks" class="text-3xl font-bold">--</h3>
|
| <div class="w-10 h-10 bg-green-500/10 rounded-lg flex items-center justify-center text-green-500">
|
| <i class="fas fa-video"></i>
|
| </div>
|
| </div>
|
| </div>
|
| <div class="glass p-6 rounded-[2rem]">
|
| <p class="text-gray-500 text-sm uppercase tracking-widest mb-2">Taux de Succès</p>
|
| <div class="flex items-center justify-between">
|
| <h3 id="stat-success" class="text-3xl font-bold">--%</h3>
|
| <div class="w-10 h-10 bg-purple-500/10 rounded-lg flex items-center justify-center text-purple-500">
|
| <i class="fas fa-check-circle"></i>
|
| </div>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="glass rounded-[2rem] overflow-hidden mb-12">
|
| <div class="p-8 border-b border-white/5 flex items-center justify-between">
|
| <h2 class="text-xl font-bold">Retours Utilisateurs</h2>
|
| <i class="fas fa-comment-dots text-brand text-xl"></i>
|
| </div>
|
| <div class="p-8">
|
| <div id="feedback-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
| <p class="text-gray-500 italic">Chargement des feedbacks...</p>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="glass rounded-[2rem] overflow-hidden">
|
| <div class="p-8 border-b border-white/5 flex items-center justify-between">
|
| <h2 class="text-xl font-bold">Historique Récent</h2>
|
| <span class="px-4 py-1 bg-brand/10 text-brand rounded-full text-xs font-bold uppercase tracking-widest">Temps réel</span>
|
| </div>
|
| <div class="overflow-x-auto">
|
| <table class="w-full text-left">
|
| <thead>
|
| <tr class="text-gray-500 text-xs uppercase tracking-widest border-b border-white/5">
|
| <th class="px-8 py-5">Fichier</th>
|
| <th class="px-8 py-5">Client ID</th>
|
| <th class="px-8 py-5">Status</th>
|
| <th class="px-8 py-5">Progrès</th>
|
| <th class="px-8 py-5">Date</th>
|
| <th class="px-8 py-5">Action</th>
|
| </tr>
|
| </thead>
|
| <tbody id="history-body">
|
|
|
| </tbody>
|
| </table>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div id="feedback-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-6 bg-black/80 backdrop-blur-sm">
|
| <div class="glass max-w-md w-full rounded-[2.5rem] p-8 md:p-10 fade-in relative text-left">
|
| <button onclick="closeFeedback()" class="absolute top-6 right-6 text-gray-500 hover:text-white transition-colors">
|
| <i class="fas fa-times text-xl"></i>
|
| </button>
|
| <div class="text-center mb-8">
|
| <div class="w-16 h-16 bg-brand/20 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
| <i class="fas fa-comment-dots text-2xl text-brand"></i>
|
| </div>
|
| <h2 class="text-2xl font-bold mb-2">Votre avis compte !</h2>
|
| <p class="text-gray-400 text-sm">C'est un outil de test, vos retours nous aident à nous améliorer.</p>
|
| </div>
|
|
|
| <div class="space-y-6">
|
| <div>
|
| <p class="text-center mb-4 font-semibold">Appréciez-vous l'outil ?</p>
|
| <div class="flex justify-center gap-4">
|
| <button onclick="setRating(event, 'Oui')" class="rating-btn flex-1 py-3 rounded-xl border border-white/10 hover:border-brand/50 transition-all flex items-center justify-center gap-2">
|
| <i class="fas fa-thumbs-up text-green-500"></i> Oui
|
| </button>
|
| <button onclick="setRating(event, 'Non')" class="rating-btn flex-1 py-3 rounded-xl border border-white/10 hover:border-brand/50 transition-all flex items-center justify-center gap-2">
|
| <i class="fas fa-thumbs-down text-red-500"></i> Non
|
| </button>
|
| </div>
|
| </div>
|
|
|
| <div class="space-y-2">
|
| <label class="text-xs uppercase tracking-widest text-gray-500 font-bold">Feedback détaillé</label>
|
| <textarea id="feedback-text" rows="3" class="w-full bg-white/5 border border-white/10 rounded-2xl p-4 focus:outline-none focus:border-brand/50 transition-all text-sm text-white" placeholder="Dites-nous ce qu'on peut améliorer..."></textarea>
|
| </div>
|
|
|
| <button id="submit-feedback" onclick="sendFeedback()" class="w-full bg-brand text-black font-bold py-4 rounded-2xl shadow-xl shadow-brand/10 transition-all hover:scale-[1.02]">
|
| Envoyer le feedback
|
| </button>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <button onclick="openFeedback()" class="fixed bottom-6 right-6 w-14 h-14 bg-brand text-black rounded-full shadow-2xl flex items-center justify-center hover:scale-110 transition-all z-40 group">
|
| <i class="fas fa-comment-alt text-xl"></i>
|
| <span class="absolute right-full mr-4 px-4 py-2 bg-black/80 backdrop-blur glass rounded-xl text-xs font-bold text-white opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">Un retour ?</span>
|
| </button>
|
|
|
| <footer class="mt-12 py-8 text-center text-gray-500 text-sm">
|
| Ce site a été fait par <a href="https://www.linkedin.com/in/bessanh-shadrak-744049287/" target="_blank" class="text-brand font-semibold hover:underline name-glow">Shadrak BESSANH</a>
|
| </footer>
|
|
|
| <script src="/static/script.js"></script>
|
| <script>
|
| const urlParams = new URLSearchParams(window.location.search);
|
| const adminPassword = urlParams.get('password');
|
|
|
| async function loadStats() {
|
| try {
|
| const res = await fetch(`/api/stats?password=${adminPassword}`);
|
| const data = await res.json();
|
| if (data.error) throw new Error(data.error);
|
| document.getElementById('stat-active').innerText = data.active_users;
|
| document.getElementById('stat-visitors').innerText = data.total_visitors;
|
| document.getElementById('stat-tasks').innerText = data.total_tasks;
|
| document.getElementById('stat-success').innerText = data.success_rate + '%';
|
| } catch (e) { console.error(e); }
|
| }
|
|
|
| async function loadHistory() {
|
| try {
|
| const res = await fetch(`/api/history?password=${adminPassword}`);
|
| const data = await res.json();
|
| if (data.error) throw new Error(data.error);
|
| const body = document.getElementById('history-body');
|
| body.innerHTML = '';
|
|
|
| data.forEach(task => {
|
| const date = new Date(task.created_at).toLocaleString('fr-FR');
|
| const statusClass = task.status === 'Terminé' ? 'text-green-500' : (task.status === 'Erreur' ? 'text-red-500' : 'text-brand');
|
|
|
| const row = `
|
| <tr class="border-b border-white/5">
|
| <td class="px-8 py-5 font-medium truncate max-w-[200px]">${task.filename}</td>
|
| <td class="px-8 py-5 font-mono text-xs text-gray-500">${task.client_id}</td>
|
| <td class="px-8 py-5">
|
| <span class="flex items-center gap-2 ${statusClass}">
|
| <span class="w-2 h-2 rounded-full bg-current ${task.status !== 'Terminé' && task.status !== 'Erreur' ? 'animate-pulse' : ''}"></span>
|
| ${task.status}
|
| </span>
|
| </td>
|
| <td class="px-8 py-5">
|
| <div class="w-full bg-white/5 rounded-full h-1.5 max-w-[100px]">
|
| <div class="bg-brand h-1.5 rounded-full" style="width: ${task.progress}%"></div>
|
| </div>
|
| </td>
|
| <td class="px-8 py-5 text-sm text-gray-500">${date}</td>
|
| <td class="px-8 py-5">
|
| ${task.result_url ? `<a href="${task.result_url}" target="_blank" class="text-brand hover:underline">Voir</a>` : '--'}
|
| </td>
|
| </tr>
|
| `;
|
| body.innerHTML += row;
|
| });
|
| } catch (e) { console.error(e); }
|
| }
|
|
|
| async function loadFeedbacks() {
|
| try {
|
| const res = await fetch(`/api/feedbacks?password=${adminPassword}`);
|
| const data = await res.json();
|
| if (data.error) throw new Error(data.error);
|
| const container = document.getElementById('feedback-container');
|
|
|
| if (data.length === 0) {
|
| container.innerHTML = '<p class="text-gray-500 italic col-span-full">Aucun retour pour le moment.</p>';
|
| return;
|
| }
|
|
|
| container.innerHTML = '';
|
| data.forEach(fb => {
|
| const date = new Date(fb.created_at).toLocaleString('fr-FR');
|
| const ratingIcon = fb.rating === 'Oui' ? 'fa-thumbs-up text-green-500' : 'fa-thumbs-down text-red-500';
|
|
|
| const card = `
|
| <div class="glass p-6 rounded-2xl border border-white/5">
|
| <div class="flex justify-between items-start mb-4">
|
| <span class="text-[10px] text-gray-500 font-mono">${fb.client_id}</span>
|
| <i class="fas ${ratingIcon}"></i>
|
| </div>
|
| <p class="text-sm mb-4 leading-relaxed">${fb.comment || '<span class="text-gray-600 italic">Pas de commentaire</span>'}</p>
|
| <div class="text-[10px] text-gray-600">${date}</div>
|
| </div>
|
| `;
|
| container.innerHTML += card;
|
| });
|
| } catch (e) { console.error(e); }
|
| }
|
|
|
|
|
| loadStats();
|
| loadHistory();
|
| loadFeedbacks();
|
|
|
|
|
| setInterval(() => {
|
| loadStats();
|
| loadHistory();
|
| loadFeedbacks();
|
| }, 5000);
|
| </script>
|
| </body>
|
| </html>
|
|
|