TruthCheck-AI / templates /dashboard.html
CHRISDANIEL145
Fix: Add dashboard reload feedback and absolute DB path
47b16cf
Raw
History Blame Contribute Delete
14.4 kB
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Command Center | TruthCheck</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;900&display=swap"
rel="stylesheet">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
display: ['Orbitron', 'sans-serif'],
},
colors: {
brand: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
950: '#082f49',
},
neon: {
cyan: '#00f3ff',
purple: '#bc13fe',
}
},
animation: {
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'fade-in': 'fadeIn 0.5s ease-out forwards',
},
keyframes: {
fadeIn: {
'from': { opacity: '0' },
'to': { opacity: '1' }
}
}
}
}
}
</script>
<!-- Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body
class="bg-slate-950 text-white min-h-screen relative overflow-x-hidden selection:bg-brand-500 selection:text-white">
<!-- Background Grid Effect -->
<div class="fixed inset-0 z-0 opacity-20 pointer-events-none">
<div
class="absolute inset-0 bg-[linear-gradient(to_right,#4f4f4f2e_1px,transparent_1px),linear-gradient(to_bottom,#4f4f4f2e_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)]">
</div>
</div>
<!-- Navigation -->
<nav class="relative z-50 border-b border-white/10 backdrop-blur-md bg-slate-950/50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-20">
<a href="/" class="flex items-center gap-3 group">
<div
class="relative w-10 h-10 flex items-center justify-center bg-brand-500/10 rounded-lg border border-brand-500/50 shadow-[0_0_15px_rgba(14,165,233,0.3)] group-hover:shadow-[0_0_25px_rgba(14,165,233,0.5)] transition-shadow">
<i class="fa-solid fa-shield-halved text-brand-400 text-xl"></i>
</div>
<span
class="font-display font-bold text-2xl tracking-wider text-transparent bg-clip-text bg-gradient-to-r from-white to-slate-400">
TRUTH<span class="text-brand-400">CHECK</span>
</span>
</a>
<div class="hidden md:flex gap-8">
<a href="/"
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">Analyzer</a>
<a href="#" class="text-sm font-medium text-brand-400 border-b-2 border-brand-400">Dashboard</a>
<a href="/how-it-works"
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">How it
Works</a>
<a href="/api-docs"
class="text-sm font-medium text-slate-300 hover:text-brand-400 transition-colors">API</a>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="relative z-10 container mx-auto px-4 py-8">
<!-- Header -->
<div class="flex justify-between items-end mb-8 animate-fade-in">
<div>
<h1 class="font-display text-3xl md:text-4xl font-bold text-white mb-2">Command Center</h1>
<p class="text-slate-400">Global verification telemetry and history.</p>
</div>
<button onclick="loadHistory()"
class="bg-slate-800 hover:bg-slate-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors border border-white/5">
<i class="fa-solid fa-rotate-right mr-2"></i> Refresh
</button>
</div>
<!-- Stats Grid -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-12 animate-fade-in" style="animation-delay: 0.1s;">
<!-- Stat 1 -->
<div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm">
<div class="flex items-start justify-between">
<div>
<div class="text-slate-400 text-sm font-medium mb-1">Total Scans</div>
<div class="text-3xl font-display font-bold text-white" id="statTotal">0</div>
</div>
<div class="bg-brand-500/10 p-3 rounded-lg text-brand-400">
<i class="fa-solid fa-chart-line"></i>
</div>
</div>
</div>
<!-- Stat 2 -->
<div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm">
<div class="flex items-start justify-between">
<div>
<div class="text-slate-400 text-sm font-medium mb-1">Truth Rate</div>
<div class="text-3xl font-display font-bold text-white" id="statTrueRate">0%</div>
</div>
<div class="bg-green-500/10 p-3 rounded-lg text-green-400">
<i class="fa-solid fa-check"></i>
</div>
</div>
</div>
<!-- Stat 3 -->
<div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm">
<div class="flex items-start justify-between">
<div>
<div class="text-slate-400 text-sm font-medium mb-1">Misinfo Detected</div>
<div class="text-3xl font-display font-bold text-white" id="statFalseRate">0%</div>
</div>
<div class="bg-red-500/10 p-3 rounded-lg text-red-400">
<i class="fa-solid fa-triangle-exclamation"></i>
</div>
</div>
</div>
<!-- Stat 4 -->
<div class="bg-slate-900/50 border border-white/5 p-6 rounded-xl backdrop-blur-sm">
<div class="flex items-start justify-between">
<div>
<div class="text-slate-400 text-sm font-medium mb-1">Avg Confidence</div>
<div class="text-3xl font-display font-bold text-white" id="statAvgConf">0%</div>
</div>
<div class="bg-neon-purple/10 p-3 rounded-lg text-neon-purple">
<i class="fa-solid fa-bullseye"></i>
</div>
</div>
</div>
</div>
<!-- History Table -->
<div class="bg-slate-900/50 border border-white/5 rounded-2xl overflow-hidden backdrop-blur-sm animate-fade-in"
style="animation-delay: 0.2s;">
<div class="p-6 border-b border-white/5">
<h3 class="font-display text-xl font-bold text-white">Recent Verifications</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left">
<thead class="bg-slate-950/50 text-slate-400 uppercase text-xs font-medium">
<tr>
<th class="px-6 py-4">Status</th>
<th class="px-6 py-4">Claim</th>
<th class="px-6 py-4">Confidence</th>
<th class="px-6 py-4">Timestamp</th>
</tr>
</thead>
<tbody class="divide-y divide-white/5 text-sm" id="historyTableBody">
<!-- Rows injected via JS -->
</tbody>
</table>
</div>
<div id="emptyState" class="hidden p-12 text-center text-slate-500">
<i class="fa-solid fa-inbox text-4xl mb-4 opacity-50"></i>
<p>No verification history found.</p>
</div>
</div>
</main>
<script>
document.addEventListener('DOMContentLoaded', loadHistory);
async function loadHistory() {
const btn = document.querySelector('button[onclick="loadHistory()"]');
const originalContent = btn.innerHTML;
try {
// Set loading state
btn.disabled = true;
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin mr-2"></i> Loading...';
const response = await fetch('/api/history');
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
updateStats(data);
renderTable(data);
// Show success briefly
btn.innerHTML = '<i class="fa-solid fa-check mr-2"></i> Updated';
setTimeout(() => {
btn.innerHTML = originalContent;
btn.disabled = false;
}, 1000);
} catch (error) {
console.error("Failed to load history:", error);
btn.innerHTML = '<i class="fa-solid fa-triangle-exclamation mr-2"></i> Error';
alert("Failed to refresh history: " + error.message);
setTimeout(() => {
btn.innerHTML = originalContent;
btn.disabled = false;
}, 2000);
}
}
function updateStats(data) {
if (!data.length) return;
const total = data.length;
const trueCount = data.filter(i => i.label.toLowerCase() === 'true').length;
const falseCount = data.filter(i => i.label.toLowerCase() === 'false').length;
const totalConfidence = data.reduce((acc, curr) => acc + curr.confidence, 0);
const avgConf = total > 0 ? (totalConfidence / total) : 0;
document.getElementById('statTotal').textContent = total;
document.getElementById('statTrueRate').textContent = Math.round((trueCount / total) * 100) + '%';
document.getElementById('statFalseRate').textContent = Math.round((falseCount / total) * 100) + '%';
document.getElementById('statAvgConf').textContent = Math.round(avgConf * 100) + '%';
}
function renderTable(data) {
const tbody = document.getElementById('historyTableBody');
const emptyState = document.getElementById('emptyState');
tbody.innerHTML = '';
if (data.length === 0) {
emptyState.classList.remove('hidden');
return;
} else {
emptyState.classList.add('hidden');
}
data.forEach(row => {
const tr = document.createElement('tr');
tr.className = 'hover:bg-white/5 transition-colors';
// Status Badge
let statusBadge = '';
if (row.label.toLowerCase() === 'true') {
statusBadge = '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-400 border border-green-500/20"><i class="fa-solid fa-check"></i> TRUE</span>';
} else if (row.label.toLowerCase() === 'false') {
statusBadge = '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-red-500/10 text-red-400 border border-red-500/20"><i class="fa-solid fa-xmark"></i> FALSE</span>';
} else {
statusBadge = '<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-amber-500/10 text-amber-400 border border-amber-500/20"><i class="fa-solid fa-question"></i> LOW CONF</span>';
}
// Date
const date = new Date(row.date).toLocaleString();
tr.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">${statusBadge}</td>
<td class="px-6 py-4 text-slate-300 font-light truncate max-w-md" title="${row.claim}">${row.claim}</td>
<td class="px-6 py-4 text-slate-400 flex items-center gap-2">
<div class="w-16 h-1.5 bg-slate-800 rounded-full overflow-hidden">
<div class="h-full bg-brand-500" style="width: ${row.confidence * 100}%"></div>
</div>
${Math.round(row.confidence * 100)}%
</td>
<td class="px-6 py-4 text-slate-500 font-mono text-xs">${date}</td>
`;
tbody.appendChild(tr);
});
}
</script>
</body>
</html>