When a unified agent reports both host and docker data, the WebSocket
handler was calling setState separately for dockerHosts and hosts.
This caused the UnifiedAgents component's allHosts memo to recalculate
twice - first seeing only the updated dockerHosts (showing "Docker"),
then seeing both (showing "Host & Docker").
Batch both updates into a single setState call using produce() when
both are present in the same message, ensuring the component always
sees consistent data.
Related to #778
- Create EnhancedStorageBar component to show storage usage with ZFS integration
- Animated overlay for active scrubbing/resilvering operations
- Red pulse border indicator for pools with errors
- Detailed tooltip showing ZFS state, scan status, and error counts
- Replace static progress bar in Storage.tsx with EnhancedStorageBar
- Remove unused getProgressBarColor function
- Create EnhancedCPUBar component to show CPU usage with Load Average overlay
- Add vertical marker indicating 1-minute Load Average relative to core count
- Purple glow indicator when Load > Cores (system overload)
- Detailed tooltip showing Usage, Load, and Capacity metrics
- Full sparkline mode support for historical view
- Integrate into NodeSummaryTable for Proxmox nodes
- Integrate into DockerHostSummaryTable for Docker hosts
- Integrate into HostsOverview for standalone hosts
- Add StackedMemoryBar component to visualize Active/Balloon/Swap memory
- Integrate StackedMemoryBar into NodeSummaryTable, GuestRow, HostsOverview, and DockerUnifiedTable
- Add TemperatureGauge component for temperature visualization
- Standardize replication job status indicators with StatusDot
- Fix mock data generator to use realistic balloon memory values (0 by default, small % when active)
- Add StackedContainerBar for Docker container status visualization
- Add ZFSHealthMap component for storage pool health visualization
- Replace standard disk metric bar with StackedDiskBar in HostsOverview
- Show detailed per-disk usage breakdown on hover
- Use Portal for tooltips to ensure correct positioning and z-index
- Add class prop support to MetricBar
Revert expanded width from w-64 (256px) to w-72 (288px) and min/max
from 16rem to 18rem. The reduction in commit 6333a445 caused PVE node
status icons to wrap to two lines.
Related to #764
Adds a "Removed Docker Hosts" section to Settings -> Agents that displays
hosts blocked from re-enrolling and provides an "Allow re-enroll" button.
The backend API already existed; this adds the missing frontend component.
Related to #773
Show individual disk usage segments as a stacked bar in the dashboard
table. Each segment represents one disk's used space proportional to
total capacity. Multi-disk systems display a count badge [N] and
hovering shows a tooltip with per-disk breakdown (name, used/total, %).
Single-disk and aggregate fallback still work as before.
Related to #489
- Add hideLocalLogin handler in HandleUpdateSystemSettings() so the
toggle setting is saved to system.json
- Conditionally hide "or" divider and admin credentials message when
local login is hidden
Related to #750
- Remove duplicate 'v' prefix in Docker agent version display - backend
normalizeAgentVersion() already adds it. Related to #769
- Add missing Disk column to Hosts view table, matching Docker/Proxmox
views. Shows aggregate disk usage across all mounted filesystems.
Related to #771
- Fix CPU/Memory column alignment in Hosts view from left to center,
consistent with other views
- Remove animate-enter class from all table row components
- Prevents animations on routine WebSocket data updates
- Animations now only occur on initial load/new items
- Fix test expectation for Docker monitoring checkbox default state
Components updated:
- DockerUnifiedTable (container/service rows)
- DockerHostSummaryTable
- NodeSummaryTable
- GuestRow
- ConfiguredNodeTables (PVE/PBS/PMG)
- Storage and DiskList
- APITokenManager
- UnifiedAgents
All tests passing.
- Update OIDC.md docs to show correct URL path (/?show_local=true, not /login)
- Hide "Enter your credentials" subtitle when local login is hidden
Related to #750
- Default enableDocker to false in UI to prevent unintended Docker
agent activation on host-only installs (Related to #766)
- Deploy agent scripts and binaries during web UI upgrades, not just
the main binary (Related to #760)
- Apply symlink resolution fix to standalone docker agent self-update
to prevent cross-device rename failures (Related to #737)
- Add content hash check to config watcher polling path to match fsnotify
behavior, preventing unnecessary restarts when .env is touched but
content unchanged (Related to #748)
- Change settings sidebar to expanded by default and persist user
preference using usePersistentSignal (Related to #764)
Remove ~900 lines of unused code identified by static analysis:
Go:
- internal/logging: Remove 10 unused functions (InitFromConfig, New,
FromContext, WithLogger, etc.) that were built but never integrated
- cmd/pulse-sensor-proxy: Remove 7 dead validation functions for a
removed command execution feature
- internal/metrics: Remove 8 unused notification metric functions and
10 Prometheus metrics that were never wired up
Frontend:
- Delete ActivationBanner.tsx stub component
- Remove unused exports: stopMetricsSampler, getSamplerStatus,
formatSpeedCompact, parseMetricKey, getResourceAlerts
- Cap progress bars at 140px max-width for better readability
- Constrain shell layout to 1400px max-width, centered
- Fix metric columns (CPU, Memory, Disk) at 156px
- Give name/resource columns extra space via flex
- Remove Docker Disk column (data rarely available from API)
- Remove fixed percentage widths from backups table for auto-sizing
- Improve text contrast in progress bar labels
Shows shield icon next to guest name when backup is stale or missing:
- Yellow shield: backup 24-72 hours old
- Red shield: backup older than 72 hours
- Gray shield: no backup found
Fresh backups (<24h) show no indicator to keep the UI clean.
Templates are excluded from backup status display.
Convert Docker table from HTML table to CSS Grid with dynamic column
visibility, matching the responsive behavior of the Proxmox overview.
Changes:
- Add DOCKER_COLUMNS with priority-based visibility breakpoints
- Use useGridTemplate hook for dynamic grid-template-columns
- Convert DockerContainerRow and DockerServiceRow to grid layout
- Use ResponsiveMetricCell for CPU/Memory/Disk columns
- Columns show/hide automatically based on viewport width:
- essential (always): Resource, Status
- primary (sm): Type, Updated
- secondary (md): CPU, Memory
- supplementary (lg): Image, Tasks
- detailed (xl): Disk
Changes from hard-coded 1768px to min(95vw, 2400px) which scales
appropriately on 4K and ultrawide monitors while maintaining
breathing room at screen edges.
- Restore green/yellow/red color thresholds for disk and network I/O
- Use consistent text-xs font size across all columns
- Expand column widths to fit full header text at larger breakpoints
- Show full header names (Disk Read, VMID, Uptime, Memory) at xl+ screens
- Use shared GUEST_COLUMNS config for header/row grid alignment
- Add whitespace-nowrap to prevent header text wrapping
DockerAgents.tsx and HostAgents.tsx were consolidated into
UnifiedAgents.tsx, but the old test files remained and referenced
non-existent components, breaking CI.
- Remove DockerAgents.test.tsx and HostAgents.test.tsx
- Add UnifiedAgents.test.tsx covering token generation, host lookup,
managed agents table, and platform-specific install commands
Add seamless migration path from legacy agents to unified agent:
- Add AgentType field to report payloads (unified vs legacy detection)
- Update server to detect legacy agents by type instead of version
- Add UI banner showing upgrade command when legacy agents are detected
- Add deprecation notice to install-host-agent.ps1
- Create install-docker-agent.sh stub that redirects to unified installer
Legacy agents (pulse-host-agent, pulse-docker-agent) now show a "Legacy"
badge in the UI with a one-click copy command to upgrade to the unified
agent.
Count columns now have appropriate widths based on their header text:
- Dashboard: VMs/CTs use compact 40-50px (short labels)
- Storage: Storage/Disks use wider 50-70px and 45-60px
- Backups: Backups uses wider 50-70px
This fixes the broken appearance on Storage and Backups tabs where
longer header text didn't fit in the narrow fixed-width columns.
The I/O columns were not updating in real-time because they accessed
props.guest directly instead of through reactive memos. This wraps
diskRead, diskWrite, networkIn, networkOut in createMemo() to properly
track changes from WebSocket data updates.
The I/O columns (Disk Read, Disk Write, Net In, Net Out) were showing
static gray text regardless of throughput values. This restores the
visual intensity scaling that was lost during the responsive table
refactor, where higher throughput values appear bolder/brighter to
draw attention to active I/O.
Thresholds:
- < 1 MB/s: dim gray
- 1-10 MB/s: normal gray
- 10-50 MB/s: medium weight, slightly brighter
- > 50 MB/s: semibold, white/black
Use ResizeObserver to track container width and estimate text width
based on character count. When the full text (percentage + sublabel)
won't fit, only the percentage is shown to prevent text clipping.
- Add 4 separate I/O columns (D Read, D Write, N In, N Out) to guest table
- Tighten column widths: fixed-width I/O columns, flexible progress bar columns
- Remove sticky columns from NodeSummaryTable (not needed)
- Shorten "Containers" to "CTs" in node summary for consistency
- Always show full VM/LXC labels (no mobile abbreviation)
- Increase name column minWidth to 100px for mobile readability
- Add formatSpeed utility function for I/O display
- Add responsive infrastructure: useBreakpoint hook, useGridTemplate hook
- Implemented adaptive layout for NodeSummaryTable with responsive columns and sticky name column.
- Fixed GuestRow background display issues.
- Added IsLegacy field to Host and DockerHost models to flag legacy agents (version < 1.0.0).
- Updated monitor to populate IsLegacy based on agent version.
Implements #750 - allows hiding the username/password login form when
using OIDC SSO to avoid user confusion, while maintaining security.
- Added HideLocalLogin config option (env: PULSE_AUTH_HIDE_LOCAL_LOGIN)
- Exposed hideLocalLogin in /api/security/status endpoint
- Updated Login.tsx to conditionally hide local login form
- Added escape hatch via ?show_local=true URL parameter
This approach avoids the security and upgrade issues that led to
DISABLE_AUTH being removed (see #707, #678), while solving the UX
problem of users being confused by multiple login options.
- Change default server listen addresses to empty string (listen on all interfaces including IPv6)
- Add short hostname matching fallback in host lookup API to handle FQDN vs short name mismatches
- Implement retry loop (30s) in both Windows and Linux/macOS installers for registration verification
- Fix lint errors: remove unnecessary fmt.Sprintf and nil checks before len()
This resolves the 'Installer could not yet confirm host registration with Pulse' warning
by addressing timing issues, hostname matching, and network connectivity.
The Settings page was telling systemd/bare metal users to run install.sh
for upgrades, which is wrong - install.sh is for fresh installations only
and does nothing if Pulse is already installed.
Changes:
- Updated upgrade instructions to mention built-in "Install Update" button
- Added correct manual upgrade steps (download tarball, stop service, extract, start)
- Removed misleading "run install.sh" instruction
This fixes a critical UX issue where users would run install.sh and think
nothing happened, when they should either:
1. Use the built-in automatic update feature (Install Update button)
2. Manually download and extract the new binary
Related files:
- frontend-modern/src/components/Settings/Settings.tsx:4052-4072
The "SSH Fallback" label was confusing to users. Changed to "Proxy (SSH)"
to make it clearer that the proxy is using SSH to collect temperature data
from cluster nodes.
This appears in the Capabilities column on Settings → Nodes when:
- Temperature monitoring is enabled
- Socket proxy is not available/healthy
- HTTPS proxy is not available/reachable
- Added DetectDockerContainerName() to detect container name from hostname
- Extended /api/security/status to expose dockerContainerName field
- Updated FirstRunSetup to show actual container name when detected:
* Before: 'docker exec <container-name> cat /data/.bootstrap_token'
* After: 'docker exec pulse cat /data/.bootstrap_token'
This reduces friction for users - no need to look up the container name.
Works when Docker container is named (--name flag), falls back to
placeholder for auto-generated container IDs.
- Added DetectLXCCTID() to internal/system/container.go to detect Proxmox container ID
- Extended /api/security/status to expose inContainer and lxcCtid fields
- Updated FirstRunSetup to show most relevant command based on detected environment:
* LXC with CTID: Shows 'pct exec 171 -- cat /etc/pulse/.bootstrap_token'
* Docker: Shows 'docker exec <container-name> cat /data/.bootstrap_token'
* Bare metal: Shows 'cat /etc/pulse/.bootstrap_token'
- Collapsed alternative methods behind 'Show other retrieval methods' button
This addresses user feedback that showing all options was overwhelming.
Now users see the command most likely to work for their setup first,
with alternatives hidden but still accessible.
When Pulse runs in Docker inside a Proxmox LXC container, users need
specific instructions to retrieve the bootstrap token. Added pct exec
and pct enter commands to the Docker instructions section.
Now shows three scenarios:
1. Direct Docker host: docker exec
2. Kubernetes: kubectl exec
3. Proxmox LXC with Docker: pct exec / pct enter
This makes first-time setup easier for users deploying Pulse in LXC
containers on Proxmox.
This implements HTTP/HTTPS support for pulse-sensor-proxy to enable
temperature monitoring across multiple separate Proxmox instances.
Architecture changes:
- Dual-mode operation: Unix socket (local) + HTTPS (remote)
- Unix socket remains default for security/performance (no breaking change)
- HTTP mode enables temps from external PVE hosts
Backend implementation:
- Add HTTPS server with TLS + Bearer token authentication to sensor-proxy
- Add TemperatureProxyURL and TemperatureProxyToken fields to PVEInstance
- Add HTTP client (internal/tempproxy/http_client.go) for remote proxy calls
- Update temperature collector to prefer HTTP proxy when configured
- Fallback logic: HTTP proxy → Unix socket → direct SSH (if not containerized)
Configuration:
- pulse-sensor-proxy config: http_enabled, http_listen_addr, http_tls_cert/key, http_auth_token
- PVEInstance config: temperature_proxy_url, temperature_proxy_token
- Environment variables: PULSE_SENSOR_PROXY_HTTP_* for all HTTP settings
Security:
- TLS 1.2+ with modern cipher suites
- Constant-time token comparison (timing attack prevention)
- Rate limiting applied to HTTP requests (shared with socket mode)
- Audit logging for all HTTP requests
Next steps:
- Update installer script to support HTTP mode + auto-registration
- Add Pulse API endpoint for proxy registration
- Generate TLS certificates during installation
- Test multi-instance temperature collection
Related to #571 (multi-instance architecture)
Implements a "Remember Me" option that allows users to stay logged in
for 30 days instead of the default 24 hours. This addresses the pain
point of frequent re-authentication in LAN-only environments while
maintaining authentication security.
Backend changes:
- Add rememberMe field to login request handling
- Support variable session durations (24h default, 30d with Remember Me)
- Implement sliding session expiration that extends sessions on each
authenticated request using the original duration
- Store OriginalDuration in session data for proper sliding window
- Update session cookie MaxAge to match session duration
Frontend changes:
- Add "Remember Me for 30 days" checkbox to login form
- Pass rememberMe flag in login request
- Improve UI with clear duration indication
Key features:
- Sessions extend automatically on each request (sliding window)
- Original duration preserved across session extension
- Backward compatible with existing sessions (legacy sessions work)
- Sessions persist across server restarts
This provides a better user experience for LAN deployments without
compromising security by completely disabling authentication.
The "Check Proxy Nodes" button in Settings > Diagnostics was returning
403 Forbidden due to missing CSRF token. The frontend was using native
fetch() instead of apiFetch() which automatically includes CSRF tokens
for POST requests.
Fixed three endpoints in Settings.tsx:
- /api/diagnostics (GET) - for consistency
- /api/diagnostics/temperature-proxy/register-nodes (POST) - reported issue
- /api/diagnostics/docker/prepare-token (POST) - same bug
Note: Export/import config endpoints intentionally continue using native
fetch() because they need custom 401/403 handling to show the API token
modal instead of redirecting to login.
- Add job queue system to ensure only one update runs at a time
- Add Server-Sent Events (SSE) for real-time push updates
- Increase rate limit from 20/min to 60/min for update endpoints
- Add unit tests for queue and SSE functionality
- Frontend: Update modal now uses SSE with polling fallback
Eliminates: 429 rate limit errors, duplicate modals, race conditions
Related to #671
This commit implements a comprehensive refactoring of the update system
to address race conditions, redundant polling, and rate limiting issues.
Backend changes:
- Add job queue system to ensure only ONE update runs at a time
- Implement Server-Sent Events (SSE) for real-time update progress
- Add rate limiting to /api/updates/status (5-second minimum per client)
- Create SSE broadcaster for push-based status updates
- Integrate job queue with update manager for atomic operations
- Add comprehensive unit tests for queue and SSE components
Frontend changes:
- Update UpdateProgressModal to use SSE as primary mechanism
- Implement automatic fallback to polling when SSE unavailable
- Maintain backward compatibility with existing update flow
- Clean up SSE connections on component unmount
API changes:
- Add new endpoint: GET /api/updates/stream (SSE)
- Enhance /api/updates/status with client-based rate limiting
- Return cached status with appropriate headers when rate limited
Benefits:
- Eliminates 429 rate limit errors during updates
- Only one update job can run at a time (prevents race conditions)
- Real-time updates via SSE reduce unnecessary polling
- Graceful degradation to polling when SSE unavailable
- Better resource utilization and reduced server load
Testing:
- All existing tests pass
- New unit tests for queue and SSE functionality
- Integration tests verify complete update flow
This commit addresses three recurring issues with the update system:
1. **Checksum mismatches (v4.27.0, v4.28.0):**
- Root cause: Release process uploads checksums.txt first, but if artifacts
are rebuilt after that upload, checksums become stale
- Fix: Update RELEASE_CHECKLIST.md to REQUIRE running validate-release.sh
before publishing (step 9, non-negotiable)
- The validation script exists and catches these errors, but wasn't being
enforced in the release process
2. **Duplicate error modals:**
- Root cause: UpdateProgressModal rendered in both App.tsx
(GlobalUpdateProgressWatcher) and UpdateBanner.tsx
- Fix: Remove UpdateProgressModal from UpdateBanner.tsx
- GlobalUpdateProgressWatcher automatically shows the modal when updates
start, so the banner's modal is redundant
3. **Rate limiting too strict:**
- Root cause: UpdateProgressModal polls /api/updates/status every 2 seconds
(30 req/min), but rate limit was 20/min
- Fix: Increase UpdateEndpoints rate limit from 20/min to 60/min
- Allows modal to poll without hitting rate limits during updates
These were all manual process errors and configuration issues, not code bugs.
The validation script enforcement prevents future checksum mismatches.
When disabling offline alerts for VMs/containers, the setting was being persisted
correctly and honored by the alert system, but the UI always showed "Warn" instead
of the actual saved state.
Root cause: When reconstructing the overrides list from backend config, the guest
override mapping was copying poweredOffSeverity but omitting disableConnectivity,
causing ResourceTable to fall back to global defaults.
Fix: Add disableConnectivity field to guest override reconstruction in Alerts.tsx
(line 676), matching the pattern already used for Docker containers.
Users were confused about how to access the bootstrap token in Proxmox
LXC containers. They were trying to use the Proxmox web console instead
of 'pct enter' from the Proxmox host.
This adds explicit instructions in the FirstRunSetup UI that show:
- pct enter <ctid> for interactive access
- pct exec <ctid> -- cat /etc/pulse/.bootstrap_token for direct retrieval
- Clear indication that commands should be run from Proxmox host
The instructions only display when the deployment is not Docker and the
bootstrap token path is /etc/pulse/.bootstrap_token (indicating LXC).
Fixes#681
Updated FirstRunSetup to show generic container commands that work
across different orchestration platforms:
- Use <container-name> placeholder instead of hardcoded "pulse"
- Add kubectl exec example for Kubernetes/Helm deployments
- Clarify "From container host" applies to Docker, Podman, etc.
This ensures the instructions work for Docker Compose, Swarm, Helm,
and any other container orchestrator where the container might have
a different name.