From 18c67fd728eb32bab699c362551d7766ac6994ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Wed, 4 Feb 2026 14:55:40 +0100 Subject: [PATCH] poprawki --- app.py | 52 ++++++++++++----- static/css/style.css | 134 +++++++++++++++++++++++++++++++++++++------ static/js/monitor.js | 93 ++++++++++++++++++++---------- templates/index.html | 4 +- 4 files changed, 218 insertions(+), 65 deletions(-) diff --git a/app.py b/app.py index 30e5f74..9062719 100644 --- a/app.py +++ b/app.py @@ -7,7 +7,7 @@ import logging from flask import Flask, render_template, jsonify, request from flask_socketio import SocketIO from influxdb import InfluxDBClient -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import config import hashlib @@ -144,17 +144,22 @@ def get_timeseries(phase_id): finally: client.close() -from datetime import datetime, timedelta, timezone - @app.route('/api/events') def get_events(): client = get_influx_client() range_p = request.args.get('range', '24h') start_p = request.args.get('start') end_p = request.args.get('end') - now_utc = datetime.now(timezone.utc) + # Blokada dla zakresów powyżej 61 dni + if not (start_p and end_p) and "d" in range_p: + try: + days = int(''.join(filter(str.isdigit, range_p))) + if days > 61: + return jsonify({"error": "range_too_large", "message": "Zbyt duży zakres. Zaznacz obszar na wykresie."}) + except: pass + if start_p and end_p: dt_view_start = datetime.fromisoformat(start_p.replace('Z', '+00:00')) dt_view_end = datetime.fromisoformat(end_p.replace('Z', '+00:00')) @@ -164,7 +169,6 @@ def get_events(): num = int(''.join(filter(str.isdigit, clean_range))) unit = clean_range[-1] delta = timedelta(hours=num) if unit == 'h' else timedelta(days=num) - dt_view_start = now_utc - delta dt_view_end = now_utc time_filter = f"time > now() - {clean_range} - 24h" @@ -172,12 +176,13 @@ def get_events(): all_events = [] try: for p_id, p_cfg in config.PHASES.items(): + # min() + fill(0) dla pewnego wykrywania zaników query = f''' - SELECT mean("value") AS volts + SELECT min("value") AS volts FROM "{config.MEASUREMENT}" WHERE "entity_id" = '{p_cfg["entity_id"]}' AND {time_filter} - GROUP BY time(1m) fill(none) + GROUP BY time(1m) fill(0) ''' result = client.query(query) points = list(result.get_points()) @@ -185,14 +190,31 @@ def get_events(): i = 0 while i < len(points): val = points[i].get('volts') - if val is not None and float(val) < 207: + if val is None: + i += 1 + continue + + v_now = float(val) + ev_type = None + + if v_now < 100: ev_type = "zanik" + elif 100 <= v_now < 207: ev_type = "niskie" + elif v_now > 253: ev_type = "wysokie" + + if ev_type: start_str = points[i]['time'] dt_s = datetime.fromisoformat(start_str.replace('Z', '+00:00')) - j = i while j + 1 < len(points): - v_next = points[j+1].get('volts') - if v_next is not None and float(v_next) < 207: + v_next_val = points[j+1].get('volts') + next_type = None + if v_next_val is not None: + v_next = float(v_next_val) + if v_next < 100: next_type = "zanik" + elif 100 <= v_next < 207: next_type = "niskie" + elif v_next > 253: next_type = "wysokie" + + if next_type == ev_type: j += 1 else: break @@ -200,19 +222,19 @@ def get_events(): end_str = points[j]['time'] dt_e = datetime.fromisoformat(end_str.replace('Z', '+00:00')) - duration = (dt_e - dt_s).total_seconds() / 120 + duration = (dt_e - dt_s).total_seconds() / 60 + 1 - if duration >= 0.5: + if duration >= 5.1: if dt_e >= dt_view_start and dt_s <= dt_view_end: all_events.append({ "start": start_str, "end": end_str, "phase": p_id, - "duration": round(duration, 1) + "type": ev_type, + "duration": int(round(duration, 0)) }) i = j i += 1 - return jsonify(sorted(all_events, key=lambda x: x['start'], reverse=True)) except Exception as e: app.logger.error(f"Event Logic Error: {e}") diff --git a/static/css/style.css b/static/css/style.css index 5c20d6f..09ee2ac 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -5,6 +5,7 @@ --text-main: #c9d1d9; --blue-accent: #58a6ff; } + body { background-color: var(--bg-dark); color: var(--text-main); @@ -13,12 +14,15 @@ body { margin: 0; overflow-x: hidden; } + +/* Siatka wskaźników (gauges) */ .gauge-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 15px; } + .gauge-card { background-color: var(--card-bg); border: 1px solid var(--border-color); @@ -26,41 +30,53 @@ body { padding: 10px 5px; text-align: center; } + .gauge-canvas-container { max-width: 80px; margin: 0 auto; } + .gauge-label { font-size: 0.75rem; font-weight: 600; color: var(--blue-accent); margin-top: 2px; } + .voltage-value { font-size: 1.1rem; font-weight: 800; color: #ffffff; } + +/* Wybór zakresu czasu - Poprawiona responsywność */ .time-btn-container { display: flex; - flex-wrap: nowrap; - overflow-x: auto; - gap: 6px; - padding-bottom: 10px; + flex-wrap: wrap; /* Pozwala na zawijanie przycisków do nowej linii */ + gap: 8px; + padding: 10px 0; justify-content: center; } + .time-btn { font-size: 0.75rem !important; - padding: 5px 12px !important; + padding: 6px 12px !important; white-space: nowrap; - border-color: var(--border-color) !important; + border: 1px solid var(--border-color) !important; color: var(--blue-accent) !important; + background: transparent; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; } + .time-btn.active { background-color: #1f6feb !important; color: white !important; border-color: #1f6feb !important; } + +/* Wykres główny */ .main-chart-card { background-color: var(--card-bg); border: 1px solid var(--border-color); @@ -69,15 +85,99 @@ body { height: 55vh; min-height: 350px; } -footer { padding: 20px 0; opacity: 0.7; } -@media (max-width: 576px) { - .voltage-value { font-size: 0.95rem; } - .main-chart-card { height: 50vh; padding: 10px; } + +/* Lista zdarzeń (logi) */ +.events-card { + background-color: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 15px; + margin-top: 15px; } -.events-card { background-color: var(--card-bg); border: 1px solid var(--border-color); border-radius: 12px; padding: 15px; } -.event-item { display: flex; align-items: center; padding: 8px 0; border-bottom: 1px solid #30363d; } -.event-item:last-child { border-bottom: none; } -.event-badge { width: 12px; height: 12px; border-radius: 50%; margin-right: 12px; flex-shrink: 0; } -.event-time { font-family: monospace; font-size: 0.85rem; color: #8b949e; margin-right: 15px; } -.event-desc { font-size: 0.9rem; color: #c9d1d9; } -.no-events { color: #8b949e; font-style: italic; text-align: center; padding: 10px; } + +.event-item { + display: flex; + align-items: center; + padding: 10px 0; + border-bottom: 1px solid var(--border-color); +} + +.event-item:last-child { + border-bottom: none; +} + +.event-badge { + width: 12px; + height: 12px; + border-radius: 50%; + margin-right: 12px; + flex-shrink: 0; +} + +.event-time { + font-family: monospace; + font-size: 0.85rem; + color: #8b949e; + margin-right: 15px; + white-space: nowrap; +} + +.event-desc { + font-size: 0.9rem; + color: var(--text-main); +} + +.no-events { + color: #8b949e; + font-style: italic; + text-align: center; + padding: 10px; +} + +footer { + padding: 20px 0; + opacity: 0.7; + text-align: center; +} + +@media (max-width: 576px) { + .voltage-value { + font-size: 0.95rem; + } + + .main-chart-card { + height: 45vh; + padding: 10px; + } + + .time-btn { + flex: 1 1 calc(30% - 10px); + min-width: 70px; + font-size: 0.7rem !important; + padding: 8px 4px !important; + } + + .event-item { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } + + .event-badge { + margin-bottom: 4px; + } + + .event-time { + margin-right: 0; + } +} + +.event-warning { + background-color: rgba(255, 187, 51, 0.1); + border: 1px dashed #ffbb33; + border-radius: 8px; + padding: 15px; + margin: 10px 0; + text-align: center; + color: #ffbb33; +} \ No newline at end of file diff --git a/static/js/monitor.js b/static/js/monitor.js index 993b3e4..888f00f 100644 --- a/static/js/monitor.js +++ b/static/js/monitor.js @@ -260,57 +260,88 @@ function renderEventLog(events, range) { const container = document.getElementById('eventLogContainer'); if (!container) return; container.innerHTML = ''; - + + if (events && events.error === "range_too_large") { + container.innerHTML = ` +
+
⚠️
+

