Admin UI for editing configuration settings (not finished yet)

Former-commit-id: 9fce8f562f
pull/7/head
Jens Pelzetter 2020-11-05 21:30:42 +01:00
parent 7568bc4839
commit 6b7520f17d
9 changed files with 289 additions and 135 deletions

View File

@ -18,6 +18,8 @@
*/ */
package org.libreccm.ui.admin.configuration; package org.libreccm.ui.admin.configuration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.configuration.AbstractSetting; import org.libreccm.configuration.AbstractSetting;
import org.libreccm.configuration.ConfigurationInfo; import org.libreccm.configuration.ConfigurationInfo;
import org.libreccm.configuration.ConfigurationManager; import org.libreccm.configuration.ConfigurationManager;
@ -30,6 +32,7 @@ import org.libreccm.l10n.LocalizedTextsUtil;
import org.libreccm.security.AuthorizationRequired; import org.libreccm.security.AuthorizationRequired;
import org.libreccm.security.RequiresPrivilege; import org.libreccm.security.RequiresPrivilege;
import java.lang.reflect.Field;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -43,6 +46,7 @@ import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.mvc.Controller; import javax.mvc.Controller;
import javax.mvc.Models; import javax.mvc.Models;
import javax.transaction.Transactional;
import javax.ws.rs.FormParam; import javax.ws.rs.FormParam;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.POST; import javax.ws.rs.POST;
@ -58,6 +62,10 @@ import javax.ws.rs.PathParam;
@Path("/configuration/{configurationClass}") @Path("/configuration/{configurationClass}")
public class SettingsController { public class SettingsController {
private static final Logger LOGGER = LogManager.getLogger(
SettingsController.class
);
@Inject @Inject
private ConfigurationManager confManager; private ConfigurationManager confManager;
@ -72,6 +80,7 @@ public class SettingsController {
@GET @GET
@Path("/") @Path("/")
@Transactional(Transactional.TxType.REQUIRED)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
public String showSettings( public String showSettings(
@ -100,12 +109,15 @@ public class SettingsController {
textUtil.getText(confInfo.getDescKey()) textUtil.getText(confInfo.getDescKey())
); );
final Object configuration = confManager.findConfiguration(confClass);
final List<SettingsTableEntry> settings = confInfo final List<SettingsTableEntry> settings = confInfo
.getSettings() .getSettings()
.entrySet() .entrySet()
.stream() .stream()
.map(Map.Entry::getValue) .map(Map.Entry::getValue)
.map(this::buildSettingsTableEntry) .map(settingInfo -> buildSettingsTableEntry(settingInfo,
configuration))
.sorted() .sorted()
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -124,19 +136,63 @@ public class SettingsController {
models.put("SetClassName", Set.class.getName()); models.put("SetClassName", Set.class.getName());
models.put("StringClassName", String.class.getName()); models.put("StringClassName", String.class.getName());
models.put("IntegerMaxValue", Integer.toString(Integer.MAX_VALUE));
models.put("IntegerMinValue", Integer.toString(Integer.MIN_VALUE));
models.put("LongMaxValue", Long.toString(Long.MAX_VALUE));
models.put("LongMinValue", Long.toString(Long.MIN_VALUE));
models.put("DoubleMaxValue", Double.toString(Double.MAX_VALUE));
models.put("DoubleMinValue", Double.toString(Double.MIN_VALUE));
return "org/libreccm/ui/admin/configuration/settings.xhtml"; return "org/libreccm/ui/admin/configuration/settings.xhtml";
} }
@Transactional(Transactional.TxType.REQUIRED)
private SettingsTableEntry buildSettingsTableEntry( private SettingsTableEntry buildSettingsTableEntry(
final SettingInfo settingInfo final SettingInfo settingInfo,
final Object configuration
) { ) {
Objects.requireNonNull(settingInfo); Objects.requireNonNull(settingInfo);
Objects.requireNonNull(configuration);
final LocalizedTextsUtil textsUtil = globalizationHelper final LocalizedTextsUtil textsUtil = globalizationHelper
.getLocalizedTextsUtil(settingInfo.getDescBundle()); .getLocalizedTextsUtil(settingInfo.getDescBundle());
String value;
try {
final Field field = configuration
.getClass()
.getDeclaredField(settingInfo.getName());
field.setAccessible(true);
if (field.get(configuration) instanceof LocalizedString) {
final LocalizedString localizedStr = (LocalizedString) field
.get(configuration);
value = localizedStr
.getValues()
.entrySet()
.stream()
.map(
entry -> String.format(
"%s: %s",
entry.getKey().toString(), entry.getValue()
)
)
.sorted()
.collect(Collectors.joining(", "));
} else {
value = Objects.toString(field.get(configuration));
}
} catch (NoSuchFieldException | IllegalAccessException | SecurityException ex) {
LOGGER.error(
"Failed to get value for field {} of configuration {}.",
settingInfo.getName(),
configuration.getClass().getName()
);
LOGGER.error(ex);
value = "?err?";
}
final SettingsTableEntry entry = new SettingsTableEntry(); final SettingsTableEntry entry = new SettingsTableEntry();
entry.setName(settingInfo.getName()); entry.setName(settingInfo.getName());
entry.setValue(value);
entry.setValueType(settingInfo.getValueType()); entry.setValueType(settingInfo.getValueType());
entry.setDefaultValue(settingInfo.getDefaultValue()); entry.setDefaultValue(settingInfo.getDefaultValue());
entry.setLabel(textsUtil.getText(settingInfo.getLabelKey())); entry.setLabel(textsUtil.getText(settingInfo.getLabelKey()));
@ -147,12 +203,16 @@ public class SettingsController {
@POST @POST
@Path("/{settingName}") @Path("/{settingName}")
@Transactional(Transactional.TxType.REQUIRED)
@AuthorizationRequired @AuthorizationRequired
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
public String updateSettingValue( public String updateSettingValue(
@PathParam("configurationClass") final String configurationClassName, @PathParam("configurationClass")
@PathParam("settingName") final String settingName, final String configurationClassName,
@FormParam("settingValue") final String valueParam @PathParam("settingName")
final String settingName,
@FormParam("settingValue")
final String valueParam
) { ) {
final Class<?> confClass; final Class<?> confClass;
try { try {
@ -161,6 +221,7 @@ public class SettingsController {
models.put("configurationClass", configurationClassName); models.put("configurationClass", configurationClassName);
return "org/libreccm/ui/admin/configuration/configuration-class-not-found.xhtml"; return "org/libreccm/ui/admin/configuration/configuration-class-not-found.xhtml";
} }
final Object conf = confManager.findConfiguration(confClass);
final SettingInfo settingInfo = settingManager.getSettingInfo( final SettingInfo settingInfo = settingManager.getSettingInfo(
confClass, settingName confClass, settingName
); );
@ -168,38 +229,79 @@ public class SettingsController {
final String valueType = settingInfo.getValueType(); final String valueType = settingInfo.getValueType();
if (valueType.equals(BigDecimal.class.getName())) { if (valueType.equals(BigDecimal.class.getName())) {
return updateBigDecimalSetting( return updateBigDecimalSetting(
configurationClassName, settingName, valueType, valueParam configurationClassName,
confClass,
conf,
settingName,
valueType,
valueParam
); );
} else if (valueType.equals(Boolean.class.getName()) } else if (valueType.equals(Boolean.class.getName())
|| valueType.equals("boolean")) { || valueType.equals("boolean")) {
final boolean value = valueParam != null;
return updateBooleanSetting( return updateBooleanSetting(
configurationClassName, settingName, valueType, valueParam configurationClassName,
confClass,
conf,
settingName,
valueType,
value
); );
} else if (valueType.equals(Double.class.getName()) } else if (valueType.equals(Double.class.getName())
|| valueType.equals("double")) { || valueType.equals("double")) {
return updateDoubleSetting( return updateDoubleSetting(
configurationClassName, settingName, valueType, valueParam configurationClassName,
confClass,
conf,
settingName,
valueType,
valueParam
); );
} else if (valueType.equals(LocalizedString.class.getName())) { } else if (valueType.equals(LocalizedString.class.getName())) {
return updateLocalizedStringSetting( return updateLocalizedStringSetting(
configurationClassName, settingName, valueType, valueParam configurationClassName,
confClass,
conf,
settingName,
valueType,
valueParam
); );
} else if (valueType.equals(Long.class.getName()) } else if (valueType.equals(Long.class.getName())
|| valueType.equals("long")) { || valueType.equals("long")) {
return updateLongSetting( return updateLongSetting(
configurationClassName, settingName, valueType, valueParam configurationClassName,
confClass,
conf,
settingName,
valueType,
valueParam
); );
} else if (valueType.equals(List.class.getName())) { } else if (valueType.equals(List.class.getName())) {
return updateStringListSetting( return updateStringListSetting(
configurationClassName, settingName, valueType, valueParam configurationClassName,
confClass,
conf,
settingName,
valueType,
valueParam
); );
} else if (valueType.equals(Set.class.getName())) { } else if (valueType.equals(Set.class.getName())) {
return updateStringListSetting( return updateStringListSetting(
configurationClassName, settingName, valueType, valueParam configurationClassName,
confClass,
conf,
settingName,
valueType,
valueParam
); );
} else if (valueType.equals(String.class.getName())) { } else if (valueType.equals(String.class.getName())) {
return updateStringSetting( return updateStringSetting(
configurationClassName, settingName, valueType, valueParam configurationClassName,
confClass,
conf,
settingName,
valueType,
valueParam
); );
} else { } else {
models.put("configurationClass", configurationClassName); models.put("configurationClass", configurationClassName);
@ -211,6 +313,8 @@ public class SettingsController {
private String updateBigDecimalSetting( private String updateBigDecimalSetting(
final String configurationClassName, final String configurationClassName,
final Class<?> configurationClass,
final Object configuration,
final String settingName, final String settingName,
final String valueType, final String valueType,
final String valueParam final String valueParam
@ -226,54 +330,38 @@ public class SettingsController {
valueParam valueParam
); );
} }
return updateSetting(
final AbstractSetting<BigDecimal> setting = settingManager configurationClassName,
.findSetting( configurationClass,
configurationClassName, settingName, BigDecimal.class configuration,
); settingName,
valueType,
if (setting == null) { value
return buildSettingNotFoundErrorTarget( );
configurationClassName,
settingName,
valueType
);
} else {
setting.setValue(value);
settingManager.saveSetting(setting);
return buildRedirectAfterUpdateSettingTarget(
configurationClassName
);
}
} }
private String updateBooleanSetting( private String updateBooleanSetting(
final String configurationClassName, final String configurationClassName,
final Class<?> configurationClass,
final Object configuration,
final String settingName, final String settingName,
final String valueType, final String valueType,
final String valueParam final boolean value
) { ) {
final Boolean value = Boolean.valueOf(valueParam); return updateSetting(
configurationClassName,
final AbstractSetting<Boolean> setting = settingManager.findSetting( configurationClass,
configurationClassName, settingName, Boolean.class configuration,
settingName,
valueType,
value
); );
if (setting == null) {
return buildSettingNotFoundErrorTarget(
configurationClassName, settingName, valueType
);
} else {
setting.setValue(value);
settingManager.saveSetting(setting);
return buildRedirectAfterUpdateSettingTarget(
configurationClassName
);
}
} }
private String updateDoubleSetting( private String updateDoubleSetting(
final String configurationClassName, final String configurationClassName,
final Class<?> configurationClass,
final Object configuration,
final String settingName, final String settingName,
final String valueType, final String valueType,
final String valueParam final String valueParam
@ -289,26 +377,20 @@ public class SettingsController {
valueParam valueParam
); );
} }
return updateSetting(
final AbstractSetting<Double> setting = settingManager.findSetting( configurationClassName,
configurationClassName, settingName, Double.class configurationClass,
configuration,
settingName,
valueType,
value
); );
if (setting == null) {
return buildSettingNotFoundErrorTarget(
configurationClassName, settingName, valueType
);
} else {
setting.setValue(value);
settingManager.saveSetting(setting);
return buildRedirectAfterUpdateSettingTarget(
configurationClassName
);
}
} }
private String updateLocalizedStringSetting( private String updateLocalizedStringSetting(
final String configurationClassName, final String configurationClassName,
final Class<?> configurationClass,
final Object configuration,
final String settingName, final String settingName,
final String valueType, final String valueType,
final String valueParam final String valueParam
@ -324,27 +406,20 @@ public class SettingsController {
final String localeValue = tokens[1]; final String localeValue = tokens[1];
value.addValue(locale, localeValue); value.addValue(locale, localeValue);
} }
return updateSetting(
final AbstractSetting<LocalizedString> setting = settingManager configurationClassName,
.findSetting( configurationClass,
configurationClassName, settingName, LocalizedString.class configuration,
); settingName,
valueType,
if (setting == null) { value
return buildSettingNotFoundErrorTarget( );
configurationClassName, settingName, valueType
);
} else {
setting.setValue(value);
settingManager.saveSetting(setting);
return buildRedirectAfterUpdateSettingTarget(
configurationClassName
);
}
} }
private String updateLongSetting( private String updateLongSetting(
final String configurationClassName, final String configurationClassName,
final Class<?> configurationClass,
final Object configuration,
final String settingName, final String settingName,
final String valueType, final String valueType,
final String valueParam final String valueParam
@ -360,70 +435,82 @@ public class SettingsController {
valueParam valueParam
); );
} }
return updateSetting(
final AbstractSetting<Long> setting = settingManager.findSetting( configurationClassName,
configurationClassName, settingName, Long.class configurationClass,
configuration,
settingName,
valueType,
value
); );
if (setting == null) {
return buildSettingNotFoundErrorTarget(
configurationClassName, settingName, valueType
);
} else {
setting.setValue(value);
settingManager.saveSetting(setting);
return buildRedirectAfterUpdateSettingTarget(
configurationClassName
);
}
} }
private String updateStringListSetting( private String updateStringListSetting(
final String configurationClassName, final String configurationClassName,
final Class<?> configurationClass,
final Object configuration,
final String settingName, final String settingName,
final String valueType, final String valueType,
final String valueParam final String valueParam
) { ) {
final String[] tokens = valueParam.split(","); final String[] tokens = valueParam.split(",");
final List<String> value = Arrays.asList(tokens); final List<String> value = Arrays.asList(tokens);
return updateSetting(
final AbstractSetting<List> setting = settingManager.findSetting( configurationClassName,
configurationClassName, settingName, List.class configurationClass,
configuration,
settingName,
valueType,
value
); );
if (setting == null) {
return buildSettingNotFoundErrorTarget(
configurationClassName, settingName, valueType
);
} else {
setting.setValue(value);
settingManager.saveSetting(setting);
return buildRedirectAfterUpdateSettingTarget(
configurationClassName
);
}
} }
private String updateStringSetting( private String updateStringSetting(
final String configurationClassName, final String configurationClassName,
final Class<?> configurationClass,
final Object configuration,
final String settingName, final String settingName,
final String valueType, final String valueType,
final String valueParam final String value
) { ) {
final AbstractSetting<String> setting = settingManager.findSetting( return updateSetting(
configurationClassName, settingName, String.class configurationClassName,
configurationClass,
configuration,
settingName,
valueType,
value
); );
}
if (setting == null) { private String updateSetting(
return buildSettingNotFoundErrorTarget( final String configurationClassName,
configurationClassName, settingName, valueType final Class<?> configurationClass,
final Object configuration,
final String settingName,
final String valueType,
final Object value
) {
try {
final Field field = configurationClass.getDeclaredField(
settingName
); );
} else { field.setAccessible(true);
setting.setValue(valueParam); field.set(configuration, value);
settingManager.saveSetting(setting); confManager.saveConfiguration(configuration);
return buildRedirectAfterUpdateSettingTarget( return buildRedirectAfterUpdateSettingTarget(
configurationClassName configurationClassName
); );
} catch (NoSuchFieldException ex) {
return buildSettingNotFoundErrorTarget(
configurationClassName,
settingName,
valueType);
} catch (SecurityException | IllegalAccessException ex) {
LOGGER.error("Failed to update setting.", ex);
models.put("configurationClass", configurationClassName);
models.put("settingName", settingName);
return "org/libreccm/ui/admin/configuration/failed-to-update-setting.xhtml";
} }
} }

View File

@ -16,7 +16,7 @@
</ui:define> </ui:define>
</ui:define> </ui:define>
<ui:define name="panel"> <ui:define name="main">
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
#{AdminMessages.getMessage('configuration.class.not_found.message', [configurationClass])} #{AdminMessages.getMessage('configuration.class.not_found.message', [configurationClass])}
</div> </div>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<ui:composition template="/WEB-INF/views/org/libreccm/ui/admin/ccm-admin.xhtml">
<ui:param name="activePage" value="configuration" />
<ui:param name="title"
value="#{AdminMessages['configuration.setting.failed_to_update.title']}" />
<ui:define name="breadcrumb">
<ui:define name="breadcrumb">
<li class="breadcrumb-item active">
#{AdminMessages['configuration.label']}
</li>
</ui:define>
</ui:define>
<ui:define name="main">
<div class="alert alert-warning" role="alert">
#{AdminMessages.getMessage('configuration.setting.failed_to_update.message', [configurationClass, settingName])}
</div>
</ui:define>
</ui:composition>
</html>

View File

@ -16,7 +16,7 @@
</ui:define> </ui:define>
</ui:define> </ui:define>
<ui:define name="panel"> <ui:define name="main">
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
#{AdminMessages.getMessage('configuration.setting.invalid_type.message', [configurationClass, settingName, valueType, valueParam])} #{AdminMessages.getMessage('configuration.setting.invalid_type.message', [configurationClass, settingName, valueType, valueParam])}
</div> </div>

View File

@ -16,7 +16,7 @@
</ui:define> </ui:define>
</ui:define> </ui:define>
<ui:define name="panel"> <ui:define name="main">
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
#{AdminMessages.getMessage('configuration.setting.not_found.message', [configurationClass, settingName, valueType])} #{AdminMessages.getMessage('configuration.setting.not_found.message', [configurationClass, settingName, valueType])}
</div> </div>

View File

@ -150,9 +150,10 @@
<c:when test="#{setting.valueType.equals(BigDecimalClassName)}"> <c:when test="#{setting.valueType.equals(BigDecimalClassName)}">
<pre>BigDecimal</pre> <pre>BigDecimal</pre>
</c:when> </c:when>
<c:when test="#{setting.valueType.equals(BooleanClassName)}"> <c:when test="#{setting.valueType.equals(BooleanClassName) or setting.valueType.equals('boolean')}">
<div class="form-group form-check"> <div class="form-group form-check">
<input aria-describedby="#{setting.name}-setting-value-help" <input aria-describedby="#{setting.name}-setting-value-help"
checked="#{(setting.value.equals('true') or (setting.value == null and setting.defaultValue.equals('true'))) ? 'checked' : ''}"
class="form-check-input" class="form-check-input"
id="#{setting.name}-setting-value" id="#{setting.name}-setting-value"
name="settingValue" name="settingValue"
@ -167,14 +168,29 @@
</small> </small>
</div> </div>
</c:when> </c:when>
<c:when test="#{setting.valueType.equals(DoubleClassName)}"> <c:when test="#{setting.valueType.equals(DoubleClassName) or setting.valueType.equals('double')}">
<pre>Double</pre> <pre>double</pre>
</c:when> </c:when>
<c:when test="#{setting.valueType.equals(IntegerClassName)}"> <c:when test="#{setting.valueType.equals(IntegerClassName) or setting.valueType.equals('int')}">
<pre>Integer</pre> <div class="form-group">
</c:when> <label for="#{setting-name}-setting-value">
<c:when test="#{setting.valueType.equals('int')}"> #{AdminMessages['configuration.settings.setting.dialog.value.label']}
<pre>int</pre> </label>
<input aria-describedby="#{setting.name}-setting-value-help"
class="form-control"
id="#{setting.name}-setting-value"
max="#{IntegerMaxValue}"
min="#{IntegerMinValue}"
name="settingValue"
step="1"
type="number"
value="#{setting.value}" />
<small class="form-text text-muted"
id="#{setting.name}-setting-value-help">
#{setting.description}
</small>
</div>
</c:when> </c:when>
<c:when test="#{setting.valueType.equals(ListClassName)}"> <c:when test="#{setting.valueType.equals(ListClassName)}">
<pre>List</pre> <pre>List</pre>
@ -182,6 +198,21 @@
<c:when test="#{setting.valueType.equals(LocalizedStringClassName)}"> <c:when test="#{setting.valueType.equals(LocalizedStringClassName)}">
<pre>LocalizedString</pre> <pre>LocalizedString</pre>
</c:when> </c:when>
<c:when test="#{setting.valueType.equals(LongClassName) or setting.valueType.equals('long')}">
<div class="form-group">
<label for="#{setting-name}-setting-value">
#{AdminMessages['configuration.settings.setting.dialog.value.label']}
</label>
<input aria-describedby="#{setting.name}-setting-value-help"
class="form-control"
id="#{setting.name}-setting-value"
name="settingValue"
step="1"
type="number"
value="#{setting.value}" />
</div>
</c:when>
<c:when test="#{setting.valueType.equals(SetClassName)}"> <c:when test="#{setting.valueType.equals(SetClassName)}">
<pre>Set</pre> <pre>Set</pre>
</c:when> </c:when>

View File

@ -16,7 +16,7 @@
</ui:define> </ui:define>
</ui:define> </ui:define>
<ui:define name="panel"> <ui:define name="main">
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
#{AdminMessages.getMessage('configuration.setting.type_unsupported.message', [configurationClass, settingName, valueType])} #{AdminMessages.getMessage('configuration.setting.type_unsupported.message', [configurationClass, settingName, valueType])}
</div> </div>

View File

@ -234,3 +234,5 @@ configuration.setting.invalid_type.message=Setting {1} of configuration class {0
configuration.setting.not_found.title=Setting not found configuration.setting.not_found.title=Setting not found
configuration.setting.not_found.message=Setting {1} of type {2} not found in configuration class {0} configuration.setting.not_found.message=Setting {1} of type {2} not found in configuration class {0}
configuration.settings.setting.dialog.value.label=Value configuration.settings.setting.dialog.value.label=Value
configuration.setting.failed_to_update.title=Failed to update setting
configuration.setting.failed_to_update.message=Failed to update setting {1} of configuration {0}.

View File

@ -232,5 +232,7 @@ configuration.setting.type_unsupported.message=Einstellung {1} aus Konfiguration
configuration.setting.invalid_type.title=Invalid type of value configuration.setting.invalid_type.title=Invalid type of value
configuration.setting.invalid_type.message=Einstellung {1} der Konfigurations-Klasse {0} ist vom Typ {2}, der angegebene Wert {3} kann aber nicht in diesen Typ konvertiert werden configuration.setting.invalid_type.message=Einstellung {1} der Konfigurations-Klasse {0} ist vom Typ {2}, der angegebene Wert {3} kann aber nicht in diesen Typ konvertiert werden
configuration.setting.not_found.title=Einstellung nicht gefunden configuration.setting.not_found.title=Einstellung nicht gefunden
configuration.setting.not_found.message=Keine Einstellung {1} vom Typ {3} in Konfiguration {0} gefunden configuration.setting.not_found.message=Keine Einstellung {1} vom Typ {2} in Konfiguration {0} gefunden
configuration.settings.setting.dialog.value.label=Wert configuration.settings.setting.dialog.value.label=Wert
configuration.setting.failed_to_update.title=Aktualisieren der Einstellung fehlgeschlagen
configuration.setting.failed_to_update.message=Aktualisieren der Einstellung {1} in Konfiguration {0} fehlgeschlagen.