A live-deployment audit pass over the v1.5.0 Site Wizard + ACME
Diagnostic Panel surface. Two adversarial review rounds (R23, R24)
each capped by an end-to-end smoke test against a multi-cluster
staging deployment.
Bulgu #83 — Frontend Management page warned about stale data
without a clear retry CTA. The toast now carries an in-place
"Reload" action and the page-level Empty state surfaces the same
recovery affordance, so operators never get stuck on a stale-data
view without an obvious way out.
Bulgu #84 — ACME diagnostics ran with the wrong "last_heartbeat"
column reference against the agents table. Aligned the SELECT
with the actual schema column (`last_seen`); pinned by an idempotent
regression test in `test_acme_diagnostics.py`.
Bulgu #85 — ACME order error_detail rendering could leak the raw
asyncpg/SQL exception class name when humanize_error_detail
encountered an unhandled CA response shape. Added a backwards-
compatible fallback branch that emits an "ACME error (raw)" panel
without exposing parse_error class name to the user.
Bulgu #86 — Multi-cluster apply with concurrent rejects could
leave wizard_staged orders dangling without their parent draft.
Pinned via reject_order_with_cluster_orphan test.
Bulgu #87 — Frontend Management page list virtualization
mis-keyed during a re-sort + stale-row replace race; fixed by
keying rows on `id + version` so React reconciler does not reuse
DOM for a logically different row.
Bulgu #88 — Site Wizard "Cancel" mid-flow now surfaces an
unsaved-draft prompt with explicit Save / Discard buttons (and
the same prompt on browser tab close), so the operator never
loses 5 steps of input to an accidental ESC.
Bulgu #89 — Existing-cert SSL mode showed an empty dropdown when
the cluster had >100 certs because the listing endpoint
default-limited results. Endpoint now exposes pagination AND
the wizard switches to client-side filtering above 50 rows.
Bulgu #90 — ACME pre-check on the wizard preview path did NOT
re-validate the account against `letsencrypt_accounts` if the
operator stepped Back/Forward between SSL and Review. Added a
debounced re-validation on Review entry.
Bulgu #93 — Site Wizard hsts_enabled toggle in HTTPS frontend
was idempotent-by-name (the generated `http-response set-header
Strict-Transport-Security` line could duplicate across a Save +
Apply cycle). The renderer now upserts the header in place.
Cumulative outcome: backend pytest 1084/1084, frontend lint
clean, and a 6-hour live-deployment smoke session against staging
with no regressions reported.
Closes#13, Closes#14.
This release squashes the v1.4.0 → v1.5.0 development line. v1.4.0
shipped the ACME stability & enterprise audit (Issues #10/#11/#12).
v1.5.0 builds on that foundation with two co-equal headline features
plus a 22-round audit campaign hardening the prior configuration
surface. License remains MIT for v1.5.0 (relicense to AGPL-3.0
lands in v1.5.2).
------------------------------------------------------------------
HEADLINE FEATURE A — ACME Diagnostic Panel (Issue #13)
------------------------------------------------------------------
A live pre-flight + post-failure diagnostic surface for every ACME
order, reachable from the ACME Automation page. The panel exists
to make ACME failures legible to operators who do NOT have shell
access to the API host.
Endpoints (`backend/routers/acme_diagnostics.py`):
POST /api/letsencrypt/orders/{order_id}/diagnostics
Run the full 5-check suite (DNS / port-80 / routing /
account / agents) and humanize the order's `error_detail`
(>=11 RFC-8555 problem types, backwards compatible with
legacy plain-string failures).
POST /api/letsencrypt/orders/{order_id}/diagnostics/
{check_id}/rerun
Re-run a single check in place — used by the "Re-run"
button on every row of the modal's pre-flight table.
GET /api/letsencrypt/orders/{order_id}/events
Merged event timeline combining the typed
`acme_order_events` rows with correlated
`user_activity_logs` entries (resource_type =
'letsencrypt_order' AND resource_id = order_id). The
diagnostic modal auto-tails this timeline every 5 seconds
while open.
Service-level checks (`backend/services/acme_diagnostics.py`):
* DNS resolution via stdlib socket.gethostbyname_ex through
run_in_executor (intentionally avoiding an aiodns runtime
dep for v1.5.0).
* Port-80 HEAD probe, target locked to the order's domains,
success on HTTP 200 OR 404, warns on egress timeout
(corp egress policies routinely blackhole outbound 80 —
fail-hard would be too noisy).
* SSRF guard: probe refuses non-public IPs and surfaces the
skip in the diagnostic result; IPv4-mapped IPv6 normalisation
closes the `::ffff:169.254.169.254` cloud-metadata vector.
* HAProxy routing presence check: matches the order's
cluster_ids to a port-80 HTTP frontend.
* ACME account validity check against `letsencrypt_accounts`.
* Agent presence check (>=1 active agent in target cluster).
* Every sub-check wrapped in a wall-clock timeout to bound
impact on the API event loop.
RBAC: ssl.read for run, ssl.read for events. Per-user 5/min rate
limit on both run and rerun, backed by the (user_id, action,
created_at DESC) composite index.
Frontend (`frontend/src/components/ACMEAutomation.js`):
* "Diagnose" button on every order row + the existing
"stuck order" warning row.
* Modal with two tabs:
- Pre-flight Checks (Antd Table with status pills + Re-run
buttons + humanized error banner)
- Event Log (Antd Timeline with auto-tail polling, scroll-
to-bottom, pause-on-hover)
* Correlation IDs surfaced in error banners and individual
check fail details for backend-log lookup.
------------------------------------------------------------------
HEADLINE FEATURE B — Site Setup Wizard (Issue #14)
------------------------------------------------------------------
A single guided flow that creates a Backend + Servers + HTTP
Frontend (and optional HTTPS Frontend) in one atomic transaction.
Endpoints (`backend/routers/site_wizard.py`):
POST /api/site-wizard/preview — diff-preview the changeset
POST /api/site-wizard/create — atomic execute
POST /api/site-wizard/reject — clean rollback (including
any wizard_staged ACME
orders)
GET /api/site-wizard/drafts — draft persistence
PUT /api/site-wizard/drafts/{id} — save/update
DELETE /api/site-wizard/drafts/{id}
Feature surface:
* One screen captures both backend (mode + servers) AND
frontend (http + optional https + SSL mode) inputs.
* SSL modes: ACME (new order, HTTP-01 only for v1.5.0),
Upload (existing PEM), Existing (link to a stored cert),
or None.
* ACME-staged path: wizard_staged_until watermark on the
`letsencrypt_orders` row defers finalisation until agent
confirmation; per-mode reject cleanly cancels and rolls
back the staged order.
* Live diff preview against the cluster's current generated
config (renderer-evolution noise stripped — track-sc<N>
dedup, per-server cookie strip, defaults-cookie
inheritance, listen-block flattening).
* Draft persistence with PEM stripped at save time (private
keys never round-trip through the drafts table).
* Per-cluster multi-tenancy: drafts and wizard_staged orders
are isolated to the creating user's cluster scope.
Frontend (`frontend/src/components/SiteWizard.js`):
* 4-step Antd Steps flow: Backend → Frontend → SSL → Review.
* Render the live diff preview inline before commit.
* Antd Form-level validation mirrors backend Pydantic
validators (numeric bounds, HAProxy reserved keywords, ALPN
consistency, IPv6 scope-id, domain regex, server name
dedup).
------------------------------------------------------------------
AUDIT CAMPAIGN — Rounds 1 → 22 (Bulgu #1 → #82)
------------------------------------------------------------------
v1.5.0 includes 22 adversarial review passes. Each round produced
its own commit set in the corporate development line; this squash
collapses those into the v1.5.0 release artefact. Highlights:
Round 1-4 Site Wizard core: dry-run parity, single-line
value injection guard, ACL -f pattern-file block,
SSL parity, timeout regex, form-state pin.
Round 5-7 defaults-cookie inheritance, server-named-cookie
guard, fe/be mode mismatch, duplicate server
names, health_check_uri + server_address
validators.
Round 8-10 cookie_name / cookie_options newline-injection
guard, dry-run parity (round 9), TCP-mode HTTP-only
feature blockers.
Round 11 SSL name path traversal + health-check >= 1.
Round 12-13 SSL & ACME deep dive (Bulgu #23-#32).
Round 14 single-line value injection (Bulgu #33).
Round 15-17 ACME multi-tenant UX, numeric bounds, HAProxy
reserved keywords, ALPN/TLS consistency,
all-backup, multi-domain & multi-user enterprise
edges, drain/HSTS/post-completion (Bulgu
#34-#53).
Round 18-21 concurrency, agent state, TCP-mode HTTP-only,
list size caps, IPv6 scope-id, preview account
validation, TCP backend + balance uri reject
(Bulgu #54-#61).
Round 22 FE error visibility + 3x stale-data lockouts,
referential integrity + cascade safety,
authentication & authorization, multi-cluster
isolation, apply_pending_changes concurrency,
script injection + bulk import multi-tenancy,
prefix-stripped signature comparison
(Bulgu #62-#82).
------------------------------------------------------------------
NO CORPORATE-SPECIFIC ARTIFACTS
------------------------------------------------------------------
This squash deliberately sanitises corporate hostnames, container
registry references, and TLS secret names into generic
placeholders (`your-registry.example.com/your-org`,
`haproxy-openmanager*.example.com`, `wildcard-tls`,
`taylanbakircioglu/haproxy-openmanager-*`) so the public artefact
contains no internal infrastructure detail. Pilot / development
history that retained those values stays in the corporate fork
and is NOT part of this commit.
- Agent scripts now detect and send ip_address in DAEMON heartbeat (Linux: ip route, macOS: ifconfig)
- Backend validates agent-reported IPs via ipaddress stdlib, COALESCE preserves existing on NULL
- IP/VIP change logging (non-critical, try/except wrapped) for operational visibility
- New source_file_hash column on agent_script_templates for reliable update detection
- Migration changed to ON CONFLICT DO NOTHING to prevent overwriting UI-customized scripts on restart
- GET /versions returns script_update_available flag (disk hash vs DB hash comparison with fallback)
- Frontend Alert banner warns users of new agent script versions and directs to Reset to Defaults
- Reset to Defaults and Popconfirm modals explicitly warn about custom script edit loss
- Full backward compatibility: old agents without ip_address field continue working unchanged
Made-with: Cursor
- Server-level change detection in bulk import (field-by-field comparison
for 17 server attributes with UPDATE/NO CHANGES status and tooltip)
- Multi-select delete for backends and frontends with dependency checks
- Dashboard "Backends Summary" address column for servers
- Fix unique constraint violation on bulk-create for existing servers
(natural key lookup matching DB constraint instead of backend_id FK)
- ORDER BY is_active DESC on all entity lookups to prefer active records
- Strip auto-generated content (ACME, rate-limit, WAF) from bulk import
comparison to eliminate false positive changes on re-import
- Fix toolbar overflow with Space wrap prop
- Frontend bulk delete modal clarity (selected vs deletable count)
- Version bump to 1.3.0
Made-with: Cursor
Bulk import accepted dots in frontend/backend/server names but UI and
backend validators rejected them with ^[a-zA-Z0-9_-]+$. After import,
entities with dots could not be edited. HAProxy itself allows dots in
section names, so the regex is expanded to ^[a-zA-Z0-9_.-]+$ across
all 12 validation points (5 React form rules, 1 ACL char-strip,
3 Pydantic validators, 1 WAF validator, 2 config-validator warnings).
- Fix frontend port conflict validation to consider bind_address+port
combination instead of port-only. HAProxy allows same port on different
bind addresses (e.g., bind 10.0.0.1:443 vs bind 10.0.0.2:443). This
was blocking frontend edit/save in multi-VIP environments.
- Add form field dependency so port re-validates when bind_address changes.
- Fix API URL construction using window.location.host instead of hostname
to preserve non-standard ports (e.g., :8080), preventing CORS errors
in BulkConfigImport.
- Make CORS_ORIGINS configurable via environment variable.
- Step 3 (Enable ACME on Cluster) now shows a process icon instead of
a misleading green checkmark when ACME is enabled but not yet applied.
Per-cluster "(pending apply)" annotation for multi-cluster setups.
- Step 4 button and all /apply-management navigation buttons now say
"Apply Changes" instead of "Configure" for clearer guidance.
- Setup Guide auto-selects the correct cluster before navigating to
Apply Management, showing pending cluster names in alerts.
- Pending ACME disable changes are now correctly detected in Step 4
even when acme_enabled is already FALSE in the database.
- Entity snapshot rollback for cluster ACME settings: reject correctly
restores acme_enabled/acme_backend_url to pre-change values.
- Deduplication logic prevents "last wins" bug when multiple ACME
toggles are rejected in sequence.
- Connection leak prevention with try/finally around conn2 in ACME
config version creation.
- Step 4 branching uses boolean has_enabled instead of fragile string
truthiness check.
Made-with: Cursor
- Fix critical cascading NULL status bug in ACME challenge flow that could cause 404s
- Add defense-in-depth NULL handling across all ACME service methods
- Add new GET /api/letsencrypt/prerequisites endpoint for configuration checks
- Add interactive ACME Setup Guide with step-by-step navigation links
- Add URL-based tab navigation in Settings and SSL Management pages
- Harden retry flow: return clear 409 errors for invalid/cancelled orders
- Allow cancellation of invalid orders (backend + frontend)
- Improve error message extraction with consistent getErrorMsg helper
- Add status filter tabs (Active/Completed/Failed/All) for order list
- Add visual dimming for cancelled/invalid orders
- Add enhanced pagination with size changer and total count
- Add comprehensive diagnostic logging with ACME: prefix
- Update README with ACME architecture docs, quick start guide, and troubleshooting
Resolves#9
Made-with: Cursor
- Full dark mode support across all pages with lightbulb toggle in header
- Theme preference persisted in localStorage across sessions
- Ant Design 5 token-based theming (40+ components updated)
- Recharts dark mode: axes, grids, tooltips adapt to theme
- Login page redesigned with product-consistent blue-gray palette
- Overscroll bounce background matches dark theme
- All Servers search with multi-field filtering
- Bulk Config Import UI streamlined with collapsible guidelines
- ConfigProvider moved above AppContent for correct token resolution
Made-with: Cursor
Root cause: config_status enum was created with only PENDING and APPLIED
values. The REJECTED value was never added due to a silent duplicate_object
exception in create_essential_tables(). This caused SSL certificate listing
to crash with "invalid input value for enum config_status: REJECTED" on
fresh installations.
Also adds scrollable containers to Apply Management page to prevent
Agent Sync Status card from being pushed off-screen.
Closes#7
Made-with: Cursor
Fixes#6
- Fix NameError in soft-deleted certificate reactivation path by
reordering variable extraction before DB operations
- Replace silent empty-array returns with HTTP 500 on SQL errors,
making failures visible in both API responses and server logs
- Add primary_domain migration for schema consistency across fresh
and upgraded installations (backfill from legacy domain column)
- Use primary_domain in non-cluster SSL query branch for schema
compatibility
- Surface SSL fetch errors in frontend via toast notifications
- Harden connection cleanup in error handlers with try/except
- Add QEMU + Buildx for linux/amd64,linux/arm64 multi-platform
Docker image builds
- Update GitHub Actions to latest versions (checkout v4, login v3,
build-push v6)
Made-with: Cursor
When user changes redirect type from location/prefix to scheme, the
target field (e.g. "https://example.com") stays as-is but the UI shows
a blank Select. If saved without re-selecting, invalid config would be
sent. Now auto-sets target to "https" when switching to scheme type.
Made-with: Cursor
- Enforce mutual exclusivity for -m flags (only one match method at a time)
- Show error status on value field when -f flag is used without absolute
file path, with tooltip explaining the requirement
- Replace free-text input with Select dropdown for redirect scheme type,
restricting to valid values (http/https) only
- Fix flag serialization order (-i → -m → -f) to prevent HAProxy parse errors
Made-with: Cursor
Replace plain TextArea inputs with an interactive card-based visual builder
for ACL rules, backend routing rules, and redirect rules. Includes:
- Structured ACL definition cards with match type, flags, and value fields
- Backend routing cards with operator (if/unless) and ACL condition selector
- Redirect rule cards with type, target, code, and condition fields
- Visual ↔ Raw mode toggle for each section
- HAProxy config preview panel
- Safe flag serialization order (-i → -m → -f) to prevent parse errors
- Guard against empty/incomplete rules in serializers
- Context-aware flag placeholder hints per match type category
Made-with: Cursor
Remove the blue background/border container that looked disconnected
from the page. Instead highlight the Select itself with a subtle blue
border glow when no cluster is selected.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Replace wrapping "active" text in Servers column with compact tag
showing active/total count with tooltip for details
- Add subtle pulse animation and dashed border to cluster selector
when no cluster is selected to guide new users
Co-authored-by: Cursor <cursoragent@cursor.com>
New page that provides a unified view of all IPs across all clusters.
Users can search any IP to identify which cluster/agent/backend it
belongs to, and whether it is an Agent IP, VIP (keepalive), or
Backend Server address.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Description: constrained to 200px width, 2-line clamp with tooltip
for full text on hover. Prevents long descriptions from stretching
the table.
- Connection Type: shortened labels ("Agent"/"Sidecar" instead of
"Agent Connection"/"Sidecar Agent"), reduced column width to 110px.
- HAProxy Version: reduced width from 220px to 170px.
- Keepalive: reduced width from 180px to 155px.
- Table scroll: changed from max-content to 1100px fixed minimum,
so the table fits on normal screens without scrolling.
Co-authored-by: Cursor <cursoragent@cursor.com>
- ClusterManagement: show all unique VIPs from all agents (not just
first agent's VIP) to prevent misleading display in multi-agent
clusters with different virtual IPs.
- ClusterManagement: limit displayed agents to 4 with "+N more"
indicator to prevent row height explosion in large clusters.
- ClusterManagement: make keepalive IP search case-insensitive.
- Dashboard: add fetchAgentsStatus to overview tab auto-refresh
(60s interval) so keepalive state changes are reflected without
manual refresh.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Fix DB: COALESCE(NULLIF) prevented clearing stale MASTER/BACKUP state.
Use CASE WHEN IS NOT NULL to distinguish absent fields (old agents)
from empty fields (keepalived stopped) and properly clear to NULL.
- Fix Redis: explicitly delete cache key when keepalived stops instead
of relying on 90s TTL expiry, preventing stale dashboard data.
- Fix agent scripts: SKIP_TO_DAEMON send_heartbeat now always sends
keepalive_state/keepalive_ip fields (even empty) so backend can
detect stopped keepalived and clear stale data.
- Fix legacy heartbeat endpoint: POST /{agent_id}/heartbeat now also
updates keepalive_state and keepalive_ip fields.
- UI: add horizontal scroll to ClusterManagement table to prevent
column overflow with new Keepalive column.
- UI: add VIP tooltip to Dashboard AgentStatusCard keepalive tag.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add keepalive_state and keepalive_ip columns to agents table (migration + schema)
- Add keepalive fields to AgentHeartbeat Pydantic model (backward compatible)
- Update heartbeat endpoint to persist keepalive data to DB and cache in Redis
- Add multi-method keepalived detection in agent scripts (journalctl, log files, VIP check)
- Update dashboard-stats agents/status API with Redis-first keepalive lookup
- Update GET /api/agents to include keepalive_state and keepalive_ip
- Show MASTER/BACKUP tag in Dashboard AgentStatusCard
- Show keepalive info in Agent Management registered agents table
- Add Keepalive column to Cluster Management table with VIP search support
Co-authored-by: Cursor <cursoragent@cursor.com>
- Timeout/INCOMPLETE logic now applies only to ssl_certificates entity type
- Non-SSL entities (frontend, backend, waf) retain original APPLYING behavior
- Remove all emoji characters from console.log and UI strings
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add per-cluster SSL sync status tracking with APPLYING/SYNCED/PARTIAL states
- Add timeout detection (10min) for stalled SSL deployments showing PARTIAL status
- Add SSL-specific info in Apply Management dialog (cluster count, auto-apply notice)
- Add Deployment Status tab in SSL details showing per-cluster agent sync progress
- Implement SSL auto-reject (reject propagates to all clusters) and auto-undo
- Add conditional entity rollback for SSL reject (time-window based safety check)
- Filter REJECTED status from SSL last_config_status display
- Add pending_cluster_names to SSL list API response
- Optimize agent SSL reload: only trigger HAProxy reload when changed cert is
actually referenced in the cluster's HAProxy config (grep -qF check), preventing
unnecessary reloads on clusters that don't use the updated certificate
- Applied to all 4 SSL deploy paths: Linux/macOS standalone and daemon embedded modes
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the always-Active status badge with actual usage information by
querying which frontends and backend servers reference each certificate.
Backend: detail endpoint returns used_by_frontends, used_by_servers and
usage_count; list endpoint includes usage_count via scalar subqueries
checking both ssl_certificate_id and ssl_certificate_ids JSONB fields.
Frontend: General Info badge shows In Use/Not In Use, new Usage tab with
searchable frontend and server tables, summary card shows In Use count.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Remove useMemo from table columns to prevent stale closures for
upgradeAgent, toggleAgent, deleteAgent after cluster switches
- Add selectedClusterRef and fetchAgentsRef for safe async access
in setTimeout callbacks (upgrade delayed refresh)
- Force-fetch (bypass throttle) after all user actions: toggle,
refresh button, reset scripts, update version
- Remove unused useMemo import
Co-authored-by: Cursor <cursoragent@cursor.com>
- Combine cluster change clear + fetch into single useEffect to prevent
race between clearing agents and throttled fetch being skipped
- Force-fetch agents on cluster change (bypass throttle and loading guard)
- Use loadingRef to avoid stale closure in fetchAgents useCallback
- Reset throttle timer on cluster change so fetch is never blocked
Co-authored-by: Cursor <cursoragent@cursor.com>
Enable showSearch on the header cluster selector so users can quickly
filter clusters by name, pool, connection type or description.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Fix backend cleanup order: delete backend_servers before backends
to prevent orphan records and foreign key constraint violations
- Fix Apply progress getting stuck at "0/55" when all entities deleted
by using verifyRealAgentSync result for accurate syncedCount
- Add completion condition for "all entities deleted" scenario
- Fix cluster selector truncation with dynamic width calculation
- Fix sidebar menu label truncation: increase sider width to 240px,
use concise menu labels, add CSS overflow handling
- Add responsive breakpoints for header title and content padding
- Auto-collapse/expand sidebar on breakpoint change
Co-authored-by: Cursor <cursoragent@cursor.com>
Disabled agents were blocking apply sync progress indefinitely because
they were counted in total_agents but could never report as synced.
Backend:
- agent-sync endpoint: disabled agents excluded from total/synced/unsynced
counts, added disabled_agents and total_agents_including_disabled fields
- SSL cert agent-sync endpoint: same disabled agent exclusion
- Both endpoints still return disabled agents in the list with
sync_excluded: true for UI display
Frontend:
- ApplyManagement: cluster sync complete logic handles 0 enabled agents,
all updateEntityCounts calls pass disabled count, agent table shows
OFF/Excluded tags for disabled agents
- GlobalProgress: shows "(X off)" indicator, visible even when all
agents are disabled
- ProgressContext: agentCounts state supports disabled field
- agentSync utility: verifyRealAgentSync treats null/0-agent sync_status
as synced (nothing to wait for)
Co-authored-by: Cursor <cursoragent@cursor.com>
Fixed TypeError "t.children.toLowerCase is not a function" when searching
in Default Backend dropdown after editing a frontend.
Root cause: option.children was a React element array (multiple JSX parts),
not a string, so toLowerCase() failed.
Solution: Added label prop to Option and use optionLabelProp="label" pattern
(consistent with SSL Certificates Select in same file).
Changes:
- Added optionLabelProp="label" to Select component
- Added label={backendLabel} to each Option
- Changed filterOption to use option.label instead of option.children
- Users can now search by backend name, server count, or "No servers"
- Add haproxy_error_parser.py: Parses HAProxy validation errors with
confidence scoring, extracts entity type/name, line number, error type
- Add ValidationErrorModal.js: Rich modal with parsed error summary,
quick fix suggestions, and manual troubleshooting guide
- Update cluster.py: Integrate error parser into agent-sync and
config-versions endpoints with graceful fallback
- Update ApplyManagement.js: Add validation error banner with quick
navigation buttons and error detail modal
- Update FrontendManagement.js & BackendServers.js: Handle URL params
for deep-linking to entity edit forms with field highlighting
Enables users to see actionable validation failure details directly
in the UI without needing server access for debugging.
- Add new API endpoint to serve uninstall scripts by platform
- Display uninstall script alongside install script in setup wizard
- Add dedicated delete agent modal with 2-step workflow
- Modern UI with gradient banners, platform icons, and info cards
- Enhanced uninstall scripts to clean all agent temp/backup files
- HAProxy service and config remain untouched during uninstall
- Add 'random' and 'first' options to backend balance method selector
- Add balance method validation in config parser with warning for unknown methods
- Update API documentation with all supported balance algorithms
- Fix token-agent relationship not updating when agent config changes
- Agent's api_key now syncs with DB on heartbeat when using different token
- Change Security page badge color from red to blue for better UX
- Add HAProxy Version column to cluster list table
- Show version number with agent names directly (no hover required)
- Fetch agents for each cluster to get haproxy_version info
- Show warning icon (yellow) when agents have different HAProxy versions
- Green color for consistent versions, yellow for mismatched versions
- Agent names displayed below each version in smaller gray text
- UI-only change, no backend modifications
- Safe implementation using existing /api/agents endpoint
- Add haproxy_version field to heartbeat payload in Linux agent script
- Add haproxy_version field to heartbeat payload in macOS agent script
- Display HAProxy version below IP address in Registered Agents list
- Safe extraction with fallback to 'unknown' if haproxy command fails
- Version is updated on every heartbeat (30s interval)
- Green color styling for easy visibility
Backend already supports haproxy_version field in AgentHeartbeat model
and saves it to database on each heartbeat.
CRITICAL BUG FIX: SSL advanced options were not being returned by GET API
Problem:
- Database has ssl_alpn, ssl_npn, ssl_ciphers, etc. columns ✅
- Response builder tries to access them (Line 341-347) ✅
- BUT SELECT statements did NOT include these fields ❌
- Result: f.get('ssl_alpn') returned None for all frontends
Impact:
- Frontend Edit modal always showed empty SSL advanced options fields
- User edits would overwrite existing values with NULL
- Data loss on every frontend edit!
Solution:
- Added all 7 SSL advanced options to ALL 6 SELECT queries:
1. cluster_id filter (line 174)
2. cluster_id fallback (line 190)
3. global include_inactive=True (line 219)
4. global include_inactive=False (line 231)
5. global fallback include_inactive=True (line 246)
6. global fallback include_inactive=False (line 258)
Testing:
- Added debug console.log for SSL advanced options (line 551-559)
- After deployment, check browser console for 'SSL ADVANCED OPTIONS DEBUG'
- Should now show: ssl_alpn: 'h2,http/1.1' etc.
Files Changed:
- backend/routers/frontend.py: All 6 SELECT statements
- frontend/src/components/FrontendManagement.js: Debug logging
🔴 PRODUCTION CRITICAL FIX - Agent HTTP 422 Validation Error
PROBLEM:
- Production agents sending heartbeat with flat system_info fields
- Backend Pydantic model was strict and rejecting unknown fields
- Agents going offline with 'Validation error in request data' (HTTP 422)
ROOT CAUSE:
- Legacy agents embed system_info as flat key-value pairs in heartbeat JSON
- Backend expected only defined fields, rejected extra fields
- No backward compatibility for agent format variations
SOLUTION - BACKEND ONLY (NO AGENT CHANGES):
✅ Added 'extra = "allow"' to AgentHeartbeat Pydantic Config
✅ Backend now accepts both formats:
- Flat format: operating_system, kernel_version, etc. (legacy agents)
- Nested format: system_info: {...} (future agents)
✅ Updated comments to clarify backward compatibility
IMPACT:
- ✅ ZERO CHANGES to production agent scripts
- ✅ Existing agents will work immediately after backend deploy
- ✅ Forward compatible with future agent upgrades
- ✅ Tolerant to agent format variations
SAFETY:
- Minimal change (3 lines)
- Pydantic still validates required fields
- Extra fields ignored silently (no breaking changes)
- Production agents continue without restart or upgrade
DEPLOYMENT:
1. Deploy backend (this commit)
2. Agents come online automatically (no action needed)
3. Agent upgrades can happen later (when convenient)
This fix ensures production stability without touching agent scripts.
COMPLETE UX FIX: Backend validation + Frontend required field
Changes Summary:
1. Backend API validation (waf.py)
2. Frontend API validation (frontend.py)
3. Frontend UI required field (WAFManagement.js)
Problem:
- User creates WAF without selecting frontends
- Backend applies WAF to ALL frontends (unintentional)
- No visual indication that frontend selection is required
- User confused about where WAF is applied
Solution - Part 1: Backend API Validation
waf.py CREATE (lines 418-426):
- Validate frontend_ids not empty
- HTTP 400 if no frontends selected
- Error: "At least one frontend must be selected"
waf.py UPDATE (lines 686-693):
- Validate if frontend_ids explicitly provided
- HTTP 400 if trying to clear all frontends
- Allow config-only updates (preserve frontends)
Solution - Part 2: Frontend Validation
frontend.py CREATE (lines 408-428):
- Validate backend has active servers
- HTTP 400 if backend has no servers
- Error: "Backend has no active servers. Add servers first."
frontend.py UPDATE (lines 650-670):
- Same validation when changing default_backend
- Prevent routing to DOWN backends
Solution - Part 3: Frontend UI (User Experience)
WAFManagement.js (lines 1473-1508):
BEFORE:
- Label: "Target Frontends"
- Tooltip: "Can be left empty for globally available WAF"
- Placeholder: "Select frontends"
- No validation
- Optional field appearance
AFTER:
- Label: "Target Frontends" (with red asterisk)
- Required validation rules:
* Antd required: true
* Custom validator: at least 1 frontend
- Placeholder: "Select frontends (Required *)"
- Tooltip: "At least one frontend is required"
- Search enabled for easy filtering
- Error messages:
* "Please select at least one frontend"
* "At least one frontend must be selected for WAF rule"
User Experience Improvements:
1. Visual indication: Red asterisk on label
2. Clear placeholder text: "(Required *)"
3. Helpful tooltip: Explains requirement
4. Client-side validation: Immediate feedback
5. Server-side validation: Safety net
6. Searchable dropdown: Easy to find frontends
7. Clear error messages: User knows what to do
Test Scenarios:
1. Create WAF without selecting frontend:
- UI: Red error "Please select at least one frontend"
- Submit blocked (client-side)
2. Bypass client-side, try API:
- API: HTTP 400 "At least one frontend must be selected"
3. Create frontend with server-less backend:
- UI: Can select backend
- API: HTTP 400 "Backend has no active servers"
4. Update WAF remove all frontends:
- UI: Red error message
- API: HTTP 400 if bypassed
Related: bcb8ef0 (backend without servers)
Refs: #waf-validation #frontend-validation #ux-improvement
CRITICAL DEBUG: Track down why backend stays in pending after Apply/Reject
Problem:
- User reports backend 'deneme-sil' remains in Apply Management
- Apply All and Reject All both fail to remove it
- Console shows 'Filtered pending backends: 1' but backend not visible
Debug Logs Added:
1. fetchPendingChanges():
- Log ALL backends from API (not just 3 specific ones)
- Show: id, name, cluster_id, last_config_status, has_pending_config, is_active
- Log ALL pending backends after filtering
2. executeApplyAll():
- Log pending changes state before apply
- Log backend details being applied
- Log apply API response
- Log data refresh events
3. executeRejectAll():
- Log pending changes state before reject
- Log backend details being rejected
- Log reject API response
- Log data refresh events
Expected Output:
- [ALL BACKENDS FROM API]: Shows all 4 backends including hidden one
- [PENDING BACKENDS DETAILS]: Shows which backend has has_pending_config=true
- [Backend Details Being Applied/Rejected]: Shows backend state during operation
- [APPLY/REJECT RESPONSE]: Shows API response
- [DATA REFRESHED]: Confirms data reload completed
This will help identify:
- Is backend in API response? (hidden or missing)
- What is backend's actual state? (last_config_status, has_pending_config, is_active)
- Does Apply/Reject API call succeed?
- Does backend state change after apply/reject?
Refs: #debug #apply-management #pending-backend