fix: AI settings persistence and UI improvements

Bug Fixes:
- Fix boolean fields with 'omitempty' not persisting false values
  - AlertTriggeredAnalysis, PatrolAnalyzeNodes/Guests/Docker/Storage
  - omitempty causes Go to skip false (zero value) when marshaling JSON
  - On reload, NewDefaultAIConfig() sets true, and missing field stays true

- Fix model dropdown losing selection after save (SolidJS reactivity issue)
  - Added explicit 'selected' attribute to option elements
  - Ensures browser maintains selection with optgroups during re-renders

Improvements:
- Change patrol type label from 'Quick' to 'Patrol' in history table
- Add chat_model and patrol_model to AI settings update log
- Add alert_triggered_analysis to AI config load log for debugging
This commit is contained in:
rcourtman 2025-12-21 21:48:09 +00:00
parent 4d7d2e42dc
commit 0ce6bda33b
5 changed files with 11 additions and 9 deletions

View file

@ -653,7 +653,7 @@ export const AISettings: Component = () => {
<optgroup label={PROVIDER_DISPLAY_NAMES[provider] || provider}> <optgroup label={PROVIDER_DISPLAY_NAMES[provider] || provider}>
<For each={models}> <For each={models}>
{(model) => ( {(model) => (
<option value={model.id}> <option value={model.id} selected={model.id === form.model}>
{model.name || model.id.split(':').pop()} {model.name || model.id.split(':').pop()}
</option> </option>
)} )}
@ -667,7 +667,7 @@ export const AISettings: Component = () => {
<optgroup label={`⚠️ ${PROVIDER_DISPLAY_NAMES[provider] || provider} (not configured)`}> <optgroup label={`⚠️ ${PROVIDER_DISPLAY_NAMES[provider] || provider} (not configured)`}>
<For each={models}> <For each={models}>
{(model) => ( {(model) => (
<option value={model.id} class="text-gray-400"> <option value={model.id} selected={model.id === form.model} class="text-gray-400">
{model.name || model.id.split(':').pop()} {model.name || model.id.split(':').pop()}
</option> </option>
)} )}

View file

@ -3490,7 +3490,7 @@ function OverviewTab(props: {
? 'bg-violet-100 dark:bg-violet-900/50 text-violet-700 dark:text-violet-300' ? 'bg-violet-100 dark:bg-violet-900/50 text-violet-700 dark:text-violet-300'
: 'bg-sky-100 dark:bg-sky-900/50 text-sky-700 dark:text-sky-300' : 'bg-sky-100 dark:bg-sky-900/50 text-sky-700 dark:text-sky-300'
}`}> }`}>
{run.type === 'deep' ? 'Deep' : 'Quick'} {run.type === 'deep' ? 'Deep' : 'Patrol'}
</span> </span>
</td> </td>
<td class="p-1.5 px-2 text-center"> <td class="p-1.5 px-2 text-center">

View file

@ -489,6 +489,8 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
Bool("enabled", settings.Enabled). Bool("enabled", settings.Enabled).
Str("provider", settings.Provider). Str("provider", settings.Provider).
Str("model", settings.GetModel()). Str("model", settings.GetModel()).
Str("chatModel", settings.ChatModel).
Str("patrolModel", settings.PatrolModel).
Str("patrolPreset", settings.PatrolSchedulePreset). Str("patrolPreset", settings.PatrolSchedulePreset).
Bool("alertTriggeredAnalysis", settings.AlertTriggeredAnalysis). Bool("alertTriggeredAnalysis", settings.AlertTriggeredAnalysis).
Msg("AI settings updated") Msg("AI settings updated")

View file

@ -43,15 +43,15 @@ type AIConfig struct {
PatrolEnabled bool `json:"patrol_enabled"` // Enable background AI health patrol PatrolEnabled bool `json:"patrol_enabled"` // Enable background AI health patrol
PatrolIntervalMinutes int `json:"patrol_interval_minutes,omitempty"` // How often to run quick patrols (default: 360 = 6 hours) PatrolIntervalMinutes int `json:"patrol_interval_minutes,omitempty"` // How often to run quick patrols (default: 360 = 6 hours)
PatrolSchedulePreset string `json:"patrol_schedule_preset,omitempty"` // User-friendly preset: "15min", "1hr", "6hr", "12hr", "daily", "disabled" PatrolSchedulePreset string `json:"patrol_schedule_preset,omitempty"` // User-friendly preset: "15min", "1hr", "6hr", "12hr", "daily", "disabled"
PatrolAnalyzeNodes bool `json:"patrol_analyze_nodes,omitempty"` // Include Proxmox nodes in patrol PatrolAnalyzeNodes bool `json:"patrol_analyze_nodes"` // Include Proxmox nodes in patrol
PatrolAnalyzeGuests bool `json:"patrol_analyze_guests,omitempty"` // Include VMs/containers in patrol PatrolAnalyzeGuests bool `json:"patrol_analyze_guests"` // Include VMs/containers in patrol
PatrolAnalyzeDocker bool `json:"patrol_analyze_docker,omitempty"` // Include Docker hosts in patrol PatrolAnalyzeDocker bool `json:"patrol_analyze_docker"` // Include Docker hosts in patrol
PatrolAnalyzeStorage bool `json:"patrol_analyze_storage,omitempty"` // Include storage in patrol PatrolAnalyzeStorage bool `json:"patrol_analyze_storage"` // Include storage in patrol
PatrolAutoFix bool `json:"patrol_auto_fix,omitempty"` // When true, patrol can attempt automatic remediation (default: false, observe only) PatrolAutoFix bool `json:"patrol_auto_fix,omitempty"` // When true, patrol can attempt automatic remediation (default: false, observe only)
AutoFixModel string `json:"auto_fix_model,omitempty"` // Model for automatic remediation (defaults to PatrolModel, may want more capable model) AutoFixModel string `json:"auto_fix_model,omitempty"` // Model for automatic remediation (defaults to PatrolModel, may want more capable model)
// Alert-triggered AI analysis - analyze specific resources when alerts fire // Alert-triggered AI analysis - analyze specific resources when alerts fire
AlertTriggeredAnalysis bool `json:"alert_triggered_analysis,omitempty"` // Enable AI analysis when alerts fire (token-efficient) AlertTriggeredAnalysis bool `json:"alert_triggered_analysis"` // Enable AI analysis when alerts fire (token-efficient)
// AI cost controls // AI cost controls
// Budget is expressed as an estimated USD amount over a 30-day window (pro-rated in UI for other ranges). // Budget is expressed as an estimated USD amount over a 30-day window (pro-rated in UI for other ranges).

View file

@ -1452,7 +1452,7 @@ func (c *ConfigPersistence) LoadAIConfig() (*AIConfig, error) {
settings.PatrolIntervalMinutes = 15 settings.PatrolIntervalMinutes = 15
} }
log.Info().Str("file", c.aiFile).Bool("enabled", settings.Enabled).Bool("patrol_enabled", settings.PatrolEnabled).Msg("AI configuration loaded") log.Info().Str("file", c.aiFile).Bool("enabled", settings.Enabled).Bool("patrol_enabled", settings.PatrolEnabled).Bool("alert_triggered_analysis", settings.AlertTriggeredAnalysis).Msg("AI configuration loaded")
return settings, nil return settings, nil
} }