From 6c9bc4cfacdd26bdb1ac5cdaf0e9cd651f86deaf Mon Sep 17 00:00:00 2001 From: Henrique Sato Date: Fri, 6 Mar 2026 16:42:18 -0300 Subject: [PATCH] Add support for new variables to the GUI whitelabel runtime system --- .../gui/theme/GuiThemeServiceImpl.java | 110 +----------------- .../JsonConfigAttributeValidator.java | 27 +++++ .../config/validator/JsonConfigValidator.java | 76 ++++++++++++ .../validator/attributes/AttributeBase.java | 72 ++++++++++++ .../validator/attributes/ErrorAttribute.java | 32 +++++ .../attributes/PluginsAttribute.java | 60 ++++++++++ .../validator/attributes/ThemeAttribute.java | 35 ++++++ .../attributes/UserCardAttribute.java | 80 +++++++++++++ .../core/spring-server-core-misc-context.xml | 5 + ui/src/components/view/Setting.vue | 2 +- ui/src/utils/guiTheme.js | 27 ++++- 11 files changed, 415 insertions(+), 111 deletions(-) create mode 100644 server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/JsonConfigAttributeValidator.java create mode 100644 server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/JsonConfigValidator.java create mode 100644 server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/AttributeBase.java create mode 100644 server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ErrorAttribute.java create mode 100644 server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/PluginsAttribute.java create mode 100644 server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ThemeAttribute.java create mode 100644 server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/UserCardAttribute.java diff --git a/server/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeServiceImpl.java b/server/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeServiceImpl.java index 1cb80e3dc8d3..9a92b9bef013 100644 --- a/server/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeServiceImpl.java @@ -27,11 +27,6 @@ import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.exception.CloudRuntimeException; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.command.user.gui.theme.CreateGuiThemeCmd; import org.apache.cloudstack.api.command.user.gui.theme.ListGuiThemesCmd; @@ -43,6 +38,7 @@ import org.apache.cloudstack.gui.theme.dao.GuiThemeDao; import org.apache.cloudstack.gui.theme.dao.GuiThemeDetailsDao; import org.apache.cloudstack.gui.theme.dao.GuiThemeJoinDao; +import org.apache.cloudstack.gui.theme.json.config.validator.JsonConfigValidator; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -52,24 +48,12 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Map; -import java.util.Set; @Component public class GuiThemeServiceImpl implements GuiThemeService { protected Logger logger = LogManager.getLogger(getClass()); - private static final List ALLOWED_PRIMITIVE_PROPERTIES = List.of("appTitle", "footer", "loginFooter", "logo", "minilogo", "banner"); - - private static final List ALLOWED_ERROR_PROPERTIES = List.of("403", "404", "500"); - - private static final List ALLOWED_PLUGIN_PROPERTIES = List.of("name", "path", "icon", "isExternalLink"); - - private static final String ERROR = "error"; - - private static final String PLUGINS = "plugins"; - @Inject GuiThemeDao guiThemeDao; @@ -91,6 +75,9 @@ public class GuiThemeServiceImpl implements GuiThemeService { @Inject DomainDao domainDao; + @Inject + JsonConfigValidator jsonConfigValidator; + @Override public ListResponse listGuiThemes(ListGuiThemesCmd cmd) { ListResponse response = new ListResponse<>(); @@ -244,94 +231,7 @@ protected void validateParameters(String jsonConfig, String domainIds, String ac validateObjectUuids(accountIds, Account.class); validateObjectUuids(domainIds, Domain.class); - validateJsonConfiguration(jsonConfig); - } - - protected void validateJsonConfiguration(String jsonConfig) { - if (jsonConfig == null) { - return; - } - - JsonObject jsonObject = new JsonObject(); - - try { - JsonElement jsonElement = new JsonParser().parse(jsonConfig); - Set> entries = jsonElement.getAsJsonObject().entrySet(); - entries.stream().forEach(entry -> validateJsonAttributes(entry, jsonObject)); - } catch (JsonSyntaxException exception) { - logger.error("The following exception was thrown while parsing the JSON object: [{}].", exception.getMessage()); - throw new CloudRuntimeException("Specified JSON configuration is not a valid JSON object."); - } - } - - /** - * Validates the informed JSON attributes considering the allowed properties by the API, any invalid option is ignored. - * All valid options are added to a {@link JsonObject} that will be considered as the final JSON configuration used by the GUI theme. - */ - private void validateJsonAttributes(Map.Entry entry, JsonObject jsonObject) { - JsonElement entryValue = entry.getValue(); - String entryKey = entry.getKey(); - - if (entryValue.isJsonPrimitive() && ALLOWED_PRIMITIVE_PROPERTIES.contains(entryKey)) { - logger.trace("The JSON attribute [{}] is a valid option.", entryKey); - jsonObject.add(entryKey, entryValue); - } else if (entryValue.isJsonObject() && ERROR.equals(entryKey)) { - validateErrorAttribute(entry, jsonObject); - } else if (entryValue.isJsonArray() && PLUGINS.equals(entryKey)) { - validatePluginsAttribute(entry, jsonObject); - } else { - warnOfInvalidJsonAttribute(entryKey); - } - } - - /** - * Creates a {@link JsonObject} with only the valid options for the Plugins' properties specified in the {@link #ALLOWED_PLUGIN_PROPERTIES}. - */ - protected void validatePluginsAttribute(Map.Entry entry, JsonObject jsonObject) { - Set> entries = entry.getValue().getAsJsonArray().get(0).getAsJsonObject().entrySet(); - JsonObject objectToBeAdded = createJsonObject(entries, ALLOWED_PLUGIN_PROPERTIES); - JsonArray jsonArray = new JsonArray(); - - if (objectToBeAdded.entrySet().isEmpty()) { - return; - } - - jsonArray.add(objectToBeAdded); - jsonObject.add(entry.getKey(), jsonArray); - } - - /** - * Creates a {@link JsonObject} with only the valid options for the Error's properties specified in the {@link #ALLOWED_ERROR_PROPERTIES}. - */ - protected void validateErrorAttribute(Map.Entry entry, JsonObject jsonObject) { - Set> entries = entry.getValue().getAsJsonObject().entrySet(); - JsonObject objectToBeAdded = createJsonObject(entries, ALLOWED_ERROR_PROPERTIES); - - if (objectToBeAdded.entrySet().isEmpty()) { - return; - } - - jsonObject.add(entry.getKey(), objectToBeAdded); - } - - protected JsonObject createJsonObject(Set> entries, List allowedProperties) { - JsonObject objectToBeAdded = new JsonObject(); - - for (Map.Entry recursiveEntry : entries) { - String entryKey = recursiveEntry.getKey(); - - if (!allowedProperties.contains(entryKey)) { - warnOfInvalidJsonAttribute(entryKey); - continue; - } - objectToBeAdded.add(entryKey, recursiveEntry.getValue()); - } - - return objectToBeAdded; - } - - protected void warnOfInvalidJsonAttribute(String entryKey) { - logger.warn("The JSON attribute [{}] is not a valid option, therefore, it will be ignored.", entryKey); + jsonConfigValidator.validateJsonConfiguration(jsonConfig); } /** diff --git a/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/JsonConfigAttributeValidator.java b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/JsonConfigAttributeValidator.java new file mode 100644 index 000000000000..a3d6a7b4ef9d --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/JsonConfigAttributeValidator.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.json.config.validator; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.Map; + +public interface JsonConfigAttributeValidator { + + void validate(Map.Entry entry, JsonObject jsonObject); +} diff --git a/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/JsonConfigValidator.java b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/JsonConfigValidator.java new file mode 100644 index 000000000000..7e8e5a6004c5 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/JsonConfigValidator.java @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.json.config.validator; + +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.inject.Inject; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class JsonConfigValidator { + protected Logger logger = LogManager.getLogger(getClass()); + + private static final List ALLOWED_PRIMITIVE_PROPERTIES = List.of("appTitle", "footer", "loginFooter", "logo", "minilogo", "banner", "docBase", "apidocs"); + private static final List ALLOWED_DYNAMIC_PROPERTIES = List.of("error", "theme", "plugins", "keyboardOptions", "userCard", "docHelpMappings"); + + @Inject + private List attributes; + + public void validateJsonConfiguration(String jsonConfig) { + if (StringUtils.isBlank(jsonConfig)) { + return; + } + + JsonObject jsonObject = new JsonObject(); + + try { + JsonElement jsonElement = JsonParser.parseString(jsonConfig); + Set> entries = jsonElement.getAsJsonObject().entrySet(); + entries.forEach(entry -> validateJsonAttributes(entry, jsonObject)); + } catch (JsonSyntaxException exception) { + logger.error("The following exception was thrown while parsing the JSON object: [{}].", exception.getMessage()); + throw new CloudRuntimeException("Specified JSON configuration is not a valid JSON object."); + } + } + + /** + * Validates the informed JSON attributes considering the allowed properties by the API, any invalid option is ignored. + * All valid options are added to a {@link JsonObject} that will be considered as the final JSON configuration used by the GUI theme. + */ + private void validateJsonAttributes(Map.Entry entry, JsonObject jsonObject) { + JsonElement entryValue = entry.getValue(); + String entryKey = entry.getKey(); + + if (entryValue.isJsonPrimitive() && ALLOWED_PRIMITIVE_PROPERTIES.contains(entryKey)) { + logger.trace("The JSON attribute [{}] is a valid option.", entryKey); + jsonObject.add(entryKey, entryValue); + } else if (ALLOWED_DYNAMIC_PROPERTIES.contains(entryKey)) { + attributes.forEach(attribute -> attribute.validate(entry, jsonObject)); + } else { + logger.warn("The JSON attribute [{}] is not a valid option, therefore, it will be ignored.", entryKey); + } + } +} diff --git a/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/AttributeBase.java b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/AttributeBase.java new file mode 100644 index 000000000000..b4bafb1f5da5 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/AttributeBase.java @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.json.config.validator.attributes; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.cloudstack.gui.theme.json.config.validator.JsonConfigAttributeValidator; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public abstract class AttributeBase implements JsonConfigAttributeValidator { + protected Logger logger = LogManager.getLogger(getClass()); + + protected abstract String getAttributeName(); + protected abstract List getAllowedProperties(); + + @Override + public void validate(Map.Entry entry, JsonObject jsonObject) { + if (!getAttributeName().equals(entry.getKey())) { + return; + } + + Set> entries = entry.getValue().getAsJsonObject().entrySet(); + JsonObject objectToBeAdded = createJsonObject(entries, getAllowedProperties()); + + if (!objectToBeAdded.entrySet().isEmpty()) { + jsonObject.add(entry.getKey(), objectToBeAdded); + } + } + + /** + * Creates a {@link JsonObject} with only the valid options for the attribute properties specified in the allowedProperties parameter. + */ + public JsonObject createJsonObject(Set> entries, List allowedProperties) { + JsonObject objectToBeAdded = new JsonObject(); + + for (Map.Entry recursiveEntry : entries) { + String entryKey = recursiveEntry.getKey(); + + if (!allowedProperties.contains(entryKey)) { + warnOfInvalidJsonAttribute(entryKey); + continue; + } + objectToBeAdded.add(entryKey, recursiveEntry.getValue()); + } + + logger.trace("JSON object with valid options: {}.", objectToBeAdded); + return objectToBeAdded; + } + + protected void warnOfInvalidJsonAttribute(String entryKey) { + logger.warn("The JSON attribute [{}] is not a valid option, therefore, it will be ignored.", entryKey); + } +} diff --git a/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ErrorAttribute.java b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ErrorAttribute.java new file mode 100644 index 000000000000..f33dfacaabab --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ErrorAttribute.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.json.config.validator.attributes; + +import java.util.List; + +public class ErrorAttribute extends AttributeBase { + + @Override + protected String getAttributeName() { + return "error"; + } + + @Override + protected List getAllowedProperties() { + return List.of("403", "404", "500"); + } +} diff --git a/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/PluginsAttribute.java b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/PluginsAttribute.java new file mode 100644 index 000000000000..5fdb5bda2ea6 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/PluginsAttribute.java @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.json.config.validator.attributes; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class PluginsAttribute extends AttributeBase { + + @Override + protected String getAttributeName() { + return "plugins"; + } + + @Override + protected List getAllowedProperties() { + return List.of("name", "path", "icon", "isExternalLink"); + } + + @Override + public void validate(Map.Entry entry, JsonObject jsonObject) { + if (!getAttributeName().equals(entry.getKey())) { + return; + } + + JsonArray jsonArrayResult = new JsonArray(); + JsonArray sourceJsonArray = entry.getValue().getAsJsonArray(); + for (JsonElement jsonElement : sourceJsonArray) { + Set> pluginEntries = jsonElement.getAsJsonObject().entrySet(); + JsonObject pluginObjectToBeAdded = createJsonObject(pluginEntries, getAllowedProperties()); + + if (pluginObjectToBeAdded.entrySet().isEmpty()) { + return; + } + + jsonArrayResult.add(pluginObjectToBeAdded); + } + + jsonObject.add(entry.getKey(), jsonArrayResult); + } +} diff --git a/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ThemeAttribute.java b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ThemeAttribute.java new file mode 100644 index 000000000000..7e08cc868a86 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ThemeAttribute.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.json.config.validator.attributes; + +import java.util.List; + +public class ThemeAttribute extends AttributeBase { + + @Override + protected String getAttributeName() { + return "theme"; + } + + @Override + protected List getAllowedProperties() { + return List.of("@layout-mode", "@logo-background-color", "@mini-logo-background-color", "@navigation-background-color", + "@project-nav-background-color", "@project-nav-text-color", "@navigation-text-color", "@primary-color", "@link-color", "@link-hover-color", "@loading-color", "@processing-color", + "@success-color", "@warning-color", "@error-color", "@font-size-base", "@heading-color", "@text-color", "@text-color-secondary", "@disabled-color", "@border-color-base", "@border-radius-base", + "@box-shadow-base", "@logo-width", "@logo-height", "@mini-logo-width", "@mini-logo-height", "@banner-width", "@banner-height", "@error-width", "@error-height"); + } +} diff --git a/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/UserCardAttribute.java b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/UserCardAttribute.java new file mode 100644 index 000000000000..a391614228f4 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/UserCardAttribute.java @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.json.config.validator.attributes; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class UserCardAttribute extends AttributeBase { + private static final List ALLOWED_USER_CARD_LINKS_PROPERTIES = List.of("title", "text", "link", "icon"); + private static final String LINKS = "links"; + + @Override + protected String getAttributeName() { + return "userCard"; + } + + @Override + protected List getAllowedProperties() { + return List.of("title", "icon", "links"); + } + + @Override + public void validate(Map.Entry entry, JsonObject jsonObject) { + if (!getAttributeName().equals(entry.getKey())) { + return; + } + + Set> entries = entry.getValue().getAsJsonObject().entrySet(); + JsonObject objectToBeAdded = new JsonObject(); + for (Map.Entry recursiveEntry : entries) { + String entryKey = recursiveEntry.getKey(); + + if (!getAllowedProperties().contains(entryKey)) { + warnOfInvalidJsonAttribute(entryKey); + continue; + } + + if (LINKS.equals(entryKey)) { + createLinkJsonObject(recursiveEntry, jsonObject); + } + + objectToBeAdded.add(entryKey, recursiveEntry.getValue()); + } + } + + private void createLinkJsonObject(Map.Entry entry, JsonObject jsonObject) { + JsonArray jsonArrayResult = new JsonArray(); + JsonArray sourceJsonArray = entry.getValue().getAsJsonArray(); + for (JsonElement jsonElement : sourceJsonArray) { + Set> linkEntries = jsonElement.getAsJsonObject().entrySet(); + JsonObject linkObjectToBeAdded = createJsonObject(linkEntries, ALLOWED_USER_CARD_LINKS_PROPERTIES); + + if (linkObjectToBeAdded.entrySet().isEmpty()) { + return; + } + + jsonArrayResult.add(linkObjectToBeAdded); + } + jsonObject.add(entry.getKey(), jsonArrayResult); + } +} diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml index f4fd57d59fc4..755c50f7a3a3 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml @@ -83,4 +83,9 @@ + + + + + diff --git a/ui/src/components/view/Setting.vue b/ui/src/components/view/Setting.vue index 4987a0bd6121..5f1db1445ee7 100644 --- a/ui/src/components/view/Setting.vue +++ b/ui/src/components/view/Setting.vue @@ -251,7 +251,7 @@ export default { this.parentToggleSetting(false) }, downloadSetting () { - this.downloadObjectAsJson(this.uiSettings) + this.downloadObjectAsJson(this.$config.theme) }, resetSetting () { this.uiSettings = {} diff --git a/ui/src/utils/guiTheme.js b/ui/src/utils/guiTheme.js index b1a7209fd270..be6b1916c5e6 100644 --- a/ui/src/utils/guiTheme.js +++ b/ui/src/utils/guiTheme.js @@ -69,12 +69,14 @@ async function applyDynamicCustomization (response) { vueProps.$config.logo = jsonConfig?.logo ?? vueProps.$config.logo vueProps.$config.minilogo = jsonConfig?.minilogo ?? vueProps.$config.minilogo vueProps.$config.banner = jsonConfig?.banner ?? vueProps.$config.banner + vueProps.$config.docBase = jsonConfig?.docBase ?? vueProps.$config.docBase + vueProps.$config.apidocs = jsonConfig?.apidocs ?? vueProps.$config.apidocs + vueProps.$config.docHelpMappings = jsonConfig?.docHelpMappings ?? vueProps.$config.docHelpMappings + vueProps.$config.keyboardOptions = jsonConfig?.keyboardOptions ?? vueProps.$config.keyboardOptions - if (jsonConfig?.error) { - vueProps.$config.error[403] = jsonConfig?.error[403] ?? vueProps.$config.error[403] - vueProps.$config.error[404] = jsonConfig?.error[404] ?? vueProps.$config.error[404] - vueProps.$config.error[500] = jsonConfig?.error[500] ?? vueProps.$config.error[500] - } + applyJsonConfigToObject(jsonConfig?.error, vueProps.$config.error) + applyJsonConfigToObject(jsonConfig?.userCard, vueProps.$config.userCard) + applyJsonConfigToObject(jsonConfig?.theme, vueProps.$config.theme) if (jsonConfig?.plugins) { jsonConfig.plugins.forEach(plugin => { @@ -82,12 +84,27 @@ async function applyDynamicCustomization (response) { }) } + if (vueProps.$store) { + vueProps.$store.dispatch('SetDarkMode', (vueProps.$config.theme['@layout-mode'] === 'dark')) + } + window.less.modifyVars(vueProps.$config.theme) + vueProps.$config.favicon = jsonConfig?.favicon ?? vueProps.$config.favicon vueProps.$config.css = response?.css ?? null await applyStaticCustomization(vueProps.$config.favicon, vueProps.$config.css) } +function applyJsonConfigToObject (sourceConfig, targetObject) { + if (!sourceConfig) { + return + } + + for (const [variableName, value] of Object.entries(targetObject)) { + targetObject[variableName] = sourceConfig?.[variableName] ?? value + } +} + async function applyStaticCustomization (favicon, css) { document.getElementById('favicon').href = favicon