From f96672437320b263078cbecfae5305df71aededf Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Mar 2026 17:57:06 +0000 Subject: [PATCH] Fix tenant config saving and gather rules editing 1. UnrestrictedMode was not persisted to Azure Table Storage because it was missing from both ConvertToTableEntity and ConvertFromTableEntity mappings in TenantConfigurationService. 2. GatherRule toggle/edit destroyed rule data: the toggle sent only { enabled: bool } which deserialized with IsBuiltIn=false, causing the backend to store a near-empty custom rule instead of updating the rule state. Fixed by checking global rules for type determination in UpdateRuleAsync (backend) and including isBuiltIn/isCommunity in the toggle payload (frontend). 3. GatherRule edit payload was missing enabled/author/createdAt, causing edits to reset the enabled state to false and lose metadata. https://claude.ai/code/session_01Xcix9bwMXijqN7xa57Vko6 --- .../Services/GatherRuleService.cs | 11 ++++++++++- .../Services/TenantConfigurationService.cs | 6 ++++-- .../autopilot-monitor-web/app/gather-rules/page.tsx | 5 ++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Backend/AutopilotMonitor.Functions/Services/GatherRuleService.cs b/src/Backend/AutopilotMonitor.Functions/Services/GatherRuleService.cs index d5f1b8a4..3d4560f5 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/GatherRuleService.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/GatherRuleService.cs @@ -81,11 +81,20 @@ namespace AutopilotMonitor.Functions.Services /// Updates a gather rule. /// For built-in/community rules: only the enabled/disabled state is stored (per tenant). /// For custom rules: the full rule is updated in the tenant partition. + /// The rule type is determined from existing data (not the incoming payload) to prevent + /// partial payloads (e.g. toggle requests with only { enabled: bool }) from being + /// misclassified and overwriting rule data. /// public async Task UpdateRuleAsync(string tenantId, GatherRule rule) { - if (rule.IsBuiltIn || rule.IsCommunity) + // Always check global rules for type determination — the incoming payload + // may not include isBuiltIn/isCommunity (e.g. toggle requests). + var globalRules = await _storageService.GetGatherRulesAsync("global"); + var globalRule = globalRules.FirstOrDefault(r => r.RuleId == rule.RuleId); + + if (globalRule != null && (globalRule.IsBuiltIn || globalRule.IsCommunity)) { + // Built-in/community rule: only persist enabled state per tenant return await _storageService.StoreRuleStateAsync(tenantId, rule.RuleId, rule.Enabled); } diff --git a/src/Backend/AutopilotMonitor.Functions/Services/TenantConfigurationService.cs b/src/Backend/AutopilotMonitor.Functions/Services/TenantConfigurationService.cs index b51e5701..136d2dc4 100644 --- a/src/Backend/AutopilotMonitor.Functions/Services/TenantConfigurationService.cs +++ b/src/Backend/AutopilotMonitor.Functions/Services/TenantConfigurationService.cs @@ -239,7 +239,8 @@ namespace AutopilotMonitor.Functions.Services { "SendTraceEvents", config.SendTraceEvents }, { "EnableLocalAdminAnalyzer", config.EnableLocalAdminAnalyzer }, { "LocalAdminAllowedAccountsJson", config.LocalAdminAllowedAccountsJson }, - { "BootstrapTokenEnabled", config.BootstrapTokenEnabled } + { "BootstrapTokenEnabled", config.BootstrapTokenEnabled }, + { "UnrestrictedMode", config.UnrestrictedMode } }; return entity; @@ -297,7 +298,8 @@ namespace AutopilotMonitor.Functions.Services SendTraceEvents = entity.GetBoolean("SendTraceEvents") ?? true, EnableLocalAdminAnalyzer = entity.GetBoolean("EnableLocalAdminAnalyzer"), LocalAdminAllowedAccountsJson = entity.GetString("LocalAdminAllowedAccountsJson"), - BootstrapTokenEnabled = entity.GetBoolean("BootstrapTokenEnabled") ?? false + BootstrapTokenEnabled = entity.GetBoolean("BootstrapTokenEnabled") ?? false, + UnrestrictedMode = entity.GetBoolean("UnrestrictedMode") ?? false }; } } diff --git a/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx b/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx index a7858c70..954b0263 100644 --- a/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx +++ b/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx @@ -136,7 +136,7 @@ export default function GatherRulesPage() { { method: "PUT", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ enabled: !rule.enabled }), + body: JSON.stringify({ enabled: !rule.enabled, isBuiltIn: rule.isBuiltIn, isCommunity: rule.isCommunity }), } ); if (result !== null) { @@ -295,6 +295,9 @@ export default function GatherRulesPage() { outputEventType: form.outputEventType, outputSeverity: form.outputSeverity, version: bumpVersion(rule.version), + enabled: rule.enabled, + author: rule.author, + createdAt: rule.createdAt, }; const result = await mutate(