Add frontend infrastructure for displaying baseline anomalies:
- useAnomalies hook for fetching and caching anomaly data
- AnomalyCell component for displaying multiple anomalies
- AnomalyIndicator/AnomalyBadge components for inline display
- Update EnhancedCPUBar to accept optional anomaly prop
The anomaly endpoint is polled every 30 seconds and cached.
Anomaly badges show severity (color) and deviation ratio (e.g., '2.5x').
This prepares the UI for displaying real-time baseline deviations
without requiring LLM interaction.
Add /api/ai/intelligence/anomalies endpoint that compares live metrics
against learned baselines to surface deviations - all deterministic
(no LLM required).
Backend:
- Add AnomalyReport struct with severity classification
- Add CheckResourceAnomalies method to baseline store
- Add HandleGetAnomalies API handler
- Add GetStateProvider getter to AI service
Frontend:
- Add AnomalyReport and AnomaliesResponse types
- Add getAnomalies API function
- Add AnomalySeverity type
This is the first step toward surfacing deterministic intelligence
directly in the UI without requiring LLM interaction.
- Create Intelligence struct that aggregates all AI subsystems
- Add /api/ai/intelligence endpoint for system-wide and per-resource insights
- Wire Intelligence into PatrolService as a facade (not replacement)
- Add TypeScript types and API client for frontend
- Add unit tests for Intelligence orchestrator
- Fix pre-existing test failures using diagnostic commands instead of actionable ones
The Intelligence orchestrator provides:
- System-wide health scoring (A-F grades)
- Aggregated findings, predictions, correlations
- Per-resource context generation for AI prompts
- Learning progress tracking
This unifies access to AI subsystems without replacing existing code paths.
- KubernetesClusters.tsx: Escape -> as → in JSX text to fix parsing error
- Settings.tsx: Remove unused HostProxySummary interface (deprecated in v5)
- AIOverviewTable.tsx: Prefix unused summarizeAction with underscore
The pulse-sensor-proxy feature was deprecated in v5 and disabled by default.
The frontend was still calling /api/temperature-proxy/host-status which
returned 410 Gone, causing console errors.
Removed:
- HostProxyStatusResponse interface
- _hostProxyStatus signal (was never read)
- refreshHostProxyStatus function
- Polling interval that called the deprecated endpoint
The temperature monitoring now uses pulse-agent instead.
Backend:
- Enhanced buildEnrichedResourceContext to ALWAYS show learned baselines with
status indicators (normal/elevated/anomaly) instead of only when anomalous
- This makes Pulse Pro's 'moat' visible - users can see the AI understands
their infrastructure's normal behavior patterns
- Added baseline import to service.go
Frontend (user changes):
- Added incident event type filtering with toggle buttons
- Added resource incident panel to view all incidents for a resource
- Added timeline expand/collapse functionality in alert history
- Added incident note saving with proper incidentId tracking
- Added startedAt parameter for proper incident timeline loading
Multiple frontend components were using - as a fallback
when guest.id was falsy. This format drops the node component, which is
critical for clustered setups where the same VMID can exist on different
nodes.
Changes:
- GuestDrawer.tsx: Updated guestId() and handleAskAI() to use canonical format
- GuestRow.tsx: Updated buildGuestId() to use canonical format
- Dashboard.tsx: Updated handleGuestRowClick() and guest rendering loop,
also fixed legacy metadata fallback to use consistent keying
- ThresholdsTable.tsx: Updated guestsGroupedByNode() to use canonical format
Backend changes:
- Removed temporary debug logging added during investigation
- Added alert history section to AI buildEnrichedResourceContext() function
The backend generates VM/Container IDs in instance:node:vmid format (e.g.,
delly:delly:101) via makeGuestID(). This format is now consistently used
across all frontend fallbacks to prevent AI context, metadata, overrides,
and metrics from colliding or desyncing in clustered environments.
- Fixed normalizeStorageDefaults to allow Trigger=0
- Fixed normalizeNodeDefaults (Temperature) to allow Trigger=0
- Added comprehensive tests for all threshold normalization patterns
- Updated existing test that expected old behavior
Related to #864
- Login.tsx: Use apiClient.fetch with skipAuth to avoid auth loops
- router.go: Skip CSRF validation for /api/login endpoint
- hot-dev.sh: Detect encrypted files before generating new key to prevent data loss
Header and action buttons now stack vertically on narrow screens
instead of overflowing. Button labels are shortened on mobile.
Related to discussion #845 (feedback from @MDE186)
When the user logged out, the code would immediately set needsAuth=true
and return WITHOUT first fetching /api/security/status. This meant the
securityStatus signal was null, causing shouldShowLocalLogin() in Login.tsx
to return true (since !undefined === true).
Now we always fetch security status before showing the login form, even
in the just_logged_out path. This ensures hideLocalLogin, oidcEnabled,
and other OIDC settings are properly available to the Login component.
- Add HandleLicenseFeatures handler that was missing from license_handlers.go
- Add /api/license/features route to router
- Update AI service and metadata provider
- Update frontend license API and components
- Fix CI build failure caused by tests referencing unimplemented method
The 'Removed Docker Hosts' section was not appearing in Settings -> Agents
even when hosts were blocked from re-enrolling. This prevented users from
using the 'Allow re-enroll' button to unblock their Docker agents.
Root cause: The WebSocket store was missing:
1. The 'removedDockerHosts' property in its initial state
2. A handler to process removedDockerHosts data from WebSocket messages
This meant the backend was correctly sending the data, but the frontend
was completely ignoring it.
Changes:
- Add removedDockerHosts to WebSocket store initial state and message handler
- Add removedDockerHosts to App.tsx fallback state for consistency
- Add missing BroadcastState call after AllowDockerHostReenroll succeeds
Also includes previous fixes from this session:
- Add PULSE_AGENT_URL as alias for PULSE_AGENT_CONNECT_URL (config.go)
- Add runtime Docker/Podman auto-detection in pulse-agent (main.go)
Fixes issue reported by darthrater78 in discussion #845
- Create reusable UrlEditPopover component with fixed positioning
- Add createUrlEditState hook for managing editing state
- Update DockerHostSummaryTable to use new popover
- Update DockerUnifiedTable (containers & services) to use new popover
- Update GuestRow (Proxmox VMs/containers) to use new popover
- Update HostsOverview (Proxmox hosts) to use new popover
- Add Docker host metadata API for custom URLs
- Consistent styling with save, delete, cancel buttons and keyboard shortcuts
Previously, each DockerContainerRow component made 2 API calls on mount:
- AIAPI.getSettings() for AI enabled status
- DockerMetadataAPI.getMetadata() for annotations
With 100+ containers, this resulted in 200+ API calls firing simultaneously,
exceeding the 500 requests/minute rate limit and causing 429 errors.
Fix:
- Lift AI settings check to DockerUnifiedTable parent component (1 call)
- Use pre-fetched dockerMetadata prop for annotations (already batch-fetched)
- Pass aiEnabled and initialNotes as props to child rows
This reduces API calls from O(n*2) to O(1) when loading the Docker overview.
Fixes#859
Adds IncludeAllDeployments option to show all deployments, not just
problem ones (where replicas don't match desired). This provides parity
with the existing --kube-include-all-pods flag.
- Add IncludeAllDeployments to kubernetesagent.Config
- Add --kube-include-all-deployments flag and PULSE_KUBE_INCLUDE_ALL_DEPLOYMENTS env var
- Update collectDeployments to respect the new flag
- Add test for IncludeAllDeployments functionality
- Update UNIFIED_AGENT.md documentation
Addresses feedback from PR #855
When 'Hide local login form' was enabled in Settings -> Authentication,
the local login form was still displayed instead of showing only the
SSO login. This regression occurred in Pulse 5.x.
Root cause: When App.tsx passed hasAuth to Login.tsx, the Login component
created a minimal SecurityStatus object with only hasAuthentication set,
missing the hideLocalLogin and other OIDC settings.
Changes:
- App.tsx: Store and pass full securityStatus to Login component
- Login.tsx: Accept securityStatus prop and initialize state from it
- Login.tsx: Initialize authStatus directly from props to respect
hideLocalLogin on first render
- Added tests for hideLocalLogin behavior
Fixes#857
- Remove unused type imports: SnapshotAlertConfig, PMGThresholdDefaults, RawOverrideConfig, BackupAlertConfig
- Remove unused imports: Settings, Power, JSX, onMount, createEffect
- Remove unused function _getUnitSuffix
- Fix GuestDefaults type to avoid index signature conflict
- Prefix unused catch variables with underscore
- Fix StickyNote title prop by wrapping in span element
- Add CollapsibleSection component with animated expand/collapse
- Wrap all 6 resource sections (Nodes, VMs, PBS, Storage, Backups, Snapshots) with accordion UI
- Add section icons and resource counts in headers
- Add expand all / collapse all buttons for quick navigation
- Make help banner dismissible with localStorage persistence
- Add Ctrl/Cmd+F keyboard shortcut to focus search
- Add keyboard shortcut hint badge on search input
- Add icons to tab navigation for quick identification
- Improve mobile tab labels with shorter text on small screens
- Create reusable components: ThresholdBadge, ResourceCard, GlobalDefaultsRow
- Create useCollapsedSections hook with localStorage persistence
- Default less-used sections (Storage, Backups, Snapshots, PBS) to collapsed
The issue was a SolidJS reactivity problem in the Dashboard component.
When guestMetadata signal was accessed inside a For loop callback and
assigned to a plain variable, SolidJS lost reactive tracking.
Changed from:
const metadata = guestMetadata()[guestId] || ...
customUrl={metadata?.customUrl}
To:
const getMetadata = () => guestMetadata()[guestId] || ...
customUrl={getMetadata()?.customUrl}
This ensures SolidJS properly tracks the signal dependency when the
getter function is called directly in JSX props.
The previous fix (3b4c77de) only addressed PVE/PBS nodes but missed
guest rows. Now VMs and LXC containers also show CPU details tooltip
on hover using EnhancedCPUBar.
Backend:
- Add smart provider fallback when selected model's provider isn't configured
- Automatically switch to a model from a configured provider instead of failing
- Log warning when fallback occurs for visibility
Frontend (AISettings.tsx):
- Add helper functions to check if model's provider is configured
- Group model dropdown: configured providers first, unconfigured marked with ⚠️
- Add inline warning when selecting model from unconfigured provider
- Validate on save that model's provider is configured (or being added)
- Warn before clearing last configured provider (would disable AI)
- Warn before clearing provider that current model uses
- Add patrol interval validation (must be 0 or >= 10 minutes)
- Show red border + inline error for invalid patrol intervals 1-9
- Update patrol interval hint: '(0=off, 10+ to enable)'
These changes prevent confusing '500 Internal Server Error' and
'AI is not enabled or configured' errors when model/provider mismatch.
- Remove confusing Force Docker, Force Kubernetes, Force Proxmox checkboxes
- Auto-detection handles these platforms; checkboxes were redundant
- Keep Skip TLS verification checkbox (commonly needed for self-signed certs)
- Add Troubleshooting section with --enable-* and --disable-* flags for edge cases
- Update tests to reflect simplified UI
Release improvements:
- Add standalone Linux agent archives (amd64, arm64, armv7, armv6, 386) to releases
- Previously only Darwin/Windows had standalone agent downloads
Agent version mismatch detection:
- New agentVersion.ts utility compares agent versions against server version
- DockerHostSummaryTable now shows accurate 'outdated' warnings when
agent version is older than the connected Pulse server
- Better tooltips explain version status and upgrade instructions
Add max-height and overflow-y-auto to the model selector dropdown in
the AI chat panel to prevent it from extending past the screen when
there are many models available (e.g., with Ollama).
Fixes feedback from discussion #845
Adds FreshHours and StaleHours settings to control when the dashboard
backup indicator shows green (fresh), amber (stale), or red (critical).
- Backend: Added FreshHours/StaleHours to BackupAlertConfig (default 24/72 hours)
- Frontend: getBackupInfo() now accepts optional thresholds parameter
- Dashboard/GuestRow components use thresholds from alert config
- Settings saved/loaded with alert configuration
Closes#839
- Replace verbose info banner with streamlined layout
- Add collapsible 'Advanced Model Selection' accordion for Chat/Patrol models
- Make AI Patrol Settings section collapsible with inline summary badges
- Compact Cost Controls into single-row inline layout
- Reduce form spacing for tighter presentation
- Remove unused formHelpText import
Also includes:
- OpenAI provider fixes for max_tokens parameters
- Security setup CSRF and 401 fixes
- Minor UI tweaks
The frontend was incorrectly assuming any 401/403 response from the
quick-setup endpoint meant the legacy DISABLE_AUTH flag was present.
This was wrong - the real causes are typically:
- Missing bootstrap token (for first-run setup)
- Invalid bootstrap token
- Authentication required for existing setups
Now the frontend properly displays the actual error message from the
backend, which includes helpful instructions for retrieving the
bootstrap token.
Backend fixes:
- Strip provider prefix (anthropic:, openai:, deepseek:, ollama:) in all
provider Chat methods and constructors for robust handling
- Models are now correctly parsed regardless of caller format
Frontend fixes:
- Tool cards now persist in AI chat after approval execution by adding
to streamEvents array
- Dashboard now listens for pulse:metadata-changed custom event
- AI Chat emits this event when set_resource_url tool completes
- Guest URL icons now update instantly when AI sets them
- Add setup modal that appears when enabling AI without configured provider
- Modal allows selecting provider (Anthropic, OpenAI, DeepSeek, Ollama)
- Enter API key/URL and enable AI in one smooth flow
- Reorder backend to apply API keys before enabled check
- Fix Ollama to strip 'ollama:' prefix from model names
- Simplify backend error message for unconfigured providers
The form pre-fills the Ollama URL field with http://localhost:11434 but
compared it against that same default value to detect changes. This meant
users configuring Ollama with the default URL never actually saved it.
Also shows actual backend error messages instead of generic "Failed to update"
when toggling the AI enable switch.
Related to #847
Fixes#828
Memory bars were always showing green regardless of usage percentage.
Added getMemoryColor() function that applies threshold-based coloring:
- Green: Below 70%
- Yellow/Orange: 70-90%
- Red: Above 90%
This matches the existing disk bar behavior and restores the expected
visual warning for high memory usage.
Fixes#825
Long Docker image names (like Immich postgres containers) were expanding
the entire table row, requiring horizontal scrolling to see CPU/Memory.
Changed the image cell to use:
- Inline style max-width (more reliable in table layouts)
- Block span with truncate class
- overflow-hidden on parent container
Images now truncate with ellipsis at 200px, with full name in tooltip.
Fixes#773
The type badge order was flapping between 'Host Docker' and 'Docker Host'
because Array.from(new Set([...])) doesn't preserve insertion order reliably.
Added a sortTypes helper that ensures types are always displayed in a
consistent order: 'host' before 'docker'. This prevents visual flapping
even when the underlying data sources update at slightly different times.
The Quick tips banner on the Alerts page said "Set any threshold to 0" but
the actual code uses -1 to disable alerts. The tooltips on individual fields
were correct; only the top-of-page banner was wrong.
Related to #843
- Install script now auto-detects Docker, Kubernetes, and Proxmox
- Platform monitoring is enabled automatically when detected
- Users can override with --disable-* or --enable-* flags
- Allow same token to register multiple hosts (one per hostname)
- Update tests to reflect new multi-host token behavior
- Improve CompleteStep and UnifiedAgents UI components
- Update UNIFIED_AGENT.md documentation
- Add cluster-aware guest ID generation (clusterName-VMID instead of instanceName-VMID)
to prevent duplicate VMs/containers when multiple cluster nodes are monitored
- Add cluster deduplication at registration time - when a node is added that belongs
to an already-configured cluster, merge as endpoint instead of creating duplicate
- Add startup consolidation to automatically merge duplicate cluster instances
- Change host agent token binding from agent GUID to hostname, allowing:
- Multiple host agents to share a token (each bound by hostname)
- Agent reinstalls on same host without token conflicts
- Remove 12-character password minimum requirement
- Remove emoji from auto-registration success message
- Fix grouped view node lookup to support both cluster-aware node IDs
(clusterName-nodeName) and legacy guest grouping keys (instance-nodeName)
Fixes duplicate guests appearing when agents are installed on multiple
cluster nodes. Also improves multi-agent UX by allowing shared tokens.
- NodeGroupHeader shows '+ Host Agent' badge when node has linked agent
- NodeSummaryTable shows '+Agent' badge in the node row
- Filter out linked hosts from Managed Agents list (prevents duplication)
This provides visual feedback when a PVE node has enhanced monitoring
via an installed host agent.
- Added linkedHostAgentId to Node interface
- Added linkedNodeId/linkedVmId/linkedContainerId to Host interface
- Filter out linked hosts from 'Managed Agents' list (they show merged with nodes)
Next: Update Dashboard to display linked entity badges
Users no longer need to manually click 'New Token' - a fresh token is
automatically generated after each copy. The command shown is always
ready for the next host.