diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..bedf0db Binary files /dev/null and b/.DS_Store differ diff --git a/static/js/charts.js b/static/js/charts.js index bb88dff..be57012 100644 --- a/static/js/charts.js +++ b/static/js/charts.js @@ -7,132 +7,150 @@ class GPONCharts { init() { this.createOpticalChart(); this.createTemperatureChart(); + this.createTXBiasChart(); this.createTrafficChart(); this.createBytesChart(); this.createVolumeChart(); this.createFECChart(); - - this.setupGlobalPeriodSelector(); - + + // Period selectors document.getElementById('period-optical').addEventListener('change', (e) => { this.updateOpticalChart(e.target.value); }); - + document.getElementById('period-temperature').addEventListener('change', (e) => { this.updateTemperatureChart(e.target.value); }); - + + document.getElementById('period-txbias').addEventListener('change', (e) => { + this.updateTXBiasChart(e.target.value); + }); + document.getElementById('period-traffic').addEventListener('change', (e) => { this.updateTrafficChart(e.target.value); }); - + document.getElementById('period-bytes').addEventListener('change', (e) => { this.updateBytesChart(e.target.value); }); - + document.getElementById('period-volume').addEventListener('change', (e) => { this.updateVolumeChart(e.target.value); }); - + document.getElementById('period-fec').addEventListener('change', (e) => { this.updateFECChart(e.target.value); }); - + + // Volume chart type switcher document.querySelectorAll('input[name="volume-chart-type"]').forEach(radio => { radio.addEventListener('change', (e) => { this.changeVolumeChartType(e.target.value); }); }); - + + // Global period selector + this.setupGlobalPeriodSelector(); + + // Initial load this.updateOpticalChart('1h'); this.updateTemperatureChart('1h'); + this.updateTXBiasChart('7d'); // Default 7d for trends this.updateTrafficChart('1h'); this.updateBytesChart('1h'); this.updateVolumeChart('1h'); this.updateFECChart('1h'); - + + // Auto-refresh every 30s setInterval(() => { const optPeriod = document.getElementById('period-optical').value; const tempPeriod = document.getElementById('period-temperature').value; + const txbiasPeriod = document.getElementById('period-txbias').value; const trafficPeriod = document.getElementById('period-traffic').value; const bytesPeriod = document.getElementById('period-bytes').value; const volumePeriod = document.getElementById('period-volume').value; const fecPeriod = document.getElementById('period-fec').value; - + this.updateOpticalChart(optPeriod); this.updateTemperatureChart(tempPeriod); + this.updateTXBiasChart(txbiasPeriod); this.updateTrafficChart(trafficPeriod); this.updateBytesChart(bytesPeriod); this.updateVolumeChart(volumePeriod); this.updateFECChart(fecPeriod); }, 30000); - + console.log('[Charts] Initialized'); } setupGlobalPeriodSelector() { const radios = document.querySelectorAll('input[name="global-period"]'); - radios.forEach(radio => { radio.addEventListener('change', (e) => { const period = e.target.value; - document.getElementById('period-optical').value = period; document.getElementById('period-temperature').value = period; + document.getElementById('period-txbias').value = period; document.getElementById('period-traffic').value = period; document.getElementById('period-bytes').value = period; document.getElementById('period-volume').value = period; document.getElementById('period-fec').value = period; - + this.updateOpticalChart(period); this.updateTemperatureChart(period); + this.updateTXBiasChart(period); this.updateTrafficChart(period); this.updateBytesChart(period); this.updateVolumeChart(period); this.updateFECChart(period); - - console.log('[Charts] Global period: ' + period); + + console.log('[Charts] Global period:', period); }); }); } createOpticalChart() { const ctx = document.getElementById('chart-optical').getContext('2d'); - this.charts.optical = new Chart(ctx, { type: 'line', data: { labels: [], - datasets: [ - { - label: 'RX Power (dBm)', - data: [], - borderColor: '#00ccff', - backgroundColor: 'rgba(0, 204, 255, 0.1)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - }, - { - label: 'TX Power (dBm)', - data: [], - borderColor: '#ffcc00', - backgroundColor: 'rgba(255, 204, 0, 0.1)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - } - ] + datasets: [{ + label: 'RX Power (dBm)', + data: [], + borderColor: '#00ccff', + backgroundColor: 'rgba(0, 204, 255, 0.1)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }, { + label: 'TX Power (dBm)', + data: [], + borderColor: '#ffcc00', + backgroundColor: 'rgba(255, 204, 0, 0.1)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }] }, options: { responsive: true, maintainAspectRatio: false, - interaction: { mode: 'index', intersect: false }, + interaction: { + mode: 'index', + intersect: false + }, plugins: { - legend: { display: true, labels: { color: '#ffffff' } }, - tooltip: { mode: 'index', intersect: false } + legend: { + display: true, + labels: { color: '#ffffff' } + }, + tooltip: { + mode: 'index', + intersect: false + } }, scales: { x: { @@ -142,7 +160,11 @@ class GPONCharts { y: { ticks: { color: '#b0b0b0' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, - title: { display: true, text: 'dBm', color: '#b0b0b0' } + title: { + display: true, + text: 'dBm', + color: '#b0b0b0' + } } } } @@ -151,7 +173,6 @@ class GPONCharts { createTemperatureChart() { const ctx = document.getElementById('chart-temperature').getContext('2d'); - this.charts.temperature = new Chart(ctx, { type: 'line', data: { @@ -171,11 +192,14 @@ class GPONCharts { responsive: true, maintainAspectRatio: false, plugins: { - legend: { display: true, labels: { color: '#ffffff' } }, + legend: { + display: true, + labels: { color: '#ffffff' } + }, tooltip: { callbacks: { label: function(context) { - return context.parsed.y ? context.parsed.y.toFixed(1) + '°C' : 'N/A'; + return context.parsed.y ? `${context.parsed.y.toFixed(1)}°C` : 'N/A'; } } } @@ -188,107 +212,46 @@ class GPONCharts { y: { ticks: { color: '#b0b0b0' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, - title: { display: true, text: '°C', color: '#b0b0b0' } + title: { + display: true, + text: '°C', + color: '#b0b0b0' + } } } } }); } - createTrafficChart() { - const ctx = document.getElementById('chart-traffic').getContext('2d'); - - this.charts.traffic = new Chart(ctx, { + createTXBiasChart() { + const ctx = document.getElementById('chart-txbias').getContext('2d'); + this.charts.txbias = new Chart(ctx, { type: 'line', data: { labels: [], - datasets: [ - { - label: 'RX Packets/s', - data: [], - borderColor: '#4bc0c0', - backgroundColor: 'rgba(75, 192, 192, 0.1)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - }, - { - label: 'TX Packets/s', - data: [], - borderColor: '#ff6384', - backgroundColor: 'rgba(255, 99, 132, 0.1)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - } - ] + datasets: [{ + label: 'TX Bias (mA)', + data: [], + borderColor: '#9b59b6', + backgroundColor: 'rgba(155, 89, 182, 0.1)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { - legend: { display: true, labels: { color: '#ffffff' } }, - tooltip: { mode: 'index', intersect: false } - }, - scales: { - x: { - ticks: { color: '#b0b0b0', maxRotation: 0 }, - grid: { color: 'rgba(255, 255, 255, 0.1)' } + legend: { + display: true, + labels: { color: '#ffffff' } }, - y: { - ticks: { color: '#b0b0b0' }, - grid: { color: 'rgba(255, 255, 255, 0.1)' }, - title: { display: true, text: 'packets/s', color: '#b0b0b0' } - } - } - } - }); - } - - createBytesChart() { - const ctx = document.getElementById('chart-bytes').getContext('2d'); - - this.charts.bytes = new Chart(ctx, { - type: 'line', - data: { - labels: [], - datasets: [ - { - label: 'RX (Mb/s)', - data: [], - borderColor: '#4bc0c0', - backgroundColor: 'rgba(75, 192, 192, 0.1)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - }, - { - label: 'TX (Mb/s)', - data: [], - borderColor: '#ff6384', - backgroundColor: 'rgba(255, 99, 132, 0.1)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - } - ] - }, - options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { display: true, labels: { color: '#ffffff' } }, tooltip: { - mode: 'index', - intersect: false, callbacks: { label: function(context) { - return context.dataset.label + ': ' + - (context.parsed.y ? context.parsed.y.toFixed(2) : '0') + ' Mb/s'; + return context.parsed.y ? `${context.parsed.y.toFixed(2)} mA` : 'N/A'; } } } @@ -300,13 +263,143 @@ class GPONCharts { }, y: { ticks: { + color: '#b0b0b0', + callback: function(value) { + return value.toFixed(1) + ' mA'; + } + }, + grid: { color: 'rgba(255, 255, 255, 0.1)' }, + title: { + display: true, + text: 'mA', + color: '#b0b0b0' + } + } + } + } + }); + } + + createTrafficChart() { + const ctx = document.getElementById('chart-traffic').getContext('2d'); + this.charts.traffic = new Chart(ctx, { + type: 'line', + data: { + labels: [], + datasets: [{ + label: 'RX (Packets/s)', + data: [], + borderColor: '#4bc0c0', + backgroundColor: 'rgba(75, 192, 192, 0.1)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }, { + label: 'TX (Packets/s)', + data: [], + borderColor: '#ff6384', + backgroundColor: 'rgba(255, 99, 132, 0.1)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: true, + labels: { color: '#ffffff' } + }, + tooltip: { + mode: 'index', + intersect: false + } + }, + scales: { + x: { + ticks: { color: '#b0b0b0', maxRotation: 0 }, + grid: { color: 'rgba(255, 255, 255, 0.1)' } + }, + y: { + ticks: { color: '#b0b0b0' }, + grid: { color: 'rgba(255, 255, 255, 0.1)' }, + title: { + display: true, + text: 'packets/s', + color: '#b0b0b0' + } + } + } + } + }); + } + + createBytesChart() { + const ctx = document.getElementById('chart-bytes').getContext('2d'); + this.charts.bytes = new Chart(ctx, { + type: 'line', + data: { + labels: [], + datasets: [{ + label: 'RX (Mb/s)', + data: [], + borderColor: '#4bc0c0', + backgroundColor: 'rgba(75, 192, 192, 0.1)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }, { + label: 'TX (Mb/s)', + data: [], + borderColor: '#ff6384', + backgroundColor: 'rgba(255, 99, 132, 0.1)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: true, + labels: { color: '#ffffff' } + }, + tooltip: { + mode: 'index', + intersect: false, + callbacks: { + label: function(context) { + return `${context.dataset.label}: ${context.parsed.y ? context.parsed.y.toFixed(2) : 0} Mb/s`; + } + } + } + }, + scales: { + x: { + ticks: { color: '#b0b0b0', maxRotation: 0 }, + grid: { color: 'rgba(255, 255, 255, 0.1)' } + }, + y: { + ticks: { color: '#b0b0b0', callback: function(value) { return value.toFixed(1); } }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, - title: { display: true, text: 'Mb/s', color: '#b0b0b0' } + title: { + display: true, + text: 'Mb/s', + color: '#b0b0b0' + } } } } @@ -316,39 +409,39 @@ class GPONCharts { createVolumeChart() { const ctx = document.getElementById('chart-volume').getContext('2d'); const self = this; - + this.charts.volume = new Chart(ctx, { type: 'line', data: { labels: [], - datasets: [ - { - label: 'RX per minute', - data: [], - borderColor: '#4bc0c0', - backgroundColor: 'rgba(75, 192, 192, 0.05)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - }, - { - label: 'TX per minute', - data: [], - borderColor: '#ff6384', - backgroundColor: 'rgba(255, 99, 132, 0.05)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - } - ] + datasets: [{ + label: 'RX (per minute)', + data: [], + borderColor: '#4bc0c0', + backgroundColor: 'rgba(75, 192, 192, 0.05)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }, { + label: 'TX (per minute)', + data: [], + borderColor: '#ff6384', + backgroundColor: 'rgba(255, 99, 132, 0.05)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { - legend: { display: true, labels: { color: '#ffffff' } }, + legend: { + display: true, + labels: { color: '#ffffff' } + }, tooltip: { mode: 'index', intersect: false, @@ -356,7 +449,7 @@ class GPONCharts { label: function(context) { const bytes = context.parsed.y; const formatted = self.formatBytes(bytes); - return context.dataset.label + ': ' + formatted; + return `${context.dataset.label}: ${formatted}`; } } } @@ -369,14 +462,18 @@ class GPONCharts { }, y: { stacked: false, - ticks: { + ticks: { color: '#b0b0b0', callback: function(value) { return self.formatBytes(value); } }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, - title: { display: true, text: 'Data per minute', color: '#b0b0b0' } + title: { + display: true, + text: 'Data per minute', + color: '#b0b0b0' + } } } } @@ -385,39 +482,38 @@ class GPONCharts { createFECChart() { const ctx = document.getElementById('chart-fec').getContext('2d'); - this.charts.fec = new Chart(ctx, { type: 'line', data: { labels: [], - datasets: [ - { - label: 'FEC Corrected', - data: [], - borderColor: '#ffa500', - backgroundColor: 'rgba(255, 165, 0, 0.1)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - }, - { - label: 'FEC Uncorrected', - data: [], - borderColor: '#ff0000', - backgroundColor: 'rgba(255, 0, 0, 0.1)', - borderWidth: 2, - tension: 0.4, - fill: true, - pointRadius: 0 - } - ] + datasets: [{ + label: 'FEC Corrected', + data: [], + borderColor: '#ffa500', + backgroundColor: 'rgba(255, 165, 0, 0.1)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }, { + label: 'FEC Uncorrected', + data: [], + borderColor: '#ff0000', + backgroundColor: 'rgba(255, 0, 0, 0.1)', + borderWidth: 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { - legend: { display: true, labels: { color: '#ffffff' } } + legend: { + display: true, + labels: { color: '#ffffff' } + } }, scales: { x: { @@ -427,7 +523,11 @@ class GPONCharts { y: { ticks: { color: '#b0b0b0' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, - title: { display: true, text: 'errors/s', color: '#b0b0b0' } + title: { + display: true, + text: 'errors/s', + color: '#b0b0b0' + } } } } @@ -436,50 +536,51 @@ class GPONCharts { changeVolumeChartType(type) { if (!this.charts.volume) return; - + console.log('[Charts] Changing volume chart to:', type); - + const currentData = { labels: this.charts.volume.data.labels, datasets: this.charts.volume.data.datasets }; - + this.charts.volume.destroy(); + const ctx = document.getElementById('chart-volume').getContext('2d'); const self = this; - + const config = { type: type, data: { labels: currentData.labels, - datasets: [ - { - label: 'RX per minute', - data: currentData.datasets[0].data, - borderColor: '#4bc0c0', - backgroundColor: type === 'bar' ? 'rgba(75, 192, 192, 0.6)' : 'rgba(75, 192, 192, 0.05)', - borderWidth: type === 'bar' ? 1 : 2, - tension: 0.4, - fill: true, - pointRadius: 0 - }, - { - label: 'TX per minute', - data: currentData.datasets[1].data, - borderColor: '#ff6384', - backgroundColor: type === 'bar' ? 'rgba(255, 99, 132, 0.6)' : 'rgba(255, 99, 132, 0.05)', - borderWidth: type === 'bar' ? 1 : 2, - tension: 0.4, - fill: true, - pointRadius: 0 - } - ] + datasets: [{ + label: 'RX (per minute)', + data: currentData.datasets[0].data, + borderColor: '#4bc0c0', + backgroundColor: type === 'bar' ? 'rgba(75, 192, 192, 0.6)' : 'rgba(75, 192, 192, 0.05)', + borderWidth: type === 'bar' ? 1 : 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }, { + label: 'TX (per minute)', + data: currentData.datasets[1].data, + borderColor: '#ff6384', + backgroundColor: type === 'bar' ? 'rgba(255, 99, 132, 0.6)' : 'rgba(255, 99, 132, 0.05)', + borderWidth: type === 'bar' ? 1 : 2, + tension: 0.4, + fill: true, + pointRadius: 0 + }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { - legend: { display: true, labels: { color: '#ffffff' } }, + legend: { + display: true, + labels: { color: '#ffffff' } + }, tooltip: { mode: 'index', intersect: false, @@ -487,7 +588,7 @@ class GPONCharts { label: function(context) { const bytes = context.parsed.y; const formatted = self.formatBytes(bytes); - return context.dataset.label + ': ' + formatted; + return `${context.dataset.label}: ${formatted}`; } } } @@ -500,39 +601,41 @@ class GPONCharts { }, y: { stacked: false, - ticks: { + ticks: { color: '#b0b0b0', callback: function(value) { return self.formatBytes(value); } }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, - title: { display: true, text: 'Data per minute', color: '#b0b0b0' } + title: { + display: true, + text: 'Data per minute', + color: '#b0b0b0' + } } } } }; - + this.charts.volume = new Chart(ctx, config); } async updateOpticalChart(period) { try { - const response = await fetch('/api/history/optical/' + period); + const response = await fetch(`/api/history/optical/${period}`); const data = await response.json(); - + if (!data || !data.timestamps || data.timestamps.length === 0) { console.warn('[Charts] No optical data'); return; } - + const labels = this.formatLabels(data.timestamps, period); - this.charts.optical.data.labels = labels; - this.charts.optical.data.datasets[0].data = data.data.rx_power || []; - this.charts.optical.data.datasets[1].data = data.data.tx_power || []; + this.charts.optical.data.datasets[0].data = data.data.rx_power; + this.charts.optical.data.datasets[1].data = data.data.tx_power; this.charts.optical.update(); - } catch (error) { console.error('[Charts] Error optical:', error); } @@ -540,39 +643,57 @@ class GPONCharts { async updateTemperatureChart(period) { try { - const response = await fetch('/api/history/optical/' + period); + const response = await fetch(`/api/history/optical/${period}`); const data = await response.json(); - - if (!data || !data.timestamps || data.timestamps.length === 0) return; - + + if (!data || !data.timestamps || data.timestamps.length === 0) { + return; + } + const labels = this.formatLabels(data.timestamps, period); - this.charts.temperature.data.labels = labels; - this.charts.temperature.data.datasets[0].data = data.data.temperature || []; + this.charts.temperature.data.datasets[0].data = data.data.temperature; this.charts.temperature.update(); - } catch (error) { console.error('[Charts] Error temperature:', error); } } + async updateTXBiasChart(period) { + try { + const response = await fetch(`/api/history/optical/${period}`); + const data = await response.json(); + + if (!data || !data.timestamps || data.timestamps.length === 0) { + return; + } + + const labels = this.formatLabels(data.timestamps, period); + this.charts.txbias.data.labels = labels; + this.charts.txbias.data.datasets[0].data = data.data.tx_bias; + this.charts.txbias.update(); + } catch (error) { + console.error('[Charts] Error TX Bias:', error); + } + } + async updateTrafficChart(period) { try { - const response = await fetch('/api/history/traffic/' + period); + const response = await fetch(`/api/history/traffic/${period}`); const data = await response.json(); - - if (!data || !data.timestamps || data.timestamps.length === 0) return; - + + if (!data || !data.timestamps || data.timestamps.length === 0) { + return; + } + const labels = this.formatLabels(data.timestamps, period); - - const rxPps = data.data.rx_packets || []; - const txPps = data.data.tx_packets || []; - + const rxPps = data.data.rx_packets; + const txPps = data.data.tx_packets; + this.charts.traffic.data.labels = labels; this.charts.traffic.data.datasets[0].data = rxPps; this.charts.traffic.data.datasets[1].data = txPps; this.charts.traffic.update(); - } catch (error) { console.error('[Charts] Error traffic:', error); } @@ -580,27 +701,23 @@ class GPONCharts { async updateBytesChart(period) { try { - const response = await fetch('/api/history/traffic/' + period); + const response = await fetch(`/api/history/traffic/${period}`); const data = await response.json(); - - if (!data || !data.timestamps || data.timestamps.length === 0) return; - + + if (!data || !data.timestamps || data.timestamps.length === 0) { + return; + } + const labels = this.formatLabels(data.timestamps, period); - - const rxMbps = (data.data.rx_bytes || []).map(v => - v !== null ? (v * 8) / 1000000 : null - ); - const txMbps = (data.data.tx_bytes || []).map(v => - v !== null ? (v * 8) / 1000000 : null - ); - + const rxMbps = data.data.rx_bytes.map(v => v !== null ? (v * 8) / 1000000 : null); + const txMbps = data.data.tx_bytes.map(v => v !== null ? (v * 8) / 1000000 : null); + this.charts.bytes.data.labels = labels; this.charts.bytes.data.datasets[0].data = rxMbps; this.charts.bytes.data.datasets[1].data = txMbps; this.charts.bytes.update(); - + console.log('[Charts] Bytes - max RX Mb/s:', Math.max(...rxMbps.filter(v => v !== null)).toFixed(2)); - } catch (error) { console.error('[Charts] Error bytes:', error); } @@ -608,32 +725,28 @@ class GPONCharts { async updateVolumeChart(period) { try { - const response = await fetch('/api/history/traffic/' + period); + const response = await fetch(`/api/history/traffic/${period}`); const data = await response.json(); - - if (!data || !data.timestamps || data.timestamps.length === 0) return; - + + if (!data || !data.timestamps || data.timestamps.length === 0) { + return; + } + const labels = this.formatLabels(data.timestamps, period); - - const rxBytesRate = data.data.rx_bytes || []; - const txBytesRate = data.data.tx_bytes || []; + const rxBytesRate = data.data.rx_bytes; + const txBytesRate = data.data.tx_bytes; const step = data.step || 60; - - const rxPerInterval = rxBytesRate.map(rate => - rate !== null ? rate * step : null - ); - const txPerInterval = txBytesRate.map(rate => - rate !== null ? rate * step : null - ); - + + const rxPerInterval = rxBytesRate.map(rate => rate !== null ? rate * step : null); + const txPerInterval = txBytesRate.map(rate => rate !== null ? rate * step : null); + this.charts.volume.data.labels = labels; this.charts.volume.data.datasets[0].data = rxPerInterval; this.charts.volume.data.datasets[1].data = txPerInterval; this.charts.volume.update(); - + const maxRx = Math.max(...rxPerInterval.filter(v => v !== null)); console.log('[Charts] Volume - max per minute:', this.formatBytes(maxRx)); - } catch (error) { console.error('[Charts] Error volume:', error); } @@ -641,21 +754,21 @@ class GPONCharts { async updateFECChart(period) { try { - const response = await fetch('/api/history/fec/' + period); + const response = await fetch(`/api/history/fec/${period}`); const data = await response.json(); - - if (!data || !data.timestamps || data.timestamps.length === 0) return; - + + if (!data || !data.timestamps || data.timestamps.length === 0) { + return; + } + const labels = this.formatLabels(data.timestamps, period); - - const correctedRate = data.data.corrected || []; - const uncorrectedRate = data.data.uncorrected || []; - + const correctedRate = data.data.corrected; + const uncorrectedRate = data.data.uncorrected; + this.charts.fec.data.labels = labels; this.charts.fec.data.datasets[0].data = correctedRate; this.charts.fec.data.datasets[1].data = uncorrectedRate; this.charts.fec.update(); - } catch (error) { console.error('[Charts] Error FEC:', error); } @@ -664,12 +777,10 @@ class GPONCharts { formatLabels(timestamps, period) { return timestamps.map(ts => { const date = new Date(ts * 1000); - if (period === '7d' || period === '14d' || period === '30d' || period === '60d' || period === '90d') { return date.toLocaleDateString('pl-PL', { day: '2-digit', month: '2-digit' }) + ' ' + date.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' }); } - return date.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' }); }); } @@ -677,16 +788,14 @@ class GPONCharts { formatBytes(bytes, decimals = 2) { if (bytes === null || bytes === undefined) return 'N/A'; if (bytes === 0) return '0 B'; - const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } } document.addEventListener('DOMContentLoaded', () => { window.charts = new GPONCharts(); -}); +}); \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 4c03d0d..ee2cd6f 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,69 +4,75 @@
- -