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
This commit is contained in:
Claude
2026-03-13 17:57:06 +00:00
parent 4b2e9e1660
commit f966724373
3 changed files with 18 additions and 4 deletions
@@ -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.
/// </summary>
public async Task<bool> 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);
}
@@ -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
};
}
}
@@ -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(