settings menu reworks (#5864)

This commit is contained in:
Anthony Stirling
2026-03-05 16:20:20 +00:00
committed by GitHub
parent ba2d10a75b
commit 0f7ee5c5b0
18 changed files with 640 additions and 580 deletions

View File

@@ -873,6 +873,36 @@ public class GeneralUtils {
settingsYaml.saveOverride(settingsPath);
}
/**
* Updates multiple settings in a single transaction. This ensures that nested settings (e.g.,
* oauth2.client.google.*) don't lose sibling values when partial updates are made.
*
* <p>Instead of multiple read-update-write cycles (which could cause race conditions), this
* method loads the YAML once, applies all updates, and saves once.
*
* @param settingsMap Map of dotted-notation keys to values to update
* @throws IOException if file read/write fails
*/
public void updateSettingsTransactional(Map<String, Object> settingsMap) throws IOException {
if (settingsMap == null || settingsMap.isEmpty()) {
return;
}
Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());
YamlHelper settingsYaml = new YamlHelper(settingsPath);
// Apply all updates to the same YamlHelper instance
for (Map.Entry<String, Object> entry : settingsMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
String[] keyArray = key.split("\\.");
settingsYaml.updateValue(Arrays.asList(keyArray), value);
}
// Save only once after all updates are applied
settingsYaml.saveOverride(settingsPath);
}
/*
* Machine fingerprint generation with better error logging and fallbacks.
*

View File

@@ -172,7 +172,7 @@ public class AdminSettingsController {
.body(Map.of("error", "No settings provided to update"));
}
int updatedCount = 0;
// Validate all settings first before applying any changes
for (Map.Entry<String, Object> entry : settings.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
@@ -192,15 +192,18 @@ public class AdminSettingsController {
return ResponseEntity.badRequest()
.body(Map.of("error", HtmlUtils.htmlEscape(validationError)));
}
}
// Apply all updates in a single transaction (load once, update all, save once)
// This ensures nested settings like oauth2.client.* don't lose sibling values
GeneralUtils.updateSettingsTransactional(settings);
// Track all as pending changes
for (Map.Entry<String, Object> entry : settings.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
log.info("Admin updating setting: {} = {}", key, value);
GeneralUtils.saveKeyToSettings(key, value);
// Track this as a pending change (convert null to empty string for
// ConcurrentHashMap)
pendingChanges.put(key, value != null ? value : "");
updatedCount++;
}
return ResponseEntity.ok(
@@ -209,7 +212,7 @@ public class AdminSettingsController {
String.format(
"Successfully updated %d setting(s). Changes will take effect on"
+ " application restart.",
updatedCount)));
settings.size())));
} catch (IOException e) {
log.error("Failed to save settings to file: {}", e.getMessage(), e);