Commit graph

473 commits

Author SHA1 Message Date
rcourtman
c9915ead8b feat: Add OS type display for LXC containers
- Extract ostype from LXC container config (debian, ubuntu, alpine, etc.)
- Map ostype values to human-readable names (e.g., "debian" -> "Debian")
- Add OSName field to Container model and ContainerFrontend
- Add icons for NixOS, openSUSE, and Gentoo in frontend
- LXC containers now show OS icons alongside VMs in the dashboard

Supported LXC OS types: alpine, archlinux, centos, debian, devuan,
fedora, gentoo, nixos, opensuse, ubuntu, unmanaged
2025-12-05 12:43:32 +00:00
rcourtman
41acb4f2ce feat: Replace OS column text with icons and rich tooltips
- Add OSInfoCell component with OS-specific icons (Windows, Ubuntu,
  Debian, Alpine, CentOS/RHEL, Fedora, Arch, FreeBSD, generic Linux)
- Each OS type has a distinct color for quick visual identification
- Portal tooltip shows full OS name, version, and guest agent version
- Much more compact than text strings like "Microsoft Windows Server 2022"
2025-12-05 12:34:52 +00:00
rcourtman
c169ead4d2 fix: Correct column render order (Tags before OS) 2025-12-05 12:23:24 +00:00
rcourtman
871a714a3e fix: Move OS column to detailed tier (xl+ screens)
OS info requires guest agent to be installed and configured, so most
guests won't have this data. Move to detailed tier so it only shows
on extra-wide screens or when explicitly enabled by user.
2025-12-05 12:21:43 +00:00
rcourtman
3b38578bce feat: Add icons to backup status column with rich tooltip
- Add checkmark icon for fresh backups
- Add warning triangle for stale backups
- Add X icon for critical/never backups
- Use consistent Portal-based tooltip matching other columns
- Show formatted date, time, and relative age in tooltip
2025-12-05 12:16:43 +00:00
rcourtman
fcb258ac2d wip: AI provider improvements and chat store enhancements
- Improve Anthropic provider error handling
- Add AI service enhancements
- Update AI chat store with additional state management
2025-12-05 12:14:00 +00:00
rcourtman
a15dc6b899 feat: Toggleable table columns with rich tooltips for Proxmox dashboard
Replace drawer-based info display with inline columns that can be toggled:
- Add IP, Uptime, Node, Backup, OS, Tags columns (user-toggleable)
- Add ColumnPicker dropdown to show/hide columns with localStorage persistence
- Columns auto-show based on screen width using priority system
- Remove GuestDrawer - all info now visible inline or via tooltips

Rich hover tooltips:
- Disk bar: Shows all mount points with usage %, color-coded by severity
- Memory bar: Shows used/free/balloon/swap breakdown
- IP column: Shows network icon + count, hover for interfaces, MACs, IPs, traffic

Also:
- Create useColumnVisibility hook for responsive column management
- Create ColumnPicker component for column toggle UI
- Update drawer layouts in Hosts/Docker tabs for consistency
2025-12-05 12:12:56 +00:00
rcourtman
309302a793 fix: StatusDot not updating reactively when VM status changes
The StatusDot component was computing variant, size, and className once
at mount time, not reactively. When a VM transitioned from stopped to
running, the tooltip updated (it accessed props.title directly) but the
dot color stayed red because className was stale.

Fix: Convert plain variable assignments to getter functions that access
props reactively, and call them in the JSX template.
2025-12-05 10:40:03 +00:00
rcourtman
cc3c0187a0 feat: AI features, agent improvements, and host monitoring enhancements
AI Chat Integration:
- Multi-provider support (Anthropic, OpenAI, Ollama)
- Streaming responses with markdown rendering
- Agent command execution for remote troubleshooting
- Context-aware conversations with host/container metadata

Agent Updates:
- Add --enable-proxmox flag for automatic PVE/PBS token setup
- Improve auto-update with semver comparison (prevents downgrades)
- Add updatedFrom tracking to report previous version after update
- Reduce initial update check delay from 30s to 5s
- Add agent version column to Hosts page table

Host Metrics:
- Add DiskIO stats collection (read/write bytes, ops, time)
- Improve disk filtering to exclude Docker overlay mounts
- Add RAID array monitoring via mdadm
- Enhanced temperature sensor parsing

Frontend:
- New Agent Version column on Hosts overview table
- Improved node modal with agent-first installation flow
- Add DiskIO display in host drawer
- Better responsive handling for metric bars
2025-12-05 10:37:02 +00:00
rcourtman
3f08bfe8d0 wip: AI chat integration with multi-provider support
- Add AI service with Anthropic, OpenAI, and Ollama providers
- Add AI chat UI component with streaming responses
- Add AI settings page for configuration
- Add agent exec framework for command execution
- Add API endpoints for AI chat and configuration
2025-12-04 20:16:53 +00:00
rcourtman
715c0bb451 fix: Docker table resource column overlap regression
The max-width:0 CSS trick from d3f22f06 caused the resource column
content to overlap with the TYPE badge. Use a proper max-w-[300px]
class instead to constrain long container names while maintaining
proper column spacing.

Related to #810, #789
2025-12-04 13:29:08 +00:00
rcourtman
d195c8357e fix: Use specific checkbox selector in UnifiedAgents test
The test was using getByRole('checkbox') which now matches multiple
elements after adding the "Skip certificate verification" checkbox.
Use name matcher to select the specific Docker monitoring checkbox.
2025-12-03 14:32:05 +00:00
rcourtman
4585db8a9d feat: Add "Skip certificate verification" option for agent install commands
Adds a checkbox in Settings → Host Agents that enables insecure mode for
users running Pulse behind self-signed HTTPS certificates.

When enabled:
- Adds -k flag to curl commands for downloading the install script
- Adds --insecure flag to the agent for connecting back to Pulse

Related to #806
2025-12-03 14:15:17 +00:00
rcourtman
d3f22f06bf fix: Truncate very long container names in Docker table
Add max-width constraint to the resource column in the Docker table to
ensure very long container names (like Kubernetes UUID-based names) are
properly truncated instead of extending the table width.

Related to #789
2025-12-03 14:09:16 +00:00
rcourtman
93223d8283 Add refresh-cluster button to detect new Proxmox cluster members
When new nodes are added to a Proxmox cluster after Pulse was
initially configured, they weren't showing up in Settings. The
existing "Refresh" button only triggered network discovery, not
cluster membership re-detection.

Changes:
- Add POST /api/config/nodes/{id}/refresh-cluster endpoint
- Add "Refresh" button in cluster node panel in Settings
- Re-detect cluster membership and update stored endpoints

Related to #799
2025-12-02 22:01:00 +00:00
rcourtman
fdf7de29f0 fix: Resolve TypeScript errors in StackedMemoryBar and Settings
StackedMemoryBar.tsx:
- Fixed 'props.balloon' possibly undefined error by adding fallback
  to second comparison in Show condition

Settings.tsx:
- Fixed 'systemSettings' scope error by using updateChannel() signal
  instead of referencing out-of-scope variable from previous try block

Both files now pass strict TypeScript checks.
2025-12-02 20:37:44 +00:00
rcourtman
282a323c10 fix: Update Mail Gateway disconnect state for consistency
Changed from warning (amber) to danger (red) tone and added:
- Dynamic description based on reconnecting status
- Manual "Reconnect now" button when not auto-reconnecting
- Consistent "Connection lost" title

All 7 major pages now have unified connection lost UX:
Dashboard, Storage, Backups, Replication, Hosts, Docker, Mail Gateway
2025-12-02 20:34:32 +00:00
rcourtman
6505ff5edf fix: Add connection lost indicator to Docker page
Docker/Containers page now shows a clear error state when WebSocket
connection is lost, with a manual "Reconnect now" button. This
matches the pattern established across all other major pages.

Connection lost UX is now consistent across: Dashboard, Storage,
Backups, Replication, Hosts, and Docker.
2025-12-02 20:31:59 +00:00
rcourtman
779e4e8a86 fix: Add connection lost indicator to Hosts page
Hosts page now shows a clear error state when WebSocket connection
is lost, with a manual "Reconnect now" button. Also improved loading
state logic to differentiate between initial loading and connection
loss after having received data.

This completes the connection lost UX consistency across all major
pages: Dashboard, Storage, Backups, Replication, and now Hosts.
2025-12-02 20:29:15 +00:00
rcourtman
3a5f0d1c19 fix: Add reconnect button to Backups and Replication pages
Both pages now show a consistent disconnect state with:
- Dynamic description based on reconnecting status
- Manual "Reconnect now" button when not auto-reconnecting

This matches the Dashboard and Storage page behavior, providing a
consistent UX across all main pages when connection is lost.
2025-12-02 20:24:34 +00:00
rcourtman
2cec2215f1 fix: Add connection lost indicator to Storage page
Storage page now shows a clear error state when WebSocket connection
is lost, matching the Dashboard's behavior. Users see the issue and
can manually reconnect instead of wondering why data isn't updating.
2025-12-02 20:20:39 +00:00
rcourtman
1d188db11f fix: Correct lastBackup TypeScript type from string to number
The backend sends lastBackup as Unix milliseconds (int64), not as an
ISO string. Update VM and Container interfaces to match the actual
JSON payload.

