Files
zmb-webui/backend/routers/pools.py
T
patrick f49793e6f2 Refactor: Java-Klassen aus Services entfernt + kritische Bugs gefixt
- AuthService, SystemInfo, IdentitiesManager Klassen → Modul-Funktionen
- grp.getall() → grp.getgrall() (Bug: Methode existierte nie)
- open('/proc/loadavg') ohne context manager gefixt (File-Handle-Leak)
- rx_packets/tx_packets null-check im Frontend (toLocaleString auf undefined)
- PoolCard onClick: /pools/{name} → /zfs (Route existierte nicht, löste Seitenreload aus)
- Alle Router-Imports auf Modul-Aliase umgestellt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 14:11:32 +02:00

191 lines
6.4 KiB
Python

"""
Pool management endpoints
"""
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from typing import List
import re
from services.zfs_runner import zfs_runner
from services import auth as auth_service
from models import Pool, PoolStatus, PoolHealth
router = APIRouter(prefix="/api/pools", tags=["pools"])
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
@router.get("/", response_model=List[Pool])
async def list_pools():
"""
Get list of all ZFS pools (public)
"""
try:
pools = zfs_runner.list_pools()
# Convert to Pydantic models
return [
Pool(
name=p["name"],
size=p["size"],
alloc=p["alloc"],
free=p["free"],
fragmentation=p["fragmentation"],
capacity=p["capacity"],
health=PoolHealth(p["health"])
)
for p in pools
]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{pool_name}", response_model=PoolStatus)
async def get_pool_status(pool_name: str):
"""
Get detailed status of specific pool (public)
"""
try:
status_data = zfs_runner.get_pool_status(pool_name)
if not status_data:
raise HTTPException(status_code=404, detail=f"Pool {pool_name} not found")
# Map state to health enum
health = PoolHealth.ONLINE
if "state" in status_data and status_data["state"]:
try:
health = PoolHealth(status_data["state"])
except ValueError:
health = PoolHealth.ONLINE
return PoolStatus(
name=pool_name,
state=status_data.get("state"),
health=health,
scan=status_data.get("scan"),
errors=status_data.get("errors"),
vdevs=status_data.get("vdevs", [])
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{pool_name}/scrub")
async def scrub_pool(pool_name: str, current_user: str = Depends(get_current_user)):
"""
Start or resume scrub on pool
"""
try:
result = zfs_runner.scrub_pool(pool_name)
if result.get("status") == "error":
raise HTTPException(status_code=400, detail=result.get("message"))
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{pool_name}/clear")
async def clear_pool_errors(pool_name: str, current_user: str = Depends(get_current_user)):
"""
Clear error counters on pool
"""
try:
stdout, stderr, rc = zfs_runner.run_command(["zpool", "clear", pool_name])
if rc != 0:
raise HTTPException(status_code=400, detail=stderr.strip() or "Failed to clear errors")
return {"status": "success", "message": f"Errors cleared on {pool_name}"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{pool_name}/resilver")
async def resilver_pool(pool_name: str, current_user: str = Depends(get_current_user)):
"""
Start resilver on pool
"""
try:
stdout, stderr, rc = zfs_runner.run_command(["zpool", "resilver", pool_name])
if rc != 0:
raise HTTPException(status_code=400, detail=stderr.strip() or "Failed to start resilver")
return {"status": "success", "message": f"Resilver started on {pool_name}"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{pool_name}/disk/offline")
async def disk_offline(pool_name: str, disk: str, current_user: str = Depends(get_current_user)):
try:
stdout, stderr, rc = zfs_runner.run_command(["zpool", "offline", pool_name, disk])
if rc != 0:
raise HTTPException(status_code=400, detail=stderr.strip() or "Failed to offline disk")
return {"status": "success", "message": f"{disk} offlined"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{pool_name}/disk/online")
async def disk_online(pool_name: str, disk: str, current_user: str = Depends(get_current_user)):
try:
stdout, stderr, rc = zfs_runner.run_command(["zpool", "online", pool_name, disk])
if rc != 0:
raise HTTPException(status_code=400, detail=stderr.strip() or "Failed to online disk")
return {"status": "success", "message": f"{disk} onlined"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{pool_name}/disk/detach")
async def disk_detach(pool_name: str, disk: str, current_user: str = Depends(get_current_user)):
try:
stdout, stderr, rc = zfs_runner.run_command(["zpool", "detach", pool_name, disk])
if rc != 0:
raise HTTPException(status_code=400, detail=stderr.strip() or "Failed to detach disk")
return {"status": "success", "message": f"{disk} detached"}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/disks/{disk}/smart")
async def get_disk_smart(disk: str, current_user: str = Depends(get_current_user)):
"""
Get SMART data for a specific disk (e.g. sda, sdb, nvme0n1).
Requires smartmontools installed on the system.
"""
# Basic validation to prevent path traversal
if not re.match(r'^[a-zA-Z0-9]+$', disk):
raise HTTPException(status_code=400, detail="Invalid disk name")
try:
data = zfs_runner.get_smart_info(disk)
return data
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/clear-cache")
async def clear_cache(current_user: str = Depends(get_current_user)):
"""
Clear ZFS command cache (for testing/debugging)
"""
zfs_runner.clear_cache()
return {"status": "success", "message": "Cache cleared"}