poprawki i eventy
This commit is contained in:
204
app.py
204
app.py
@@ -1,21 +1,35 @@
|
||||
import os
|
||||
import warnings
|
||||
|
||||
os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
|
||||
import eventlet
|
||||
eventlet.monkey_patch(all=True)
|
||||
|
||||
import os
|
||||
import warnings
|
||||
import logging
|
||||
from flask import Flask, render_template, jsonify, request
|
||||
from flask_socketio import SocketIO
|
||||
from influxdb import InfluxDBClient
|
||||
from datetime import datetime, timedelta
|
||||
import config
|
||||
import hashlib
|
||||
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning, module="eventlet")
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger('werkzeug')
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = config.FLASK_CONFIG['secret_key']
|
||||
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet')
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = config.FLASK_CONFIG.get('static_cache_timeout', 60)
|
||||
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet', logger=False)
|
||||
|
||||
def get_file_hash(filename):
|
||||
full_path = os.path.join(app.static_folder, filename)
|
||||
try:
|
||||
with open(full_path, "rb") as f:
|
||||
return hashlib.md5(f.read()).hexdigest()[:8]
|
||||
except FileNotFoundError:
|
||||
return "1"
|
||||
|
||||
# Klient InfluxDB v1
|
||||
def get_influx_client():
|
||||
client = InfluxDBClient(
|
||||
host=config.INFLUXDB_CONFIG['host'],
|
||||
@@ -26,6 +40,8 @@ def get_influx_client():
|
||||
client.switch_user(config.INFLUXDB_CONFIG['username'], config.INFLUXDB_CONFIG['password'])
|
||||
return client
|
||||
|
||||
# --- LOGIKA ---
|
||||
|
||||
def get_current_voltage(phase_id):
|
||||
client = get_influx_client()
|
||||
entity_id = config.PHASES[phase_id]['entity_id']
|
||||
@@ -41,34 +57,182 @@ def get_current_voltage(phase_id):
|
||||
client.close()
|
||||
return {'voltage': 0, 'timestamp': None}
|
||||
|
||||
# --- ENDPOINTY ---
|
||||
|
||||
@app.context_processor
|
||||
def inject_static_version():
|
||||
def static_v(filename):
|
||||
full_path = os.path.join(app.static_folder, filename)
|
||||
try:
|
||||
with open(full_path, "rb") as f:
|
||||
v = hashlib.md5(f.read()).hexdigest()[:8]
|
||||
except Exception:
|
||||
v = "1"
|
||||
|
||||
return f"{request.script_root}/static/{filename}?v={v}"
|
||||
return dict(static_v=static_v)
|
||||
|
||||
@app.after_request
|
||||
def add_header(response):
|
||||
if request.path.startswith('/static/'):
|
||||
response.cache_control.max_age = 31536000
|
||||
response.cache_control.public = True
|
||||
else:
|
||||
response.cache_control.no_cache = True
|
||||
response.cache_control.no_store = True
|
||||
response.cache_control.must_revalidate = True
|
||||
return response
|
||||
|
||||
@app.route('/favicon.ico')
|
||||
def favicon():
|
||||
return '', 204
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html', phases=config.PHASES, time_ranges=config.TIME_RANGES,
|
||||
default_range=config.DEFAULT_TIME_RANGE, footer=config.FOOTER)
|
||||
|
||||
@app.route('/api/timeseries/<int:phase_id>')
|
||||
def api_timeseries(phase_id):
|
||||
if phase_id not in config.PHASES: return jsonify({'error': 'Invalid phase'}), 400
|
||||
def get_timeseries(phase_id):
|
||||
if phase_id not in config.PHASES:
|
||||
return jsonify({'error': 'Invalid phase'}), 400
|
||||
|
||||
client = get_influx_client()
|
||||
t_range = request.args.get('range', config.DEFAULT_TIME_RANGE)
|
||||
cfg = config.TIME_RANGES.get(t_range, config.TIME_RANGES['24h'])
|
||||
query = config.PHASES[phase_id]['query'].replace('$timeFilter', cfg['filter']).replace('$__interval', cfg['interval'])
|
||||
range_param = request.args.get('range', config.DEFAULT_TIME_RANGE)
|
||||
start_param = request.args.get('start')
|
||||
end_param = request.args.get('end')
|
||||
entity_id = config.PHASES[phase_id]['entity_id']
|
||||
|
||||
if start_param and end_param:
|
||||
time_filter = f"time >= '{start_param}' AND time <= '{end_param}'"
|
||||
interval = "1m"
|
||||
else:
|
||||
clean_range = range_param.replace(" ", "")
|
||||
time_filter = f"time > now() - {clean_range}"
|
||||
|
||||
mapping = {
|
||||
"1h": "10s",
|
||||
"6h": "30s",
|
||||
"24h": "2m",
|
||||
"7d": "10m",
|
||||
"30d": "30m",
|
||||
"60d": "1h",
|
||||
"120d": "2h",
|
||||
"180d": "4h",
|
||||
"365d": "6h"
|
||||
}
|
||||
|
||||
interval = mapping.get(range_param, "1m")
|
||||
|
||||
query = f'''
|
||||
SELECT mean("value") AS voltage
|
||||
FROM "{config.MEASUREMENT}"
|
||||
WHERE "entity_id" = '{entity_id}'
|
||||
AND {time_filter}
|
||||
GROUP BY time({interval}) fill(none)
|
||||
'''
|
||||
|
||||
try:
|
||||
result = client.query(query)
|
||||
data = []
|
||||
for p in result.get_points():
|
||||
val = p.get('voltage') or p.get('min') or p.get('mean') or p.get('value')
|
||||
if val is not None:
|
||||
data.append({'time': p['time'], 'voltage': round(float(val), 2)})
|
||||
else:
|
||||
data.append({'time': p['time'], 'voltage': 0})
|
||||
data = [{"time": p['time'], "voltage": round(p['voltage'], 2)}
|
||||
for p in result.get_points()
|
||||
if p.get('voltage') is not None]
|
||||
return jsonify(data)
|
||||
except Exception as e:
|
||||
print(f"History Error: {e}")
|
||||
app.logger.error(f"Timeseries Error: {e} | Query: {query}")
|
||||
return jsonify([])
|
||||
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)
|
||||
|
||||
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'))
|
||||
time_filter = f"time >= '{start_p}' - 24h AND time <= '{end_p}'"
|
||||
else:
|
||||
clean_range = range_p.replace(" ", "")
|
||||
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"
|
||||
|
||||
all_events = []
|
||||
try:
|
||||
for p_id, p_cfg in config.PHASES.items():
|
||||
query = f'''
|
||||
SELECT mean("value") AS volts
|
||||
FROM "{config.MEASUREMENT}"
|
||||
WHERE "entity_id" = '{p_cfg["entity_id"]}'
|
||||
AND {time_filter}
|
||||
GROUP BY time(1m) fill(none)
|
||||
'''
|
||||
result = client.query(query)
|
||||
points = list(result.get_points())
|
||||
|
||||
i = 0
|
||||
while i < len(points):
|
||||
val = points[i].get('volts')
|
||||
if val is not None and float(val) < 207:
|
||||
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:
|
||||
j += 1
|
||||
else:
|
||||
break
|
||||
|
||||
end_str = points[j]['time']
|
||||
dt_e = datetime.fromisoformat(end_str.replace('Z', '+00:00'))
|
||||
|
||||
duration = (dt_e - dt_s).total_seconds() / 120
|
||||
|
||||
if duration >= 0.5:
|
||||
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)
|
||||
})
|
||||
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}")
|
||||
return jsonify([])
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
@app.route('/api/outages/<int:phase_id>')
|
||||
def api_outages(phase_id):
|
||||
client = get_influx_client()
|
||||
t_range = request.args.get('range', '24h')
|
||||
entity_id = config.PHASES[phase_id]['entity_id']
|
||||
query = f'SELECT "value" FROM "{config.MEASUREMENT}" WHERE "entity_id" = \'{entity_id}\' AND "value" < 10 AND time > now() - {t_range}'
|
||||
try:
|
||||
result = client.query(query)
|
||||
return jsonify(list(result.get_points()))
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
def background_voltage_update():
|
||||
while True:
|
||||
try:
|
||||
@@ -83,5 +247,9 @@ def background_voltage_update():
|
||||
eventlet.sleep(config.CHART_CONFIG['update_interval'] / 1000)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("\n" + "="*50)
|
||||
print(f"Voltage Monitor API / Port: {config.FLASK_CONFIG['port']}")
|
||||
print("="*50 + "\n")
|
||||
|
||||
eventlet.spawn(background_voltage_update)
|
||||
socketio.run(app, host='0.0.0.0', port=config.FLASK_CONFIG['port'])
|
||||
|
||||
Reference in New Issue
Block a user