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;
+ }
}