213 lines
6.5 KiB
Python
213 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Core configuration REST API (buttons, layout, background, feedback,
|
|
settings, icons) for the ButtonTask web configurator."""
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
from flask import Blueprint, abort, jsonify, request, send_from_directory
|
|
|
|
from auth import login_required
|
|
from config_store import (ALLOWED_ICON_EXT, icons_dir, list_icons, load_config,
|
|
save_config)
|
|
|
|
api_bp = Blueprint("api", __name__)
|
|
|
|
|
|
@api_bp.route("/icons/<path:fname>")
|
|
@login_required
|
|
def serve_icon(fname):
|
|
return send_from_directory(str(icons_dir()), fname)
|
|
|
|
|
|
# --- config ---
|
|
@api_bp.route("/api/config", methods=["GET"])
|
|
@login_required
|
|
def api_get_config():
|
|
return jsonify(load_config())
|
|
|
|
|
|
@api_bp.route("/api/config", methods=["PUT"])
|
|
@login_required
|
|
def api_put_config():
|
|
data = request.get_json(force=True, silent=True)
|
|
if not isinstance(data, dict):
|
|
abort(400, "JSON object required")
|
|
save_config(data)
|
|
return jsonify({"ok": True})
|
|
|
|
|
|
# --- buttons ---
|
|
@api_bp.route("/api/buttons", methods=["POST"])
|
|
@login_required
|
|
def api_add_button():
|
|
data = request.get_json(force=True, silent=True) or {}
|
|
cfg = load_config()
|
|
btn = {
|
|
"id": data.get("id") or uuid.uuid4().hex,
|
|
"label": data.get("label", "Кнопка"),
|
|
"iconPath": data.get("iconPath", ""),
|
|
"action": {
|
|
"type": data.get("actionType", "http_get"),
|
|
"url": data.get("url", ""),
|
|
"headers": data.get("headers", {}),
|
|
"body": data.get("body", ""),
|
|
},
|
|
"feedback": {
|
|
"successText": data.get("successText", ""),
|
|
"errorText": data.get("errorText", "Ошибка"),
|
|
"fadeMs": int(data.get("fadeMs", 5000)),
|
|
},
|
|
"color": data.get("color", "#7b007b"),
|
|
}
|
|
cfg.setdefault("buttons", []).append(btn)
|
|
save_config(cfg)
|
|
return jsonify(btn)
|
|
|
|
|
|
@api_bp.route("/api/buttons/<bid>", methods=["PUT"])
|
|
@login_required
|
|
def api_update_button(bid):
|
|
data = request.get_json(force=True, silent=True) or {}
|
|
cfg = load_config()
|
|
for b in cfg.get("buttons", []):
|
|
if b.get("id") == bid:
|
|
b["label"] = data.get("label", b.get("label"))
|
|
b["iconPath"] = data.get("iconPath", b.get("iconPath"))
|
|
action = b.setdefault("action", {})
|
|
action["type"] = data.get("actionType", action.get("type", "http_get"))
|
|
action["url"] = data.get("url", action.get("url", ""))
|
|
if "headers" in data:
|
|
action["headers"] = data["headers"]
|
|
if "body" in data:
|
|
action["body"] = data["body"]
|
|
feedback = b.setdefault("feedback", {})
|
|
feedback["successText"] = data.get("successText", feedback.get("successText", ""))
|
|
feedback["errorText"] = data.get("errorText", feedback.get("errorText", "Ошибка"))
|
|
feedback["fadeMs"] = int(data.get("fadeMs", feedback.get("fadeMs", 5000)))
|
|
if "color" in data:
|
|
b["color"] = data["color"]
|
|
save_config(cfg)
|
|
return jsonify(b)
|
|
abort(404)
|
|
|
|
|
|
@api_bp.route("/api/buttons/<bid>", methods=["DELETE"])
|
|
@login_required
|
|
def api_delete_button(bid):
|
|
cfg = load_config()
|
|
cfg["buttons"] = [b for b in cfg.get("buttons", []) if b.get("id") != bid]
|
|
save_config(cfg)
|
|
return jsonify({"ok": True})
|
|
|
|
|
|
@api_bp.route("/api/buttons/reorder", methods=["POST"])
|
|
@login_required
|
|
def api_reorder_buttons():
|
|
data = request.get_json(force=True, silent=True) or {}
|
|
order = data.get("order", [])
|
|
cfg = load_config()
|
|
by_id = {b.get("id"): b for b in cfg.get("buttons", [])}
|
|
new_list = [by_id[i] for i in order if i in by_id]
|
|
for b in cfg.get("buttons", []):
|
|
if b not in new_list:
|
|
new_list.append(b)
|
|
cfg["buttons"] = new_list
|
|
save_config(cfg)
|
|
return jsonify({"ok": True, "count": len(new_list)})
|
|
|
|
|
|
# --- layout / background / feedback / settings ---
|
|
@api_bp.route("/api/layout", methods=["PUT"])
|
|
@login_required
|
|
def api_set_layout():
|
|
data = request.get_json(force=True, silent=True) or {}
|
|
cfg = load_config()
|
|
layout = cfg.setdefault("layout", {})
|
|
for k in ("mode", "columns", "spacing", "showLabels"):
|
|
if k in data:
|
|
layout[k] = data[k]
|
|
save_config(cfg)
|
|
return jsonify(layout)
|
|
|
|
|
|
@api_bp.route("/api/background", methods=["PUT"])
|
|
@login_required
|
|
def api_set_background():
|
|
data = request.get_json(force=True, silent=True) or {}
|
|
cfg = load_config()
|
|
bg = cfg.setdefault("background", {})
|
|
for k in ("type", "color1", "color2", "animated", "imagePath"):
|
|
if k in data:
|
|
bg[k] = data[k]
|
|
save_config(cfg)
|
|
return jsonify(bg)
|
|
|
|
|
|
@api_bp.route("/api/feedback", methods=["PUT"])
|
|
@login_required
|
|
def api_set_feedback():
|
|
data = request.get_json(force=True, silent=True) or {}
|
|
cfg = load_config()
|
|
fb = cfg.setdefault("feedback", {})
|
|
for k in ("okColor", "errorColor"):
|
|
if k in data:
|
|
fb[k] = data[k]
|
|
for k in ("okWidth", "errorWidth", "glowRadius"):
|
|
if k in data:
|
|
try:
|
|
fb[k] = int(data[k])
|
|
except (TypeError, ValueError):
|
|
pass
|
|
save_config(cfg)
|
|
return jsonify(fb)
|
|
|
|
|
|
@api_bp.route("/api/settings", methods=["PUT"])
|
|
@login_required
|
|
def api_set_settings():
|
|
data = request.get_json(force=True, silent=True) or {}
|
|
cfg = load_config()
|
|
st = cfg.setdefault("settings", {})
|
|
for k in ("darkMode", "password", "kioskMode", "iconsDir", "webPort"):
|
|
if k in data:
|
|
st[k] = data[k]
|
|
save_config(cfg)
|
|
return jsonify({"ok": True})
|
|
|
|
|
|
# --- icons ---
|
|
@api_bp.route("/api/icons", methods=["GET"])
|
|
@login_required
|
|
def api_list_icons():
|
|
return jsonify(list_icons())
|
|
|
|
|
|
@api_bp.route("/api/icons", methods=["POST"])
|
|
@login_required
|
|
def api_upload_icon():
|
|
if "file" not in request.files:
|
|
abort(400, "file required")
|
|
fobj = request.files["file"]
|
|
fname = Path(fobj.filename).name
|
|
if not fname:
|
|
abort(400, "empty filename")
|
|
ext = Path(fname).suffix.lower()
|
|
if ext not in ALLOWED_ICON_EXT:
|
|
abort(400, f"extension {ext} not allowed")
|
|
target = icons_dir() / fname
|
|
fobj.save(str(target))
|
|
return jsonify({"ok": True, "name": fname})
|
|
|
|
|
|
@api_bp.route("/api/icons/<path:fname>", methods=["DELETE"])
|
|
@login_required
|
|
def api_delete_icon(fname):
|
|
p = icons_dir() / fname
|
|
try:
|
|
p.resolve().relative_to(icons_dir().resolve())
|
|
except ValueError:
|
|
abort(400)
|
|
if p.exists():
|
|
p.unlink()
|
|
return jsonify({"ok": True})
|