Initial commit: StackPilot Phase 1 (Core)
Self-hosted Docker Compose manager. - Backend: FastAPI + docker-py + SQLite (JWT auth, file-first stacks, lifecycle, live status, WebSocket logs, docker-run converter, audit log) - Frontend: React + Vite + Tailwind (login/setup, dashboard, stacks, stack detail, Monaco editor, dark/light theme) - Deployment: docker-compose.yml, Dockerfiles, nginx reverse proxy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""Host / Docker system information."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from auth import get_current_user
|
||||
from config import settings
|
||||
from docker_client import DockerError, get_client, safe_call
|
||||
from models.user import User
|
||||
|
||||
router = APIRouter(prefix="/api/system", tags=["system"])
|
||||
|
||||
|
||||
def _read_proc(path: str) -> str:
|
||||
full = os.path.join(settings.HOST_PROC_PATH, path)
|
||||
if not os.path.isfile(full):
|
||||
full = os.path.join("/proc", path)
|
||||
try:
|
||||
with open(full, "r", encoding="utf-8") as fh:
|
||||
return fh.read()
|
||||
except OSError:
|
||||
return ""
|
||||
|
||||
|
||||
def _mem_info() -> dict:
|
||||
info = {}
|
||||
for line in _read_proc("meminfo").splitlines():
|
||||
parts = line.split(":")
|
||||
if len(parts) == 2:
|
||||
key = parts[0].strip()
|
||||
val = parts[1].strip().split()[0]
|
||||
try:
|
||||
info[key] = int(val) * 1024 # kB -> bytes
|
||||
except ValueError:
|
||||
pass
|
||||
total = info.get("MemTotal", 0)
|
||||
available = info.get("MemAvailable", info.get("MemFree", 0))
|
||||
return {"total": total, "available": available, "used": max(total - available, 0)}
|
||||
|
||||
|
||||
def _uptime() -> float:
|
||||
raw = _read_proc("uptime")
|
||||
try:
|
||||
return float(raw.split()[0])
|
||||
except (IndexError, ValueError):
|
||||
return 0.0
|
||||
|
||||
|
||||
def _cpu_count() -> int:
|
||||
return os.cpu_count() or 0
|
||||
|
||||
|
||||
def _disk_usage() -> dict:
|
||||
try:
|
||||
usage = shutil.disk_usage(settings.DATA_DIR)
|
||||
return {"total": usage.total, "used": usage.used, "free": usage.free}
|
||||
except OSError:
|
||||
return {"total": 0, "used": 0, "free": 0}
|
||||
|
||||
|
||||
@router.get("/info")
|
||||
def system_info(_user: User = Depends(get_current_user)) -> dict:
|
||||
docker_version = ""
|
||||
host_os = ""
|
||||
containers_running = 0
|
||||
containers_total = 0
|
||||
try:
|
||||
client = get_client()
|
||||
version = safe_call(client.version)
|
||||
docker_version = version.get("Version", "")
|
||||
info = safe_call(client.info)
|
||||
host_os = info.get("OperatingSystem", "")
|
||||
containers_running = info.get("ContainersRunning", 0)
|
||||
containers_total = info.get("Containers", 0)
|
||||
except DockerError as exc:
|
||||
docker_version = f"unavailable ({exc.error})"
|
||||
|
||||
return {
|
||||
"docker_version": docker_version,
|
||||
"host_os": host_os,
|
||||
"hostname": os.uname().nodename,
|
||||
"cpu_cores": _cpu_count(),
|
||||
"ram": _mem_info(),
|
||||
"disk": _disk_usage(),
|
||||
"uptime_seconds": _uptime(),
|
||||
"containers_running": containers_running,
|
||||
"containers_total": containers_total,
|
||||
"gpus": [], # populated in Phase 2
|
||||
}
|
||||
Reference in New Issue
Block a user