diff --git a/backend/routers/pools.py b/backend/routers/pools.py index f591523..f785413 100644 --- a/backend/routers/pools.py +++ b/backend/routers/pools.py @@ -93,6 +93,38 @@ async def scrub_pool(pool_name: str, current_user: str = Depends(get_current_use 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("/clear-cache") async def clear_cache(current_user: str = Depends(get_current_user)): """ diff --git a/frontend/app/zfs/page.tsx b/frontend/app/zfs/page.tsx index fd7d744..89bc34d 100644 --- a/frontend/app/zfs/page.tsx +++ b/frontend/app/zfs/page.tsx @@ -79,7 +79,7 @@ function VdevTree({ vdevs, depth = 0 }: { vdevs: any[]; depth?: number }) { {v.write ?? 0} {v.cksum ?? 0} {v.message || ""} - {v.product || ""} + {v.product || ""} @@ -696,6 +716,31 @@ export default function ZfsPage() { + {/* Pool Context Menu */} + {poolMenu && ( + <> +
setPoolMenu(null)} /> +
+ {[ + { action: "scrub", label: "Scrub Storage Pool" }, + { action: "resilver", label: "Resilver Storage Pool" }, + { action: "clear", label: "Clear Storage Pool Errors" }, + ].map(({ action, label }) => ( + + ))} +
+ + )} + {/* Snapshot Context Menu */} {snapContextMenu && ( <> diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts index 5a97596..00f1daf 100644 --- a/frontend/lib/api.ts +++ b/frontend/lib/api.ts @@ -223,6 +223,16 @@ export class ZFSManagerAPI { return response.data } + async clearPoolErrors(poolName: string): Promise<{ status: string }> { + const response = await this.client.post(`/api/pools/${poolName}/clear`) + return response.data + } + + async resilverPool(poolName: string): Promise<{ status: string }> { + const response = await this.client.post(`/api/pools/${poolName}/resilver`) + return response.data + } + // Datasets async getDatasets(pool: string = "tank"): Promise { const response = await this.client.get("/api/datasets/", { params: { pool } })