diff --git a/.gitignore b/.gitignore index 37086e31701..3645d0958bf 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ bin /sormas-e2e-tests/logs .DS_Store + +/.run-configs \ No newline at end of file diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Validations.java b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Validations.java index 0522c3698e9..3a6f7e6d1a4 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Validations.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/i18n/Validations.java @@ -274,6 +274,8 @@ public interface Validations { String systemConfigurationValueInvalidKey = "systemConfigurationValueInvalidKey"; String systemConfigurationValueInvalidValue = "systemConfigurationValueInvalidValue"; String systemConfigurationValuePatternNotMatched = "systemConfigurationValuePatternNotMatched"; + String systemConfigurationValueValidationInvalidBackgroundColor = "systemConfigurationValueValidationInvalidBackgroundColor"; + String systemConfigurationValueValidationMenuSubtitle = "systemConfigurationValueValidationMenuSubtitle"; String systemConfigurationValueValidationNotADirectory = "systemConfigurationValueValidationNotADirectory"; String systemConfigurationValueValidationNotAEmail = "systemConfigurationValueValidationNotAEmail"; String systemConfigurationValueValidationNotAFile = "systemConfigurationValueValidationNotAFile"; diff --git a/sormas-api/src/main/resources/validations.properties b/sormas-api/src/main/resources/validations.properties index 4476e27f881..e4b5a628975 100644 --- a/sormas-api/src/main/resources/validations.properties +++ b/sormas-api/src/main/resources/validations.properties @@ -329,4 +329,6 @@ systemConfigurationValueValidationNotAEmail = Value is not a valid email address systemConfigurationValueValidationNotAValidEmailsenderName = Value is not a valid email sender name. Name should contain only alphabets. systemConfigurationValueValidationNotAValidSmsSenderName = Value is not a valid name. Name should contain only letters and numbers without spaces & special characters. For more info please see https://developer.vonage.com/en/messaging/sms/guides/custom-sender-id . smsAuthKeyValueValidation = SMS Auth key value is not valid -smsAuthSecretValueValidation = SMS Auth secret value is not valid \ No newline at end of file +smsAuthSecretValueValidation = SMS Auth secret value is not valid +systemConfigurationValueValidationInvalidBackgroundColor = Pre-defined values are: green, red, indigo, gray, default (case-sensitive) otherwise must match hexadecimal format, example: #dd2b0e or #4AA +systemConfigurationValueValidationMenuSubtitle = Can be empty or up to 16 (any) characters. Can be used to define name of the environment: PRODUCTION - TEST etc. diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/common/StartupShutdownService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/common/StartupShutdownService.java index ffdff6e44c2..c1c653d02eb 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/common/StartupShutdownService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/common/StartupShutdownService.java @@ -697,6 +697,8 @@ private void updateDatabase(EntityManager entityManager, String schemaFileName) databaseVersion = null; } + logger.info("Current databaseVersion: [{}]", databaseVersion); + try (InputStream schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(schemaFileName); Scanner scanner = new Scanner(schemaStream, StandardCharsets.UTF_8.name())) { StringBuilder nextUpdateBuilder = new StringBuilder(); diff --git a/sormas-backend/src/main/resources/sql/sormas_schema.sql b/sormas-backend/src/main/resources/sql/sormas_schema.sql index 3b941bd6e76..1bfb19afe3c 100644 --- a/sormas-backend/src/main/resources/sql/sormas_schema.sql +++ b/sormas-backend/src/main/resources/sql/sormas_schema.sql @@ -15062,4 +15062,58 @@ ALTER TABLE testreport_history ADD COLUMN serotype character varying(255); ALTER TABLE testreport_history ADD COLUMN straincallstatus character varying(255); INSERT INTO schema_version (version_number, comment) VALUES (602, 'External message additional fields'); --- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** + +ALTER TABLE systemconfigurationvalue ADD CONSTRAINT uk_systemconfigurationvalue_config_key UNIQUE (config_key); + +DO +$$ + DECLARE + general_configuration_id bigint; + + BEGIN + -- Check if the category 'GENERAL_CATEGORY' already exists + SELECT id + INTO general_configuration_id + FROM systemconfigurationcategory + WHERE name = 'GENERAL_CATEGORY'; + + -- Insert the general category if it doesn't exist + IF + general_configuration_id IS NULL THEN + INSERT INTO systemconfigurationcategory(id, uuid, changedate, creationdate, name, caption, description) + VALUES (nextval('entity_seq'), generate_base32_uuid(), now(), now(), 'GENERAL_CATEGORY', 'i18n/General/categoryGeneral', + 'i18n/General/categoryGeneral') + RETURNING id INTO general_configuration_id; + END IF; + + -- Insert new configurations if missing + -- Background color + INSERT INTO systemconfigurationvalue(config_key, config_value, category_id, value_optional, value_pattern, + value_encrypt, data_provider, validation_message, changedate, creationdate, + id, + uuid) + VALUES ('MENU_BACKGROUND_COLOR', 'default', general_configuration_id, true, + '^(default|red|green|indigo|gray)|(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}))$', false, + null, + 'i18n/systemConfigurationValueValidationInvalidBackgroundColor', now(), now(), nextval('entity_seq'), + generate_base32_uuid()) + ON CONFLICT (config_key) DO NOTHING; + + -- Menu Subtitle + INSERT INTO systemconfigurationvalue(config_key, config_value, category_id, value_optional, value_pattern, + value_encrypt, data_provider, validation_message, changedate, creationdate, + id, + uuid) + VALUES ('MENU_SUBTITLE', '', general_configuration_id, true, + '^\w{0,16}$', false, + null, + 'i18n/systemConfigurationValueValidationMenuSubtitle', now(), now(), nextval('entity_seq'), + generate_base32_uuid()) + ON CONFLICT (config_key) DO NOTHING; + + END +$$ +LANGUAGE plpgsql; + +INSERT INTO schema_version (version_number, comment) VALUES (603, 'System configuration: to visually distinguish different system types'); +-- *** Insert new sql commands BEFORE this line. Remember to always consider _history tables. *** \ No newline at end of file diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/Menu.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/Menu.java index f3c8976dd29..e5cb6fdf04c 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/Menu.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/Menu.java @@ -1,17 +1,17 @@ /******************************************************************************* * SORMAS® - Surveillance Outbreak Response Management & Analysis System * Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) - * + *

* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + *

* This program 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 General Public License for more details. - * + *

* You should have received a copy of the GNU General Public License * along with this program. If not, see . *******************************************************************************/ @@ -22,12 +22,15 @@ import java.util.HashMap; import java.util.Map; +import javax.validation.constraints.NotNull; + import org.apache.commons.lang3.StringUtils; import com.vaadin.icons.VaadinIcons; import com.vaadin.navigator.Navigator; import com.vaadin.navigator.View; import com.vaadin.server.FileResource; +import com.vaadin.server.Page; import com.vaadin.server.Resource; import com.vaadin.server.ThemeResource; import com.vaadin.ui.Alignment; @@ -39,6 +42,7 @@ import com.vaadin.ui.MenuBar; import com.vaadin.ui.MenuBar.Command; import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; import com.vaadin.ui.themes.ValoTheme; @@ -57,30 +61,47 @@ /** * Responsive navigation menu presenting a list of available views to the user. */ -@SuppressWarnings("serial") public class Menu extends CssLayout { private static final String VALO_MENUITEMS = "valo-menuitems"; private static final String VALO_MENU_TOGGLE = "valo-menu-toggle"; private static final String VALO_MENU_VISIBLE = "valo-menu-visible"; - private Navigator navigator; - private Map viewButtons = new HashMap(); - private CssLayout menuItemsLayout; - private CssLayout menuPart; + private static final String BACKGROUND_COLOR_CONFIG_KEY = "MENU_BACKGROUND_COLOR"; + private static final String MENU_SUBTITLE_CONFIG_KEY = "MENU_SUBTITLE"; + + private static final String DEFAULT_BACKGROUND_COLOR_NAME = "default"; + + private static final String COLOR_BACKGROUND_STYLE_NAME = "color-background"; + private static final String MENU_SUBTITLE_STYLE_NAME = "menu-subtitle"; + private static final String TOP_MENU_CONTAINER_STYLE_NAME = "top-menu-container"; + + private final Navigator navigator; + private final Map viewButtons = new HashMap<>(); + + private final CssLayout menuItemsLayout; + private final CssLayout menuPart; public Menu(Navigator navigator) { this.navigator = navigator; setPrimaryStyleName(ValoTheme.MENU_ROOT); + + defineCustomColorBackgroundIfSystemConfigured(); + menuPart = new CssLayout(); menuPart.addStyleName(ValoTheme.MENU_PART); // header of the menu - final HorizontalLayout top = new HorizontalLayout(); + VerticalLayout topContainer = new VerticalLayout(); + topContainer.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER); + CssStyles.style(topContainer, ValoTheme.MENU_TITLE, TOP_MENU_CONTAINER_STYLE_NAME); + topContainer.setSpacing(false); + + HorizontalLayout top = new HorizontalLayout(); top.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER); - top.addStyleName(ValoTheme.MENU_TITLE); top.setSpacing(true); + Label title = new Label(FacadeProvider.getConfigFacade().getSormasInstanceName()); title.setSizeUndefined(); @@ -95,8 +116,13 @@ public Menu(Navigator navigator) { CssStyles.style(image, ValoTheme.MENU_LOGO, ValoTheme.BUTTON_LINK); top.addComponent(image); top.addComponent(title); - top.addLayoutClickListener(listener -> SormasUI.get().getNavigator().navigateTo(SurveillanceDashboardView.VIEW_NAME)); - menuPart.addComponent(top); + + topContainer.addLayoutClickListener(listener -> SormasUI.get().getNavigator().navigateTo(SurveillanceDashboardView.VIEW_NAME)); + topContainer.addComponent(top); + + defineMenuSubtitleIfSystemConfigured(topContainer); + + menuPart.addComponent(topContainer); // button for toggling the visibility of the menu when on a small screen final Button showMenu = ButtonHelper.createIconButton(Captions.menu, VaadinIcons.MENU, event -> { @@ -136,14 +162,70 @@ public Menu(Navigator navigator) { addComponent(menuPart); } + private void defineCustomColorBackgroundIfSystemConfigured() { + String backgroundColor = FacadeProvider.getSystemConfigurationValueFacade().getValue(BACKGROUND_COLOR_CONFIG_KEY); + + if (StringUtils.isBlank(backgroundColor) || DEFAULT_BACKGROUND_COLOR_NAME.equals(backgroundColor)) { + // no need to configure anything as we can keep the default color. + return; + } + + addStyleName(COLOR_BACKGROUND_STYLE_NAME); + + String actualColorBackgroundColor = determineActualColor(backgroundColor); + + Page.Styles styles = Page.getCurrent().getStyles(); + // specifying !important as inline CSS has lower precedence + styles.add( + ".valo-menu-color-background {\n" + " background-color: " + actualColorBackgroundColor + " !important;\n" + + " background-image: -webkit-linear-gradient(right, " + actualColorBackgroundColor + " 0%, #EEFA5F" + actualColorBackgroundColor + + " 8px) !important;\n" + " background-image: linear-gradient(to left, " + actualColorBackgroundColor + " 0%, " + + actualColorBackgroundColor + " 8px) !important;" + " }"); + } + + private static void defineMenuSubtitleIfSystemConfigured(VerticalLayout topContainer) { + String menuSubtitle = FacadeProvider.getSystemConfigurationValueFacade().getValue(MENU_SUBTITLE_CONFIG_KEY); + + if (StringUtils.isBlank(menuSubtitle)) { + // no need to configure anything as no subtitled must be added + return; + } + + Label systemConfigurationLabel = new Label(menuSubtitle); + systemConfigurationLabel.setSizeUndefined(); + CssStyles.style(systemConfigurationLabel, CssStyles.LABEL_UNDERLINE, MENU_SUBTITLE_STYLE_NAME); + + topContainer.addComponent(systemConfigurationLabel); + } + + private static @NotNull String determineActualColor(String backgroundColor) { + String actualColorBackgroundColor; + switch (backgroundColor) { + case "green": + actualColorBackgroundColor = "#108548"; + break; + case "red": + actualColorBackgroundColor = "#dd2b0e"; + break; + case "indigo": + actualColorBackgroundColor = "#7b58cf"; + break; + case "gray": + actualColorBackgroundColor = "#737278"; + break; + default: + actualColorBackgroundColor = backgroundColor; + } + return actualColorBackgroundColor; + } + private void showSettingsPopup() { Window window = VaadinUiUtil.createPopupWindow(); window.setCaption(I18nProperties.getString(Strings.headingUserSettings)); window.setModal(true); - CommitDiscardWrapperComponent component = - ControllerProvider.getUserController().getUserSettingsComponent(() -> window.close()); + CommitDiscardWrapperComponent component = ControllerProvider.getUserController().getUserSettingsComponent(window::close); window.setContent(component); UI.getCurrent().addWindow(window); diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java index efedfdc6338..16f23ec8d98 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CssStyles.java @@ -116,6 +116,7 @@ private CssStyles() { public static final String LABEL_BOLD = "bold"; public static final String LABEL_UPPERCASE = "uppercase"; public static final String LABEL_ITALIC = "italic"; + public static final String LABEL_UNDERLINE = "underline"; // Label styles public static final String LABEL_BOTTOM_LINE = "bottom-line"; diff --git a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/menu.scss b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/menu.scss index 1a203e41ed6..15e64db8478 100644 --- a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/menu.scss +++ b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/components/menu.scss @@ -63,17 +63,17 @@ $valo-menu-background-color: $s-default-color !default; &::before { position: absolute; top: -12px; - right: 0px; + right: 0; width: 4px; height: 74px; - background-color: $v-selection-color; + background-color: $v-selection-color; content: ""; } } } .v-menubar-user-menu { - margin: 8px 0px; + margin: 8px 0; text-align: left; } } @@ -124,5 +124,13 @@ $valo-menu-background-color: $s-default-color !default; .valo-menu .v-menubar-user-menu .v-menubar-menuitem-caption { width: auto; } - } + } + + .top-menu-container { + max-height: 115px; + } + + .menu-subtitle { + font-size: 24px; + } } diff --git a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/global.scss b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/global.scss index 289ac635002..f3778e39b20 100644 --- a/sormas-ui/src/main/webapp/VAADIN/themes/sormas/global.scss +++ b/sormas-ui/src/main/webapp/VAADIN/themes/sormas/global.scss @@ -530,4 +530,8 @@ background-color: #FFFFFF !important; border-radius: 24px; } + + .underline { + text-decoration: underline; + } }