233 lines
8.6 KiB
HTML
233 lines
8.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Speed Test Dashboard</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
|
|
<style>
|
|
body { font-family: sans-serif; padding: 20px; background: #f4f4f9; }
|
|
.controls { background: white; padding: 15px; border-radius: 8px; margin-bottom: 20px; display: flex; gap: 10px; align-items: center;}
|
|
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 20px; }
|
|
.card { background: white; padding: 20px; border-radius: 8px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
.card h3 { margin: 0 0 10px 0; color: #666; font-size: 0.9em; text-transform: uppercase; }
|
|
.card .value { font-size: 1.8em; font-weight: bold; color: #333; }
|
|
.chart-container { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; height: 400px; }
|
|
table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; }
|
|
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
|
|
th { background: #007bff; color: white; }
|
|
.failed-row { background-color: #ffe6e6; color: #b30000; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="controls">
|
|
<label>From: <input type="date" id="startDate"></label>
|
|
<label>To: <input type="date" id="endDate"></label>
|
|
<button onclick="fetchData()">Update Dashboard</button>
|
|
</div>
|
|
|
|
<div class="cards">
|
|
<div class="card">
|
|
<h3>Uptime</h3>
|
|
<div class="value" id="uptimeVal">--%</div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>Avg Download</h3>
|
|
<div class="value" id="avgDownVal">-- Mbps</div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>Avg Upload</h3>
|
|
<div class="value" id="avgUpVal">-- Mbps</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<canvas id="speedChart"></canvas>
|
|
</div>
|
|
|
|
<div class="chart-container" style="margin-top: 20px;">
|
|
<h3>Average Speed by Hour of Day (0-24h)</h3>
|
|
<canvas id="hourlyChart"></canvas>
|
|
</div>
|
|
|
|
<h3>Detailed Logs</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>ISP</th>
|
|
<th>Download (90th)</th>
|
|
<th>Upload (90th)</th>
|
|
<th>Latency</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tableBody">
|
|
</tbody>
|
|
</table>
|
|
|
|
<script>
|
|
let chartInstance = null;
|
|
|
|
async function fetchData() {
|
|
const start = document.getElementById('startDate').value;
|
|
const end = document.getElementById('endDate').value;
|
|
|
|
let url = '/api/data';
|
|
if(start && end) {
|
|
url += `?start=${start}&end=${end}`;
|
|
}
|
|
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
updateStats(data.stats);
|
|
updateChart(data.rows);
|
|
updateTable(data.rows);
|
|
}
|
|
|
|
function updateStats(stats) {
|
|
document.getElementById('uptimeVal').innerText = stats.uptime + '%';
|
|
document.getElementById('avgDownVal').innerText = stats.avg_down + ' Mbps';
|
|
document.getElementById('avgUpVal').innerText = stats.avg_up + ' Mbps';
|
|
}
|
|
|
|
function updateTable(rows) {
|
|
const tbody = document.getElementById('tableBody');
|
|
tbody.innerHTML = '';
|
|
|
|
// Show last 50 rows for performance (reverse order)
|
|
const recentRows = rows.slice().reverse().slice(0, 100);
|
|
|
|
recentRows.forEach(row => {
|
|
const tr = document.createElement('tr');
|
|
if(row.failed) tr.classList.add('failed-row');
|
|
|
|
tr.innerHTML = `
|
|
<td>${row.readable_date}</td>
|
|
<td>${row.isp || 'N/A'}</td>
|
|
<td>${row.down ? row.down.toFixed(2) : '-'}</td>
|
|
<td>${row.up ? row.up.toFixed(2) : '-'}</td>
|
|
<td>${row.latency ? row.latency.toFixed(2) + 'ms' : '-'}</td>
|
|
<td>${row.failed ? 'FAILED' : 'OK'}</td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
function updateChart(rows) {
|
|
const ctx = document.getElementById('speedChart').getContext('2d');
|
|
|
|
// Prepare data for Chart.js
|
|
const downData = rows.filter(r => !r.failed).map(r => ({x: r.timestamp, y: r.down}));
|
|
const upData = rows.filter(r => !r.failed).map(r => ({x: r.timestamp, y: r.up}));
|
|
|
|
if (chartInstance) {
|
|
chartInstance.destroy();
|
|
}
|
|
|
|
chartInstance = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
datasets: [
|
|
{
|
|
label: 'Download (90th)',
|
|
data: downData,
|
|
borderColor: '#007bff',
|
|
tension: 0.1
|
|
},
|
|
{
|
|
label: 'Upload (90th)',
|
|
data: upData,
|
|
borderColor: '#28a745',
|
|
tension: 0.1
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: {
|
|
type: 'time',
|
|
time: { unit: 'day' },
|
|
title: { display: true, text: 'Date' }
|
|
},
|
|
y: {
|
|
title: { display: true, text: 'Speed (Mbps)' },
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Set default dates (past 7 days) and load
|
|
window.onload = function() {
|
|
const today = new Date();
|
|
const lastWeek = new Date();
|
|
lastWeek.setDate(today.getDate() - 7);
|
|
|
|
document.getElementById('endDate').valueAsDate = today;
|
|
document.getElementById('startDate').valueAsDate = lastWeek;
|
|
|
|
fetchData();
|
|
fetchHourlyData();
|
|
};
|
|
|
|
async function fetchHourlyData() {
|
|
const response = await fetch('/api/hourly_stats');
|
|
const data = await response.json();
|
|
|
|
const ctx = document.getElementById('hourlyChart').getContext('2d');
|
|
|
|
new Chart(ctx, {
|
|
type: 'bar', // Bar chart is better for comparing buckets
|
|
data: {
|
|
labels: data.map(d => d.hour + ":00"),
|
|
datasets: [
|
|
{
|
|
label: 'Avg Download (Mbps)',
|
|
data: data.map(d => d.down),
|
|
backgroundColor: 'rgba(0, 123, 255, 0.7)',
|
|
},
|
|
{
|
|
label: 'Avg Upload (Mbps)',
|
|
data: data.map(d => d.up),
|
|
backgroundColor: 'rgba(40, 167, 69, 0.7)',
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: { beginAtZero: true, title: { display: true, text: 'Mbps' } }
|
|
},
|
|
plugins: {
|
|
annotation: {
|
|
annotations: {
|
|
box1: {
|
|
// Highlight the 1pm - 11pm danger zone
|
|
type: 'box',
|
|
xMin: 13,
|
|
xMax: 23,
|
|
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
|
borderWidth: 0,
|
|
label: { content: 'Problem Area', enabled: true }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Call this function in window.onload along with your existing fetchData()
|
|
// window.onload = function() { ... fetchHourlyData(); ... }
|
|
</script>
|
|
</body>
|
|
</html>
|