Add Layouts module with Konva.js builder; smart offline polling; UI improvements
- 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)
This commit is contained in:
33
run.py
33
run.py
@@ -11,31 +11,48 @@ import threading
|
||||
import time
|
||||
|
||||
from app import create_app, socketio
|
||||
from app.services.board_service import poll_all_boards
|
||||
from app.services.board_service import poll_online_boards, recheck_offline_boards
|
||||
|
||||
app = create_app(os.environ.get("FLASK_ENV", "development"))
|
||||
|
||||
|
||||
def _background_poller():
|
||||
"""Poll all boards in a loop every BOARD_POLL_INTERVAL seconds."""
|
||||
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_all_boards(app)
|
||||
poll_online_boards(app)
|
||||
except Exception as exc:
|
||||
app.logger.warning("Poller error: %s", 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__":
|
||||
# Start background board poller
|
||||
t = threading.Thread(target=_background_poller, daemon=True)
|
||||
t.start()
|
||||
# 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)
|
||||
|
||||
Reference in New Issue
Block a user