Files
he d01228a21e feat(GUI): web interface for install and pos commands
- gui/main.py: FastAPI app with Jinja2 templates and static files
- gui/state.py: shared application state (runner, template env)
- gui/services/pos_runner.py: abstracted command execution layer
  with subprocess, SSE streaming, and sudo support
- gui/routes/: 8 route modules (dashboard, install, compose,
  network, vbox, system, media, ssh)
- gui/templates/: Bulma-based layout with sidebar navigation +
  section templates for all routes
- gui/static/css/style.css: dark theme styling
- gui/requirements.txt: fastapi, uvicorn, jinja2, python-multipart
- 1- Start_GUI.sh: entry point with sudo cache, pip install, uvicorn launch

Architecture: routes delegate to pos_runner which shells out to
actual pos-* scripts — no logic duplication. SSE streaming for
real-time output (install, compose up/down/logs, network scan, media).
2026-07-28 11:47:48 -04:00

70 lines
2.3 KiB
Python

import asyncio
from pathlib import Path
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, StreamingResponse
from gui.state import runner, templates
router = APIRouter(prefix="/install")
@router.get("", response_class=HTMLResponse)
async def install_page(request: Request):
app_categories = runner.list_app_categories()
return templates.TemplateResponse(
"install/index.html",
{"request": request, "active": "install", "app_categories": app_categories},
)
@router.get("/stream")
async def install_stream(phases: str = "1,2,3,4", apps: str = ""):
phase_map = {
"1": ("preinstall", False),
"2": ("scripts", True),
"3": ("postinstall", False),
"4": ("scalepoint", True),
}
async def event_stream():
repo = Path(__file__).resolve().parents[2]
selected = [p.strip() for p in phases.split(",") if p.strip() in phase_map]
for phase_num in selected:
name, needs_sudo = phase_map[phase_num]
script = phase_map[phase_num][0]
yield {"event": "phase_start", "data": f"Phase {phase_num}: {script}"}
if needs_sudo:
gen = runner.stream_sudo(f"bash {repo}/install.sh --steps {phase_num}")
else:
gen = runner.stream(f"bash {repo}/install.sh --steps {phase_num}")
async for event in gen:
yield event
if apps:
app_list = apps.split(",")
for app in app_list:
app = app.strip()
if not app:
continue
yield {"event": "phase_start", "data": f"Installing app: {app}"}
category, name = app.split("/", 1)
script = repo / "apps" / category / f"{name}.sh"
if script.is_file():
async for event in runner.stream_sudo(f"bash {script}"):
yield event
else:
yield {"event": "error", "data": f"App script not found: {app}"}
yield {"event": "exit", "data": "0"}
async def generate():
async for event in event_stream():
yield f"event: {event['event']}\ndata: {event['data']}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")