112 lines
4.7 KiB
PHP
112 lines
4.7 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Live-Graph</title>
|
|
<style>
|
|
body, html { margin: 0; padding: 0; width: 100%; height: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
|
|
.chart-container { position: relative; width: 100%; height: 100%; padding: 10px; box-sizing: border-box; }
|
|
.loader { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
|
@keyframes blinker { 50% { opacity: 0; } }
|
|
.blink_me { animation: blinker 1s linear infinite; color: #dc3545; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="chart-container">
|
|
<canvas id="liveChartCanvas"></canvas>
|
|
<div class="loader" id="loader">
|
|
<p>Lade initiale Daten...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/plugins/axios/axios.min.js"></script>
|
|
<script src="/plugins/moment/moment.min.js"></script>
|
|
<script src="/plugins/chart.js/chart.4.4.6.js"></script>
|
|
<script src="/plugins/chart.js/chartjs-adapter-moment.min.js"></script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const rxId = params.get('rx_id');
|
|
const txId = params.get('tx_id');
|
|
const interfaceName = params.get('name');
|
|
const apiBaseUrl = '<?= $API_BASE_URL ?>';
|
|
|
|
let chartInstance = null;
|
|
let pollInterval = null;
|
|
|
|
document.title = `Live: ${interfaceName}`;
|
|
|
|
const addPointToChart = (datasetIndex, newPoint) => {
|
|
const dataset = chartInstance.data.datasets[datasetIndex].data;
|
|
if (dataset.length > 0 && dataset[dataset.length - 1].x >= newPoint.x) return;
|
|
dataset.push(newPoint);
|
|
if (dataset.length > 120) dataset.shift(); // Keep rolling 10-minute window (120 points @ 5s interval)
|
|
};
|
|
|
|
const fetchLatestValue = async (itemId) => {
|
|
try {
|
|
const res = await axios.get(`${apiBaseUrl}/liveData`, { params: { itemId } });
|
|
return res.data;
|
|
} catch (e) {
|
|
console.error(`Failed to fetch live data for item ${itemId}`, e);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
let isPolling = false;
|
|
const startPolling = () => {
|
|
pollInterval = setInterval(async () => {
|
|
if (isPolling) return; // Prevent overlapping requests
|
|
isPolling = true;
|
|
const [rxPoint, txPoint] = await Promise.all([
|
|
fetchLatestValue(rxId),
|
|
fetchLatestValue(txId)
|
|
]);
|
|
if (rxPoint) addPointToChart(0, rxPoint);
|
|
if (txPoint) addPointToChart(1, txPoint);
|
|
chartInstance.update('none');
|
|
isPolling = false;
|
|
}, 250);
|
|
};
|
|
|
|
const initChart = (initialData) => {
|
|
document.getElementById('loader').style.display = 'none';
|
|
const ctx = document.getElementById('liveChartCanvas').getContext('2d');
|
|
chartInstance = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
datasets: [
|
|
{ label: 'Empfangen', data: initialData[rxId] || [], borderColor: '#4CAF50', borderWidth: 1.5, pointRadius: 1, tension: 0.2, fill: true, backgroundColor: 'rgba(76, 175, 80, 0.1)' },
|
|
{ label: 'Gesendet', data: initialData[txId] || [], borderColor: '#2196F3', borderWidth: 1.5, pointRadius: 1, tension: 0.2, fill: true, backgroundColor: 'rgba(33, 150, 243, 0.1)' }
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true, maintainAspectRatio: false,
|
|
scales: {
|
|
x: { type: 'time', time: { unit: 'minute', tooltipFormat: 'HH:mm:ss' } },
|
|
y: { beginAtZero: true, title: { display: true, text: 'Mbps' } }
|
|
},
|
|
plugins: {
|
|
title: { display: true, text: `Live-Traffic für ${interfaceName}`},
|
|
legend: { position: 'bottom' }
|
|
}
|
|
}
|
|
});
|
|
startPolling();
|
|
};
|
|
|
|
// Fetch initial data for the last 10 minutes
|
|
axios.post(`${apiBaseUrl}/interfaceData`, { itemIds: [rxId, txId], timeRange: '10m' })
|
|
.then(res => initChart(res.data))
|
|
.catch(err => {
|
|
document.getElementById('loader').innerText = "Fehler beim Laden der initialen Daten.";
|
|
console.error(err);
|
|
});
|
|
|
|
window.addEventListener('beforeunload', () => clearInterval(pollInterval));
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |