diff --git a/backend/routers/backend.py b/backend/routers/backend.py index 554a733..bea2519 100644 --- a/backend/routers/backend.py +++ b/backend/routers/backend.py @@ -428,14 +428,21 @@ async def get_backends(cluster_id: Optional[int] = None, include_inactive: bool # Check if backend has pending changes via: # 1. Config versions (pending_backend_ids) - version name parsing # 2. Entity's own last_config_status (for bulk import and other operations) - # 3. Backend is inactive (soft delete) - BUT NOT if REJECTED + # 3. Backend is inactive (soft delete) AND pending - NOT if already APPLIED has_config_version = backend["id"] in pending_backend_ids has_pending_status = backend.get("last_config_status") == "PENDING" is_inactive = not backend.get("is_active", True) is_rejected = backend.get("last_config_status") == "REJECTED" + is_applied = backend.get("last_config_status") == "APPLIED" - # CRITICAL: Exclude REJECTED entities from pending (already rejected, no action needed) - backend["has_pending_config"] = (has_config_version or has_pending_status or is_inactive) and not is_rejected + # CRITICAL FIX: Inactive backend should only be pending if not already APPLIED/REJECTED + # Problem: Backend with is_active=FALSE and last_config_status='APPLIED' was showing as pending + # Result: Apply/Reject couldn't process it (no PENDING status), but UI kept showing it + # Solution: Inactive backend is only pending if last_config_status is PENDING + is_inactive_and_pending = is_inactive and has_pending_status + + # CRITICAL: Exclude REJECTED and APPLIED inactive entities from pending + backend["has_pending_config"] = (has_config_version or has_pending_status or is_inactive_and_pending) and not is_rejected and not (is_inactive and is_applied) # Enhanced debug logging if backend['name'] == 'backend7' or has_config_version or has_pending_status: diff --git a/backend/routers/frontend.py b/backend/routers/frontend.py index be69e0d..e001908 100644 --- a/backend/routers/frontend.py +++ b/backend/routers/frontend.py @@ -357,12 +357,16 @@ async def get_frontends(cluster_id: Optional[int] = None, include_inactive: bool "updated_at": f["updated_at"].isoformat().replace('+00:00', 'Z') if f["updated_at"] else None, "cluster_id": f.get("cluster_id"), "has_pending_config": ( + # CRITICAL FIX: Same as backend.py logic for consistency + # Problem: Frontend with is_active=FALSE and last_config_status='APPLIED' was showing as pending + # Solution: Inactive frontend is only pending if last_config_status is PENDING, not APPLIED ( - f["id"] in pending_frontend_ids or - f.get("last_config_status") == "PENDING" or - not f.get("is_active", True) + (f["id"] in pending_frontend_ids) or + (f.get("last_config_status") == "PENDING") or + (not f.get("is_active", True) and f.get("last_config_status") == "PENDING") ) and - f.get("last_config_status") != "REJECTED" # Exclude REJECTED entities + (f.get("last_config_status") != "REJECTED") and + not (not f.get("is_active", True) and f.get("last_config_status") == "APPLIED") ) } for f in frontends ]