diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index c8694e076b8..27f507d3a75 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -809,6 +809,11 @@ public class EventTypes { public static final String VM_LEASE_CANCELLED = "VM.LEASE.CANCELLED"; public static final String VM_LEASE_EXPIRING = "VM.LEASE.EXPIRING"; + // GUI Theme + public static final String EVENT_GUI_THEME_CREATE = "GUI.THEME.CREATE"; + public static final String EVENT_GUI_THEME_REMOVE = "GUI.THEME.REMOVE"; + public static final String EVENT_GUI_THEME_UPDATE = "GUI.THEME.UPDATE"; + static { // TODO: need a way to force author adding event types to declare the entity details as well, with out braking @@ -1312,6 +1317,11 @@ public class EventTypes { entityEventDetails.put(VM_LEASE_EXPIRING, VirtualMachine.class); entityEventDetails.put(VM_LEASE_DISABLED, VirtualMachine.class); entityEventDetails.put(VM_LEASE_CANCELLED, VirtualMachine.class); + + // GUI theme + entityEventDetails.put(EVENT_GUI_THEME_CREATE, "GuiTheme"); + entityEventDetails.put(EVENT_GUI_THEME_REMOVE, "GuiTheme"); + entityEventDetails.put(EVENT_GUI_THEME_UPDATE, "GuiTheme"); } public static boolean isNetworkEvent(String eventType) { 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 22e7e807502..882634ec6c8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -1256,6 +1256,22 @@ public class ApiConstants { public static final String VMWARE_DC = "vmwaredc"; + public static final String CSS = "css"; + + public static final String JSON_CONFIGURATION = "jsonconfiguration"; + + public static final String COMMON_NAMES = "commonnames"; + + public static final String COMMON_NAME = "commonname"; + + public static final String DOMAIN_IDS = "domainids"; + + public static final String SHOW_PUBLIC = "showpublic"; + + public static final String LIST_ONLY_DEFAULT_THEME = "listonlydefaulttheme"; + + public static final String RECURSIVE_DOMAINS = "recursivedomains"; + /** * This enum specifies IO Drivers, each option controls specific policies on I/O. * Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0). diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index 3288eb58d75..d0683299e73 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -64,6 +64,7 @@ import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.GuestOsMappingResponse; import org.apache.cloudstack.api.response.GuestVlanRangeResponse; import org.apache.cloudstack.api.response.GuestVlanResponse; +import org.apache.cloudstack.api.response.GuiThemeResponse; import org.apache.cloudstack.api.response.HostForMigrationResponse; import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse; @@ -150,6 +151,7 @@ import org.apache.cloudstack.config.ConfigurationGroup; import org.apache.cloudstack.direct.download.DirectDownloadCertificate; import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap; import org.apache.cloudstack.direct.download.DirectDownloadManager; +import org.apache.cloudstack.gui.theme.GuiThemeJoin; import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; import org.apache.cloudstack.region.PortableIp; @@ -579,4 +581,6 @@ public interface ResponseGenerator { SharedFSResponse createSharedFSResponse(ResponseView view, SharedFS sharedFS); void updateTemplateIsoResponsesForIcons(List responses, ResourceTag.ResourceObjectType type); + + GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin); } 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 new file mode 100644 index 00000000000..8566b413cc1 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java @@ -0,0 +1,129 @@ +// 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.api.command.user.gui.theme; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.gui.theme.GuiTheme; +import org.apache.cloudstack.gui.theme.GuiThemeJoin; +import org.apache.cloudstack.gui.theme.GuiThemeService; + +import javax.inject.Inject; + +@APICommand(name = "createGuiTheme", description = "Creates a customized GUI theme for a set of Common Names (fixed or wildcard), a set of domain UUIDs, and/or a set of " + + "account UUIDs.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0.0", authorized = {RoleType.Admin}) +public class CreateGuiThemeCmd extends BaseCmd { + + @Inject + GuiThemeService guiThemeService; + + @Parameter(name = ApiConstants.NAME, required = true, type = CommandType.STRING, length = 2048, description = "A name to identify the theme.") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, length = 4096, description = "A description for the theme.") + private String description; + + @Parameter(name = ApiConstants.CSS, type = CommandType.STRING, length = 65535, description = "The CSS to be retrieved and imported into the GUI " + + "when matching the theme access configurations.") + private String css; + + @Parameter(name = ApiConstants.JSON_CONFIGURATION, type = CommandType.STRING, length = 65535, description = "The JSON with the configurations to be " + + "retrieved and imported into the GUI when matching the theme access configurations.") + private String jsonConfiguration; + + @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; + + @Parameter(name = ApiConstants.DOMAIN_IDS, type = CommandType.STRING, length = 65535, description = "A set of domain UUIDs (also known as ID for " + + "the end-user) separated by comma that can retrieve the theme.") + private String domainIds; + + @Parameter(name = ApiConstants.ACCOUNT_IDS, type = CommandType.STRING, length = 65535, description = "A set of account UUIDs (also known as ID for" + + " the end-user) separated by comma that can retrieve the theme.") + private String accountIds; + + @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, description = "Defines whether a theme can be retrieved by anyone when only " + + "the `commonNames` is informed. If the `domainIds` or `accountIds` is informed, it is considered as `false`.") + private Boolean isPublic = true; + + @Parameter(name = ApiConstants.RECURSIVE_DOMAINS, type = CommandType.BOOLEAN, description = "Defines whether the subdomains of the informed domains are considered. Default " + + "value is false.") + private Boolean recursiveDomains = false; + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getCss() { + return css; + } + + public String getJsonConfiguration() { + return jsonConfiguration; + } + + public String getCommonNames() { + return commonNames; + } + + public String getDomainIds() { + return domainIds; + } + + public String getAccountIds() { + return accountIds; + } + + public Boolean getPublic() { + return isPublic; + } + + public Boolean getRecursiveDomains() { + return recursiveDomains; + } + + @Override + public void execute() { + GuiThemeJoin guiThemeJoin = guiThemeService.createGuiTheme(this); + + if (guiThemeJoin == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create the GUI theme."); + } + + GuiThemeResponse response = _responseGenerator.createGuiThemeResponse(guiThemeJoin); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java new file mode 100644 index 00000000000..35a0a749aa9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java @@ -0,0 +1,110 @@ +// 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.api.command.user.gui.theme; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.gui.theme.GuiTheme; +import org.apache.cloudstack.gui.theme.GuiThemeService; + +import javax.inject.Inject; + +@APICommand(name = "listGuiThemes", description = "Lists GUI themes.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, + since = "4.21.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.User, RoleType.DomainAdmin, + RoleType.ResourceAdmin}) +public class ListGuiThemesCmd extends BaseListCmd { + + @Inject + GuiThemeService guiThemeService; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, description = "The theme ID.", entityType = GuiThemeResponse.class) + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "The name of the theme.") + private String name; + + @Parameter(name = ApiConstants.COMMON_NAME, type = CommandType.STRING, description = "The internet Common Name (CN) to be filtered.") + private String commonName; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "The ID of the domain to be filtered.") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "The ID of the account to be filtered.") + private Long accountId; + + @Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, description = "Whether to list all themes.") + private boolean listAll = false; + + @Parameter(name = ApiConstants.SHOW_REMOVED, type = CommandType.BOOLEAN, description = "Whether to list removed themes.") + private boolean showRemoved = false; + + @Parameter(name = ApiConstants.SHOW_PUBLIC, type = CommandType.BOOLEAN, description = "Whether to list public themes.") + private Boolean showPublic; + + @Parameter(name = ApiConstants.LIST_ONLY_DEFAULT_THEME, type = CommandType.BOOLEAN, description = "Whether to only list the default theme.") + private boolean listOnlyDefaultTheme = false; + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getCommonName() { + return commonName; + } + + public Long getDomainId() { + return domainId; + } + + public Long getAccountId() { + return accountId; + } + + public boolean getListAll() { + return listAll; + } + + public boolean getShowRemoved() { + return showRemoved; + } + + public Boolean getShowPublic() { + return showPublic; + } + + public boolean getListOnlyDefaultTheme() { + return listOnlyDefaultTheme; + } + + @Override + public void execute() { + ListResponse response = guiThemeService.listGuiThemes(this); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.java new file mode 100644 index 00000000000..64164838eba --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.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.api.command.user.gui.theme; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.gui.theme.GuiTheme; +import org.apache.cloudstack.gui.theme.GuiThemeService; + +import javax.inject.Inject; + +@APICommand(name = "removeGuiTheme", description = "Removes an existing GUI theme.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, + since = "4.21.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) +public class RemoveGuiThemeCmd extends BaseCmd { + + @Inject + GuiThemeService guiThemeService; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuiThemeResponse.class, required = true, + description = "The unique identifier of the GUI theme to be removed.") + private Long id; + + public Long getId() { + return id; + } + + @Override + public void execute() { + guiThemeService.removeGuiTheme(this); + final SuccessResponse response = new SuccessResponse(); + response.setResponseName(getCommandName()); + response.setSuccess(true); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} 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 new file mode 100644 index 00000000000..daef2235ce8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java @@ -0,0 +1,136 @@ +// 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.api.command.user.gui.theme; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.gui.theme.GuiTheme; +import org.apache.cloudstack.gui.theme.GuiThemeJoin; +import org.apache.cloudstack.gui.theme.GuiThemeService; + +import javax.inject.Inject; + + +@APICommand(name = "updateGuiTheme", description = "Updates an existing GUI theme.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, + since = "4.21.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) +public class UpdateGuiThemeCmd extends BaseCmd { + + @Inject + GuiThemeService guiThemeService; + + @Parameter(name = ApiConstants.ID, required = true, type = CommandType.UUID, entityType = GuiThemeResponse.class, description = "The ID of the theme to be updated.") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, length = 2048, description = "A name to identify the theme.") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, length = 4096, description = "A description for the theme.") + private String description; + + @Parameter(name = ApiConstants.CSS, type = CommandType.STRING, length = 65535, description = "The CSS to be retrieved and imported into the GUI " + + "when matching the theme access configurations.") + private String css; + + @Parameter(name = ApiConstants.JSON_CONFIGURATION, type = CommandType.STRING, length = 65535, description = "The JSON with the configurations to be " + + "retrieved and imported into the GUI when matching the theme access configurations.") + private String jsonConfiguration; + + @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; + + @Parameter(name = ApiConstants.DOMAIN_IDS, type = CommandType.STRING, length = 65535, description = "A set of domain UUIDs (also known as ID for " + + "the end-user) separated by comma that can retrieve the theme.") + private String domainIds; + + @Parameter(name = ApiConstants.RECURSIVE_DOMAINS, type = CommandType.BOOLEAN, description = "Defines whether the subdomains of the informed domains are considered. Default " + + "value is false.") + private Boolean recursiveDomains = false; + + @Parameter(name = ApiConstants.ACCOUNT_IDS, type = CommandType.STRING, length = 65535, description = "A set of account UUIDs (also known as ID for" + + " the end-user) separated by comma that can retrieve the theme.") + private String accountIds; + + @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, description = "Defines whether a theme can be retrieved by anyone when only " + + "the `commonNames` is informed. If the `domainIds` or `accountIds` is informed, it is considered as `false`.") + private Boolean isPublic = true; + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getCss() { + return css; + } + + public String getJsonConfiguration() { + return jsonConfiguration; + } + + public String getCommonNames() { + return commonNames; + } + + public String getDomainIds() { + return domainIds; + } + + public String getAccountIds() { + return accountIds; + } + + public Boolean getRecursiveDomains() { + return recursiveDomains; + } + + public Boolean getIsPublic() { + return isPublic; + } + + @Override + public void execute() { + GuiThemeJoin guiThemeJoin = guiThemeService.updateGuiTheme(this); + + if (guiThemeJoin == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update the GUI theme."); + } + + GuiThemeResponse response = _responseGenerator.createGuiThemeResponse(guiThemeJoin); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} 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 new file mode 100644 index 00000000000..fe8a85b4176 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/GuiThemeResponse.java @@ -0,0 +1,179 @@ +//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.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.gui.theme.GuiThemeJoin; + +import java.util.Date; + +@EntityReference(value = {GuiThemeJoin.class}) +public class GuiThemeResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the custom GUI theme.") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the GUI theme.") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "Description of the GUI theme.") + private String description; + + @SerializedName(ApiConstants.CSS) + @Param(description = "The CSS to be retrieved and imported into the GUI when matching the theme access configurations.") + private String css; + + @SerializedName(ApiConstants.JSON_CONFIGURATION) + @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.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; + + @SerializedName(ApiConstants.DOMAIN_IDS) + @Param(description = "A set of domain UUIDs (also known as ID for the end-user) separated by comma that can retrieve the theme.") + private String domainIds; + + @SerializedName(ApiConstants.RECURSIVE_DOMAINS) + @Param(description = "Whether to consider the subdomains of the informed domain IDs.") + private Boolean recursiveDomains; + + @SerializedName(ApiConstants.ACCOUNT_IDS) + @Param(description = "A set of account UUIDs (also known as ID for the end-user) separated by comma that can retrieve the theme.") + private String accountIds; + + @SerializedName(ApiConstants.IS_PUBLIC) + @Param(description = "Defines whether a theme can be retrieved by anyone when only the `commonNames` is informed. If the `domainIds` or `accountIds` is informed, it is " + + "considered as `false`.") + private Boolean isPublic; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "When the GUI theme was created.") + private Date created; + + @SerializedName(ApiConstants.REMOVED) + @Param(description = "When the GUI theme was removed.") + private Date removed; + + public GuiThemeResponse() { + super("guiThemes"); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCss() { + return css; + } + + public void setCss(String css) { + this.css = css; + } + + public String getJsonConfiguration() { + return jsonConfiguration; + } + + public void setJsonConfiguration(String jsonConfiguration) { + this.jsonConfiguration = jsonConfiguration; + } + + public String getCommonNames() { + return commonNames; + } + + public void setCommonNames(String commonNames) { + this.commonNames = commonNames; + } + + public String getDomainIds() { + return domainIds; + } + + public void setDomainIds(String domainIds) { + this.domainIds = domainIds; + } + + public String getAccountIds() { + return accountIds; + } + + public void setAccountIds(String accountIds) { + this.accountIds = accountIds; + } + + public Boolean getPublic() { + return isPublic; + } + + public void setPublic(Boolean aPublic) { + isPublic = aPublic; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getRemoved() { + return removed; + } + + public Boolean getRecursiveDomains() { + return recursiveDomains; + } + + public void setRecursiveDomains(Boolean recursiveDomains) { + this.recursiveDomains = recursiveDomains; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } +} 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 new file mode 100644 index 00000000000..0cca8bc2d7d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiTheme.java @@ -0,0 +1,61 @@ +// 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; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +public interface GuiTheme extends InternalIdentity, Identity { + + String getName(); + + String getDescription(); + + String getCss(); + + String getJsonConfiguration(); + + Date getCreated(); + + Date getRemoved(); + + boolean getIsPublic(); + + void setId(Long id); + + void setUuid(String uuid); + + void setName(String name); + + void setDescription(String description); + + void setCss(String css); + + void setJsonConfiguration(String jsonConfiguration); + + void setCreated(Date created); + + void setRemoved(Date removed); + + void setIsPublic(boolean isPublic); + + boolean isRecursiveDomains(); + + void setRecursiveDomains(boolean recursiveDomains); +} diff --git a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetails.java b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetails.java new file mode 100644 index 00000000000..164535033d9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetails.java @@ -0,0 +1,36 @@ +// 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; + +import org.apache.cloudstack.api.InternalIdentity; + +public interface GuiThemeDetails extends InternalIdentity { + + void setId(Long id); + + Long getGuiThemeId(); + + void setGuiThemeId(Long guiThemeId); + + String getType(); + + void setType(String type); + + String getValue(); + + void setValue(String value); +} 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 new file mode 100644 index 00000000000..e54d53138ef --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoin.java @@ -0,0 +1,47 @@ +// 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; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +public interface GuiThemeJoin extends InternalIdentity, Identity { + + String getName(); + + String getDescription(); + + String getCss(); + + String getJsonConfiguration(); + + String getCommonNames(); + + String getDomains(); + + String getAccounts(); + + boolean isRecursiveDomains(); + + boolean getIsPublic(); + + Date getCreated(); + + Date getRemoved(); +} diff --git a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeService.java b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeService.java new file mode 100644 index 00000000000..8a066d3ffec --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeService.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; + +import org.apache.cloudstack.api.command.user.gui.theme.CreateGuiThemeCmd; +import org.apache.cloudstack.api.command.user.gui.theme.ListGuiThemesCmd; +import org.apache.cloudstack.api.command.user.gui.theme.RemoveGuiThemeCmd; +import org.apache.cloudstack.api.command.user.gui.theme.UpdateGuiThemeCmd; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.api.response.ListResponse; + +public interface GuiThemeService { + + ListResponse listGuiThemes(ListGuiThemesCmd cmd); + + GuiThemeJoin createGuiTheme(CreateGuiThemeCmd cmd); + + GuiThemeJoin updateGuiTheme(UpdateGuiThemeCmd cmd); + + void removeGuiTheme(RemoveGuiThemeCmd cmd); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.java new file mode 100644 index 00000000000..046a19c59fa --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.java @@ -0,0 +1,91 @@ +// 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; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "gui_themes_details") +public class GuiThemeDetailsVO implements GuiThemeDetails { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "gui_theme_id", nullable = false) + private Long guiThemeId; + + @Column(name = "type", nullable = false, length = 100) + private String type; + + @Column(name = "value", nullable = false, length = 65535) + private String value; + + public GuiThemeDetailsVO() { + } + + public GuiThemeDetailsVO(Long guiThemeId, String type, String value) { + this.guiThemeId = guiThemeId; + this.type = type; + this.value = value; + } + + @Override + public long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + @Override + public Long getGuiThemeId() { + return guiThemeId; + } + + @Override + public void setGuiThemeId(Long guiThemeId) { + this.guiThemeId = guiThemeId; + } + + @Override + public String getType() { + return type; + } + + @Override + public void setType(String type) { + this.type = type; + } + + @Override + public String getValue() { + return value; + } + + @Override + public void setValue(String value) { + this.value = value; + } +} 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 new file mode 100644 index 00000000000..2df23b3d106 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java @@ -0,0 +1,141 @@ +// 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; + +import com.cloud.utils.db.GenericDao; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; + +@Entity +@Table(name = "gui_themes_view") +public class GuiThemeJoinVO implements GuiThemeJoin { + @Id + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "uuid", nullable = false) + private String uuid; + + @Column(name = "name", nullable = false, length = 2048) + private String name; + + @Column(name = "description", length = 4096) + private String description; + + @Column(name = "css", nullable = false, length = 65535) + private String css; + + @Column(name = "json_configuration", nullable = false, length = 65535) + private String jsonConfiguration; + + @Column(name = "common_names", length = 65535) + private String commonNames; + + @Column(name = "domains", length = 65535) + private String domains; + + @Column(name = "accounts", length = 65535) + private String accounts; + + @Column(name = "recursive_domains") + private boolean recursiveDomains; + + @Column(name = "is_public") + private boolean isPublic; + + @Column(name = GenericDao.CREATED_COLUMN, nullable = false) + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed = null; + + public GuiThemeJoinVO() { + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getCss() { + return css; + } + + @Override + public String getJsonConfiguration() { + return jsonConfiguration; + } + + @Override + public String getCommonNames() { + return commonNames; + } + + @Override + public String getDomains() { + return domains; + } + + @Override + public String getAccounts() { + return accounts; + } + + @Override + public boolean isRecursiveDomains() { + return recursiveDomains; + } + + @Override + public boolean getIsPublic() { + return isPublic; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public Date getRemoved() { + return removed; + } +} 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 new file mode 100644 index 00000000000..887e3886f6c --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java @@ -0,0 +1,189 @@ +// 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; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "gui_themes") +public class GuiThemeVO implements GuiTheme { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "uuid", nullable = false) + private String uuid = UUID.randomUUID().toString(); + + @Column(name = "name", nullable = false, length = 2048) + private String name; + + @Column(name = "description", length = 4096) + private String description; + + @Column(name = "css", length = 65535) + private String css; + + @Column(name = "json_configuration", length = 65535) + private String jsonConfiguration; + + @Column(name = "is_public") + private boolean isPublic; + + @Column(name = "recursive_domains") + private boolean recursiveDomains = false; + + @Column(name = GenericDao.CREATED_COLUMN, nullable = false) + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed = null; + + public GuiThemeVO() { + + } + + public GuiThemeVO(String name, String description, String css, String jsonConfiguration, boolean recursiveDomains, boolean isPublic, Date created, Date removed) { + this.name = name; + this.description = description; + this.css = css; + this.jsonConfiguration = jsonConfiguration; + this.recursiveDomains = recursiveDomains; + this.isPublic = isPublic; + this.created = created; + this.removed = removed; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getCss() { + return css; + } + + @Override + public String getJsonConfiguration() { + return jsonConfiguration; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public Date getRemoved() { + return removed; + } + + @Override + public boolean getIsPublic() { + return isPublic; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + @Override + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public void setDescription(String description) { + this.description = description; + } + + @Override + public void setCss(String css) { + this.css = css; + } + + @Override + public void setJsonConfiguration(String jsonConfiguration) { + this.jsonConfiguration = jsonConfiguration; + } + + @Override + public void setCreated(Date created) { + this.created = created; + } + + @Override + public void setRemoved(Date removed) { + this.removed = removed; + } + + @Override + public void setIsPublic(boolean isPublic) { + this.isPublic = isPublic; + } + + @Override + public boolean isRecursiveDomains() { + return recursiveDomains; + } + + @Override + public void setRecursiveDomains(boolean recursiveDomains) { + this.recursiveDomains = recursiveDomains; + } + + @Override + public String toString() { + return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "uuid", "name", "description", "isPublic", "recursiveDomains"); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDao.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDao.java new file mode 100644 index 00000000000..c00aedcc786 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDao.java @@ -0,0 +1,24 @@ +// 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 +// 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.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gui.theme.GuiThemeVO; + +public interface GuiThemeDao extends GenericDao { + +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDaoImpl.java new file mode 100644 index 00000000000..bc58c5f80f3 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDaoImpl.java @@ -0,0 +1,25 @@ +// 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.dao; + +import com.cloud.utils.db.GenericDaoBase; +import org.apache.cloudstack.gui.theme.GuiThemeVO; +import org.springframework.stereotype.Component; + +@Component +public class GuiThemeDaoImpl extends GenericDaoBase implements GuiThemeDao { +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDao.java new file mode 100644 index 00000000000..af243b1ffa4 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDao.java @@ -0,0 +1,30 @@ +// 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.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gui.theme.GuiThemeDetailsVO; + +import java.util.List; + +public interface GuiThemeDetailsDao extends GenericDao { + List listGuiThemeIdsByCommonName(String commonName); + + List listGuiThemeIdsByDomainUuids(String domainUuid); + + void expungeByGuiThemeId(long guiThemeId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java new file mode 100644 index 00000000000..b0969833eb0 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java @@ -0,0 +1,126 @@ +// 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.dao; + +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.gui.theme.GuiThemeDetailsVO; +import org.apache.cloudstack.gui.theme.GuiThemeVO; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; + +@Component +public class GuiThemeDetailsDaoImpl extends GenericDaoBase implements GuiThemeDetailsDao { + + @Inject + DomainDao domainDao; + + @Inject + GuiThemeDao guiThemeDao; + + public List listGuiThemeIdsByCommonName(String commonName) { + GenericSearchBuilder detailsDaoSearchBuilder = createSearchBuilder(Long.class); + detailsDaoSearchBuilder.selectFields(detailsDaoSearchBuilder.entity().getGuiThemeId()); + detailsDaoSearchBuilder.and("commonNameType", detailsDaoSearchBuilder.entity().getType(), SearchCriteria.Op.EQ); + detailsDaoSearchBuilder.and().op("firstReplace", detailsDaoSearchBuilder.entity().getValue(), SearchCriteria.Op.LIKE_REPLACE); + detailsDaoSearchBuilder.or("secondReplace", detailsDaoSearchBuilder.entity().getValue(), SearchCriteria.Op.LIKE_REPLACE).cp(); + detailsDaoSearchBuilder.done(); + + SearchCriteria searchCriteria = detailsDaoSearchBuilder.create(); + searchCriteria.setParameters("commonNameType", "commonName"); + searchCriteria.setParameters("firstReplace", commonName, "*", "%"); + searchCriteria.setParameters("secondReplace", commonName, "*.", "%"); + + return customSearch(searchCriteria, null); + } + + public List listGuiThemeIdsByDomainUuids(String domainUuid) { + List guiThemeIds = new ArrayList<>(); + String requestedDomainPath = domainDao.findByUuid(domainUuid).getPath(); + + SearchBuilder domainSearchBuilderPathLike = domainDao.createSearchBuilder(); + domainSearchBuilderPathLike.and("pathLike", domainSearchBuilderPathLike.entity().getPath(), SearchCriteria.Op.LIKE_CONCAT); + + SearchBuilder domainSearchBuilderPathEq = domainDao.createSearchBuilder(); + domainSearchBuilderPathEq.and("pathEq", domainSearchBuilderPathEq.entity().getPath(), SearchCriteria.Op.EQ); + + GenericSearchBuilder detailsSearchBuilderPathLike = createDetailsSearchBuilder(domainSearchBuilderPathLike); + SearchCriteria searchCriteriaDomainPathLike = setParametersDomainPathLike(detailsSearchBuilderPathLike, requestedDomainPath); + + GenericSearchBuilder detailsSearchBuilderPathEq = createDetailsSearchBuilder(domainSearchBuilderPathEq); + SearchCriteria searchCriteriaDomainPathEq = setParametersDomainPathEq(detailsSearchBuilderPathEq, requestedDomainPath); + + guiThemeIds.addAll(customSearch(searchCriteriaDomainPathLike, null)); + guiThemeIds.addAll(customSearch(searchCriteriaDomainPathEq, null)); + return guiThemeIds; + } + + private SearchCriteria setParametersDomainPathLike(GenericSearchBuilder detailsSearchBuilderPathLike, String requestedDomainPath) { + SearchCriteria searchCriteria = detailsSearchBuilderPathLike.create(); + searchCriteria.setParameters("domainUuidType", "domain"); + searchCriteria.setJoinParameters("domainJoin", "pathLike", requestedDomainPath, "%"); + searchCriteria.setJoinParameters("guiThemesJoin", "recursiveDomains", true); + + return searchCriteria; + } + + private SearchCriteria setParametersDomainPathEq(GenericSearchBuilder detailsSearchBuilderPathEq, String requestedDomainPath) { + SearchCriteria searchCriteria = detailsSearchBuilderPathEq.create(); + searchCriteria.setParameters("domainUuidType", "domain"); + searchCriteria.setJoinParameters("domainJoin", "pathEq", requestedDomainPath); + searchCriteria.setJoinParameters("guiThemesJoin", "recursiveDomains", false); + + return searchCriteria; + } + + private GenericSearchBuilder createDetailsSearchBuilder(SearchBuilder domainSearchBuilder) { + SearchBuilder guiThemeDaoSearchBuilder = guiThemeDao.createSearchBuilder(); + guiThemeDaoSearchBuilder.and("recursiveDomains", guiThemeDaoSearchBuilder.entity().isRecursiveDomains(), SearchCriteria.Op.EQ); + + GenericSearchBuilder guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder = createSearchBuilder(Long.class); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.selectFields(guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.entity().getGuiThemeId()); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.and("domainUuidType", guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.entity().getType(), SearchCriteria.Op.EQ); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.join("domainJoin", domainSearchBuilder, domainSearchBuilder.entity().getUuid(), + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.entity().getValue(), JoinBuilder.JoinType.INNER); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.join("guiThemesJoin", guiThemeDaoSearchBuilder, guiThemeDaoSearchBuilder.entity().getId(), + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.entity().getGuiThemeId(), JoinBuilder.JoinType.INNER); + + domainSearchBuilder.done(); + guiThemeDaoSearchBuilder.done(); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.done(); + + return guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder; + } + + public void expungeByGuiThemeId(long guiThemeId) { + SearchBuilder searchBuilder = createSearchBuilder(); + searchBuilder.and("guiThemeId", searchBuilder.entity().getGuiThemeId(), SearchCriteria.Op.EQ); + searchBuilder.done(); + + SearchCriteria searchCriteria = searchBuilder.create(); + searchCriteria.setParameters("guiThemeId", guiThemeId); + expunge(searchCriteria); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDao.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDao.java new file mode 100644 index 00000000000..740199cfca7 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDao.java @@ -0,0 +1,31 @@ +// 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.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gui.theme.GuiThemeJoinVO; + +import java.util.List; + +public interface GuiThemeJoinDao extends GenericDao { + GuiThemeJoinVO findDefaultTheme(); + + Pair, Integer> listGuiThemesWithNoAuthentication(String commonName); + + Pair, Integer> listGuiThemes(Long id, String name, String commonName, String domainUuid, String accountUuid, boolean listAll, boolean showRemoved, Boolean showPublic); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java new file mode 100644 index 00000000000..ce6f7055812 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java @@ -0,0 +1,139 @@ +// 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.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.gui.theme.GuiThemeJoinVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; + +@Component +public class GuiThemeJoinDaoImpl extends GenericDaoBase implements GuiThemeJoinDao { + @Inject + GuiThemeDetailsDao guiThemeDetailsDao; + + public static final Long INVALID_ID = -1L; + + public GuiThemeJoinVO findDefaultTheme() { + SearchBuilder searchBuilder = createSearchBuilder(); + searchBuilder.and("commonNames", searchBuilder.entity().getCommonNames(), SearchCriteria.Op.NULL); + searchBuilder.and("domainUuids", searchBuilder.entity().getDomains(), SearchCriteria.Op.NULL); + searchBuilder.and("accountUuids", searchBuilder.entity().getAccounts(), SearchCriteria.Op.NULL); + searchBuilder.done(); + + SearchCriteria searchCriteria = searchBuilder.create(); + + return findOneBy(searchCriteria); + } + + public Pair, Integer> listGuiThemesWithNoAuthentication(String commonName) { + SearchCriteria searchCriteria = createGuiThemeSearchCriteria(null, null, commonName, null, null, null, false); + return searchOrderByCreatedDate(searchCriteria, false); + } + + public Pair, Integer> listGuiThemes(Long id, String name, String commonName, String domainUuid, String accountUuid, boolean listAll, + boolean showRemoved, Boolean showPublic) { + SearchCriteria searchCriteria = createGuiThemeSearchCriteria(id, name, commonName, domainUuid, accountUuid, showPublic, listAll); + + if (listAll) { + showRemoved = false; + } + + return searchOrderByCreatedDate(searchCriteria, showRemoved); + } + + private Pair, Integer> searchOrderByCreatedDate(SearchCriteria searchCriteria, boolean showRemoved) { + Filter filter = new Filter(GuiThemeJoinVO.class, "created", false); + return searchAndCount(searchCriteria, filter, showRemoved); + } + + private SearchCriteria createGuiThemeSearchCriteria(Long id, String name, String commonName, String domainUuid, String accountUuid, Boolean showPublic, boolean listAll) { + SearchCriteria searchCriteria = createGuiThemeJoinSearchBuilder(listAll, showPublic).create(); + List idList = new ArrayList<>(); + + if (id != null) { + idList.add(id); + } + + searchCriteria.setParametersIfNotNull("name", name); + searchCriteria.setParametersIfNotNull("isPublic", showPublic); + + if (StringUtils.isNotBlank(accountUuid)) { + searchCriteria.setParameters("accountUuid", "%" + accountUuid + "%"); + } + + if (StringUtils.isNotBlank(commonName)) { + setGuiThemeIdsFilteredByType(idList, ApiConstants.COMMON_NAME, commonName); + } + + if (StringUtils.isNotBlank(domainUuid)) { + setGuiThemeIdsFilteredByType(idList, ApiConstants.DOMAIN, domainUuid); + } + + searchCriteria.setParametersIfNotNull("idIn", idList.toArray()); + + return searchCriteria; + } + + /** + * Sets the `id IN ( )` clause of the query. If the informed value of common name or domain ID does not retrieve any GUI theme ID; then, an invalid ID (-1) is passed to the + * list, as not a single entity has this ID. This is necessary as to set the parameter even if it did not find any GUI theme ID; otherwise, the query would not filter the + * common name or domain ID passed. + */ + public void setGuiThemeIdsFilteredByType(List idList, String type, String value) { + List guiThemeIdsFilteredByType = new ArrayList<>(); + + switch (type) { + case ApiConstants.COMMON_NAME: + guiThemeIdsFilteredByType = guiThemeDetailsDao.listGuiThemeIdsByCommonName(value); + break; + case ApiConstants.DOMAIN: + guiThemeIdsFilteredByType = guiThemeDetailsDao.listGuiThemeIdsByDomainUuids(value); + break; + } + + if (CollectionUtils.isNotEmpty(guiThemeIdsFilteredByType)) { + idList.addAll(guiThemeIdsFilteredByType); + return; + } + logger.trace(String.format("No GUI theme with the specified [%s] with UUID [%s] was found, adding an invalid ID for filtering.", type, value)); + idList.add(INVALID_ID); + } + + private SearchBuilder createGuiThemeJoinSearchBuilder(boolean listAll, Boolean showPublic) { + SearchBuilder guiThemeJoinSearchBuilder = createSearchBuilder(); + guiThemeJoinSearchBuilder.and("idIn", guiThemeJoinSearchBuilder.entity().getId(), SearchCriteria.Op.IN); + guiThemeJoinSearchBuilder.and("name", guiThemeJoinSearchBuilder.entity().getName(), SearchCriteria.Op.EQ); + guiThemeJoinSearchBuilder.and("accountUuid", guiThemeJoinSearchBuilder.entity().getAccounts(), SearchCriteria.Op.LIKE); + + if (!listAll && showPublic != null) { + guiThemeJoinSearchBuilder.and("isPublic", guiThemeJoinSearchBuilder.entity().getIsPublic(), SearchCriteria.Op.EQ); + } + + return guiThemeJoinSearchBuilder; + } +} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index d05635f4614..474569f4924 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -301,4 +301,7 @@ + + + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql index 5a50b96d8f2..53c2bbf60cc 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql @@ -203,3 +203,27 @@ SET `sort_key` = CASE ELSE `sort_key` END; -- End: Changes for Guest OS category cleanup + +-- Whitelabel GUI +CREATE TABLE IF NOT EXISTS `cloud`.`gui_themes` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `uuid` varchar(255) UNIQUE, + `name` varchar(2048) NOT NULL COMMENT 'A name to identify the theme.', + `description` varchar(4096) DEFAULT NULL COMMENT 'A description for the theme.', + `css` text DEFAULT NULL COMMENT 'The CSS to be retrieved and imported into the GUI when matching the theme access configurations.', + `json_configuration` text DEFAULT NULL COMMENT 'The JSON with the configurations to be retrieved and imported into the GUI when matching the theme access configurations.', + `recursive_domains` tinyint(1) DEFAULT 0 COMMENT 'Defines whether the subdomains of the informed domains are considered. Default value is false.', + `is_public` tinyint(1) default 1 COMMENT 'Defines whether a theme can be retrieved by anyone when only the `internet_domains_names` is informed. If the `domain_uuids` or `account_uuids` is informed, it is considered as `false`.', + `created` datetime NOT NULL, + `removed` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE IF NOT EXISTS `cloud`.`gui_themes_details` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `gui_theme_id` bigint(20) unsigned NOT NULL COMMENT 'Foreign key referencing the GUI theme on `gui_themes` table.', + `type` varchar(100) NOT NULL COMMENT 'The type of GUI theme details. Valid options are: `account`, `domain` and `commonName`', + `value` text NOT NULL COMMENT 'The value of the `type` details. Can be an UUID (account or domain) or internet common name.', + PRIMARY KEY (`id`), + CONSTRAINT `fk_gui_themes_details__gui_theme_id` FOREIGN KEY (`gui_theme_id`) REFERENCES `gui_themes`(`id`) +); 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 new file mode 100644 index 00000000000..3173274623e --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.gui_themes_view.sql @@ -0,0 +1,38 @@ +-- 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. + +-- VIEW `cloud`.`gui_themes_view`; + +DROP VIEW IF EXISTS `cloud`.`gui_themes_view`; + +CREATE VIEW `cloud`.`gui_themes_view` AS +SELECT + `cloud`.`gui_themes`.`id` AS `id`, + `cloud`.`gui_themes`.`uuid` AS `uuid`, + `cloud`.`gui_themes`.`name` AS `name`, + `cloud`.`gui_themes`.`description` AS `description`, + `cloud`.`gui_themes`.`css` AS `css`, + `cloud`.`gui_themes`.`json_configuration` AS `json_configuration`, + (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, + `cloud`.`gui_themes`.`recursive_domains` AS `recursive_domains`, + `cloud`.`gui_themes`.`is_public` AS `is_public`, + `cloud`.`gui_themes`.`created` AS `created`, + `cloud`.`gui_themes`.`removed` AS `removed` +FROM `cloud`.`gui_themes` LEFT JOIN `cloud`.`gui_themes_details` ON `cloud`.`gui_themes_details`.`gui_theme_id` = `cloud`.`gui_themes`.`id` +GROUP BY `cloud`.`gui_themes`.`id`; diff --git a/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java b/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java index b4c27746799..512941f4ee3 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java +++ b/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java @@ -479,6 +479,14 @@ public abstract class SearchBase, T, K> { sql.append(" FIND_IN_SET(?, "); } + if (op == Op.LIKE_REPLACE) { + sql.append(" ? LIKE REPLACE ("); + } + + if (op == Op.LIKE_CONCAT) { + sql.append(" ? LIKE CONCAT ("); + } + if (tableAlias == null) { if (joinName != null) { tableAlias = joinName; diff --git a/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java b/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java index 4a5349b31f4..15807eb26d4 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java +++ b/framework/db/src/main/java/com/cloud/utils/db/SearchCriteria.java @@ -39,7 +39,8 @@ public class SearchCriteria { " NOT BETWEEN ? AND ? ", 2), IN(" IN () ", -1), NOTIN(" NOT IN () ", -1), LIKE(" LIKE ? ", 1), NLIKE(" NOT LIKE ? ", 1), NIN(" NOT IN () ", -1), NULL(" IS NULL ", 0), NNULL( " IS NOT NULL ", - 0), SC(" () ", 1), TEXT(" () ", 1), RP("", 0), AND(" AND ", 0), OR(" OR ", 0), NOT(" NOT ", 0), FIND_IN_SET(" ) ", 1), BINARY_OR(" & ?) > 0", 1); + 0), SC(" () ", 1), TEXT(" () ", 1), RP("", 0), AND(" AND ", 0), OR(" OR ", 0), NOT(" NOT ", 0), FIND_IN_SET(" ) ", 1), BINARY_OR(" & ?) > 0", 1), + LIKE_REPLACE(", ?, ?)", 3), LIKE_CONCAT(", ?)", 2); private final String op; int params; @@ -60,7 +61,8 @@ public class SearchCriteria { } public enum Func { - NATIVE("@", 1), MAX("MAX(@)", 1), MIN("MIN(@)", 1), FIRST("FIRST(@)", 1), LAST("LAST(@)", 1), SUM("SUM(@)", 1), COUNT("COUNT(@)", 1), DISTINCT("DISTINCT(@)", 1), DISTINCT_PAIR("DISTINCT @, @", 2); + NATIVE("@", 1), MAX("MAX(@)", 1), MIN("MIN(@)", 1), FIRST("FIRST(@)", 1), LAST("LAST(@)", 1), SUM("SUM(@)", 1), COUNT("COUNT(@)", 1), DISTINCT("DISTINCT(@)", 1), DISTINCT_PAIR("DISTINCT @, @", 2), + CONCAT("CONCAT(@,@)", 2); private String func; private int count; diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index ef65c4c3120..15d8c7309d4 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -97,6 +97,7 @@ import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.GuestOsMappingResponse; import org.apache.cloudstack.api.response.GuestVlanRangeResponse; import org.apache.cloudstack.api.response.GuestVlanResponse; +import org.apache.cloudstack.api.response.GuiThemeResponse; import org.apache.cloudstack.api.response.HostForMigrationResponse; import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse; @@ -206,6 +207,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.gui.theme.GuiThemeJoin; import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.network.BgpPeerVO; import org.apache.cloudstack.network.RoutedIpv4Manager; @@ -5515,8 +5517,7 @@ public class ApiResponseHelper implements ResponseGenerator { SharedFSJoinVO sharedFSView = ApiDBUtils.newSharedFSView(sharedFS); return ApiDBUtils.newSharedFSResponse(view, sharedFSView); } - - protected Map getResourceIconsUsingOsCategory(List responses) { +protected Map getResourceIconsUsingOsCategory(List responses) { Set guestOsCategoryIds = responses.stream().map(TemplateResponse::getOsTypeCategoryId).collect(Collectors.toSet()); Map guestOsCategoryIcons = resourceIconManager.getByResourceTypeAndIds(ResourceTag.ResourceObjectType.GuestOsCategory, @@ -5550,4 +5551,28 @@ public class ApiResponseHelper implements ResponseGenerator { response.setResourceIconResponse(iconResponse); } } + @Override + public GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin) { + GuiThemeResponse guiThemeResponse = new GuiThemeResponse(); + + Long callerId = CallContext.current().getCallingAccount().getAccountId(); + if (callerId != Account.ACCOUNT_ID_SYSTEM && _accountMgr.isRootAdmin(callerId)) { + guiThemeResponse.setId(guiThemeJoin.getUuid()); + guiThemeResponse.setName(guiThemeJoin.getName()); + guiThemeResponse.setDescription(guiThemeJoin.getDescription()); + guiThemeResponse.setCommonNames(guiThemeJoin.getCommonNames()); + guiThemeResponse.setDomainIds(guiThemeJoin.getDomains()); + guiThemeResponse.setRecursiveDomains(guiThemeJoin.isRecursiveDomains()); + guiThemeResponse.setAccountIds(guiThemeJoin.getAccounts()); + guiThemeResponse.setPublic(guiThemeJoin.getIsPublic()); + guiThemeResponse.setCreated(guiThemeJoin.getCreated()); + guiThemeResponse.setRemoved(guiThemeJoin.getRemoved()); + } + + guiThemeResponse.setJsonConfiguration(guiThemeJoin.getJsonConfiguration()); + guiThemeResponse.setCss(guiThemeJoin.getCss()); + guiThemeResponse.setResponseName("guithemes"); + + return guiThemeResponse; + } } diff --git a/server/src/main/java/com/cloud/api/ApiServer.java b/server/src/main/java/com/cloud/api/ApiServer.java index 34752000907..e0737a6891d 100644 --- a/server/src/main/java/com/cloud/api/ApiServer.java +++ b/server/src/main/java/com/cloud/api/ApiServer.java @@ -87,6 +87,7 @@ import org.apache.cloudstack.api.command.admin.user.ListUsersCmd; import org.apache.cloudstack.api.command.user.account.ListAccountsCmd; import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; import org.apache.cloudstack.api.command.user.event.ListEventsCmd; +import org.apache.cloudstack.api.command.user.gui.theme.ListGuiThemesCmd; import org.apache.cloudstack.api.command.user.offering.ListDiskOfferingsCmd; import org.apache.cloudstack.api.command.user.offering.ListServiceOfferingsCmd; import org.apache.cloudstack.api.command.user.project.ListProjectInvitationsCmd; @@ -967,6 +968,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer final User user = ApiDBUtils.findUserById(userId); return commandAvailable(remoteAddress, commandName, user); } else { + if (commandName.equalsIgnoreCase(ListGuiThemesCmd.class.getAnnotation(APICommand.class).name())) { + return true; + } // check against every available command to see if the command exists or not if (!s_apiNameCmdClassMap.containsKey(commandName) && !commandName.equals("login") && !commandName.equals("logout")) { final String errorMessage = "The given command " + commandName + " either does not exist, is not available" + diff --git a/server/src/main/java/com/cloud/api/ApiServlet.java b/server/src/main/java/com/cloud/api/ApiServlet.java index 41229670c38..21d09375812 100644 --- a/server/src/main/java/com/cloud/api/ApiServlet.java +++ b/server/src/main/java/com/cloud/api/ApiServlet.java @@ -37,6 +37,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ApiServerService; @@ -46,6 +47,7 @@ import org.apache.cloudstack.api.auth.APIAuthenticationManager; import org.apache.cloudstack.api.auth.APIAuthenticationType; import org.apache.cloudstack.api.auth.APIAuthenticator; import org.apache.cloudstack.api.command.user.consoleproxy.CreateConsoleEndpointCmd; +import org.apache.cloudstack.api.command.user.gui.theme.ListGuiThemesCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.managed.context.ManagedContext; import org.apache.cloudstack.utils.consoleproxy.ConsoleAccessUtils; @@ -397,6 +399,8 @@ public class ApiServlet extends HttpServlet { CallContext.register(accountMgr.getSystemUser(), accountMgr.getSystemAccount()); } setProjectContext(params); + setGuiThemeParameterIfApiCallIsUnauthenticated(userId, command, req, params); + if (LOGGER.isTraceEnabled()) { LOGGER.trace(String.format("verifying request for user %s from %s with %d parameters", userId, remoteAddress.getHostAddress(), params.size())); @@ -457,6 +461,19 @@ public class ApiServlet extends HttpServlet { } } + private void setGuiThemeParameterIfApiCallIsUnauthenticated(Long userId, String command, HttpServletRequest req, Map params) { + String listGuiThemesApiName = ListGuiThemesCmd.class.getAnnotation(APICommand.class).name(); + + if (userId != null || !listGuiThemesApiName.equalsIgnoreCase(command)) { + return; + } + + String serverName = req.getServerName(); + LOGGER.info("Unauthenticated call to {} API, thus, the `commonName` parameter will be inferred as {}.", listGuiThemesApiName, serverName); + params.put(ApiConstants.COMMON_NAME, new String[]{serverName}); + } + + private boolean checkIfAuthenticatorIsOf2FA(String command) { boolean verify2FA = false; APIAuthenticator apiAuthenticator = authManager.getAPIAuthenticator(command); diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index e385c472779..1ae2ded5b1c 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -403,6 +403,10 @@ import org.apache.cloudstack.api.command.user.firewall.UpdateFirewallRuleCmd; import org.apache.cloudstack.api.command.user.firewall.UpdatePortForwardingRuleCmd; import org.apache.cloudstack.api.command.user.guest.ListGuestOsCategoriesCmd; import org.apache.cloudstack.api.command.user.guest.ListGuestOsCmd; +import org.apache.cloudstack.api.command.user.gui.theme.CreateGuiThemeCmd; +import org.apache.cloudstack.api.command.user.gui.theme.ListGuiThemesCmd; +import org.apache.cloudstack.api.command.user.gui.theme.RemoveGuiThemeCmd; +import org.apache.cloudstack.api.command.user.gui.theme.UpdateGuiThemeCmd; import org.apache.cloudstack.api.command.user.iso.AttachIsoCmd; import org.apache.cloudstack.api.command.user.iso.CopyIsoCmd; import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; @@ -4187,6 +4191,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(UpdateSecondaryStorageSelectorCmd.class); cmdList.add(RemoveSecondaryStorageSelectorCmd.class); cmdList.add(ListAffectedVmsForStorageScopeChangeCmd.class); + cmdList.add(ListGuiThemesCmd.class); + cmdList.add(UpdateGuiThemeCmd.class); + cmdList.add(CreateGuiThemeCmd.class); + cmdList.add(RemoveGuiThemeCmd.class); // Out-of-band management APIs for admins cmdList.add(EnableOutOfBandManagementForHostCmd.class); 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 new file mode 100644 index 00000000000..78bcb6bbc03 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeServiceImpl.java @@ -0,0 +1,445 @@ +// 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; + +import com.cloud.domain.Domain; +import com.cloud.domain.dao.DomainDao; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.user.Account; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Pair; +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; +import org.apache.cloudstack.api.command.user.gui.theme.RemoveGuiThemeCmd; +import org.apache.cloudstack.api.command.user.gui.theme.UpdateGuiThemeCmd; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.api.response.ListResponse; +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.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +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 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; + + @Inject + GuiThemeDetailsDao guiThemeDetailsDao; + + @Inject + GuiThemeJoinDao guiThemeJoinDao; + + @Inject + ResponseGenerator responseGenerator; + + @Inject + EntityManager entityManager; + + @Inject + AccountDao accountDao; + + @Inject + DomainDao domainDao; + + @Override + public ListResponse listGuiThemes(ListGuiThemesCmd cmd) { + ListResponse response = new ListResponse<>(); + Pair, Integer> result; + boolean listOnlyDefaultTheme = cmd.getListOnlyDefaultTheme(); + + if (listOnlyDefaultTheme) { + result = retrieveDefaultTheme(); + } else if (CallContext.current().getCallingAccountId() == Account.ACCOUNT_ID_SYSTEM) { + logger.info("Unauthenticated call to `listGuiThemes` API, ignoring all parameters, except `commonName`."); + result = listGuiThemesWithNoAuthentication(cmd); + } else { + result = listGuiThemesInternal(cmd); + } + List guiThemeResponses = new ArrayList<>(); + + for (GuiThemeJoin guiThemeJoin : result.first()) { + GuiThemeResponse guiThemeResponse = responseGenerator.createGuiThemeResponse(guiThemeJoin); + guiThemeResponses.add(guiThemeResponse); + } + + response.setResponses(guiThemeResponses); + return response; + } + + private Pair, Integer> retrieveDefaultTheme() { + GuiThemeJoinVO defaultTheme = guiThemeJoinDao.findDefaultTheme(); + List list = new ArrayList<>(); + + if (defaultTheme != null) { + list.add(defaultTheme); + } + + return new Pair<>(list, list.size()); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_GUI_THEME_CREATE, eventDescription = "Creating GUI theme") + public GuiThemeJoin createGuiTheme(CreateGuiThemeCmd cmd) { + String name = cmd.getName(); + String description = cmd.getDescription(); + String css = cmd.getCss(); + String jsonConfiguration = cmd.getJsonConfiguration(); + String commonNames = cmd.getCommonNames(); + String providedDomainIds = cmd.getDomainIds(); + String providedAccountIds = cmd.getAccountIds(); + boolean isPublic = cmd.getPublic(); + Boolean recursiveDomains = cmd.getRecursiveDomains(); + + CallContext.current().setEventDetails(String.format("Name: %s, AccountIDs: %s, DomainIDs: %s, RecursiveDomains: %s, CommonNames: %s", name, providedAccountIds, + providedDomainIds, recursiveDomains, commonNames)); + + if (StringUtils.isAllBlank(css, jsonConfiguration)) { + throw new CloudRuntimeException("Either the `css` or `jsonConfiguration` parameter must be informed."); + } + + validateParameters(jsonConfiguration, providedDomainIds, providedAccountIds, commonNames, null); + + if (shouldSetGuiThemeToPrivate(providedDomainIds, providedAccountIds)) { + isPublic = false; + } + + GuiThemeVO guiThemeVO = new GuiThemeVO(name, description, css, jsonConfiguration, recursiveDomains, isPublic, new Date(), null); + guiThemeDao.persist(guiThemeVO); + persistGuiThemeDetails(guiThemeVO.getId(), commonNames, providedDomainIds, providedAccountIds); + return guiThemeJoinDao.findById(guiThemeVO.getId()); + } + + protected void persistGuiThemeDetails(long guiThemeId, String commonNames, String providedDomainIds, String providedAccountIds) { + persistDetailValueIfNotNull(guiThemeId, commonNames, "commonName"); + persistDetailValueIfNotNull(guiThemeId, providedDomainIds, "domain"); + persistDetailValueIfNotNull(guiThemeId, providedAccountIds, "account"); + } + + protected void persistDetailValueIfNotNull(long guiThemeId, String providedParameter, String type) { + if (providedParameter == null) { + logger.trace("GUI theme provided parameter `{}` is null; therefore, it will be ignored.", type); + return; + } + for (String splitParameter : StringUtils.deleteWhitespace(providedParameter).split(",")) { + guiThemeDetailsDao.persist(new GuiThemeDetailsVO(guiThemeId, type, splitParameter)); + } + + } + + protected boolean shouldSetGuiThemeToPrivate(String providedDomainIds, String providedAccountIds) { + if (StringUtils.isNotBlank(providedAccountIds)) { + logger.info("Parameter `accountIds` was informed during GUI theme creation, therefore, `isPublic` will be set to `false`."); + return true; + } + + if (StringUtils.isNotBlank(providedDomainIds)) { + logger.info("Parameter `domainIds` was informed during GUI theme creation, therefore, `isPublic` will be set to `false`."); + return true; + } + return false; + } + + /** + * A GUI theme is only considered the default one if the parameters `commonNames`, `domainIds` and `accountIds` are all blank. + * @return true if all parameters are blank, false otherwise. + */ + protected boolean isConsideredDefaultTheme(String commonNames, String providedDomainIds, String providedAccountIds) { + return StringUtils.isAllBlank(commonNames, providedDomainIds, providedAccountIds); + } + + /** + * There can only be one default theme registered, therefore, a {@link CloudRuntimeException} will be thrown if: + *
    + *
  • There is already a default theme registered when creating a new GUI theme.
  • + *
  • Or, the GUI theme to be updated is not the default theme already registered.
  • + *
+ */ + protected void checkIfDefaultThemeIsAllowed(String commonNames, String providedDomainIds, String providedAccountIds, Long idOfThemeToBeUpdated) { + if (!isConsideredDefaultTheme(commonNames, providedDomainIds, providedAccountIds)) { + logger.info("The GUI theme will not be considered as the default one, as the `commonNames`, `domainIds` and `accountIds` are not all blank."); + return; + } + + GuiThemeJoinVO defaultTheme = guiThemeJoinDao.findDefaultTheme(); + + if (defaultTheme != null && (idOfThemeToBeUpdated == null || defaultTheme.getId() != idOfThemeToBeUpdated)) { + throw new CloudRuntimeException(String.format("Only one default GUI theme is allowed. Remove the current default theme %s and try again.", defaultTheme)); + } + + logger.info("The parameters `commonNames`, `domainIds` and `accountIds` were not informed. The created theme will be considered as the default theme."); + } + + protected Pair, Integer> listGuiThemesWithNoAuthentication(ListGuiThemesCmd cmd) { + return guiThemeJoinDao.listGuiThemesWithNoAuthentication(cmd.getCommonName()); + } + + + protected Pair, Integer> listGuiThemesInternal(ListGuiThemesCmd cmd) { + Long id = cmd.getId(); + String name = cmd.getName(); + String commonName = cmd.getCommonName(); + String domainUuid = cmd.getDomainId() == null ? null : domainDao.findById(cmd.getDomainId()).getUuid(); + String accountUuid = cmd.getAccountId() == null ? null : accountDao.findById(cmd.getAccountId()).getUuid(); + boolean listAll = cmd.getListAll(); + boolean showRemoved = cmd.getShowRemoved(); + Boolean showPublic = cmd.getShowPublic(); + + return guiThemeJoinDao.listGuiThemes(id, name, commonName, domainUuid, accountUuid, listAll, showRemoved, showPublic); + } + + protected void validateParameters(String jsonConfig, String domainIds, String accountIds, String commonNames, Long idOfThemeToBeUpdated) { + if (isConsideredDefaultTheme(commonNames, domainIds, accountIds)) { + checkIfDefaultThemeIsAllowed(commonNames, domainIds, accountIds, idOfThemeToBeUpdated); + } + + 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); + } + + /** + * Validate if the comma separated list of UUIDs of the fields {@link GuiThemeJoinVO#getAccounts()} and {@link GuiThemeJoinVO#getDomains()} are valid. + * @param providedIds a comma separated list of UUIDs of {@link Account} or {@link Domain} + * @param clazz the class to infer the DAO object. Valid options are: {@link Account} and {@link Domain} + */ + protected void validateObjectUuids(String providedIds, Class clazz) { + if (StringUtils.isBlank(providedIds)) { + return; + } + + String[] commaSeparatedIds = providedIds.split("\\s*,\\s*"); + for (String id : commaSeparatedIds) { + Object objectVO = entityManager.findByUuid(clazz, id); + + if (objectVO == null) { + throw new CloudRuntimeException(String.format("The %s ID %s does not exist. Verify the informed IDs and try again.", clazz.getSimpleName(), id)); + } + } + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_GUI_THEME_UPDATE, eventDescription = "Updating GUI theme") + public GuiThemeJoin updateGuiTheme(UpdateGuiThemeCmd cmd) { + Long guiThemeId = cmd.getId(); + GuiThemeJoinVO guiThemeJoinVO = guiThemeJoinDao.findById(guiThemeId); + + String name = cmd.getName(); + String description = cmd.getDescription(); + String css = cmd.getCss(); + String jsonConfiguration = cmd.getJsonConfiguration(); + 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(); + Boolean isPublic = cmd.getIsPublic(); + Boolean recursiveDomains = cmd.getRecursiveDomains(); + + CallContext.current().setEventDetails(String.format("ID: %s, Name: %s, AccountIDs: %s, DomainIDs: %s, RecursiveDomains: %s, CommonNames: %s", guiThemeId, name, + providedAccountIds, providedDomainIds, recursiveDomains, commonNames)); + + validateParameters(jsonConfiguration, providedDomainIds, providedAccountIds, commonNames, guiThemeId); + + if (shouldSetGuiThemeToPrivate(providedDomainIds, providedAccountIds)) { + isPublic = false; + } + + return persistGuiTheme(guiThemeId, name, description, css, jsonConfiguration, 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){ + return Transaction.execute((TransactionCallback) status -> { + GuiThemeVO guiThemeVO = guiThemeDao.findById(guiThemeId); + + if (name != null) { + guiThemeVO.setName(ifBlankReturnNull(name)); + } + + if (description != null) { + guiThemeVO.setDescription(ifBlankReturnNull(description)); + } + + if (css != null) { + guiThemeVO.setCss(css); + } + + if (jsonConfiguration != null) { + guiThemeVO.setJsonConfiguration(jsonConfiguration); + } + + if (isPublic != null) { + guiThemeVO.setIsPublic(isPublic); + } + + if (recursiveDomains != null) { + guiThemeVO.setRecursiveDomains(recursiveDomains); + } + + logger.trace("Persisting GUI theme [{}] with CSS [{}] and JSON configuration [{}].", guiThemeVO, guiThemeVO.getCss(), guiThemeVO.getJsonConfiguration()); + + guiThemeDao.persist(guiThemeVO); + guiThemeDetailsDao.expungeByGuiThemeId(guiThemeId); + persistGuiThemeDetails(guiThemeId, commonNames, providedDomainIds, providedAccountIds); + + return guiThemeJoinDao.findById(guiThemeId); + }); + } + + protected String ifBlankReturnNull(String value) { + if (StringUtils.isBlank(value)) { + return null; + } + return value; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_GUI_THEME_REMOVE, eventDescription = "Removing GUI theme") + public void removeGuiTheme(RemoveGuiThemeCmd cmd) { + Long guiThemeId = cmd.getId(); + GuiThemeVO guiThemeVO = guiThemeDao.findById(guiThemeId); + CallContext.current().setEventDetails(String.format("ID: %s", guiThemeId)); + + if (guiThemeVO != null) { + guiThemeDao.remove(guiThemeId); + } else { + logger.error("Unable to find a GUI theme with the specified ID [{}].", guiThemeId); + throw new CloudRuntimeException("Unable to find a GUI theme with the specified ID."); + } + } +} diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index dfb38cd7d8b..d82e20dc344 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -390,4 +390,5 @@
+ diff --git a/server/src/test/java/org/apache/cloudstack/gui/theme/GuiThemeServiceImplTest.java b/server/src/test/java/org/apache/cloudstack/gui/theme/GuiThemeServiceImplTest.java new file mode 100644 index 00000000000..c47fdadd32c --- /dev/null +++ b/server/src/test/java/org/apache/cloudstack/gui/theme/GuiThemeServiceImplTest.java @@ -0,0 +1,215 @@ +// 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; + +import com.cloud.user.Account; +import com.cloud.utils.Pair; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.api.command.user.gui.theme.ListGuiThemesCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.gui.theme.dao.GuiThemeJoinDaoImpl; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.List; + + +@RunWith(MockitoJUnitRunner.class) +public class GuiThemeServiceImplTest { + + @Mock + GuiThemeJoinDaoImpl guiThemeJoinDaoMock; + + @Mock + GuiThemeJoinVO guiThemeJoinVOMock; + + @Mock + EntityManager entityManagerMock; + + @Mock + Object objectMock; + + @Mock + ListGuiThemesCmd listGuiThemesCmdMock; + + @Spy + @InjectMocks + GuiThemeServiceImpl guiThemeServiceSpy = new GuiThemeServiceImpl(); + + private static final String COMMON_NAME = "*acme.com,acm2.com"; + + private static final String DOMAIN_IDS = "1,2,3"; + + private static final String ACCOUNT_IDS = "4,5,6"; + + private static final String BLANK_STRING = ""; + + @Test + public void listGuiThemesTestShouldIgnoreParametersWhenItIsCalledUnauthenticated() { + Pair, Integer> emptyPair = new Pair<>(new ArrayList<>(), 0); + + Long accountId = Account.ACCOUNT_ID_SYSTEM; + + try (MockedStatic callContextMocked = Mockito.mockStatic(CallContext.class)) { + CallContext callContextMock = Mockito.mock(CallContext.class); + callContextMocked.when(CallContext::current).thenReturn(callContextMock); + Mockito.doReturn(accountId).when(callContextMock).getCallingAccountId(); + Mockito.doReturn(emptyPair).when(guiThemeServiceSpy).listGuiThemesWithNoAuthentication(Mockito.nullable(ListGuiThemesCmd.class)); + Mockito.when(listGuiThemesCmdMock.getListOnlyDefaultTheme()).thenReturn(false); + guiThemeServiceSpy.listGuiThemes(listGuiThemesCmdMock); + Mockito.verify(guiThemeServiceSpy, Mockito.times(1)).listGuiThemesWithNoAuthentication(Mockito.nullable(ListGuiThemesCmd.class)); + } + } + + @Test + public void listGuiThemesTestShouldCallNormalFlowWhenAuthenticated() { + Pair, Integer> emptyPair = new Pair<>(new ArrayList<>(), 0); + + Long accountId = 3L; + + try (MockedStatic callContextMocked = Mockito.mockStatic(CallContext.class)) { + CallContext callContextMock = Mockito.mock(CallContext.class); + callContextMocked.when(CallContext::current).thenReturn(callContextMock); + Mockito.doReturn(accountId).when(callContextMock).getCallingAccountId(); + Mockito.doReturn(emptyPair).when(guiThemeServiceSpy).listGuiThemesInternal(Mockito.nullable(ListGuiThemesCmd.class)); + Mockito.when(listGuiThemesCmdMock.getListOnlyDefaultTheme()).thenReturn(false); + guiThemeServiceSpy.listGuiThemes(listGuiThemesCmdMock); + Mockito.verify(guiThemeServiceSpy, Mockito.times(1)).listGuiThemesInternal(Mockito.nullable(ListGuiThemesCmd.class)); + } + } + + @Test + public void listGuiThemesTestListOnlyDefaultThemesShouldCallFindDefaultTheme() { + Mockito.when(listGuiThemesCmdMock.getListOnlyDefaultTheme()).thenReturn(true); + guiThemeServiceSpy.listGuiThemes(listGuiThemesCmdMock); + Mockito.verify(guiThemeJoinDaoMock, Mockito.times(1)).findDefaultTheme(); + } + + @Test + public void shouldSetGuiThemeToPrivateTestDomainIdsAndAccountIdsAreBlankShouldReturnFalse() { + boolean result = guiThemeServiceSpy.shouldSetGuiThemeToPrivate(BLANK_STRING, BLANK_STRING); + + Assert.assertFalse(result); + } + + @Test + public void shouldSetGuiThemeToPrivateTestDomainIdsNotBlankAndAccountIdsIsBlankShouldReturnTrue() { + boolean result = guiThemeServiceSpy.shouldSetGuiThemeToPrivate(DOMAIN_IDS, BLANK_STRING); + + Assert.assertTrue(result); + } + + @Test + public void shouldSetGuiThemeToPrivateTestDomainIdsIsBlankAndAccountIdsIsNotBlankShouldReturnTrue() { + boolean result = guiThemeServiceSpy.shouldSetGuiThemeToPrivate(BLANK_STRING, ACCOUNT_IDS); + + Assert.assertTrue(result); + } + + @Test + public void shouldSetGuiThemeToPrivateTestDomainIdsAndAccountIdsAreNotBlankShouldReturnTrue() { + boolean result = guiThemeServiceSpy.shouldSetGuiThemeToPrivate(DOMAIN_IDS, ACCOUNT_IDS); + + Assert.assertTrue(result); + } + + @Test + public void validateObjectUuidsTestProvidedUuidsIsNullShouldNotThrowCloudRuntimeException() { + guiThemeServiceSpy.validateObjectUuids(null, null); + } + + @Test + public void validateObjectUuidsTestProvidedUuidsIsBlankShouldNotThrowCloudRuntimeException() { + guiThemeServiceSpy.validateObjectUuids(BLANK_STRING, null); + } + + @Test + public void validateObjectUuidsTestProvidedUuidsIsBlankAndCommaSeparatedShouldNotThrowCloudRuntimeException() { + String blankCommaSeparatedString = ",,,"; + guiThemeServiceSpy.validateObjectUuids(blankCommaSeparatedString, null); + } + + @Test + public void validateObjectUuidsTestProvidedUuidIsValidShouldNotThrowCloudRuntimeException() { + String validUuid = "4"; + + Mockito.when(entityManagerMock.findByUuid(Mockito.any(Class.class), Mockito.anyString())).thenReturn(objectMock); + guiThemeServiceSpy.validateObjectUuids(validUuid, Account.class); + } + + @Test + public void validateObjectUuidsTestProvidedUuidsAreValidShouldNotThrowCloudRuntimeException() { + Mockito.when(entityManagerMock.findByUuid(Mockito.any(Class.class), Mockito.anyString())).thenReturn(objectMock); + guiThemeServiceSpy.validateObjectUuids(ACCOUNT_IDS, Account.class); + } + + @Test(expected = CloudRuntimeException.class) + public void validateObjectUuidsTestProvidedUuidsAreNotValidShouldThrowCloudRuntimeException() { + Mockito.when(entityManagerMock.findByUuid(Mockito.any(Class.class), Mockito.anyString())).thenReturn(null); + guiThemeServiceSpy.validateObjectUuids(ACCOUNT_IDS, Account.class); + } + + @Test + public void checkIfDefaultThemeIsAllowedTestThemeIsNotConsideredDefault() { + Mockito.when(guiThemeServiceSpy.isConsideredDefaultTheme(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(false); + + guiThemeServiceSpy.checkIfDefaultThemeIsAllowed(COMMON_NAME, BLANK_STRING, BLANK_STRING, null); + } + + @Test + public void checkIfDefaultThemeIsAllowedTestThemeIsConsideredDefaultAndThereIsNotADefaultThemeRegistered() { + Mockito.when(guiThemeServiceSpy.isConsideredDefaultTheme(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(true); + Mockito.when(guiThemeJoinDaoMock.findDefaultTheme()).thenReturn(null); + + guiThemeServiceSpy.checkIfDefaultThemeIsAllowed(BLANK_STRING, BLANK_STRING, BLANK_STRING, null); + } + + @Test(expected = CloudRuntimeException.class) + public void checkIfDefaultThemeIsAllowedTestThemeIsConsideredDefaultAndThereIsADefaultThemeRegisteredShouldThrowCloudRuntimeException() { + Mockito.when(guiThemeServiceSpy.isConsideredDefaultTheme(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(true); + Mockito.when(guiThemeJoinDaoMock.findDefaultTheme()).thenReturn(guiThemeJoinVOMock); + + guiThemeServiceSpy.checkIfDefaultThemeIsAllowed(BLANK_STRING, BLANK_STRING, BLANK_STRING, null); + } + + @Test(expected = CloudRuntimeException.class) + public void checkIfDefaultThemeIsAllowedTestThemeIsConsideredDefaultAndWillBeUpdatedAndIsDifferentIdShouldThrowCloudRuntimeException() { + Mockito.when(guiThemeServiceSpy.isConsideredDefaultTheme(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(true); + Mockito.when(guiThemeJoinDaoMock.findDefaultTheme()).thenReturn(guiThemeJoinVOMock); + Mockito.when(guiThemeJoinVOMock.getId()).thenReturn(1L); + + guiThemeServiceSpy.checkIfDefaultThemeIsAllowed(BLANK_STRING, BLANK_STRING, BLANK_STRING, 2L); + } + + @Test + public void checkIfDefaultThemeIsAllowedTestThemeIsConsideredDefaultAndWillBeUpdatedAndIsTheSameShouldAllowUpdate() { + Mockito.when(guiThemeServiceSpy.isConsideredDefaultTheme(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(true); + Mockito.when(guiThemeJoinDaoMock.findDefaultTheme()).thenReturn(guiThemeJoinVOMock); + Mockito.when(guiThemeJoinVOMock.getId()).thenReturn(1L); + + guiThemeServiceSpy.checkIfDefaultThemeIsAllowed(BLANK_STRING, BLANK_STRING, BLANK_STRING, 1L); + } +} diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 8fafd841be4..8d507670ba8 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -260,7 +260,11 @@ known_categories = { 'addNodesToKubernetesCluster': 'Kubernetes Service', 'removeNodesFromKubernetesCluster': 'Kubernetes Service', 'configureStorageAccess': 'Storage Access Groups', - 'listStorageAccessGroups': 'Storage Access Groups' + 'listStorageAccessGroups': 'Storage Access Groups', + 'listGuiThemes': 'GUI Theme', + 'createGuiTheme': 'GUI Theme', + 'updateGuiTheme': 'GUI Theme', + 'removeGuiTheme': 'GUI Theme' } diff --git a/ui/public/assets/asf-logo.svg b/ui/public/assets/asf-logo.svg new file mode 100644 index 00000000000..d69e8d7ebb8 --- /dev/null +++ b/ui/public/assets/asf-logo.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/public/assets/feather.svg b/ui/public/assets/feather.svg new file mode 100644 index 00000000000..95cc5b2c07a --- /dev/null +++ b/ui/public/assets/feather.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/public/config.json b/ui/public/config.json index d56e6f03783..bea9599fa3a 100644 --- a/ui/public/config.json +++ b/ui/public/config.json @@ -17,6 +17,7 @@ "logo": "assets/logo.svg", "minilogo": "assets/mini-logo.svg", "banner": "assets/banner.svg", + "favicon": "cloud.ico", "error": { "403": "assets/403.png", "404": "assets/404.png", diff --git a/ui/public/css/apache-theme.css b/ui/public/css/apache-theme.css new file mode 100644 index 00000000000..573c062c513 --- /dev/null +++ b/ui/public/css/apache-theme.css @@ -0,0 +1,1169 @@ +/* +* 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. +*/ + +@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); + +:root { + --main-verde: #3E7C59; + --main-vermelho: #ae0000; + --main-normal:#327355; + --main-exception:#ae0000; + --main-primary-claro: #db4e2d22; + --main-primary-escuro: #db4e2d; + --main-secondary-claro: #E9782622; + --main-secondary-escuro: #E97826; + --main-grey-claro: #eee; + --main-grey-escuro: #6D6E71; + --main-image-menu: url("../assets/asf-logo.svg"); + --main-image-login: url("../assets/asf-logo.svg"); + --main-image-icon: url("../assets/feather.svg"); + --main-linear-cora: #ddd; + --main-linear-corb: #ddd; +} + +#app > div > form > img.user-layout-logo{ + display:none !important; +} + +#app > div > form{ + background-image:var(--main-image-login); + background-repeat:no-repeat; + background-size:250px; + background-position:top center; + padding-top:120px; +} + +.userLayout .user-layout-header{ + min-height:100px; + background-image:var(--main-image-login); + background-repeat:no-repeat; + background-size:250px; + background-position:top center; +} + +.ant-layout-sider.light.ant-fixed-sidemenu > div > div{ + height: 100px !important; + background-image:var(--main-image-menu); + background-repeat:no-repeat; + background-size:200px; + background-position:25px 25px; + margin-bottom:20px; +} + +.ant-layout-sider.light.ant-fixed-sidemenu.ant-layout-sider-collapsed > div > div{ + height: 70px !important; + background-image:var(--main-image-icon); + background-repeat:no-repeat; + background-size:30px; + background-position:25px 20px; +} + +.sider.light .ant-menu-light a, +.sider.light .ant-menu-submenu > .ant-menu-submenu-title { + color:#000 !important; +} + +.sider.light .ant-menu-light a path{ + +} + +.ant-menu:not(.ant-menu-horizontal) span.ant-menu-title-content .custom-icon path{ + transition:fill 1s easyin; +} + +.ant-menu:not(.ant-menu-horizontal) span.ant-menu-title-content:hover .custom-icon path{ + fill:#fff !important; +} + +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-active:hover a, +.ant-menu:not(.ant-menu-horizontal) .ant-menu-submenu .ant-menu-submenu-title:hover{ + color:#fff !important; +} + +.ant-menu-submenu ul a, +.ant-menu-submenu ul .ant-menu-item-active.ant-menu-item-selected a span{ + color:#fff !important; +} + +.ant-menu-submenu ul .ant-menu-item-selected a span{ + color:#000 !important; +} + +*{ + font-family: 'Poppins' , Courier !important; +} + +aside, +.ant-menu-submenu ul{ + background: linear-gradient(var(--main-linear-cora), var(--main-linear-corb)) !important; +} + +html{ + background-color: var(--main-secondary-escuro); +} + +/* HEADER */ +.layout.ant-layout .header{ + background: linear-gradient(var(--main-linear-cora), var(--main-linear-corb)) !important; + box-shadow: 0px 0px 10px #00000055; +} + +.layout.ant-layout .header .user-menu > span.ant-dropdown-open{ + background-color:var(--main-primary-claro) !important; + border-bottom:5px solid var(--main-primary-escuro) !important; +} + +.layout.ant-layout .header .anticon-menu-unfold:hover, +.layout.ant-layout .header .anticon-menu-fold:hover, +.layout.ant-layout .header .user-menu > span:hover{ + background-color:var(--main-primary-escuro) !important; + color:#fff !important; +} + +.layout.ant-layout .header .user-menu > span:hover *{ + color:#fff !important; +} +.layout.ant-layout .header .user-menu > span *{ + color:#000 !important; +} + +.layout.ant-layout .header .anticon-menu-unfold, +.layout.ant-layout .header .anticon-menu-fold{ + color:#000; +} + +.header-notice-opener{ + padding-left:15px; +} + + +.ant-table-tbody > .ant-table-row:hover td{ + background-color:var(--main-secondary-claro) !important; +} + +/* SIDE BAR */ + +.vm-info-card .ant-card-body .ant-card-bordered{ + border-top:2px solid var(--main-primary-escuro) !important; + margin-bottom:50px; +} + +.vm-info-card .ant-card-body .ant-card-bordered .ant-card-head, +.vm-info-card .ant-card-body .ant-card-bordered .ant-card-grid{ + padding:10px 0px !important; +} + +.vm-info-card .ant-card-body .ant-card-bordered .resource-detail-item{ + margin-bottom:20px; +} + +.vm-info-card .ant-card-body .ant-card-bordered .resource-detail-item .resource-detail-item__label{ + font-weight:900; +} + +/* USER LOGIN SCREEN */ + +.ant-tabs-tab, +.user-layout{ + background-color:var(--main-grey-claro) !important; +} + +.user-layout .ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab{ + background-color:var(--main-grey-claro) !important; + color: var(--main-primary-escuro); + border-bottom:0px solid var(--main-primary-escuro); +} + +.user-layout .ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab:hover{ + background-color:var(--main-secondary-escuro) !important; + color: #fff; + border-bottom:3px solid var(--main-primary-escuro); +} + +.user-layout .ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab.ant-tabs-tab-active{ + background-color:var(--main-grey-claro) !important; + color: var(--main-primary-escuro); + border-bottom:3px solid var(--main-primary-escuro); +} + +.user-layout .ant-tabs-nav{ + width:100%; +} + +.user-layout .ant-tabs-nav > div > div{ + width:50%; +} + +/* IMAGES LOGO */ +.userLayout .user-layout-logo{ + display:none; +} + +.ant-layout-sider.light.ant-fixed-sidemenu > div > div > img{ + display:none !important; +} + +/* PROGRESS */ +.ant-progress.ant-progress-status-active .ant-progress-inner, +.ant-progress.ant-progress-status-normal .ant-progress-inner{ + background-color:var(--main-grey-claro) !important; +} + +.ant-progress.ant-progress-status-active .ant-progress-inner .ant-progress-bg, +.ant-progress.ant-progress-status-normal .ant-progress-inner .ant-progress-bg{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-progress .ant-progress-text{ + color:var(--main-grey-escuro) !important; + font-weight:900; +} + +.ant-progress.ant-progress-status-exception .ant-progress-inner .ant-progress-bg{ + background-color:var(--main-exception) !important; +} + +/* FORMS */ +.ant-select-selector, +.ant-input{ + background-color:white !important; +} + +.ant-select.ant-select-focused .ant-select-arrow .anticon{ + color:var(--main-grey-claro); +} + +.ant-select-selector .ant-select-selection-placeholder, +.ant-select, +.ant-input::placeholder{ + color:var(--main-grey-escuro) !important; +} + +.ant-select-selector:hover, .ant-select-selector:focus, +.ant-input:hover, .ant-input:focus{ + box-shadow: 0px 0px 3px var(--main-primary-escuro); + border-color:var(--main-primary-escuro) !important; +} + +.ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector { + border-color: var(--main-primary-escuro); + border-right-width: 1px !important; + box-shadow: 0 0 0 2px var(--main-primary-claro); +} + +.ant-select-dropdown .ant-select-item.ant-select-item-option-selected, +.ant-select-dropdown .ant-select-item:hover{ + background-color:var(--main-primary-claro) !important; +} + +/* PAGINATION */ +.ant-pagination .ant-pagination-item.ant-pagination-item-active{ + background-color:var(--main-primary-escuro) !important; + border-color:#fff; +} + +.ant-pagination .ant-pagination-item.ant-pagination-item-active a{ + color:#fff !important; + font-weight:bold; +} + +.ant-pagination .ant-pagination-item:not(.ant-pagination-item-active){ + background-color:#fff !important; +} + +.ant-pagination .ant-pagination-item:not(.ant-pagination-item-active):hover{ + background-color:var(--main-secondary-escuro) !important; + border-color:#fff; +} + +.ant-pagination .ant-pagination-item:not(.ant-pagination-item-active):hover a{ + color:#fff !important; + font-weight:bold; +} + +.ant-pagination .ant-pagination-next span, +.ant-pagination .ant-pagination-prev span{ + color:var(--main-grey-escuro) !important; +} + +.ant-pagination .ant-pagination-next span:hover, +.ant-pagination .ant-pagination-prev span:hover{ + color:var(--main-secondary-escuro) !important; +} + + +/* RADIO GROUP */ +.ant-radio-group label.ant-radio-button-wrapper-checked{ + background-color:var(--main-primary-escuro) !important; + border-color:var(--main-primary-escuro) !important; +} + +.ant-radio-group label:not(.ant-radio-button-wrapper-checked):hover{ + color:#000 !important; + background-color:var(--main-primary-claro) !important; +} + +.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { + background-color:var(--main-primary-escuro) !important; +} + +/* TABLES */ +.ant-table{ + font-size:16px !important; +} + +.ant-table th{ + border-bottom:5px solid var(--main-primary-claro) !important; +} + +.ant-table th.ant-table-column-has-sorters:hover{ + border-bottom:5px solid var(--main-primary-escuro) !important; +} + +.ant-table thead.ant-table-thead{ + background-color:Red !important; + border-bottom:5px solid var(--main-primary-escuro) !important; +} + +.anticon.ant-table-column-sorter-up.on *, +.anticon.ant-table-column-sorter-down.on *{ + color: var(--main-primary-escuro) !important; +} + +/* PAGE TABS LEFT */ +.ant-tabs-left-bar .ant-tabs-nav-scroll { + border-right:3px solid var(--main-primary-escuro); +} + +.ant-tabs-left-bar .ant-tabs-nav-scroll .ant-tabs-tab{ + margin:0px; + padding:10px 15px; + border-right:3px solid transparent; + transform: all 1s easyin !important; + background-color:#fff !important; +} + +.ant-tabs-left-bar .ant-tabs-nav-scroll .ant-tabs-tab.ant-tabs-tab-active, +.ant-tabs-left-bar .ant-tabs-nav-scroll .ant-tabs-tab:hover{ + background-color:var(--main-grey-claro) !important; + color: var(--main-primary-escuro); + border-right:3px solid var(--main-primary-escuro); +} + +.ant-tabs-left-bar .ant-tabs-nav-scroll .ant-tabs-ink-bar{ + display:none !important; + width:0px !important; + background-color:var(--main-primary-escuro) !important; +} + +/* PAGE TABS TOP */ +.ant-tabs-tab-next, +.ant-tabs-tab-prev{ + color:var(--main-primary-escuro) !important; +} + +.ant-tabs-tab-next svg, +.ant-tabs-tab-prev svg{ + transform: scale(1.5); +} + +.ant-tabs-tab-next:not(.ant-tabs-tab-btn-disabled):hover, +.ant-tabs-tab-prev:not(.ant-tabs-tab-btn-disabled):hover{ + background-color:var(--main-primary-escuro) !important; + color:#fff !important; +} + +.ant-tabs-tab-next.ant-tabs-tab-btn-disabled, +.ant-tabs-tab-prev.ant-tabs-tab-btn-disabled{ + color:var(--main-primary-claro) !important; +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll { + border-bottom:3px solid var(--main-primary-escuro); +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab{ + margin:0px; + padding:14px 30px; + border-bottom:3px solid transparent; + transform: all 1s easyin !important; + background-color:#fff !important; +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab.ant-tabs-tab-active, +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab:hover{ + background-color:var(--main-grey-claro) !important; + color: var(--main-primary-escuro); + border-bottom:3px solid var(--main-primary-escuro); +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-ink-bar{ + height:0px !important; + background-color:var(--main-primary-escuro) !important; +} + +/* LINKS */ +a{ + color: var(--main-primary-escuro); +} + +a:hover{ + color: var(--main-secondary-escuro); +} + +.ant-breadcrumb-link a:hover{ + color: var(--main-primary-escuro) !important;; +} + +.ant-breadcrumb-link .anticon{ + margin-top:5px !important; +} + +.ant-breadcrumb-link .ant-select-arrow .anticon{ + margin-top:0px !important; +} + +/* USER MENU */ +.user-menu > span{ + border-bottom:5px solid transparent; +} + +.user-menu > span:hover{ + border-bottom:5px solid var(--main-primary-escuro); + background-color:var(--main-grey-claro) !important; +} + +.ant-dropdown .ant-dropdown-menu-item:hover{ + background-color:var(--main-primary-claro) !important; +} + +.ant-dropdown .ant-dropdown-menu-item:hover *{ + color:#000; +} + +.ant-dropdown .ant-dropdown-menu-item.ant-dropdown-menu-item-selected, +.ant-dropdown .ant-dropdown-menu-item.ant-dropdown-menu-item-selected:hover *{ + background-color:var(--main-primary-escuro) !important; + color:#fff !important; +} + +/* CHECKBOXES */ +.ant-checkbox-checked .ant-checkbox-inner { + background-color: var(--main-primary-escuro) !important; + border-color: var(--main-primary-escuro) !important; +} + +.ant-checkbox .ant-checkbox-inner { + border-color: var(--main-primary-escuro) !important; +} + +.ant-checkbox-indeterminate .ant-checkbox-inner::after{ + background-color: var(--main-primary-escuro) !important; +} + +/* SEARCH */ +/* +.ant-input-search .ant-input-wrapper.ant-input-group{ + display:flex; + justify-content: flex-end; + padding-right:2%; + +} + +.ant-input-search.ant-input-affix-wrapper{ + width:300px !important; + transform: width 1s easyin; + border:0px; +} + +.ant-input-search.ant-input-affix-wrapper-focused{ + width:98% !important; + border:0px !important; + border-color:transparent !important; +} +*/ + +.breadcrumb-card > .ant-card-body >.ant-row{ + display:flex; + align-items: end; + height:90px; +} + +.ant-breadcrumb > span:last-child label, +.ant-breadcrumb-link .router-link-exact-active{ + font-size:35px; + position:absolute; + left:10px; + top:-65px; + /*! width:100vh !important; */ +} + +.ant-card-head .ant-card-head-title{ + font-size:35px; +} + +.breadcrumb-card > .ant-card-body >.ant-row .ant-col:last-child{ + padding-bottom:7px; +} + +.ant-input-search .ant-input-group-addon{ + padding:0px !important; + width:30px !important; +} + +.ant-input-search .ant-input-group-addon button{ + height:30px !important; + width:30px; + border-radius:7px 0px 0px 7px; + border:2px solid var(--main-primary-claro) !important; +} + +.ant-select.project-select > .ant-select-selector, +.ant-input-affix-wrapper, +.ant-input-affix-wrapper input, +.ant-input-search .ant-input-search.input-search, +.ant-input-search .ant-input-search.input-search input{ + background-color:white !important; +} + +.ant-select.project-select.ant-select-focused > .ant-select-selector, +.ant-input-affix-wrapper.ant-input-affix-wrapper-focused, +.ant-input-affix-wrapper.ant-input-affix-wrapper-focused input, +.ant-input-search .ant-input-search.input-search.ant-input-affix-wrapper-focused, +.ant-input-search .ant-input-search.input-search.ant-input-affix-wrapper-focused input{ + background-color: var(--main-grey-escuro) !important; + color:#fff !important; +} + +.ant-input-search .ant-input-search.input-search.ant-input-affix-wrapper-focused .anticon{ + background-color:red !important; +} + +.user-layout .ant-input-affix-wrapper-focused .ant-input-prefix, +.user-layout .ant-input-affix-wrapper-focused .ant-input-suffix{ + color:#fff !important; +} + +/* +.ant-select.project-select.ant-select-focused .anticon path{ + color:#fff !important; + stroke:#fff !important; +} +*/ + +.ant-input-search .ant-input-search.input-search input::placeholder { + color: #666; +} + +.ant-input-affix-wrapper.ant-input-affix-wrapper-focused input::placeholder, +.ant-input-search .ant-input-search.input-search.ant-input-affix-wrapper-focused input::placeholder{ + color:#ffffffDD !important; +} + +.ant-input-search .ant-input-search.input-search{ + border-radius:0px 7px 7px 0px; +} + +.anticon-search.ant-input-search-icon{ + background-color:var(--main-primary-escuro) !important; + position:absolute; + height:30px; + width:30px; + border-radius:0px 7px 7px 0px; + display:flex; + justify-content: center; + align-items: center; + color:#fff; + right:0; +} + +.anticon-search.ant-input-search-icon:hover{ + background-color:var(--main-secondary-escuro) !important; +} + +/* SWITCH E BOTAO */ +button.ant-btn:not(.ant-btn-icon-only){ + background-color:var(--main-primary-escuro) !important; + color:#fff; + border:0px; +} + +button.ant-btn:not(.ant-btn-icon-only):hover{ + background-color:var(--main-secondary-escuro) !important; + color:#fff; +} + +.ant-radio .ant-radio-inner{ + border-color:var(--main-primary-escuro) !important; +} + +.ant-radio .ant-radio-inner:after{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-switch{ + background-color:var(--main-primary-claro); + border-color:var(--main-primary-escuro) !important; + height:24px; + /*! position:absolute; */ +} + +.ant-switch-loading-icon, .ant-switch::after{ + height:20px; +} + +.ant-switch-checked{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-switch span{ + color:#444; +} + +.ant-switch-checked span{ + color:#fff; +} + +.ant-slider-handle, +.ant-slider-handle .ant-tooltip-open{ + background-color: #fff; + border: solid 2px var(--main-primary-escuro); + border-radius: 50%; + box-shadow: 0; +} + +.ant-slider-handle.ant-tooltip-open{ + box-shadow: 0px 0px 5px var(--main-grey-claro) !important; + border: solid 2px var(--main-primary-escuro); +} + +.ant-slider:hover .ant-slider-handle, +.ant-slider-handle:hover, +.ant-slider-handle:focus{ + background-color: var(--main-primary-escuro); + border: solid 2px var(--main-primary-escuro) !important; + border-radius: 50%; + box-shadow: 0; + box-shadow: 0px 0px 5px var(--main-grey-claro) !important; +} + +.ant-slider-track{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-slider-rail{ + background-color:var(--main-primary-claro) !important; +} + + +/* HEADER */ +.layout.ant-layout .header{ + background-color:#fff !important; +} + +/* MENU ACTIVE */ +.ant-menu-vertical .ant-menu-item::after, .ant-menu-vertical-left .ant-menu-item::after, .ant-menu-vertical-right .ant-menu-item::after, .ant-menu-inline .ant-menu-item::after { + border-color:var(--main-primary-escuro) !important; + /*! color: red !important; */ +} + +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item a, +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected a{ + color:#000 !important; +} + +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item.ant-menu-item-selected { + background-color: var(--main-primary-claro) !important; +} + +/* MENU LIGHT */ +.sider.light .ant-menu-light, +.sider.light .ant-menu-submenu > .ant-menu{ + background-color:var(--main-secondary-escuro); + background-color:transparent; +} + + +.sider.light .ant-menu-dark{ + background-color:green; +} + +.ant-menu-submenu-inline > .ant-menu-submenu-title .ant-menu-submenu-arrow::before, +.ant-menu-submenu-inline > .ant-menu-submenu-title .ant-menu-submenu-arrow::after{ + background-color: #000 !important; +} + +.ant-menu-submenu-inline:hover .ant-menu-submenu-title .ant-menu-submenu-arrow::before, +.ant-menu-submenu-inline:hover .ant-menu-submenu-title .ant-menu-submenu-arrow::after{ + background-color: #fff !important; +} + +/* MENU ITEM */ +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item:hover, +.ant-menu:not(.ant-menu-horizontal) .ant-menu-submenu div:hover{ + background-color: var(--main-primary-escuro) !important; +} + +.ant-menu-inline-collapsed .ant-menu-submenu-title{ + margin:0px !important; +} + + +/* CARDS DASHBOARD */ +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(2) .ant-card.usage-dashboard-chart-card{ + background-color:var(--main-vermelho) !important; +} + +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(1) .ant-card.usage-dashboard-chart-card{ + background-color:var(--main-verde) !important; +} + +.usage-dashboard-chart-card-inner h2, +.usage-dashboard-chart-card-inner h3{ + color:#fff !important; +} + +/* CHARTS */ + +.ant-progress .ant-progress-status-normal path{ + stroke:var(--main-exception) !important; +} + +.ant-progress-circle.ant-progress-status-normal .ant-progress-text{ + color:var(--main-normal) !important; + font-weight: bold +} + +.ant-progress.ant-progress-status-normal path.ant-progress-circle-path{ + stroke:var(--main-normal) !important; +} + +.ant-progress.ant-progress-status-exception path.ant-progress-circle-path, +.ant-progress.ant-progress-status-active path.ant-progress-circle-path{ + stroke:var(--main-exception) !important; +} + +.ant-progress path.ant-progress-circle-trail{ + stroke:var(--main-grey-claro) !important; +} + +.ant-progress-circle.ant-progress-status-exception .ant-progress-text, +.ant-progress-circle.ant-progress-status-active .ant-progress-text{ + color:var(--main-exception) !important; + font-weight: bold +} + +/* LIST TABLES */ +.ant-list .ant-list-item{ + border-left:5px solid transparent !important; + padding-left:10px; +} + +.ant-list .ant-list-item:hover{ + background-color:var(--main-grey-claro) !important; + border-left:5px solid var(--main-primary-escuro) !important; +} + +.ant-list .ant-list-item .ant-btn{ + border-color:var(--main-grey-escuro) !important; + background-color:var(--main-grey-claro) !important; + color:var(--main-grey-escuro) !important; + +} + +.ant-list .ant-list-item:hover .ant-btn{ + border-color:red!important; + background-color:var(--main-primary-escuro) !important; + color:#fff !important; +} + +.ant-list .ant-list-item:hover .ant-btn:hover{ + border-color:var(--main-secondary-escuro) !important; + background-color:var(--main-secondary-escuro) !important; +} + +/* OTHERS */ + +.ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected{ + background-color:var(--main-primary-claro) +} + +/* MODAL */ +.ant-modal .ant-modal-header{ + background-color:var(--main-secondary-escuro); +} + +.ant-modal .ant-modal-header *{ + color:#fff; +} + +.ant-modal .ant-row.ant-form-item{ + margin-bottom:20px !important; +} + +.ant-modal .ant-row.ant-form-item .ant-form-item-label{ + padding-bottom:0px !important; +} + +.ant-row.ant-form-item{ + margin-bottom:20px !important; +} + +.ant-steps-item .ant-form.ant-form-vertical{ + margin-top:20px !important; +} + +.ant-row.ant-form-item .ant-form-item-label{ + padding-bottom:0px !important; +} + +.ant-modal .ant-modal-title{ + font-size:20px +} + +.ant-modal .ant-modal-title .anticon{ + font-size:15px +} + +.ant-modal .ant-modal-body .anticon:not(.anticon-cloud-upload){ + position:absolute; + transform: scale(0.9); + margin-left:5px; +} + +.ant-select-selector .anticon{ + position: relative !important; + margin-left:0px !important; +} + +.ant-modal .anticon:hover{ + color:var(--main-primary-escuro); +} + +.ant-modal .ant-modal-close{ + color:#fff; + height:54px; + transition:all 1s easyin; + background-color:transparent; +} + +.ant-modal .ant-modal-close:hover{ + background-color:var(--main-primary-escuro); +} + +.ant-modal .ant-modal-close-x:hover svg{ + color:#fff; +} + +.ant-modal .ant-modal-close-x svg{ + transform: scale(1.3); +} + +/* REGISTRATIONS FORMS AND ADDS */ +.ant-steps-item-tail{ + margin-top:15px; +} + +.ant-steps-item-tail:after{ + background-color:var(--main-grey-claro) !important; +} + +.ant-steps-item-icon{ + background-color:var(--main-primary-escuro) !important; + border-color:var(--main-primary-escuro) !important; + margin-top:10px +} + +.ant-steps-item .ant-card-head{ + padding:0px; +} + +.ant-steps-item .ant-radio-group .ant-card-grid{ + box-shadow: ;none; + border:2px solid var(--main-grey-claro) +} + +.ant-steps-item .ant-card-meta-description{ + color:var(--main-grey-escuro) !important; +} + +.ant-steps-item .ant-card-body .ant-tabs-nav > div .ant-tabs-tab, +.ant-steps-item .ant-card-contain-tabs .ant-card-body{ + background-color:var(--main-grey-claro) !important; +} + +.ant-steps-item .ant-card-grid .ant-radio-wrapper{ + width:100%; + border-radius:7px; + padding:10px; +} + +.ant-steps-item .ant-radio-wrapper:hover{ + background-color:green !important; +} + +.ant-steps-item .ant-card-grid .ant-radio-wrapper:hover{ + background-color: blue !important; +} + +.ant-steps-item .ant-radio-group label:not(.ant-radio-button-wrapper-checked):hover{ + background-color:transparent !important; +} + +.ant-steps-item #zoneid .ant-card-grid .ant-radio-wrapper{ + background-color: var(--main-grey-claro) !important; +} + +.ant-steps-item #zoneid .ant-card-grid .ant-radio-wrapper:hover{ + background-color: var(--main-primary-claro) !important; +} + +.ant-steps-item .ant-card-grid .ant-radio-wrapper .anticon{ + margin-left:0px !important; + width:100%; + margin-top:0px !important; +} + +.ant-steps-item .ant-list .ant-list-item:hover{ + background-color:var(--main-primary-claro) !important; + border-left:5px solid transparent !important; +} + +.ant-steps-item{ + margin-bottom:30px; + margin-top:0px; +} + +.ant-table-body, +.ant-list-vertical.form-item-scroll .ant-spin-container{ + max-height:none !important;; +} + +.ant-steps-item-title{ + width:100%; + height:40px; + display: flex; + align-items: center; + border-top:2px solid var(--main-primary-claro) !important; + font-weight:00 !important; + font-size:22px !important; +} + + +.ant-menu-submenu-arrow:after{ + /*! color:red !important; */ +} + +button.ant-btn.ant-btn-dangerous:not(.ant-btn-icon-only) +{ + background-color:var(--main-vermelho) !important; +} + +.resource-detail-item{ + border-bottom: 1px solid #ddd; + padding-bottom: 20px; +} + +.account-center-tags .ant-divider-horizontal{ + display:none +} + +.ant-table tr.ant-table-row-selected td{ + background-color:var(--main-secondary-claro); +} + +/* MODAL Steps */ + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item-title{ + font-size:14px !important; + border-top:0px !important; + justify-content: center; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item.ant-steps-item-wait .ant-steps-item-icon{ + background-color:#ddd !important; + border-color:#999 !important; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item.ant-steps-item-wait .ant-steps-item-icon span{ + color:#999; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item.ant-steps-item-finish .ant-steps-item-icon{ + background-color:#555 !important; + border-color:#555 !important; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item.ant-steps-item-finish .ant-steps-item-icon span{ + color:white !important; + right:-3px; + top:9px; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item-container .ant-steps-item-icon{ + background-color:var(--main-primary-claro) !important; + margin-top:0px; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item.ant-steps-item-active .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item.ant-steps-item-wait .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{ + background-color:var(--main-primary-claro) !important; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item.ant-steps-item-finish .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{ + background-color:var(--main-secondary-escuro) !important; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item.ant-steps-item-finish .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{ + color:white !important; + left:0px !important; + top:0px; +} + +.ant-modal-body .ant-table-body .ant-tag span{ + position:relative !important; +} + +.ant-modal-body .ant-table-body .traffic-select-item > span button.ant-btn-circle{ + padding-top:15px !important; +} + +.ant-modal-body .ant-table-body button.ant-btn-circle.ant-btn-dangerous{ + background-color:var( --main-vermelho); + border-color:var( --main-vermelho); +} + +.ant-modal-body .ant-form-item-control-input .anticon.anticon-close-circle{ + top:0px !important; + bottom:0px !important; + left:-10px !important; +} + +.ant-modal-body .ant-form-item-control-input .ant-select-arrow{ + right:15px +} + +.ant-form-item-has-feedback .ant-form-item-control-input :not(.ant-input-group-addon) > .ant-select .ant-select-arrow{ + right:40px +} + +.ant-modal-body .ant-form-item-control-input .ant-select-clear{ + background-color:transparent !important; + top:13px; + right:-20px !important; +} + +.ant-modal-body .ant-table-body button.ant-btn-circle.ant-btn-dangerous .anticon{ + /*! left:2px; */ + /*! top:7px; */ +} + +.ant-modal-body .ant-table-body button.ant-btn-circle.ant-btn-dangerous:hover, +.ant-modal-body .ant-table-body .traffic-select-item > span button.ant-btn-circle:hover{ + background-color:var(--main-secondary-escuro); + border-color:var(--main-secondary-escuro); +} + +.ant-modal-body .ant-table-body button.ant-btn-circle.ant-btn-dangerous .anticon, +.ant-modal-body .ant-table-body .traffic-select-item > span button.ant-btn-circle:hover svg{ + color:#fff; +} + +.ant-modal-body .ant-table-body .traffic-select-item > span button.ant-btn-circle > span{ + top:4px; + left:-1px; +} + +.ant-modal-body .traffic-select-item .ant-select-arrow .anticon.anticon-down{ + margin-left:0px !important; +} + +.ant-modal-body .ant-form-item-control-input .anticon.anticon-close-circle, +.ant-modal-body .ant-form-item-control-input .anticon.anticon-check-circle{ + left:5px; + top:4px !important; +} + +.ant-modal-body .card-waiting-launch .anticon-check-circle{ + font-size:40px !important; + left:60px; + top:40px +} + +.ant-modal-body .anticon-rocket{ + left:1px; + bottom:7px; +} + +.ant-modal-body .anticon-rocket:hover{ + color:inherit !important; +} + +.anticon-loading{ + top:0px; +} + +.card-launch-content .ant-steps-item-finish .anticon.anticon-check.ant-steps-finish-icon{ + right:-6px; + color:#fff; + top:19px +} + +.card-launch-content .ant-steps-item .ant-steps-item-icon .ant-steps-icon .anticon{ + left:-10px +} + +.card-launch-content .ant-steps-item .ant-steps-item-icon{ + min-width:23px !important; +} + +.card-launch-content .anticon.anticon-close-circle{ + left:-15px !important; +} + +.card-launch-content .anticon.anticon-loading{ + top:1px +} + + +.ant-modal-body .ant-table-bordered .ant-table-body .ant-table-row-cell-break-word .ant-btn-circle.ant-btn-dangerous{ + background-color:var(--main-vermelho) !important; + + &:hover{ + background-color:var(--main-secondary-escuro) !important; + } + +} + +.ant-modal-body .ant-table-bordered .ant-table-body .ant-table-row-cell-break-word .ant-btn-circle.ant-btn-dangerous span{ + background-color: transparent; + margin-right:5px !important; +} + +.ant-alert:not(.ant-alert-no-icon) .ant-alert-message{ + margin-left:40px; +} + +.ant-btn-dangerous.ant-btn-primary.ant-btn-dangerous{ + background:var(--main-vermelho); + border-color:var(--main-vermelho); +} diff --git a/ui/public/css/dark-theme.css b/ui/public/css/dark-theme.css new file mode 100644 index 00000000000..3af275b966f --- /dev/null +++ b/ui/public/css/dark-theme.css @@ -0,0 +1,1592 @@ +/* +* 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. +*/ + +@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); + +:root { + --main-verde: #3E7C59; + --main-vermelho: #ae0000; + --main-normal:#b2e061; + --main-exception:#fd7f6f; + --main-primary-claro: #0094F055; + --main-primary-text: #A1E4F0; + --main-primary-escuro: #B08A2A; + --main-secondary-claro: #B08A2A22; + --main-secondary-escuro: #7eb0d5; + --main-grey-claro: #ccc; + --main-grey-escuro: #444; + --main-image-menu: url("../assets/logo.svg"); + --main-image-login: url("../assets/banner.svg"); + --main-image-icon: url("../assets/mini-logo.svg"); + --main-linear-cora: #333; + --main-linear-corb: #333; +} + +#app{ + background-color: var(--main-linear-cora); +} + +#app > div > form.ant-form.ant-form-horizontal h1, +#app > div > form.ant-form.ant-form-horizontal p{ + color:#fff !important; +} + +#app > div > form > img.user-layout-logo{ + display:none !important; +} + +#app > div > form{ + background-image:var(--main-image-login); + background-repeat:no-repeat; + background-size:250px; + background-position:top center; + padding-top:120px; +} + +.userLayout .user-layout-header{ + min-height:130px; + background-image:var(--main-image-login); + background-repeat:no-repeat; + background-size:400px; + background-position:top center; + margin-bottom:20px; +} + +.ant-layout-sider.light.ant-fixed-sidemenu > div > div{ + height: 90px; + background-image:var(--main-image-menu); + background-repeat:no-repeat; + background-size:150px; + background-position:25px 25px; + margin-bottom:20px; +} + +.ant-layout-sider.light.ant-fixed-sidemenu.ant-layout-sider-collapsed > div > div{ + height: 80px; + background-image:var(--main-image-icon); + background-repeat:no-repeat; + background-size:60px; + background-position:10px 20px; +} + +.sider.light .ant-menu-light a, +.sider.light .ant-menu-submenu > .ant-menu-submenu-title { + color:#fff !important; +} + +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-active:hover a, +.ant-menu:not(.ant-menu-horizontal) .ant-menu-submenu span.ant-menu-title-content:hover{ + color:#fff !important; +} + +.ant-menu-submenu ul a, +.ant-menu-submenu ul .ant-menu-item-selected a span{ + color:#000 !important; +} + +/* USER MENU DARK */ +*{ + font-family: 'Poppins' , Courier !important; +} + +aside, +.ant-menu-submenu ul{ + background: linear-gradient(var(--main-linear-cora), var(--main-linear-corb)) !important; +} + +.ant-menu-submenu ul a, +.ant-menu-submenu ul .ant-menu-item-selected a span{ + color:#fff !important; +} + +html{ + background-color: var(--main-secondary-escuro); +} + +/* HEADER */ +.layout.ant-layout .header{ + background: linear-gradient(var(--main-linear-cora), var(--main-linear-corb)) !important; + box-shadow: 0px 0px 10px #00000055; +} + +.layout.ant-layout .header .user-menu > span.ant-dropdown-open{ + background-color:var(--main-primary-claro) !important; + border-bottom:5px solid var(--main-primary-escuro) !important; +} + +.layout.ant-layout .header .anticon-menu-unfold:hover, +.layout.ant-layout .header .anticon-menu-fold:hover, +.layout.ant-layout .header .user-menu > span:hover{ + background-color:var(--main-primary-escuro) !important; + color:#fff !important; +} + +.layout.ant-layout .header .user-menu > span:hover *, +.layout.ant-layout .header .user-menu > span *{ + color:#fff !important; +} + +.layout.ant-layout .header .anticon-menu-unfold, +.layout.ant-layout .header .anticon-menu-fold{ + color:#fff; +} + +.header-notice-opener{ + padding-left:15px; +} + + +.ant-table-tbody > .ant-table-row:hover td{ + background-color:var(--main-secondary-claro) !important; +} + +/* SIDE BAR */ +.ant-card { + background-color: var(--main-linear-cora) !important; +} + +.vm-info-card .ant-card-body .ant-card-bordered{ + border-top:2px solid var(--main-primary-escuro) !important; + margin-bottom:50px; +} + +.vm-info-card .ant-card-body .ant-card-bordered .ant-card-head, +.vm-info-card .ant-card-body .ant-card-bordered .ant-card-grid{ + padding:10px 0px !important; +} + +.vm-info-card .ant-card-body .ant-card-bordered .resource-detail-item{ + margin-bottom:20px; +} + +.vm-info-card .ant-card-body .ant-card-bordered .resource-detail-item .resource-detail-item__label{ + font-weight:900; +} + +/* USER LOGIN SCREEN */ + +.ant-tabs-tab, +.user-layout{ + background-color:var(--main-grey-claro) !important; +} + +.user-layout .ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab{ + background-color:var(--main-grey-claro) !important; + color: var(--main-primary-escuro); + border-bottom:0px solid var(--main-primary-escuro); +} + +.user-layout .ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab:hover{ + background-color:var(--main-secondary-escuro) !important; + color: #fff; + border-bottom:3px solid var(--main-primary-escuro); +} + +.user-layout .ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab.ant-tabs-tab-active{ + background-color:var(--main-grey-claro) !important; + color: var(--main-primary-escuro); + border-bottom:3px solid var(--main-primary-escuro); +} + +.user-layout .ant-tabs-nav{ + width:100%; +} + +.user-layout .ant-tabs-nav > div > div{ + width:50%; +} + +/* IMAGES LOGO */ +.userLayout .user-layout-logo{ + display:none; +} + +.ant-layout-sider.light.ant-fixed-sidemenu > div > div > img{ + display:none !important; +} + +/* PROGRESS */ +.ant-progress.ant-progress-status-active .ant-progress-inner, +.ant-progress.ant-progress-status-normal .ant-progress-inner{ + background-color:var(--main-grey-claro) !important; +} + +.ant-progress.ant-progress-status-active .ant-progress-inner .ant-progress-bg, +.ant-progress.ant-progress-status-normal .ant-progress-inner .ant-progress-bg{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-progress .ant-progress-text{ + color:var(--main-grey-claro) !important; + font-weight:700; +} + +.ant-progress.ant-progress-status-exception .ant-progress-inner .ant-progress-bg{ + background-color:var(--main-exception) !important; +} + +/* FORMS */ +.ant-input-number-input{ + color:#000; +} + +.ant-input-number-disabled .ant-input-number-input{ + color:#666; +} + +.ant-select-selector, +.ant-input{ + background-color:white !important; + color:var(--main-grey-escuro) !important; +} + +.ant-select.ant-select-focused .ant-select-arrow .anticon{ + color:var(--main-grey-claro); +} + +.ant-select-selector .ant-select-selection-placeholder, +.ant-select, +.ant-input::placeholder{ + color:var(--main-grey-escuro) !important; +} + + +.ant-select-selector:hover, .ant-select-selector:focus, +.ant-input:hover, .ant-input:focus{ + box-shadow: 0px 0px 3px var(--main-primary-escuro); + border-color:var(--main-primary-escuro) !important; +} + +.ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector { + border-color: var(--main-primary-escuro); + border-right-width: 1px !important; + box-shadow: 0 0 0 2px var(--main-primary-claro); +} + +.ant-select-dropdown .ant-select-item.ant-select-item-option-selected, +.ant-select-dropdown .ant-select-item:hover{ + background-color:var(--main-primary-claro) !important; +} + +/* PAGINATION */ +.ant-pagination .ant-pagination-item.ant-pagination-item-active{ + background-color:var(--main-primary-escuro) !important; + border-color:#fff; +} + +.ant-pagination .ant-pagination-item.ant-pagination-item-active a{ + color:#fff !important; + font-weight:bold; +} + +.ant-pagination .ant-pagination-item:not(.ant-pagination-item-active){ + background-color:#fff !important; +} + +.ant-pagination .ant-pagination-item:not(.ant-pagination-item-active):hover{ + background-color:var(--main-secondary-escuro) !important; + border-color:#666; +} + +.ant-pagination .ant-pagination-item:not(.ant-pagination-item-active):hover a{ + color:#666 !important; + font-weight:bold; +} + +.ant-pagination .ant-pagination-next span, +.ant-pagination .ant-pagination-prev span{ + color:var(--main-grey-escuro) !important; +} + +.ant-pagination .ant-pagination-next span:hover, +.ant-pagination .ant-pagination-prev span:hover{ + color:var(--main-secondary-escuro) !important; +} + + +/* RADIO GROUP */ +.ant-radio-group label.ant-radio-button-wrapper-checked{ + background-color:var(--main-primary-escuro) !important; + border-color:var(--main-primary-escuro) !important; +} + +.ant-radio-group label:not(.ant-radio-button-wrapper-checked):hover{ + color:#000 !important; + background-color:var(--main-primary-claro) !important; +} + +.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before { + background-color:var(--main-primary-escuro) !important; +} + +/* TABLES */ +.ant-table{ + font-size:16px !important; + background:var(--main-linear-cora) !important; +} + +.ant-table th{ + border-bottom:5px solid #ffffff55!important; +} + +.ant-table th.ant-table-column-has-sorters:hover{ + border-bottom:5px solid var(--main-primary-escuro) !important; + background-color:#ffffff22 !important; +} + +.ant-table thead.ant-table-thead{ + border-bottom:5px solid var(--main-primary-escuro) !important; +} + +.anticon.ant-table-column-sorter-up.on *, +.anticon.ant-table-column-sorter-down.on *{ + color: var(--main-primary-escuro) !important; +} + +.anticon.anticon-camera.upload-icon{ + background:#ffffff33 !important; + border:0px; +} + +.anticon.anticon-camera.upload-icon:hover{ + background:var(--main-secondary-escuro) !important; +} + +.anticon.anticon-camera.upload-icon{ + background: !important; +} + +/* PAGE TABS LEFT */ +.ant-tabs-left-bar .ant-tabs-nav-scroll { + border-right:3px solid var(--main-primary-escuro); +} + +.ant-tabs-left-bar .ant-tabs-nav-scroll .ant-tabs-tab{ + margin:0px; + padding:10px 15px; + border-right:3px solid transparent; + transition: all 1s ease !important; + background-color:var(--main-linear-cora) !important; +} + +.ant-tabs-left-bar .ant-tabs-nav-scroll .ant-tabs-tab.ant-tabs-tab-active, +.ant-tabs-left-bar .ant-tabs-nav-scroll .ant-tabs-tab:hover{ + background-color:#ffffff33 !important; + color: var(--main-primary-escuro); + border-right:3px solid var(--main-primary-escuro); +} + +.ant-tabs-left-bar .ant-tabs-nav-scroll .ant-tabs-ink-bar{ + display:none !important; + width:0px !important; + background-color:var(--main-primary-escuro) !important; +} + +/* PAGE TABS TOP */ +.ant-tabs-tab-next, +.ant-tabs-tab-prev{ + color:var(--main-primary-escuro) !important; +} + +.ant-tabs-tab-next svg, +.ant-tabs-tab-prev svg{ + transform: scale(1.5); +} + +.ant-tabs-tab-next:not(.ant-tabs-tab-btn-disabled):hover, +.ant-tabs-tab-prev:not(.ant-tabs-tab-btn-disabled):hover{ + background-color:var(--main-primary-escuro) !important; + color:#fff !important; +} + +.ant-tabs-tab-next.ant-tabs-tab-btn-disabled, +.ant-tabs-tab-prev.ant-tabs-tab-btn-disabled{ + color:var(--main-primary-claro) !important; +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll { + border-bottom:3px solid var(--main-primary-escuro); +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab{ + margin:0px; + padding:14px 30px; + border-bottom:3px solid transparent; + transform: all 1s easyin !important; + background-color:#fff !important; +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab.ant-tabs-tab-active, +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab:hover{ + background-color:var(--main-grey-claro) !important; + color: var(--main-primary-escuro); + border-bottom:3px solid var(--main-primary-escuro); +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-ink-bar{ + height:0px !important; + background-color:var(--main-primary-escuro) !important; +} + +/* LINKS */ +a{ + color: var(--main-primary-escuro); +} + +a:hover{ + color: var(--main-secondary-escuro); +} + +.ant-breadcrumb-link a:hover{ + color: var(--main-primary-escuro) !important;; +} + +.ant-breadcrumb-link .anticon{ + margin-top:5px !important; +} + +.ant-breadcrumb-link .ant-select-arrow .anticon{ + margin-top:0px !important; +} + + +/* USER MENU */ +.user-menu > span{ + border-bottom:5px solid transparent; +} + +.user-menu > span:hover{ + border-bottom:5px solid var(--main-primary-escuro); + background-color:var(--main-grey-claro) !important; +} + +.ant-dropdown .ant-dropdown-menu-item:hover{ + background-color:var(--main-primary-claro) !important; +} + +.ant-dropdown .ant-dropdown-menu-item:hover *{ + color:#000; +} + +.ant-dropdown .ant-dropdown-menu-item.ant-dropdown-menu-item-selected, +.ant-dropdown .ant-dropdown-menu-item.ant-dropdown-menu-item-selected:hover *{ + background-color:var(--main-primary-escuro) !important; + color:#fff !important; +} + +/* CHECKBOXES */ +.ant-checkbox-checked .ant-checkbox-inner { + background-color: var(--main-primary-escuro) !important; + border-color: var(--main-primary-escuro) !important; +} + +.ant-checkbox .ant-checkbox-inner { + border-color: var(--main-primary-escuro) !important; +} + +.ant-checkbox-indeterminate .ant-checkbox-inner::after{ + background-color: var(--main-primary-escuro) !important; +} + +/* SEARCH */ +/* +.ant-input-search .ant-input-wrapper.ant-input-group{ + display:flex; + justify-content: flex-end; + padding-right:2%; + +} + +.ant-input-search.ant-input-affix-wrapper{ + width:300px !important; + transform: width 1s easyin; + border:0px; +} + +.ant-input-search.ant-input-affix-wrapper-focused{ + width:98% !important; + border:0px !important; + border-color:transparent !important; +} +*/ + +.breadcrumb-card > .ant-card-body >.ant-row{ + display:flex; + align-items: end; + height:90px; +} + +.ant-breadcrumb > span:last-child label, +.ant-breadcrumb-link .router-link-exact-active{ + font-size:35px; + position:absolute; + left:10px; + top:-65px; + /*! width:100vh !important; */ +} + +.ant-card-head{ + background-color:var(--main-linear-cora); + color:#fff; +} + +.ant-card-head .ant-card-head-title{ + font-size:35px; +} + +.breadcrumb-card > .ant-card-body >.ant-row .ant-col:last-child{ + padding-bottom:7px; +} + +.ant-input-search .ant-input-group-addon{ + padding:0px !important; + width:30px !important; +} + +.ant-input-search .ant-input-group-addon button{ + height:30px !important; + width:30px; + border-radius:7px 0px 0px 7px; + border:2px solid var(--main-primary-escuro) !important; +} + +.ant-select.project-select > .ant-select-selector, +.ant-input-affix-wrapper, +.ant-input-affix-wrapper input, +.ant-input-search .ant-input-search.input-search, +.ant-input-search .ant-input-search.input-search input{ + background-color:white !important; +} + +.ant-input-affix-wrapper{ + border-radius: 7px; +} + +.ant-select.project-select.ant-select-focused > .ant-select-selector, +.ant-input-affix-wrapper.ant-input-affix-wrapper-focused, +.ant-input-affix-wrapper.ant-input-affix-wrapper-focused input, +.ant-input-search .ant-input-search.input-search.ant-input-affix-wrapper-focused, +.ant-input-search .ant-input-search.input-search.ant-input-affix-wrapper-focused input{ + background-color: var(--main-grey-claro) !important; + color: var(--main-grey-escuro) !important; +} + +.user-layout .ant-input-affix-wrapper-focused .ant-input-prefix, +.user-layout .ant-input-affix-wrapper-focused .ant-input-suffix{ + color:#333 !important; +} + +/* +.ant-select.project-select.ant-select-focused .anticon path{ + color:#fff !important; + stroke:#fff !important; +} +*/ + +.ant-input-search .ant-input-search.input-search input::placeholder { + color: #666; +} + +.ant-input-affix-wrapper.ant-input-affix-wrapper-focused input::placeholder, +.ant-input-search .ant-input-search.input-search.ant-input-affix-wrapper-focused input::placeholder{ + color:var(--main-grey-escuro) !important; +} + +.ant-input-search .ant-input-search.input-search{ + border-radius:0px 7px 7px 0px; +} + +.anticon-search.ant-input-search-icon{ + background-color:var(--main-primary-escuro) !important; + position:absolute; + height:30px; + width:30px; + border-radius:0px 7px 7px 0px; + display:flex; + justify-content: center; + align-items: center; + color:#fff; + right:0; +} + +.anticon-search.ant-input-search-icon:hover{ + background-color:var(--main-secondary-escuro) !important; +} + +/* SWITCH E BOTAO */ +button.ant-btn{ + background-color:var(--main-primary-escuro) !important; + color:#fff; + border:0px; +} + +button.ant-btn:hover{ + background-color:var(--main-secondary-escuro) !important; + color:#fff; +} + +.ant-radio .ant-radio-inner{ + border-color:var(--main-primary-escuro) !important; +} + +.ant-radio .ant-radio-inner:after{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-tabs-tabpane .ant-radio-button-wrapper{ + background-color:var(--main-linear-cora); +} + +.ant-switch{ + background-color:var(--main-primary-claro); + border-color:var(--main-primary-escuro) !important; + height:24px; + /*! position:absolute; */ +} + +.ant-switch-loading-icon, .ant-switch::after{ + height:20px; +} + +.ant-switch-disabled, +.ant-switch-checked{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-switch span{ + color:#444; +} + +.ant-switch-checked span{ + color:#fff; +} + +.ant-slider-handle, +.ant-slider-handle .ant-tooltip-open{ + background-color: #fff; + border: solid 2px var(--main-primary-escuro); + border-radius: 50%; + box-shadow: 0; +} + +.ant-slider-handle.ant-tooltip-open{ + box-shadow: 0px 0px 5px var(--main-grey-claro) !important; + border: solid 2px var(--main-primary-escuro); +} + +.ant-slider:hover .ant-slider-handle, +.ant-slider-handle:hover, +.ant-slider-handle:focus{ + background-color: var(--main-primary-escuro); + border: solid 2px var(--main-primary-escuro) !important; + border-radius: 50%; + box-shadow: 0; + box-shadow: 0px 0px 5px var(--main-grey-claro) !important; +} + +.ant-slider-track{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-slider-rail{ + background-color:var(--main-primary-claro) !important; +} + +.ant-avatar-icon{ + background-color:transparent; +} + + +/* HEADER */ +.layout.ant-layout .header{ + background-color:#fff !important; +} + +/* MENU ACTIVE */ +.ant-menu-vertical .ant-menu-item::after, .ant-menu-vertical-left .ant-menu-item::after, .ant-menu-vertical-right .ant-menu-item::after, .ant-menu-inline .ant-menu-item::after { + border-color:var(--main-primary-escuro) !important; +} + +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected a{ + color:#000 !important; +} + +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item.ant-menu-item-selected { + background-color: var(--main-primary-claro) !important; +} + +/* MENU LIGHT */ +.sider.light .ant-menu-light, +.sider.light .ant-menu-submenu > .ant-menu{ + background-color:transparent; +} + +.ant-menu-submenu-inline > .ant-menu-submenu-title .ant-menu-submenu-arrow::before, +.ant-menu-submenu-inline > .ant-menu-submenu-title .ant-menu-submenu-arrow::after{ + background-color: #fff !important; +} + +/* MENU ITEM */ +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item:hover, +.ant-menu:not(.ant-menu-horizontal) .ant-menu-submenu div:hover{ + background-color: var(--main-primary-escuro) !important; +} + +.ant-menu-inline-collapsed .ant-menu-submenu-title{ + margin:0px !important; +} + + +/* CARDS DASHBOARD */ +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(2) .ant-card.usage-dashboard-chart-card{ + background-color:var(--main-vermelho) !important; +} + +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(1) .ant-card.usage-dashboard-chart-card{ + background-color:var(--main-verde) !important; +} + +.usage-dashboard-chart-card-inner h2, +.usage-dashboard-chart-card-inner h3{ + color:#fff !important; +} + +/* CHARTS */ + +.ant-progress .ant-progress-status-normal path{ + stroke:var(--main-exception) !important; +} + +.ant-progress-circle.ant-progress-status-normal .ant-progress-text{ + color:var(--main-normal) !important; + font-weight: bold +} + +.ant-progress.ant-progress-status-normal path.ant-progress-circle-path{ + stroke:var(--main-normal) !important; +} + +.ant-progress.ant-progress-status-exception path.ant-progress-circle-path, +.ant-progress.ant-progress-status-active path.ant-progress-circle-path{ + stroke:var(--main-exception) !important; +} + +.ant-progress path.ant-progress-circle-trail{ + stroke:var(--main-grey-claro) !important; +} + +.ant-progress-circle.ant-progress-status-exception .ant-progress-text, +.ant-progress-circle.ant-progress-status-active .ant-progress-text{ + color:var(--main-exception) !important; + font-weight: bold +} + +/* LIST TABLES */ +.ant-list .ant-list-item{ + border-left:5px solid transparent !important; + padding-left:10px; +} + +.ant-list .ant-list-item:hover{ + background-color:#ffffff33 !important; + border-left:5px solid var(--main-primary-escuro) !important; +} + +.ant-list.ant-list-split .ant-list-item:hover{ + background-color:transparent !important; + +} + +.ant-list .ant-list-item .ant-btn{ + opacity:0; +} + +.ant-list .ant-list-item:hover .ant-btn{ + background-color:var(--main-primary-escuro) !important; + color:#fff !important; + opacity:1; +} + +.ant-list .ant-list-item-action-split{ + opacity:0; +} + +.ant-list .ant-list-item:hover .ant-btn:hover{ + border-color:var(--main-secondary-escuro) !important; + background-color:var(--main-secondary-escuro) !important; +} + +/* OTHERS */ + +.ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected{ + background-color:var(--main-primary-claro) +} + +/* MODAL */ +.ant-modal .ant-modal-header{ + background-color:var(--main-primary-claro); +} + +.ant-modal .ant-modal-header *{ + color:#fff; +} + +.ant-modal .ant-row.ant-form-item{ + margin-bottom:20px !important; +} + +.ant-modal .ant-row.ant-form-item .ant-form-item-label{ + padding-bottom:0px !important; +} + +.ant-row.ant-form-item{ + margin-bottom:20px !important; +} + +.ant-steps-item .ant-form.ant-form-vertical{ + margin-top:20px !important; +} + +.ant-row.ant-form-item .ant-form-item-label{ + padding-bottom:0px !important; +} + +.ant-modal .ant-modal-title{ + font-size:20px +} + +.ant-modal .ant-modal-title .anticon{ + font-size:15px +} + +.ant-modal .ant-modal-body .anticon{ + /* position:absolute; */ + transform: scale(0.9); + margin-left:5px; +} + +.ant-select-selector .anticon{ + position: relative !important; + margin-left:0px !important; +} + +.ant-modal .anticon:hover{ + color:var(--main-primary-escuro); +} + +.ant-modal .ant-modal-close{ + color:#fff; + height:54px; + transition:all 1s easyin; + background-color:transparent; +} + +.ant-modal .ant-modal-close:hover{ + background-color:var(--main-primary-escuro); +} + +.ant-modal .ant-modal-close-x:hover svg{ + color:#fff; +} + +.ant-modal .ant-modal-close-x svg{ + transform: scale(1.3); +} + +/* REGISTRATIONS FORMS AND ADDS */ +.ant-steps-item-tail{ + margin-top:15px; +} + +.ant-steps-item-tail:after{ + background-color:var(--main-grey-claro) !important; +} + +.ant-steps-item-icon{ + background-color:var(--main-primary-escuro) !important; + border-color:var(--main-primary-escuro) !important; + margin-top:10px +} + +.ant-steps-item .ant-card-head{ + padding:0px; +} + +.ant-steps-item .ant-radio-group .ant-card-grid{ + box-shadow: ;none; + border:2px solid var(--main-grey-claro) +} + +.ant-steps-item .ant-card-meta-description{ + color:var(--main-grey-claro) !important; +} + +.ant-steps-item .ant-card-body .ant-tabs-nav > div .ant-tabs-tab, +.ant-steps-item .ant-card-contain-tabs .ant-card-body{ + background-color:var(--main-linear-cora) !important; +} + +.ant-steps-item .ant-card-grid .ant-radio-wrapper{ + width:100%; + border-radius:7px; + padding:10px; +} + +.ant-steps-item .ant-radio-wrapper:hover{ + background-color:green !important; +} + + +.ant-steps-item .ant-card-grid .ant-radio-wrapper:hover{ + background-color: blue !important; +} + +.ant-steps-item .ant-radio-group label:not(.ant-radio-button-wrapper-checked):hover{ + background-color:transparent !important; +} + +.ant-steps-item #zoneid .ant-card-grid .ant-radio-wrapper{ + background-color: var(--main-grey-escuro) !important; +} + +.ant-steps-item #zoneid .ant-card-grid .ant-radio-wrapper:hover{ + background-color: var(--main-primary-claro) !important; +} + +.ant-steps-item .ant-card-grid .ant-radio-wrapper .anticon{ + margin-left:0px !important; + width:100%; + margin-top:0px !important; +} + +.ant-steps-item .ant-list .ant-list-item:hover{ + background-color:var(--main-primary-claro) !important; + border-left:5px solid transparent !important; +} + +.ant-steps-item{ + margin-bottom:30px; + margin-top:0px; +} + +.ant-table-body, +.ant-list-vertical.form-item-scroll .ant-spin-container{ + max-height:none !important;; +} + +.ant-steps-item-title{ + width:100%; + height:40px; + display: flex; + align-items: center; + border-top:0px solid #ffffff55 !important; + font-weight:00 !important; + font-size:22px !important; +} + +/* DARK HOME */ +body #userLayout{ + background-color:var(--main-linear-cora) !important; +} + +.userLayout .user-layout-header{ + margin-bottom:20px; +} + +.userLayout .anticon-translation{ + color:#FFF; +} + +.user-layout .ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab.ant-tabs-tab-active{ + background-color:var(--main-primary-escuro) !important; + color: #fff; + border-bottom:3px solid var(--main-primary-escuro); +} + +.user-layout .ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab{ + background-color:var(--main-linear-cora) !important; + color: #fff; + border-bottom:3px solid var(--main-primary-escuro); +} + +.ant-layout-footer *{ + color:#fff !important; +} + +.ant-card{ + border:0px !important; +} + +.ant-card-body *:not(a):not(.ant-tag):not(.ant-tag *):not(input):not(.ant-select-selection-item span):not(div.ant-select *), +.ant-table *:not(a):not(.ant-tag):not(.ant-tag *):not(input):not(.ant-select-selection-item span){ + color:#fff; +} + +.ant-input-suffix .anticon:not(.anticon-search ) *{ + color:#000 !important; +} + +.ant-alert-info *{ + color:#000 !important; +} + +.anticon.anticon-delete{ + background-color:var(--main-vermelho); + padding:0px; + border-radius:50px; +} + +.ant-popover-inner-content .anticon.anticon-delete{ + padding:0px; +} + +.anticon.anticon-delete svg path:first-child{ + fill:transparent; +} + +.anticon.anticon-delete svg path:last-child{ + fill:#fff; +} + +.ant-pagination .ant-select-item-option-content span{ + color:#000 !important; +} + +.ant-select-selection-item{ + color:#000 !important; +} + +.ant-table a{ + color:var(--main-primary-escuro) !important; +} + + +.ant-table tr, +.ant-table th, +.ant-table-footer, +.ant-card-body, +.ant-layout, +.ant-layout-footer{ + background-color:var(--main-linear-cora) !important; +} + +.ant-table tr.ant-table-row-selected td{ + background-color:var(--main-secondary-claro); +} + +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(1) .ant-card.usage-dashboard-chart-card .ant-card-body{ + background-color:var(--main-verde) !important; +} + +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(2) .ant-card.usage-dashboard-chart-card .ant-card-body{ + background-color:var(--main-vermelho) !important; +} + +.usage-dashboard .ant-card-body .ant-row .ant-col .ant-card.usage-dashboard-chart-card .ant-card-body{ + background-color:#fff !important; +} + +.usage-dashboard-chart-card-inner h2, +.usage-dashboard-chart-card-inner h3, +.usage-dashboard-chart-card-inner svg{ + color:#000 !important; + fill:#000; +} + +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(2) .ant-card.usage-dashboard-chart-card .ant-card-body .usage-dashboard-chart-card-inner svg, +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(1) .ant-card.usage-dashboard-chart-card .ant-card-body .usage-dashboard-chart-card-inner svg, +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(2) .ant-card.usage-dashboard-chart-card .ant-card-body .usage-dashboard-chart-card-inner h2, +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(1) .ant-card.usage-dashboard-chart-card .ant-card-body .usage-dashboard-chart-card-inner h2, +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(2) .ant-card.usage-dashboard-chart-card .ant-card-body .usage-dashboard-chart-card-inner h3, +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(1) .ant-card.usage-dashboard-chart-card .ant-card-body .usage-dashboard-chart-card-inner h3{ + color:#fff !important; + fill:#fff; +} + +.usage-dashboard .ant-card-body .ant-row .ant-col:nth-child(2) .ant-card.usage-dashboard-chart-card .ant-card-body{ + background-color:var(--main-vermelho) !important; +} + +.ant-table tr a, +.ant-table th a{ + color:var(--main-primary-text) !important; +} + +.ant-pagination{ + color:#fff; +} + +.ant-tabs-tabpane .ant-table tr, +.ant-tabs-tabpane .ant-table-footer, +.ant-tabs-tabpane .ant-table th{ + background-color:var(--main-linear-cora) !important; +} + +.usage-dashboard .ant-tabs-tabpane .ant-table table, +.usage-dashboard .ant-tabs-tabpane .ant-table tr, +.usage-dashboard .ant-tabs-tabpane .ant-table-footer, +.usage-dashboard .ant-tabs-tabpane .ant-table th{ + background-color:var(--main-linear-cora) !important; +} + +.ant-tabs-tabpane .ant-table tr, +.ant-tabs-tabpane .ant-table th{ + background-color: var(--main-linear-cora) !important; +} + +.ant-tabs-tabpane .ant-pagination{ + color:#000; +} + +.ant-tabs .ant-tabs-left-content{ + border-left: 0px !important; +} + +.ant-tabs .ant-tabs-bar{ + border-color:transparent; +} + +.ant-layout-content .page-header-wrapper-grid-content-main .ant-row .ant-col-lg-16 .ant-card-body{ + background-color:var(--main-linear-cora) !important; +} + +.ant-select-selection-item, +.ant-select-selector, +.ant-select-selector *{ + color:#666 !important; +} + +.ant-layout-content .page-header-wrapper-grid-content-main .ant-row .ant-col-lg-16 .ant-card-body *:not(a):not(input):not(.ant-radio-button-wrapper-checked span):not(button *):not(.ant-select-selector *):not(.ant-select-item-option-content){ + color:#fff; +} + +.ant-layout-content .page-header-wrapper-grid-content-main .ant-row .ant-col-lg-16 .ant-card-body p.ant-empty-description:not(.ant-radio-button-wrapper-checked span):not(button *){ + color:#fff !important; +} + +.input-search .ant-input-group-addon{ + border:0px; +} + +.ant-select-arrow path{ + color:#666 !important; +} + +.ant-input-search, +.ant-input-search .ant-input-search-icon{ + border:0px !important; +} + +.ant-progress.ant-progress-status-active .ant-progress-inner, +.ant-progress.ant-progress-status-normal .ant-progress-inner{ + background-color:#ffffff33 !important; +} + +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected a{ + color:#fff !important; +} + +.ant-menu span.ant-menu-title-content .custom-icon path, +.ant-menu span.ant-menu-title-content:hover .custom-icon path{ + fill:#fff !important; +} + +.ant-menu-submenu-popup.ant-menu-light, .ant-menu-light > .ant-menu, +.ant-menu-submenu ul, +.ant-menu-submenu ul .ant-menu-item { + background:var(--main-grey-escuro) !important; +} + +.ant-pagination .ant-pagination-item.ant-pagination-item-active{ + border:0px; +} + +.ant-pagination .ant-pagination-item{ + border:0px; + margin-left:2px !important; +} + +.ant-table-placeholder{ + background:var(--main-linear-cora) +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab{ + margin:0px; + padding:14px 30px; + border-bottom:3px solid transparent; + transform: all 1s easyin !important; + background-color:var(--main-linear-cora) !important; + font-weight: 600; +} + +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab.ant-tabs-tab-active, +.ant-tabs-top-bar .ant-tabs-nav-scroll .ant-tabs-tab:hover{ + background-color:var(--main-primary-escuro) !important; + color: #fff; + border-bottom:3px solid var(--main-primary-escuro); +} + +.ant-table .ant-table-body .ant-btn{ + background:var(--main-primary-escuro) !important; +} + +.ant-table .ant-table-body .ant-btn:hover{ + background:var(--main-primary-claro) !important; +} + +.ant-input-group-addon{ + background-color:transparent; +} + +/* FIM DARKMODE */ + +button.ant-btn.ant-btn-dangerous:not(.ant-btn-icon-only) +{ + background-color:var(--main-vermelho) !important; +} + +/* TREE FILES */ +.ant-tree-icon__customize, +.ant-tree.ant-tree-show-line li span.ant-tree-switcher{ + background-color:transparent; +} + +.ant-tree .ant-tree-node-content-wrapper:hover{ + background-color:inherit !important; +} + +.ant-tree .ant-tree-node-content-wrapper:hover span{ + color:var(--main-primary-escuro) !important; +} + + + + + + +.ant-dropdown-placement-bottomRight ul{ + background-color:#fff;; +} + + +.ant-dropdown-placement-bottomRight li *{ + color:#000 !important; +} + +.ant-table .ant-table-row-expand-icon{ + border-radius:50px; +} + +.ant-table .ant-table-expanded-row.ant-table-expanded-row-level-1:hover td, +.ant-table .ant-table-expanded-row.ant-table-expanded-row-level-1 td:hover{ + background-color:transparent !important; +} + +.ant-table .ant-table-row-expand-icon:after{ + color:#000; + display:flex; + justify-content: center; + align-items: center; + height:100%; + width:100%; +} + +.vm-info-card .resource-detail-item__details a, .vm-info-card .resource-detail-item a { + color: var(--main-primary-escuro); + cursor: default; + pointer-events: none; +} + +/* Modal */ +.ant-modal-content{ + background-color:var(--main-linear-cora); + border:1px solid #fff; +} + +.ant-modal-content .ant-radio-group.ant-radio-group-solid span{ + color:#000 !important; +} + +.ant-modal-content .ant-radio-group.ant-radio-group-solid label{ + background-color:#fff; +} + +.ant-modal-content .ant-radio-group.ant-radio-group-solid .ant-radio-button-wrapper-checked span, +.ant-modal-content .ant-radio-group.ant-radio-group-solid label:hover span{ + color:#fff !important; +} + +.ant-modal-content label{ + color:#fff; +} + +.ant-modal-content .ant-tabs-nav .ant-tabs-tab{ + color:#fff; +} + +.resource-detail-item{ + border-bottom: 1px solid #777; + padding-bottom: 20px; +} + +.account-center-tags .ant-divider-horizontal{ + display:none +} + + +/* MODAL Steps */ + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item-title{ + font-size:14px !important; + border-top:0px !important; + justify-content: center; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item.ant-steps-item-wait .ant-steps-item-icon{ + background-color:#ddd !important; + border-color:#999 !important; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item.ant-steps-item-wait .ant-steps-item-icon span{ + color:#999; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item.ant-steps-item-finish .ant-steps-item-icon{ + background-color:#555 !important; + border-color:#555 !important; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item.ant-steps-item-finish .ant-steps-item-icon span{ + color:white !important; + right:-3px; + top:9px; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item-container .ant-steps-item-icon{ + background-color:var(--main-primary-claro) !important; + margin-top:0px; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item.ant-steps-item-active .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{ + background-color:var(--main-primary-escuro) !important; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item.ant-steps-item-wait .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{ + background-color:var(--main-primary-claro) !important; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item.ant-steps-item-finish .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{ + background-color:var(--main-secondary-escuro) !important; +} + +.ant-modal-body .ant-steps.ant-steps-dot .ant-steps-item.ant-steps-item-finish .ant-steps-item-container .ant-steps-item-icon .ant-steps-icon-dot{ + color:white !important; + left:0px !important; + top:0px; +} + +.ant-modal-body .ant-table-body .ant-tag span{ + position:relative !important; +} + +.ant-modal-body .ant-table-body .traffic-select-item > span button.ant-btn-circle{ + padding-top:15px !important; +} + +.ant-modal-body .ant-table-body button.ant-btn-circle.ant-btn-dangerous{ + background-color:var( --main-vermelho); + border-color:var( --main-vermelho); +} + +.ant-modal-body .ant-form-item-control-input .anticon.anticon-close-circle{ + top:0px !important; + bottom:0px !important; + left:-10px !important; +} + +.ant-modal-body .ant-form-item-control-input .ant-select-arrow{ + right:15px +} + +.ant-form-item-has-feedback .ant-form-item-control-input :not(.ant-input-group-addon) > .ant-select .ant-select-arrow{ + right:40px +} + +.ant-modal-body .ant-form-item-control-input .ant-select-clear{ + background-color:transparent !important; + top:13px; + right:-20px !important; +} + +.ant-modal-body .ant-table-body button.ant-btn-circle.ant-btn-dangerous .anticon{ + /* left:2px; + top:7px; */ +} + +.ant-modal-body .ant-table-body button.ant-btn-circle.ant-btn-dangerous:hover, +.ant-modal-body .ant-table-body .traffic-select-item > span button.ant-btn-circle:hover{ + background-color:var(--main-secondary-escuro); + border-color:var(--main-secondary-escuro); +} + +.ant-modal-body .ant-table-body button.ant-btn-circle.ant-btn-dangerous .anticon, +.ant-modal-body .ant-table-body .traffic-select-item > span button.ant-btn-circle:hover svg{ + color:#fff; +} + +.ant-modal-body .ant-table-body .traffic-select-item > span button.ant-btn-circle > span{ + top:4px; + left:-1px; +} + +.ant-modal-body .traffic-select-item .ant-select-arrow .anticon.anticon-down{ + margin-left:0px !important; +} + +.ant-modal-body .ant-form-item-control-input .anticon.anticon-close-circle, +.ant-modal-body .ant-form-item-control-input .anticon.anticon-check-circle{ + left:5px; + top:4px !important; +} + +.ant-modal-body .card-waiting-launch .anticon-check-circle{ + position: absolute; + font-size:40px !important; + left:60px; + top:40px +} + +.ant-modal-body .anticon-rocket{ + left:1px; + bottom:7px; +} + +.ant-modal-body .anticon-rocket:hover{ + color:inherit !important; +} + +.card-launch-content .ant-steps-item-finish .anticon.anticon-check.ant-steps-finish-icon{ + right:-6px; + color:#fff; + top:19px +} + +.card-launch-content .ant-steps-item .ant-steps-item-icon .ant-steps-icon .anticon{ + left:-10px +} + +.card-launch-content .ant-steps-item .ant-steps-item-icon .ant-steps-icon .anticon.ant-steps-finish-icon{ + position: absolute; +} + +.card-launch-content .ant-steps-item .ant-steps-item-icon{ + min-width:23px !important; +} + +.card-launch-content .anticon.anticon-close-circle{ + left:-15px !important; +} + +.card-launch-content .anticon.anticon-loading{ + top:1px +} + +/* NOVAS ENTRADAS */ +.ant-spin-nested-loading .ant-table-bordered .ant-table-body .ant-table-row-cell-break-word .ant-btn-circle{ + +} + +.ant-modal-body .ant-table-bordered .ant-table-body .ant-table-row-cell-break-word .ant-btn-circle.ant-btn-dangerous{ + background-color:var(--main-vermelho) !important; + + &:hover{ + background-color:var(--main-secondary-escuro) !important; + } + +} + +.ant-alert .ant-alert-message{ + margin-left:40px; +} + +.ant-alert:not(.ant-alert-no-icon) .ant-alert-message{ + margin-left:40px; +} + +.ant-btn-dangerous.ant-btn-primary.ant-btn-dangerous{ + background:var(--main-vermelho); + border-color:var(--main-vermelho); +} + + +/* DARK ONLY */ +.ant-modal-body .ant-table-bordered .ant-table-body .ant-table-row-cell-break-word .ant-btn-circle.ant-btn-dangerous span{ + background-color: transparent; + margin-right:5px !important; +} + +.ant-steps-item-content .ant-card.ant-card-bordered .ant-card-body{ + border:1px solid var(--main-primary-escuro) !important; + border-radius: 0px; +} + +.ant-modal-body .ant-form.ant-form-horizontal.form-content{ + background-color:transparent !important; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item-title{ + color:#fff; +} +.ant-modal-body .zone-support{ + background:transparent !important; +} + +.ant-card-bordered:not(:last-child){ + border-bottom:1px solid rgba(255,255,255,0.5) !important; +} + +.ant-steps-item-description .ant-card-bordered:not(:last-child){ + border-bottom:0px !important; +} + +.ant-card-bordered.ant-form-text{ + background-color:var(--main-grey-escuro); + border-bottom:0px !important; + color:#fff; +} + +.ant-modal-body .ant-steps.ant-steps-horizontal .ant-steps-item.ant-steps-item-finish .ant-steps-item-icon span{ + color:white !important; + right:3px; + top:0px; +} + +.ant-steps-item-icon{ + background-color:var(--main-primary-escuro) !important; + border-color:var(--main-primary-escuro) !important; + margin-top:10px +} + +.card-launch-content .ant-steps-item .ant-steps-item-icon{ + background-color:transparent !important; +} + +form.ant-form .ant-form-item-explain.ant-form-item-explain-error{ + color:pink !important; +} diff --git a/ui/public/index.html b/ui/public/index.html index b681ad6a902..7397f27d545 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -54,12 +54,4 @@
- diff --git a/ui/src/api/index.js b/ui/src/api/index.js index 85c46c483b2..45938df6cab 100644 --- a/ui/src/api/index.js +++ b/ui/src/api/index.js @@ -88,10 +88,13 @@ export function login (arg) { }) } -export function logout () { - message.destroy() - notification.destroy() - return postAPI('logout') +export async function logout () { + const result = await postAPI('logout').finally(() => { + sourceToken.cancel() + message.destroy() + notification.destroy() + }) + return result } export function oauthlogin (arg) { diff --git a/ui/src/components/header/UserMenu.vue b/ui/src/components/header/UserMenu.vue index 34d38d8327b..a67fe06096a 100644 --- a/ui/src/components/header/UserMenu.vue +++ b/ui/src/components/header/UserMenu.vue @@ -80,6 +80,8 @@ import { mapActions, mapGetters } from 'vuex' import ResourceIcon from '@/components/view/ResourceIcon' import eventBus from '@/config/eventBus' import { SERVER_MANAGER } from '@/store/mutation-types' +import { sourceToken } from '@/utils/request' +import { applyCustomGuiTheme } from '@/utils/guiTheme' export default { name: 'UserMenu', @@ -178,7 +180,9 @@ export default { } }, handleLogout () { - return this.Logout({}).then(() => { + this.Logout({}).finally(async () => { + sourceToken.init() + await applyCustomGuiTheme(null, null) this.$router.push('/user/login') }).catch(err => { this.$message.error({ diff --git a/ui/src/main.js b/ui/src/main.js index d7f32ff503d..c25ab1066d4 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -43,6 +43,9 @@ import { } from './utils/plugins' import { VueAxios } from './utils/request' import directives from './utils/directives' +import Cookies from 'js-cookie' +import { api } from '@/api' +import { applyCustomGuiTheme } from './utils/guiTheme' vueApp.use(VueAxios, router) vueApp.use(pollJobPlugin) @@ -89,7 +92,7 @@ fetch('config.json?ts=' + Date.now()) } return response.json() }) - .then(config => { + .then(async config => { vueProps.$config = config let baseUrl = config.apiBase if (config.multipleServer) { @@ -98,6 +101,19 @@ fetch('config.json?ts=' + Date.now()) vueProps.axios.defaults.baseURL = baseUrl + const userid = Cookies.get('userid') + let accountid = null + let domainid = null + + if (userid !== undefined && Cookies.get('sessionkey')) { + await api('listUsers', { userid: userid }).then(response => { + accountid = response.listusersresponse.user[0].accountid + domainid = response.listusersresponse.user[0].domainid + }) + } + + await applyCustomGuiTheme(accountid, domainid) + loadLanguageAsync().then(() => { vueApp.use(store) .use(router) diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js index ffa0c2b136e..0fbdc5788c0 100644 --- a/ui/src/store/modules/user.js +++ b/ui/src/store/modules/user.js @@ -47,6 +47,10 @@ import { LATEST_CS_VERSION } from '@/store/mutation-types' +import { + applyCustomGuiTheme +} from '@/utils/guiTheme' + const user = { state: { token: '', @@ -243,7 +247,6 @@ const user = { const latestVersion = vueProps.$localStorage.get(LATEST_CS_VERSION, { version: '', fetchedTs: 0 }) commit('SET_LATEST_VERSION', latestVersion) notification.destroy() - resolve() }).catch(error => { reject(error) @@ -406,6 +409,7 @@ const user = { getAPI('listUsers', { id: Cookies.get('userid'), showicon: true }).then(response => { const result = response.listusersresponse.user[0] + applyCustomGuiTheme(result.accountid, result.domainid) commit('SET_INFO', result) commit('SET_NAME', result.firstname + ' ' + result.lastname) commit('SET_AVATAR', result.icon?.base64image || '') diff --git a/ui/src/utils/guiTheme.js b/ui/src/utils/guiTheme.js new file mode 100644 index 00000000000..7cc3dc0cc03 --- /dev/null +++ b/ui/src/utils/guiTheme.js @@ -0,0 +1,100 @@ +// 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. + +import { vueProps } from '@/vue-app' +import { api } from '@/api' + +export async function applyCustomGuiTheme (accountid, domainid) { + await fetch('config.json').then(response => response.json()).then(config => { + vueProps.$config = config + }) + + let guiTheme + + if (accountid != null) { + guiTheme = await fetchGuiTheme({ accountid: accountid }) + } + + if (guiTheme === undefined && domainid != null) { + guiTheme = await fetchGuiTheme({ domainid: domainid }) + } + + if (guiTheme === undefined) { + guiTheme = await fetchGuiTheme({ commonname: window.location.hostname }) + } + + if (guiTheme === undefined) { + guiTheme = await fetchGuiTheme({ listonlydefaulttheme: true }) + } + + await applyDynamicCustomization(guiTheme) +} + +async function fetchGuiTheme (params) { + return await api('listGuiThemes', params).then(response => { + if (response.listguithemesresponse.guiThemes) { + return response.listguithemesresponse.guiThemes[0] + } + }) +} + +async function applyDynamicCustomization (response) { + let jsonConfig + + if (response?.jsonconfiguration) { + jsonConfig = JSON.parse(response?.jsonconfiguration) + } + + // 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 + vueProps.$config.loginFooter = jsonConfig?.loginFooter ?? vueProps.$config.loginFooter + vueProps.$config.logo = jsonConfig?.logo ?? vueProps.$config.logo + vueProps.$config.minilogo = jsonConfig?.minilogo ?? vueProps.$config.minilogo + vueProps.$config.banner = jsonConfig?.banner ?? vueProps.$config.banner + + 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] + } + + if (jsonConfig?.plugins) { + jsonConfig.plugins.forEach(plugin => { + vueProps.$config.plugins.push(plugin) + }) + } + + vueProps.$config.favicon = jsonConfig?.favicon ?? vueProps.$config.favicon + vueProps.$config.css = response?.css ?? null + + await applyStaticCustomization(vueProps.$config.favicon, vueProps.$config.css) +} + +async function applyStaticCustomization (favicon, css) { + document.getElementById('favicon').href = favicon + + let style = document.getElementById('guiThemeCSS') + if (style != null) { + style.innerHTML = css + } else { + style = document.createElement('style') + style.setAttribute('id', 'guiThemeCSS') + style.innerHTML = css + document.body.appendChild(style) + } +}