From 26a6534ae4bd99bd4392e3cacd503a8aa5ca8180 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Mon, 28 Jul 2025 12:18:19 +0100 Subject: [PATCH] remove file endpoint, show delta cahgned to tracking instead --- .../api/AdminSettingsController.java | 188 ++++-------------- 1 file changed, 41 insertions(+), 147 deletions(-) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AdminSettingsController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AdminSettingsController.java index 452323526..8fc684bbe 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AdminSettingsController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AdminSettingsController.java @@ -1,11 +1,9 @@ package stirling.software.proprietary.security.controller.api; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import org.springframework.http.HttpStatus; @@ -17,10 +15,10 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.util.HtmlUtils; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -32,7 +30,6 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.common.model.ApplicationProperties; import stirling.software.common.util.GeneralUtils; import stirling.software.proprietary.security.model.api.admin.SettingValueResponse; @@ -49,11 +46,14 @@ public class AdminSettingsController { private final ApplicationProperties applicationProperties; private final ObjectMapper objectMapper; + + // Track settings that have been modified but not yet applied (require restart) + private static final ConcurrentHashMap pendingChanges = new ConcurrentHashMap<>(); @GetMapping @Operation( summary = "Get all application settings", - description = "Retrieve all current application settings. Admin access required.") + description = "Retrieve all current application settings. Use includePending=true to include settings that will take effect after restart. Admin access required.") @ApiResponses( value = { @ApiResponse(responseCode = "200", description = "Settings retrieved successfully"), @@ -61,116 +61,44 @@ public class AdminSettingsController { responseCode = "403", description = "Access denied - Admin role required") }) - public ResponseEntity getSettings() { - log.debug("Admin requested all application settings"); - return ResponseEntity.ok(applicationProperties); - } - - @GetMapping("/file") - @Operation( - summary = "Get settings file as JSON", - description = - "Retrieve the settings.yml file parsed as JSON, showing the latest saved values (after restart). Comments and formatting are ignored. Admin access required.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "Settings file retrieved and parsed successfully"), - @ApiResponse(responseCode = "404", description = "Settings file not found"), - @ApiResponse( - responseCode = "403", - description = "Access denied - Admin role required"), - @ApiResponse( - responseCode = "500", - description = "Failed to read or parse settings file") - }) - public ResponseEntity getSettingsFile() { - try { - Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath()); - if (!Files.exists(settingsPath)) { - return ResponseEntity.notFound().build(); - } - - // Parse YAML file to JSON - ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); - ObjectMapper jsonMapper = new ObjectMapper(); - - Object yamlData = yamlMapper.readValue(settingsPath.toFile(), Object.class); - - log.debug("Admin requested settings file as JSON"); - return ResponseEntity.ok(yamlData); - - } catch (IOException e) { - log.error("Failed to read or parse settings file: {}", e.getMessage(), e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Failed to read or parse settings file: " + e.getMessage()); - } catch (Exception e) { - log.error("Unexpected error reading settings file: {}", e.getMessage(), e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Unexpected error reading settings file"); + public ResponseEntity getSettings(@RequestParam(defaultValue = "false") boolean includePending) { + log.debug("Admin requested all application settings (includePending={})", includePending); + + if (!includePending) { + return ResponseEntity.ok(applicationProperties); } + + // Include pending changes in response + Map response = new HashMap<>(); + response.put("currentSettings", applicationProperties); + response.put("pendingChanges", pendingChanges); + response.put("hasPendingChanges", !pendingChanges.isEmpty()); + + return ResponseEntity.ok(response); } @GetMapping("/delta") @Operation( - summary = "Get settings delta (pending changes)", + summary = "Get pending settings changes", description = - "Compare current runtime settings with saved file settings to show pending changes that will take effect after restart. Admin access required.") + "Retrieve settings that have been modified but not yet applied (require restart). Admin access required.") @ApiResponses( value = { @ApiResponse( responseCode = "200", - description = "Settings delta retrieved successfully"), - @ApiResponse(responseCode = "404", description = "Settings file not found"), + description = "Pending changes retrieved successfully"), @ApiResponse( responseCode = "403", - description = "Access denied - Admin role required"), - @ApiResponse( - responseCode = "500", - description = "Failed to calculate settings delta") + description = "Access denied - Admin role required") }) public ResponseEntity getSettingsDelta() { - try { - Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath()); - if (!Files.exists(settingsPath)) { - return ResponseEntity.notFound().build(); - } - - // Get current runtime settings as JSON - Map runtimeSettings = - objectMapper.convertValue(applicationProperties, Map.class); - - // Parse YAML file to get saved settings - ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); - Object fileData = yamlMapper.readValue(settingsPath.toFile(), Object.class); - Map savedSettings = objectMapper.convertValue(fileData, Map.class); - - // Calculate differences - Map delta = new HashMap<>(); - Map pendingChanges = new HashMap<>(); - Map currentValues = new HashMap<>(); - - findDifferences("", runtimeSettings, savedSettings, pendingChanges, currentValues); - - delta.put( - "pendingChanges", pendingChanges); // Values that will take effect after restart - delta.put("currentValues", currentValues); // Current runtime values - delta.put("hasPendingChanges", !pendingChanges.isEmpty()); - - log.debug( - "Admin requested settings delta - found {} pending changes", - pendingChanges.size()); - return ResponseEntity.ok(delta); - - } catch (IOException e) { - log.error("Failed to calculate settings delta: {}", e.getMessage(), e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Failed to calculate settings delta: " + e.getMessage()); - } catch (Exception e) { - log.error("Unexpected error calculating settings delta: {}", e.getMessage(), e); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Unexpected error calculating settings delta"); - } + Map response = new HashMap<>(); + response.put("pendingChanges", pendingChanges); + response.put("hasPendingChanges", !pendingChanges.isEmpty()); + response.put("count", pendingChanges.size()); + + log.debug("Admin requested pending changes - found {} settings", pendingChanges.size()); + return ResponseEntity.ok(response); } @PutMapping @@ -209,6 +137,10 @@ public class AdminSettingsController { log.info("Admin updating setting: {} = {}", key, value); GeneralUtils.saveKeyToSettings(key, value); + + // Track this as a pending change + pendingChanges.put(key, value); + updatedCount++; } @@ -314,6 +246,10 @@ public class AdminSettingsController { log.info("Admin updating section setting: {} = {}", fullKey, value); GeneralUtils.saveKeyToSettings(fullKey, value); + + // Track this as a pending change + pendingChanges.put(fullKey, value); + updatedCount++; } @@ -401,6 +337,9 @@ public class AdminSettingsController { Object value = request.getValue(); log.info("Admin updating single setting: {} = {}", key, value); GeneralUtils.saveKeyToSettings(key, value); + + // Track this as a pending change + pendingChanges.put(key, value); String escapedKey = HtmlUtils.htmlEscape(key); return ResponseEntity.ok( @@ -557,49 +496,4 @@ public class AdminSettingsController { } } - /** Recursively compare two maps to find differences between runtime and saved settings */ - @SuppressWarnings("unchecked") - private void findDifferences( - String keyPrefix, - Map runtime, - Map saved, - Map pendingChanges, - Map currentValues) { - - // Check all keys in saved settings (these are the pending changes) - for (Map.Entry entry : saved.entrySet()) { - String key = entry.getKey(); - String fullKey = keyPrefix.isEmpty() ? key : keyPrefix + "." + key; - Object savedValue = entry.getValue(); - Object runtimeValue = runtime.get(key); - - if (savedValue instanceof Map && runtimeValue instanceof Map) { - // Recursively check nested objects - findDifferences( - fullKey, - (Map) runtimeValue, - (Map) savedValue, - pendingChanges, - currentValues); - } else { - // Compare values - if they're different, savedValue is pending - if (!java.util.Objects.equals(runtimeValue, savedValue)) { - pendingChanges.put(fullKey, savedValue); - currentValues.put(fullKey, runtimeValue); - } - } - } - - // Check for keys that exist in runtime but not in saved (these would be removed on restart) - for (Map.Entry entry : runtime.entrySet()) { - String key = entry.getKey(); - String fullKey = keyPrefix.isEmpty() ? key : keyPrefix + "." + key; - - if (!saved.containsKey(key)) { - // This runtime setting would be lost on restart - pendingChanges.put(fullKey + " (will be removed)", null); - currentValues.put(fullKey, entry.getValue()); - } - } - } }