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).
82 lines
2.5 KiB
Python
82 lines
2.5 KiB
Python
import json
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Form, Request
|
|
from fastapi.responses import HTMLResponse, StreamingResponse
|
|
|
|
from gui.state import runner, templates
|
|
|
|
router = APIRouter(prefix="/compose")
|
|
|
|
|
|
@router.get("", response_class=HTMLResponse)
|
|
async def compose_page(request: Request):
|
|
services = runner.list_services()
|
|
deployed = runner.list_deployed()
|
|
return templates.TemplateResponse(
|
|
"compose/index.html",
|
|
{
|
|
"request": request,
|
|
"active": "compose",
|
|
"services": services,
|
|
"deployed": deployed,
|
|
},
|
|
)
|
|
|
|
|
|
@router.get("/{service}", response_class=HTMLResponse)
|
|
async def service_detail(request: Request, service: str):
|
|
deployed = (Path("/srv") / service).is_dir()
|
|
return templates.TemplateResponse(
|
|
"compose/detail.html",
|
|
{
|
|
"request": request,
|
|
"active": "compose",
|
|
"service": service,
|
|
"deployed": deployed,
|
|
},
|
|
)
|
|
|
|
|
|
@router.get("/{service}/stream/{action}")
|
|
async def service_stream(service: str, action: str):
|
|
async def generate():
|
|
cmd_map = {
|
|
"up": f"pos docker compose up {service}",
|
|
"down": f"pos docker compose down {service}",
|
|
"restart": f"pos docker compose restart {service}",
|
|
"logs": f"pos docker compose logs {service}",
|
|
}
|
|
cmd = cmd_map.get(action)
|
|
if not cmd:
|
|
yield f"event: error\ndata: Unknown action: {action}\n\n"
|
|
return
|
|
async for event in runner.stream(cmd):
|
|
yield f"event: {event['event']}\ndata: {event['data']}\n\n"
|
|
|
|
return StreamingResponse(generate(), media_type="text/event-stream")
|
|
|
|
|
|
@router.post("/config/set")
|
|
async def config_set(key: str = Form(...), value: str = Form(...)):
|
|
code, out, err = await runner.run(f"pos docker compose config set {key}={value}")
|
|
return {"ok": code == 0, "output": out or err}
|
|
|
|
|
|
@router.get("/config", response_class=HTMLResponse)
|
|
async def config_page(request: Request):
|
|
code, out, _ = await runner.run("pos docker compose config")
|
|
config_file = Path.home() / ".config" / "linux_post_install" / "compose.env"
|
|
current = ""
|
|
if config_file.is_file():
|
|
current = config_file.read_text()
|
|
return templates.TemplateResponse(
|
|
"compose/config.html",
|
|
{
|
|
"request": request,
|
|
"active": "compose",
|
|
"current": current,
|
|
"status": out,
|
|
},
|
|
)
|