diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java index e17f0b80d99058a4917a541acb32863e2b39e329..d2093d59554e9fd74535bc6bf309c6f5cd28238f 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java @@ -12,6 +12,7 @@ import de.deadlocker8.budgetmaster.database.InternalDatabase; import de.deadlocker8.budgetmaster.database.model.BackupDatabase; import de.deadlocker8.budgetmaster.services.ImportResultItem; import de.deadlocker8.budgetmaster.services.ImportService; +import de.deadlocker8.budgetmaster.settings.containers.SecuritySettingsContainer; import de.deadlocker8.budgetmaster.update.BudgetMasterUpdateService; import de.deadlocker8.budgetmaster.utils.LanguageType; import de.deadlocker8.budgetmaster.utils.Mappings; @@ -36,6 +37,7 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; @@ -54,7 +56,6 @@ public class SettingsController extends BaseController public static final String VERIFICATION_CODE = "verificationCode"; public static final String IMPORT_DATABASE = "importDatabase"; public static final String ERROR_IMPORT_DATABASE = "errorImportDatabase"; - public static final String AVAILABLE_ACCOUNTS = "availableAccounts"; public static final String IMPORT_RESULT_ITEMS = "importResultItems"; public static final String ERROR_MESSAGES = "errorMessages"; public static final String PERFORM_UPDATE = "performUpdate"; @@ -65,6 +66,7 @@ public class SettingsController extends BaseController public static final String AUTO_BACKUP_TIME = "autoBackupTimes"; public static final String AUTO_BACKUP_STATUS = "autoBackupStatus"; public static final String NEXT_BACKUP_TIME = "nextBackupTime"; + public static final String TOAST_CONTENT = "toastContent"; } private static class ReturnValues @@ -76,6 +78,7 @@ public class SettingsController extends BaseController public static final String REDIRECT_IMPORT_DATABASE_STEP_1 = "redirect:/settings/database/import/step1"; public static final String IMPORT_DATABASE_STEP_1 = "settings/importStepOne"; public static final String IMPORT_DATABASE_RESULT = "settings/importResult"; + public static final String CONTAINER_SECURITY = "settings/containers/settingsSecurity"; } private static class RequestAttributeNames @@ -97,7 +100,7 @@ public class SettingsController extends BaseController private final List<Integer> SEARCH_RESULTS_PER_PAGE_OPTIONS = Arrays.asList(10, 20, 25, 30, 50, 100); @Autowired - public SettingsController(SettingsService settingsService, DatabaseService databaseService, AccountService accountService, CategoryService categoryService, ImportService importService, BudgetMasterUpdateService budgetMasterUpdateService, BackupService backupService) + public SettingsController(SettingsService settingsService, DatabaseService databaseService, CategoryService categoryService, ImportService importService, BudgetMasterUpdateService budgetMasterUpdateService, BackupService backupService) { this.settingsService = settingsService; this.databaseService = databaseService; @@ -119,11 +122,55 @@ public class SettingsController extends BaseController return ReturnValues.ALL_ENTITIES; } + @PostMapping(value = "/save/security") + public String saveContainerSecurity(Model model, + @ModelAttribute("SecuritySettingsContainer") SecuritySettingsContainer securitySettingsContainer, + BindingResult bindingResult) + { + Optional<FieldError> passwordErrorOptional = securitySettingsContainer.validate(); + passwordErrorOptional.ifPresent(bindingResult::addError); + + String toastMessage; + String toastClasses; + + if(bindingResult.hasErrors()) + { + model.addAttribute(ModelAttributes.ERROR, bindingResult); + toastMessage = Localization.getString("notification.settings.security.error"); + toastClasses = getToastClasses(NotificationType.ERROR); + } + else + { + final String password = securitySettingsContainer.getPassword(); + if(password.equals(PASSWORD_PLACEHOLDER)) + { + toastMessage = Localization.getString("notification.settings.security.warning"); + toastClasses = getToastClasses(NotificationType.WARNING); + } + else + { + settingsService.savePassword(password); + toastMessage = Localization.getString("notification.settings.security.saved"); + toastClasses = getToastClasses(NotificationType.SUCCESS); + } + } + + final JsonObject toastContent = new JsonObject(); + toastContent.addProperty("localizedMessage", toastMessage); + toastContent.addProperty("classes", toastClasses); + model.addAttribute(ModelAttributes.TOAST_CONTENT, toastContent); + + return ReturnValues.CONTAINER_SECURITY; + } + + private String getToastClasses(NotificationType notificationType) + { + return MessageFormat.format("{0} {1}", notificationType.getBackgroundColor(), notificationType.getTextColor()); + } + @PostMapping(value = "/save") public String post(WebRequest request, Model model, @ModelAttribute("Settings") Settings settings, BindingResult bindingResult, - @RequestParam(value = "password") String password, - @RequestParam(value = "passwordConfirmation") String passwordConfirmation, @RequestParam(value = "languageType") String languageType, @RequestParam(value = "autoBackupStrategyType", required = false) String autoBackupStrategyType, @RequestParam(value = "runBackup", required = false) Boolean runBackup) @@ -138,9 +185,6 @@ public class SettingsController extends BaseController settings.setAutoBackupStrategy(AutoBackupStrategy.fromName(autoBackupStrategyType)); } - Optional<FieldError> passwordErrorOptional = settingsService.validatePassword(password, passwordConfirmation); - passwordErrorOptional.ifPresent(bindingResult::addError); - SettingsValidator settingsValidator = new SettingsValidator(); settingsValidator.validate(settings, bindingResult); @@ -153,11 +197,6 @@ public class SettingsController extends BaseController return ReturnValues.ALL_ENTITIES; } - if(!password.equals(PASSWORD_PLACEHOLDER)) - { - settingsService.savePassword(password); - } - updateSettings(settings); runBackup(request, runBackup); diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsService.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsService.java index a39f5f5d051b4476a21cd2c75c11acccb689a882..d895128552b57ee16e75e84b75182d7aaffde2a5 100644 --- a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsService.java +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsService.java @@ -158,30 +158,6 @@ public class SettingsService } } - public Optional<FieldError> validatePassword(String password, String passwordConfirmation) - { - if(password == null || password.equals("")) - { - return Optional.of(new FieldError("Settings", "password", password, false, new String[]{Strings.WARNING_SETTINGS_PASSWORD_EMPTY}, null, Strings.WARNING_SETTINGS_PASSWORD_EMPTY)); - } - else if(password.length() < 3) - { - return Optional.of(new FieldError("Settings", "password", password, false, new String[]{Strings.WARNING_SETTINGS_PASSWORD_LENGTH}, null, Strings.WARNING_SETTINGS_PASSWORD_LENGTH)); - } - - if(passwordConfirmation == null || passwordConfirmation.equals("")) - { - return Optional.of(new FieldError("Settings", "passwordConfirmation", passwordConfirmation, false, new String[]{Strings.WARNING_SETTINGS_PASSWORD_CONFIRMATION_EMPTY}, null, Strings.WARNING_SETTINGS_PASSWORD_CONFIRMATION_EMPTY)); - } - - if(!password.equals(passwordConfirmation)) - { - return Optional.of(new FieldError("Settings", "passwordConfirmation", passwordConfirmation, false, new String[]{Strings.WARNING_SETTINGS_PASSWORD_CONFIRMATION_WRONG}, null, Strings.WARNING_SETTINGS_PASSWORD_CONFIRMATION_WRONG)); - } - - return Optional.empty(); - } - public void savePassword(String password) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); diff --git a/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/SecuritySettingsContainer.java b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/SecuritySettingsContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..308fdbf81e2e66438ab08bd2111586b7bcb9ff8b --- /dev/null +++ b/BudgetMasterServer/src/main/java/de/deadlocker8/budgetmaster/settings/containers/SecuritySettingsContainer.java @@ -0,0 +1,52 @@ +package de.deadlocker8.budgetmaster.settings.containers; + +import de.deadlocker8.budgetmaster.utils.Strings; +import org.springframework.validation.FieldError; + +import java.util.Optional; + +public class SecuritySettingsContainer +{ + private final String password; + private final String passwordConfirmation; + + public SecuritySettingsContainer(String password, String passwordConfirmation) + { + this.password = password; + this.passwordConfirmation = passwordConfirmation; + } + + public String getPassword() + { + return password; + } + + public String getPasswordConfirmation() + { + return passwordConfirmation; + } + + public Optional<FieldError> validate() + { + if(password == null || password.equals("")) + { + return Optional.of(new FieldError("Settings", "password", password, false, new String[]{Strings.WARNING_SETTINGS_PASSWORD_EMPTY}, null, Strings.WARNING_SETTINGS_PASSWORD_EMPTY)); + } + else if(password.length() < 3) + { + return Optional.of(new FieldError("Settings", "password", password, false, new String[]{Strings.WARNING_SETTINGS_PASSWORD_LENGTH}, null, Strings.WARNING_SETTINGS_PASSWORD_LENGTH)); + } + + if(passwordConfirmation == null || passwordConfirmation.equals("")) + { + return Optional.of(new FieldError("Settings", "passwordConfirmation", passwordConfirmation, false, new String[]{Strings.WARNING_SETTINGS_PASSWORD_CONFIRMATION_EMPTY}, null, Strings.WARNING_SETTINGS_PASSWORD_CONFIRMATION_EMPTY)); + } + + if(!password.equals(passwordConfirmation)) + { + return Optional.of(new FieldError("Settings", "passwordConfirmation", passwordConfirmation, false, new String[]{Strings.WARNING_SETTINGS_PASSWORD_CONFIRMATION_WRONG}, null, Strings.WARNING_SETTINGS_PASSWORD_CONFIRMATION_WRONG)); + } + + return Optional.empty(); + } +} diff --git a/BudgetMasterServer/src/main/resources/languages/base_de.properties b/BudgetMasterServer/src/main/resources/languages/base_de.properties index 187c0a4a3c242265a3166b52f348910b6c248d26..974fd17c885bff100c90b135662e42d93f2c40dc 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_de.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_de.properties @@ -168,6 +168,9 @@ info.text.migration=BudgetMaster 2.10.0 führt ein neues Datenbank-Backend ein.< info.button.migration=Bestehende Datenbank migrieren info.button.migration.start=Datenbank migrieren notification.settings.saved=Einstellungen gespeichert +notification.settings.security.saved=Passwort gespeichert +notification.settings.security.warning=Passwort entspricht bereits gespeicherten Passwort +notification.settings.security.error=Fehler beim Speichern des Passworts notification.settings.update.available=BudgetMaster Update "{0}" verfügbar notification.settings.database.delete.success=Datenbank erfolgreich gelöscht notification.settings.backup.run.success=Backup erfolgreich @@ -307,7 +310,7 @@ settings.category.circle.style=Kategorien anzeigen als settings.category.circle.style.description=Legt die Darstellung von Kategorien in der Buchungsübersicht fest settings.category.circle.style.deactivated=Quadrate settings.category.circle.style.activated=Kreise -settings.appearance=Oberfläche +settings.appearance=Personalisierung settings.database.import=Importieren settings.database.export=Exportieren diff --git a/BudgetMasterServer/src/main/resources/languages/base_en.properties b/BudgetMasterServer/src/main/resources/languages/base_en.properties index 3fc91e778513db5583bec4c0de15b47208926028..aba7f6bf98b0c86bd28debec12ada7c4ee37d485 100644 --- a/BudgetMasterServer/src/main/resources/languages/base_en.properties +++ b/BudgetMasterServer/src/main/resources/languages/base_en.properties @@ -169,6 +169,9 @@ info.text.migration=BudgetMaster 2.10.0 introduces a new database backend.<br>Yo info.button.migration=Migrate existing database info.button.migration.start=Migrate database notification.settings.saved=Settings saved +notification.settings.security.saved=Password saved +notification.settings.security.warning=Password equals already saved password +notification.settings.security.error=Error saving password notification.settings.update.available=BudgetMaster update "{0}" available notification.settings.database.delete.success=Successfully deleted database notification.settings.backup.run.success=Backup successful @@ -308,7 +311,7 @@ settings.category.circle.style=Show categories as settings.category.circle.style.description=Defines how categories are displayed in the transaction overview settings.category.circle.style.deactivated=Squares settings.category.circle.style.activated=Circles -settings.appearance=Appearance +settings.appearance=Personalization settings.database.import=Import settings.database.export=Export diff --git a/BudgetMasterServer/src/main/resources/static/js/settings.js b/BudgetMasterServer/src/main/resources/static/js/settings.js index eff674f342b826094fddd98b0744051deffbfd2a..19e933ca7f0ad525e54c42077550ebb003651896 100644 --- a/BudgetMasterServer/src/main/resources/static/js/settings.js +++ b/BudgetMasterServer/src/main/resources/static/js/settings.js @@ -1,5 +1,7 @@ $(document).ready(function() { + $('.collapsible').collapsible(); + $('.modal').modal('open'); $('#button-confirm-database-delete').click(function() diff --git a/BudgetMasterServer/src/main/resources/static/js/settingsContainers/settingsSecurity.js b/BudgetMasterServer/src/main/resources/static/js/settingsContainers/settingsSecurity.js new file mode 100644 index 0000000000000000000000000000000000000000..8b248440ac47fd3ba9516527c5af206ce1d62392 --- /dev/null +++ b/BudgetMasterServer/src/main/resources/static/js/settingsContainers/settingsSecurity.js @@ -0,0 +1,37 @@ +$("[name='SecuritySettingsContainer']").submit(function(event) +{ + let form = document.getElementsByName('SecuritySettingsContainer')[0]; + + $.ajax({ + type: 'POST', + url: form.action.formAction, + data: new FormData(form), + processData: false, + contentType: false, + success: function(response) + { + $('#securitySettingsContainer').html(response); + $('.tooltipped').tooltip(); + + let toastContent = document.querySelector('#securitySettingsContainer .securityContainerToastContent').innerHTML.trim(); + if(toastContent) + { + let data = JSON.parse(toastContent); + M.toast({ + html: data['localizedMessage'], + classes: data['classes'] + }); + } + }, + error: function(response) + { + M.toast({ + html: "Error saving security settings", + classes: 'red' + }); + console.error(response); + } + }); + + event.preventDefault(); +}); diff --git a/BudgetMasterServer/src/main/resources/templates/settings/containers/settingsSecurity.ftl b/BudgetMasterServer/src/main/resources/templates/settings/containers/settingsSecurity.ftl new file mode 100644 index 0000000000000000000000000000000000000000..7acc05ae176d7e664749130d2802285da6bc9e66 --- /dev/null +++ b/BudgetMasterServer/src/main/resources/templates/settings/containers/settingsSecurity.ftl @@ -0,0 +1,44 @@ +<#import "/spring.ftl" as s> +<#import "../../helpers/validation.ftl" as validation> +<#import "../../helpers/header.ftl" as header> +<@header.globals/> + +<#macro securitySettingsContainer> + <form name="SecuritySettingsContainer" method="post"> + <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" id="token"/> + + <#-- password --> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <i class="material-icons prefix">vpn_key</i> + <input id="settings-password" type="password" name="password" <@validation.validation "password"/> value="•••••"> + <label for="settings-password">${locale.getString("settings.password")}</label> + </div> + </div> + + <#-- password confirmation--> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <i class="material-icons prefix">vpn_key</i> + <input id="settings-password-confirmation" type="password" name="passwordConfirmation" <@validation.validation "passwordConfirmation"/> value="•••••"> + <label for="settings-password-confirmation">${locale.getString("settings.password.confirmation")}</label> + </div> + </div> + + <div class="row"> + <div class="col s12 center-align"> + <@header.buttonSubmit name='action' icon='save' localizationKey='save' color='background-green' formaction='/settings/save/security'/> + </div> + </div> + </form> + + <div class="hidden securityContainerToastContent"> + <#if toastContent??>${toastContent}</#if> + </div> + + <script src="<@s.url '/webjars/jquery/3.6.0/jquery.min.js'/>"></script> + <script src="<@s.url '/webjars/materializecss/1.0.0/js/materialize.min.js'/>"></script> + <script src="<@s.url '/js/settingsContainers/settingsSecurity.js'/>"></script> +</#macro> + +<@securitySettingsContainer/> \ No newline at end of file diff --git a/BudgetMasterServer/src/main/resources/templates/settings/settings.ftl b/BudgetMasterServer/src/main/resources/templates/settings/settings.ftl index 74ce01a16c86a0c58cdcbb8375822ce2b3263f85..24bf87f93dd079b233e561fcb854b86dfddd04c6 100644 --- a/BudgetMasterServer/src/main/resources/templates/settings/settings.ftl +++ b/BudgetMasterServer/src/main/resources/templates/settings/settings.ftl @@ -4,14 +4,19 @@ <@header.globals/> <@header.header "BudgetMaster - ${locale.getString('menu.settings')}"/> <@header.style "settings"/> + <@header.style "collapsible"/> <#import "/spring.ftl" as s> </head> <@header.body> <#import "../helpers/navbar.ftl" as navbar> <@navbar.navbar "settings" settings/> + <#import "../helpers/validation.ftl" as validation> <#import "settingsMacros.ftl" as settingsMacros> + <#import "containers/settingsSecurity.ftl" as settingsSecurityMacros> + + <main> <div class="card main-card background-color"> <div class="container"> @@ -22,232 +27,236 @@ <@header.content> <div class="container"> - <#import "../helpers/validation.ftl" as validation> - <form name="Settings" action="<@s.url '/settings/save'/>" method="post" onsubmit="return validateForm()"> - <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" id="token"/> - <input type="hidden" name="ID" value="${settings.getID()?c}"> - <input type="hidden" name="lastBackupReminderDate" value="${dateService.getLongDateString(settings.getLastBackupReminderDate())}"> - <input type="hidden" name="installedVersionCode" value="${settings.getInstalledVersionCode()}"> - <input type="hidden" name="whatsNewShownForCurrentVersion" value="${settings.getWhatsNewShownForCurrentVersion()?c}"> - <input type="hidden" name="migrationDeclined" value="${settings.getMigrationDeclined()?c}"> - <#-- password --> - <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2"> - <i class="material-icons prefix">vpn_key</i> - <input id="settings-password" type="password" name="password" <@validation.validation "password"/> value="•••••"> - <label for="settings-password">${locale.getString("settings.password")}</label> - </div> - </div> - - <#-- password confirmation--> - <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2"> - <i class="material-icons prefix">vpn_key</i> - <input id="settings-password-confirmation" type="password" name="passwordConfirmation" <@validation.validation "passwordConfirmation"/> value="•••••"> - <label for="settings-password-confirmation">${locale.getString("settings.password.confirmation")}</label> - </div> - </div> - - <div class="row"> - <div class="col s12"> - <div class="table-container"> - <div class="table-cell"> - <div class="switch-cell-margin">${locale.getString("settings.updates.automatic")}</div> - </div> - <div class="table-cell table-cell-spacer"></div> - <div class="table-cell"> - <@settingsMacros.switch "updates.automatic" "autoUpdateCheckEnabled" settings.isAutoUpdateCheckEnabled()/> - </div> - <div class="table-cell table-cell-spacer"></div> - <div class="table-cell"> - <div class="switch-cell-margin"> - <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.updates.automatic.description")}"><i class="material-icons">help_outline</i></a> + <div class="row"> + <div class="col s12"> + <ul class="collapsible"> + <@settingsMacros.settingsCollapsibleItem "securitySettingsContainer" "vpn_key" "Security"> + <@settingsSecurityMacros.securitySettingsContainer/> + </@settingsMacros.settingsCollapsibleItem> + + <@settingsMacros.settingsCollapsibleItem "" "format_paint" locale.getString("settings.appearance")> + <#-- language --> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <i class="material-icons prefix">translate</i> + <select id="settings-language" name="languageType" <@validation.validation "language"/>> + <#list helpers.getAvailableLanguages() as language> + <#if settings.getLanguage() == language> + <option selected value="${language.getName()}">${language.getName()}</option> + <#else> + <option value="${language.getName()}">${language.getName()}</option> + </#if> + </#list> + </select> + <label for="settings-language">${locale.getString("settings.language")}</label> </div> </div> - </div> - </div> - </div> - - <hr> - - <#-- appearance --> - <div class="container"> - <div class="section center-align"> - <div class="headline">${locale.getString("settings.appearance")}</div> - </div> - </div> - - <#-- language --> - <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2"> - <i class="material-icons prefix">translate</i> - <select id="settings-language" name="languageType" <@validation.validation "language"/>> - <#list helpers.getAvailableLanguages() as language> - <#if settings.getLanguage() == language> - <option selected value="${language.getName()}">${language.getName()}</option> - <#else> - <option value="${language.getName()}">${language.getName()}</option> - </#if> - </#list> - </select> - <label for="settings-language">${locale.getString("settings.language")}</label> - </div> - </div> - - <#-- currency --> - <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2"> - <i class="material-icons prefix">euro</i> - <input id="settings-currency" type="text" name="currency" <@validation.validation "currency"/> value="<#if settings.getCurrency()??>${settings.getCurrency()}</#if>"> - <label for="settings-currency">${locale.getString("settings.currency")}</label> - </div> - </div> - <#-- rest, dark theme and category style --> - <@settingsMacros.switches settings/> - - <#-- search items per page --> - <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2"> - <i class="material-icons prefix">search</i> - <select id="settings-search-items-per-page" name="searchItemsPerPage" <@validation.validation "searchItemsPerPage"/>> - <#list searchResultsPerPageOptions as number> - <#if settings.getSearchItemsPerPage() == number> - <option selected value="${number}">${number}</option> - <#else> - <option value="${number}">${number}</option> - </#if> - </#list> - </select> - <label for="settings-search-items-per-page">${locale.getString("settings.search.itemsPerPage")}</label> - </div> - </div> - - <hr> + <#-- currency --> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <i class="material-icons prefix">euro</i> + <input id="settings-currency" type="text" name="currency" <@validation.validation "currency"/> value="<#if settings.getCurrency()??>${settings.getCurrency()}</#if>"> + <label for="settings-currency">${locale.getString("settings.currency")}</label> + </div> + </div> - <#-- backups --> - <div class="container"> - <div class="section center-align"> - <div class="headline">${locale.getString("settings.backup")}</div> - </div> - </div> + <#-- rest, dark theme and category style --> + <@settingsMacros.switches settings/> + + <#-- search items per page --> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <i class="material-icons prefix">search</i> + <select id="settings-search-items-per-page" name="searchItemsPerPage" <@validation.validation "searchItemsPerPage"/>> + <#list searchResultsPerPageOptions as number> + <#if settings.getSearchItemsPerPage() == number> + <option selected value="${number}">${number}</option> + <#else> + <option value="${number}">${number}</option> + </#if> + </#list> + </select> + <label for="settings-search-items-per-page">${locale.getString("settings.search.itemsPerPage")}</label> + </div> + </div> - <div class="row"> - <div class="col s12"> - <div class="table-container"> - <div class="table-cell"> - <div class="switch-cell-margin">${locale.getString("settings.backupReminder")}</div> - <div class="switch-cell-margin">${locale.getString("settings.backup.auto")}</div> + <div class="row"> + <div class="col s12 center-align"> + <@header.buttonSubmit name='action' icon='save' localizationKey='save' color='background-green'/> + </div> </div> - <div class="table-cell table-cell-spacer"></div> - <div class="table-cell"> - <@settingsMacros.switch "backupReminder" "backupReminderActivated" settings.getBackupReminderActivated()/> - <@settingsMacros.switch "backup.auto" "autoBackupActivated" settings.isAutoBackupActive()/> + </@settingsMacros.settingsCollapsibleItem> + + <@settingsMacros.settingsCollapsibleItem "" "list" "Transactions"> + <div class="row"> + <div class="col s12"> + <div class="table-container"> + <div class="table-cell"> + <div class="switch-cell-margin">${locale.getString("settings.rest")}</div> + <div class="switch-cell-margin">Warn about expenditure switch</div> + </div> + <div class="table-cell table-cell-spacer"></div> + <div class="table-cell"> + <@settingsMacros.switch "rest" "restActivated" settings.isRestActivated()/> + <@settingsMacros.switch "rest" "restActivated" settings.isRestActivated()/> + </div> + <div class="table-cell table-cell-spacer"></div> + <div class="table-cell"> + <div class="switch-cell-margin"> + <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.rest.description")}"><i class="material-icons">help_outline</i></a> + </div> + <div class="switch-cell-margin"> + <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.rest.description")}"><i class="material-icons">help_outline</i></a> + </div> + </div> + </div> + </div> </div> - <div class="table-cell table-cell-spacer"></div> - <div class="table-cell"> - <div class="switch-cell-margin"> - <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.backupReminder.description")}"><i class="material-icons">help_outline</i></a> + + <div class="row"> + <div class="col s12 center-align"> + <@header.buttonSubmit name='action' icon='save' localizationKey='save' color='background-green'/> </div> - <div class="switch-cell-margin"> - <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.backup.auto.description")}"><i class="material-icons">help_outline</i></a> + </div> + </@settingsMacros.settingsCollapsibleItem> + + <@settingsMacros.settingsCollapsibleItem "" "cloud_download" locale.getString("settings.backup")> + <div class="row"> + <div class="col s12"> + <div class="table-container"> + <div class="table-cell"> + <div class="switch-cell-margin">${locale.getString("settings.backupReminder")}</div> + <div class="switch-cell-margin">${locale.getString("settings.backup.auto")}</div> + </div> + <div class="table-cell table-cell-spacer"></div> + <div class="table-cell"> + <@settingsMacros.switch "backupReminder" "backupReminderActivated" settings.getBackupReminderActivated()/> + <@settingsMacros.switch "backup.auto" "autoBackupActivated" settings.isAutoBackupActive()/> + </div> + <div class="table-cell table-cell-spacer"></div> + <div class="table-cell"> + <div class="switch-cell-margin"> + <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.backupReminder.description")}"><i class="material-icons">help_outline</i></a> + </div> + <div class="switch-cell-margin"> + <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.backup.auto.description")}"><i class="material-icons">help_outline</i></a> + </div> + </div> + </div> </div> </div> - </div> - </div> - </div> - - <#-- auto backup --> - <@settingsMacros.autoBackup/> - <hr> - <br> + <#-- auto backup --> + <@settingsMacros.autoBackup/> - <#-- buttons --> - <div class="row"> - <div class="col s12 center-align"> - <@header.buttonSubmit name='action' icon='save' localizationKey='save' color='background-green'/> - </div> - </div> - </form> - </div> - - <hr> - - <#-- updates --> - <div class="container"> - <div class="section center-align"> - <div class="headline">${locale.getString("settings.updates")}</div> - </div> - </div> - - <div class="row"> - <div class="col s12 m12 l8 offset-l2 center-align"> - <div class="table-container"> - <div class="table-cell"> - <div class="right-align" style="margin-bottom: 1em;">${locale.getString("settings.updates.current.version")}</div> - <div class="right-align">${locale.getString("settings.updates.latest.version")}</div> - </div> + <div class="row"> + <div class="col s12 center-align"> + <@header.buttonSubmit name='action' icon='save' localizationKey='save' color='background-green'/> + </div> + </div> + </@settingsMacros.settingsCollapsibleItem> + + <@settingsMacros.settingsCollapsibleItem "" "system_update" locale.getString("settings.updates")> + <div class="row"> + <div class="col s12 m12 l8 offset-l2 center-align"> + <div class="table-container"> + <div class="table-cell"> + <div class="right-align" style="margin-bottom: 1em;">${locale.getString("settings.updates.current.version")}</div> + <div class="right-align">${locale.getString("settings.updates.latest.version")}</div> + </div> + + <div class="table-cell table-cell-spacer"></div> + + <div class="table-cell"> + <div class="left-align" style="margin-bottom: 1em; margin-right: 5em"> + <div class="banner background-grey <#if settings.isUseDarkTheme()>text-black<#else>text-white</#if>"> + v${build.getVersionName()} + </div> + </div> + <div class="left-align"> + <#if updateService.getAvailableVersionString() == "-"> + <#if settings.isUseDarkTheme()> + <#assign bannerClasses="background-grey text-black"> + <#else> + <#assign bannerClasses="background-grey text-white"> + </#if> + <#else> + <#if updateService.isUpdateAvailable()> + <#assign bannerClasses="background-orange text-black"> + <#else> + <#assign bannerClasses="background-green text-white"> + </#if> + </#if> + + <div class="banner ${bannerClasses}"> + ${updateService.getAvailableVersionString()} + </div> + </div> + </div> + + <div class="table-cell table-cell-valign"> + <@header.buttonLink url='/settings/updateSearch' icon='refresh' localizationKey='settings.updates.search'/> + </div> + </div> + </div> + </div> - <div class="table-cell table-cell-spacer"></div> + <div class="row"> + <div class="col s12"> + <div class="table-container"> + <div class="table-cell"> + <div class="switch-cell-margin">${locale.getString("settings.updates.automatic")}</div> + </div> + <div class="table-cell table-cell-spacer"></div> + <div class="table-cell"> + <@settingsMacros.switch "updates.automatic" "autoUpdateCheckEnabled" settings.isAutoUpdateCheckEnabled()/> + </div> + <div class="table-cell table-cell-spacer"></div> + <div class="table-cell"> + <div class="switch-cell-margin"> + <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.updates.automatic.description")}"><i class="material-icons">help_outline</i></a> + </div> + </div> + </div> + </div> + </div> - <div class="table-cell"> - <div class="left-align" style="margin-bottom: 1em; margin-right: 5em"> - <div class="banner background-grey <#if settings.isUseDarkTheme()>text-black<#else>text-white</#if>"> - v${build.getVersionName()} + <div class="row"> + <div class="col s12 center-align"> + <@header.buttonSubmit name='action' icon='save' localizationKey='save' color='background-green'/> + </div> </div> - </div> - <div class="left-align"> - <#if updateService.getAvailableVersionString() == "-"> - <#if settings.isUseDarkTheme()> - <#assign bannerClasses="background-grey text-black"> - <#else> - <#assign bannerClasses="background-grey text-white"> - </#if> - <#else> - <#if updateService.isUpdateAvailable()> - <#assign bannerClasses="background-orange text-black"> - <#else> - <#assign bannerClasses="background-green text-white"> - </#if> - </#if> - - <div class="banner ${bannerClasses}"> - ${updateService.getAvailableVersionString()} + </@settingsMacros.settingsCollapsibleItem> + + <@settingsMacros.settingsCollapsibleItem "" "miscellaneous_services" "Misc"> + <div class="row no-margin-bottom"> + <div class="col s12 center-align"> + <@header.buttonLink url='/hints/resetAll' icon='restore' localizationKey='button.hints.reset'/> + </div> </div> - </div> - </div> + </@settingsMacros.settingsCollapsibleItem> - <div class="table-cell table-cell-valign"> - <@header.buttonLink url='/settings/updateSearch' icon='refresh' localizationKey='settings.updates.search'/> - </div> + <@settingsMacros.settingsCollapsibleItem "" "fas fa-database" locale.getString("menu.settings.database") true> + <@settingsMacros.databaseNormal/> + <@settingsMacros.databaseSmall/> + </@settingsMacros.settingsCollapsibleItem> + </ul> </div> </div> - </div> - <hr> - <#-- hints --> - <div class="container"> - <div class="section center-align"> - <div class="headline">${locale.getString("headline.hints")}</div> - </div> - </div> - <div class="row"> - <div class="col s12 center-align"> - <@header.buttonLink url='/hints/resetAll' icon='restore' localizationKey='button.hints.reset'/> - </div> - </div> - <hr> - <#-- database --> - <div class="container"> - <div class="section center-align"> - <div class="headline">${locale.getString("menu.settings.database")}</div> - </div> + + <form name="Settings" action="<@s.url '/settings/save'/>" method="post" onsubmit="return validateForm()"> + <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" id="token"/> + <input type="hidden" name="ID" value="${settings.getID()?c}"> + <input type="hidden" name="lastBackupReminderDate" value="${dateService.getLongDateString(settings.getLastBackupReminderDate())}"> + <input type="hidden" name="installedVersionCode" value="${settings.getInstalledVersionCode()}"> + <input type="hidden" name="whatsNewShownForCurrentVersion" value="${settings.getWhatsNewShownForCurrentVersion()?c}"> + <input type="hidden" name="migrationDeclined" value="${settings.getMigrationDeclined()?c}"> + </form> </div> - <@settingsMacros.databaseNormal/> - <@settingsMacros.databaseSmall/> </@header.content> </div> </main> diff --git a/BudgetMasterServer/src/main/resources/templates/settings/settingsMacros.ftl b/BudgetMasterServer/src/main/resources/templates/settings/settingsMacros.ftl index 1ab390b9e7986fbb1ae50c42f385fbb5dac11df2..5e3c2d9f7d10c580223c0ea451e709e1812678d8 100644 --- a/BudgetMasterServer/src/main/resources/templates/settings/settingsMacros.ftl +++ b/BudgetMasterServer/src/main/resources/templates/settings/settingsMacros.ftl @@ -5,21 +5,16 @@ <div class="col s12"> <div class="table-container"> <div class="table-cell"> - <div class="switch-cell-margin">${locale.getString("settings.rest")}</div> <div class="switch-cell-margin">${locale.getString("settings.darkTheme")}</div> <div class="switch-cell-margin">${locale.getString("settings.category.circle.style")}</div> </div> <div class="table-cell table-cell-spacer"></div> <div class="table-cell"> - <@switch "rest" "restActivated" settings.isRestActivated()/> <@switch "darkTheme" "useDarkTheme" settings.isUseDarkTheme()/> <@switch "category.circle.style" "showCategoriesAsCircles" settings.getShowCategoriesAsCircles()?? && settings.getShowCategoriesAsCircles()/> </div> <div class="table-cell table-cell-spacer"></div> <div class="table-cell"> - <div class="switch-cell-margin"> - <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.rest.description")}"><i class="material-icons">help_outline</i></a> - </div> <div class="switch-cell-margin"> <a class="btn btn-flat tooltipped text-default" data-position="bottom" data-tooltip="${locale.getString("settings.darkTheme.description")}"><i class="material-icons">help_outline</i></a> </div> @@ -44,7 +39,7 @@ </#macro> <#macro databaseNormal> - <div class="row hide-on-small-only"> + <div class="row hide-on-small-only no-margin-bottom"> <div class="col m4 l4 center-align"> <@header.buttonLink url='/settings/database/requestImport' icon='cloud_upload' localizationKey='settings.database.import'/> </div> @@ -302,4 +297,15 @@ <@header.buttonSubmit id='settings-backup-run-now' name='action' icon='cloud_download' localizationKey='settings.backup.auto.run.now'/> </div> </div> -</#macro> \ No newline at end of file +</#macro> + +<#macro settingsCollapsibleItem id icon title isFontAwesomeIcon=false> + <li class="z-depth-2"> + <div class="collapsible-header bold"><#if isFontAwesomeIcon><i class="${icon}"></i><#else><i class="material-icons">${icon}</i></#if>${title}</div> + <div class="collapsible-body"> + <div class="row no-margin-bottom" id="${id}"> + <#nested> + </div> + </div> + </li> +</#macro>