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__":
|
if __name__ == "__main__":
|
||||||
from devices import start_timer
|
from devices import start_timer
|
||||||
|
from tui import start_tui
|
||||||
port = int(__import__("os").environ.get("PORT", "10000"))
|
port = int(__import__("os").environ.get("PORT", "10000"))
|
||||||
start_timer()
|
start_timer()
|
||||||
|
start_tui(config.TICK_INTERVAL)
|
||||||
server = HTTPServer(("0.0.0.0", port), Handler)
|
server = HTTPServer(("0.0.0.0", port), Handler)
|
||||||
print(f"🚀 Open http://localhost:{port}")
|
print(f"🚀 Open http://localhost:{port}")
|
||||||
print(f"⏱️ Budget timer running (checks every {config.TICK_INTERVAL}s, reset at 7:00 AM)")
|
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