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>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
Pool management endpoints
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from typing import List
|
||||
|
||||
from services.zfs_runner import zfs_runner
|
||||
from services.auth import 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("/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"}
|
||||
Reference in New Issue
Block a user