diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/AdminServlet.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/AdminServlet.java index 9201daf68..5958b320e 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/admin/AdminServlet.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/AdminServlet.java @@ -129,7 +129,7 @@ public class AdminServlet new CategoriesTab()); tabbedPane.addTab( - new Label(new GlobalizedMessage("ui.admin.tab.registry.title", + new Label(new GlobalizedMessage("ui.admin.tab.configuration.title", BUNDLE_NAME)), new RegistryAdminTab()); diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/configuration/ConfigurationTab.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/configuration/ConfigurationTab.java new file mode 100644 index 000000000..43cc04fa4 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/configuration/ConfigurationTab.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package com.arsdigita.ui.admin.configuration; + +import com.arsdigita.bebop.ActionLink; +import com.arsdigita.bebop.BoxPanel; +import com.arsdigita.bebop.Form; +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.Page; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.ParameterSingleSelectionModel; +import com.arsdigita.bebop.SegmentedPanel; +import com.arsdigita.bebop.form.Submit; +import com.arsdigita.bebop.form.TextField; +import com.arsdigita.bebop.parameters.StringParameter; +import com.arsdigita.globalization.GlobalizedMessage; +import com.arsdigita.toolbox.ui.LayoutPanel; + +import static com.arsdigita.ui.admin.AdminUiConstants.*; + +/** + * + * @author Jens Pelzetter + */ +public class ConfigurationTab extends LayoutPanel { + + private static final String CONF_CLASSES_FILTER = "confClassesFilter"; + + private final StringParameter selectedConfParam; + private final ParameterSingleSelectionModel selectedConf; + + private final StringParameter selectedSettingParam; + private final ParameterSingleSelectionModel selectedSetting; + + private final Label confClassesFilterHeading; + private final Form confClassesFilterForm; + private final ConfigurationsTable configurationsTable; + + public ConfigurationTab() { + super(); + + setClassAttr("sidebarNavPanel"); + + selectedConfParam = new StringParameter("selectedConfiguration"); + selectedConf = new ParameterSingleSelectionModel<>(selectedConfParam); + + selectedSettingParam = new StringParameter("selectedSetting"); + selectedSetting = new ParameterSingleSelectionModel<>( + selectedSettingParam); + + final SegmentedPanel left = new SegmentedPanel(); + + confClassesFilterHeading = new Label(new GlobalizedMessage( + "ui.admin.configuration.classes.filter.heading", ADMIN_BUNDLE)); + + confClassesFilterForm = new Form("confClassesForm"); + final TextField confClassesFilter = new TextField(CONF_CLASSES_FILTER); + confClassesFilterForm.add(confClassesFilter); + confClassesFilterForm.add(new Submit(new GlobalizedMessage( + "ui.admin.configuration.classes.filter.submit", ADMIN_BUNDLE))); + final ActionLink clearLink = new ActionLink(new GlobalizedMessage( + "ui.admin.configuration.classes.filter.clear", ADMIN_BUNDLE)); + clearLink.addActionListener(e -> { + final PageState state = e.getPageState(); + confClassesFilter.setValue(state, null); + }); + confClassesFilterForm.add(clearLink); + left.addSegment(confClassesFilterHeading, confClassesFilterForm); + + setLeft(left); + + final BoxPanel body = new BoxPanel(BoxPanel.VERTICAL); + configurationsTable = new ConfigurationsTable( + this, selectedConf, confClassesFilter); + body.add(configurationsTable); + + setBody(body); + } + + @Override + public void register(final Page page) { + super.register(page); + + page.setVisibleDefault(confClassesFilterHeading, true); + page.setVisibleDefault(confClassesFilterForm, true); + page.setVisibleDefault(configurationsTable, true); + } + + protected void showConfigurationsTable(final PageState state) { + confClassesFilterHeading.setVisible(state, true); + confClassesFilterForm.setVisible(state, true); + configurationsTable.setVisible(state, true); + } + + protected void hideConfigurationsTable(final PageState state) { + confClassesFilterHeading.setVisible(state, false); + confClassesFilterForm.setVisible(state, false); + configurationsTable.setVisible(state, false); + } + + protected void showConfiguration(final PageState state) { + hideConfigurationsTable(state); + + } + + protected void hideConfiguration(final PageState state) { + + showConfigurationsTable(state); + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/configuration/ConfigurationTable.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/configuration/ConfigurationTable.java new file mode 100644 index 000000000..7c6b28c4b --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/configuration/ConfigurationTable.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2016 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package com.arsdigita.ui.admin.configuration; + +import com.arsdigita.bebop.Component; +import com.arsdigita.bebop.ControlLink; +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.ParameterSingleSelectionModel; +import com.arsdigita.bebop.Table; +import com.arsdigita.bebop.event.TableActionEvent; +import com.arsdigita.bebop.event.TableActionListener; +import com.arsdigita.bebop.table.TableCellRenderer; +import com.arsdigita.bebop.table.TableColumn; +import com.arsdigita.bebop.table.TableColumnModel; +import com.arsdigita.bebop.table.TableModel; +import com.arsdigita.bebop.table.TableModelBuilder; +import com.arsdigita.globalization.GlobalizedMessage; +import com.arsdigita.util.LockableImpl; +import com.arsdigita.util.UncheckedWrapperException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.configuration.ConfigurationManager; +import org.libreccm.configuration.SettingInfo; +import org.libreccm.configuration.SettingManager; +import org.libreccm.l10n.GlobalizationHelper; + +import java.util.List; +import java.util.logging.Level; + +import static com.arsdigita.ui.admin.AdminUiConstants.*; + +/** + * + * @author Jens Pelzetter + */ +public class ConfigurationTable extends Table { + + private static final Logger LOGGER = LogManager.getLogger( + ConfigurationTable.class); + + private static final int COL_SETTING_LABEL = 0; + private static final int COL_SETTING_VALUE = 1; + private static final int COL_SETTING_DESC = 2; + private static final int COL_EDIT_SETTING = 3; + + private ParameterSingleSelectionModel selectedConf; + + public ConfigurationTable( + final ConfigurationTab configurationTab, + final ParameterSingleSelectionModel selectedConf, + final ParameterSingleSelectionModel selectedSetting) { + + super(); + + setIdAttr("configurationTable"); + + this.selectedConf = selectedConf; + + setEmptyView(new Label(new GlobalizedMessage( + "ui.admin.configuration.settings.none", ADMIN_BUNDLE))); + + final TableColumnModel columnModel = getColumnModel(); + columnModel.add(new TableColumn( + COL_SETTING_LABEL, + new Label(new GlobalizedMessage( + "ui.admin.configuration.settings.table.col_setting_label.header", + ADMIN_BUNDLE)))); + columnModel.add(new TableColumn( + COL_SETTING_VALUE, + new Label(new GlobalizedMessage( + "ui.admin.configuration.settings.table.col_setting_value.header", + ADMIN_BUNDLE)))); + columnModel.add(new TableColumn( + COL_SETTING_DESC, + new Label(new GlobalizedMessage( + "ui.admin.configuration.settings.table.col_setting_desc.header", + ADMIN_BUNDLE)))); + columnModel.add(new TableColumn( + COL_EDIT_SETTING, + new Label(new GlobalizedMessage( + "ui.admin.configuration.settings.table.col_edit_setting.header", + ADMIN_BUNDLE)))); + + columnModel.get(COL_EDIT_SETTING).setCellRenderer( + new TableCellRenderer() { + + @Override + public Component getComponent(final Table table, + final PageState state, + final Object value, + final boolean isSelected, + final Object key, + final int row, + final int column) { + return new ControlLink((String) value); + } + + }); + + addTableActionListener(new TableActionListener() { + + @Override + public void cellSelected(final TableActionEvent event) { + final PageState state = event.getPageState(); + + if (event.getColumn() == COL_EDIT_SETTING) { + final String settingName = (String) event.getRowKey(); + selectedSetting.setSelectedKey(state, settingName); + } + } + + @Override + public void headSelected(final TableActionEvent event) { + //Nothing + } + + }); + + setModelBuilder(new ConfigurationTableModelBuilder()); + } + + private class ConfigurationTableModelBuilder + extends LockableImpl implements TableModelBuilder { + + @Override + public TableModel makeModel(final Table table, final PageState state) { + final ConfigurationManager confManager = CdiUtil.createCdiUtil() + .findBean(ConfigurationManager.class); + final Class confClass; + try { + confClass = Class.forName(selectedConf.getSelectedKey(state)); + } catch (ClassNotFoundException ex) { + LOGGER.error("Configuration class '{}' not found.", + selectedConf.getSelectedKey(state)); + throw new UncheckedWrapperException(String.format( + "Configuration class '%s not found'", + selectedConf.getSelectedKey(state)), ex); + } + + final Object configuration = confManager + .findConfiguration(confClass); + + return new ConfigurationTableModel(configuration, state); + } + + } + + private class ConfigurationTableModel implements TableModel { + + private final Object configuration; + + private final ConfigurationManager confManager; + private final SettingManager settingManager; + private final GlobalizationHelper globalizationHelper; + + private final List settings; + private int index = -1; + + public ConfigurationTableModel(final Object configuration, + final PageState state) { + this.configuration = configuration; + + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + confManager = cdiUtil.findBean(ConfigurationManager.class); + settingManager = cdiUtil.findBean(SettingManager.class); + globalizationHelper = cdiUtil.findBean(GlobalizationHelper.class); + + settings = settingManager.getAllSettings(configuration.getClass()); + } + + @Override + public int getColumnCount() { + return 4; + } + + @Override + public boolean nextRow() { + index++; + return index < settings.size(); + } + + @Override + public Object getElementAt(final int columnIndex) { + final String setting = settings.get(index); + final SettingInfo settingInfo = settingManager.getSettingInfo( + configuration.getClass(), setting); + + switch (columnIndex) { + case COL_SETTING_LABEL: + return settingInfo.getLabel(globalizationHelper + .getNegotiatedLocale()); + case COL_SETTING_VALUE: { + try { + return configuration.getClass().getField(setting).get( + configuration); + } catch (NoSuchFieldException | SecurityException | IllegalAccessException ex) { + LOGGER.error("Failed to read value from configuration.", + ex); + return new Label(new GlobalizedMessage( + "ui.admin.configuration.settings.read_error", + ADMIN_BUNDLE)); + } + } + case COL_SETTING_DESC: + return settingInfo.getDescription(globalizationHelper + .getNegotiatedLocale()); + case COL_EDIT_SETTING: + return new Label(new GlobalizedMessage( + "ui.admin.configuration.settings.edit", ADMIN_BUNDLE)); + default: + throw new IllegalArgumentException("Illegal column index"); + } + } + + @Override + public Object getKeyAt(final int columnIndex) { + return settings.get(index); + } + + } + +} diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/configuration/ConfigurationsTable.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/configuration/ConfigurationsTable.java new file mode 100644 index 000000000..feae8a213 --- /dev/null +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/configuration/ConfigurationsTable.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2016 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package com.arsdigita.ui.admin.configuration; + +import com.arsdigita.bebop.Component; +import com.arsdigita.bebop.ControlLink; +import com.arsdigita.bebop.Label; +import com.arsdigita.bebop.PageState; +import com.arsdigita.bebop.ParameterSingleSelectionModel; +import com.arsdigita.bebop.Table; +import com.arsdigita.bebop.event.TableActionEvent; +import com.arsdigita.bebop.event.TableActionListener; +import com.arsdigita.bebop.form.TextField; +import com.arsdigita.bebop.table.TableCellRenderer; +import com.arsdigita.bebop.table.TableColumn; +import com.arsdigita.bebop.table.TableColumnModel; +import com.arsdigita.bebop.table.TableModel; +import com.arsdigita.bebop.table.TableModelBuilder; +import com.arsdigita.globalization.GlobalizedMessage; +import com.arsdigita.util.LockableImpl; + +import org.apache.logging.log4j.util.Strings; +import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.configuration.ConfigurationInfo; +import org.libreccm.configuration.ConfigurationManager; +import org.libreccm.l10n.GlobalizationHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.SortedSet; +import java.util.stream.Collectors; + +import static com.arsdigita.ui.admin.AdminUiConstants.*; + +/** + * + * @author Jens Pelzetter + */ +public class ConfigurationsTable extends Table { + + private static final int COL_TITLE = 0; + private static final int COL_DESC = 1; + + public ConfigurationsTable( + final ConfigurationTab configurationTab, + final ParameterSingleSelectionModel selectedConf, + final TextField confClassesFilter) { + + super(); + + setIdAttr("configurationsTable"); + + setEmptyView(new Label(new GlobalizedMessage( + "ui.admin.configuration.configurations.none", ADMIN_BUNDLE))); + + final TableColumnModel columnModel = getColumnModel(); + columnModel.add(new TableColumn( + COL_TITLE, + new Label(new GlobalizedMessage( + "ui.admin.configuration.configurations.table.name", + ADMIN_BUNDLE)))); + columnModel.add(new TableColumn( + COL_DESC, + new Label(new GlobalizedMessage( + "ui.admin.configuration.configurations.table.desc", + ADMIN_BUNDLE)))); + + columnModel.get(COL_TITLE).setCellRenderer(new TableCellRenderer() { + + @Override + public Component getComponent(final Table table, + final PageState state, + final Object value, + final boolean isSelected, + final Object key, + final int row, + final int column) { + return new ControlLink((String) value); + } + + }); + + addTableActionListener(new TableActionListener() { + + @Override + public void cellSelected(final TableActionEvent event) { + final PageState state = event.getPageState(); + + if (event.getColumn() == COL_TITLE) { + final String confClassName = (String) event.getRowKey(); + selectedConf.setSelectedKey(state, confClassName); + } + } + + @Override + public void headSelected(final TableActionEvent event) { + //Nothing + } + + }); + + setModelBuilder(new ConfigurationsTableModelBuilder(confClassesFilter)); + } + + private class ConfigurationsTableModelBuilder + extends LockableImpl implements TableModelBuilder { + + private final TextField confClassesFilter; + + public ConfigurationsTableModelBuilder( + final TextField confClassesField) { + + this.confClassesFilter = confClassesField; + } + + @Override + public TableModel makeModel(final Table table, + final PageState state) { + table.getRowSelectionModel().clearSelection(state); + + return new ConfigurationsTableModel(confClassesFilter, state); + } + + } + + private class ConfigurationsTableModel implements TableModel { + + private final List> configurations; + private int index = -1; + private final ConfigurationManager confManager; + private final Locale negoiatedLocale; + + public ConfigurationsTableModel(final TextField confClassesFilter, + final PageState state) { + final String filterTerm = (String) confClassesFilter + .getValue(state); + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); + confManager = cdiUtil.findBean(ConfigurationManager.class); + final GlobalizationHelper globalizationHelper = cdiUtil.findBean( + GlobalizationHelper.class); + negoiatedLocale = globalizationHelper.getNegotiatedLocale(); + final SortedSet> confs = confManager + .findAllConfigurations(); + configurations = confs.stream() + .filter(c -> { + final ConfigurationInfo info = confManager + .getConfigurationInfo(c); +// return c.getName().startsWith(filterTerm); + return info.getTitle(negoiatedLocale).startsWith(filterTerm); + }) + .collect(Collectors.toCollection(ArrayList::new)); + configurations.sort((c1, c2) -> { +// return c1.getName().compareTo(c2.getName()); + final ConfigurationInfo info1 = confManager + .getConfigurationInfo(c1); + final ConfigurationInfo info2 = confManager + .getConfigurationInfo(c2); + + return info1.getTitle(negoiatedLocale) + .compareTo(info2.getTitle(negoiatedLocale)); + }); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public boolean nextRow() { + index++; + return index < configurations.size(); + } + + @Override + public Object getElementAt(final int columnIndex) { + final ConfigurationInfo info = confManager.getConfigurationInfo( + configurations.get(index)); + switch (columnIndex) { + case COL_TITLE: + if (Strings.isBlank(info.getTitle(negoiatedLocale))) { + return configurations.get(index).getSimpleName(); + } else { + return info.getTitle(negoiatedLocale); + } + case COL_DESC: + return info.getDescription(negoiatedLocale); + default: + throw new IllegalArgumentException("Illegal column index"); + } + } + + @Override + public Object getKeyAt(final int columnIndex) { + return configurations.get(index).getName(); + } + + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/configuration/Configuration.java b/ccm-core/src/main/java/org/libreccm/configuration/Configuration.java index aad4a85fd..1e9d811f6 100644 --- a/ccm-core/src/main/java/org/libreccm/configuration/Configuration.java +++ b/ccm-core/src/main/java/org/libreccm/configuration/Configuration.java @@ -18,7 +18,6 @@ */ package org.libreccm.configuration; -import org.libreccm.configuration.*; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,37 +25,44 @@ import java.lang.annotation.Target; import java.util.ResourceBundle; /** - * Marks a class as configuration class which is managed by the + * Marks a class as configuration class which is managed by the * {@link ConfigurationManager}. - * + * * @author Jens Pelzetter */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Configuration { - + /** * The name of the configuration. If left blank the simple name of the class * is used. - * + * * @return Name of the configuration. */ //String name() default ""; - /** - * Points to the {@link ResourceBundle} containing the descriptions - * of the configuration and all entries of the configuration. - * + * Points to the {@link ResourceBundle} containing the descriptions of the + * configuration and all entries of the configuration. + * * @return Fully qualified name of the {@link ResourceBundle}. */ String descBundle() default ""; - + /** - * Key of the description of the configuration in the resource bundle - * provided by {@link #descBundle()}. + * Key of the title of the description in the resource bundle provided by + * {@link #descBundle()}. * + * @return Key of the title + */ + String titleKey() default ""; + + /** + * Key of the description of the configuration in the resource bundle + * provided by {@link #descBundle()}. + * * @return Key of the description. */ String descKey() default ""; - + } diff --git a/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationInfo.java b/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationInfo.java index c6f808be0..c53f49564 100644 --- a/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationInfo.java +++ b/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationInfo.java @@ -26,8 +26,7 @@ import java.util.ResourceBundle; import java.util.TreeMap; /** - * Describes a configuration. Useful for generating user - * interfaces. + * Describes a configuration. Useful for generating user interfaces. * * @author Jens Pelzetter */ @@ -44,6 +43,8 @@ public final class ConfigurationInfo { */ private String descBundle; + private String titleKey; + /** * The key for the description of the configuration in the resource bundle. */ @@ -79,6 +80,14 @@ public final class ConfigurationInfo { return ResourceBundle.getBundle(descBundle); } + public String getTitleKey() { + return titleKey; + } + + void setTitleKey(final String titleKey) { + this.titleKey = titleKey; + } + public String getDescKey() { return descKey; } @@ -87,6 +96,10 @@ public final class ConfigurationInfo { this.descKey = descKey; } + public String getTitle(final Locale locale) { + return getDescriptionBundle(locale).getString(titleKey); + } + public String getDescription(final Locale locale) { return getDescriptionBundle(locale).getString(descKey); } diff --git a/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationManager.java b/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationManager.java index 01aa14bfd..95e7c7e23 100644 --- a/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationManager.java +++ b/ccm-core/src/main/java/org/libreccm/configuration/ConfigurationManager.java @@ -19,12 +19,21 @@ package org.libreccm.configuration; import java.lang.reflect.Field; + import javax.enterprise.context.RequestScoped; import javax.inject.Inject; -import javax.persistence.EntityManager; import javax.transaction.Transactional; + import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.util.Strings; +import org.libreccm.modules.CcmModule; +import org.libreccm.modules.Module; + +import java.util.Arrays; +import java.util.ServiceLoader; +import java.util.SortedSet; +import java.util.TreeSet; /** * Maps between configuration classes and the settings stored in the database. @@ -43,9 +52,37 @@ public class ConfigurationManager { @Inject private SettingConverter settingConverter; - @Inject - private EntityManager entityManager; + /** + * Finds all configuration classes listed by the installed modules. + * + * @return A sorted set containing all configuration classes. + * + * @see Module#configurations() + */ + public SortedSet> findAllConfigurations() { + final ServiceLoader modules = ServiceLoader.load( + CcmModule.class); + + final SortedSet> configurations = new TreeSet<>((c1, c2) -> { + return c1.getName().compareTo(c2.getName()); + }); + + for(CcmModule module : modules) { + final Module annotation = module.getClass().getAnnotation( + Module.class); + + if (annotation == null) { + continue; + } + + Arrays.stream(annotation.configurations()).forEach(c -> { + configurations.add(c); + }); + } + return configurations; + } + /** * Load all settings of the provided configuration class. * @@ -170,8 +207,12 @@ public class ConfigurationManager { } else { confInfo.setDescBundle(annotation.descBundle()); } - if (annotation.descKey() == null - || annotation.descKey().isEmpty()) { + if (Strings.isBlank(annotation.titleKey())) { + confInfo.setTitleKey("title"); + } else { + confInfo.setTitleKey(annotation.titleKey()); + } + if (Strings.isBlank(annotation.descKey())) { confInfo.setDescKey("description"); } else { confInfo.setDescKey(annotation.descKey()); diff --git a/ccm-core/src/main/java/org/libreccm/configuration/SettingInfo.java b/ccm-core/src/main/java/org/libreccm/configuration/SettingInfo.java index 2a76b2937..d1c7fc12a 100644 --- a/ccm-core/src/main/java/org/libreccm/configuration/SettingInfo.java +++ b/ccm-core/src/main/java/org/libreccm/configuration/SettingInfo.java @@ -114,6 +114,10 @@ public final class SettingInfo { void setLabelKey(final String labelKey) { this.labelKey = labelKey; } + + public String getLabel(final Locale locale) { + return getDescriptionBundle(locale).getString(labelKey); + } public String getDescKey() { return descKey; diff --git a/ccm-core/src/main/java/org/libreccm/configuration/SettingManager.java b/ccm-core/src/main/java/org/libreccm/configuration/SettingManager.java index 015479272..9f699e9f2 100644 --- a/ccm-core/src/main/java/org/libreccm/configuration/SettingManager.java +++ b/ccm-core/src/main/java/org/libreccm/configuration/SettingManager.java @@ -20,13 +20,18 @@ package org.libreccm.configuration; import java.lang.reflect.Field; import java.util.List; + import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import javax.transaction.Transactional; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.util.Strings; + +import java.util.ArrayList; /** * @@ -43,18 +48,55 @@ import org.apache.logging.log4j.Logger; public class SettingManager { private static final Logger LOGGER = LogManager.getLogger( - SettingManager.class); + SettingManager.class); @Inject private EntityManager entityManager; + /** + * Get the names of all settings of a configuration class. + * + * @param configuration The configuration class for which the settings are + * retrieved. + * + * @return A list with the names of all settings provided by the + * configuration class. + */ + public List getAllSettings(final Class configuration) { + if (configuration == null) { + throw new IllegalArgumentException("Configuration can't be null"); + } + + if (configuration.getAnnotation(Configuration.class) == null) { + throw new IllegalArgumentException(String.format( + "The class \"%s\" of the provided object is not annotated " + + "with \"%s\".", + configuration.getClass().getName(), + Configuration.class.getName())); + } + + final List settings = new ArrayList<>(); + final Field[] fields = configuration.getDeclaredFields(); + for (Field field : fields) { + if (field.getAnnotation(Setting.class) != null) { + settings.add(field.getName()); + } + } + + settings.sort((s1, s2) -> { + return s1.compareTo(s2); + }); + + return settings; + } + /** * Create a {@link SettingInfo} instance for a setting. * * @param configuration The configuration class to which the settings - * belongs. - * @param name The name of the setting for which the {@link SettingInfo} is - * generated. + * belongs. + * @param name The name of the setting for which the + * {@link SettingInfo} is generated. * * @return The {@link SettingInfo} for the provided configuration class. */ @@ -62,18 +104,18 @@ public class SettingManager { "PMD.CyclomaticComplexity", "PMD.StandardCyclomaticComplexity"}) public SettingInfo getSettingInfo( - final Class configuration, - final String name) { + final Class configuration, + final String name) { if (configuration == null) { throw new IllegalArgumentException("Configuration can't be null"); } if (configuration.getAnnotation(Configuration.class) == null) { throw new IllegalArgumentException(String.format( - "The class \"%s\" of the provided object is not annotated " - + "with \"%s\".", - configuration.getClass().getName(), - Configuration.class.getName())); + "The class \"%s\" of the provided object is not annotated " + + "with \"%s\".", + configuration.getClass().getName(), + Configuration.class.getName())); } final Field field; @@ -81,10 +123,10 @@ public class SettingManager { field = configuration.getDeclaredField(name); } catch (SecurityException | NoSuchFieldException ex) { LOGGER.warn(String.format( - "Failed to generate SettingInfo for field \"%s\" of " - + "configuration \"%s\". Ignoring field.", - configuration.getClass().getName(), - name), + "Failed to generate SettingInfo for field \"%s\" of " + + "configuration \"%s\". Ignoring field.", + configuration.getClass().getName(), + name), ex); return null; } @@ -96,7 +138,7 @@ public class SettingManager { final Setting settingAnnotation = field.getAnnotation(Setting.class); final SettingInfo settingInfo = new SettingInfo(); if (settingAnnotation.name() == null - || settingAnnotation.name().isEmpty()) { + || settingAnnotation.name().isEmpty()) { settingInfo.setName(field.getName()); } else { settingInfo.setName(settingAnnotation.name()); @@ -109,7 +151,7 @@ public class SettingManager { settingInfo.setDefaultValue(field.get(conf).toString()); } catch (InstantiationException | IllegalAccessException ex) { LOGGER.warn(String.format("Failed to create instance of \"%s\" to " - + "get default values.", + + "get default values.", configuration.getName()), ex); } @@ -117,16 +159,14 @@ public class SettingManager { settingInfo.setConfClass(configuration.getName()); settingInfo.setDescBundle(getDescBundle(configuration)); - if (settingAnnotation.labelKey() == null - || settingAnnotation.labelKey().isEmpty()) { + if (Strings.isBlank(settingAnnotation.labelKey())) { settingInfo.setLabelKey(String.join(".", field.getName(), "label")); } else { settingInfo.setLabelKey(name); } - if (settingAnnotation.descKey() == null - || settingAnnotation.descKey().isEmpty()) { + if (Strings.isBlank(settingAnnotation.descKey())) { settingInfo.setDescKey(String.join(".", field.getName(), "descripotion")); @@ -140,25 +180,25 @@ public class SettingManager { /** * A low level method for finding a setting in the registry. * - * @param Type of the value of the setting + * @param Type of the value of the setting * @param confName Name of the configuration to which the setting belongs - * @param name The fully qualified name of the setting. - * @param clazz The class of the setting. + * @param name The fully qualified name of the setting. + * @param clazz The class of the setting. * * @return The requested setting if it exists in the registry, {@code null} - * otherwise. + * otherwise. */ public AbstractSetting findSetting(final String confName, final String name, final Class clazz) { LOGGER.debug(String.format( - "Trying to find setting \"%s\" of type \"%s\"", - name, - clazz.getName())); + "Trying to find setting \"%s\" of type \"%s\"", + name, + clazz.getName())); final TypedQuery query = entityManager. - createNamedQuery("AbstractSetting.findByClassAndName", - AbstractSetting.class); + createNamedQuery("AbstractSetting.findByClassAndName", + AbstractSetting.class); query.setParameter("class", confName); query.setParameter("name", name); final List result = query.getResultList(); @@ -185,9 +225,9 @@ public class SettingManager { private String getDescBundle(final Class configuration) { final Configuration confAnnotation = configuration.getAnnotation( - Configuration.class); + Configuration.class); if (confAnnotation.descBundle() == null - || confAnnotation.descBundle().isEmpty()) { + || confAnnotation.descBundle().isEmpty()) { return String.join("", configuration.getClass().getName(), "Description"); @@ -195,4 +235,5 @@ public class SettingManager { return confAnnotation.descBundle(); } } + } diff --git a/ccm-core/src/main/java/org/libreccm/core/CcmCore.java b/ccm-core/src/main/java/org/libreccm/core/CcmCore.java index d4965303a..6ddd3e18a 100644 --- a/ccm-core/src/main/java/org/libreccm/core/CcmCore.java +++ b/ccm-core/src/main/java/org/libreccm/core/CcmCore.java @@ -18,12 +18,18 @@ */ package org.libreccm.core; +import com.arsdigita.bebop.BebopConfig; +import com.arsdigita.kernel.KernelConfig; +import com.arsdigita.mail.MailConfig; +import com.arsdigita.notification.NotificationConfig; import com.arsdigita.ui.admin.AdminApplicationCreator; import com.arsdigita.ui.admin.AdminServlet; import com.arsdigita.ui.admin.AdminApplicationSetup; import com.arsdigita.ui.login.LoginApplicationCreator; import com.arsdigita.ui.login.LoginServlet; import com.arsdigita.ui.login.LoginApplicationSetup; +import com.arsdigita.workflow.simple.WorkflowConfig; +import com.arsdigita.xml.XmlConfig; import org.libreccm.modules.CcmModule; import org.libreccm.modules.InitEvent; @@ -31,9 +37,10 @@ import org.libreccm.modules.InstallEvent; import org.libreccm.modules.Module; import org.libreccm.modules.ShutdownEvent; import org.libreccm.modules.UnInstallEvent; +import org.libreccm.security.EmailTemplates; +import org.libreccm.security.OneTimeAuthConfig; import org.libreccm.security.SystemUsersSetup; - import org.libreccm.web.ApplicationType; /** @@ -51,48 +58,23 @@ import org.libreccm.web.ApplicationType; singleton = true, creator = AdminApplicationCreator.class, servlet = AdminServlet.class)}, - entities = {org.libreccm.auditing.CcmRevision.class, - org.libreccm.categorization.Categorization.class, - org.libreccm.categorization.Category.class, - org.libreccm.categorization.Domain.class, - org.libreccm.categorization.DomainOwnership.class, - org.libreccm.core.CcmObject.class, - org.libreccm.core.Resource.class, - org.libreccm.core.ResourceType.class, - org.libreccm.modules.InstalledModule.class, - org.libreccm.formbuilder.Component.class, - org.libreccm.formbuilder.DataDrivenSelect.class, - org.libreccm.formbuilder.FormSection.class, - org.libreccm.formbuilder.Listener.class, - org.libreccm.formbuilder.MetaObject.class, - org.libreccm.formbuilder.ObjectType.class, - org.libreccm.formbuilder.Option.class, - org.libreccm.formbuilder.PersistentDataQuery.class, - org.libreccm.formbuilder.ProcessListener.class, - org.libreccm.formbuilder.Widget.class, - org.libreccm.formbuilder.WidgetLabel.class, - org.libreccm.formbuilder.actions.ConfirmEmailListener.class, - org.libreccm.formbuilder.actions.ConfirmRedirectListener.class, - org.libreccm.formbuilder.actions.RemoteServerPostListener.class, - org.libreccm.formbuilder.actions.SimpleEmailListener.class, - org.libreccm.formbuilder.actions.TemplateEmailListener.class, - org.libreccm.formbuilder.actions.XmlEmailListener.class, - org.libreccm.messaging.Attachment.class, - org.libreccm.messaging.Message.class, - org.libreccm.messaging.MessageThread.class, - org.libreccm.notification.Digest.class, - org.libreccm.notification.Notification.class, - org.libreccm.notification.QueueItem.class, - org.libreccm.portal.Portal.class, - org.libreccm.portal.Portlet.class, - org.libreccm.runtime.Initalizer.class, - org.libreccm.search.lucene.Document.class, - org.libreccm.search.lucene.Index.class, - org.libreccm.web.CcmApplication.class, - org.libreccm.web.Host.class, - org.libreccm.workflow.Task.class, - org.libreccm.workflow.UserTask.class, - org.libreccm.workflow.Workflow.class}) + configurations = { + com.arsdigita.bebop.BebopConfig.class, + com.arsdigita.dispatcher.DispatcherConfig.class, + com.arsdigita.globalization.GlobalizationConfig.class, + com.arsdigita.kernel.KernelConfig.class, + com.arsdigita.kernel.security.SecurityConfig.class, + com.arsdigita.mail.MailConfig.class, + com.arsdigita.notification.NotificationConfig.class, + com.arsdigita.templating.TemplatingConfig.class, + com.arsdigita.ui.UIConfig.class, + com.arsdigita.web.WebConfig.class, + com.arsdigita.workflow.simple.WorkflowConfig.class, + com.arsdigita.xml.XmlConfig.class, + com.arsdigita.xml.formatters.DateFormatterConfig.class, + org.libreccm.security.EmailTemplates.class, + org.libreccm.security.OneTimeAuthConfig.class, + }) public class CcmCore implements CcmModule { @Override @@ -100,13 +82,15 @@ public class CcmCore implements CcmModule { // final EntityManager entityManager = event.getEntityManager(); final SystemUsersSetup systemUsersSetup = new SystemUsersSetup( - event); + event); systemUsersSetup.setupSystemUsers(); - final AdminApplicationSetup adminSetup = new AdminApplicationSetup(event); + final AdminApplicationSetup adminSetup + = new AdminApplicationSetup(event); adminSetup.setup(); - - final LoginApplicationSetup loginSetup = new LoginApplicationSetup(event); + + final LoginApplicationSetup loginSetup + = new LoginApplicationSetup(event); loginSetup.setup(); } diff --git a/ccm-core/src/main/java/org/libreccm/modules/Module.java b/ccm-core/src/main/java/org/libreccm/modules/Module.java index 36cf3c9e5..6999f2045 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/Module.java +++ b/ccm-core/src/main/java/org/libreccm/modules/Module.java @@ -18,6 +18,8 @@ */ package org.libreccm.modules; +import org.libreccm.configuration.Configuration; +import org.libreccm.configuration.ConfigurationManager; import org.libreccm.web.ApplicationType; import java.lang.annotation.Retention; @@ -29,59 +31,63 @@ import java.lang.annotation.Target; /** * Annotation for describing some meta data of a module. - * + * * @author Jens Pelzetter */ @Target({TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Module { - - /** - * Name of the module, overriding the name provided by the pom.xml and the - * module info file of the module. - * - * @return The name of the module, overriding the value provided by the - * module-info file. + + /** + * Name of the module, overriding the name provided by the pom.xml and the + * module info file of the module. + * + * @return The name of the module, overriding the value provided by the + * module-info file. */ String name() default ""; - /** - * Package name of resources of the package like DB migrations, overriding - * default value constructed from the group id and the artifact id of the + /** + * Package name of resources of the package like DB migrations, overriding + * default value constructed from the group id and the artifact id of the * module. - * - * @return The package name for resources of the module. + * + * @return The package name for resources of the module. */ String packageName() default ""; - /** - * The version of module, overriding the value provided by the module info + /** + * The version of module, overriding the value provided by the module info * file of the module. - * - * @return The version of the module. + * + * @return The version of the module. */ String version() default ""; /** * Modules required by the annotated module. - * + * * @return An array of the dependencies of the module. */ RequiredModule[] requiredModules() default {}; - + /** * ApplicationType types provided by the annotated module. - * - * @return An array containing the type descriptions for all application - * types provided by the annotated module. + * + * @return An array containing the type descriptions for all application + * types provided by the annotated module. */ ApplicationType[] applicationTypes() default {}; - + /** - * The JPA entities provided by the annotated module. + * Configuration classes provided by the module. + * + * @return An array containing all configuration classes provided by the + * module. * - * @return An array with the JPA entity classes of the annotated module. + * @see Configuration + * @see ConfigurationManager */ - Class[] entities() default {}; + Class[] configurations() default {}; } diff --git a/ccm-core/src/main/java/org/libreccm/modules/ModuleInfo.java b/ccm-core/src/main/java/org/libreccm/modules/ModuleInfo.java index 99c94e062..64c019081 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/ModuleInfo.java +++ b/ccm-core/src/main/java/org/libreccm/modules/ModuleInfo.java @@ -59,10 +59,6 @@ public class ModuleInfo { * The data package of the module (group id). */ private String moduleDataPackage; - /** - * The entities provided by the module. - */ - private Class[] moduleEntities; /** * The version of the module. */ @@ -129,10 +125,6 @@ public class ModuleInfo { return moduleVersion; } - public List> getModuleEntities() { - return Collections.unmodifiableList(Arrays.asList(moduleEntities)); - } - public List getRequiredModules() { return Collections.unmodifiableList(Arrays.asList(requiredModules)); } @@ -168,7 +160,6 @@ public class ModuleInfo { LOGGER.info("Module version is \"{}.\"", moduleVersion); requiredModules = annotation.requiredModules(); - moduleEntities = annotation.entities(); } /** diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties index 40eb8d853..8fc0cae64 100644 --- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources.properties @@ -154,7 +154,7 @@ ui.admin.user.userpasswordform.retrieving_user_failed=Failed to retrieve user ui.admin.groups.couldnt_find_specified_group=Couldn't find the specified group ui.admin.tab.users_groups_roles.title=Users/Groups/Roles ui.admin.tab.categories.title=Categories -ui.admin.tab.registry.title=Registry +ui.admin.tab.configuration.title=Configuration ui.admin.change_password=Change password ui.admin.tab.workflows.title=Workflows ui.admin.users_groups_roles.users.title=Users @@ -408,3 +408,16 @@ ui.admin.categories.doamin_details.mappings.error.please_select_app=Please selec ui.admin.categories.domain_details.mappings.remove.confirm=Are you sure to remove this domain mapping? ui.admin.categories.tree.header=Categories ui.admin.categories.tree.back=Back to domain properties +ui.admin.configuration.classes.filter.heading=Filter configurations +ui.admin.configuration.classes.filter.submit=Apply +ui.admin.configuration.classes.filter.clear=Clear +ui.admin.configuration.configurations.none=No matching configurations found. +ui.admin.configuration.configurations.table.name=Name +ui.admin.configuration.configurations.table.desc=Description +ui.admin.configuration.settings.none=No settings +ui.admin.configuration.settings.table.col_setting_label.header=Setting +ui.admin.configuration.settings.table.col_setting_value.header=Value +ui.admin.configuration.settings.table.col_setting_desc.header=Description +ui.admin.configuration.settings.table.col_edit_setting.header=Edit +ui.admin.configuration.settings.edit=Edit +ui.admin.configuration.settings.read_error=Failed to read setting value. diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties index 160124f03..bfad98e8e 100644 --- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_de.properties @@ -154,7 +154,7 @@ ui.admin.user.userpasswordform.retrieving_user_failed=Konnte Benutzer nicht abru ui.admin.groups.couldnt_find_specified_group=Konnte die spezifische Gruppe nicht finden ui.admin.tab.users_groups_roles.title=Benutzer/Gruppen/Rollen ui.admin.tab.categories.title=Kategorien -ui.admin.tab.registry.title=Registry +ui.admin.tab.configuration.title=Konfiguration ui.admin.change_password=Passwort \u00e4ndern ui.admin.tab.workflows.title=Arbeitsabl\u00e4ufe ui.admin.users_groups_roles.users.title=Benutzer @@ -411,3 +411,16 @@ ui.admin.categories.doamin_details.mappings.error.please_select_app=Bitte w\u00e ui.admin.categories.domain_details.mappings.remove.confirm=Sind Sie sicher, dass Sie dieses Mapping entfernen wollen? ui.admin.categories.tree.header=Kategorien ui.admin.categories.tree.back=Zur\u00fcck zur Domain Eigenschaften +ui.admin.configuration.classes.filter.heading=Konfigurationen filtern +ui.admin.configuration.classes.filter.submit=Anwenden +ui.admin.configuration.classes.filter.clear=Zur\u00fccksetzen +ui.admin.configuration.configurations.none=Keine passenden Konfigurationen gefunden. +ui.admin.configuration.configurations.table.name=Name +ui.admin.configuration.configurations.table.desc=Beschreibung +ui.admin.configuration.settings.none=Keine Parameter +ui.admin.configuration.settings.table.col_setting_label.header=Parameter +ui.admin.configuration.settings.table.col_setting_value.header=Wert +ui.admin.configuration.settings.table.col_setting_desc.header=Beschreibung +ui.admin.configuration.settings.table.col_edit_setting.header=Bearbeiten +ui.admin.configuration.settings.edit=Bearbeiten +ui.admin.configuration.settings.read_error=Fehler beim lesen des Parameters. diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties index f653820f2..19d585bcc 100755 --- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_en.properties @@ -112,7 +112,7 @@ ui.admin.user.userpasswordform.retrieving_user_failed=Failed to retrieve user ui.admin.groups.couldnt_find_specified_group=Couldn't find the specified group ui.admin.tab.users_groups_roles.title=Users/Groups/Roles ui.admin.tab.categories.title=Categories -ui.admin.tab.registry.title=Registry +ui.admin.tab.configuration.title=Configuration ui.admin.change_password=Change password ui.admin.tab.workflows.title=Workflows ui.admin.tab.sysinfo.title=System information @@ -384,3 +384,16 @@ ui.admin.categories.doamin_details.mappings.error.please_select_app=Please selec ui.admin.categories.domain_details.mappings.remove.confirm=Are you sure to remove this domain mapping? ui.admin.categories.tree.header=Categories ui.admin.categories.tree.back=Back to domain properties +ui.admin.configuration.classes.filter.heading=Filter configurations +ui.admin.configuration.classes.filter.submit=Apply +ui.admin.configuration.classes.filter.clear=Clear +ui.admin.configuration.configurations.none=No matching configurations found. +ui.admin.configuration.configurations.table.name=Name +ui.admin.configuration.configurations.table.desc=Description +ui.admin.configuration.settings.none=No settings +ui.admin.configuration.settings.table.col_setting_label.header=Setting +ui.admin.configuration.settings.table.col_setting_value.header=Value +ui.admin.configuration.settings.table.col_setting_desc.header=Description +ui.admin.configuration.settings.table.col_edit_setting.header=Edit +ui.admin.configuration.settings.edit=Edit +ui.admin.configuration.settings.read_error=Failed to read setting value. diff --git a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties index e35af5525..d621fff92 100755 --- a/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties +++ b/ccm-core/src/main/resources/com/arsdigita/ui/admin/AdminResources_fr.properties @@ -97,7 +97,7 @@ ui.admin.user.userpasswordform.retrieving_user_failed=Impossible de retrouver l' ui.admin.groups.couldnt_find_specified_group=Impossible de trouver le groupe sp\u00e9cifi\u00e9 ui.admin.tab.users_groups_roles.title= ui.admin.tab.categories.title=Categories -ui.admin.tab.registry.title=Registry +ui.admin.tab.configuration.title=Configuration ui.admin.change_password=Change password ui.admin.tab.workflows.title=Workflows ui.admin.save=Save @@ -375,3 +375,16 @@ ui.admin.categories.doamin_details.mappings.error.please_select_app=Please selec ui.admin.categories.domain_details.mappings.remove.confirm=Are you sure to remove this domain mapping? ui.admin.categories.tree.header=Categories ui.admin.categories.tree.back=Back to domain properties +ui.admin.configuration.classes.filter.heading=Filter configurations +ui.admin.configuration.classes.filter.submit=Apply +ui.admin.configuration.classes.filter.clear=Clear +ui.admin.configuration.configurations.none=No matching configurations found. +ui.admin.configuration.configurations.table.name=Name +ui.admin.configuration.configurations.table.desc=Description +ui.admin.configuration.settings.none=No settings +ui.admin.configuration.settings.table.col_setting_label.header=Setting +ui.admin.configuration.settings.table.col_setting_value.header=Value +ui.admin.configuration.settings.table.col_setting_desc.header=Description +ui.admin.configuration.settings.table.col_edit_setting.header=Edit +ui.admin.configuration.settings.edit=Edit +ui.admin.configuration.settings.read_error=Failed to read setting value.