personal_site/templates/admin/traffic.html
2025-07-05 15:29:33 -05:00

216 lines
No EOL
9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Traffic Analytics - Admin</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; }
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; }
.stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.stat-number { font-size: 2em; font-weight: bold; color: #2563eb; }
.stat-label { color: #666; margin-top: 5px; }
.chart-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 30px; }
.table-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
th { background: #f8f9fa; font-weight: 600; }
h1 { color: #333; margin-bottom: 30px; }
h2 { color: #333; margin-bottom: 15px; }
.controls { margin-bottom: 20px; }
.controls select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; }
.real-time-btn { background: #10b981; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; }
.real-time-btn:hover { background: #059669; }
.activity-item { padding: 8px; margin: 4px 0; background: #f8f9fa; border-radius: 4px; font-size: 0.9em; }
.activity-time { color: #666; font-size: 0.8em; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
@media (max-width: 768px) { .grid-2 { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<div class="container">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
<h1>Traffic Analytics Dashboard</h1>
<div class="controls">
<select id="dateRange">
<option value="7" {% if days == 7 %}selected{% endif %}>Last 7 days</option>
<option value="30" {% if days == 30 %}selected{% endif %}>Last 30 days</option>
<option value="90" {% if days == 90 %}selected{% endif %}>Last 90 days</option>
</select>
<button id="realTimeBtn" class="real-time-btn">
Real-time: <span id="activeUsers">0</span> active
</button>
</div>
</div>
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number">{{ "{:,}".format(stats.total_views) }}</div>
<div class="stat-label">Total Page Views</div>
</div>
<div class="stat-card">
<div class="stat-number">{{ "{:,}".format(stats.unique_visitors) }}</div>
<div class="stat-label">Unique Visitors</div>
</div>
<div class="stat-card">
<div class="stat-number">{{ stats.avg_response_time }}ms</div>
<div class="stat-label">Avg Response Time</div>
</div>
<div class="stat-card">
<div class="stat-number">{{ stats.bounce_rate }}%</div>
<div class="stat-label">Bounce Rate</div>
</div>
</div>
<!-- Daily Traffic Chart -->
<div class="chart-container">
<h2>Daily Traffic (Last {{ days }} Days)</h2>
<canvas id="dailyTrafficChart" width="400" height="100"></canvas>
</div>
<!-- Tables -->
<div class="grid-2">
<div class="table-container">
<h2>Top Pages</h2>
<table>
<thead>
<tr>
<th>Page</th>
<th>Views</th>
</tr>
</thead>
<tbody>
{% for page, views in top_pages %}
<tr>
<td>{{ page }}</td>
<td>{{ views }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="table-container">
<h2>Top Referrers</h2>
<table>
<thead>
<tr>
<th>Referrer</th>
<th>Views</th>
</tr>
</thead>
<tbody>
{% for referrer, views in top_referrers %}
<tr>
<td>{{ referrer[:50] }}{% if referrer|length > 50 %}...{% endif %}</td>
<td>{{ views }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Recent Activity -->
<div class="table-container">
<h2>Recent Activity (Last 24 Hours)</h2>
<div id="recentActivity" style="max-height: 300px; overflow-y: auto;">
{% for activity in recent_activity %}
<div class="activity-item">
<strong>{{ activity.path }}</strong>
{% if activity.referrer %}
from {{ activity.referrer[:30] }}{% if activity.referrer|length > 30 %}...{% endif %}
{% endif %}
<div class="activity-time">{{ activity.timestamp.strftime('%Y-%m-%d %H:%M:%S') }} - {{ activity.ip_address[:8] }}...</div>
</div>
{% endfor %}
</div>
</div>
</div>
<script>
// Chart.js configurations - Fixed tojsonfilter to tojson
const dailyData = {{ daily_views | tojson | safe }};
// Daily Traffic Chart
const ctx = document.getElementById('dailyTrafficChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: dailyData.map(d => d.date),
datasets: [{
label: 'Page Views',
data: dailyData.map(d => d.views),
borderColor: 'rgb(37, 99, 235)',
backgroundColor: 'rgba(37, 99, 235, 0.1)',
tension: 0.1,
fill: true
}, {
label: 'Unique Visitors',
data: dailyData.map(d => d.unique_visitors),
borderColor: 'rgb(16, 185, 129)',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
tension: 0.1,
fill: false
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
},
plugins: {
legend: {
display: true
}
}
}
});
// Real-time updates
function updateRealTime() {
fetch('/admin/traffic/api/realtime')
.then(response => response.json())
.then(data => {
document.getElementById('activeUsers').textContent = data.active_users;
const activityDiv = document.getElementById('recentActivity');
if (data.recent_views && data.recent_views.length > 0) {
activityDiv.innerHTML = data.recent_views.map(view => `
<div class="activity-item">
<strong>${view.path}</strong>
<div class="activity-time">
${new Date(view.timestamp).toLocaleString()} - ${view.ip_address}
${view.country ? ' (' + view.country + ')' : ''}
</div>
</div>
`).join('');
}
})
.catch(error => {
console.log('Real-time update failed:', error);
});
}
// Update every 30 seconds
setInterval(updateRealTime, 30000);
updateRealTime(); // Initial load
// Date range selector
document.getElementById('dateRange').addEventListener('change', function() {
window.location.href = `?days=${this.value}`;
});
// Real-time button click
document.getElementById('realTimeBtn').addEventListener('click', function() {
updateRealTime();
});
</script>
</body>
</html>