From 49b6cf89f45e341b214d560afc254ab3cfafbdeb Mon Sep 17 00:00:00 2001 From: toros Date: Tue, 8 Sep 2026 23:34:23 +0500 Subject: [PATCH] =?UTF-8?q?feat(webui):=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC?= =?UTF-8?q?=20console=20(=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20RS232-?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=81=D0=BE=D0=BB=D1=8C,=20=D0=BF=D0=B8?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=BE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ATXPI_MODE=console: /api/power/* → 403, кнопки питания скрыты в UI - /api/status отдаёт mode; env-шаблон и install.sh учитывают ATXPI_MODE - нужно для безопасного запуска панели на серверах-«управленцах» без риска питания --- atxpi/deploy/atxpi.env.example | 3 +++ atxpi/webui/server.py | 15 ++++++++++++++- atxpi/webui/static/app.js | 5 ++++- atxpi/webui/static/index.html | 10 ++++++---- install.sh | 3 +++ 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/atxpi/deploy/atxpi.env.example b/atxpi/deploy/atxpi.env.example index 9851c44..5eb1158 100644 --- a/atxpi/deploy/atxpi.env.example +++ b/atxpi/deploy/atxpi.env.example @@ -5,6 +5,9 @@ # Драйвер железа: real | mock ATXPI_DRIVER=real +# Режим: full | console. В console питание отключено (только RS232-консоль). +ATXPI_MODE=full + # RS232-консоль сервера SERIAL_PORT=/dev/ttyUSB0 SERIAL_BAUD=115200 diff --git a/atxpi/webui/server.py b/atxpi/webui/server.py index 49f710c..ba5dc36 100644 --- a/atxpi/webui/server.py +++ b/atxpi/webui/server.py @@ -22,7 +22,7 @@ import os import time from pathlib import Path -from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request +from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request, HTTPException from fastapi.responses import FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles @@ -34,9 +34,16 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(mess STATIC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static") WEB_TOKEN = os.environ.get("ATXPI_WEB_TOKEN", "") +MODE = os.environ.get("ATXPI_MODE", "full") # full | console (только консоль, питание отключено) AUDIT = os.environ.get("ATXPI_AUDIT", "/etc/atxpi/audit.jsonl") +def _power_guard(): + """В режиме console питание выключено — деструктивные действия запрещены.""" + if MODE == "console": + raise HTTPException(status_code=403, detail="console-only mode: power disabled") + + def audit(action: str, **kw): """Логируем действие в jsonl (для /api/history).""" entry = {"t": time.time(), "action": action, **kw} @@ -77,29 +84,34 @@ async def auth_mw(request: Request, call_next): # ── REST: питание ────────────────────────────────────────────────────────── @app.get("/api/power") async def api_power_status(): + _power_guard() return power_mod.status() @app.post("/api/power/on") async def api_power_on(): + _power_guard() audit("power_on") return power_mod.press_power() @app.post("/api/power/off") async def api_power_off(): + _power_guard() audit("power_off") return power_mod.force_off() @app.post("/api/power/reset") async def api_power_reset(): + _power_guard() audit("power_reset") return power_mod.press_reset() @app.post("/api/power/cycle") async def api_power_cycle(): + _power_guard() audit("power_cycle") return power_mod.cycle() @@ -113,6 +125,7 @@ async def api_status(): "driver": DRIVER, "serial_port": SERIAL_PORT, "serial_baud": SERIAL_BAUD, + "mode": MODE, } diff --git a/atxpi/webui/static/app.js b/atxpi/webui/static/app.js index 0376bef..9eac2e7 100644 --- a/atxpi/webui/static/app.js +++ b/atxpi/webui/static/app.js @@ -28,7 +28,10 @@ async function refresh() { document.getElementById('powerState').textContent = s.power === 'on' ? '🟢 ON' : '🔴 OFF'; document.getElementById('status').textContent = - `driver=${s.driver} · serial=${s.serial_port}@${s.serial_baud}`; + `driver=${s.driver} · serial=${s.serial_port}@${s.serial_baud}` + + (s.mode === 'console' ? ' · режим: только консоль' : ''); + const pc = document.getElementById('powerControls'); + if (pc) pc.style.display = (s.mode === 'console') ? 'none' : ''; } catch (e) { /* уже обработано в api() */ } } diff --git a/atxpi/webui/static/index.html b/atxpi/webui/static/index.html index 41169bd..bdd8111 100644 --- a/atxpi/webui/static/index.html +++ b/atxpi/webui/static/index.html @@ -25,10 +25,12 @@

⚡ atxpi

- - - - + + + + + +
diff --git a/install.sh b/install.sh index fc63759..e66bb05 100755 --- a/install.sh +++ b/install.sh @@ -29,6 +29,7 @@ ATXPI_BIND_PORT="${ATXPI_BIND_PORT:-8443}" ATXPI_SERIAL="${ATXPI_SERIAL:-/dev/ttyUSB0}" ATXPI_BAUD="${ATXPI_BAUD:-115200}" ATXPI_DRIVER="${ATXPI_DRIVER:-real}" +ATXPI_MODE="${ATXPI_MODE:-full}" log() { echo -e "\e[1;32m[atxpi]\e[0m $*"; } err() { echo -e "\e[1;31m[atxpi]\e[0m $*" >&2; } @@ -99,6 +100,8 @@ if [ ! -f "$ENV_FILE" ]; then # atxpi — локальная конфигурация (создаётся install.sh, НЕ хранится в git) # ATXPI_DRIVER: real | mock ATXPI_DRIVER=$ATXPI_DRIVER +# Режим: full | console (в console питание отключено) +ATXPI_MODE=$ATXPI_MODE SERIAL_PORT=$ATXPI_SERIAL SERIAL_BAUD=$ATXPI_BAUD # токен для REST-API и браузера (генерируется при установке)