Add a TUI.
This commit is contained in:
@ -253,8 +253,10 @@ HTML = HTML.replace("{max_budget}", str(int(config.BUDGET_S / 60)))
|
||||
|
||||
if __name__ == "__main__":
|
||||
from devices import start_timer
|
||||
from tui import start_tui
|
||||
port = int(__import__("os").environ.get("PORT", "10000"))
|
||||
start_timer()
|
||||
start_tui(config.TICK_INTERVAL)
|
||||
server = HTTPServer(("0.0.0.0", port), Handler)
|
||||
print(f"🚀 Open http://localhost:{port}")
|
||||
print(f"⏱️ Budget timer running (checks every {config.TICK_INTERVAL}s, reset at 7:00 AM)")
|
||||
|
||||
120
tui.py
Normal file
120
tui.py
Normal file
@ -0,0 +1,120 @@
|
||||
"""Terminal TUI for device status monitoring."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from devices import status_all, is_curfew_allowed
|
||||
|
||||
|
||||
# ANSI escape codes
|
||||
CLEAR = "\033[2J\033[H"
|
||||
BOLD = "\033[1m"
|
||||
RESET = "\033[0m"
|
||||
GREEN = "\033[92m"
|
||||
RED = "\033[91m"
|
||||
YELLOW = "\033[93m"
|
||||
CYAN = "\033[96m"
|
||||
GRAY = "\033[90m"
|
||||
|
||||
|
||||
def _get_terminal_size():
|
||||
"""Get terminal width and height."""
|
||||
try:
|
||||
return os.get_terminal_size()
|
||||
except OSError:
|
||||
return (80, 24)
|
||||
|
||||
|
||||
def _color(text: str, color_code: str) -> str:
|
||||
"""Wrap text in ANSI color code."""
|
||||
return f"{color_code}{text}{RESET}"
|
||||
|
||||
|
||||
def _separator(width: int = 60) -> str:
|
||||
"""Create a separator line."""
|
||||
return "─" * width
|
||||
|
||||
|
||||
def render(devices_status, curfew, refresh_interval: int = 10):
|
||||
"""Render the TUI screen."""
|
||||
width, height = _get_terminal_size()
|
||||
lines = []
|
||||
|
||||
# Header
|
||||
lines.append(_color("⚡ KIDS DEVICES — STATUS", BOLD))
|
||||
lines.append("")
|
||||
|
||||
# Curfew status
|
||||
if curfew["in_curfew"]:
|
||||
curfew_icon = _color("🔴 BLOCKED", RED)
|
||||
else:
|
||||
curfew_icon = _color("🟢 ALLOWED", GREEN)
|
||||
lines.append(f" Curfew: {curfew_icon} | {curfew['message']}")
|
||||
lines.append("")
|
||||
|
||||
# Device list
|
||||
for device in devices_status:
|
||||
icon = device["icon"]
|
||||
name = device["title"]
|
||||
detail = device["detail"]
|
||||
|
||||
# Parse budget from detail
|
||||
parts = detail.split(" · ")
|
||||
status_text = parts[0] if parts else "Unknown"
|
||||
budget_text = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
# Color the status
|
||||
if "Online" in status_text:
|
||||
status_color = GREEN
|
||||
elif "Offline" in status_text:
|
||||
status_color = GRAY
|
||||
else:
|
||||
status_color = YELLOW
|
||||
|
||||
status_colored = _color(status_text, status_color)
|
||||
|
||||
# Color the budget
|
||||
if "🔴" in device["icon"]:
|
||||
budget_color = RED
|
||||
elif "🟠" in device["icon"]:
|
||||
budget_color = YELLOW
|
||||
else:
|
||||
budget_color = CYAN
|
||||
|
||||
budget_colored = _color(budget_text, budget_color) if budget_text else ""
|
||||
|
||||
lines.append(f" {icon} {name:<12} {status_colored} · {budget_colored}")
|
||||
|
||||
# Footer with timestamp
|
||||
now = time.strftime("%H:%M:%S")
|
||||
lines.append(f" {_color('Last update:', GRAY)} {now} | Refresh: {refresh_interval}s")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def run_tui(refresh_interval: int = 10):
|
||||
"""Run the TUI in a loop."""
|
||||
# Run in a separate thread to not block the server
|
||||
while True:
|
||||
try:
|
||||
devices_status, curfew = status_all()
|
||||
screen = render(devices_status, curfew, refresh_interval)
|
||||
sys.stdout.write(CLEAR + screen)
|
||||
sys.stdout.flush()
|
||||
except Exception:
|
||||
pass # Don't crash TUI on errors
|
||||
|
||||
time.sleep(refresh_interval)
|
||||
|
||||
|
||||
def start_tui(refresh_interval: int = 10):
|
||||
"""Start the TUI in a background thread."""
|
||||
tui_thread = threading.Thread(
|
||||
target=run_tui,
|
||||
args=(refresh_interval,),
|
||||
daemon=True,
|
||||
)
|
||||
tui_thread.start()
|
||||
return tui_thread
|
||||
Reference in New Issue
Block a user