${events.message}

+
`; + return; + } + if (events && events.length > 0) { events.forEach(ev => { const start = new Date(ev.start); const end = new Date(ev.end); - const dur = ev.duration; + const dur = ev.duration; const phase = phasesConfig[ev.phase]; + + const typeConfig = { + 'zanik': { label: 'Brak zasilania', color: '#ff4444' }, + 'niskie': { label: 'Zbyt niskie', color: '#ffbb33' }, + 'wysokie': { label: 'Zbyt wysokie', color: '#aa66cc' } + }; + const config = typeConfig[ev.type] || { label: 'Problem', color: '#888' }; + const item = document.createElement('div'); item.className = 'event-item'; item.style.display = 'flex'; item.style.alignItems = 'center'; - item.style.gap = '10px'; + item.style.justifyContent = 'space-between'; - const dateOpt = { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }; + const timeRangeStr = `${start.toLocaleTimeString('pl-PL', {hour:'2-digit', minute:'2-digit'})} - ${end.toLocaleTimeString('pl-PL', {hour:'2-digit', minute:'2-digit'})}`; item.innerHTML = ` -
-
${start.toLocaleString('pl-PL', dateOpt)} - ${end.toLocaleTimeString('pl-PL', {hour:'2-digit', minute:'2-digit'})}
-
Faza ${phase.label}: brak przez ${dur} min.
- `; - - const btn = item.querySelector('.show-event-btn'); - btn.addEventListener('click', async () => { - const eventCenter = start.getTime() + (end.getTime() - start.getTime()) / 2; - const windowMs = 3 * 60 * 60 * 1000; - const viewStart = eventCenter - (windowMs / 2); - const viewEnd = eventCenter + (windowMs / 2); - - document.querySelectorAll('.time-btn').forEach(b => b.classList.remove('active')); - currentTimeRange = 'precise'; - - if (voltageChart) { - voltageChart.options.scales.x.min = undefined; - voltageChart.options.scales.x.max = undefined; - } - await reloadDataForRange(viewStart, viewEnd, null); - - document.getElementById('voltageChart').scrollIntoView({ behavior: 'smooth' }); - }); - container.appendChild(item); }); } else { - container.innerHTML = '
Brak zarejestrowanych zaników.
'; + container.innerHTML = '
Brak zarejestrowanych zdarzeń w tym zakresie.
'; } } +/** + * Funkcja przybliżająca wykres do konkretnego zdarzenia (zakres 3h) + */ +window.showEventOnChart = function(startTimeStr) { + const eventTime = new Date(startTimeStr).getTime(); + const padding = 1.5 * 60 * 60 * 1000; // 1.5 godziny w milisekundach + + const min = eventTime - padding; + const max = eventTime + padding; + + if (voltageChart) { + document.querySelectorAll('.time-btn').forEach(b => b.classList.remove('active')); + + voltageChart.options.scales.x.min = min; + voltageChart.options.scales.x.max = max; + + currentTimeRange = 'precise'; + reloadDataForRange(min, max); + } +}; + function setupGauges() { const config = { diff --git a/templates/index.html b/templates/index.html index d1e0f60..e69dbbb 100644 --- a/templates/index.html +++ b/templates/index.html @@ -22,7 +22,7 @@
- Norma PN-EN 50160: 230V ±10% (207V - 253V) + Norma PN-EN 50160: 230V ±10% (207V - 253V)
@@ -46,7 +46,7 @@
-
Dziennik zdarzeń (zaniki faz)
+
Dziennik zdarzeń
Ładowanie...