- Move board cards from dashboard to top of boards list page - Fix Werkzeug duplicate polling (WERKZEUG_RUN_MAIN guard) - Smart offline polling: fast loop for online boards, slow recheck for offline - Add manual ping endpoint POST /api/boards/<id>/ping - Add spin animation CSS for ping button Layouts module (new): - app/models/layout.py: Layout model (canvas_json, thumbnail_b64) - app/routes/layouts.py: 5 routes (list, create, builder, save, delete) - app/templates/layouts/: list and builder templates - app/static/js/layout_builder.js: full Konva.js builder engine - app/static/vendor/konva/: vendored Konva.js 9 - Structure mode: wall, room, door, window, fence, text shapes - Devices mode: drag relay/input/Sonoff channels onto canvas - Live view mode: click relays/Sonoff to toggle, socket.io state updates - Device selection: click to select, remove individual device, Delete key - Fix door/Arc size persistence across save/reload (outerRadius, scaleX/Y) - Fix Sonoff devices missing from palette (add makeSonoffChip function)
59 lines
1.9 KiB
Python
59 lines
1.9 KiB
Python
"""Application entry point.
|
|
|
|
Run with:
|
|
python run.py
|
|
|
|
Or for production:
|
|
gunicorn -w 1 -k eventlet "run:create_socketio_app()"
|
|
"""
|
|
import os
|
|
import threading
|
|
import time
|
|
|
|
from app import create_app, socketio
|
|
from app.services.board_service import poll_online_boards, recheck_offline_boards
|
|
|
|
app = create_app(os.environ.get("FLASK_ENV", "development"))
|
|
|
|
|
|
def _online_poller():
|
|
"""Fast loop — polls only online boards every BOARD_POLL_INTERVAL seconds."""
|
|
interval = app.config.get("BOARD_POLL_INTERVAL", 10)
|
|
while True:
|
|
try:
|
|
poll_online_boards(app)
|
|
except Exception as exc:
|
|
app.logger.warning("Online poller error: %s", exc)
|
|
time.sleep(interval)
|
|
|
|
|
|
def _offline_recheck_poller():
|
|
"""Slow loop — probes offline boards every OFFLINE_RECHECK_INTERVAL seconds.
|
|
|
|
Waits one full interval before the first check so startup is clean.
|
|
"""
|
|
interval = app.config.get("OFFLINE_RECHECK_INTERVAL", 60)
|
|
while True:
|
|
time.sleep(interval) # wait first, then check
|
|
try:
|
|
recheck_offline_boards(app)
|
|
except Exception as exc:
|
|
app.logger.warning("Offline recheck error: %s", exc)
|
|
|
|
|
|
def create_socketio_app():
|
|
"""WSGI callable for gunicorn / production."""
|
|
return socketio.middleware(app)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Werkzeug debug-reloader starts two processes; only start the poller in
|
|
# the actual worker (WERKZEUG_RUN_MAIN=true) to avoid duplicate polling.
|
|
# In non-debug mode (WERKZEUG_RUN_MAIN is unset) we always start it.
|
|
if not app.debug or os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
|
threading.Thread(target=_online_poller, daemon=True).start()
|
|
threading.Thread(target=_offline_recheck_poller, daemon=True).start()
|
|
|
|
port = int(os.environ.get("PORT", 5000))
|
|
socketio.run(app, host="0.0.0.0", port=port, debug=app.debug, allow_unsafe_werkzeug=True)
|