diff --git a/backend/app/api/v1/router.py b/backend/app/api/v1/router.py index bfce30f..e68abce 100644 --- a/backend/app/api/v1/router.py +++ b/backend/app/api/v1/router.py @@ -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} diff --git a/backend/app/services/providers/proxmox.py b/backend/app/services/providers/proxmox.py index e59b16c..fd53c1a 100644 --- a/backend/app/services/providers/proxmox.py +++ b/backend/app/services/providers/proxmox.py @@ -130,4 +130,8 @@ class ProxmoxProvider(Provider): async def apply_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]: if connection.read_only: return {"applied": False, "reason": "Cluster is read-only", "rules": rules} - return {"applied": False, "reason": "Apply adapter intentionally requires explicit implementation", "rules": rules} + return { + "applied": False, + "reason": "Live Proxmox firewall apply needs rule-to-VM mapping before NexaFabric can safely write provider rules.", + "rules": rules, + } diff --git a/frontend/src/pages/FirewallPreview.tsx b/frontend/src/pages/FirewallPreview.tsx index d4db96a..8626bc6 100644 --- a/frontend/src/pages/FirewallPreview.tsx +++ b/frontend/src/pages/FirewallPreview.tsx @@ -49,13 +49,18 @@ export function FirewallPreview() { setDryRun(event.target.checked)} /> Dry run +
{preview || "No policy output yet."}