The getBackupInfo() function already handles both string and number
types, so this is a type-safety fix that aligns types with reality.
2025-12-02 20:15:35 +00:00
rcourtman
1a9d15b1c1 chore: Remove unused usePersistentSignal import
Cleanup from Settings sidebar change - the import was left behind
when switching from usePersistentSignal to createSignal.
2025-12-02 20:09:54 +00:00
rcourtman
42766be8cc fix: Settings sidebar always starts expanded for discoverability
The sidebar no longer persists its collapsed state to localStorage.
Each visit to Settings starts with the sidebar expanded, showing
all menu labels for better discoverability by new users.

Users can still collapse the sidebar during their session if they
want more space, but it will reset to expanded on page reload.

Related to #764
2025-12-02 20:03:54 +00:00
rcourtman
b70a4eba9d feat: Add 'Needs Backup' filter to Dashboard
Add a new filter button that shows only guests with stale, critical,
or missing backups. This makes it easy to identify which VMs and
containers need attention for backup scheduling.

- Adds backupMode state with 'all' and 'needs-backup' options
- Filters out templates (they don't need backups)
- Uses existing getBackupInfo() thresholds (>24h stale, >72h critical)
- Integrates with Reset button and Escape key handling
- Persists filter state in localStorage

Related to #762
2025-12-02 20:00:33 +00:00
rcourtman
6c76823754 fix: Constrain Docker drawer width to force card wrapping
The previous fix (2078421d) added overflow-hidden but didn't address
the root cause: the drawer div inside overflow-x-auto context had no
width constraint, so flex-wrap saw infinite space and didn't wrap.

Adding w-0 min-w-full forces the div to take exactly 100% of parent
width, which properly constrains flex-wrap to wrap cards within the
visible viewport.

Related to #789
2025-12-02 18:02:28 +00:00
rcourtman
53a2c2a587 fix: Prevent agent type badge flapping in UnifiedAgents view
Track previously seen host types and preserve them when one data source
temporarily has no entry for a hostname. This prevents the "Host" or
"Docker" type badge from disappearing and reappearing when websocket
updates cause momentary state inconsistencies.

The fix only preserves types if the corresponding source array has any
data at all, ensuring that intentional host removal (both arrays empty
for that host) still works correctly.

Related to #773
2025-12-02 11:29:03 +00:00
rcourtman
99a6debd3a fix: Update Channel setting not persisting after save
The Update Channel dropdown reverted to "stable" after saving because the
frontend was overwriting the user's saved preference with the running
version's channel. Now the saved setting takes priority over the detected
channel.

Related to #738
2025-12-02 03:03:51 +00:00
rcourtman
aec63b7abd Fix memory bar width showing 100% instead of actual percentage
The StackedMemoryBar component was incorrectly calculating segments when
Proxmox balloon was set to maxmem (no actual ballooning). In this case:
- balloon = total (e.g., 32GB)
- used = actual memory (e.g., 20GB)
- active = used - balloon = 20GB - 32GB = 0 (clamped)
- balloonPercent = 32GB/32GB * 100 = 100%

This caused the bar to always show 100% yellow (balloon) even when the
actual memory usage was much lower.

Fixed by only showing balloon segment when actual ballooning is in effect
(balloon > 0 && balloon < total). When there's no ballooning, the bar
now correctly shows used memory as green with the actual percentage.

Related to #788
2025-12-02 00:08:05 +00:00
rcourtman
2078421da4 Fix Docker expanded drawer cards being cut off
The expanded container/service drawer cards were overflowing
horizontally instead of wrapping when the table had overflow-x-auto.
Adding overflow-hidden to the drawer's outer container forces the
flex-wrap to work correctly.

Related to #789
2025-12-01 15:06:49 +00:00
rcourtman
32ffc1046c Fix memory bar width not scaling correctly
The StackedMemoryBar component was using flex layout for segments,
but flex children grow by default regardless of percentage width.
Changed to absolute positioning (like MetricBar uses) so the width
percentages actually work as expected.

Related to #788
2025-11-30 23:46:56 +00:00
rcourtman
6c1a0815ea Fix host agent type badge flapping in UI
Add transient empty payload protection for hosts data, matching the
existing protection for dockerHosts. When a websocket message contains
an empty hosts array (transient state), the UI now waits for 3
consecutive empty payloads before applying the change.

This prevents the "Host" type badge from disappearing intermittently
in Settings → Agents → Managed Agents when the unified agent is
reporting both host and docker data.

Related to #773
2025-11-30 22:07:40 +00:00
rcourtman
6ae7ed4ae0 Add pagination to backup list for large backup counts
- Add pagination (100 items per page) to prevent UI lockup with 2500+ backups
- Show year in date labels for non-current year backups
- Reset to page 1 when filters change
- Add First/Previous/Next/Last navigation controls

Fixes #541
2025-11-30 09:55:01 +00:00
rcourtman
949d54905c Remove CPU capacity marker and highlight high container restarts
- Remove load average marker from CPU progress bar (confusing, not useful)
- Keep load average in tooltip only
- Highlight container restart count in red when >5 (indicates instability)
2025-11-29 22:37:17 +00:00
rcourtman
e2cdcb7b3e Match original sublabel style for CPU core count display 2025-11-29 21:49:00 +00:00
rcourtman
46f90e1585 Show CPU core count on EnhancedCPUBar progress bar 2025-11-29 21:47:22 +00:00
rcourtman
02d8d8a9f9 Use left-aligned headers consistently in Docker table 2025-11-29 21:31:44 +00:00
rcourtman
f2983634e5 Center CPU and Memory cells in Docker container/service rows 2025-11-29 21:27:25 +00:00
rcourtman
c7184a1327 Center CPU and Memory column headers in Docker table 2025-11-29 21:25:08 +00:00
rcourtman
f32d22dc41 Convert data tables from CSS Grid to HTML tables
- Replace CSS Grid with native <table> elements across all data tables
- HTML tables naturally align columns based on widest content in each column
- Add min-width on CPU/Memory/Disk columns for proper progress bar display
- Remove responsive column header abbreviations (show full labels always)
- Remove icon components from NodeSummaryTable headers
- Simplify TemperatureGauge to text-only display (no progress bar)

Tables converted: Dashboard guest table, NodeSummaryTable, HostsOverview,
DockerUnifiedTable, DockerHostSummaryTable
2025-11-29 21:20:02 +00:00
rcourtman
fbabace023 Fix SolidJS proxy error in batched WebSocket updates
Using reconcile() inside produce() is invalid - reconcile returns a
function that creates a new proxy, but produce expects direct mutations.
Use batch() from solid-js instead to atomically batch the two setState
calls without proxy conflicts.
2025-11-29 18:54:22 +00:00
rcourtman
8bc8a98bb8 Remove unused usagePercent variable in Storage.tsx 2025-11-29 18:46:14 +00:00
rcourtman
67519304f9 Fix agent type flapping in Settings/Agents page
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
2025-11-29 18:06:41 +00:00
courtmanr@gmail.com
a8911b0f32 Add EnhancedStorageBar component with ZFS status visualization
- 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
2025-11-29 17:32:54 +00:00
courtmanr@gmail.com
3ed190e8bc Add EnhancedCPUBar component with Load Average visualization
- 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
2025-11-29 15:15:38 +00:00
courtmanr@gmail.com
ae3a349124 Refine memory visualization with StackedMemoryBar component
- 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
2025-11-29 13:48:30 +00:00
courtmanr@gmail.com
6852004ffb feat: add stacked disk bar to hosts overview and fix tooltip positioning
- 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
2025-11-28 22:10:15 +00:00
rcourtman
4de209b8a1 Fix TypeScript errors in Login.tsx and UnifiedAgents.tsx 2025-11-28 20:16:31 +00:00
rcourtman
d93e17e898 Fix settings sidebar width causing status icons to wrap
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
2025-11-28 15:07:42 +00:00
rcourtman
f9bd09a2b4 Add UI for removed Docker hosts re-enrollment
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
2025-11-28 12:07:28 +00:00
rcourtman
71580181d0 Add stacked bar visualization for multiple disks
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
2025-11-28 10:36:23 +00:00
rcourtman
8afc4d0c6c Fix hideLocalLogin toggle persistence and login page UI
- 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
2025-11-28 09:05:41 +00:00
rcourtman
12dc8eac0f Fix duplicate version prefix and Hosts view column alignment
- 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
2025-11-28 00:07:40 +00:00
courtmanr@gmail.com
b0ff539fcc Remove unwanted table animations on data updates
- 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.
2025-11-27 18:52:43 +00:00
rcourtman
22dac34acf fix: correct show_local URL path and hide subtitle when local login hidden
- 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
2025-11-27 16:23:44 +00:00
rcourtman
6ff5ae4200 fix: multiple agent installation and update issues
- 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)
2025-11-27 15:49:03 +00:00
rcourtman
cb8c46cfb6 fix: add hash check to polling fallback and persist sidebar state
- 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)
2025-11-27 15:24:23 +00:00
rcourtman
455c711185 fix: remove unused samplerInterval variable 2025-11-27 13:20:58 +00:00
rcourtman
f7ffb36c41 chore: remove dead code and unused exports
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
2025-11-27 13:17:39 +00:00
rcourtman
244431b6c2 refactor(ui): improve table column sizing and progress bar constraints
- 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
2025-11-27 12:09:12 +00:00
courtmanr@gmail.com
185476750d feat(ui): apply glassmorphism and staggered animations to tables 2025-11-27 10:44:36 +00:00
rcourtman
91c72a274c feat: add backup status indicator to guest table
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.
2025-11-26 21:30:41 +00:00
courtmanr@gmail.com
c021dc6ca5 Enhance table responsiveness across multiple components 2025-11-26 17:57:09 +00:00
rcourtman
09ec0c3f01 security: harden agent installers and auto-update mechanism
Install script (scripts/install.sh):
- Add multi-platform support: Unraid, OpenRC/Alpine, Synology DSM 6/7
- Add input validation for URL, token format, and interval
- Add binary magic verification (ELF/Mach-O/PE)
- Add cleanup trap for temp files
- Wrap script in main() for partial download protection
- Fix shellcheck compliance issues
- Add curl timeouts

