Files
location_managemet/app/routes/api.py
ske087 90cbf4e1f0 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)
2026-02-27 13:34:44 +02:00

106 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""REST API board webhook receiver and JSON relay control."""
from datetime import datetime
from flask import Blueprint, request, jsonify, abort, current_app
from flask_login import login_required
from app import db, socketio
from app.models.board import Board
from app.services import workflow_engine
from app.services.board_service import poll_board
api_bp = Blueprint("api", __name__)
# ── webhook endpoint (boards POST input events here) ──────────────────────────
@api_bp.route("/webhook/<int:board_id>", methods=["POST"])
def webhook(board_id: int):
board = db.session.get(Board, board_id)
if board is None:
abort(404)
data = request.get_json(silent=True) or {}
input_num = data.get("input")
state = data.get("state")
if input_num is None or state is None:
return jsonify({"error": "missing input or state"}), 400
# Update cached input state
states = board.input_states
states[f"input_{input_num}"] = bool(state)
board.input_states = states
board.is_online = True
board.last_seen = datetime.utcnow()
db.session.commit()
# Let the workflow engine decide what to do
workflow_engine.process_input_event(board_id, int(input_num), bool(state))
# Push live update to all connected clients immediately
socketio.emit("board_update", {
"board_id": board_id,
"is_online": True,
"input_states": board.input_states,
"relay_states": board.relay_states,
})
return jsonify({"status": "ok"})
# ── Manual ping / status check ───────────────────────────────────────────────
@api_bp.route("/boards/<int:board_id>/ping", methods=["POST"])
@login_required
def ping_board(board_id: int):
"""Trigger an immediate poll for any board (online or offline).
Called by the 'Check Status' button in the UI. The poll result is both
returned as JSON *and* broadcast via socket.io so all open tabs update.
"""
db.get_or_404(Board, board_id) # 404 if unknown
poll_board(current_app._get_current_object(), board_id)
board = db.session.get(Board, board_id)
return jsonify({
"board_id": board.id,
"is_online": board.is_online,
"relay_states": board.relay_states,
"input_states": board.input_states,
"last_seen": board.last_seen.isoformat() if board.last_seen else None,
})
# ── JSON relay status ─────────────────────────────────────────────────────────
@api_bp.route("/boards/<int:board_id>/relays")
def relay_states(board_id: int):
board = db.get_or_404(Board, board_id)
return jsonify({
"board_id": board.id,
"name": board.name,
"is_online": board.is_online,
"relay_states": board.relay_states,
"input_states": board.input_states,
})
# ── JSON board list ───────────────────────────────────────────────────────────
@api_bp.route("/boards")
def board_list():
boards = Board.query.order_by(Board.name).all()
return jsonify([
{
"id": b.id,
"name": b.name,
"board_type": b.board_type,
"host": b.host,
"port": b.port,
"is_online": b.is_online,
"last_seen": b.last_seen.isoformat() if b.last_seen else None,
"relay_states": b.relay_states,
"input_states": b.input_states,
}
for b in boards
])