Service snapshot_mgmt_service.py:
- list_snapshots(db, vcenter_filter_id=None): itere les vCenters actifs, walk recursif
des snapshot tree de chaque VM, retourne (vcenter, vm, snap_name, snap_id, vm_moid,
created_at, age_days, author, description, is_current)
- delete_snapshot(db, vcenter_id, vm_moid, snap_id, remove_children=False): supprime
un snapshot par moRef, attend la fin de la task vCenter
- Auteur deduit du prefixe du nom (format PatchCenter '<auteur>_YYYY-MM-DD_<suffixe>')
Router /snapshots:
- GET /snapshots: page principale (filtres + table)
- POST /snapshots/list: AJAX scan vCenters, retourne JSON
- POST /snapshots/delete: AJAX suppression batch, double confirmation cote UI
Template snapshots.html:
- Filtres: vCenter, auteur, 'Mes snapshots uniquement' (preselectionne user courant),
age min en jours (defaut 3)
- Table avec checkboxes, sel-all, badge age (vert <3j, orange 3-7j, rouge >7j)
- Bouton 'Charger/Refresh' (lazy load, eviter scan auto au pageload)
- Bouton 'Supprimer la selection' avec 2 confirmations + liste des snapshots
- Recharge auto apres suppression
Nav: lien '📸 Snapshots VM' ajoute dans le menu Patching.
122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
"""Router /snapshots — listing + suppression des snapshots VM existants sur les vCenters."""
|
|
import logging
|
|
from fastapi import APIRouter, Request, Depends, Form
|
|
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
from sqlalchemy import text
|
|
|
|
from ..dependencies import get_db, get_current_user, get_user_perms, can_view, can_edit, base_context
|
|
from ..config import APP_NAME
|
|
|
|
log = logging.getLogger("patchcenter.snapshots_router")
|
|
|
|
router = APIRouter()
|
|
templates = Jinja2Templates(directory="app/templates")
|
|
|
|
|
|
def _can_view_snaps(perms):
|
|
return can_view(perms, "campaigns") or can_view(perms, "planning") or can_view(perms, "settings")
|
|
|
|
|
|
def _can_delete_snaps(perms):
|
|
return can_edit(perms, "campaigns") or can_edit(perms, "planning") or can_edit(perms, "settings")
|
|
|
|
|
|
def _get_user_intervenant_name(db, user):
|
|
"""Retourne le nom utilisé comme préfixe dans les snapshots PatchCenter de cet utilisateur.
|
|
Priorité : settings 'patcher' > username."""
|
|
try:
|
|
from ..services.secrets_service import get_secret
|
|
patcher = (get_secret(db, "patcher") or "").strip()
|
|
if patcher:
|
|
return patcher
|
|
except Exception:
|
|
pass
|
|
return user.get("username", "")
|
|
|
|
|
|
@router.get("/snapshots", response_class=HTMLResponse)
|
|
async def snapshots_page(request: Request, db=Depends(get_db)):
|
|
user = get_current_user(request)
|
|
if not user:
|
|
return RedirectResponse(url="/login")
|
|
perms = get_user_perms(db, user)
|
|
if not _can_view_snaps(perms):
|
|
return RedirectResponse(url="/dashboard")
|
|
|
|
intervenant = _get_user_intervenant_name(db, user)
|
|
vcenters = db.execute(text(
|
|
"SELECT id, name, endpoint FROM vcenters WHERE is_active = true ORDER BY name"
|
|
)).fetchall()
|
|
|
|
ctx = base_context(request, db, user)
|
|
ctx.update({
|
|
"app_name": APP_NAME,
|
|
"intervenant_default": intervenant,
|
|
"vcenters": vcenters,
|
|
"can_delete": _can_delete_snaps(perms),
|
|
})
|
|
return templates.TemplateResponse("snapshots.html", ctx)
|
|
|
|
|
|
@router.post("/snapshots/list")
|
|
async def snapshots_list(request: Request, db=Depends(get_db),
|
|
vcenter_id: str = Form("")):
|
|
"""Endpoint AJAX : liste les snapshots (tous vCenters ou un seul)."""
|
|
user = get_current_user(request)
|
|
if not user:
|
|
return JSONResponse({"ok": False, "msg": "Non authentifié"}, status_code=401)
|
|
perms = get_user_perms(db, user)
|
|
if not _can_view_snaps(perms):
|
|
return JSONResponse({"ok": False, "msg": "Permission refusée"}, status_code=403)
|
|
|
|
from ..services.snapshot_mgmt_service import list_snapshots
|
|
vc_id = int(vcenter_id) if vcenter_id and vcenter_id.isdigit() else None
|
|
res = list_snapshots(db, vcenter_filter_id=vc_id)
|
|
return JSONResponse(res)
|
|
|
|
|
|
@router.post("/snapshots/delete")
|
|
async def snapshots_delete(request: Request, db=Depends(get_db)):
|
|
"""Endpoint AJAX : supprime une liste de snapshots.
|
|
Body JSON : [{vcenter_id, vm_moid, snap_id}, ...]"""
|
|
user = get_current_user(request)
|
|
if not user:
|
|
return JSONResponse({"ok": False, "msg": "Non authentifié"}, status_code=401)
|
|
perms = get_user_perms(db, user)
|
|
if not _can_delete_snaps(perms):
|
|
return JSONResponse({"ok": False, "msg": "Permission refusée"}, status_code=403)
|
|
|
|
try:
|
|
body = await request.json()
|
|
except Exception:
|
|
return JSONResponse({"ok": False, "msg": "Body JSON invalide"}, status_code=400)
|
|
items = body.get("items") if isinstance(body, dict) else None
|
|
if not isinstance(items, list) or not items:
|
|
return JSONResponse({"ok": False, "msg": "Aucun snapshot ciblé"}, status_code=400)
|
|
|
|
from ..services.snapshot_mgmt_service import delete_snapshot
|
|
results = []
|
|
n_ok = 0
|
|
for it in items:
|
|
try:
|
|
r = delete_snapshot(
|
|
db,
|
|
vcenter_id=int(it["vcenter_id"]),
|
|
vm_moid=str(it["vm_moid"]),
|
|
snap_id=str(it["snap_id"]),
|
|
remove_children=bool(it.get("remove_children", False)),
|
|
)
|
|
except Exception as e:
|
|
r = {"ok": False, "msg": f"Param error: {e}"}
|
|
r["vm_name"] = it.get("vm_name", "?")
|
|
r["snap_name"] = it.get("snap_name", "?")
|
|
results.append(r)
|
|
if r.get("ok"):
|
|
n_ok += 1
|
|
return JSONResponse({
|
|
"ok": n_ok == len(items),
|
|
"summary": f"{n_ok}/{len(items)} snapshots supprimés",
|
|
"results": results,
|
|
})
|