d01228a21e
- 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).
70 lines
2.3 KiB
Python
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")
|