Files
zmb-webui/backend/routers/system.py
T
Claude Code 92bed208e0 ZMB Webui: Complete Project – Rebrand & Initial Clean Commit
ARCHITECTURE
============
Backend: FastAPI + uvicorn (port 8000)
  - JWT authentication with PAM system users
  - ZFS CLI wrapper with caching (30-60s TTL)
  - WebSocket pool status broadcaster (30s interval)
  - Services: auth, zfs_runner, file_manager, shares, identities, system_info
  - Routers: pools, datasets, snapshots, shares, identities, navigator, system

Frontend: Next.js 15 + TypeScript (static export)
  - Incremental Static Regeneration (ISR) for weak hardware
  - Type-safe API client (lib/api.ts)
  - Dark mode + custom Tailwind theme
  - Pages: Dashboard, Login, Snapshots, Datasets, Shares, etc.

DEPLOYMENT
==========
Test Target: 192.168.1.179:8090 (Debian LXC)
Production: 10.66.120.3:9090 (Raspberry Pi 4GB ARM64)
Updater: Automated Gitea-based deployment (update-test.sh, update-pi.sh)

FEATURES COMPLETED
==================
Phase 3a: Dashboard Quick Stats (System, CPU, Memory, Storage)
  - Real-time stats with color-coded progress bars
  - Responsive grid layout (mobile: 1, tablet: 2, desktop: 4 columns)
  - ISR-optimized for fast loads on weak hardware

REBRANDING
==========
Renamed throughout:
  - Project: 'ZFS Manager' → 'ZMB Webui'
  - Services: 'zfs-manager' → 'zmb-webui'
  - Systemd units: zfs-manager-backend → zmb-webui-backend
  - Configuration files and documentation

Co-Authored-By: Patrick <patrick@perlbach24.de>
2026-04-22 00:43:05 +02:00

211 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
System Management endpoints like cockpit-system
"""
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
from services.system_info import system_info
from services.auth import auth_service
router = APIRouter(prefix="/api/system", tags=["system"])
security = HTTPBearer()
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
"""Verify JWT token and return username"""
username = auth_service.verify_token(credentials.credentials)
if not username:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
return username
class SetHostnameRequest(BaseModel):
hostname: str
class SetTimeRequest(BaseModel):
iso: str
# ============== SYSTEM INFO ==============
@router.get("/info")
async def get_info():
"""Get general system information (public)"""
return system_info.get_system_info()
@router.get("/hostname")
async def get_hostname():
"""Get system hostname (public)"""
result = system_info.get_hostname()
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
@router.post("/hostname")
async def set_hostname(
request: SetHostnameRequest,
current_user: str = Depends(get_current_user)
):
"""Set system hostname"""
result = system_info.set_hostname(request.hostname)
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
# ============== UPTIME ==============
@router.get("/uptime")
async def get_uptime():
"""Get system uptime (public)"""
result = system_info.get_uptime()
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
# ============== MEMORY ==============
@router.get("/memory")
async def get_memory():
"""Get memory usage (public)"""
result = system_info.get_memory()
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
# ============== CPU ==============
@router.get("/cpu")
async def get_cpu_info():
"""Get CPU information (public)"""
result = system_info.get_cpu_info()
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
# ============== TIME ==============
@router.get("/time")
async def get_time():
"""Get system time (public)"""
result = system_info.get_time()
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
@router.post("/time")
async def set_time(
request: SetTimeRequest,
current_user: str = Depends(get_current_user)
):
"""Set system time (ISO format)"""
result = system_info.set_time(request.iso)
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
# ============== UPDATES ==============
@router.get("/updates")
async def check_updates(current_user: str = Depends(get_current_user)):
"""Check available updates"""
result = system_info.get_updates()
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
# ============== REBOOT/SHUTDOWN ==============
@router.post("/reboot")
async def reboot(current_user: str = Depends(get_current_user)):
"""Reboot system"""
result = system_info.reboot()
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
@router.post("/shutdown")
async def shutdown(current_user: str = Depends(get_current_user)):
"""Shutdown system"""
result = system_info.shutdown()
if "error" in result:
raise HTTPException(status_code=400, detail=result["error"])
return result
# ============== NETWORK ==============
@router.get("/network")
async def get_network():
"""Get network interface information (public)"""
result = system_info.get_network_info()
if "error" in result:
raise HTTPException(status_code=500, detail=result["error"])
return result
@router.get("/network/traffic")
async def get_network_traffic():
"""Get network interface traffic (RX/TX bytes) (public)"""
result = system_info.get_network_traffic()
if "error" in result:
raise HTTPException(status_code=500, detail=result["error"])
return result
# ============== DISK I/O ==============
@router.get("/diskio")
async def get_diskio():
"""Get disk I/O statistics (read/write operations and bytes) (public)"""
result = system_info.get_disk_io()
if "error" in result:
raise HTTPException(status_code=500, detail=result["error"])
return result
# ============== SERVICES ==============
@router.get("/services")
async def get_services():
"""Get running systemd services (public)"""
result = system_info.get_services()
if "error" in result:
raise HTTPException(status_code=500, detail=result["error"])
return result
@router.get("/units")
async def get_units():
"""Get all systemd units (services, targets, sockets, timers, paths) (public)"""
result = system_info.get_all_units()
if "error" in result:
raise HTTPException(status_code=500, detail=result["error"])
return result
# ============== LOGS ==============
@router.get("/logs")
async def get_logs(limit: int = 20):
"""Get recent system logs (public)"""
result = system_info.get_journal_logs(limit)
if "error" in result:
raise HTTPException(status_code=500, detail=result["error"])
return result