Agent auto-update (agentupdate, dockeragent):
- Enforce TLS 1.2 minimum version
- Make SHA256 checksum verification mandatory
- Add 100MB binary size limit
- Add binary magic verification before replacement
- Add Unraid persistent binary update after self-update
- Add 5-minute download timeout

Frontend:
- Update Linux install description to note auto-detection of init systems
2025-11-26 13:14:58 +00:00
rcourtman
2b19127c0c feat: add responsive column hiding to Docker table
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
2025-11-26 10:58:12 +00:00
rcourtman
507ad730e2 fix: use responsive max-width for better scaling on all displays
Changes from hard-coded 1768px to min(95vw, 2400px) which scales
appropriately on 4K and ultrawide monitors while maintaining
breathing room at screen edges.
2025-11-26 09:46:56 +00:00
rcourtman
f6fe95c77c fix: constrain max layout width to 1768px on wide screens
Prevents the UI from stretching uncomfortably on ultra-wide displays.
2025-11-26 09:37:20 +00:00
rcourtman
17c99377f6 fix: restore I/O column styling to match v4.32.5 release
- 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
2025-11-26 09:32:58 +00:00
rcourtman
34137aeca5 fix: resolve TypeScript errors in frontend components
- UnifiedBackups: remove unused cols variable in colspan calculation
- GuestDrawer: remove unused IOMetric import, fix osInfo -> osName/osVersion
- Settings: add 'agents' to SettingsTab type, remove unused Boxes import,
  fix ToggleChangeEvent target -> currentTarget
