Commit Graph

79 Commits

Author SHA1 Message Date
taylanbakircioglu deb784bb65 fix: ACME certificate issuance improvements, null-safe hardening, guided setup UX, and order list enhancements (Issue #9)
- 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
2026-04-04 15:15:05 +03:00
taylanbakircioglu c2766eadbd feat: dark mode support, UI improvements, and version bump to v1.2.0
- 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
2026-04-03 01:16:05 +03:00
taylanbakircioglu 2fc2603d1a fix: ACME account removal, status display, and timeline icon clipping
- Add permanent delete endpoint for deactivated ACME accounts (DELETE /accounts/{id}/permanent)
- Add "Remove from database" button in ACME Accounts modal for deactivated accounts
- Fix ACME Account stat card showing "Active" for deactivated accounts
- Fix Timeline dot/icon clipping in Applied/Rejected/Pending sections

Made-with: Cursor
2026-04-02 00:36:56 +03:00
taylanbakircioglu 93d7ad8fdb feat: add ACME Auto SSL with Let's Encrypt integration (v1.1.0)
Add automated SSL certificate management via ACME protocol (RFC 8555):
- Full ACME client implementation (account registration, HTTP-01 challenges, certificate issuance/renewal)
- Configurable ACME providers (Let's Encrypt, ZeroSSL, custom CA) via Settings UI
- Auto-renewal scheduler with PENDING -> Apply -> APPLIED flow alignment
- ACME account management (register, deactivate) from UI
- Certificate request wizard with domain validation and cluster targeting
- Zero changes to HAProxy agent scripts - challenges routed through existing architecture
- Comprehensive security hardening (no private key exposure in API responses)
- Full backward compatibility with existing SSL, Apply, Rollback, and Restore workflows
- Updated README, API documentation, and Kubernetes deployment notes
- Version management embedded in code (v1.1.0)
- UI messaging improvements for agent-pull architecture accuracy

Made-with: Cursor
2026-04-02 00:25:50 +03:00
taylanbakircioglu 77bd3ecead fix: add missing REJECTED and DELETION values to config_status enum
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
2026-04-01 13:18:24 +03:00
taylanbakircioglu 922bc2ce6d improve: SSL certificate listing reliability, error visibility, and ARM64 support
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
2026-03-30 10:42:28 +03:00
taylanbakircioglu 5796d3d1fd feat: rebrand to HAProxy OpenManager, remove demo accounts from login
- Replace login icon with ClusterOutlined, title "HAProxy OpenManager"
- Add subtitle "Multi-Cluster Load Balancer Management" on login page
- Remove demo account list from login page
- Update sidebar logo: emoji → ClusterOutlined icon
- Update header and browser tab title
- Clean up unused demo-account CSS styles

Made-with: Cursor
2026-03-19 13:12:24 +03:00
taylanbakircioglu bb6cb58db3 fix: auto-correct redirect target when switching type to scheme
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
2026-03-19 12:07:56 +03:00
taylanbakircioglu 7105213ba5 feat: add UI-level validation to prevent invalid HAProxy ACL configurations
- 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
2026-03-19 12:02:07 +03:00
taylanbakircioglu 4780f0c4d6 feat: add visual ACL Rule Builder for frontend management
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
2026-03-18 18:45:35 +03:00
taylanbakircioglu d4b58d62c7 fix: upgrade react-scripts to 5.0.1 for frontend Docker build compatibility
- react-scripts 4.0.3 -> 5.0.1 (Webpack 5, supports modern JS in node_modules)
- @monaco-editor/react 3.7.5 -> ^4.6.0 (resolves peer dep conflict with monaco-editor)
- Removed --legacy-peer-deps from Dockerfile (no longer needed, was causing ajv resolution issues)
- Added @babel/plugin-proposal-private-property-in-object as explicit devDependency
- Removed overrides section (not needed with react-scripts 5)

Made-with: Cursor
2026-03-16 13:25:03 +03:00
taylanbakircioglu 68a4d1ac23 fix: resolve Babel version conflict in frontend Docker build
Add npm overrides to pin @babel/core and @babel/plugin-proposal-private-property-in-object
to versions compatible with react-scripts 4.0.3. Fixes build failure caused by transitive
dependency resolution pulling incompatible Babel 7.12.3.

Made-with: Cursor
2026-03-16 12:56:37 +03:00
taylanbakircioglu efb466e6af ui: softer cluster selector highlight without container border
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>
2026-02-18 20:41:17 +03:00
taylanbakircioglu 16c502dc17 ui: improve backend servers column and cluster selector visibility
- 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>
2026-02-18 20:41:17 +03:00
taylanbakircioglu f526c3b6b6 feat: add IP Inventory page for cross-cluster IP search
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>
2026-02-18 20:41:17 +03:00
taylanbakircioglu 833c551929 ui: compact Clusters table layout to avoid horizontal scroll
- 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>
2026-02-18 20:41:17 +03:00
taylanbakircioglu 9ddc495e4f fix: keepalive UI edge cases and dashboard auto-refresh
- 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>
2026-02-18 20:41:17 +03:00
taylanbakircioglu 4082152312 fix: resolve stale keepalive state when keepalived stops
- 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>
2026-02-18 20:41:17 +03:00
taylanbakircioglu b0feca3c2e feat: add keepalived VRRP state (MASTER/BACKUP) detection to agent heartbeat
- 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>
2026-02-18 20:41:17 +03:00
taylanbakircioglu eabbb2a987 fix: SSL page header layout - prevent Add Certificate button wrapping
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 16:36:27 +03:00
taylanbakircioglu 0b40d7b8a2 refactor: replace Expiring Soon alert banner with hover tooltip
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 16:36:27 +03:00
taylanbakircioglu 1f100d01e7 fix: SSL reject rollback, expiry calculation, and UI improvements
- SSL Reject: Remove conditional has_same_update_applied check, always
  rollback since Auto-Reject handles cross-cluster consistency
- SSL Rollback: Restore expiry_date by parsing ISO string back to datetime
- SSL UI: Add In Use filter toggle, fix Expiring Soon count using actual
  date calculation, smart Private Key content validation messages
- Remove emojis from log messages

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 16:36:27 +03:00
taylanbakircioglu 6c8d771c3a fix: restrict INCOMPLETE status to SSL only, remove emojis from code
- 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>
2026-02-17 16:36:27 +03:00
taylanbakircioglu 7dedd0f573 feat: SSL sync status tracking and smart reload optimization
- 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>
2026-02-17 16:36:27 +03:00
taylanbakircioglu 9b8016588c feat: show SSL certificate usage status (In Use / Not In Use)
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>
2026-02-17 16:36:27 +03:00
taylanbakircioglu 45d5919dc2 fix: resolve stale closure in agent actions after cluster change
- 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>
2026-02-16 20:37:49 +03:00
taylanbakircioglu 4002c89d58 fix: agent list not loading on cluster change due to throttle race condition
- 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>
2026-02-16 20:37:49 +03:00
taylanbakircioglu e6142a9a9c feat: add search capability to cluster selector dropdown
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>
2026-02-16 16:49:59 +03:00
taylanbakircioglu 4c7a6d79f3 fix: apply sync for empty config, UI responsiveness improvements
- 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>
2026-02-16 16:45:29 +03:00
taylanbakircioglu 6349f1ad7c fix: exclude disabled (OFF) agents from sync calculations
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>
2026-02-16 16:45:28 +03:00
taylanbakircioglu 946b1a8e84 fix: Default Backend select filterOption crash on search
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"
2026-01-28 16:04:42 +03:00
taylanbakircioglu 8076f3fcfd feat: add intelligent HAProxy validation error display in UI
- 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.
2026-01-26 15:25:15 +03:00
taylanbakircioglu 0c1d68eb01 feat: add uninstall script UI with modern design
- 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
2026-01-26 15:25:15 +03:00
taylanbakircioglu 5d054f3426 feat: Add random and first balance methods support
- 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
2026-01-26 15:25:15 +03:00
taylanbakircioglu bc8563d455 fix: Update agent token association on config change and improve Security UI
- 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
2026-01-26 15:25:15 +03:00
taylanbakircioglu 86c7b3a57f feat: Display HAProxy versions with agent names in Cluster Management
- 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
2025-12-26 15:26:46 +03:00
taylanbakircioglu 81d2b3b8d5 feat: Display HAProxy version in Agent Management page
- 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.
2025-12-23 13:28:29 +03:00
Taylan Bakırcıoğlu ac5ba4dc58 fix(ssl): add SSL advanced options to GET /api/frontends SELECT queries
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
2025-11-18 21:58:05 +03:00
Taylan Bakırcıoğlu cb645b5ef9 CRITICAL FIX: Agent Offline Issue - Backend Tolerance for Legacy Agents
🔴 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.
2025-11-18 21:58:05 +03:00
taylanbakircioglu e0fb7180ae fix: Agent heartbeat cluster-pool auto-healing + global token support
MAIN BUG FIX:
- Agent offline issue resolved (cluster created before pool scenario)
- 2-method cluster lookup: pool_id -> cluster_id fallback
- Auto-healing: pool_id NULL automatically corrected on first heartbeat

SECURITY & VALIDATION:
- Removed pool-based security check (token is globally usable)
- Pool-cluster validation for new agents (frontend + backend)
- Relaxed validation for agent upgrades (fallback pool_id tolerated)

AGENT IMPROVEMENTS:
- HTTP error logging in agent scripts (curl status code check)
- Detailed backend error response logging
- Better troubleshooting capabilities

PRODUCTION SAFE:
- Backward compatible (no breaking changes)
- Existing agents unaffected (Method 1 priority)
- Agent upgrades work (relaxed validation)
- Global token model preserved (cross-pool usage OK)
2025-11-17 20:20:09 +03:00
Taylan Bakırcıoğlu c75b5572d9 fix: Change validation strategy - soft warnings instead of hard blocks
PHILOSOPHY: Don't block what HAProxy allows - guide users instead

Changes:
1. Frontend → Backend (server-less): REMOVED validation, ADDED warning
2. WAF → Frontend: KEPT validation (risk of global apply)

Problem with Previous Approach:
- Backend validation too strict
- Blocked valid HAProxy configs
- Bulk import would fail
- Users confused: "Why can't I do this?"

HAProxy Validation Standards:
- Frontend → Backend without servers: VALID (backend DOWN, 503 errors)
- Frontend without default_backend: VALID (ACL-only routing)
- WAF without frontend: INVALID (applies to ALL frontends - dangerous!)

Solution Part 1: Frontend Validation REMOVED

frontend.py CREATE + UPDATE:
- REMOVED: Backend server check validation
- Reason: HAProxy allows this (valid syntax)
- User can: Create frontend, assign backend, add servers later
- Production: Traffic gets 503 until servers added (acceptable)

frontend.py Changes:
- Lines 408-428: REMOVED backend server validation (CREATE)
- Lines 650-670: REMOVED backend server validation (UPDATE)
- Result: No API blocking, user has full control

Solution Part 2: Frontend UI Warning ADDED

FrontendManagement.js (lines 1402-1443):
BEFORE:
- Backend dropdown: Shows server count
- No warning when selecting server-less backend
- User unaware of consequences

AFTER:
- Tooltip: "Optional: Leave empty for ACL-only routing"
- Placeholder: "Select default backend (optional)"
- onChange handler: Checks server count
- Warning message if no servers:
  "Warning: Backend 'X' has no active servers. Traffic will receive 503 errors until you add servers."
- Visual indicator: Red text for backends without servers
- Display: "backend-name (0 servers) - No servers"

Solution Part 3: WAF Validation KEPT

waf.py CREATE (lines 418-426):
- KEPT: Frontend selection required
- Reason: WAF without frontend = applies to ALL frontends
- Risk: Unintentional global WAF application
- Too dangerous to allow without explicit user action

waf.py UPDATE (lines 676-683):
- KEPT: Cannot remove all frontend assignments
- Reason: Same risk as CREATE

User Experience:
FRONTEND:
- Before: HTTP 400 error, blocked
- After: Warning message, allowed
- Message: Clear consequences, user decides

WAF:
- Before: HTTP 400 error, blocked
- After: Same (still blocked - too risky)
- Reason: Global apply risk too high

Bulk Import Impact:
- Before: Would FAIL on frontends with server-less backends
- After: SUCCESS - no validation blocking
- Result: Bulk import works smoothly

Test Scenarios:
1. Create frontend with server-less backend:
   - API: SUCCESS (no validation)
   - UI: WARNING shown (6 seconds)
   - User: Can proceed with awareness

2. Select backend without servers:
   - Dropdown: Shows "backend (0 servers) - No servers" in RED
   - OnChange: Warning message appears
   - Submit: Allowed

3. Create WAF without frontend:
   - API: HTTP 400 (blocked)
   - UI: Required field validation
   - Reason: Too risky to allow

4. Bulk import with server-less backends:
   - API: SUCCESS (no blocking)
   - Frontends created successfully
   - Users can add servers later

Philosophy:
- HAProxy validation = source of truth
- Backend validation = guidance, not blocking
- Dangerous operations = strict validation (WAF global apply)
- User empowerment = soft warnings with clear info

Related: bcb8ef0 (backend without servers), 595d981 (WAF validation)
Refs: #validation-strategy #soft-warnings #bulk-import #ux
2025-11-17 14:15:44 +03:00
Taylan Bakırcıoğlu 7d3eeebcb7 fix(waf): Require frontend selection with backend + frontend validation
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
2025-11-17 14:15:44 +03:00
Taylan Bakırcıoğlu 60b77734a5 debug(frontend): Add comprehensive debug logs for Apply/Reject operations
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
2025-11-17 14:15:44 +03:00
Taylan Bakırcıoğlu 8131b0b18c fix(frontend): Handle null/undefined servers array in BackendServers component
CRITICAL FIX: Backends without servers were not visible in UI due to missing null/undefined checks

Problem:
- Backend tanımı yaparken server eklenmezse, frontend'de görünmüyordu
- servers.filter() ve servers.length çağrıları undefined/null servers array'inde crash veriyordu
- Bu crash nedeniyle backend satırı render edilmiyordu

Solution:
- Servers kolonu render: servers = [] default parameter + null/Array.isArray kontrolü
- Expandable row render: const servers = record.servers || [] defensive check
- renderServerList: flatMap içinde servers || [] kontrolü
- Boş backend'ler için ⚠️ Empty tag ve tooltip eklendi

Impact Analysis:
 Sync Status: ETKİLENMEDİ (EntitySyncStatus props'ları servers'a bağımlı değil)
 Config Status: ETKİLENMEDİ (has_pending_config backend'de hesaplanıyor, servers'a bağımsız)
 Tüm diğer kolonlar: ETKİLENMEDİ (servers field'ına bağımlı değil)
 Backend API: DEĞİŞMEDİ (servers her zaman array döndürüyor)

Testing:
- Backend API'den servers her zaman array geliyor (boş olabilir)
- Defensive programming ile cache/race condition/parse error durumlarına karşı korundu
- UI artık boş backend'leri 0/0 active ve ⚠️ Empty tag ile gösterecek

Refs: #backend-visibility-bug #defensive-programming
2025-11-17 14:14:26 +03:00
taylanbakircioglu 8a24d22e5d debug: Add console logs for tcp_request_rules in frontend edit modal
Added debug logging to track tcp_request_rules value when frontend edit modal opens.
This will help diagnose why tcp_request_rules from bulk import are not appearing in the edit form.
2025-11-14 01:06:38 +03:00
taylanbakircioglu 1276725f45 fix: Frontend modal - preserve tcp_request_rules, redirect_rules and use-service on update
🐛 Bug Düzeltmeleri:

1. tcp_request_rules field'ı edit modal'da gösterilmiyordu
   - handleEdit() setFieldsValue'ya tcp_request_rules eklendi
   - Artık DB'deki TCP rules edit modal'da görünüyor

2. use-service header'ları (prometheus-exporter gibi) bulk import'ta siliniyordu
   - config.py bulk_create_entities'e merge stratejisi eklendi
   - Mevcut use-service direktifleri parse edilip yeni header'larla birleştiriliyor
   - Manuel eklenmiş servisler korunuyor

3. redirect_rules field'ı form'da hiç yoktu (yeni bulgu!)
   - ACL Rules panel'ına redirect_rules textarea eklendi
   - HTTP to HTTPS redirect kuralları için kullanılabilir

 Diğer modaller kontrol edildi:
   - Backend modal: Tam ✓
   - Server modal: Tam ✓
   - SSL modal: Tam ✓
   - WAF modal: Tam ✓
2025-11-14 01:06:38 +03:00
taylanbakircioglu 01ae508d39 feat: Line-by-line diff for multi-line fields + rollback merge strategy
USER FEEDBACK SUMMARY:
1. "Old/New görünümü karışık, sadece yeni eklenen görünsün"
2. "Merge logic silme işlemini engelliyor, geri al"

CHANGES OVERVIEW:
 Backend: Rolled back merge strategy (prevents deletion issue)
 Frontend: Added line-by-line diff renderer (cleaner UX)

═══════════════════════════════════════════════════════════

PART 1: BACKEND ROLLBACK (config.py)
═══════════════════════════════════════════════════════════

REMOVED: merge_multiline_field() function
REASON: Merge strategy was additive-only, prevented deletion

OLD BEHAVIOR (MERGE):
DB:     "option http-keep-alive"
Config: "option forwardfor"
Result: "option http-keep-alive\noption forwardfor"  ← BOTH kept!
Issue:  User CANNOT delete http-keep-alive 

NEW BEHAVIOR (REPLACE):
DB:     "option http-keep-alive"
Config: "option forwardfor"
Result: "option forwardfor"  ← Old deleted! ✓
Works:  Deletion and addition both work ✓

ROLLBACK DETAILS:
- Removed merge_multiline_field() function (lines 30-71)
- Restored simple comparison for frontend fields:
  * request_headers: simple != comparison
  * response_headers: simple != comparison
  * options: simple != comparison
  * tcp_request_rules: simple != comparison

- Restored simple comparison for backend fields:
  * request_headers: simple != comparison
  * response_headers: simple != comparison
  * options: simple != comparison

USER CONFIRMATION:
"aslında bu senaryoyu denedim. entitiy'de option http-keep-alive
varken option forwardfor'ı da ekledim. Confirm & Create Entitiy
dedikten sonra oluşan versiyon sadece yeni eklenen option forwardfor'u
dahil edecek şekilde oluştu. Yani sağlıklı çalıştı ve önceki option'u
kaldırmadı."

ANALYSIS: User's config ALREADY HAD both options!
Parser extracted: "option http-keep-alive\noption forwardfor"
No data loss occurred because BOTH were in the imported config.

═══════════════════════════════════════════════════════════

PART 2: FRONTEND LINE-BY-LINE DIFF (BulkConfigImport.js)
═══════════════════════════════════════════════════════════

NEW FEATURE: Smart line-by-line diff rendering

PROBLEM (User's Example):
DB:     "option http-keep-alive"
Config: "option http-keep-alive\noption forwardfor"

Old Preview (Confusing):
  Old: option http-keep-alive
  New: option http-keep-alive
       option forwardfor
  → User can't see what changed! 

New Preview (Clear):
  Added:
  + option forwardfor
  → Only changes shown! ✓

IMPLEMENTATION:

1. calculateLineDiff() Helper Function:
   - Splits old/new into lines
   - Trims whitespace and filters empty lines
   - Uses Set comparison for efficient diff
   - Returns: { added: [], removed: [], unchanged: [] }

   Algorithm:
   oldSet = Set(oldLines)
   newSet = Set(newLines)
   added = newLines.filter(line => !oldSet.has(line))
   removed = oldLines.filter(line => !newSet.has(line))

2. MultiLineDiffRenderer Component:
   - No changes: Shows plain text
   - Has changes: Shows diff boxes
   - Removed lines: Red box with - prefix
   - Added lines: Green box with + prefix
   - Unchanged lines: HIDDEN (per user request!)

APPLIED TO 7 FIELDS:

Frontend Entity (4 fields):
✓ frontend.options               (line 1097)
✓ frontend.request_headers        (line 1116)
✓ frontend.response_headers       (line 1135)
✓ frontend.tcp_request_rules      (line 1154)

Backend Entity (3 fields):
✓ backend.options                 (line 1228)
✓ backend.request_headers         (line 1248)
✓ backend.response_headers        (line 1268)

EDGE CASES HANDLED:
✓ Both null: No crash, returns empty arrays
✓ Old null: Shows all as added (green)
✓ New null: Shows all as removed (red)
✓ Empty lines: Filtered out before comparison
✓ Whitespace: Trimmed before comparison
✓ Duplicates: Set ensures unique comparison

VISUAL DESIGN:
- Removed box: #fff1f0 bg, #ff4d4f border, red text
- Added box: #f6ffed bg, #52c41a border, green text
- Spacing: 8px margin between boxes
- Font: 11px code font for readability
- Labels: 10px secondary text ("Removed:", "Added:")

USER FEEDBACK:
"neden old gösteriyor ki aslında sadece New: option forwardfor
gösterse daha doğru olmaz mı?"

RESPONSE: Absolutely! Removed unchanged section completely.

═══════════════════════════════════════════════════════════

TEST SCENARIOS:
═══════════════════════════════════════════════════════════

Scenario 1: Addition Only
Old: "option http-keep-alive"
New: "option http-keep-alive\noption forwardfor"
Result: Shows "+ option forwardfor" (green) ✓

Scenario 2: Deletion Only
Old: "option http-keep-alive\noption forwardfor"
New: "option http-keep-alive"
Result: Shows "- option forwardfor" (red) ✓

Scenario 3: Replacement
Old: "option http-keep-alive"
New: "option forwardfor"
Result: Shows "- option http-keep-alive" (red)
        and "+ option forwardfor" (green) ✓

Scenario 4: No Change
Old: "option http-keep-alive"
New: "option http-keep-alive"
Result: Plain text, no diff boxes ✓

Scenario 5: Empty Lines & Whitespace
Old: "  option http-keep-alive  \n\noption forwardfor"
New: "option http-keep-alive\noption forwardfor"
Result: No diff (trimmed and filtered) ✓

═══════════════════════════════════════════════════════════

SYSTEMATIC VERIFICATION COMPLETED:
═══════════════════════════════════════════════════════════

Backend Analysis:
✓ merge_multiline_field removed
✓ No merge logic in frontend comparison
✓ No merge logic in backend comparison
✓ Simple != comparison restored
✓ Deletion works correctly

Frontend Analysis:
✓ calculateLineDiff helper correct
✓ MultiLineDiffRenderer renders correctly
✓ Unchanged section removed
✓ 7 fields updated (3 backend + 4 frontend)
✓ Props passed correctly
✓ Edge cases handled
✓ No debug code left
✓ Badge colors consistent

No Side Effects:
✓ Backend edit modal: Still works
✓ Version diff: No impact
✓ Apply management: No impact
✓ Bulk create endpoint: No changes
✓ Other fields: Unaffected

BACKWARD COMPATIBILITY: ✓
PERFORMANCE: No performance impact (Set operations O(n))
CODE QUALITY: Clean, DRY, maintainable
2025-11-13 10:12:30 +03:00
taylanbakircioglu 82a88ee244 fix: Apply inline rendering to frontend fields (same empty string issue)
USER QUESTION: "backend içinde aynı durum olabilir mi?"
ANSWER: Yes! Frontend entity has the same issue.

ISSUE SCOPE EXPANDED:
The empty string oldValue problem affects ALL multi-line text fields:
- Backend entity: ✓ Already fixed (options, request_headers, response_headers)
- Frontend entity:  Still using FieldChange component (same bug!)

FRONTEND FIELDS AFFECTED:
- Frontend Options (line 984)
- Request Headers (line 1001)
- Response Headers (line 1018)
- TCP Request Rules (line 1035)

All use FieldChange component → All fail with oldValue = '' (empty string)

SOLUTION: Replace FieldChange with inline rendering for frontend

Applied same pattern as backend:
1. Frontend Options:
   - Inline diff rendering
   - Badge: NEW/CHANGED based on old value
   - Old box: Only if old has content (skips empty string)
   - New box: Always shown with green styling

2. Request Headers, Response Headers, TCP Request Rules:
   - Same inline pattern
   - Badge: CHANGED (orange)
   - Graceful empty string handling

CONSISTENCY:
✓ Backend fields: inline rendering
✓ Frontend fields: inline rendering
✓ All text fields handle empty string correctly
✓ No more FieldChange component issues

EDGE CASE HANDLING:
- oldValue = '' → Old box not shown, NEW badge
- oldValue = null → Old box not shown, NEW badge
- oldValue = 'content' → Old box shown, CHANGED badge

RESULT: Complete fix across both backend and frontend entities
2025-11-13 10:12:30 +03:00
taylanbakircioglu 969259b7de debug: Add systematic debugging for empty Backend Options field
ISSUE: Backend Options field appears EMPTY even though:
- ✓ Confirm & Create generates correct version with option
- ✓ Cookie Options field works (same component type)
- ✓ Request Headers field works (same ternary pattern)
-  Backend Options field: Label visible, content empty

SYSTEMATIC ROOT CAUSE ANALYSIS:

Checkpoint 1: Parser adds options field ✓
- Line 906 in config.py: 'options': backend.options
- Parser correctly extracts 'option http-keep-alive' from config

Checkpoint 2: _changes object created ✓
- Line 1125-1127: Compares with existing backend
- Line 1138: backend['_changes'] = changes
- If options changed: changes['options'] = {old: None, new: 'option...'}

Checkpoint 3: API response includes _changes ✓
- Line 1189: return {'backends': backends_data}
- backends_data contains backend dicts with _changes key

Checkpoint 4: Frontend render logic
- HYPOTHESIS: backend.options exists but falsy?
- HYPOTHESIS: _changes object not reaching frontend?
- HYPOTHESIS: Ternary operator short-circuit issue?

SIMULATION:
Scenario A (_changes exists):
  backend._changes.options = {old: null, new: 'option...'}
  → backend._changes?.options → truthy
  → FieldChange renders
  → hasOldValue = true, hasChange = true
  → Should display with NEW badge ✓

Scenario B (_changes missing):
  backend._changes = undefined
  → backend._changes?.options → undefined (falsy)
  → Descriptions.Item with NEW badge renders
  → Should display green text ✓

Both scenarios SHOULD work!

SOLUTION: Detailed Debug Logging + Robust Fallback

Changed from ternary to IIFE (Immediately Invoked Function Expression):
1. console.group() for organized logging
2. Logs: field value, type, length, _changes object, render decision
3. Clear if-else (no ternary ambiguity)
4. Early return if no options field
5. Explicit FieldChange vs Descriptions.Item branches

NEXT STEP: Test and inspect browser console
- Check: Is backend.options present? What's its value/type?
- Check: Is backend._changes present? What's its structure?
- Check: Which branch is rendered?
- Result: Pinpoint exact cause (data or rendering)

RATIONALE FOR THIS APPROACH:
- Avoids trial-and-error (user's concern)
- Provides observable data points
- Guarantees one of two render paths executes
- Maintains fallback for both _changes presence/absence scenarios
2025-11-13 10:12:30 +03:00
taylanbakircioglu d07388dc97 fix: Fallback rendering for fields without change tracking + TODO for reject bug
PRIMARY FIX: Empty Backend Options / Request Headers display issue

ISSUE:
- Backend Options field: Empty (even though data exists)
- Request Headers field: Empty (even though data exists)
- Cookie Options field: Works  (using old Descriptions.Item)

ROOT CAUSE:
FieldChange component requires _changes object to render.
If _changes is undefined/null, component shows nothing.

SOLUTION: Dual render pattern
1. IF _changes exists → Show FieldChange (with diff visualization)
2. IF _changes missing → Show plain Descriptions.Item (fallback)

Pattern:
{field && entity._changes?.field && (
  <FieldChange ... />  // Show diff
)}
{field && !entity._changes?.field && (
  <Descriptions.Item ... />  // Fallback display
)}

Applied to:
- Backend: options, request_headers, response_headers
- Frontend: options, request_headers, response_headers, tcp_request_rules

RESULT:
 Fields always visible (even without change tracking)
 Diff shown when _changes available
 Graceful degradation when _changes missing
2025-11-13 10:12:30 +03:00