From de09326c38b7651321500cb620ef1e8e38f8b272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD?= Date: Fri, 9 Jan 2026 21:05:02 +0300 Subject: [PATCH] minor UI fix, minot data processing improvements --- APP/openvpn_api_v3.py | 22 ++++++++++++++++------ APP/openvpn_gatherer_v3.py | 2 +- UI/client/src/components/HistoryModal.vue | 16 ++++++++++------ UI/client/src/composables/useFormatters.js | 22 ++++++++++++++++++---- UI/client/src/views/Analytics.vue | 4 ++-- 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/APP/openvpn_api_v3.py b/APP/openvpn_api_v3.py index 86f69f7..bb0728b 100644 --- a/APP/openvpn_api_v3.py +++ b/APP/openvpn_api_v3.py @@ -193,7 +193,7 @@ class OpenVPNAPI: # 1. Установка временных рамок if not end_date: - end_date = datetime.now() + end_date = datetime.utcnow() if not start_date: start_date = end_date - timedelta(hours=24) # Дефолт - сутки @@ -341,17 +341,24 @@ class OpenVPNAPI: for _ in range(points_count): current += timedelta(seconds=interval) ts_str = current.strftime('%Y-%m-%d %H:%M:%S') + ts_iso = ts_str.replace(' ', 'T') + 'Z' if ts_str in db_data_map: - final_data.append(db_data_map[ts_str]) + item = db_data_map[ts_str].copy() + item['timestamp'] = ts_iso + final_data.append(item) else: final_data.append({ - 'timestamp': ts_str, + 'timestamp': ts_iso, 'bytes_received': 0, 'bytes_sent': 0, 'bytes_received_rate_mbps': 0, 'bytes_sent_rate_mbps': 0 }) + else: + for item in final_data: + if 'timestamp' in item and isinstance(item['timestamp'], str): + item['timestamp'] = item['timestamp'].replace(' ', 'T') + 'Z' return { 'data': final_data, @@ -475,7 +482,7 @@ class OpenVPNAPI: # Post-processing: Zero Fill analytics['global_history_24h'] = [] - now = datetime.utcnow() + now = datetime.now(timezone.utc) # Round down to nearest interval ts_now = now.timestamp() ts_aligned = ts_now - (ts_now % interval_seconds) @@ -490,12 +497,15 @@ class OpenVPNAPI: for _ in range(96): current += timedelta(seconds=interval_seconds) ts_str = current.strftime('%Y-%m-%d %H:%M:%S') + ts_iso = ts_str.replace(' ', 'T') + 'Z' if ts_str in db_data: - analytics['global_history_24h'].append(db_data[ts_str]) + item = db_data[ts_str].copy() + item['timestamp'] = ts_iso + analytics['global_history_24h'].append(item) else: analytics['global_history_24h'].append({ - 'timestamp': ts_str, + 'timestamp': ts_iso, 'total_rx': 0, 'total_tx': 0, 'total_rx_rate': 0, diff --git a/APP/openvpn_gatherer_v3.py b/APP/openvpn_gatherer_v3.py index 9474726..710743a 100644 --- a/APP/openvpn_gatherer_v3.py +++ b/APP/openvpn_gatherer_v3.py @@ -35,7 +35,7 @@ class TimeSeriesAggregator: conn = self.db_provider() cursor = conn.cursor() - now = datetime.now() + now = datetime.utcnow() # --- РАСЧЕТ ВРЕМЕННЫХ КВАНТОВ --- # 1. Сутки (00:00:00) diff --git a/UI/client/src/components/HistoryModal.vue b/UI/client/src/components/HistoryModal.vue index 859ca9d..f5eec6a 100644 --- a/UI/client/src/components/HistoryModal.vue +++ b/UI/client/src/components/HistoryModal.vue @@ -147,20 +147,24 @@ const renderChart = () => { { label: !isSpeedMode.value ? 'Received (MB)' : 'RX Mbps', data: dataRx, - borderColor: '#27ae60', - backgroundColor: 'rgba(39, 174, 96, 0.1)', + borderColor: '#3fb950', + backgroundColor: 'rgba(63, 185, 80, 0.15)', borderWidth: 2, fill: true, - tension: 0.3 + tension: 0.3, + pointRadius: 3, + pointHoverRadius: 4 }, { label: !isSpeedMode.value ? 'Sent (MB)' : 'TX Mbps', data: dataTx, - borderColor: '#2980b9', - backgroundColor: 'rgba(41, 128, 185, 0.1)', + borderColor: '#58a6ff', + backgroundColor: 'rgba(88, 166, 255, 0.15)', borderWidth: 2, fill: true, - tension: 0.3 + tension: 0.3, + pointRadius: 3, + pointHoverRadius: 4 } ] }, diff --git a/UI/client/src/composables/useFormatters.js b/UI/client/src/composables/useFormatters.js index 314ca1b..8533bf7 100644 --- a/UI/client/src/composables/useFormatters.js +++ b/UI/client/src/composables/useFormatters.js @@ -14,11 +14,25 @@ export function useFormatters() { function parseServerDate(dateStr) { if (!dateStr) return null; - let isoStr = dateStr.replace(' ', 'T'); - if (!isoStr.endsWith('Z') && !isoStr.includes('+')) { - isoStr += 'Z'; + // Handle ISO strings with Z + if (dateStr.endsWith('Z')) return new Date(dateStr); + + // Assume format YYYY-MM-DD HH:MM:SS (standard backend output) + const [datePart, timePart] = dateStr.split(' '); + if (!datePart || !timePart) { + // Fallback for other formats + let isoStr = dateStr.replace(' ', 'T'); + if (!isoStr.endsWith('Z') && !isoStr.includes('+')) { + isoStr += 'Z'; + } + return new Date(isoStr); } - return new Date(isoStr); + + const [y, m, d] = datePart.split('-').map(Number); + const [h, min, s] = timePart.split(':').map(Number); + + // Construct Date in UTC + return new Date(Date.UTC(y, m - 1, d, h, min, s !== undefined ? s : 0)); } return { diff --git a/UI/client/src/views/Analytics.vue b/UI/client/src/views/Analytics.vue index bc85866..a4443b1 100644 --- a/UI/client/src/views/Analytics.vue +++ b/UI/client/src/views/Analytics.vue @@ -304,7 +304,7 @@ const renderMainChart = () => { borderWidth: 2, fill: true, tension: 0.3, - pointRadius: 0, + pointRadius: 3, pointHoverRadius: 4 }, { @@ -315,7 +315,7 @@ const renderMainChart = () => { borderWidth: 2, fill: true, tension: 0.3, - pointRadius: 0, + pointRadius: 3, pointHoverRadius: 4 } ]