feat: add policy deletion, improve dry run handling, and enhance policy designer UX
CI / backend (push) Failing after 3s
CI / frontend (push) Failing after 32s

Add DELETE /policies/{policy_id} endpoint with audit logging, improve firewall apply to handle dry run mode without calling provider and track operation success separately from applied status, update Proxmox provider error message to clarify rule-to-VM mapping requirement, add dry run explanation text to FirewallPreview with conditional button labels, enhance Policies page with expanded DataTable columns showing source
This commit is contained in:
2026-07-09 13:32:35 +02:00
parent b382d4362c
commit 7fa4bcaad1
5 changed files with 103 additions and 30 deletions
+36 -15
View File
@@ -725,6 +725,17 @@ def update_policy(policy_id: str, payload: PolicyCreate, user: CurrentUser, db:
return policy
@api_router.delete("/policies/{policy_id}")
def delete_policy(policy_id: str, user: CurrentUser, db: Session = Depends(get_db)) -> dict[str, str]:
policy = db.get(Policy, policy_id)
if not policy:
raise HTTPException(status_code=404, detail="Policy not found")
db.delete(policy)
commit_or_400(db)
write_audit(db, action="policy.deleted", object_type="policy", object_id=policy_id, user_id=user.id)
return {"status": "deleted", "id": policy_id}
@api_router.post("/policies/{policy_id}/compile", response_model=PolicyRead)
def compile_policy(policy_id: str, user: CurrentUser, db: Session = Depends(get_db)) -> Policy:
from app.services.policy_engine import PolicyEngine
@@ -760,24 +771,34 @@ async def firewall_apply(payload: FirewallApplyRequest, user: CurrentUser, db: S
if not policy or not cluster:
raise HTTPException(status_code=404, detail="Policy or cluster not found")
preview = await FirewallOrchestrator().preview(cluster, policy)
provider = get_provider(cluster.provider)
result = await provider.apply_rules(
ProviderConnection(
api_url=cluster.api_url,
token=cluster.token_ref or "",
verify_tls=cluster.verify_tls,
read_only=payload.dry_run or cluster.mode == "read_only",
),
preview.generated_rules,
)
if payload.dry_run:
result = {
"applied": False,
"dry_run": True,
"reason": "Dry run completed. No firewall rules were applied.",
"rules": preview.generated_rules,
}
else:
provider = get_provider(cluster.provider)
result = await provider.apply_rules(
ProviderConnection(
api_url=cluster.api_url,
token=cluster.token_ref or "",
verify_tls=cluster.verify_tls,
read_only=cluster.mode == "read_only",
),
preview.generated_rules,
)
applied = bool(result.get("applied"))
operation_success = applied or payload.dry_run
job = Job(
kind="firewall.apply",
status="success" if result.get("applied") else "failed",
status="success" if operation_success else "failed",
progress=100,
started_at=datetime.utcnow(),
finished_at=datetime.utcnow(),
logs=[f"Policy {policy.name}", f"Dry run: {payload.dry_run}", str(result)],
error=None if result.get("applied") else result.get("reason", "Provider did not apply rules"),
logs=[f"Policy {policy.name}", f"Cluster mode: {cluster.mode}", f"Dry run: {payload.dry_run}", str(result)],
error=None if operation_success else result.get("reason", "Provider did not apply rules"),
)
db.add(job)
commit_or_400(db)
@@ -788,8 +809,8 @@ async def firewall_apply(payload: FirewallApplyRequest, user: CurrentUser, db: S
object_id=policy.id,
user_id=user.id,
new_values={"request": payload.model_dump(), "result": result},
result="success" if result.get("applied") else "blocked",
error_text=None if result.get("applied") else result.get("reason"),
result="success" if operation_success else "blocked",
error_text=None if operation_success else result.get("reason"),
)
return {"job_id": job.id, "preview": preview.model_dump(), "provider_result": result}