This is an automated email from the ASF dual-hosted git repository.
fabricio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 3166e64891f Add support for new variables to the GUI whitelabel
runtime system (#12760)
3166e64891f is described below
commit 3166e64891fc75d4d32b66d874cff3f613b09b52
Author: Henrique Sato <[email protected]>
AuthorDate: Fri Apr 17 10:59:50 2026 -0300
Add support for new variables to the GUI whitelabel runtime system (#12760)
* Add support for new variables to the GUI whitelabel runtime system
* Address review
---
.../cloudstack/gui/theme/GuiThemeServiceImpl.java | 110 +--------------------
.../validator/JsonConfigAttributeValidator.java | 27 +++++
.../json/config/validator/JsonConfigValidator.java | 76 ++++++++++++++
.../config/validator/attributes/AttributeBase.java | 72 ++++++++++++++
.../validator/attributes/ErrorAttribute.java | 40 ++++++++
.../validator/attributes/PluginsAttribute.java | 68 +++++++++++++
.../validator/attributes/ThemeAttribute.java | 43 ++++++++
.../validator/attributes/UserCardAttribute.java | 88 +++++++++++++++++
.../core/spring-server-core-misc-context.xml | 5 +
ui/src/components/view/Setting.vue | 2 +-
ui/src/utils/guiTheme.js | 27 ++++-
11 files changed, 447 insertions(+), 111 deletions(-)
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 6fb6a1235f7..9a92b9bef01 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.EntityManager;
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.context.CallContext;
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 javax.inject.Inject;
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<String> ALLOWED_PRIMITIVE_PROPERTIES =
List.of("appTitle", "footer", "loginFooter", "logo", "minilogo", "banner",
"defaultLanguage");
-
- private static final List<String> ALLOWED_ERROR_PROPERTIES =
List.of("403", "404", "500");
-
- private static final List<String> 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<GuiThemeResponse> listGuiThemes(ListGuiThemesCmd cmd) {
ListResponse<GuiThemeResponse> response = new ListResponse<>();
@@ -244,94 +231,7 @@ public class GuiThemeServiceImpl implements
GuiThemeService {
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<Map.Entry<String, JsonElement>> 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<String, JsonElement> 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<String, JsonElement>
entry, JsonObject jsonObject) {
- Set<Map.Entry<String, JsonElement>> 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<String, JsonElement>
entry, JsonObject jsonObject) {
- Set<Map.Entry<String, JsonElement>> 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<Map.Entry<String, JsonElement>>
entries, List<String> allowedProperties) {
- JsonObject objectToBeAdded = new JsonObject();
-
- for (Map.Entry<String, JsonElement> 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 00000000000..a3d6a7b4ef9
--- /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<String, JsonElement> 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 00000000000..69a707d4d4d
--- /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<String> ALLOWED_PRIMITIVE_PROPERTIES =
List.of("appTitle", "footer", "loginFooter", "logo", "minilogo", "banner",
"docBase", "apidocs", "defaultLanguage");
+ private static final List<String> ALLOWED_DYNAMIC_PROPERTIES =
List.of("error", "theme", "plugins", "keyboardOptions", "userCard",
"docHelpMappings");
+
+ @Inject
+ private List<JsonConfigAttributeValidator> attributes;
+
+ public void validateJsonConfiguration(String jsonConfig) {
+ if (StringUtils.isBlank(jsonConfig)) {
+ return;
+ }
+
+ JsonObject jsonObject = new JsonObject();
+
+ try {
+ JsonElement jsonElement = JsonParser.parseString(jsonConfig);
+ Set<Map.Entry<String, JsonElement>> 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<String, JsonElement> 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 00000000000..b4bafb1f5da
--- /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<String> getAllowedProperties();
+
+ @Override
+ public void validate(Map.Entry<String, JsonElement> entry, JsonObject
jsonObject) {
+ if (!getAttributeName().equals(entry.getKey())) {
+ return;
+ }
+
+ Set<Map.Entry<String, JsonElement>> 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<Map.Entry<String, JsonElement>>
entries, List<String> allowedProperties) {
+ JsonObject objectToBeAdded = new JsonObject();
+
+ for (Map.Entry<String, JsonElement> 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 00000000000..40e519f8133
--- /dev/null
+++
b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ErrorAttribute.java
@@ -0,0 +1,40 @@
+// 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;
+
+/**
+ * Specific validator for the "error" object within the GUI theme JSON
configuration.
+ *
+ * <p>
+ * This component is defined as a bean in the Spring XML configuration and is
automatically injected into
+ * the {@link
org.apache.cloudstack.gui.theme.json.config.validator.JsonConfigValidator}
attribute list.
+ * </p>
+ */
+public class ErrorAttribute extends AttributeBase {
+
+ @Override
+ protected String getAttributeName() {
+ return "error";
+ }
+
+ @Override
+ protected List<String> 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 00000000000..1e0fa64669f
--- /dev/null
+++
b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/PluginsAttribute.java
@@ -0,0 +1,68 @@
+// 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;
+
+/**
+ * Specific validator for the "plugins" object within the GUI theme JSON
configuration.
+ *
+ * <p>
+ * This component is defined as a bean in the Spring XML configuration and is
automatically injected into
+ * the {@link
org.apache.cloudstack.gui.theme.json.config.validator.JsonConfigValidator}
attribute list.
+ * </p>
+ */
+public class PluginsAttribute extends AttributeBase {
+
+ @Override
+ protected String getAttributeName() {
+ return "plugins";
+ }
+
+ @Override
+ protected List<String> getAllowedProperties() {
+ return List.of("name", "path", "icon", "isExternalLink");
+ }
+
+ @Override
+ public void validate(Map.Entry<String, JsonElement> entry, JsonObject
jsonObject) {
+ if (!getAttributeName().equals(entry.getKey())) {
+ return;
+ }
+
+ JsonArray jsonArrayResult = new JsonArray();
+ JsonArray sourceJsonArray = entry.getValue().getAsJsonArray();
+ for (JsonElement jsonElement : sourceJsonArray) {
+ Set<Map.Entry<String, JsonElement>> 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 00000000000..92c74626b89
--- /dev/null
+++
b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/ThemeAttribute.java
@@ -0,0 +1,43 @@
+// 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;
+
+/**
+ * Specific validator for the "theme" object within the GUI theme JSON
configuration.
+ *
+ * <p>
+ * This component is defined as a bean in the Spring XML configuration and is
automatically injected into
+ * the {@link
org.apache.cloudstack.gui.theme.json.config.validator.JsonConfigValidator}
attribute list.
+ * </p>
+ */
+public class ThemeAttribute extends AttributeBase {
+
+ @Override
+ protected String getAttributeName() {
+ return "theme";
+ }
+
+ @Override
+ protected List<String> 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 00000000000..c6dc5dc6910
--- /dev/null
+++
b/server/src/main/java/org/apache/cloudstack/gui/theme/json/config/validator/attributes/UserCardAttribute.java
@@ -0,0 +1,88 @@
+// 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;
+
+/**
+ * Specific validator for the "userCard" object within the GUI theme JSON
configuration.
+ *
+ * <p>
+ * This component is defined as a bean in the Spring XML configuration and is
automatically injected into
+ * the {@link
org.apache.cloudstack.gui.theme.json.config.validator.JsonConfigValidator}
attribute list.
+ * </p>
+ */
+public class UserCardAttribute extends AttributeBase {
+ private static final List<String> 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<String> getAllowedProperties() {
+ return List.of("title", "icon", "links");
+ }
+
+ @Override
+ public void validate(Map.Entry<String, JsonElement> entry, JsonObject
jsonObject) {
+ if (!getAttributeName().equals(entry.getKey())) {
+ return;
+ }
+
+ Set<Map.Entry<String, JsonElement>> entries =
entry.getValue().getAsJsonObject().entrySet();
+ JsonObject objectToBeAdded = new JsonObject();
+ for (Map.Entry<String, JsonElement> 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<String, JsonElement> entry,
JsonObject jsonObject) {
+ JsonArray jsonArrayResult = new JsonArray();
+ JsonArray sourceJsonArray = entry.getValue().getAsJsonArray();
+ for (JsonElement jsonElement : sourceJsonArray) {
+ Set<Map.Entry<String, JsonElement>> 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 f4fd57d59fc..755c50f7a3a 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 @@
<bean id="domainHelper" class="com.cloud.utils.DomainHelper" />
+ <bean id="jsonConfigValidator"
class="org.apache.cloudstack.gui.theme.json.config.validator.JsonConfigValidator"
/>
+ <bean id="errorAttribute"
class="org.apache.cloudstack.gui.theme.json.config.validator.attributes.ErrorAttribute"
/>
+ <bean id="pluginsAttribute"
class="org.apache.cloudstack.gui.theme.json.config.validator.attributes.PluginsAttribute"
/>
+ <bean id="themeAttribute"
class="org.apache.cloudstack.gui.theme.json.config.validator.attributes.ThemeAttribute"
/>
+ <bean id="userCardAttribute"
class="org.apache.cloudstack.gui.theme.json.config.validator.attributes.UserCardAttribute"
/>
</beans>
diff --git a/ui/src/components/view/Setting.vue
b/ui/src/components/view/Setting.vue
index 4987a0bd612..5f1db1445ee 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 6499539f052..438ce9333a4 100644
--- a/ui/src/utils/guiTheme.js
+++ b/ui/src/utils/guiTheme.js
@@ -70,13 +70,15 @@ 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
vueProps.$config.defaultLanguage = vueProps.$localStorage.get('LOCALE') ??
jsonConfig?.defaultLanguage ?? vueProps.$config.defaultLanguage
- 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 => {
@@ -84,6 +86,11 @@ 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
@@ -95,6 +102,16 @@ async function applyDynamicCustomization (response) {
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