diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index c5671d22d07b..3c78ee04fd53 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1397,6 +1397,7 @@ public class ApiConstants { public static final String CSS = "css"; public static final String JSON_CONFIGURATION = "jsonconfiguration"; + public static final String CUSTOM_LABELS_PATH = "customlabelspath"; public static final String COMMON_NAMES = "commonnames"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java index 8566b413cc12..4ca2a77aeab7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java @@ -53,6 +53,10 @@ public class CreateGuiThemeCmd extends BaseCmd { "retrieved and imported into the GUI when matching the theme access configurations.") private String jsonConfiguration; + @Parameter(name = ApiConstants.CUSTOM_LABELS_PATH, type = CommandType.STRING, length = 65535, description = "The JSON with the custom labels to be " + + "retrieved and imported into the GUI when matching the theme access configurations.") + private String customLabelsPath; + @Parameter(name = ApiConstants.COMMON_NAMES, type = CommandType.STRING, length = 65535, description = "A set of Common Names (CN) (fixed or " + "wildcard) separated by comma that can retrieve the theme; e.g.: *acme.com,acme2.com") private String commonNames; @@ -89,6 +93,10 @@ public String getJsonConfiguration() { return jsonConfiguration; } + public String getCustomLabelsPath() { + return customLabelsPath; + } + public String getCommonNames() { return commonNames; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java index daef2235ce89..ade9b9db050e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java @@ -56,6 +56,10 @@ public class UpdateGuiThemeCmd extends BaseCmd { "retrieved and imported into the GUI when matching the theme access configurations.") private String jsonConfiguration; + @Parameter(name = ApiConstants.CUSTOM_LABELS_PATH, type = CommandType.STRING, length = 65535, description = "The JSON with the custom labels to be " + + "retrieved and imported into the GUI when matching the theme access configurations.") + private String customLabelsPath; + @Parameter(name = ApiConstants.COMMON_NAMES, type = CommandType.STRING, length = 65535, description = "A set of Common Names (CN) (fixed or " + "wildcard) separated by comma that can retrieve the theme; e.g.: *acme.com,acme2.com") private String commonNames; @@ -96,6 +100,10 @@ public String getJsonConfiguration() { return jsonConfiguration; } + public String getCustomLabelsPath() { + return customLabelsPath; + } + public String getCommonNames() { return commonNames; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GuiThemeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GuiThemeResponse.java index fe8a85b4176e..825873a06801 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/GuiThemeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/GuiThemeResponse.java @@ -48,6 +48,10 @@ public class GuiThemeResponse extends BaseResponse { @Param(description = "The JSON with the configurations to be retrieved and imported into the GUI when matching the theme access configurations.") private String jsonConfiguration; + @SerializedName(ApiConstants.CUSTOM_LABELS_PATH) + @Param(description = "The JSON with the custom labels to be retrieved and imported into the GUI when matching the theme access configurations.") + private String customLabelsPath; + @SerializedName(ApiConstants.COMMON_NAMES) @Param(description = "A set of Common Names (CN) (fixed or wildcard) separated by comma that can retrieve the theme; e.g.: *acme.com,acme2.com") private String commonNames; @@ -121,6 +125,14 @@ public void setJsonConfiguration(String jsonConfiguration) { this.jsonConfiguration = jsonConfiguration; } + public String getCustomLabelsPath() { + return customLabelsPath; + } + + public void setCustomLabelsPath(String customLabelsPath) { + this.customLabelsPath = customLabelsPath; + } + public String getCommonNames() { return commonNames; } diff --git a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiTheme.java b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiTheme.java index 0cca8bc2d7db..d8c407a7593f 100644 --- a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiTheme.java +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiTheme.java @@ -37,6 +37,8 @@ public interface GuiTheme extends InternalIdentity, Identity { boolean getIsPublic(); + String getCustomLabelsPath(); + void setId(Long id); void setUuid(String uuid); @@ -58,4 +60,6 @@ public interface GuiTheme extends InternalIdentity, Identity { boolean isRecursiveDomains(); void setRecursiveDomains(boolean recursiveDomains); + + void setCustomLabelsPath(String customLabelsPath); } diff --git a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoin.java b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoin.java index e54d53138ef6..b6dd917690d7 100644 --- a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoin.java +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoin.java @@ -44,4 +44,6 @@ public interface GuiThemeJoin extends InternalIdentity, Identity { Date getCreated(); Date getRemoved(); + + String getCustomLabelsPath(); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java index 2df23b3d1064..8209e37951da 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java @@ -48,6 +48,9 @@ public class GuiThemeJoinVO implements GuiThemeJoin { @Column(name = "json_configuration", nullable = false, length = 65535) private String jsonConfiguration; + @Column(name = "custom_labels_path", nullable = false, length = 65535) + private String customLabelsPath; + @Column(name = "common_names", length = 65535) private String commonNames; @@ -74,6 +77,24 @@ public class GuiThemeJoinVO implements GuiThemeJoin { public GuiThemeJoinVO() { } + public GuiThemeJoinVO(Long id, String uuid, String name, String description, String css, String jsonConfiguration, String customLabelsPath, String commonNames, String domains, + String accounts, boolean recursiveDomains, boolean isPublic, Date created, Date removed) { + this.id = id; + this.uuid = uuid; + this.name = name; + this.description = description; + this.css = css; + this.jsonConfiguration = jsonConfiguration; + this.customLabelsPath = customLabelsPath; + this.commonNames = commonNames; + this.domains = domains; + this.accounts = accounts; + this.recursiveDomains = recursiveDomains; + this.isPublic = isPublic; + this.created = created; + this.removed = removed; + } + @Override public long getId() { return id; @@ -138,4 +159,9 @@ public Date getCreated() { public Date getRemoved() { return removed; } + + @Override + public String getCustomLabelsPath() { + return customLabelsPath; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java index 887e3886f6c6..9fa82c8efc0b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java @@ -53,6 +53,9 @@ public class GuiThemeVO implements GuiTheme { @Column(name = "json_configuration", length = 65535) private String jsonConfiguration; + @Column(name = "custom_labels_path", nullable = false, length = 65535) + private String customLabelsPath; + @Column(name = "is_public") private boolean isPublic; @@ -71,11 +74,12 @@ public GuiThemeVO() { } - public GuiThemeVO(String name, String description, String css, String jsonConfiguration, boolean recursiveDomains, boolean isPublic, Date created, Date removed) { + public GuiThemeVO(String name, String description, String css, String jsonConfiguration, String customLabelsPath, boolean recursiveDomains, boolean isPublic, Date created, Date removed) { this.name = name; this.description = description; this.css = css; this.jsonConfiguration = jsonConfiguration; + this.customLabelsPath = customLabelsPath; this.recursiveDomains = recursiveDomains; this.isPublic = isPublic; this.created = created; @@ -113,6 +117,10 @@ public String getJsonConfiguration() { } @Override + public String getCustomLabelsPath() { + return customLabelsPath; + } + public Date getCreated() { return created; } @@ -158,6 +166,10 @@ public void setJsonConfiguration(String jsonConfiguration) { } @Override + public void setCustomLabelsPath(String customLabelsPath) { + this.customLabelsPath = customLabelsPath; + } + public void setCreated(Date created) { this.created = created; } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index 2d25b3355d8e..c9d478a8e244 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -208,3 +208,6 @@ INSERT INTO cloud.role_permissions (uuid, role_id, rule, permission, sort_order) SELECT uuid(), role_id, 'quotaResourceStatement', permission, sort_order FROM cloud.role_permissions rp WHERE rule = 'quotaStatement' AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaResourceStatement'); + +-- Add custom labels to GUI themes +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.gui_themes', 'custom_labels_path', 'TEXT DEFAULT NULL'); diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.gui_themes_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.gui_themes_view.sql index 3173274623ed..99699d8a6e06 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.gui_themes_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.gui_themes_view.sql @@ -27,6 +27,7 @@ SELECT `cloud`.`gui_themes`.`description` AS `description`, `cloud`.`gui_themes`.`css` AS `css`, `cloud`.`gui_themes`.`json_configuration` AS `json_configuration`, + `cloud`.`gui_themes`.`custom_labels_path` AS `custom_labels_path`, (SELECT group_concat(gtd.`value` separator ',') FROM `cloud`.`gui_themes_details` gtd WHERE gtd.`type` = 'commonName' AND gtd.gui_theme_id = `cloud`.`gui_themes`.`id`) common_names, (SELECT group_concat(gtd.`value` separator ',') FROM `cloud`.`gui_themes_details` gtd WHERE gtd.`type` = 'domain' AND gtd.gui_theme_id = `cloud`.`gui_themes`.`id`) domains, (SELECT group_concat(gtd.`value` separator ',') FROM `cloud`.`gui_themes_details` gtd WHERE gtd.`type` = 'account' AND gtd.gui_theme_id = `cloud`.`gui_themes`.`id`) accounts, diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index a8551b4c6693..fb43102cdde2 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -5718,6 +5718,7 @@ public GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin) { } guiThemeResponse.setJsonConfiguration(guiThemeJoin.getJsonConfiguration()); + guiThemeResponse.setCustomLabelsPath(guiThemeJoin.getCustomLabelsPath()); guiThemeResponse.setCss(guiThemeJoin.getCss()); guiThemeResponse.setResponseName("guithemes"); 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 9a92b9bef013..295c210074a7 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 @@ -121,6 +121,7 @@ public GuiThemeJoin createGuiTheme(CreateGuiThemeCmd cmd) { String description = cmd.getDescription(); String css = cmd.getCss(); String jsonConfiguration = cmd.getJsonConfiguration(); + String customLabelsPath = cmd.getCustomLabelsPath(); String commonNames = cmd.getCommonNames(); String providedDomainIds = cmd.getDomainIds(); String providedAccountIds = cmd.getAccountIds(); @@ -140,7 +141,7 @@ public GuiThemeJoin createGuiTheme(CreateGuiThemeCmd cmd) { isPublic = false; } - GuiThemeVO guiThemeVO = new GuiThemeVO(name, description, css, jsonConfiguration, recursiveDomains, isPublic, new Date(), null); + GuiThemeVO guiThemeVO = new GuiThemeVO(name, description, css, jsonConfiguration, customLabelsPath, recursiveDomains, isPublic, new Date(), null); guiThemeDao.persist(guiThemeVO); persistGuiThemeDetails(guiThemeVO.getId(), commonNames, providedDomainIds, providedAccountIds); return guiThemeJoinDao.findById(guiThemeVO.getId()); @@ -264,6 +265,7 @@ public GuiThemeJoin updateGuiTheme(UpdateGuiThemeCmd cmd) { String description = cmd.getDescription(); String css = cmd.getCss(); String jsonConfiguration = cmd.getJsonConfiguration(); + String customLabelsPath = cmd.getCustomLabelsPath(); String commonNames = cmd.getCommonNames() == null ? guiThemeJoinVO.getCommonNames() : cmd.getCommonNames(); String providedDomainIds = cmd.getDomainIds() == null ? guiThemeJoinVO.getDomains() : cmd.getDomainIds(); String providedAccountIds = cmd.getAccountIds() == null ? guiThemeJoinVO.getAccounts() : cmd.getAccountIds(); @@ -279,11 +281,11 @@ public GuiThemeJoin updateGuiTheme(UpdateGuiThemeCmd cmd) { isPublic = false; } - return persistGuiTheme(guiThemeId, name, description, css, jsonConfiguration, commonNames, providedDomainIds, providedAccountIds, isPublic, recursiveDomains); + return persistGuiTheme(guiThemeId, name, description, css, jsonConfiguration, customLabelsPath, commonNames, providedDomainIds, providedAccountIds, isPublic, recursiveDomains); } - protected GuiThemeJoinVO persistGuiTheme(Long guiThemeId, String name, String description, String css, String jsonConfiguration, String commonNames, String providedDomainIds, - String providedAccountIds, Boolean isPublic, Boolean recursiveDomains){ + protected GuiThemeJoinVO persistGuiTheme(Long guiThemeId, String name, String description, String css, String jsonConfiguration, String customLabelsPath, String commonNames, String providedDomainIds, + String providedAccountIds, Boolean isPublic, Boolean recursiveDomains){ return Transaction.execute((TransactionCallback) status -> { GuiThemeVO guiThemeVO = guiThemeDao.findById(guiThemeId); @@ -303,6 +305,10 @@ protected GuiThemeJoinVO persistGuiTheme(Long guiThemeId, String name, String de guiThemeVO.setJsonConfiguration(jsonConfiguration); } + if (customLabelsPath != null) { + guiThemeVO.setCustomLabelsPath(customLabelsPath); + } + if (isPublic != null) { guiThemeVO.setIsPublic(isPublic); } diff --git a/ui/src/locales/index.js b/ui/src/locales/index.js index 6933e05206e1..33665f638006 100644 --- a/ui/src/locales/index.js +++ b/ui/src/locales/index.js @@ -20,6 +20,7 @@ import { vueProps } from '@/vue-app' const loadedLanguage = [] const messages = {} +let systemLang export const i18n = createI18n({ locale: 'en', @@ -39,11 +40,23 @@ export function loadLanguageAsync (lang) { return Promise.resolve(setLanguage(lang)) } + systemLang = lang return fetch(`locales/${lang}.json?ts=${Date.now()}`) .then(response => response.json()) .then(json => Promise.resolve(setLanguage(lang, json))) } +export function updateMessages (customPath) { + fetch(`${customPath}/${systemLang}.json`) + .then(response => response.json()) + .then((data) => { + const keys = Object.keys(data) + keys.forEach(x => { + messages[systemLang][x] = data[x] + }) + }) +} + function setLanguage (lang, message) { if (i18n) { i18n.global.locale = lang @@ -60,4 +73,6 @@ function setLanguage (lang, message) { if (message && Object.keys(message).length > 0) { messages[lang] = message } + + systemLang = lang } diff --git a/ui/src/main.js b/ui/src/main.js index 7441f8010865..6b47b0d589b4 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -116,14 +116,14 @@ fetch('config.json?ts=' + Date.now()) }) } - await applyCustomGuiTheme(accountid, domainid) - loadLanguageAsync().then(() => { - vueApp.use(store) - .use(router) - .use(i18n) - .use(bootstrap) - .mount('#app') + applyCustomGuiTheme(accountid, domainid).finally(() => { + vueApp.use(store) + .use(router) + .use(i18n) + .use(bootstrap) + .mount('#app') + }) }) }).catch(error => { renderError(error) diff --git a/ui/src/utils/guiTheme.js b/ui/src/utils/guiTheme.js index 438ce9333a4e..135561d33f76 100644 --- a/ui/src/utils/guiTheme.js +++ b/ui/src/utils/guiTheme.js @@ -17,7 +17,7 @@ import { vueProps } from '@/vue-app' import { getAPI } from '@/api' -import { loadLanguageAsync } from '../locales' +import { loadLanguageAsync, updateMessages } from '../locales' export async function applyCustomGuiTheme (accountid, domainid) { await fetch('config.json').then(response => response.json()).then(config => { @@ -63,6 +63,10 @@ async function applyDynamicCustomization (response) { jsonConfig = JSON.parse(response?.jsonconfiguration) } + if (response?.customlabelspath) { + updateMessages(response.customlabelspath) + } + // Sets custom GUI fields only if is not nullish. vueProps.$config.appTitle = jsonConfig?.appTitle ?? vueProps.$config.appTitle vueProps.$config.footer = jsonConfig?.footer ?? vueProps.$config.footer