- UnifiedAgents: remove unused onCleanup import, prefix unused hostname param
- NodeSummaryTable: remove unused index parameter in For callback
- Toggle: add locked and lockedMessage props to LabeledToggleProps
2025-11-26 09:11:25 +00:00
rcourtman
c110a3b803 fix: replace stale agent tests with UnifiedAgents tests
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
2025-11-26 09:06:15 +00:00
rcourtman
920f271b41 feat: improve legacy agent detection and migration UX
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.
2025-11-25 23:26:22 +00:00
rcourtman
661ca22eab fix: adapt NodeSummaryTable column widths per tab context
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.
2025-11-25 22:31:59 +00:00
rcourtman
5dce402d62 fix: make I/O metrics reactive to WebSocket updates
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.
2025-11-25 21:36:27 +00:00
rcourtman
bc3bb7a7ac fix: restore intensity-based coloring for I/O columns
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
2025-11-25 21:29:06 +00:00
rcourtman
b43354a3d7 fix: auto-hide sublabel when progress bar text overflows
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.
2025-11-25 20:45:11 +00:00
rcourtman
98b0721461 feat: improve responsive table layout with tighter columns
- 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
2025-11-25 20:37:28 +00:00
courtmanr@gmail.com
ebcafa1dac feat: adaptive node table layout, guest row fixes, and legacy agent detection
- 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.
2025-11-25 17:19:36 +00:00
courtmanr@gmail.com
a8949dbfba feat: add managed agents list and cleanup legacy scripts 2025-11-25 12:54:13 +00:00
courtmanr@gmail.com
ceb5f1936a feat: unify agent installation UI and scripts 2025-11-25 12:23:22 +00:00
courtmanr@gmail.com
26ebd476da WIP: Save all pending changes including frontend updates and unified agent scaffolding 2025-11-25 11:27:07 +00:00
courtmanr@gmail.com
ac747ddf11 Enhance UI responsiveness for tables: apply percentage-based widths and selective column hiding 2025-11-25 10:57:33 +00:00
courtmanr@gmail.com
3acd29c3f3 Implement UI toggle for Hide Local Login (related to issue #750) 2025-11-25 08:14:19 +00:00
courtmanr@gmail.com
6b84b9a2bf Add PULSE_AUTH_HIDE_LOCAL_LOGIN option to hide password form
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.
2025-11-24 17:40:43 +00:00
courtmanr@gmail.com
07d8381858 Fix host agent registration verification issues (#746)
- 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.
2025-11-24 14:28:09 +00:00
courtmanr@gmail.com
efad01b717 Fix dashboard layout on wide screens and remove wasted whitespace #643 2025-11-24 08:09:52 +00:00
courtmanr@gmail.com
0c7af34ad2 Implement status bars for Storage, Backups, and Hosts tabs #744 2025-11-23 22:53:33 +00:00
courtmanr@gmail.com
6e404b24eb Fix install-host-agent.sh function order, remove duplicate, and improve dev serving 2025-11-23 12:27:11 +00:00
courtmanr@gmail.com
2312345a02 feat: add degraded status dot to Proxmox and Docker tabs (fixes #744) 2025-11-23 00:10:58 +00:00
rcourtman
9b50dec0e4 Align alert grouping defaults with backend 2025-11-22 17:11:52 +00:00
rcourtman
5dbaf7c596 Add OIDC CA bundle support 2025-11-22 09:44:03 +00:00
rcourtman
bbbb417b01 Prevent token manager auth swap and fix docker agent perms (Related to #740) 2025-11-22 07:18:42 +00:00
rcourtman
91c51689c9 Guard host-agent installs on noexec filesystems (Related to #718) 2025-11-21 23:00:47 +00:00
rcourtman
4e68675e0b Add recovery notifications and grouping controls 2025-11-21 22:07:00 +00:00
rcourtman
30a069dfa6 Add TrueNAS SCALE persistence for host agent (Related to #718) 2025-11-21 10:07:14 +00:00
rcourtman
0adc4e7838 Initialize ObservedValues in Windows installer 2025-11-20 21:01:44 +00:00
rcourtman
516097990a Add sorting controls to Docker resources table
Related to #730
2025-11-20 17:58:19 +00:00
rcourtman
d779328878 Add Apprise test support for notifications
Related to #584
2025-11-20 17:54:20 +00:00
courtmanr@gmail.com
1e229d5f38 Improve login page UI and fix hot-dev script for macOS 2025-11-20 12:21:49 +00:00
courtmanr@gmail.com
1626b0e3ae Fix 'Remember me' label toggle behavior 2025-11-20 11:58:42 +00:00
rcourtman
64c654b330 Fix NodeModal authType typing for type-check 2025-11-20 10:07:22 +00:00
rcourtman
bdcde70b81 Handle host agent overrides in thresholds
Related to #722
2025-11-20 10:02:19 +00:00
rcourtman
f4e2cbca48 Improve token collision handling and installer subnet support 2025-11-20 09:45:36 +00:00
rcourtman
4067b0717a Improve Windows installer arch detection (related to #723) 2025-11-20 09:37:45 +00:00
courtmanr@gmail.com
a39fdc9e99 fix: local dev setup, encryption key generation, and pnpm lockfile 2025-11-19 14:48:09 +00:00
rcourtman
c5ef7684a7 fix: update js-yaml to address CVE-2025-64718 prototype pollution vulnerability 2025-11-18 22:50:07 +00:00
rcourtman
74c2dbfa15 Fix node modal form reset when toggling temperature (#713) 2025-11-18 22:28:49 +00:00
rcourtman
3f46d35a81 feat: make PVE polling interval configurable (related to #467) 2025-11-18 21:30:04 +00:00
rcourtman
2c3f197f63 Gate backups spinner on initial payload only 2025-11-18 16:43:02 +00:00
rcourtman
1d214a1c61 Stop showing indefinite backup spinner with no data 2025-11-18 16:37:10 +00:00
rcourtman
a46ca5052b Fix proxy UI type errors 2025-11-17 14:35:32 +00:00
rcourtman
a479040651 Improve temperature proxy workflow 2025-11-17 14:25:46 +00:00
rcourtman
aa7b7b0922 Set initialDataReceived on first websocket payload 2025-11-16 00:03:04 +00:00
rcourtman
26442beca1 Tie backup loading state to initial data flag 2025-11-15 23:46:20 +00:00
rcourtman
4e9572d5e3 Gate backups spinner on websocket connection 2025-11-15 23:41:38 +00:00
rcourtman
3c1f4c20ae Stop backup page spinner after first state update 2025-11-15 23:31:36 +00:00
rcourtman
e375a21d4b Fix backup UX and proxy config dedupe 2025-11-15 23:26:44 +00:00
rcourtman
de5b314842 Improve temperature proxy control-plane flow 2025-11-15 21:49:51 +00:00
rcourtman
b577a3d464 Fix incorrect upgrade instructions for systemd/bare metal installs
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
2025-11-15 13:39:12 +00:00
rcourtman
068c17aad9 Change 'SSH Fallback' to 'Proxy (SSH)' in Capabilities column
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
2025-11-15 10:50:48 +00:00
rcourtman
639635a84b Add Docker container name auto-detection to bootstrap UI
- 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.
2025-11-15 10:45:00 +00:00
rcourtman
ae0481c982 Improve bootstrap token UX with smart environment detection
- 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.
2025-11-15 10:18:59 +00:00
rcourtman
f64344d1da Add Proxmox LXC instructions to bootstrap token screen
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.
2025-11-15 09:56:27 +00:00
rcourtman
20194d9bb7 Add CI build workflow and tighten proxy diagnostics 2025-11-14 13:32:29 +00:00
rcourtman
65fbd72b15 feat: add shared status indicators
Related to #677
2025-11-14 12:42:08 +00:00
rcourtman
1d0d23d237 Related to #710: harden Windows installer arch detection 2025-11-14 10:50:56 +00:00
rcourtman
4f8b807f0c Refine Windows host installer logging (related to #709) 2025-11-13 23:09:22 +00:00
rcourtman
70673c1fdc Improve temperature proxy diagnostics and tests 2025-11-13 22:31:53 +00:00
rcourtman
22f092f941 Add HTTP mode to pulse-sensor-proxy for multi-instance temperature monitoring
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)
2025-11-13 16:13:53 +00:00
rcourtman
c1d076db0c Simplify Remember Me label text
Remove '30 days' from label - most apps just say 'Remember me' without
specifying the duration.
2025-11-13 10:40:53 +00:00
rcourtman
56a7579c99 Add Remember Me feature with sliding session expiration (Related to #707)
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.
2025-11-13 10:37:08 +00:00
rcourtman
bb55144637 Improve update integration diagnostics 2025-11-12 22:27:05 +00:00
rcourtman
88d0aeebdf Ensure alert cooldown persists reliably
Related to #706
2025-11-12 21:52:24 +00:00
rcourtman
3fa7356ff3 Improve auth timeout handling (related to #705) 2025-11-12 21:50:53 +00:00
rcourtman
3b079eeddb Add release dry run workflow and API update integration test 2025-11-12 21:02:52 +00:00
rcourtman
7815b45483 Fix CSRF token validation failure in Settings diagnostics endpoints (related to #600)
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.
2025-11-12 00:00:55 +00:00
rcourtman
ea6cad10ce Release workflow guardrails (related to #695) 2025-11-11 22:34:00 +00:00
rcourtman
b2d441852d Merge update service refactor with SSE and job queue
- 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
2025-11-11 10:06:16 +00:00
Claude
cc46e3c6f0
Refactor update service to eliminate polling and race conditions
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
2025-11-11 09:33:05 +00:00
rcourtman
e894bc7b1d Fix recurring update issues (related to #671)
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.
2025-11-11 09:09:30 +00:00
rcourtman
bb6ea3b23c Fix offline alert state not displaying in thresholds UI (related to #683)
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.
2025-11-10 20:32:04 +00:00
rcourtman
14ac4bbb8b Add Proxmox LXC instructions to bootstrap token UI
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
2025-11-10 12:20:41 +00:00
rcourtman
999e598e44 Improve bootstrap token instructions for all container types
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.
2025-11-09 23:48:43 +00:00
rcourtman
df185985eb Fix bootstrap token path display for Docker deployments (related to #680)
The first-run setup UI was displaying incorrect bootstrap token paths for
Docker deployments. It showed `/etc/pulse/.bootstrap_token` regardless of
deployment type, but Docker containers use `/data/.bootstrap_token` by
default (via PULSE_DATA_DIR env var).

Changes:
- Extended `/api/security/status` endpoint to include `bootstrapTokenPath`
  and `isDocker` fields when a bootstrap token is active
- Updated FirstRunSetup component to fetch and display the correct path
  dynamically based on actual deployment configuration
- For Docker deployments, UI now shows both `docker exec` command and
  in-container command
- Falls back to showing both standard and Docker paths if API data
  unavailable (backward compatibility)

This fix ensures users always see the correct command for their specific
deployment, including custom PULSE_DATA_DIR configurations.
2025-11-09 23:41:55 +00:00
rcourtman
a82a345cd6 Improve table column widths and sparkline visibility 2025-11-09 23:36:52 +00:00
rcourtman
4c6f565855 fix: sparkline canvas wrapper display mode for flex layout
Change sparkline wrapper from inline-block to block w-full to properly
fill flex parent container. Inline-block was preventing the canvas from
calculating the correct width when width={0} (auto-size mode).
2025-11-09 22:35:32 +00:00
rcourtman
886368ec44 feat: add sparklines view mode for metrics visualization
Add comprehensive sparkline chart support as an alternative to progress bars
for CPU, Memory, and Disk metrics across all tables.

Features:
- Toggle between bars/trends view modes (persisted to localStorage)
- 30-second sampling with 2-hour retention window using ring buffer
- Canvas-based rendering with shared requestAnimationFrame for efficiency
- Hover tooltips showing exact values and timestamps
- Threshold reference lines (warning/critical) for context
- localStorage persistence survives page refreshes (12-hour max age)
- Dynamic width adaptation to column size
- Namespaced resource IDs prevent collisions
- Lifecycle cleanup prevents memory leaks

Performance optimizations:
- Decoupled sampling from WebSocket handler (6x reduction in recording)
- O(1) ring buffer insertions (no array cloning)
- Batched canvas rendering (single rAF for all sparklines)
- Debounced localStorage writes
- Automatic pruning of removed resources

UI improvements:
- Consistent radio toggle styling matching other filters
- Fixed column widths prevent layout shift during toggle
- Fixed row heights prevent vertical size changes
- Sparklines fill available column width proportionally
2025-11-09 22:31:35 +00:00
rcourtman
078248770e Fix Docker host custom display name not showing in main Docker tab RESOURCE column (related to #662)
The previous fix (a1ba915ca) correctly added customDisplayName to the WebSocket
payload and made it persist in Settings, but the main Docker tab's RESOURCE
column still showed the default name.

DockerUnifiedTable had four locations that built display names but ignored
customDisplayName:
- DockerHostGroupHeader (RESOURCE column header) - line 549
- containerMatchesToken (search/filter logic) - line 391
- serviceMatchesToken (search/filter logic) - line 472
- sortedHosts (host sorting logic) - lines 1879-1880

All four now prioritize customDisplayName first, matching the pattern used in
DockerHostSummaryTable and Settings (customDisplayName || displayName ||
hostname || id).

This ensures custom Docker host names display consistently across the entire UI.
2025-11-09 18:03:38 +00:00
rcourtman
bb7ca93c18 feat: Add mdadm RAID monitoring support for host agents
Implements comprehensive mdadm RAID array monitoring for Linux hosts
via pulse-host-agent. Arrays are automatically detected and monitored
with real-time status updates, rebuild progress tracking, and automatic
alerting for degraded or failed arrays.

Key changes:

**Backend:**
- Add mdadm package for parsing mdadm --detail output
- Extend host agent report structure with RAID array data
- Integrate mdadm collection into host agent (Linux-only, best-effort)
- Add RAID array processing in monitoring system
- Implement automatic alerting:
  - Critical alerts for degraded arrays or arrays with failed devices
  - Warning alerts for rebuilding/resyncing arrays with progress tracking
  - Auto-clear alerts when arrays return to healthy state

**Frontend:**
- Add TypeScript types for RAID arrays and devices
- Display RAID arrays in host details drawer with:
  - Array status (clean/degraded/recovering) with color-coded indicators
  - Device counts (active/total/failed/spare)
  - Rebuild progress percentage and speed when applicable
  - Green for healthy, amber for rebuilding, red for degraded

**Documentation:**
- Document mdadm monitoring feature in HOST_AGENT.md
- Explain requirements (Linux, mdadm installed, root access)
- Clarify scope (software RAID only, hardware RAID not supported)

**Testing:**
- Add comprehensive tests for mdadm output parsing
- Test parsing of healthy, degraded, and rebuilding arrays
- Verify proper extraction of device states and rebuild progress

All builds pass successfully. RAID monitoring is automatic and best-effort
- if mdadm is not installed or no arrays exist, host agent continues
reporting other metrics normally.

Related to #676
2025-11-09 16:36:33 +00:00
rcourtman
1b221cca71 feat: Add configurable allowlist for webhook private IP targets (addresses #673)
Allow homelab users to send webhooks to internal services while maintaining security defaults.

Changes:
- Add webhookAllowedPrivateCIDRs field to SystemSettings (persistent config)
- Implement CIDR parsing and validation in NotificationManager
- Convert ValidateWebhookURL to instance method to access allowlist
- Add UI controls in System Settings for configuring trusted CIDR ranges
- Maintain strict security by default (block all private IPs)
- Keep localhost, link-local, and cloud metadata services blocked regardless of allowlist
- Re-validate on both config save and webhook delivery (DNS rebinding protection)
- Add comprehensive tests for CIDR parsing and IP matching

Backend:
- UpdateAllowedPrivateCIDRs() parses comma-separated CIDRs with validation
- Support for bare IPs (auto-converts to /32 or /128)
- Thread-safe allowlist updates with RWMutex
- Logging when allowlist is updated or used
- Validation errors prevent invalid CIDRs from being saved

Frontend:
- New "Webhook Security" section in System Settings
- Input field with examples and helpful placeholder text
- Real-time unsaved changes tracking
- Loads and saves allowlist via system settings API

Security:
- Default behavior unchanged (all private IPs blocked)
- Explicit opt-in required via configuration
- Localhost (127/8) always blocked
- Link-local (169.254/16) always blocked
- Cloud metadata services always blocked
- DNS resolution checked at both save and send time

Testing:
- Tests for CIDR parsing (valid/invalid inputs)
- Tests for IP allowlist matching
- Tests for bare IP address handling
- Tests for security boundaries (localhost, link-local remain blocked)

Related to #673

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 08:31:12 +00:00
rcourtman
de10ec949e Fix CRITICAL bug: UpdateProgressModal polling never started (fixes #671)
ROOT CAUSE: The onMount hook checked props.isOpen, but onMount only runs ONCE
when the component first mounts. Since UpdateProgressModal mounts when the app
loads (before the user clicks "Apply Update"), props.isOpen is false at mount
time, so polling never initializes.

When the user later clicks "Apply Update" and props.isOpen becomes true, onMount
doesn't re-run, leaving the modal in a broken state with no polling, no restart
detection, and no auto-reload - exactly what users reported (stuck for 30+ mins).

SOLUTION: Changed from onMount to createEffect watching props.isOpen. Now:
- Polling starts immediately when the modal opens (user clicks "Apply Update")
- Polling stops when the modal closes (cleanup)
- The entire update flow works as designed

This was the ACTUAL bug - the previous commits (global watcher, fallback polling)
were helpful additions but didn't fix the root cause.
2025-11-08 23:26:55 +00:00
rcourtman
c004c4517f Bulletproof the update auto-refresh with fallback mechanisms (related to #671)
After the initial fix, added multiple layers of reliability to ensure updates
ALWAYS auto-refresh, even in edge cases:

1. Fallback polling: GlobalUpdateProgressWatcher now polls /api/updates/status
   every 5 seconds as a safety net in case WebSocket events are dropped, missed,
   or the tab connects mid-update. This ensures tabs that join late or have
   WebSocket issues still detect in-progress updates.

2. Manual reload button: Added "Reload Now" button in UpdateProgressModal that
   appears after 5+ health check attempts during restart. Gives users an escape
   hatch if auto-reload is delayed (slow DNS, reverse proxy issues, etc.).

3. Already protected: Modal close button only shows when update is complete,
   preventing users from accidentally closing it mid-update.

These changes address all failure modes identified:
- Tabs without WebSocket: covered by polling fallback
- Tabs joining mid-update: covered by polling fallback
- Health check delays: covered by manual reload button
- User accidentally closing modal: already prevented

The combination of WebSocket events (primary), polling (fallback), health checks
(restart detection), and manual reload (escape hatch) should make this bulletproof.
2025-11-08 23:19:51 +00:00
rcourtman
706822ed58 Fix updater auto-refresh for all open tabs (related to #671)
Problem: When an update was triggered, only the tab that clicked "Apply Update"
would show the progress modal and auto-refresh after completion. Other open tabs
would remain on the old version indefinitely.

Root cause: The UpdateProgressModal was only shown when explicitly opened via the
UpdateBanner component. WebSocket already broadcasts update:progress events, but
no global listener existed to show the modal in all tabs.

Solution: Added GlobalUpdateProgressWatcher component in App.tsx that:
- Listens to WebSocket updateProgress events globally (in all tabs)
- Filters to only real update-in-progress states (downloading, verifying, extracting,
  installing, restarting) to avoid false positives from routine update checks
- Auto-opens the progress modal when an update starts
- Allows manual dismissal after update completes
- Works independently of UpdateBanner visibility (e.g., when banner is dismissed)

The modal's existing health-check and auto-reload logic handles the page refresh
once the backend is healthy again.
2025-11-08 23:15:50 +00:00
rcourtman
8dad0872ae Fix CSRF token parsing in config export/import (related to #646)
The config backup export and import functions were incorrectly parsing
the CSRF token from cookies, causing "Export requires authentication"
errors even when users were properly logged in.

Two issues were fixed:

1. Cookie parsing used `.split('=')[1]` which truncated tokens containing
   `=` padding characters (common in base64 tokens). Fixed by using
   `.split('=').slice(1).join('=')` to preserve the full value.

2. Missing URL decoding of the cookie value. Browsers percent-encode
   cookie values, so `=` becomes `%3D`. The backend then failed to match
   the encoded token hash. Fixed by adding `decodeURIComponent()`.

Both fixes mirror the pattern already used in apiClient.ts.
2025-11-08 11:17:18 +00:00
rcourtman
cb9d8d1ab1 Fix config backup/restore by enforcing 12-char minimum password (related to #646)
Users with 8-11 character passwords could not export/restore config backups
because the export encryption requires 12+ character passphrases for security,
but the password creation UI only enforced an 8-character minimum.

This created a confusing UX where users with short passwords saw validation
errors when trying to export backups, with the only solution being to use a
custom passphrase or change their password.

Root cause:
- FirstRunSetup and ChangePasswordModal allowed 8+ char passwords
- Config export/import requires 12+ char passphrases (backend validation)
- The v4.26.4 fix added frontend validation that showed the mismatch
- Users hit client-side validation before request was sent (no backend logs)

This fix raises the minimum password length to 12 characters everywhere:
- internal/auth/password.go: MinPasswordLength 8 → 12
- FirstRunSetup.tsx: validation and placeholder updated
- ChangePasswordModal.tsx: validation, minLength, and help text updated
- QuickSecuritySetup.tsx: validation and label updated

Impact:
- New users must create 12+ character passwords
- Existing users with <12 char passwords are unaffected (can't detect from hash)
- Those users will see the existing helpful error directing them to use custom
  passphrase for backups
- "Use your login password" option now works for all future passwords

This aligns password requirements across the system and eliminates the
confusing mismatch between login credentials and backup encryption requirements.

Related to #646 where user confirmed backups still failed in v4.26.5
2025-11-07 22:51:55 +00:00
rcourtman
d70fa88d26 Fix config restore for CLI exports (related to #646)
The v4.26.4 fix inadvertently broke CLI export compatibility. The frontend
attempted JSON.parse on all backup files and returned early with "Invalid
JSON file format" when parsing failed. This prevented the format detection
code from ever executing, breaking CLI-generated exports which are raw
base64 strings without a JSON wrapper.

Root cause:
- CLI exports (`pulse config export`) output raw base64 via
  internal/config/export.go:128
- The fix at Settings.tsx:2030-2034 called showError() and returned
  immediately on parse failure
- Format detection logic at lines 2040-2049 never executed for CLI exports

This changes the parsing flow to:
1. Try JSON.parse first (handles UI exports with {status, data} format)
2. On parse success, extract data field as before
3. On parse failure, treat entire file contents as raw base64 (CLI format)

This preserves the v4.26.4 improvements (12-char validation, better error
messages) while restoring CLI export compatibility.

Related to #646 where user confirmed v4.26.4 still failed to restore backups.
2025-11-07 17:57:22 +00:00
rcourtman
48fabdd827 Improve Docker temperature monitoring documentation for clarity (related to #600)
Updated the Quick Start for Docker section in TEMPERATURE_MONITORING.md to be
more user-friendly and address common setup issues:

- Added clear explanation of why the proxy is needed (containers can't access hardware)
- Provided concrete IP example instead of placeholder
- Showed full docker-compose.yml context with proper YAML structure
- Added sudo to commands where needed
- Updated docker-compose commands to v2 syntax with note about v1
- Expanded verification steps with clearer success indicators
- Added reminder to check container name in verification commands

These improvements should help users who encounter blank temperature displays
due to missing proxy installation or bind mount configuration.
2025-11-07 15:09:42 +00:00
rcourtman
32e0d453c4 Add Windows ARM64 support for host agent (related to #654)
Windows 11 25H2 ships exclusively on ARM64 hardware. When users on ARM64
attempt to install the host agent, the Service Control Manager fails to
load the amd64 binary with ERROR_BAD_EXE_FORMAT, surfaced as "The Pulse
Host Agent is not compatible with this Windows version".

Changes:
- Dockerfile: Build pulse-host-agent-windows-arm64.exe alongside amd64
- Dockerfile: Copy windows-arm64 binary and create symlink for download endpoint
- install-host-agent.ps1: Use RuntimeInformation.OSArchitecture to detect ARM64
- build-release.sh: Build darwin-amd64, darwin-arm64, windows-amd64, windows-arm64
- build-release.sh: Package Windows binaries as .zip archives
- validate-release.sh: Check for windows-arm64 binary and symlink
- validate-release.sh: Add architecture validation for all darwin/windows variants

The installer now correctly detects ARM64 and downloads the appropriate binary.
2025-11-07 12:18:57 +00:00
rcourtman
9257071ca1 Add encryption status to notification health endpoint (P2)
Backend:
- Add IsEncryptionEnabled() method to ConfigPersistence
- Include encryption status in /api/notifications/health response
- Allows frontend to warn when credentials are stored in plaintext

Frontend:
- Update NotificationHealth type to include encryption.enabled field
- Frontend can now display warnings when encryption is disabled

This addresses the P2 requirement for encryption visibility, allowing
operators to know when notification credentials are not encrypted at rest.
2025-11-07 08:36:55 +00:00
rcourtman
c6a69e525c Fix critical notification system bugs and security issues
Critical fixes (P0):
- Fix cooldown timing: Mark cooldown only after successful delivery, not before enqueue
- Add os.MkdirAll to queue initialization to prevent silent failures on fresh installs
- Add DNS re-validation at webhook send time to prevent DNS rebinding SSRF attacks
- Add SSRF validation for Apprise HTTP URLs
- Remove secret logging (bot tokens, routing keys) from debug logs
- Implement lastNotified cleanup to prevent unbounded memory growth
- Use shared HTTP client for webhooks to enable TLS connection reuse
- Add fallback to direct sending when queue enqueue fails
- Make queue worker concurrent (5 workers with semaphore) to prevent head-of-line blocking
- Fix webhook rate limiter race condition with separate mutex
- Fix email manager thread safety with mutex on rate limiter
- Fix grouping timer leak by adding stopCleanup signal
- Fix webhook 429 double sleep (use Retry-After OR backoff, not both)

Frontend improvements:
- Add queue/DLQ management API methods (getQueueStats, getDLQ, retryDLQItem, deleteDLQItem)
- Add getNotificationHealth and getWebhookHistory endpoints
- Add Apprise test support to NotificationTestRequest type

Related to notification system audit
2025-11-07 08:29:13 +00:00
rcourtman
9d2bad3af6 Add notification system documentation and fix tab panel corner radius
- Add NOTIFICATION_AUDIT.md for system analysis
- Add NOTIFICATION_QUICK_REFERENCE.md for quick lookup
- Add NOTIFICATION_SYSTEM_MAP.md for architecture overview
- Fix tab panel missing rounded-tl corner when first tab is active
2025-11-07 08:19:50 +00:00
rcourtman
5898cb81be Fix update modal hanging indefinitely after completion (related to #628)
When updates complete quickly, the status API may return 'completed' before
the frontend detects the 'restarting' phase. This left users staring at a
frozen modal with no feedback, requiring manual page refresh.

Changes:
- When status is 'completed', immediately check /api/health
- If backend is healthy, reload the page to get new version
- If health check fails, assume restart in progress and start health polling
- Ensures users always get reloaded to the new version automatically

This fixes the UX issue reported in discussion #628 where the update modal
appeared frozen indefinitely despite successful update completion.
2025-11-07 08:11:52 +00:00
rcourtman
4f9ba7a285 Allow layout to expand on wide displays (related to #643)
Changed .pulse-shell from fixed 95rem cap to fluid clamp(95rem, 92vw, 120rem)
to match standard monitoring dashboard behavior (Proxmox, Grafana, Portainer).

On laptops/small screens: unchanged (capped at 1520px)
On 1080p displays: expands to ~1766px usable width
On 4K/ultrawide: expands up to 1920px max for readability

Added back 2xl column widths (totaling ~1720px) that properly fit within
the expanded shell, giving wide-display users more breathing room while
maintaining proportional scaling across all breakpoints.

Changed files:
- index.css: Update .pulse-shell max-width to use clamp()
- Dashboard.tsx: Add 2xl column widths calculated for expanded shell
- GuestRow.tsx: Add matching 2xl column widths
2025-11-06 21:51:17 +00:00
rcourtman
68caf5592b Fix Proxmox dashboard table overflow on wide displays (related to #643)
Removed 2xl: width overrides that caused the table to exceed container width.
At ≥1536px viewport, the 2xl breakpoint expanded table columns to ~1528px
total width while .pulse-shell container provides only ~1416px usable space,
forcing Net In/Net Out columns off-screen and requiring horizontal scroll.

Table now caps at xl: breakpoint widths (~1266px) which fit comfortably within
the container at all viewport sizes. Net In/Net Out columns are now visible
without scrolling on 1080p, 4K, and all wide displays.

Changed files:
- Dashboard.tsx: Remove 2xl: width classes from all table header columns
- GuestRow.tsx: Remove 2xl: width classes from all table cell columns
2025-11-06 21:36:30 +00:00
rcourtman
2c3768341a Fix Docker host row dimming for degraded status
Docker hosts with 'degraded' status were incorrectly appearing dimmed
(opacity-60) in the summary table, making them visually identical to
offline hosts. This was confusing because degraded hosts are still
actively reporting - they just have unhealthy containers or >35% of
containers not running.

The isHostOnline function now treats 'degraded' as an online status,
so these rows maintain full opacity. The status badge already provides
visual indication of the degraded state.
2025-11-06 19:11:17 +00:00
rcourtman
7ed9203e4b Fix config backup/restore failures (related to #646)
Addresses two issues preventing configuration backup/restore:

1. Export passphrase validation mismatch: UI only validated 12+ char
   requirement when using custom passphrase, but backend always enforced
   it. Users with shorter login passwords saw unexplained failures.
   - Frontend now validates all passphrases meet 12-char minimum
   - Clear error message suggests custom passphrase if login password too short

2. Import data parsing failed silently: Frontend sent `exportData.data`
   which was undefined for legacy/CLI backups (raw base64 strings).
   Backend rejected these with no logs.
   - Frontend now handles both formats: {status, data} and raw strings
   - Backend logs validation failures for easier troubleshooting

Related to #646 where user reported "error after entering password" with
no container logs. These changes ensure proper validation feedback and
make the backup system resilient to different export formats.
2025-11-06 17:53:54 +00:00
rcourtman
47748230f4 Fix first-run setup 401 error by adding bootstrap token unlock screen (related to #639)
After the security hardening that introduced bootstrap token protection,
the first-run setup flow was broken because FirstRunSetup.tsx didn't
prompt users for the token. This caused a 401 "Bootstrap setup token
required" error during initial admin account creation.

Changes:
- Add dedicated unlock screen before the setup wizard
- Display instructions for retrieving token from host
- Include bootstrap token in quick-setup API request headers and body
- Only require unlock for first-run setup (skip in force mode)

The unlock screen follows the documented flow in README.md and ensures
only users with host access can configure an unconfigured instance.

Related to #639
2025-11-06 16:45:51 +00:00
rcourtman
40abcd1237 Fix empty space below backup chart by matching container and SVG heights
The chart container was set to min-h-[12rem] (192px) on desktop while the SVG
was hardcoded to 128px, creating 64px of unwanted empty space. Changed container
to fixed h-32 (128px) to match the SVG height.
2025-11-06 15:34:20 +00:00
rcourtman
a8fa834d24 Fix critical truncation bug preventing data readability on touch devices (related to #643)
Removed CSS truncate from key identifier columns (container names, service names,
guest names, host names, image names) that were making data inaccessible on mobile/
touch devices where title tooltips don't work.

Users can now read full identifiers via horizontal scroll (already implemented via
ScrollableTable component). Data should always be readable without requiring additional
UI affordances.

Changed files:
- DockerUnifiedTable: Remove truncate from container/service names and images
- GuestRow: Remove truncate from guest names
- HostsOverview: Remove truncate from host display names and hostnames

Column resizing remains on backlog as optional enhancement; users should not need
a drag handle just to read the contents.
2025-11-06 15:00:36 +00:00
rcourtman
20854256c3 Fix VM migration issue where custom alert thresholds are lost
Resolves #641

## Problem
When a VM migrates between Proxmox nodes, Pulse was treating it as a new
resource and discarding custom alert threshold overrides. This occurred
because guest IDs included the node name (e.g., `instance-node-VMID`),
causing the ID to change when the VM moved to a different node.

Users reported that after migrating a VM, previously disabled alerts
(e.g., memory threshold set to 0) would resume firing.

## Root Cause
Guest IDs were constructed as:
- Standalone: `node-VMID`
- Cluster: `instance-node-VMID`

When a VM migrated from node1 to node2, the ID changed from
`instance-node1-100` to `instance-node2-100`, causing:
- Alert threshold overrides to be orphaned (keyed by old ID)
- Guest metadata (custom URLs, descriptions) to be orphaned
- Active alerts to reference the wrong resource ID

## Solution
Changed guest ID format to be stable across node migrations:
- New format: `instance-VMID` (for both standalone and cluster)
- Retains uniqueness across instances while being node-independent
- Allows VMs to migrate freely without losing configuration

## Implementation

### Backend Changes
1. **Guest ID Construction** (`monitor_polling.go`):
   - Simplified to always use `instance-VMID` format
   - Removed node from the ID construction logic

2. **Alert Override Migration** (`alerts.go`):
   - Added lazy migration in `getGuestThresholds()`
   - Detects legacy ID formats and migrates to new format
   - Preserves user configurations automatically

3. **Guest Metadata Migration** (`guest_metadata.go`):
   - Added `GetWithLegacyMigration()` helper method
   - Called during VM/container polling to migrate metadata
   - Preserves custom URLs and descriptions

4. **Active Alerts Migration** (`alerts.go`):
   - Added migration logic in `LoadActiveAlerts()`
   - Translates legacy alert resource IDs to new format
   - Preserves alert acknowledgments across restarts

### Frontend Changes
5. **ID Construction Updates**:
   - `ThresholdsTable.tsx`: Updated fallback from `instance-node-vmid` to `instance-vmid`
   - `Dashboard.tsx`: Simplified guest ID construction
   - `GuestRow.tsx`: Updated `buildGuestId()` helper

## Migration Strategy
- **Lazy Migration**: Configs are migrated as guests are discovered
- **Backwards Compatible**: Old IDs are detected and automatically converted
- **Zero Downtime**: No manual intervention required
- **Persisted**: Migrated configs are saved on next config write cycle

## Testing Recommendations
After deployment:
1. Verify existing alert overrides still apply
2. Test VM migration - confirm thresholds persist
3. Check guest metadata (custom URLs) survive migration
4. Verify active alerts maintain acknowledgment state

## Related
- Addresses similar issues with guest metadata and active alert tracking
- Lays groundwork for any future guest-specific configuration features
- Aligns with project philosophy: correctness and UX over implementation complexity
2025-11-06 10:27:15 +00:00
rcourtman
d62259ffa7 Add AMD GPU temperature monitoring support
Related to #600

- Add GPU field to Temperature model with edge, junction, and mem sensors
- Add amdgpu chip recognition to temperature parser
- Implement parseGPUTemps() to extract AMD GPU temperature data
- Update frontend TypeScript types to include GPU temperatures
- Display GPU temps in node table tooltip alongside CPU temps
- Set hasGPU flag when GPU data is available

This enables temperature monitoring for AMD GPUs (amdgpu sensors)
that was previously being collected via SSH but silently discarded
during parsing.
2025-11-06 00:19:04 +00:00
rcourtman
88ad986877 Revert "Hide Settings tab when authentication is not configured"
This reverts commit d5a1e3d07729bad61743e8645a636e2545e11038.
2025-11-05 23:21:34 +00:00
rcourtman
7936808193 Add custom display name support for Docker hosts
This implements the ability for users to assign custom display names to Docker hosts,
similar to the existing functionality for Proxmox nodes. This addresses the issue where
multiple Docker hosts with identical hostnames but different IPs/domains cannot be
easily distinguished in the UI.

Backend changes:
- Add CustomDisplayName field to DockerHost model (internal/models/models.go:201)
- Update UpsertDockerHost to preserve custom display names across updates (internal/models/models.go:1110-1113)
- Add SetDockerHostCustomDisplayName method to State for updating names (internal/models/models.go:1221-1235)
- Add SetDockerHostCustomDisplayName method to Monitor (internal/monitoring/monitor.go:1070-1088)
- Add HandleSetCustomDisplayName API handler (internal/api/docker_agents.go:385-426)
- Route /api/agents/docker/hosts/{id}/display-name PUT requests (internal/api/docker_agents.go:117-120)

Frontend changes:
- Add customDisplayName field to DockerHost TypeScript interface (frontend-modern/src/types/api.ts:136)
- Add MonitoringAPI.setDockerHostDisplayName method (frontend-modern/src/api/monitoring.ts:151-187)
- Update getDisplayName function to prioritize custom names (frontend-modern/src/components/Settings/DockerAgents.tsx:84-89)
- Add inline editing UI with save/cancel buttons in Docker Agents settings (frontend-modern/src/components/Settings/DockerAgents.tsx:1349-1413)
- Update sorting to use custom display names (frontend-modern/src/components/Docker/DockerHosts.tsx:58-59)
- Update DockerHostSummaryTable to display custom names (frontend-modern/src/components/Docker/DockerHostSummaryTable.tsx:40-42, 87, 120, 254)

Users can now click the edit icon next to any Docker host name in Settings > Docker Agents
to set a custom display name. The custom name will be preserved across agent reconnections
and takes priority over the hostname reported by the agent.

Related to #623
2025-11-05 23:18:03 +00:00
rcourtman
3d1c910daa Hide Settings tab when authentication is not configured
Related to #636

When authentication is not configured (hasAuth() returns false), the
Settings tab is now automatically hidden from the web interface. This
provides a cleaner monitoring-only view for unauthenticated deployments
where users only need to check the health of their environment.

The Settings icon beside the Alerts tab will only appear when
authentication is properly configured via PULSE_AUTH_USER/PASS,
API tokens, proxy auth, or OIDC.

Changes:
- Modified utilityTabs in App.tsx to conditionally include Settings
  based on hasAuth() signal
- Updated CONFIGURATION.md to document this UI behavior
2025-11-05 23:10:20 +00:00
rcourtman
fd4182563e Fix custom backup polling interval selection not persisting
Addresses issue #567 where selecting "Custom interval..." from the
backup polling dropdown would revert to a preset option if the current
custom minutes value happened to match a predefined interval.

The bug occurred because:
1. User selects "Custom interval..." from dropdown
2. Code sets interval based on current customMinutes value
3. If that value matches a preset (e.g., 60 min = 1 hour), the
   computed select value returns the preset instead of 'custom'
4. Dropdown reverts, hiding the custom input field

Fix introduces a dedicated state variable (backupPollingUseCustom)
to explicitly track whether custom mode is active, independent of
whether the current interval value matches a preset option.

Changes:
- Add backupPollingUseCustom signal to track custom mode state
- Update backupIntervalSelectValue() to check custom flag first
- Set/clear custom flag in dropdown onChange handler
- Initialize custom flag when loading settings from API

Related to #567
2025-11-05 20:15:48 +00:00
rcourtman
e21a72578f Add configurable SSH port for temperature monitoring
Related to #595

This change adds support for custom SSH ports when collecting temperature
data from Proxmox nodes, resolving issues for users who run SSH on non-standard
ports.

**Why SSH is still needed:**
Temperature monitoring requires reading /sys/class/hwmon sensors on Proxmox
nodes, which is not exposed via the Proxmox API. Even when using API tokens
for authentication, Pulse needs SSH access to collect temperature data.

**Changes:**
- Add `sshPort` configuration to SystemSettings (system.json)
- Add `SSHPort` field to Config with environment variable support (SSH_PORT)
- Add per-node SSH port override capability for PVE, PBS, and PMG instances
- Update TemperatureCollector to accept and use custom SSH port
- Update SSH known_hosts manager to support non-standard ports
- Add NewTemperatureCollectorWithPort() constructor with port parameter
- Maintain backward compatibility with NewTemperatureCollector() (uses port 22)
- Update frontend TypeScript types for SSH port configuration

**Configuration methods:**
1. Environment variable: SSH_PORT=2222
2. system.json: {"sshPort": 2222}
3. Per-node override in nodes.enc (future UI support)

**Default behavior:**
- Defaults to port 22 if not configured
- Maintains full backward compatibility
- No changes required for existing deployments

The implementation includes proper ssh-keyscan port handling and known_hosts
management for non-standard ports using [host]:port notation per SSH standards.
2025-11-05 20:03:29 +00:00
rcourtman
b1488620b1 Fix Docker container prefix textarea not accepting newlines
Added explicit onKeyDown handler to stop event propagation when Enter
is pressed in the ignored container prefixes textarea. This ensures
newlines can be properly entered to separate multiple prefixes.

Related to #625
2025-11-05 19:37:16 +00:00
rcourtman
059e8bf562 Redirect to login when authentication expires
Related to #626

When authentication expires after some time, users see "Connection lost"
and must refresh the page to see "Authentication required". This commit
implements automatic redirect to login when authentication expires.

Changes:
- Add authentication check to WebSocket endpoint to prevent unauthenticated
  WebSocket connections
- Handle WebSocket close with code 1008 (policy violation) as auth failure
  and redirect to login
- Intercept 401 responses on API calls (except initial auth checks) and
  automatically redirect to login page
- Clear stored credentials and set logout flag before redirect to ensure
  clean login flow

This provides a better user experience by immediately redirecting to the
login page when the session expires, rather than showing a confusing
"Connection lost" message that requires manual page refresh.
2025-11-05 19:36:01 +00:00
rcourtman
404d461d35 Add helpful guidance for empty physical disk list
Improves user experience when physical disks don't appear by providing
clear, context-aware instructions. The empty state now shows:

- When no PVE nodes are configured: prompt to add nodes
- When nodes exist but no disks appear: step-by-step requirements
  including enabling SMART monitoring in both Pulse and Proxmox

This addresses confusion from issue #594 where users didn't realize
they needed to enable SMART monitoring in Proxmox itself (not just
in Pulse settings) and wait 5 minutes for data collection.

Related to #594
2025-11-05 19:25:59 +00:00
rcourtman
b1831d7b3e Add guest URL support for PVE hosts
Related to discussion #615

Add optional GuestURL field to PVE instances and cluster endpoints,
allowing users to specify a separate guest-accessible URL for web UI
navigation that differs from the internal management URL.

Backend changes:
- Add GuestURL field to PVEInstance and ClusterEndpoint structs
- Add GuestURL field to Node model
- Update cluster auto-discovery to preserve existing GuestURL values
- Update node creation logic to populate GuestURL from config
- Update API handlers to accept and persist GuestURL field

Frontend changes:
- Add GuestURL input field to NodeModal for configuration
- Update NodeGroupHeader and NodeSummaryTable to use GuestURL for navigation
- Add GuestURL to Node and PVENodeConfig TypeScript interfaces

When GuestURL is configured, it will be used for navigation links
instead of the Host URL, allowing users to access PVE hosts through
a reverse proxy or different domain while maintaining internal API
connections.
2025-11-05 19:06:08 +00:00
rcourtman
449d77504f Improve PMG connection testing to validate metrics endpoints
Related to #551

Enhanced the PMG connection test to actually validate the metrics
endpoints that Pulse uses for monitoring, rather than only checking
the version endpoint. This provides users with immediate feedback if
their PMG credentials lack the necessary permissions to collect metrics.

Backend changes:
- Test mail statistics, cluster status, and quarantine endpoints during
  connection test (internal/api/config_handlers.go:1695-1714)
- Return warnings array in test response when endpoints are unavailable
- Increased timeout from 10s to 15s to accommodate multiple endpoint checks
- Added warning logs for failed endpoint checks

Frontend changes:
- Added showWarning() toast function for warning messages
- Enhanced NodeModal to display warning status with amber styling
- Added warnings list display in test results UI
- Updated Settings.tsx to show warnings from connection tests

This change helps users identify permission issues immediately rather
than discovering later that metrics aren't being collected despite a
"successful" connection.
2025-11-05 18:40:39 +00:00
rcourtman
fdf0977be2 Add host agent multi-platform binary distribution and improve host details UI
- Build host agent binaries for all platforms (linux/darwin/windows, amd64/arm64/armv7) in Docker
- Add Makefile target for building agent binaries locally
- Add startup validation to check for missing agent binaries
- Improve download endpoint error messages with troubleshooting guidance
- Enhance host details drawer layout with better organization and visual hierarchy
- Update base images to rolling versions (node:20-alpine, golang:1.24-alpine, alpine:3.20)
2025-11-05 17:38:17 +00:00
rcourtman
5a328637af Improve drawer layout and UX for Docker and Proxmox pages
- Add flex-1 to all drawer cards for consistent space filling
- Implement single-drawer-open behavior (accordion-style)
- Add text truncation to labels and IP badges to prevent overflow
- Replace Map-based state with reactive signals for better performance
2025-11-05 14:46:13 +00:00
rcourtman
27f2038dab Add per-node temperature monitoring and fix critical config update bug
This commit implements per-node temperature monitoring control and fixes a critical
bug where partial node updates were destroying existing configuration.

Backend changes:
- Add TemperatureMonitoringEnabled field (*bool) to PVEInstance, PBSInstance, and PMGInstance
- Update monitor.go to check per-node temperature setting with global fallback
- Convert all NodeConfigRequest boolean fields to *bool pointers
- Add nil checks in HandleUpdateNode to prevent overwriting unmodified fields
- Fix critical bug where partial updates zeroed out MonitorVMs, MonitorContainers, etc.
- Update NodeResponse, NodeFrontend, and StateSnapshot to include temperature setting
- Fix HandleAddNode and test connection handlers to use pointer-based boolean fields

Frontend changes:
- Add temperatureMonitoringEnabled to Node interface and config types
- Create per-node temperature monitoring toggle handler with optimistic updates
- Update NodeModal to wire up per-node temperature toggle
- Add isTemperatureMonitoringEnabled helper to check effective monitoring state
- Update ConfiguredNodeTables to show/hide temperature badge based on monitoring state
- Update NodeSummaryTable to conditionally show temperature column
- Pass globalTemperatureMonitoringEnabled prop through component tree

The critical bug fix ensures that when updating a single field (like temperature
monitoring), the backend only modifies that specific field instead of zeroing out
all other boolean configuration fields.
2025-11-05 14:11:53 +00:00
rcourtman
d52ac6d8b5 Fix CSRF token validation and improve token management
- Add Access-Control-Expose-Headers to allow frontend to read X-CSRF-Token response header
- Implement proactive CSRF token issuance on GET requests when session exists but CSRF cookie is missing
- Ensures frontend always has valid CSRF token before making POST requests
- Fixes 403 Forbidden errors when toggling system settings

This resolves CSRF validation failures that occurred when CSRF tokens expired or were missing while valid sessions existed.
2025-11-05 09:23:44 +00:00
rcourtman
6eb1a10d9b Refactor: Code cleanup and localStorage consolidation
This commit includes comprehensive codebase cleanup and refactoring:

## Code Cleanup
- Remove dead TypeScript code (types/monitoring.ts - 194 lines duplicate)
- Remove unused Go functions (GetClusterNodes, MigratePassword, GetClusterHealthInfo)
- Clean up commented-out code blocks across multiple files
- Remove unused TypeScript exports (helpTextClass, private tag color helpers)
- Delete obsolete test files and components

## localStorage Consolidation
- Centralize all storage keys into STORAGE_KEYS constant
- Update 5 files to use centralized keys:
  * utils/apiClient.ts (AUTH, LEGACY_TOKEN)
  * components/Dashboard/Dashboard.tsx (GUEST_METADATA)
  * components/Docker/DockerHosts.tsx (DOCKER_METADATA)
  * App.tsx (PLATFORMS_SEEN)
  * stores/updates.ts (UPDATES)
- Benefits: Single source of truth, prevents typos, better maintainability

## Previous Work Committed
- Docker monitoring improvements and disk metrics
- Security enhancements and setup fixes
- API refactoring and cleanup
- Documentation updates
- Build system improvements

## Testing
- All frontend tests pass (29 tests)
- All Go tests pass (15 packages)
- Production build successful
- Zero breaking changes

Total: 186 files changed, 5825 insertions(+), 11602 deletions(-)
2025-11-04 21:50:46 +00:00
rcourtman
5c4be1921c chore: snapshot current changes 2025-11-02 22:47:55 +00:00
rcourtman
fb22469eb0 Add disk usage threshold support for Docker containers
Extends the Docker monitoring and alerting system to track writable layer
usage as a percentage of the container's root filesystem. This helps
identify containers with bloated copy-on-write layers before they
consume excessive disk space.

- Add disk threshold to DockerThresholdConfig (default: 85% trigger, 80% clear)
- Evaluate disk alerts for running containers when RootFilesystemBytes > 0
- Include disk metadata (writable layer, total filesystem, block I/O stats)
- Update frontend to display and configure disk thresholds
- Add test coverage for disk usage alert hysteresis
- Document disk monitoring in DOCKER_MONITORING.md

Per-container and per-host overrides apply to disk thresholds the same
way they do for CPU and memory.
2025-10-29 14:52:25 +00:00
rcourtman
730c6bf864 Fix Docker agent removal and improve security
This commit addresses multiple issues in the Docker/host agent removal flow:

Agent Stop Fix:
- Add systemctl stop command after agent acknowledgement to prevent systemd restart
- Previous behavior: agent disabled but systemd immediately restarted it (Restart=always)
- New behavior: agent disables itself, sends ack, then stops systemd service completely

UX Improvements:
- Add real-time elapsed time counter during removal wait
- Show progress indicators prominently (no longer hidden in dropdown)
- Display expected time range (30-60 seconds) and last heartbeat
- Auto-show timeout warning after 2 minutes with actionable "Force remove" button
- Add contextual help explaining what's happening at each stage

Security Enhancement:
- Automatically revoke API tokens when removing Docker/host agents
- Previous behavior: tokens remained valid after agent removal
- New behavior: tokens are revoked and persisted immediately on removal
- Prevents removed agents from re-authenticating with old credentials
2025-10-29 12:27:36 +00:00
rcourtman
32392d1212 Add disk metrics, block I/O, and mount details to Docker monitoring
Extends Docker container monitoring with comprehensive disk and storage information:
- Writable layer size and root filesystem usage displayed in new Disk column
- Block I/O statistics (read/write bytes totals) shown in container drawer
- Mount metadata including type, source, destination, mode, and driver details
- Configurable via --collect-disk flag (enabled by default, can be disabled for large fleets)

Also fixes config watcher to consistently use production auth config path instead of following PULSE_DATA_DIR when in mock mode.
2025-10-29 12:05:36 +00:00
rcourtman
35ab52b75b Align Docker drawer cards with Proxmox layout 2025-10-29 11:00:04 +00:00
rcourtman
b606a0db99 Adjust Docker host badges and task type column 2025-10-29 10:53:17 +00:00
rcourtman
93faaacbd1 Simplify metric bar labels 2025-10-29 10:37:18 +00:00
rcourtman
7ee417a629 Refine grouped guest indentation 2025-10-29 08:43:02 +00:00
rcourtman
b3285c05c8 Consolidate pending changes
- Add Docker metadata test comment
- Update alerts configuration and thresholds
- Enhance config file watcher
- Update documentation
- Refine settings UI
2025-10-28 23:20:44 +00:00
rcourtman
bd4f12c98f Fix Docker URL link icon to appear immediately after save
Optimistically update local and parent state before the API call completes, so the external link icon appears instantly when the user saves a URL. If the API fails, the state reverts automatically.

Before: Link icon appeared after API response (~100-500ms delay)
After: Link icon appears immediately with smooth fade-in animation
2025-10-28 23:14:38 +00:00
rcourtman
0d2bd7a304 Add custom URL editing to Docker containers and services
Implements clickable name field with inline URL editor for Docker resources, matching the Proxmox guest URL feature:

- Create DockerMetadataAPI for storing custom URLs
- Add metadata loading and caching in DockerHosts component
- Add URL editing UI to DockerContainerRow and DockerServiceRow
- Global editing state prevents multiple simultaneous edits
- Shows external link icon when URL is set
- Supports Enter to save, Escape to cancel
- Toast notifications for save/delete operations
- Stores metadata with format: {hostId}:container:{containerId}

Allows users to add quick links to container/service dashboards (e.g., Portainer, Traefik, internal UIs).
2025-10-28 22:50:31 +00:00
rcourtman
28277757ab Align Docker table row heights with Proxmox table
Change vertical padding from py-1 to py-0.5 on all data cells to match Proxmox guest table row heights for visual consistency across both monitoring interfaces.
2025-10-28 22:41:30 +00:00
rcourtman
33331d34f6 Remove status dots from Docker table to match Proxmox UI
Remove redundant colored status dots from Docker container and service rows to align with Proxmox guest table design. Status is already clearly indicated by the Status column badge.
2025-10-28 22:39:31 +00:00
rcourtman
6462ec6ba1 Apply offline dimming to Docker containers and services
Match Proxmox behavior by dimming stopped containers and degraded services with opacity-60, and showing dashes for metrics (CPU, memory, uptime, restarts) when containers are not running.
2025-10-28 22:37:49 +00:00
rcourtman
0f43c33bb7 Fix Docker table Type column width
The Type column was too narrow at 8%, causing Service and Container badges to be truncated. Increased to 11% and redistributed space from other columns to maintain 100% total width.
2025-10-28 22:31:31 +00:00