You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by nv...@apache.org on 2021/09/15 03:51:03 UTC

[cloudstack] branch main updated: UI: Support to upload resource icons (#5157)

This is an automated email from the ASF dual-hosted git repository.

nvazquez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 3f827ef  UI: Support to upload resource icons (#5157)
3f827ef is described below

commit 3f827ef22bfa76439fcedc4c191204ac69f8d1f4
Author: Pearl Dsilva <pe...@gmail.com>
AuthorDate: Wed Sep 15 09:20:42 2021 +0530

    UI: Support to upload resource icons (#5157)
    
    * Resource Icon support - backend
    
    * Add API support for resourceicon
    
    * update reponse params + ui support
    
    * Add exclusive list api for icons and UI changes
    
    * refactor upload view
    
    * UI changes to support resource icon wherever necessary
    
    * convert api to POST + refactor icon view
    
    * Add response name to list API + cosmetic changes in UI
    
    * Added support for the following:
    resource icon support for vpcs, networks, domains, and projects
    add icons to list view if reosurces support icons to be added
    support for showing project icons in the project switching drop-down menu
    
    * List resourceicon cmds to be allowed for user role too
    Users to inherit account icon if present (in listUsers response)
    Move common code to plugin.js
    Add icon to project list view - while switching between projects - Dashboard page
    Show icons against zones - Capacity Dashboard view
    Show user / account icon at the login button if present
    
    * cosmetic changes
    
    * optimize ui code
    
    * fix reload issue for domain view
    
    * add access check for delete operation
    
    * ui-related changes to show iso icons
    
    * iso image in uservm response
    
    * add icons to custom form's list resources
    
    * some more custom forms aligned to show icon for resources
    
    * conmitic changes + add listing of icons to listdomainchildren cmd
    
    * Add backend/server-side validation for base64 string passed for image
    
    * change preview border
    
    * preselect zone if there's only one
    
    * add default icon
    
    * show icon for network list in deploy vm view
    
    * add custom icons if any to the import-export VM view
    
    * preselect zone persistence on clearing cache
    
    * prevent root vol from inheriting template/iso icon
    
    * show tempalte icon in the info card details
    
    * fix icon not being show on hard-refresh / initial traversal
    
    * fx success message
---
 api/src/main/java/com/cloud/event/EventTypes.java  |   4 +
 .../main/java/com/cloud/server/ResourceIcon.java   |  19 +-
 .../java/com/cloud/server/ResourceIconManager.java |  15 +-
 .../java/com/cloud/server/ResourceManagerUtil.java |  19 +-
 .../main/java/com/cloud/server/ResourceTag.java    |  31 +-
 .../com/cloud/server/TaggedResourceService.java    |  12 -
 .../org/apache/cloudstack/api/ApiConstants.java    |   3 +
 .../java/org/apache/cloudstack/api/BaseCmd.java    |  12 +-
 .../apache/cloudstack/api/ResponseGenerator.java   |   6 +-
 .../admin/domain/ListDomainChildrenCmd.java        |  25 ++
 .../api/command/admin/domain/ListDomainsCmd.java   |  25 ++
 .../admin/resource/icon/DeleteResourceIconCmd.java | 104 +++++++
 .../resource/icon/ListResourceIconCmd.java}        |  72 +++--
 .../admin/resource/icon/UploadResourceIconCmd.java | 144 ++++++++++
 .../api/command/admin/user/ListUsersCmd.java       |  30 ++
 .../api/command/admin/zone/CreateZoneCmd.java      |   2 +-
 .../api/command/admin/zone/UpdateZoneCmd.java      |   2 +-
 .../api/command/user/account/ListAccountsCmd.java  |  25 ++
 .../api/command/user/iso/ListIsosCmd.java          |  26 ++
 .../api/command/user/network/ListNetworksCmd.java  |  29 ++
 .../api/command/user/project/ListProjectsCmd.java  |  25 ++
 .../user/resource/ListDetailOptionsCmd.java        |   4 +-
 .../api/command/user/tag/CreateTagsCmd.java        |   8 +-
 .../api/command/user/tag/DeleteTagsCmd.java        |   2 +-
 .../command/user/template/ListTemplatesCmd.java    |  24 ++
 .../cloudstack/api/command/user/vm/ListVMsCmd.java |  35 +++
 .../command/user/volume/AddResourceDetailCmd.java  |   2 +-
 .../user/volume/ListResourceDetailsCmd.java        |   2 +-
 .../user/volume/RemoveResourceDetailCmd.java       |   2 +-
 .../api/command/user/vpc/ListVPCsCmd.java          |  25 ++
 .../api/command/user/vpn/AddVpnUserCmd.java        |   2 +-
 .../api/command/user/zone/ListZonesCmd.java        |   8 +-
 .../cloudstack/api/response/AccountResponse.java   |  10 +-
 .../cloudstack/api/response/DomainResponse.java    |  11 +-
 .../cloudstack/api/response/NetworkResponse.java   |  19 +-
 .../cloudstack/api/response/ProjectResponse.java   |  15 +-
 .../api/response/ResourceIconResponse.java         |  61 ++++
 .../api/response/SetResourceIconResponse.java      |  16 +-
 .../cloudstack/api/response/TemplateResponse.java  |  11 +-
 .../cloudstack/api/response/UserResponse.java      |  11 +-
 .../cloudstack/api/response/UserVmResponse.java    |  15 +-
 .../cloudstack/api/response/VpcResponse.java       |  15 +-
 .../cloudstack/api/response/ZoneResponse.java      |  15 +-
 .../org/apache/cloudstack/query/QueryService.java  |   4 +
 .../com/cloud/resource/icon/ResourceIconVO.java    | 167 +++++++++++
 .../cloud/resource/icon/dao/ResourceIconDao.java   |  20 +-
 .../resource/icon/dao/ResourceIconDaoImpl.java     |  79 ++++++
 .../spring-engine-schema-core-daos-context.xml     |   1 +
 .../resources/META-INF/db/schema-41520to41600.sql  |  14 +
 .../apache/cloudstack/api/ListVMsMetricsCmd.java   |   1 +
 server/src/main/java/com/cloud/api/ApiDBUtils.java |  27 +-
 .../main/java/com/cloud/api/ApiResponseHelper.java |  11 +-
 .../java/com/cloud/api/query/QueryManagerImpl.java |  27 +-
 .../com/cloud/api/query/ViewResponseHelper.java    |   4 +-
 .../com/cloud/api/query/dao/DataCenterJoinDao.java |   2 +-
 .../cloud/api/query/dao/DataCenterJoinDaoImpl.java |  12 +-
 .../metadata/ResourceMetaDataManagerImpl.java      |   7 +-
 .../resourceicon/ResourceIconManagerImpl.java      | 230 +++++++++++++++
 .../com/cloud/server/ManagementServerImpl.java     |   6 +
 .../com/cloud/tags/ResourceManagerUtilImpl.java    | 186 ++++++++++++
 .../com/cloud/tags/TaggedResourceManagerImpl.java  | 152 +---------
 .../core/spring-server-core-managers-context.xml   |   6 +
 .../metadata/ResourceMetaDataManagerTest.java      |   7 +-
 tools/apidoc/gen_toc.py                            |   1 +
 ui/public/locales/en.json                          |   7 +
 ui/src/components/header/ProjectMenu.vue           |   8 +-
 ui/src/components/header/UserMenu.vue              |  46 ++-
 ui/src/components/view/InfoCard.vue                | 194 ++++++++++++-
 ui/src/components/view/ListView.vue                |  27 +-
 ui/src/components/view/ResourceIcon.vue            |  59 ++++
 ui/src/components/view/SearchView.vue              |  24 +-
 ui/src/components/view/TreeView.vue                |  10 +-
 ui/src/components/view/UploadResourceIcon.vue      | 314 +++++++++++++++++++++
 ui/src/core/lazy_lib/components_use.js             |   2 +
 ui/src/main.js                                     |   4 +-
 ui/src/utils/plugins.js                            |  33 +++
 ui/src/views/AutogenView.vue                       |  60 +++-
 ui/src/views/compute/AssignInstance.vue            |  16 ++
 ui/src/views/compute/CreateKubernetesCluster.vue   |   7 +-
 ui/src/views/compute/DeployVM.vue                  |  65 ++++-
 ui/src/views/compute/InstanceTab.vue               |   5 +
 ui/src/views/compute/wizard/NetworkSelection.vue   |  14 +-
 .../views/compute/wizard/TemplateIsoRadioGroup.vue |  13 +-
 ui/src/views/dashboard/CapacityDashboard.vue       |   8 +-
 ui/src/views/iam/AddAccount.vue                    |   7 +-
 ui/src/views/iam/AddUser.vue                       |  11 +-
 ui/src/views/iam/DomainView.vue                    |  11 +-
 .../views/image/AddKubernetesSupportedVersion.vue  |   5 +
 ui/src/views/image/IsoZones.vue                    |  29 +-
 ui/src/views/image/RegisterOrUploadIso.vue         |   9 +
 ui/src/views/image/RegisterOrUploadTemplate.vue    |  10 +-
 ui/src/views/image/TemplateZones.vue               |  29 +-
 .../views/image/UpdateTemplateIsoPermissions.vue   |  12 +-
 ui/src/views/infra/AddPrimaryStorage.vue           |   8 +-
 ui/src/views/infra/AddSecondaryStorage.vue         |  13 +-
 ui/src/views/infra/ClusterAdd.vue                  |   8 +-
 ui/src/views/infra/HostAdd.vue                     |   8 +-
 ui/src/views/infra/PodAdd.vue                      |   8 +-
 ui/src/views/infra/network/DedicatedVLANTab.vue    |  17 +-
 ui/src/views/infra/network/IpRangesTabGuest.vue    |   7 +-
 ui/src/views/network/CreateIsolatedNetworkForm.vue |   7 +-
 ui/src/views/network/CreateL2NetworkForm.vue       |  10 +-
 ui/src/views/network/CreateSharedNetworkForm.vue   |  13 +-
 ui/src/views/network/CreateVpc.vue                 |   6 +-
 ui/src/views/network/NicsTable.vue                 |  56 +++-
 ui/src/views/offering/AddComputeOffering.vue       |   8 +
 ui/src/views/offering/AddDiskOffering.vue          |   8 +
 ui/src/views/offering/AddNetworkOffering.vue       |   8 +
 ui/src/views/offering/AddVpcOffering.vue           |   8 +
 ui/src/views/offering/ImportBackupOffering.vue     |   8 +-
 ui/src/views/offering/UpdateOfferingAccess.vue     |  10 +
 ui/src/views/storage/CreateVolume.vue              |   6 +-
 ui/src/views/storage/UploadLocalVolume.vue         |   6 +-
 ui/src/views/tools/ImportUnmanagedInstance.vue     |  53 +++-
 ui/src/views/tools/ManageInstances.vue             |  21 +-
 115 files changed, 2849 insertions(+), 394 deletions(-)

diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java
index 5947334..72e623f 100644
--- a/api/src/main/java/com/cloud/event/EventTypes.java
+++ b/api/src/main/java/com/cloud/event/EventTypes.java
@@ -493,6 +493,10 @@ public class EventTypes {
     public static final String EVENT_TAGS_CREATE = "CREATE_TAGS";
     public static final String EVENT_TAGS_DELETE = "DELETE_TAGS";
 
+    // resource icon related events
+    public static final String EVENT_RESOURCE_ICON_UPLOAD = "UPLOAD.RESOURCE.ICON";
+    public static final String EVENT_RESOURCE_ICON_DELETE = "DELETE.RESOURCE.ICON";
+
     // meta data related events
     public static final String EVENT_RESOURCE_DETAILS_CREATE = "CREATE_RESOURCE_DETAILS";
     public static final String EVENT_RESOURCE_DETAILS_DELETE = "DELETE_RESOURCE_DETAILS";
diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java b/api/src/main/java/com/cloud/server/ResourceIcon.java
similarity index 61%
copy from server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
copy to api/src/main/java/com/cloud/server/ResourceIcon.java
index 1c3ff1b..a5ac3ee 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
+++ b/api/src/main/java/com/cloud/server/ResourceIcon.java
@@ -14,18 +14,19 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.api.query.dao;
+package com.cloud.server;
 
-import org.apache.cloudstack.api.ResponseObject.ResponseView;
-import org.apache.cloudstack.api.response.ZoneResponse;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
 
-import com.cloud.api.query.vo.DataCenterJoinVO;
-import com.cloud.dc.DataCenter;
-import com.cloud.utils.db.GenericDao;
+public interface ResourceIcon extends Identity, InternalIdentity {
+    long getResourceId();
 
-public interface DataCenterJoinDao extends GenericDao<DataCenterJoinVO, Long> {
+    void setResourceId(long resourceId);
 
-    ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dof, Boolean showCapacities);
+    ResourceTag.ResourceObjectType getResourceType();
 
-    DataCenterJoinVO newDataCenterView(DataCenter dof);
+    String getResourceUuid();
+
+    String getIcon();
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java b/api/src/main/java/com/cloud/server/ResourceIconManager.java
similarity index 61%
copy from server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
copy to api/src/main/java/com/cloud/server/ResourceIconManager.java
index 1c3ff1b..e5111d9 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
+++ b/api/src/main/java/com/cloud/server/ResourceIconManager.java
@@ -14,18 +14,15 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.api.query.dao;
+package com.cloud.server;
 
-import org.apache.cloudstack.api.ResponseObject.ResponseView;
-import org.apache.cloudstack.api.response.ZoneResponse;
+import java.util.List;
 
-import com.cloud.api.query.vo.DataCenterJoinVO;
-import com.cloud.dc.DataCenter;
-import com.cloud.utils.db.GenericDao;
+public interface ResourceIconManager {
 
-public interface DataCenterJoinDao extends GenericDao<DataCenterJoinVO, Long> {
+    boolean uploadResourceIcon(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType, String base64Image);
 
-    ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dof, Boolean showCapacities);
+    boolean deleteResourceIcon(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType);
 
-    DataCenterJoinVO newDataCenterView(DataCenter dof);
+    ResourceIcon getByResourceTypeAndUuid(ResourceTag.ResourceObjectType type, String resourceId);
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java b/api/src/main/java/com/cloud/server/ResourceManagerUtil.java
similarity index 61%
copy from server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
copy to api/src/main/java/com/cloud/server/ResourceManagerUtil.java
index 1c3ff1b..9a3b51a 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
+++ b/api/src/main/java/com/cloud/server/ResourceManagerUtil.java
@@ -14,18 +14,11 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.api.query.dao;
+package com.cloud.server;
 
-import org.apache.cloudstack.api.ResponseObject.ResponseView;
-import org.apache.cloudstack.api.response.ZoneResponse;
-
-import com.cloud.api.query.vo.DataCenterJoinVO;
-import com.cloud.dc.DataCenter;
-import com.cloud.utils.db.GenericDao;
-
-public interface DataCenterJoinDao extends GenericDao<DataCenterJoinVO, Long> {
-
-    ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dof, Boolean showCapacities);
-
-    DataCenterJoinVO newDataCenterView(DataCenter dof);
+public interface ResourceManagerUtil {
+    long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType);
+    String getUuid(String resourceId, ResourceTag.ResourceObjectType resourceType);
+    ResourceTag.ResourceObjectType getResourceType(String resourceTypeStr);
+    void checkResourceAccessible(Long accountId, Long domainId, String exceptionMessage);
 }
diff --git a/api/src/main/java/com/cloud/server/ResourceTag.java b/api/src/main/java/com/cloud/server/ResourceTag.java
index fb07762..ba5ab8a 100644
--- a/api/src/main/java/com/cloud/server/ResourceTag.java
+++ b/api/src/main/java/com/cloud/server/ResourceTag.java
@@ -24,13 +24,13 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit
 
     // FIXME - extract enum to another interface as its used both by resourceTags and resourceMetaData code
     public enum ResourceObjectType {
-        UserVm(true, true),
-        Template(true, true),
-        ISO(true, false),
+        UserVm(true, true, true),
+        Template(true, true, true),
+        ISO(true, false, true),
         Volume(true, true),
         Snapshot(true, false),
         Backup(true, false),
-        Network(true, true),
+        Network(true, true, true),
         Nic(false, true),
         LoadBalancer(true, true),
         PortForwardingRule(true, true),
@@ -38,14 +38,14 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit
         SecurityGroup(true, false),
         SecurityGroupRule(true, false),
         PublicIpAddress(true, true),
-        Project(true, false),
-        Account(true, false),
-        Vpc(true, true),
+        Project(true, false, true),
+        Account(true, false, true),
+        Vpc(true, true, true),
         NetworkACL(true, true),
         StaticRoute(true, false),
         VMSnapshot(true, false),
         RemoteAccessVpn(true, true),
-        Zone(false, true),
+        Zone(false, true, true),
         ServiceOffering(false, true),
         Storage(false, true),
         PrivateGateway(false, true),
@@ -53,7 +53,7 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit
         VpnGateway(false, true),
         CustomerGateway(false, true),
         VpnConnection(false, true),
-        User(true, true),
+        User(true, true, true),
         DiskOffering(false, true),
         AutoScaleVmProfile(false, true),
         AutoScaleVmGroup(false, true),
@@ -62,7 +62,8 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit
         SnapshotPolicy(true, true),
         GuestOs(false, true),
         NetworkOffering(false, true),
-        VpcOffering(true, false);
+        VpcOffering(true, false),
+        Domain(false, false, true);
 
 
         ResourceObjectType(boolean resourceTagsSupport, boolean resourceMetadataSupport) {
@@ -70,8 +71,14 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit
             metadataSupport = resourceMetadataSupport;
         }
 
+        ResourceObjectType(boolean resourceTagsSupport, boolean resourceMetadataSupport, boolean resourceIconSupport) {
+            this(resourceTagsSupport, resourceMetadataSupport);
+            this.resourceIconSupport = resourceIconSupport;
+        }
+
         private final boolean resourceTagsSupport;
         private final boolean metadataSupport;
+        private boolean resourceIconSupport;
 
         public boolean resourceTagsSupport() {
             return resourceTagsSupport;
@@ -80,6 +87,10 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit
         public boolean resourceMetadataSupport() {
             return metadataSupport;
         }
+
+        public boolean resourceIconSupport() {
+            return resourceIconSupport;
+        }
     }
 
     /**
diff --git a/api/src/main/java/com/cloud/server/TaggedResourceService.java b/api/src/main/java/com/cloud/server/TaggedResourceService.java
index 84f7eb0..27fa219 100644
--- a/api/src/main/java/com/cloud/server/TaggedResourceService.java
+++ b/api/src/main/java/com/cloud/server/TaggedResourceService.java
@@ -43,18 +43,6 @@ public interface TaggedResourceService {
 
     List<? extends ResourceTag> listByResourceTypeAndId(ResourceObjectType type, long resourceId);
 
-    //FIXME - the methods below should be extracted to its separate manager/service responsible just for retrieving object details
-    ResourceObjectType getResourceType(String resourceTypeStr);
-
-    /**
-     * @param resourceId
-     * @param resourceType
-     * @return
-     */
-    String getUuid(String resourceId, ResourceObjectType resourceType);
-
-    public long getResourceId(String resourceId, ResourceObjectType resourceType);
-
     /**
      * Retrieves tags from resource.
      * @param type
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 f930800..35c1f87 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -37,6 +37,7 @@ public class ApiConstants {
     public static final String BACKUP_ID = "backupid";
     public static final String BACKUP_OFFERING_NAME = "backupofferingname";
     public static final String BACKUP_OFFERING_ID = "backupofferingid";
+    public static final String BASE64_IMAGE = "base64image";
     public static final String BITS = "bits";
     public static final String BOOTABLE = "bootable";
     public static final String BIND_DN = "binddn";
@@ -333,6 +334,7 @@ public class ApiConstants {
     public static final String SESSIONKEY = "sessionkey";
     public static final String SHOW_CAPACITIES = "showcapacities";
     public static final String SHOW_REMOVED = "showremoved";
+    public static final String SHOW_RESOURCE_ICON = "showicon";
     public static final String SHOW_UNIQUE = "showunique";
     public static final String SIGNATURE = "signature";
     public static final String SIGNATURE_VERSION = "signatureversion";
@@ -747,6 +749,7 @@ public class ApiConstants {
     public static final String ACCESS_TYPE = "accesstype";
 
     public static final String RESOURCE_DETAILS = "resourcedetails";
+    public static final String RESOURCE_ICON = "icon";
     public static final String EXPUNGE = "expunge";
     public static final String FOR_DISPLAY = "fordisplay";
     public static final String PASSIVE = "passive";
diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
index c897aad..6575f1e 100644
--- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java
@@ -29,6 +29,11 @@ import java.util.regex.Pattern;
 
 import javax.inject.Inject;
 
+import com.cloud.server.ManagementService;
+import com.cloud.server.ResourceIconManager;
+import com.cloud.server.ResourceManagerUtil;
+import com.cloud.server.ResourceMetaDataService;
+import com.cloud.server.TaggedResourceService;
 import org.apache.cloudstack.acl.ProjectRoleService;
 import org.apache.cloudstack.acl.RoleService;
 import org.apache.cloudstack.acl.RoleType;
@@ -67,9 +72,6 @@ import com.cloud.network.vpn.RemoteAccessVpnService;
 import com.cloud.network.vpn.Site2SiteVpnService;
 import com.cloud.projects.ProjectService;
 import com.cloud.resource.ResourceService;
-import com.cloud.server.ManagementService;
-import com.cloud.server.ResourceMetaDataService;
-import com.cloud.server.TaggedResourceService;
 import com.cloud.storage.DataStoreProviderApiService;
 import com.cloud.storage.StorageService;
 import com.cloud.storage.VolumeApiService;
@@ -164,6 +166,8 @@ public abstract class BaseCmd {
     @Inject
     public TaggedResourceService _taggedResourceService;
     @Inject
+    public ResourceManagerUtil resourceManagerUtil;
+    @Inject
     public ResourceMetaDataService _resourceMetaDataService;
     @Inject
     public VpcService _vpcService;
@@ -201,6 +205,8 @@ public abstract class BaseCmd {
     public UUIDManager _uuidMgr;
     @Inject
     public AnnotationService annotationService;
+    @Inject
+    public ResourceIconManager resourceIconManager;
 
     public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException,
         ResourceAllocationException, NetworkRuleConflictException;
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 3f0d978..03f0a3c 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java
@@ -22,6 +22,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import com.cloud.server.ResourceIcon;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
 import com.cloud.resource.RollingMaintenanceManager;
 import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
@@ -270,7 +272,7 @@ public interface ResponseGenerator {
 
     PodResponse createPodResponse(Pod pod, Boolean showCapacities);
 
-    ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities);
+    ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities, Boolean showResourceIcon);
 
     VolumeResponse createVolumeResponse(ResponseView view, Volume volume);
 
@@ -487,4 +489,6 @@ public interface ResponseGenerator {
 
     RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details, List<RollingMaintenanceManager.HostUpdated> hostsUpdated, List<RollingMaintenanceManager.HostSkipped> hostsSkipped);
 
+    ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon);
+
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainChildrenCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainChildrenCmd.java
index cf35295..fb6c5be 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainChildrenCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainChildrenCmd.java
@@ -19,6 +19,9 @@ package org.apache.cloudstack.api.command.admin.domain;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -58,6 +61,10 @@ public class ListDomainChildrenCmd extends BaseListCmd {
                description = "If set to false, list only resources belonging to the command's caller; if set to true - list resources that the caller is authorized to see. Default value is false")
     private Boolean listAll;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
+            description = "flag to display the resource icon for domains")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -78,6 +85,10 @@ public class ListDomainChildrenCmd extends BaseListCmd {
         return recursive == null ? false : recursive;
     }
 
+    public Boolean getShowIcon() {
+        return showIcon != null ? showIcon : false;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -100,6 +111,20 @@ public class ListDomainChildrenCmd extends BaseListCmd {
 
         response.setResponses(domainResponses, result.second());
         response.setResponseName(getCommandName());
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateDomainResponse(response.getResponses());
+        }
         this.setResponseObject(response);
     }
+
+    private void updateDomainResponse(List<DomainResponse> response) {
+        for (DomainResponse domainResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Domain, domainResponse.getId());
+            if (resourceIcon == null) {
+                continue;
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            domainResponse.setResourceIconResponse(iconResponse);
+        }
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java
index 5e4cda3..8b6661f 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java
@@ -20,6 +20,9 @@ import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -66,6 +69,10 @@ public class ListDomainsCmd extends BaseListCmd implements UserCmd {
                description = "comma separated list of domain details requested, value can be a list of [ all, resource, min]")
     private List<String> viewDetails;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
+            description = "flag to display the resource icon for domains")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -105,6 +112,10 @@ public class ListDomainsCmd extends BaseListCmd implements UserCmd {
         return dv;
     }
 
+    public Boolean getShowIcon() {
+        return showIcon != null ? showIcon : false;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -119,5 +130,19 @@ public class ListDomainsCmd extends BaseListCmd implements UserCmd {
         ListResponse<DomainResponse> response = _queryService.searchForDomains(this);
         response.setResponseName(getCommandName());
         this.setResponseObject(response);
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateDomainResponse(response.getResponses());
+        }
+    }
+
+    private void updateDomainResponse(List<DomainResponse> response) {
+        for (DomainResponse domainResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Domain, domainResponse.getId());
+            if (resourceIcon == null) {
+                continue;
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            domainResponse.setResourceIconResponse(iconResponse);
+        }
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/DeleteResourceIconCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/DeleteResourceIconCmd.java
new file mode 100644
index 0000000..de45839
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/DeleteResourceIconCmd.java
@@ -0,0 +1,104 @@
+// 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.admin.resource.icon;
+
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import com.cloud.user.Account;
+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.SuccessResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+
+import java.util.List;
+
+@APICommand(name = "deleteResourceIcon", description = "deletes the resource icon from the specified resource(s)",
+        responseObject = SuccessResponse.class, since = "4.16.0.0", entityType = {ResourceIcon.class},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User})
+public class DeleteResourceIconCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(DeleteResourceIconCmd.class.getName());
+
+    private static final String s_name = "deleteresourceiconresponse";
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.RESOURCE_IDS,
+            type = BaseCmd.CommandType.LIST,
+            required = true,
+            collectionType = BaseCmd.CommandType.STRING,
+            description = "list of resources to upload the icon/image for")
+    private List<String> resourceIds;
+
+    @Parameter(name = ApiConstants.RESOURCE_TYPE, type = BaseCmd.CommandType.STRING, required = true, description = "type of the resource")
+    private String resourceType;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public List<String> getResourceIds() {
+        return resourceIds;
+    }
+
+    public ResourceTag.ResourceObjectType getResourceType() {
+        return resourceManagerUtil.getResourceType(resourceType);
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute() {
+        try {
+            boolean result = resourceIconManager.deleteResourceIcon(getResourceIds(), getResourceType());
+            if (result) {
+                SuccessResponse response = new SuccessResponse(getCommandName());
+                setResponseObject(response);
+            } else {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete resource image");
+            }
+
+        } catch (Exception e) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getLocalizedMessage());
+        }
+
+    }
+
+    @Override
+    public String getCommandName() {
+        return s_name;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Account account = CallContext.current().getCallingAccount();// Let's give the caller here for event logging.
+        if (account != null) {
+            return account.getAccountId();
+        }
+
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/ListResourceIconCmd.java
similarity index 52%
copy from api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java
copy to api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/ListResourceIconCmd.java
index 5007e63..bc8eb00 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/ListResourceIconCmd.java
@@ -14,75 +14,73 @@
 // 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.resource;
+package org.apache.cloudstack.api.command.admin.resource.icon;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import com.cloud.user.Account;
 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.DetailOptionsResponse;
-import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
+import org.apache.log4j.Logger;
 
-import com.cloud.server.ResourceTag;
-import com.google.common.base.Strings;
+import java.util.List;
 
-@APICommand(name = ListDetailOptionsCmd.APINAME,
-        description = "Lists all possible details and their options for a resource type such as a VM or a template",
-        responseObject = DetailOptionsResponse.class,
-        since = "4.13",
-        requestHasSensitiveInfo = false,
-        responseHasSensitiveInfo = false,
-        authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
-public class ListDetailOptionsCmd extends BaseCmd {
-    public final static String APINAME = "listDetailOptions";
+@APICommand(name = "listResourceIcon", description = "Lists the resource icon for the specified resource(s)",
+        responseObject = ResourceIconResponse.class, since = "4.16.0.0", entityType = {ResourceIcon.class},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User})
+public class ListResourceIconCmd extends BaseCmd {
+    public static final Logger s_logger = Logger.getLogger(ListResourceIconCmd.class.getName());
 
+    private static final String s_name = "listresourceiconresponse";
     /////////////////////////////////////////////////////
     //////////////// API parameters /////////////////////
     /////////////////////////////////////////////////////
+    @Parameter(name = ApiConstants.RESOURCE_IDS,
+            type = BaseCmd.CommandType.LIST,
+            required = true,
+            collectionType = BaseCmd.CommandType.STRING,
+            description = "list of resources to upload the icon/image for")
+    private List<String> resourceIds;
 
-    @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true,
-            description = "the resource type such as UserVm, Template etc.")
+    @Parameter(name = ApiConstants.RESOURCE_TYPE, type = BaseCmd.CommandType.STRING, required = true, description = "type of the resource")
     private String resourceType;
 
-    @Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING,
-            description = "the UUID of the resource (optional)")
-    private String resourceId;
-
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
 
-    public ResourceTag.ResourceObjectType getResourceType() {
-        return _taggedResourceService.getResourceType(resourceType);
+    public List<String> getResourceIds() {
+        return resourceIds;
     }
 
-    public String getResourceId() {
-        if (!Strings.isNullOrEmpty(resourceId)) {
-            return _taggedResourceService.getUuid(resourceId, getResourceType());
-        }
-        return null;
+    public ResourceTag.ResourceObjectType getResourceType() {
+        return resourceManagerUtil.getResourceType(resourceType);
     }
 
     /////////////////////////////////////////////////////
-    /////////////////// Implementation //////////////////
+    /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
 
     @Override
-    public String getCommandName() {
-        return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+    public void execute() {
+        ListResponse<ResourceIconResponse> response = _queryService.listResourceIcons(this);
+        response.setResponseName(getCommandName());
+        setResponseObject(response);
     }
 
     @Override
-    public long getEntityOwnerId() {
-        return CallContext.current().getCallingAccountId();
+    public String getCommandName() {
+        return s_name;
     }
 
     @Override
-    public void execute() {
-        final DetailOptionsResponse response = _queryService.listDetailOptions(this);
-        response.setResponseName(getCommandName());
-        response.setObjectName("detailoptions");
-        setResponseObject(response);
+    public long getEntityOwnerId() {
+        return Account.ACCOUNT_ID_SYSTEM;
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/UploadResourceIconCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/UploadResourceIconCmd.java
new file mode 100644
index 0000000..3075bf3
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/UploadResourceIconCmd.java
@@ -0,0 +1,144 @@
+// 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.admin.resource.icon;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import com.cloud.user.Account;
+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.SuccessResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
+import java.awt.image.BufferedImage;
+
+import javax.imageio.ImageIO;
+import java.io.ByteArrayInputStream;
+import java.util.Base64;
+import java.util.List;
+
+
+@APICommand(name = "uploadResourceIcon", description = "Uploads an icon for the specified resource(s)",
+        responseObject = SuccessResponse.class, since = "4.16.0.0", entityType = {ResourceIcon.class},
+        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+        authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User})
+public class UploadResourceIconCmd extends BaseCmd {
+    public static final Logger LOGGER = Logger.getLogger(UploadResourceIconCmd.class.getName());
+
+    private static final String s_name = "uploadresourceiconresponse";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name = ApiConstants.RESOURCE_IDS,
+            type = BaseCmd.CommandType.LIST,
+            required = true,
+            collectionType = BaseCmd.CommandType.STRING,
+            description = "list of resources to upload the icon/image for")
+    private List<String> resourceIds;
+
+    @Parameter(name = ApiConstants.RESOURCE_TYPE, type = BaseCmd.CommandType.STRING, required = true, description = "type of the resource")
+    private String resourceType;
+
+    @Parameter(name = ApiConstants.BASE64_IMAGE, type = BaseCmd.CommandType.STRING, required = true,
+            description = "Base64 string representation of the resource icon/image", length = 2097152)
+    private String image;
+
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public List<String> getResourceIds() {
+        return resourceIds;
+    }
+
+    public ResourceTag.ResourceObjectType getResourceType() {
+        return resourceManagerUtil.getResourceType(resourceType);
+    }
+
+    public String getImage() {
+        if (StringUtils.isEmpty(image)) {
+            throw new InvalidParameterValueException("No image provided for resource icon");
+        }
+        return image;
+    }
+
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public void execute()  {
+        try {
+            if (!imageValidator(getImage())) {
+                throw new InvalidParameterValueException("Invalid image uploaded");
+            }
+            boolean result = resourceIconManager.uploadResourceIcon(getResourceIds(), getResourceType(), getImage());
+            if (result) {
+                SuccessResponse response = new SuccessResponse(getCommandName());
+                setResponseObject(response);
+            } else {
+                throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to upload resource image");
+            }
+        } catch (Exception e) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getLocalizedMessage());
+        }
+    }
+
+    private boolean imageValidator (String base64Image) {
+        BufferedImage image = null;
+        byte[] imageByte;
+        try {
+            imageByte = Base64.getDecoder().decode(base64Image);
+            ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
+            image = ImageIO.read(bis);
+            bis.close();
+            if (image == null) {
+                return false;
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Data uploaded not a valid image");
+            return false;
+        }
+        return true;
+    }
+    @Override
+    public String getCommandName() {
+        return s_name;
+    }
+
+    @Override
+    public long getEntityOwnerId() {
+        Account account = CallContext.current().getCallingAccount();// Let's give the caller here for event logging.
+        if (account != null) {
+            return account.getAccountId();
+        }
+
+        return Account.ACCOUNT_ID_SYSTEM;
+    }
+
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java
index c0c2b24..a1adee7 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java
@@ -16,6 +16,9 @@
 // under the License.
 package org.apache.cloudstack.api.command.admin.user;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -25,6 +28,8 @@ import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.response.ListResponse;
 import org.apache.cloudstack.api.response.UserResponse;
 
+import java.util.List;
+
 @APICommand(name = "listUsers", description = "Lists user accounts", responseObject = UserResponse.class,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
 public class ListUsersCmd extends BaseListAccountResourcesCmd {
@@ -50,6 +55,10 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd {
     @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, description = "List user by the username")
     private String username;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
+            description = "flag to display the resource icon for users")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -70,6 +79,10 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd {
         return username;
     }
 
+    public Boolean getShowIcon() {
+        return showIcon != null ? showIcon : false;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -84,5 +97,22 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd {
         ListResponse<UserResponse> response = _queryService.searchForUsers(this);
         response.setResponseName(getCommandName());
         this.setResponseObject(response);
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateUserResponse(response.getResponses());
+        }
+    }
+
+    private void updateUserResponse(List<UserResponse> response) {
+        for (UserResponse userResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.User, userResponse.getObjectId());
+            if (resourceIcon == null) {
+                resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Account, userResponse.getAccountId());
+                if (resourceIcon == null) {
+                    continue;
+                }
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            userResponse.setResourceIconResponse(iconResponse);
+        }
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java
index 414c058..af377cd 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java
@@ -171,7 +171,7 @@ public class CreateZoneCmd extends BaseCmd {
         CallContext.current().setEventDetails("Zone Name: " + getZoneName());
         DataCenter result = _configService.createZone(this);
         if (result != null){
-            ZoneResponse response = _responseGenerator.createZoneResponse(ResponseView.Full, result, false);
+            ZoneResponse response = _responseGenerator.createZoneResponse(ResponseView.Full, result, false, false);
             response.setResponseName(getCommandName());
             setResponseObject(response);
         } else {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java
index 2f5feed..ac80ca5 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java
@@ -189,7 +189,7 @@ public class UpdateZoneCmd extends BaseCmd {
         CallContext.current().setEventDetails("Zone Id: " + getId());
         DataCenter result = _configService.editZone(this);
         if (result != null) {
-            ZoneResponse response = _responseGenerator.createZoneResponse(ResponseView.Full, result, false);
+            ZoneResponse response = _responseGenerator.createZoneResponse(ResponseView.Full, result, false, false);
             response.setResponseName(getCommandName());
             setResponseObject(response);
         } else {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java
index 29f86c8..993384d 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java
@@ -20,6 +20,9 @@ import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -68,6 +71,10 @@ public class ListAccountsCmd extends BaseListDomainResourcesCmd implements UserC
                description = "comma separated list of account details requested, value can be a list of [ all, resource, min]")
     private List<String> viewDetails;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
+            description = "flag to display the resource icon for accounts")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -111,6 +118,10 @@ public class ListAccountsCmd extends BaseListDomainResourcesCmd implements UserC
         return dv;
     }
 
+    public Boolean getShowIcon() {
+        return showIcon != null ? showIcon : false;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -125,5 +136,19 @@ public class ListAccountsCmd extends BaseListDomainResourcesCmd implements UserC
         ListResponse<AccountResponse> response = _queryService.searchForAccounts(this);
         response.setResponseName(getCommandName());
         setResponseObject(response);
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateAccountResponse(response.getResponses());
+        }
+    }
+
+    private void updateAccountResponse(List<AccountResponse> response) {
+        for (AccountResponse accountResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Account, accountResponse.getObjectId());
+            if (resourceIcon == null) {
+                continue;
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            accountResponse.setResourceIconResponse(iconResponse);
+        }
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java
index b741ae7..44bbb71 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java
@@ -16,6 +16,9 @@
 // under the License.
 package org.apache.cloudstack.api.command.user.iso;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -33,6 +36,8 @@ import org.apache.cloudstack.context.CallContext;
 import com.cloud.template.VirtualMachineTemplate.TemplateFilter;
 import com.cloud.user.Account;
 
+import java.util.List;
+
 @APICommand(name = "listIsos", description = "Lists all available ISO files.", responseObject = TemplateResponse.class, responseView = ResponseView.Restricted,
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd {
@@ -82,6 +87,9 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd {
     @Parameter(name = ApiConstants.SHOW_UNIQUE, type = CommandType.BOOLEAN, description = "If set to true, list only unique isos across zones", since = "4.13.2")
     private Boolean showUnique;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the isos")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -126,6 +134,10 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd {
         return showUnique != null && showUnique;
     }
 
+    public Boolean getShowIcon () {
+        return  showIcon != null ? showIcon : false;
+    }
+
     public boolean listInReadyState() {
         Account account = CallContext.current().getCallingAccount();
         // It is account specific if account is admin type and domainId and accountName are not null
@@ -162,7 +174,21 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd {
     @Override
     public void execute() {
         ListResponse<TemplateResponse> response = _queryService.listIsos(this);
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateIsoResponse(response.getResponses());
+        }
         response.setResponseName(getCommandName());
         setResponseObject(response);
     }
+
+    private void updateIsoResponse(List<TemplateResponse> response) {
+        for (TemplateResponse templateResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.ISO, templateResponse.getId());
+            if (resourceIcon == null) {
+                continue;
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            templateResponse.setResourceIconResponse(iconResponse);
+        }
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java
index c69fc69..5149d4e 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java
@@ -19,7 +19,10 @@ package org.apache.cloudstack.api.command.user.network;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
 import org.apache.cloudstack.api.response.NetworkOfferingResponse;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.acl.RoleType;
@@ -93,6 +96,10 @@ public class ListNetworksCmd extends BaseListTaggedResourcesCmd implements UserC
     @Parameter(name = ApiConstants.NETWORK_OFFERING_ID, type = CommandType.UUID, entityType = NetworkOfferingResponse.class, description = "list networks by network offering ID")
     private Long networkOfferingId;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
+            description = "flag to display the resource icon for networks")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -166,6 +173,11 @@ public class ListNetworksCmd extends BaseListTaggedResourcesCmd implements UserC
         }
         return super.getDisplay();
     }
+
+    public Boolean getShowIcon() {
+        return showIcon != null ? showIcon : false;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -186,5 +198,22 @@ public class ListNetworksCmd extends BaseListTaggedResourcesCmd implements UserC
         response.setResponses(networkResponses, networks.second());
         response.setResponseName(getCommandName());
         setResponseObject(response);
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateNetworkResponse(response.getResponses());
+        }
+    }
+
+    private void updateNetworkResponse(List<NetworkResponse> response) {
+        for (NetworkResponse networkResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Network, networkResponse.getId());
+            if (resourceIcon == null) {
+                resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Vpc, networkResponse.getVpcId());
+                if (resourceIcon == null) {
+                    continue;
+                }
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            networkResponse.setResourceIconResponse(iconResponse);
+        }
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java
index 2c96551..f434b75 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java
@@ -21,6 +21,9 @@ import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.log4j.Logger;
 
 import org.apache.cloudstack.api.APICommand;
@@ -72,6 +75,10 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd {
                description = "comma separated list of project details requested, value can be a list of [ all, resource, min]")
     private List<String> viewDetails;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
+            description = "flag to display the resource icon for projects")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -124,6 +131,10 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd {
         return dv;
     }
 
+    public Boolean getShowIcon() {
+        return showIcon != null ? showIcon : false;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -133,5 +144,19 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd {
         ListResponse<ProjectResponse> response = _queryService.listProjects(this);
         response.setResponseName(getCommandName());
         this.setResponseObject(response);
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateProjectResponse(response.getResponses());
+        }
+    }
+
+    private void updateProjectResponse(List<ProjectResponse> response) {
+        for (ProjectResponse projectResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Project, projectResponse.getId());
+            if (resourceIcon == null) {
+                continue;
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            projectResponse.setResourceIconResponse(iconResponse);
+        }
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java
index 5007e63..b7a4679 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java
@@ -54,12 +54,12 @@ public class ListDetailOptionsCmd extends BaseCmd {
     /////////////////////////////////////////////////////
 
     public ResourceTag.ResourceObjectType getResourceType() {
-        return _taggedResourceService.getResourceType(resourceType);
+        return resourceManagerUtil.getResourceType(resourceType);
     }
 
     public String getResourceId() {
         if (!Strings.isNullOrEmpty(resourceId)) {
-            return _taggedResourceService.getUuid(resourceId, getResourceType());
+            return resourceManagerUtil.getUuid(resourceId, getResourceType());
         }
         return null;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java
index cde31cd..bc68170 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java
@@ -68,7 +68,7 @@ public class CreateTagsCmd extends BaseAsyncCmd {
     /////////////////////////////////////////////////////
 
     public ResourceObjectType getResourceType() {
-        return _taggedResourceService.getResourceType(resourceType);
+        return resourceManagerUtil.getResourceType(resourceType);
     }
 
     public Map<String, String> getTags() {
@@ -106,17 +106,17 @@ public class CreateTagsCmd extends BaseAsyncCmd {
             SuccessResponse response = new SuccessResponse(getCommandName());
             setResponseObject(response);
         } else {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create tags");
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to upload resource icon");
         }
     }
 
     @Override
     public String getEventType() {
-        return EventTypes.EVENT_TAGS_CREATE;
+        return EventTypes.EVENT_RESOURCE_ICON_UPLOAD;
     }
 
     @Override
     public String getEventDescription() {
-        return "creating tags";
+        return "Uploading resource icon";
     }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java
index e42cfce..3fa6b67 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java
@@ -66,7 +66,7 @@ public class DeleteTagsCmd extends BaseAsyncCmd {
     /////////////////////////////////////////////////////
 
     public ResourceObjectType getResourceType() {
-        return _taggedResourceService.getResourceType(resourceType);
+        return resourceManagerUtil.getResourceType(resourceType);
     }
 
     public Map<String, String> getTags() {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java
index 5ef66c5..bf3b2fb 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java
@@ -17,6 +17,9 @@
 package org.apache.cloudstack.api.command.user.template;
 
 import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 
@@ -161,10 +164,17 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User
         return onlyReady;
     }
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the templates")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
 
+    public Boolean getShowIcon () {
+        return  showIcon != null ? showIcon : false;
+    }
+
     @Override
     public String getCommandName() {
         return s_name;
@@ -178,10 +188,24 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User
     @Override
     public void execute() {
         ListResponse<TemplateResponse> response = _queryService.listTemplates(this);
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateTemplateResponse(response.getResponses());
+        }
         response.setResponseName(getCommandName());
         setResponseObject(response);
     }
 
+    private void updateTemplateResponse(List<TemplateResponse> response) {
+        for (TemplateResponse templateResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Template, templateResponse.getId());
+            if (resourceIcon == null) {
+                continue;
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            templateResponse.setResourceIconResponse(iconResponse);
+        }
+    }
+
     public List<Long> getIds() {
         return ids;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java
index 72839f2..b0ff102 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java
@@ -20,7 +20,10 @@ import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
 import org.apache.cloudstack.api.command.user.UserCmd;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.api.response.SecurityGroupResponse;
 import org.apache.cloudstack.api.response.UserResponse;
 import org.apache.log4j.Logger;
@@ -141,6 +144,10 @@ public class ListVMsCmd extends BaseListTaggedResourcesCmd implements UserCmd {
     @Parameter(name = ApiConstants.HA_ENABLE, type = CommandType.BOOLEAN, description = "list by the High Availability offering; true if filtering VMs with HA enabled; false for VMs with HA disabled", since = "4.15")
     private Boolean haEnabled;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
+            description = "flag to display the resource icon for VMs", since = "4.16.0.0")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -252,6 +259,11 @@ public class ListVMsCmd extends BaseListTaggedResourcesCmd implements UserCmd {
         }
         return super.getDisplay();
     }
+
+    public Boolean getShowIcon() {
+        return showIcon != null ? showIcon : false;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -268,7 +280,30 @@ public class ListVMsCmd extends BaseListTaggedResourcesCmd implements UserCmd {
     @Override
     public void execute() {
         ListResponse<UserVmResponse> response = _queryService.searchForUserVMs(this);
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateVMResponse(response.getResponses());
+        }
         response.setResponseName(getCommandName());
         setResponseObject(response);
     }
+
+    protected void updateVMResponse(List<UserVmResponse> response) {
+        for (UserVmResponse vmResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.UserVm, vmResponse.getId());
+            if (resourceIcon == null) {
+                ResourceTag.ResourceObjectType type = ResourceTag.ResourceObjectType.Template;
+                String uuid = vmResponse.getTemplateId();
+                if (vmResponse.getIsoId() != null) {
+                    uuid = vmResponse.getIsoId();
+                    type = ResourceTag.ResourceObjectType.ISO;
+                }
+                resourceIcon = resourceIconManager.getByResourceTypeAndUuid(type, uuid);
+                if (resourceIcon == null) {
+                    continue;
+                }
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            vmResponse.setResourceIconResponse(iconResponse);
+        }
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java
index a8a3a75..d10bbb7 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java
@@ -61,7 +61,7 @@ public class AddResourceDetailCmd extends BaseAsyncCmd {
     }
 
     public ResourceTag.ResourceObjectType getResourceType() {
-        return _taggedResourceService.getResourceType(resourceType);
+        return resourceManagerUtil.getResourceType(resourceType);
     }
 
     public String getResourceId() {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListResourceDetailsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListResourceDetailsCmd.java
index 83cb1ff..33aa44a 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListResourceDetailsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListResourceDetailsCmd.java
@@ -92,7 +92,7 @@ public class ListResourceDetailsCmd extends BaseListProjectAndAccountResourcesCm
     }
 
     public ResourceTag.ResourceObjectType getResourceType() {
-        return _taggedResourceService.getResourceType(resourceType);
+        return resourceManagerUtil.getResourceType(resourceType);
     }
 
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RemoveResourceDetailCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RemoveResourceDetailCmd.java
index 6fe576d..703fe8a 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RemoveResourceDetailCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RemoveResourceDetailCmd.java
@@ -56,7 +56,7 @@ public class RemoveResourceDetailCmd extends BaseAsyncCmd {
     /////////////////////////////////////////////////////
 
     public ResourceTag.ResourceObjectType getResourceType() {
-        return _taggedResourceService.getResourceType(resourceType);
+        return resourceManagerUtil.getResourceType(resourceType);
     }
 
     public String getKey() {
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java
index adcbf8b..b230603 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java
@@ -19,6 +19,8 @@ package org.apache.cloudstack.api.command.user.vpc;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
 import org.apache.cloudstack.acl.RoleType;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
@@ -27,6 +29,7 @@ import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.command.user.UserCmd;
 import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.api.response.VpcOfferingResponse;
 import org.apache.cloudstack.api.response.VpcResponse;
 import org.apache.cloudstack.api.response.ZoneResponse;
@@ -76,6 +79,10 @@ public class ListVPCsCmd extends BaseListTaggedResourcesCmd implements UserCmd {
     @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "list resources by display flag; only ROOT admin is eligible to pass this parameter", since = "4.4", authorized = {RoleType.Admin})
     private Boolean display;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN,
+            description = "flag to display the resource icon for VPCs")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -124,6 +131,10 @@ public class ListVPCsCmd extends BaseListTaggedResourcesCmd implements UserCmd {
         return super.getDisplay();
     }
 
+    public Boolean getShowIcon() {
+        return showIcon != null ? showIcon : false;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -144,6 +155,20 @@ public class ListVPCsCmd extends BaseListTaggedResourcesCmd implements UserCmd {
         response.setResponses(vpcResponses, vpcs.second());
         response.setResponseName(getCommandName());
         setResponseObject(response);
+        if (response != null && response.getCount() > 0 && getShowIcon()) {
+            updateVpcResponse(response.getResponses());
+        }
+    }
+
+    private void updateVpcResponse(List<VpcResponse> response) {
+        for (VpcResponse vpcResponse : response) {
+            ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Vpc, vpcResponse.getId());
+            if (resourceIcon == null) {
+                continue;
+            }
+            ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon);
+            vpcResponse.setResourceIconResponse(iconResponse);
+        }
     }
 
     @Override
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java
index 9fd4c5a..0dba83b 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java
@@ -123,7 +123,7 @@ public class AddVpnUserCmd extends BaseAsyncCreateCmd {
             if (!_ravService.applyVpnUsers(vpnUser.getAccountId(), userName)) {
                 throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add vpn user");
             }
-        }catch (Exception ex) {
+        } catch (Exception ex) {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage());
         }
 
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java
index f7e3155..c79afb1 100644
--- a/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java
@@ -65,6 +65,9 @@ public class ListZonesCmd extends BaseListCmd implements UserCmd {
     @Parameter(name = ApiConstants.TAGS, type = CommandType.MAP, description = "List zones by resource tags (key/value pairs)", since = "4.3")
     private Map tags;
 
+    @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the zones")
+    private Boolean showIcon;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -97,6 +100,10 @@ public class ListZonesCmd extends BaseListCmd implements UserCmd {
         return TaggedResources.parseKeyValueMap(tags, false);
     }
 
+    public Boolean getShowIcon () {
+        return  showIcon != null ? showIcon : false;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
@@ -108,7 +115,6 @@ public class ListZonesCmd extends BaseListCmd implements UserCmd {
 
     @Override
     public void execute() {
-
         ListResponse<ZoneResponse> response = _queryService.listDataCenters(this);
         response.setResponseName(getCommandName());
         setResponseObject(response);
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java
index 5cd2fd3..770df26 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java
@@ -30,7 +30,7 @@ import com.cloud.user.Account;
 import com.google.gson.annotations.SerializedName;
 
 @EntityReference(value = Account.class)
-public class AccountResponse extends BaseResponse implements ResourceLimitAndCountResponse {
+public class AccountResponse extends BaseResponse implements ResourceLimitAndCountResponse, SetResourceIconResponse {
     @SerializedName(ApiConstants.ID)
     @Param(description = "the id of the account")
     private String id;
@@ -263,6 +263,10 @@ public class AccountResponse extends BaseResponse implements ResourceLimitAndCou
     @Param(description = "the list of acl groups that account belongs to", since = "4.4")
     private List<String> groups;
 
+    @SerializedName(ApiConstants.RESOURCE_ICON)
+    @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
+    ResourceIconResponse icon;
+
     @Override
     public String getObjectId() {
         return id;
@@ -537,4 +541,8 @@ public class AccountResponse extends BaseResponse implements ResourceLimitAndCou
         this.groups = groups;
     }
 
+    @Override
+    public void setResourceIconResponse(ResourceIconResponse icon) {
+        this.icon = icon;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java
index 556fe04..6d56552 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java
@@ -28,7 +28,7 @@ import com.cloud.serializer.Param;
 import java.util.Date;
 
 @EntityReference(value = Domain.class)
-public class DomainResponse extends BaseResponseWithAnnotations implements ResourceLimitAndCountResponse {
+public class DomainResponse extends BaseResponseWithAnnotations implements ResourceLimitAndCountResponse, SetResourceIconResponse {
     @SerializedName(ApiConstants.ID)
     @Param(description = "the ID of the domain")
     private String id;
@@ -175,6 +175,10 @@ public class DomainResponse extends BaseResponseWithAnnotations implements Resou
     @SerializedName("secondarystorageavailable") @Param(description="the total secondary storage space (in GiB) available to be used for this domain", since="4.2.0")
     private String secondaryStorageAvailable;
 
+    @SerializedName(ApiConstants.RESOURCE_ICON)
+    @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
+    ResourceIconResponse icon;
+
     public String getId() {
         return this.id;
     }
@@ -429,4 +433,9 @@ public class DomainResponse extends BaseResponseWithAnnotations implements Resou
     public void setVmRunning(Integer vmRunning) {
         // TODO Auto-generated method stub
     }
+
+    @Override
+    public void setResourceIconResponse(ResourceIconResponse icon) {
+        this.icon = icon;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java
index 80b1999..4b41610 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java
@@ -33,7 +33,7 @@ import com.google.gson.annotations.SerializedName;
 
 @SuppressWarnings("unused")
 @EntityReference(value = {Network.class, ProjectAccount.class})
-public class NetworkResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse {
+public class NetworkResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse, SetResourceIconResponse {
 
     @SerializedName(ApiConstants.ID)
     @Param(description = "the id of the network")
@@ -247,6 +247,10 @@ public class NetworkResponse extends BaseResponseWithAnnotations implements Cont
     @Param(description = "If the network has redundant routers enabled", since = "4.11.1")
     private Boolean redundantRouter;
 
+    @SerializedName(ApiConstants.RESOURCE_ICON)
+    @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
+    ResourceIconResponse icon;
+
     @SerializedName(ApiConstants.CREATED)
     @Param(description = "the date this network was created", since = "4.16.0")
     private Date created;
@@ -271,6 +275,10 @@ public class NetworkResponse extends BaseResponseWithAnnotations implements Cont
         this.id = id;
     }
 
+    public String getId() {
+        return id;
+    }
+
     public void setName(String name) {
         this.name = name;
     }
@@ -428,6 +436,10 @@ public class NetworkResponse extends BaseResponseWithAnnotations implements Cont
         this.vpcId = vpcId;
     }
 
+    public String getVpcId() {
+        return vpcId;
+    }
+
     public void setCanUseForDeploy(Boolean canUseForDeploy) {
         this.canUseForDeploy = canUseForDeploy;
     }
@@ -496,6 +508,11 @@ public class NetworkResponse extends BaseResponseWithAnnotations implements Cont
         this.vpcName = vpcName;
     }
 
+    @Override
+    public void setResourceIconResponse(ResourceIconResponse icon) {
+        this.icon = icon;
+    }
+
     public Date getCreated() {
         return created;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java
index 7f14fce..c43dd09 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java
@@ -30,7 +30,7 @@ import com.cloud.serializer.Param;
 import com.google.gson.annotations.SerializedName;
 
 @EntityReference(value = Project.class)
-public class ProjectResponse extends BaseResponse implements ResourceLimitAndCountResponse {
+public class ProjectResponse extends BaseResponse implements ResourceLimitAndCountResponse, SetResourceIconResponse {
 
     @SerializedName(ApiConstants.ID)
     @Param(description = "the id of the project")
@@ -208,6 +208,10 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou
     @Param(description = "the total number of virtual machines running for this project", since = "4.2.0")
     private Integer vmRunning;
 
+    @SerializedName(ApiConstants.RESOURCE_ICON)
+    @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
+    ResourceIconResponse icon;
+
     @SerializedName(ApiConstants.CREATED)
     @Param(description = "the date this project was created", since = "4.16.0")
     private Date created;
@@ -216,6 +220,10 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou
         this.id = id;
     }
 
+    public String getId() {
+        return id;
+    }
+
     public void setName(String name) {
         this.name = name;
     }
@@ -427,6 +435,11 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou
         this.owners = owners;
     }
 
+    @Override
+    public void setResourceIconResponse(ResourceIconResponse icon) {
+        this.icon = icon;
+    }
+
     public Date getCreated() {
         return created;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceIconResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceIconResponse.java
new file mode 100644
index 0000000..403a91c
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceIconResponse.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.api.response;
+
+import com.cloud.serializer.Param;
+import com.cloud.server.ResourceTag;
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseResponse;
+
+public class ResourceIconResponse extends BaseResponse {
+    @SerializedName(ApiConstants.RESOURCE_TYPE)
+    @Param(description = "resource type")
+    private ResourceTag.ResourceObjectType resourceType;
+
+    @SerializedName(ApiConstants.RESOURCE_ID)
+    @Param(description = "id of the resource")
+    private String resourceId;
+
+    @SerializedName(ApiConstants.BASE64_IMAGE)
+    @Param(description = "base64 representation of resource icon")
+    private String image;
+
+    public ResourceTag.ResourceObjectType getResourceType() {
+        return resourceType;
+    }
+
+    public void setResourceType(ResourceTag.ResourceObjectType resourceType) {
+        this.resourceType = resourceType;
+    }
+
+    public String getResourceId() {
+        return resourceId;
+    }
+
+    public void setResourceId(String resourceId) {
+        this.resourceId = resourceId;
+    }
+
+    public String getImage() {
+        return image;
+    }
+
+    public void setImage(String image) {
+        this.image = image;
+    }
+}
diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java b/api/src/main/java/org/apache/cloudstack/api/response/SetResourceIconResponse.java
similarity index 61%
copy from server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
copy to api/src/main/java/org/apache/cloudstack/api/response/SetResourceIconResponse.java
index 1c3ff1b..9af1afe 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/SetResourceIconResponse.java
@@ -14,18 +14,8 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.api.query.dao;
+package org.apache.cloudstack.api.response;
 
-import org.apache.cloudstack.api.ResponseObject.ResponseView;
-import org.apache.cloudstack.api.response.ZoneResponse;
-
-import com.cloud.api.query.vo.DataCenterJoinVO;
-import com.cloud.dc.DataCenter;
-import com.cloud.utils.db.GenericDao;
-
-public interface DataCenterJoinDao extends GenericDao<DataCenterJoinVO, Long> {
-
-    ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dof, Boolean showCapacities);
-
-    DataCenterJoinVO newDataCenterView(DataCenter dof);
+public interface SetResourceIconResponse {
+    void setResourceIconResponse(ResourceIconResponse icon);
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java
index 3633fa7..892b5b8 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java
@@ -34,7 +34,7 @@ import com.google.gson.annotations.SerializedName;
 
 @EntityReference(value = VirtualMachineTemplate.class)
 @SuppressWarnings("unused")
-public class TemplateResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse {
+public class TemplateResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse, SetResourceIconResponse {
     @SerializedName(ApiConstants.ID)
     @Param(description = "the template ID")
     private String id;
@@ -223,6 +223,10 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements
     @Param(description = "the URL which the template/iso is registered from")
     private String url;
 
+    @SerializedName(ApiConstants.RESOURCE_ICON)
+    @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
+    ResourceIconResponse icon;
+
     public TemplateResponse() {
         tags = new LinkedHashSet<>();
     }
@@ -458,4 +462,9 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements
     public void setUrl(String url) {
         this.url = url;
     }
+
+    @Override
+    public void setResourceIconResponse(ResourceIconResponse icon) {
+        this.icon = icon;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java
index dd10510..260d160 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java
@@ -29,7 +29,7 @@ import com.cloud.serializer.Param;
 import com.cloud.user.User;
 
 @EntityReference(value = User.class)
-public class UserResponse extends BaseResponse {
+public class UserResponse extends BaseResponse implements SetResourceIconResponse {
     @SerializedName("id")
     @Param(description = "the user ID")
     private String id;
@@ -115,6 +115,10 @@ public class UserResponse extends BaseResponse {
     @Param(description = "true if user is default, false otherwise", since = "4.2.0")
     private Boolean isDefault;
 
+    @SerializedName(ApiConstants.RESOURCE_ICON)
+    @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
+    ResourceIconResponse icon;
+
     @Override
     public String getObjectId() {
         return this.getId();
@@ -275,4 +279,9 @@ public class UserResponse extends BaseResponse {
             this.userSource = User.Source.NATIVE.toString().toLowerCase();
         }
     }
+
+    @Override
+    public void setResourceIconResponse(ResourceIconResponse icon) {
+        this.icon = icon;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
index 11c1581..3483c17 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
@@ -37,7 +37,7 @@ import com.google.gson.annotations.SerializedName;
 
 @SuppressWarnings("unused")
 @EntityReference(value = {VirtualMachine.class, UserVm.class, VirtualRouter.class})
-public class UserVmResponse extends BaseResponseWithTagInformation implements ControlledEntityResponse {
+public class UserVmResponse extends BaseResponseWithTagInformation implements ControlledEntityResponse, SetResourceIconResponse {
     @SerializedName(ApiConstants.ID)
     @Param(description = "the ID of the virtual machine")
     private String id;
@@ -328,6 +328,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
     @Param(description = "the total number of network traffic bytes sent")
     private Long bytesSent;
 
+    @SerializedName(ApiConstants.RESOURCE_ICON)
+    @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
+    ResourceIconResponse resourceIconResponse;
+
     public UserVmResponse() {
         securityGroupList = new LinkedHashSet<SecurityGroupResponse>();
         nics = new TreeSet<>(Comparator.comparingInt(x -> Integer.parseInt(x.getDeviceId())));
@@ -924,6 +928,15 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
 
     public void setPoolType(String poolType) { this.poolType = poolType; }
 
+    @Override
+    public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) {
+        this.resourceIconResponse = resourceIconResponse;
+    }
+
+    public ResourceIconResponse getResourceIconResponse() {
+        return resourceIconResponse;
+    }
+
     public void setLastUpdated(Date lastUpdated) {
         this.lastUpdated = lastUpdated;
     }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java
index f91945f..151c3a9 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java
@@ -30,7 +30,7 @@ import com.google.gson.annotations.SerializedName;
 
 @EntityReference(value = Vpc.class)
 @SuppressWarnings("unused")
-public class VpcResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse {
+public class VpcResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse, SetResourceIconResponse {
     @SerializedName("id")
     @Param(description = "the id of the VPC")
     private String id;
@@ -127,10 +127,18 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll
     @Param(description = "if this VPC has redundant router", since = "4.6")
     private boolean redundantRouter;
 
+    @SerializedName(ApiConstants.RESOURCE_ICON)
+    @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
+    ResourceIconResponse icon;
+
     public void setId(final String id) {
         this.id = id;
     }
 
+    public String getId() {
+        return id;
+    }
+
     public void setName(final String name) {
         this.name = name;
     }
@@ -231,4 +239,9 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll
     public void setRedundantRouter(final Boolean redundantRouter) {
         this.redundantRouter = redundantRouter;
     }
+
+    @Override
+    public void setResourceIconResponse(ResourceIconResponse icon) {
+        this.icon = icon;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java
index 47f3b0d..676ca4a 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java
@@ -32,7 +32,7 @@ import com.google.gson.annotations.SerializedName;
 
 @SuppressWarnings("unused")
 @EntityReference(value = DataCenter.class)
-public class ZoneResponse extends BaseResponseWithAnnotations {
+public class ZoneResponse extends BaseResponseWithAnnotations implements SetResourceIconResponse {
     @SerializedName(ApiConstants.ID)
     @Param(description = "Zone id")
     private String id;
@@ -125,6 +125,10 @@ public class ZoneResponse extends BaseResponseWithAnnotations {
     @Param(description = "Meta data associated with the zone (key/value pairs)", since = "4.3.0")
     private Map<String, String> resourceDetails;
 
+    @SerializedName(ApiConstants.RESOURCE_ICON)
+    @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0")
+    ResourceIconResponse resourceIconResponse;
+
     public ZoneResponse() {
         tags = new LinkedHashSet<ResourceTagResponse>();
     }
@@ -315,4 +319,13 @@ public class ZoneResponse extends BaseResponseWithAnnotations {
     public Map<String, String> getResourceDetails() {
         return resourceDetails;
     }
+
+    @Override
+    public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) {
+        this.resourceIconResponse = resourceIconResponse;
+    }
+
+    public ResourceIconResponse getResourceIconResponse() {
+        return resourceIconResponse;
+    }
 }
diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
index 3484de8..bb418f9 100644
--- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java
+++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java
@@ -24,6 +24,7 @@ import org.apache.cloudstack.api.command.admin.host.ListHostTagsCmd;
 import org.apache.cloudstack.api.command.admin.host.ListHostsCmd;
 import org.apache.cloudstack.api.command.admin.internallb.ListInternalLBVMsCmd;
 import org.apache.cloudstack.api.command.admin.management.ListMgmtsCmd;
+import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd;
 import org.apache.cloudstack.api.command.admin.router.GetRouterHealthCheckResultsCmd;
 import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd;
 import org.apache.cloudstack.api.command.admin.storage.ListImageStoresCmd;
@@ -67,6 +68,7 @@ import org.apache.cloudstack.api.response.ProjectAccountResponse;
 import org.apache.cloudstack.api.response.ProjectInvitationResponse;
 import org.apache.cloudstack.api.response.ProjectResponse;
 import org.apache.cloudstack.api.response.ResourceDetailResponse;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.api.response.ResourceTagResponse;
 import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
 import org.apache.cloudstack.api.response.SecurityGroupResponse;
@@ -159,6 +161,8 @@ public interface QueryService {
 
     DetailOptionsResponse listDetailOptions(ListDetailOptionsCmd cmd);
 
+    ListResponse<ResourceIconResponse> listResourceIcons(ListResourceIconCmd cmd);
+
     ListResponse<AffinityGroupResponse> searchForAffinityGroups(ListAffinityGroupsCmd cmd);
 
     List<ResourceDetailResponse> listResourceDetails(ListResourceDetailsCmd cmd);
diff --git a/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java b/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java
new file mode 100644
index 0000000..fb9e393
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java
@@ -0,0 +1,167 @@
+// 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 com.cloud.resource.icon;
+
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.GenerationType;
+import javax.persistence.Column;
+import javax.persistence.Enumerated;
+import javax.persistence.EnumType;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "resource_icon")
+public class ResourceIconVO implements ResourceIcon {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    private long id;
+
+    @Column(name = "uuid")
+    private String uuid;
+
+    @Column(name = "resource_id")
+    long resourceId;
+
+    @Column(name = "resource_uuid")
+    private String resourceUuid;
+
+    @Column(name = "resource_type")
+    @Enumerated(value = EnumType.STRING)
+    private ResourceTag.ResourceObjectType resourceType;
+
+    @Column(name = "icon", length = 65535)
+    private String icon;
+
+    @Column(name = "created")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date created = null;
+
+    @Column(name = "updated")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    Date updated;
+
+    @Column(name = "removed")
+    @Temporal(value = TemporalType.TIMESTAMP)
+    private Date removed;
+
+    protected ResourceIconVO() {
+        uuid = UUID.randomUUID().toString();
+    }
+
+    public ResourceIconVO(long resourceId, ResourceTag.ResourceObjectType resourceType, String resourceUuid, String icon) {
+        super();
+        this.resourceId = resourceId;
+        this.resourceType = resourceType;
+        uuid = UUID.randomUUID().toString();
+        this.resourceUuid = resourceUuid;
+        this.icon = icon;
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public String getUuid() {
+        return uuid;
+    }
+
+    public void setUuid(String uuid) {
+        this.uuid = uuid;
+    }
+
+    public long getResourceId() {
+        return resourceId;
+    }
+
+    public void setResourceId(long resourceId) {
+        this.resourceId = resourceId;
+    }
+
+    public String getResourceUuid() {
+        return resourceUuid;
+    }
+
+    public void setResourceUuid(String resourceUuid) {
+        this.resourceUuid = resourceUuid;
+    }
+
+    public ResourceTag.ResourceObjectType getResourceType() {
+        return resourceType;
+    }
+
+    public void setResourceType(ResourceTag.ResourceObjectType resourceType) {
+        this.resourceType = resourceType;
+    }
+
+    public String getIcon() {
+        return icon;
+    }
+
+    public void setIcon(String icon) {
+        this.icon = icon;
+    }
+
+    public Date getCreated() {
+        return created;
+    }
+
+    public void setCreated(Date created) {
+        this.created = created;
+    }
+
+    public Date getUpdated() {
+        return updated;
+    }
+
+    public void setUpdated(Date updated) {
+        this.updated = updated;
+    }
+
+    public Date getRemoved() {
+        return removed;
+    }
+
+    public void setRemoved(Date removed) {
+        this.removed = removed;
+    }
+
+    @Override
+    public String toString() {
+        return "ResourceIconVO{" +
+                "id=" + id +
+                ", uuid='" + uuid + '\'' +
+                ", resourceId=" + resourceId +
+                ", resourceUuid='" + resourceUuid + '\'' +
+                ", resourceType=" + resourceType +
+                '}';
+    }
+}
diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java
similarity index 57%
copy from server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
copy to engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java
index 1c3ff1b..3724e03 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
+++ b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java
@@ -14,18 +14,18 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.api.query.dao;
+package com.cloud.resource.icon.dao;
 
-import org.apache.cloudstack.api.ResponseObject.ResponseView;
-import org.apache.cloudstack.api.response.ZoneResponse;
-
-import com.cloud.api.query.vo.DataCenterJoinVO;
-import com.cloud.dc.DataCenter;
+import com.cloud.resource.icon.ResourceIconVO;
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
 import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 
-public interface DataCenterJoinDao extends GenericDao<DataCenterJoinVO, Long> {
-
-    ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dof, Boolean showCapacities);
+import java.util.List;
 
-    DataCenterJoinVO newDataCenterView(DataCenter dof);
+public interface ResourceIconDao extends GenericDao<ResourceIconVO, Long> {
+    ResourceIconResponse newResourceIconResponse(ResourceIcon resourceIconVO);
+    ResourceIconVO findByResourceUuid(String resourceUuid, ResourceTag.ResourceObjectType resourceType);
+    List<ResourceIconResponse> listResourceIcons(List<String> resourceUuids, ResourceTag.ResourceObjectType resourceType);
 }
diff --git a/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java
new file mode 100644
index 0000000..41eba40
--- /dev/null
+++ b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java
@@ -0,0 +1,79 @@
+// 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 com.cloud.resource.icon.dao;
+
+import com.cloud.resource.icon.ResourceIconVO;
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceTag;
+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.api.response.ResourceIconResponse;
+import org.apache.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResourceIconDaoImpl extends GenericDaoBase<ResourceIconVO, Long> implements ResourceIconDao {
+    public static final Logger s_logger = Logger.getLogger(ResourceIconDaoImpl.class);
+    private final SearchBuilder<ResourceIconVO> AllFieldsSearch;
+
+    protected ResourceIconDaoImpl() {
+        AllFieldsSearch = createSearchBuilder();
+        AllFieldsSearch.and("resourceId", AllFieldsSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
+        AllFieldsSearch.and("uuid", AllFieldsSearch.entity().getResourceUuid(), SearchCriteria.Op.IN);
+        AllFieldsSearch.and("resourceType", AllFieldsSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
+        AllFieldsSearch.done();
+    }
+
+    @Override
+    public ResourceIconResponse newResourceIconResponse(ResourceIcon resourceIcon) {
+        ResourceIconResponse resourceIconResponse = new ResourceIconResponse();
+        resourceIconResponse.setResourceId(resourceIcon.getResourceUuid());
+        resourceIconResponse.setResourceType(resourceIcon.getResourceType());
+        resourceIconResponse.setImage(resourceIcon.getIcon());
+        resourceIconResponse.setObjectName(ApiConstants.RESOURCE_ICON);
+        return resourceIconResponse;
+    }
+
+    @Override
+    public ResourceIconVO findByResourceUuid(String resourceUuid, ResourceTag.ResourceObjectType resourceType) {
+        SearchCriteria<ResourceIconVO> sc = AllFieldsSearch.create();
+        sc.setParameters("uuid", (Object[]) new String[]{resourceUuid});
+        sc.setParameters("resourceType", resourceType);
+        return findOneBy(sc);
+    }
+
+    @Override
+    public List<ResourceIconResponse> listResourceIcons(List<String> resourceUuids, ResourceTag.ResourceObjectType resourceType) {
+        SearchCriteria<ResourceIconVO> sc = AllFieldsSearch.create();
+        sc.setParameters("uuid", resourceUuids.toArray());
+        sc.setParameters("resourceType", resourceType);
+        List<ResourceIconVO> resourceIcons = listBy(sc);
+        List<ResourceIconResponse> iconResponses = new ArrayList<>();
+        for (ResourceIconVO resourceIcon : resourceIcons) {
+            ResourceIconResponse response = new ResourceIconResponse();
+            response.setResourceId(resourceIcon.getResourceUuid());
+            response.setResourceType(resourceIcon.getResourceType());
+            response.setImage(resourceIcon.getIcon());
+            response.setObjectName(ApiConstants.RESOURCE_ICON);
+            iconResponses.add(response);
+        }
+        return iconResponses;
+    }
+}
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 362054e..508b01c 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
@@ -173,6 +173,7 @@
   <bean id="regionDaoImpl" class="org.apache.cloudstack.region.dao.RegionDaoImpl" />
   <bean id="remoteAccessVpnDaoImpl" class="com.cloud.network.dao.RemoteAccessVpnDaoImpl" />
   <bean id="resourceCountDaoImpl" class="com.cloud.configuration.dao.ResourceCountDaoImpl" />
+  <bean id="resourceIconDaoImpl" class="com.cloud.resource.icon.dao.ResourceIconDaoImpl" />
   <bean id="resourceLimitDaoImpl" class="com.cloud.configuration.dao.ResourceLimitDaoImpl" />
   <bean id="resourceTagJoinDaoImpl" class="com.cloud.api.query.dao.ResourceTagJoinDaoImpl" />
   <bean id="resourceTagsDaoImpl" class="com.cloud.tags.dao.ResourceTagsDaoImpl" />
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql b/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql
index cd595ee..64c381e 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql
@@ -697,6 +697,20 @@ CREATE VIEW `cloud`.`host_view` AS
     GROUP BY
         `host`.`id`;
 
+CREATE TABLE `cloud`.`resource_icon` (
+  `id` bigint unsigned NOT NULL auto_increment COMMENT 'id',
+  `uuid` varchar(40),
+  `icon` blob COMMENT 'Base64 version of the resource icon',
+  `resource_id` bigint unsigned NOT NULL,
+  `resource_uuid` varchar(40),
+  `resource_type` varchar(255),
+  `updated` datetime default NULL,
+  `created` datetime default NULL,
+  `removed` datetime default NULL,
+  PRIMARY KEY (`id`),
+  CONSTRAINT `uc_resource_icon__uuid` UNIQUE (`uuid`)
+  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
 ALTER TABLE `cloud`.`annotations` ADD COLUMN `admins_only` tinyint(1) unsigned NOT NULL DEFAULT 1;
 
 -- Allow annotations for resource admins, domain admins and users
diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsMetricsCmd.java b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsMetricsCmd.java
index 9b8564c..947c2f9 100644
--- a/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsMetricsCmd.java
+++ b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsMetricsCmd.java
@@ -44,6 +44,7 @@ public class ListVMsMetricsCmd extends ListVMsCmd {
     @Override
     public void execute() {
         ListResponse<UserVmResponse> userVms = _queryService.searchForUserVMs(this);
+        updateVMResponse(userVms.getResponses());
         final List<VmMetricsResponse> metricsResponses = metricsService.listVmMetrics(userVms.getResponses());
         ListResponse<VmMetricsResponse> response = new ListResponse<>();
         response.setResponses(metricsResponses, userVms.getCount());
diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java
index d688c27..dd46751 100644
--- a/server/src/main/java/com/cloud/api/ApiDBUtils.java
+++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java
@@ -27,6 +27,9 @@ import java.util.Set;
 import javax.annotation.PostConstruct;
 import javax.inject.Inject;
 
+import com.cloud.resource.icon.ResourceIconVO;
+import com.cloud.resource.icon.dao.ResourceIconDao;
+import com.cloud.server.ResourceIcon;
 import org.apache.cloudstack.acl.Role;
 import org.apache.cloudstack.acl.RoleService;
 import org.apache.cloudstack.affinity.AffinityGroup;
@@ -55,6 +58,7 @@ import org.apache.cloudstack.api.response.NetworkOfferingResponse;
 import org.apache.cloudstack.api.response.ProjectAccountResponse;
 import org.apache.cloudstack.api.response.ProjectInvitationResponse;
 import org.apache.cloudstack.api.response.ProjectResponse;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.api.response.ResourceTagResponse;
 import org.apache.cloudstack.api.response.SecurityGroupResponse;
 import org.apache.cloudstack.api.response.ServiceOfferingResponse;
@@ -258,6 +262,7 @@ import com.cloud.server.ResourceTag;
 import com.cloud.server.ResourceTag.ResourceObjectType;
 import com.cloud.server.StatsCollector;
 import com.cloud.server.TaggedResourceService;
+import com.cloud.server.ResourceManagerUtil;
 import com.cloud.service.ServiceOfferingDetailsVO;
 import com.cloud.service.ServiceOfferingVO;
 import com.cloud.service.dao.ServiceOfferingDao;
@@ -418,6 +423,7 @@ public class ApiDBUtils {
     static AutoScaleVmGroupDao s_asVmGroupDao;
     static CounterDao s_counterDao;
     static ResourceTagJoinDao s_tagJoinDao;
+    static ResourceIconDao s_resourceIconDao;
     static EventJoinDao s_eventJoinDao;
     static InstanceGroupJoinDao s_vmGroupJoinDao;
     static UserAccountJoinDao s_userAccountJoinDao;
@@ -462,6 +468,7 @@ public class ApiDBUtils {
     static BackupScheduleDao s_backupScheduleDao;
     static BackupOfferingDao s_backupOfferingDao;
     static NicDao s_nicDao;
+    static ResourceManagerUtil s_resourceManagerUtil;
 
     @Inject
     private ManagementServer ms;
@@ -708,6 +715,10 @@ public class ApiDBUtils {
     private BackupScheduleDao backupScheduleDao;
     @Inject
     private NicDao nicDao;
+    @Inject
+    private ResourceIconDao resourceIconDao;
+    @Inject
+    private ResourceManagerUtil resourceManagerUtil;
 
     @PostConstruct
     void init() {
@@ -834,6 +845,8 @@ public class ApiDBUtils {
         s_backupDao = backupDao;
         s_backupScheduleDao = backupScheduleDao;
         s_backupOfferingDao = backupOfferingDao;
+        s_resourceIconDao = resourceIconDao;
+        s_resourceManagerUtil = resourceManagerUtil;
     }
 
     // ///////////////////////////////////////////////////////////
@@ -1484,7 +1497,7 @@ public class ApiDBUtils {
     }
 
     public static String getUuid(String resourceId, ResourceObjectType resourceType) {
-        return s_taggedResourceService.getUuid(resourceId, resourceType);
+        return s_resourceManagerUtil.getUuid(resourceId, resourceType);
     }
 
     public static List<? extends ResourceTag> listByResourceTypeAndId(ResourceObjectType type, long resourceId) {
@@ -1800,6 +1813,10 @@ public class ApiDBUtils {
         return s_tagJoinDao.newResourceTagResponse(vsg, keyValueOnly);
     }
 
+    public static ResourceIconResponse newResourceIconResponse(ResourceIcon resourceIcon) {
+        return s_resourceIconDao.newResourceIconResponse(resourceIcon);
+    }
+
     public static ResourceTagJoinVO newResourceTagView(ResourceTag sg) {
         return s_tagJoinDao.newResourceTagView(sg);
     }
@@ -2001,8 +2018,8 @@ public class ApiDBUtils {
         return s_serviceOfferingJoinDao.newServiceOfferingView(offering);
     }
 
-    public static ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dc, Boolean showCapacities) {
-        return s_dcJoinDao.newDataCenterResponse(view, dc, showCapacities);
+    public static ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dc, Boolean showCapacities, Boolean showResourceImage) {
+        return s_dcJoinDao.newDataCenterResponse(view, dc, showCapacities, showResourceImage);
     }
 
     public static DataCenterJoinVO newDataCenterView(DataCenter dc) {
@@ -2081,6 +2098,10 @@ public class ApiDBUtils {
         return s_tagJoinDao.listBy(resourceUUID, resourceType);
     }
 
+    public static ResourceIconVO getResourceIconByResourceUUID(String resourceUUID, ResourceObjectType resourceType) {
+        return s_resourceIconDao.findByResourceUuid(resourceUUID, resourceType);
+    }
+
     public static BackupResponse newBackupResponse(Backup backup) {
         return s_backupDao.newBackupResponse(backup);
     }
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index 09057e6..bb79998 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -33,6 +33,7 @@ import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
+import com.cloud.server.ResourceIcon;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.affinity.AffinityGroup;
@@ -113,6 +114,7 @@ import org.apache.cloudstack.api.response.ProviderResponse;
 import org.apache.cloudstack.api.response.RegionResponse;
 import org.apache.cloudstack.api.response.RemoteAccessVpnResponse;
 import org.apache.cloudstack.api.response.ResourceCountResponse;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.api.response.ResourceLimitResponse;
 import org.apache.cloudstack.api.response.ResourceTagResponse;
 import org.apache.cloudstack.api.response.RollingMaintenanceHostSkippedResponse;
@@ -1151,9 +1153,9 @@ public class ApiResponseHelper implements ResponseGenerator {
     }
 
     @Override
-    public ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities) {
+    public ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities, Boolean showResourceIcon) {
         DataCenterJoinVO vOffering = ApiDBUtils.newDataCenterView(dataCenter);
-        return ApiDBUtils.newDataCenterResponse(view, vOffering, showCapacities);
+        return ApiDBUtils.newDataCenterResponse(view, vOffering, showCapacities, showResourceIcon);
     }
 
     public static List<CapacityResponse> getDataCenterCapacityResponse(Long zoneId) {
@@ -4457,4 +4459,9 @@ public class ApiResponseHelper implements ResponseGenerator {
         response.setObjectName("rollingmaintenance");
         return response;
     }
+
+    @Override
+    public ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon) {
+        return  ApiDBUtils.newResourceIconResponse(resourceIcon);
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
index 2041e92..f61fa33 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -30,6 +30,10 @@ import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
+import com.cloud.resource.icon.dao.ResourceIconDao;
+import com.cloud.server.ResourceManagerUtil;
+import com.cloud.storage.dao.VMTemplateDetailsDao;
+import com.cloud.vm.VirtualMachineManager;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
 import org.apache.cloudstack.affinity.AffinityGroupResponse;
@@ -48,6 +52,7 @@ import org.apache.cloudstack.api.command.admin.host.ListHostsCmd;
 import org.apache.cloudstack.api.command.admin.internallb.ListInternalLBVMsCmd;
 import org.apache.cloudstack.api.command.admin.iso.ListIsosCmdByAdmin;
 import org.apache.cloudstack.api.command.admin.management.ListMgmtsCmd;
+import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd;
 import org.apache.cloudstack.api.command.admin.router.GetRouterHealthCheckResultsCmd;
 import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd;
 import org.apache.cloudstack.api.command.admin.storage.ListImageStoresCmd;
@@ -93,6 +98,7 @@ import org.apache.cloudstack.api.response.ProjectAccountResponse;
 import org.apache.cloudstack.api.response.ProjectInvitationResponse;
 import org.apache.cloudstack.api.response.ProjectResponse;
 import org.apache.cloudstack.api.response.ResourceDetailResponse;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.api.response.ResourceTagResponse;
 import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
 import org.apache.cloudstack.api.response.SecurityGroupResponse;
@@ -217,7 +223,6 @@ import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.Volume;
 import com.cloud.storage.dao.StoragePoolTagsDao;
 import com.cloud.storage.dao.VMTemplateDao;
-import com.cloud.storage.dao.VMTemplateDetailsDao;
 import com.cloud.tags.ResourceTagVO;
 import com.cloud.tags.dao.ResourceTagDao;
 import com.cloud.template.VirtualMachineTemplate.State;
@@ -244,7 +249,6 @@ import com.cloud.vm.DomainRouterVO;
 import com.cloud.vm.UserVmVO;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
-import com.cloud.vm.VirtualMachineManager;
 import com.cloud.vm.VmDetailConstants;
 import com.cloud.vm.dao.DomainRouterDao;
 import com.cloud.vm.dao.UserVmDao;
@@ -384,6 +388,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
     private TaggedResourceService _taggedResourceMgr;
 
     @Inject
+    private ResourceManagerUtil resourceManagerUtil;
+
+    @Inject
     private AffinityGroupVMMapDao _affinityGroupVMMapDao;
 
     @Inject
@@ -437,6 +444,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
     @Inject
     private VirtualMachineManager virtualMachineManager;
 
+    @Inject
+    private ResourceIconDao resourceIconDao;
+
     /*
      * (non-Javadoc)
      *
@@ -3145,7 +3155,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
             respView = ResponseView.Full;
         }
 
-        List<ZoneResponse> dcResponses = ViewResponseHelper.createDataCenterResponse(respView, cmd.getShowCapacities(), result.first().toArray(new DataCenterJoinVO[result.first().size()]));
+        List<ZoneResponse> dcResponses = ViewResponseHelper.createDataCenterResponse(respView, cmd.getShowCapacities(), cmd.getShowIcon(), result.first().toArray(new DataCenterJoinVO[result.first().size()]));
         response.setResponses(dcResponses, result.second());
         return response;
     }
@@ -3792,6 +3802,13 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
         return new DetailOptionsResponse(options);
     }
 
+    @Override
+    public ListResponse<ResourceIconResponse> listResourceIcons(ListResourceIconCmd cmd) {
+        ListResponse<ResourceIconResponse> responses = new ListResponse<>();
+        responses.setResponses(resourceIconDao.listResourceIcons(cmd.getResourceIds(), cmd.getResourceType()));
+        return responses;
+    }
+
     private void fillVMOrTemplateDetailOptions(final Map<String, List<String>> options, final HypervisorType hypervisorType) {
         if (options == null) {
             throw new CloudRuntimeException("Invalid/null detail-options response object passed");
@@ -4066,7 +4083,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
 
         //Validation - 1.3
         if (resourceIdStr != null) {
-            resourceId = _taggedResourceMgr.getResourceId(resourceIdStr, resourceType);
+            resourceId = resourceManagerUtil.getResourceId(resourceIdStr, resourceType);
             if (resourceId == null) {
                 throw new InvalidParameterValueException("Cannot find resource with resourceId " + resourceIdStr + " and of resource type " + resourceType);
             }
@@ -4102,7 +4119,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
 
     protected ResourceDetailResponse createResourceDetailsResponse(ResourceDetail requestedDetail, ResourceTag.ResourceObjectType resourceType) {
         ResourceDetailResponse resourceDetailResponse = new ResourceDetailResponse();
-        resourceDetailResponse.setResourceId(_taggedResourceMgr.getUuid(String.valueOf(requestedDetail.getResourceId()), resourceType));
+        resourceDetailResponse.setResourceId(resourceManagerUtil.getUuid(String.valueOf(requestedDetail.getResourceId()), resourceType));
         resourceDetailResponse.setName(requestedDetail.getName());
         resourceDetailResponse.setValue(requestedDetail.getValue());
         resourceDetailResponse.setForDisplay(requestedDetail.isDisplay());
diff --git a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java
index ec33974..ed6f951 100644
--- a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java
@@ -560,10 +560,10 @@ public class ViewResponseHelper {
         return respList;
     }
 
-    public static List<ZoneResponse> createDataCenterResponse(ResponseView view, Boolean showCapacities, DataCenterJoinVO... dcs) {
+    public static List<ZoneResponse> createDataCenterResponse(ResponseView view, Boolean showCapacities, Boolean showResourceImage, DataCenterJoinVO... dcs) {
         List<ZoneResponse> respList = new ArrayList<ZoneResponse>();
         for (DataCenterJoinVO vt : dcs){
-            respList.add(ApiDBUtils.newDataCenterResponse(view, vt, showCapacities));
+            respList.add(ApiDBUtils.newDataCenterResponse(view, vt, showCapacities, showResourceImage));
         }
         return respList;
     }
diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
index 1c3ff1b..a53f864 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java
@@ -25,7 +25,7 @@ import com.cloud.utils.db.GenericDao;
 
 public interface DataCenterJoinDao extends GenericDao<DataCenterJoinVO, Long> {
 
-    ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dof, Boolean showCapacities);
+    ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dof, Boolean showCapacities, Boolean showResourceImage);
 
     DataCenterJoinVO newDataCenterView(DataCenter dof);
 }
diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java
index c777e65..3762484 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java
@@ -20,6 +20,8 @@ import java.util.List;
 
 import javax.inject.Inject;
 
+import com.cloud.resource.icon.ResourceIconVO;
+import org.apache.cloudstack.api.response.ResourceIconResponse;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.context.CallContext;
@@ -61,7 +63,7 @@ public class DataCenterJoinDaoImpl extends GenericDaoBase<DataCenterJoinVO, Long
     }
 
     @Override
-    public ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dataCenter, Boolean showCapacities) {
+    public ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dataCenter, Boolean showCapacities, Boolean showResourceImage) {
         ZoneResponse zoneResponse = new ZoneResponse();
         zoneResponse.setId(dataCenter.getUuid());
         zoneResponse.setName(dataCenter.getName());
@@ -107,6 +109,14 @@ public class DataCenterJoinDaoImpl extends GenericDaoBase<DataCenterJoinVO, Long
             zoneResponse.addTag(tagResponse);
         }
 
+        if (showResourceImage) {
+            ResourceIconVO resourceIcon = ApiDBUtils.getResourceIconByResourceUUID(dataCenter.getUuid(), ResourceObjectType.Zone);
+            if (resourceIcon != null) {
+                ResourceIconResponse iconResponse = ApiDBUtils.newResourceIconResponse(resourceIcon);
+                zoneResponse.setResourceIconResponse(iconResponse);
+            }
+        }
+
         zoneResponse.setResourceDetails(ApiDBUtils.getResourceDetails(dataCenter.getId(), ResourceObjectType.Zone));
         zoneResponse.setHasAnnotation(annotationDao.hasAnnotations(dataCenter.getUuid(), AnnotationService.EntityType.ZONE.name(),
                 _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
diff --git a/server/src/main/java/com/cloud/metadata/ResourceMetaDataManagerImpl.java b/server/src/main/java/com/cloud/metadata/ResourceMetaDataManagerImpl.java
index c5006b4..8291505 100644
--- a/server/src/main/java/com/cloud/metadata/ResourceMetaDataManagerImpl.java
+++ b/server/src/main/java/com/cloud/metadata/ResourceMetaDataManagerImpl.java
@@ -24,6 +24,7 @@ import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
 import com.cloud.offerings.dao.NetworkOfferingDetailsDao;
+import com.cloud.server.ResourceManagerUtil;
 import org.apache.cloudstack.api.ResourceDetail;
 import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
 import org.apache.cloudstack.resourcedetail.dao.AutoScaleVmGroupDetailsDao;
@@ -127,6 +128,8 @@ public class ResourceMetaDataManagerImpl extends ManagerBase implements Resource
     GuestOsDetailsDao _guestOsDetailsDao;
     @Inject
     NetworkOfferingDetailsDao _networkOfferingDetailsDao;
+    @Inject
+    ResourceManagerUtil resourceManagerUtil;
 
     private static Map<ResourceObjectType, ResourceDetailsDao<? extends ResourceDetail>> s_daoMap = new HashMap<ResourceObjectType, ResourceDetailsDao<? extends ResourceDetail>>();
 
@@ -189,7 +192,7 @@ public class ResourceMetaDataManagerImpl extends ManagerBase implements Resource
                     }
 
                     DetailDaoHelper newDetailDaoHelper = new DetailDaoHelper(resourceType);
-                    newDetailDaoHelper.addDetail(_taggedResourceMgr.getResourceId(resourceId, resourceType), key, value, forDisplay);
+                    newDetailDaoHelper.addDetail(resourceManagerUtil.getResourceId(resourceId, resourceType), key, value, forDisplay);
                 }
 
                 return true;
@@ -201,7 +204,7 @@ public class ResourceMetaDataManagerImpl extends ManagerBase implements Resource
     @DB
     @ActionEvent(eventType = EventTypes.EVENT_RESOURCE_DETAILS_DELETE, eventDescription = "deleting resource meta data")
     public boolean deleteResourceMetaData(String resourceId, ResourceObjectType resourceType, String key) {
-        long id = _taggedResourceMgr.getResourceId(resourceId, resourceType);
+        long id = resourceManagerUtil.getResourceId(resourceId, resourceType);
 
         DetailDaoHelper newDetailDaoHelper = new DetailDaoHelper(resourceType);
         if (key != null) {
diff --git a/server/src/main/java/com/cloud/resourceicon/ResourceIconManagerImpl.java b/server/src/main/java/com/cloud/resourceicon/ResourceIconManagerImpl.java
new file mode 100644
index 0000000..137f655
--- /dev/null
+++ b/server/src/main/java/com/cloud/resourceicon/ResourceIconManagerImpl.java
@@ -0,0 +1,230 @@
+// 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 com.cloud.resourceicon;
+
+import com.cloud.domain.PartOf;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.metadata.ResourceMetaDataManagerImpl;
+import com.cloud.network.security.SecurityGroupRuleVO;
+import com.cloud.network.security.SecurityGroupVO;
+import com.cloud.network.vpc.NetworkACLItemVO;
+import com.cloud.network.vpc.NetworkACLVO;
+import com.cloud.network.vpc.VpcVO;
+import com.cloud.projects.ProjectVO;
+import com.cloud.resource.icon.dao.ResourceIconDao;
+import com.cloud.server.ResourceIcon;
+import com.cloud.server.ResourceIconManager;
+
+import com.cloud.server.ResourceManagerUtil;
+import com.cloud.server.ResourceTag;
+import com.cloud.resource.icon.ResourceIconVO;
+import com.cloud.storage.SnapshotPolicyVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.tags.ResourceManagerUtilImpl;
+import com.cloud.user.Account;
+import com.cloud.user.AccountService;
+import com.cloud.user.AccountVO;
+import com.cloud.user.OwnedBy;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.db.DB;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.db.SearchBuilder;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionCallbackNoReturn;
+import com.cloud.utils.db.TransactionStatus;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+import javax.persistence.EntityExistsException;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ResourceIconManagerImpl extends ManagerBase implements ResourceIconManager {
+    public static final Logger s_logger = Logger.getLogger(ResourceMetaDataManagerImpl.class);
+
+    @Inject
+    AccountService accountService;
+    @Inject
+    ResourceManagerUtil resourceManagerUtil;
+    @Inject
+    ResourceIconDao resourceIconDao;
+    @Inject
+    EntityManager entityMgr;
+    @Inject
+    AccountDao accountDao;
+
+    private Pair<Long, Long> getAccountDomain(long resourceId, ResourceTag.ResourceObjectType resourceType) {
+        Class<?> clazz = ResourceManagerUtilImpl.s_typeMap.get(resourceType);
+
+        Object entity = entityMgr.findById(clazz, resourceId);
+        Long accountId = null;
+        Long domainId = null;
+
+        // if the resource type is a security group rule, get the accountId and domainId from the security group itself
+        if (resourceType == ResourceTag.ResourceObjectType.SecurityGroupRule) {
+            SecurityGroupRuleVO rule = (SecurityGroupRuleVO)entity;
+            Object SecurityGroup = entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceTag.ResourceObjectType.SecurityGroup), rule.getSecurityGroupId());
+
+            accountId = ((SecurityGroupVO)SecurityGroup).getAccountId();
+            domainId = ((SecurityGroupVO)SecurityGroup).getDomainId();
+        }
+
+        if (resourceType == ResourceTag.ResourceObjectType.Account) {
+            AccountVO account = (AccountVO)entity;
+            accountId = account.getId();
+            domainId = account.getDomainId();
+        }
+
+        // if the resource type is network acl, get the accountId and domainId from VPC following: NetworkACLItem -> NetworkACL -> VPC
+        if (resourceType == ResourceTag.ResourceObjectType.NetworkACL) {
+            NetworkACLItemVO aclItem = (NetworkACLItemVO)entity;
+            Object networkACL = entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceTag.ResourceObjectType.NetworkACLList), aclItem.getAclId());
+            Long vpcId = ((NetworkACLVO)networkACL).getVpcId();
+
+            if (vpcId != null && vpcId != 0) {
+                Object vpc = entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceTag.ResourceObjectType.Vpc), vpcId);
+
+                accountId = ((VpcVO)vpc).getAccountId();
+                domainId = ((VpcVO)vpc).getDomainId();
+            }
+        }
+
+        if (resourceType == ResourceTag.ResourceObjectType.Project) {
+            accountId = ((ProjectVO)entity).getProjectAccountId();
+        }
+
+        if (resourceType == ResourceTag.ResourceObjectType.SnapshotPolicy) {
+            accountId = entityMgr.findById(VolumeVO.class, ((SnapshotPolicyVO)entity).getVolumeId()).getAccountId();
+        }
+
+        if (entity instanceof OwnedBy) {
+            accountId = ((OwnedBy)entity).getAccountId();
+        }
+
+        if (entity instanceof PartOf) {
+            domainId = ((PartOf)entity).getDomainId();
+        }
+
+        if (accountId == null) {
+            accountId = Account.ACCOUNT_ID_SYSTEM;
+        }
+
+        if ((domainId == null) || ((accountId != null) && (domainId.longValue() == -1))) {
+            domainId = accountDao.getDomainIdForGivenAccountId(accountId);
+        }
+        return new Pair<>(accountId, domainId);
+    }
+
+    @Override
+    @DB
+    @ActionEvent(eventType = EventTypes.EVENT_RESOURCE_ICON_UPLOAD, eventDescription = "uploading resource icon")
+    public boolean uploadResourceIcon(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType, String base64Image) {
+        final Account caller = CallContext.current().getCallingAccount();
+
+        Transaction.execute(new TransactionCallbackNoReturn() {
+            @Override
+            public void doInTransactionWithoutResult(TransactionStatus status) {
+                for (String resourceId : resourceIds) {
+                    if (!resourceType.resourceIconSupport()) {
+                        throw new InvalidParameterValueException("The resource type " + resourceType + " doesn't support resource icons");
+                    }
+
+                    if (base64Image == null) {
+                        throw new InvalidParameterValueException("No icon provided to be uploaded for resource: " + resourceId);
+                    }
+
+                    long id = resourceManagerUtil.getResourceId(resourceId, resourceType);
+                    String resourceUuid = resourceManagerUtil.getUuid(resourceId, resourceType);
+                    ResourceIconVO existingResourceIcon = resourceIconDao.findByResourceUuid(resourceUuid, resourceType);
+                    ResourceIconVO resourceIcon = null;
+                    Pair<Long, Long> accountDomainPair = getAccountDomain(id, resourceType);
+                    Long domainId = accountDomainPair.second();
+                    Long accountId = accountDomainPair.first();
+                    resourceManagerUtil.checkResourceAccessible(accountId, domainId, String.format("Account ' %s ' doesn't have permissions to upload icon for resource ' %s ", caller, id));
+
+                    if (existingResourceIcon == null) {
+                        resourceIcon = new ResourceIconVO(id, resourceType, resourceUuid, base64Image);
+                    } else {
+                        resourceIcon = existingResourceIcon;
+                        resourceIcon.setIcon(base64Image);
+                        resourceIcon.setUpdated(new Date());
+                    }
+                    try {
+                        resourceIconDao.persist(resourceIcon);
+                    } catch (EntityExistsException e) {
+                        throw new CloudRuntimeException(String.format("Image already uploaded for resource type: %s with id %s",  resourceType.toString(), resourceId),e);
+                    }
+                }
+            }
+        });
+
+        return true;
+    }
+
+    @Override
+    @ActionEvent(eventType = EventTypes.EVENT_RESOURCE_ICON_DELETE, eventDescription = "deleting resource icon")
+    public boolean deleteResourceIcon(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType) {
+        Account caller = CallContext.current().getCallingAccount();
+        List<? extends ResourceIcon> resourceIcons = searchResourceIcons(resourceIds, resourceType);
+        if (resourceIcons.isEmpty()) {
+            s_logger.debug("No resource Icon(s) uploaded for the specified resources");
+            return false;
+        }
+        Transaction.execute(new TransactionCallbackNoReturn() {
+            @Override
+            public void doInTransactionWithoutResult(TransactionStatus status) {
+                for (ResourceIcon resourceIcon : resourceIcons) {
+                    String resourceId = resourceIcon.getResourceUuid();
+                    long id = resourceManagerUtil.getResourceId(resourceId, resourceType);
+                    Pair<Long, Long> accountDomainPair = getAccountDomain(id, resourceType);
+                    Long domainId = accountDomainPair.second();
+                    Long accountId = accountDomainPair.first();
+                    resourceManagerUtil.checkResourceAccessible(accountId, domainId, String.format("Account ' %s ' doesn't have permissions to upload icon for resource ' %s ", caller, id));
+                    resourceIconDao.remove(resourceIcon.getId());
+                    s_logger.debug("Removed icon for resources (" +
+                            String.join(", ", resourceIds) + ")");
+                }
+            }
+        });
+        return true;
+    }
+
+    @Override
+    public ResourceIcon getByResourceTypeAndUuid(ResourceTag.ResourceObjectType type, String resourceId) {
+        return resourceIconDao.findByResourceUuid(resourceId, type);
+    }
+
+    private List<? extends ResourceIcon> searchResourceIcons(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType) {
+        List<String> resourceUuids = resourceIds.stream().map(resourceId -> resourceManagerUtil.getUuid(resourceId, resourceType)).collect(Collectors.toList());
+        SearchBuilder<ResourceIconVO> sb = resourceIconDao.createSearchBuilder();
+        sb.and("resourceUuid", sb.entity().getResourceUuid(), SearchCriteria.Op.IN);
+        sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ);
+
+        SearchCriteria<ResourceIconVO> sc = sb.create();
+        sc.setParameters("resourceUuid", resourceUuids.toArray());
+        sc.setParameters("resourceType", resourceType);
+        return resourceIconDao.search(sc, null);
+    }
+}
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index 9f419e0..f7cd7de 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -179,6 +179,9 @@ import org.apache.cloudstack.api.command.admin.resource.ListAlertsCmd;
 import org.apache.cloudstack.api.command.admin.resource.ListCapacityCmd;
 import org.apache.cloudstack.api.command.admin.resource.StartRollingMaintenanceCmd;
 import org.apache.cloudstack.api.command.admin.resource.UploadCustomCertificateCmd;
+import org.apache.cloudstack.api.command.admin.resource.icon.DeleteResourceIconCmd;
+import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd;
+import org.apache.cloudstack.api.command.admin.resource.icon.UploadResourceIconCmd;
 import org.apache.cloudstack.api.command.admin.router.ConfigureOvsElementCmd;
 import org.apache.cloudstack.api.command.admin.router.ConfigureVirtualRouterElementCmd;
 import org.apache.cloudstack.api.command.admin.router.CreateVirtualRouterElementCmd;
@@ -3446,6 +3449,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
         cmdList.add(GetRouterHealthCheckResultsCmd.class);
         cmdList.add(StartRollingMaintenanceCmd.class);
         cmdList.add(MigrateSecondaryStorageDataCmd.class);
+        cmdList.add(UploadResourceIconCmd.class);
+        cmdList.add(DeleteResourceIconCmd.class);
+        cmdList.add(ListResourceIconCmd.class);
 
         // Out-of-band management APIs for admins
         cmdList.add(EnableOutOfBandManagementForHostCmd.class);
diff --git a/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java b/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java
new file mode 100644
index 0000000..e088b26
--- /dev/null
+++ b/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java
@@ -0,0 +1,186 @@
+// 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 com.cloud.tags;
+
+import com.cloud.dc.DataCenterVO;
+import com.cloud.domain.DomainVO;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.network.LBHealthCheckPolicyVO;
+import com.cloud.network.as.AutoScaleVmGroupVO;
+import com.cloud.network.as.AutoScaleVmProfileVO;
+
+import com.cloud.network.rules.FirewallRuleVO;
+import com.cloud.network.rules.PortForwardingRuleVO;
+import com.cloud.network.security.SecurityGroupRuleVO;
+import com.cloud.network.security.SecurityGroupVO;
+import com.cloud.network.dao.IPAddressVO;
+import com.cloud.network.dao.LBStickinessPolicyVO;
+import com.cloud.network.dao.LoadBalancerVO;
+import com.cloud.network.dao.NetworkVO;
+import com.cloud.network.dao.RemoteAccessVpnVO;
+import com.cloud.network.dao.Site2SiteCustomerGatewayVO;
+import com.cloud.network.dao.Site2SiteVpnConnectionVO;
+import com.cloud.network.dao.Site2SiteVpnGatewayVO;
+import com.cloud.network.vpc.NetworkACLItemVO;
+import com.cloud.network.vpc.NetworkACLVO;
+import com.cloud.network.vpc.StaticRouteVO;
+import com.cloud.network.vpc.VpcOfferingVO;
+import com.cloud.network.vpc.VpcVO;
+import com.cloud.offerings.NetworkOfferingVO;
+import com.cloud.projects.ProjectVO;
+import com.cloud.server.ResourceManagerUtil;
+import com.cloud.server.ResourceTag;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.VMTemplateVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.DiskOfferingVO;
+import com.cloud.storage.SnapshotPolicyVO;
+
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.user.AccountVO;
+import com.cloud.user.DomainManager;
+import com.cloud.user.UserVO;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.NicVO;
+import com.cloud.vm.UserVmVO;
+import com.cloud.vm.snapshot.VMSnapshotVO;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.commons.lang.StringUtils;
+
+import javax.inject.Inject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class ResourceManagerUtilImpl implements ResourceManagerUtil {
+    public static final Map<ResourceTag.ResourceObjectType, Class<?>> s_typeMap = new HashMap<>();
+
+    static {
+        s_typeMap.put(ResourceTag.ResourceObjectType.UserVm, UserVmVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Volume, VolumeVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Template, VMTemplateVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.ISO, VMTemplateVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Snapshot, SnapshotVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Network, NetworkVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.LoadBalancer, LoadBalancerVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.PortForwardingRule, PortForwardingRuleVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.FirewallRule, FirewallRuleVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.SecurityGroup, SecurityGroupVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.SecurityGroupRule, SecurityGroupRuleVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.PublicIpAddress, IPAddressVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Project, ProjectVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Account, AccountVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Vpc, VpcVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Nic, NicVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.NetworkACL, NetworkACLItemVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.StaticRoute, StaticRouteVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.VMSnapshot, VMSnapshotVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.RemoteAccessVpn, RemoteAccessVpnVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Zone, DataCenterVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.ServiceOffering, ServiceOfferingVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Storage, StoragePoolVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.PrivateGateway, RemoteAccessVpnVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.NetworkACLList, NetworkACLVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.VpnGateway, Site2SiteVpnGatewayVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.CustomerGateway, Site2SiteCustomerGatewayVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.VpnConnection, Site2SiteVpnConnectionVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.User, UserVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.DiskOffering, DiskOfferingVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.AutoScaleVmProfile, AutoScaleVmProfileVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.AutoScaleVmGroup, AutoScaleVmGroupVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.LBStickinessPolicy, LBStickinessPolicyVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.LBHealthCheckPolicy, LBHealthCheckPolicyVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.SnapshotPolicy, SnapshotPolicyVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.NetworkOffering, NetworkOfferingVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.VpcOffering, VpcOfferingVO.class);
+        s_typeMap.put(ResourceTag.ResourceObjectType.Domain, DomainVO.class);
+    }
+
+    @Inject
+    EntityManager entityMgr;
+    @Inject
+    AccountManager accountMgr;
+    @Inject
+    DomainManager domainMgr;
+
+    @Override
+    public long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType) {
+        Class<?> clazz = s_typeMap.get(resourceType);
+        Object entity = entityMgr.findByUuid(clazz, resourceId);
+        if (entity != null) {
+            return ((InternalIdentity)entity).getId();
+        }
+        if (!StringUtils.isNumeric(resourceId)) {
+            throw new InvalidParameterValueException("Unable to find resource by uuid " + resourceId + " and type " + resourceType);
+        }
+        entity = entityMgr.findById(clazz, resourceId);
+        if (entity != null) {
+            return ((InternalIdentity)entity).getId();
+        }
+        throw new InvalidParameterValueException("Unable to find resource by id " + resourceId + " and type " + resourceType);
+    }
+
+    @Override
+    public String getUuid(String resourceId, ResourceTag.ResourceObjectType resourceType) {
+        if (!StringUtils.isNumeric(resourceId)) {
+            return resourceId;
+        }
+
+        Class<?> clazz = s_typeMap.get(resourceType);
+
+        Object entity = entityMgr.findById(clazz, resourceId);
+        if (entity != null && entity instanceof Identity) {
+            return ((Identity)entity).getUuid();
+        }
+
+        return resourceId;
+    }
+
+    @Override
+    public ResourceTag.ResourceObjectType getResourceType(String resourceTypeStr) {
+
+        for (ResourceTag.ResourceObjectType type : ResourceTag.ResourceObjectType.values()) {
+            if (type.toString().equalsIgnoreCase(resourceTypeStr)) {
+                return type;
+            }
+        }
+        throw new InvalidParameterValueException("Invalid resource type: " + resourceTypeStr);
+    }
+
+    public void checkResourceAccessible(Long accountId, Long domainId, String exceptionMessage) {
+        Account caller = CallContext.current().getCallingAccount();
+        if (Objects.equals(domainId, -1))
+        {
+            throw new CloudRuntimeException("Invalid DomainId: -1");
+        }
+        if (accountId != null) {
+            accountMgr.checkAccess(caller, null, false, accountMgr.getAccount(accountId));
+        } else if (domainId != null && !accountMgr.isNormalUser(caller.getId())) {
+            //check permissions;
+            accountMgr.checkAccess(caller, domainMgr.getDomain(domainId));
+        } else {
+            throw new PermissionDeniedException(exceptionMessage);
+        }
+    }
+}
diff --git a/server/src/main/java/com/cloud/tags/TaggedResourceManagerImpl.java b/server/src/main/java/com/cloud/tags/TaggedResourceManagerImpl.java
index 8364185..db6cac0 100644
--- a/server/src/main/java/com/cloud/tags/TaggedResourceManagerImpl.java
+++ b/server/src/main/java/com/cloud/tags/TaggedResourceManagerImpl.java
@@ -17,60 +17,33 @@
 package com.cloud.tags;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 import javax.persistence.EntityExistsException;
 
-import org.apache.cloudstack.api.Identity;
-import org.apache.cloudstack.api.InternalIdentity;
+import com.cloud.server.ResourceManagerUtil;
 import org.apache.cloudstack.context.CallContext;
-import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.commons.collections.MapUtils;
-import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 
-import com.cloud.dc.DataCenterVO;
 import com.cloud.domain.PartOf;
 import com.cloud.event.ActionEvent;
 import com.cloud.event.EventTypes;
 import com.cloud.exception.InvalidParameterValueException;
-import com.cloud.exception.PermissionDeniedException;
-import com.cloud.network.LBHealthCheckPolicyVO;
-import com.cloud.network.as.AutoScaleVmGroupVO;
-import com.cloud.network.as.AutoScaleVmProfileVO;
-import com.cloud.network.dao.IPAddressVO;
-import com.cloud.network.dao.LBStickinessPolicyVO;
-import com.cloud.network.dao.LoadBalancerVO;
-import com.cloud.network.dao.NetworkVO;
-import com.cloud.network.dao.RemoteAccessVpnVO;
-import com.cloud.network.dao.Site2SiteCustomerGatewayVO;
-import com.cloud.network.dao.Site2SiteVpnConnectionVO;
-import com.cloud.network.dao.Site2SiteVpnGatewayVO;
-import com.cloud.network.rules.FirewallRuleVO;
-import com.cloud.network.rules.PortForwardingRuleVO;
 import com.cloud.network.security.SecurityGroupRuleVO;
 import com.cloud.network.security.SecurityGroupVO;
 import com.cloud.network.vpc.NetworkACLItemVO;
 import com.cloud.network.vpc.NetworkACLVO;
-import com.cloud.network.vpc.StaticRouteVO;
-import com.cloud.network.vpc.VpcOfferingVO;
 import com.cloud.network.vpc.VpcVO;
-import com.cloud.offerings.NetworkOfferingVO;
 import com.cloud.projects.ProjectVO;
 import com.cloud.server.ResourceTag;
 import com.cloud.server.ResourceTag.ResourceObjectType;
 import com.cloud.server.TaggedResourceService;
-import com.cloud.service.ServiceOfferingVO;
-import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.SnapshotPolicyVO;
-import com.cloud.storage.SnapshotVO;
-import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.VolumeVO;
 import com.cloud.tags.dao.ResourceTagDao;
 import com.cloud.user.Account;
@@ -78,7 +51,6 @@ import com.cloud.user.AccountManager;
 import com.cloud.user.AccountVO;
 import com.cloud.user.DomainManager;
 import com.cloud.user.OwnedBy;
-import com.cloud.user.UserVO;
 import com.cloud.user.dao.AccountDao;
 import com.cloud.utils.Pair;
 import com.cloud.utils.component.ManagerBase;
@@ -90,54 +62,10 @@ import com.cloud.utils.db.Transaction;
 import com.cloud.utils.db.TransactionCallbackNoReturn;
 import com.cloud.utils.db.TransactionStatus;
 import com.cloud.utils.exception.CloudRuntimeException;
-import com.cloud.vm.NicVO;
-import com.cloud.vm.UserVmVO;
-import com.cloud.vm.snapshot.VMSnapshotVO;
 
 public class TaggedResourceManagerImpl extends ManagerBase implements TaggedResourceService {
     public static final Logger s_logger = Logger.getLogger(TaggedResourceManagerImpl.class);
 
-    private static final Map<ResourceObjectType, Class<?>> s_typeMap = new HashMap<>();
-    static {
-        s_typeMap.put(ResourceObjectType.UserVm, UserVmVO.class);
-        s_typeMap.put(ResourceObjectType.Volume, VolumeVO.class);
-        s_typeMap.put(ResourceObjectType.Template, VMTemplateVO.class);
-        s_typeMap.put(ResourceObjectType.ISO, VMTemplateVO.class);
-        s_typeMap.put(ResourceObjectType.Snapshot, SnapshotVO.class);
-        s_typeMap.put(ResourceObjectType.Network, NetworkVO.class);
-        s_typeMap.put(ResourceObjectType.LoadBalancer, LoadBalancerVO.class);
-        s_typeMap.put(ResourceObjectType.PortForwardingRule, PortForwardingRuleVO.class);
-        s_typeMap.put(ResourceObjectType.FirewallRule, FirewallRuleVO.class);
-        s_typeMap.put(ResourceObjectType.SecurityGroup, SecurityGroupVO.class);
-        s_typeMap.put(ResourceObjectType.SecurityGroupRule, SecurityGroupRuleVO.class);
-        s_typeMap.put(ResourceObjectType.PublicIpAddress, IPAddressVO.class);
-        s_typeMap.put(ResourceObjectType.Project, ProjectVO.class);
-        s_typeMap.put(ResourceObjectType.Account, AccountVO.class);
-        s_typeMap.put(ResourceObjectType.Vpc, VpcVO.class);
-        s_typeMap.put(ResourceObjectType.Nic, NicVO.class);
-        s_typeMap.put(ResourceObjectType.NetworkACL, NetworkACLItemVO.class);
-        s_typeMap.put(ResourceObjectType.StaticRoute, StaticRouteVO.class);
-        s_typeMap.put(ResourceObjectType.VMSnapshot, VMSnapshotVO.class);
-        s_typeMap.put(ResourceObjectType.RemoteAccessVpn, RemoteAccessVpnVO.class);
-        s_typeMap.put(ResourceObjectType.Zone, DataCenterVO.class);
-        s_typeMap.put(ResourceObjectType.ServiceOffering, ServiceOfferingVO.class);
-        s_typeMap.put(ResourceObjectType.Storage, StoragePoolVO.class);
-        s_typeMap.put(ResourceObjectType.PrivateGateway, RemoteAccessVpnVO.class);
-        s_typeMap.put(ResourceObjectType.NetworkACLList, NetworkACLVO.class);
-        s_typeMap.put(ResourceObjectType.VpnGateway, Site2SiteVpnGatewayVO.class);
-        s_typeMap.put(ResourceObjectType.CustomerGateway, Site2SiteCustomerGatewayVO.class);
-        s_typeMap.put(ResourceObjectType.VpnConnection, Site2SiteVpnConnectionVO.class);
-        s_typeMap.put(ResourceObjectType.User, UserVO.class);
-        s_typeMap.put(ResourceObjectType.DiskOffering, DiskOfferingVO.class);
-        s_typeMap.put(ResourceObjectType.AutoScaleVmProfile, AutoScaleVmProfileVO.class);
-        s_typeMap.put(ResourceObjectType.AutoScaleVmGroup, AutoScaleVmGroupVO.class);
-        s_typeMap.put(ResourceObjectType.LBStickinessPolicy, LBStickinessPolicyVO.class);
-        s_typeMap.put(ResourceObjectType.LBHealthCheckPolicy, LBHealthCheckPolicyVO.class);
-        s_typeMap.put(ResourceObjectType.SnapshotPolicy, SnapshotPolicyVO.class);
-        s_typeMap.put(ResourceObjectType.NetworkOffering, NetworkOfferingVO.class);
-        s_typeMap.put(ResourceObjectType.VpcOffering, VpcOfferingVO.class);
-    }
-
     @Inject
     EntityManager _entityMgr;
     @Inject
@@ -148,6 +76,8 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
     DomainManager _domainMgr;
     @Inject
     AccountDao _accountDao;
+    @Inject
+    ResourceManagerUtil resourceManagerUtil;
 
     @Override
     public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
@@ -164,25 +94,8 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
         return true;
     }
 
-    @Override
-    public long getResourceId(String resourceId, ResourceObjectType resourceType) {
-        Class<?> clazz = s_typeMap.get(resourceType);
-        Object entity = _entityMgr.findByUuid(clazz, resourceId);
-        if (entity != null) {
-            return ((InternalIdentity)entity).getId();
-        }
-        if (!StringUtils.isNumeric(resourceId)) {
-            throw new InvalidParameterValueException("Unable to find resource by uuid " + resourceId + " and type " + resourceType);
-        }
-        entity = _entityMgr.findById(clazz, resourceId);
-        if (entity != null) {
-            return ((InternalIdentity)entity).getId();
-        }
-        throw new InvalidParameterValueException("Unable to find resource by id " + resourceId + " and type " + resourceType);
-    }
-
     private Pair<Long, Long> getAccountDomain(long resourceId, ResourceObjectType resourceType) {
-        Class<?> clazz = s_typeMap.get(resourceType);
+        Class<?> clazz = ResourceManagerUtilImpl.s_typeMap.get(resourceType);
 
         Object entity = _entityMgr.findById(clazz, resourceId);
         Long accountId = null;
@@ -191,7 +104,7 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
         // if the resource type is a security group rule, get the accountId and domainId from the security group itself
         if (resourceType == ResourceObjectType.SecurityGroupRule) {
             SecurityGroupRuleVO rule = (SecurityGroupRuleVO)entity;
-            Object SecurityGroup = _entityMgr.findById(s_typeMap.get(ResourceObjectType.SecurityGroup), rule.getSecurityGroupId());
+            Object SecurityGroup = _entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceObjectType.SecurityGroup), rule.getSecurityGroupId());
 
             accountId = ((SecurityGroupVO)SecurityGroup).getAccountId();
             domainId = ((SecurityGroupVO)SecurityGroup).getDomainId();
@@ -206,11 +119,11 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
         // if the resource type is network acl, get the accountId and domainId from VPC following: NetworkACLItem -> NetworkACL -> VPC
         if (resourceType == ResourceObjectType.NetworkACL) {
             NetworkACLItemVO aclItem = (NetworkACLItemVO)entity;
-            Object networkACL = _entityMgr.findById(s_typeMap.get(ResourceObjectType.NetworkACLList), aclItem.getAclId());
+            Object networkACL = _entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceObjectType.NetworkACLList), aclItem.getAclId());
             Long vpcId = ((NetworkACLVO)networkACL).getVpcId();
 
             if (vpcId != null && vpcId != 0) {
-                Object vpc = _entityMgr.findById(s_typeMap.get(ResourceObjectType.Vpc), vpcId);
+                Object vpc = _entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceObjectType.Vpc), vpcId);
 
                 accountId = ((VpcVO)vpc).getAccountId();
                 domainId = ((VpcVO)vpc).getDomainId();
@@ -243,49 +156,6 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
         return new Pair<>(accountId, domainId);
     }
 
-    private void checkResourceAccessible(Long accountId, Long domainId, String exceptionMessage) {
-        Account caller = CallContext.current().getCallingAccount();
-        if (Objects.equals(domainId, -1))
-        {
-            throw new CloudRuntimeException("Invalid DomainId: -1");
-        }
-        if (accountId != null) {
-            _accountMgr.checkAccess(caller, null, false, _accountMgr.getAccount(accountId));
-        } else if (domainId != null && !_accountMgr.isNormalUser(caller.getId())) {
-            //check permissions;
-            _accountMgr.checkAccess(caller, _domainMgr.getDomain(domainId));
-        } else {
-            throw new PermissionDeniedException(exceptionMessage);
-        }
-    }
-
-    @Override
-    public ResourceObjectType getResourceType(String resourceTypeStr) {
-
-        for (ResourceObjectType type : ResourceTag.ResourceObjectType.values()) {
-            if (type.toString().equalsIgnoreCase(resourceTypeStr)) {
-                return type;
-            }
-        }
-        throw new InvalidParameterValueException("Invalid resource type " + resourceTypeStr);
-    }
-
-    @Override
-    public String getUuid(String resourceId, ResourceObjectType resourceType) {
-        if (!StringUtils.isNumeric(resourceId)) {
-            return resourceId;
-        }
-
-        Class<?> clazz = s_typeMap.get(resourceType);
-
-        Object entity = _entityMgr.findById(clazz, resourceId);
-        if (entity != null && entity instanceof Identity) {
-            return ((Identity)entity).getUuid();
-        }
-
-        return resourceId;
-    }
-
     @Override
     @DB
     @ActionEvent(eventType = EventTypes.EVENT_TAGS_CREATE, eventDescription = "creating resource tags")
@@ -303,14 +173,14 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
                             throw new InvalidParameterValueException("The resource type " + resourceType + " doesn't support resource tags");
                         }
 
-                        long id = getResourceId(resourceId, resourceType);
-                        String resourceUuid = getUuid(resourceId, resourceType);
+                        long id = resourceManagerUtil.getResourceId(resourceId, resourceType);
+                        String resourceUuid = resourceManagerUtil.getUuid(resourceId, resourceType);
 
                         Pair<Long, Long> accountDomainPair = getAccountDomain(id, resourceType);
                         Long domainId = accountDomainPair.second();
                         Long accountId = accountDomainPair.first();
 
-                        checkResourceAccessible(accountId, domainId, "Account '" + caller +
+                        resourceManagerUtil.checkResourceAccessible(accountId, domainId, "Account '" + caller +
                                 "' doesn't have permissions to create tags" + " for resource '" + id + "(" + key + ")'.");
 
                         String value = tags.get(key);
@@ -335,7 +205,7 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso
     }
 
     private List<? extends ResourceTag> searchResourceTags(List<String> resourceIds, ResourceObjectType resourceType) {
-        List<String> resourceUuids = resourceIds.stream().map(resourceId -> getUuid(resourceId, resourceType)).collect(Collectors.toList());
+        List<String> resourceUuids = resourceIds.stream().map(resourceId -> resourceManagerUtil.getUuid(resourceId, resourceType)).collect(Collectors.toList());
         SearchBuilder<ResourceTagVO> sb = _resourceTagDao.createSearchBuilder();
         sb.and("resourceUuid", sb.entity().getResourceUuid(), SearchCriteria.Op.IN);
         sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ);
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 207270d..d79908e 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
@@ -323,4 +323,10 @@
         <property name="affinityGroupProcessors"
                   value="#{affinityProcessorsRegistry.registered}" />
     </bean>
+
+    <bean id="resourceManagerUtilImpl"
+          class="com.cloud.tags.ResourceManagerUtilImpl"/>
+
+    <bean id="resourceIconManager" class="com.cloud.resourceicon.ResourceIconManagerImpl" />
+
 </beans>
diff --git a/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java b/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java
index d6d8953..ff5ebb3 100644
--- a/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java
+++ b/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java
@@ -27,6 +27,7 @@ import java.util.Map;
 
 import javax.naming.ConfigurationException;
 
+import com.cloud.server.ResourceManagerUtil;
 import org.apache.commons.collections.map.HashedMap;
 import org.junit.Before;
 import org.mockito.Mock;
@@ -49,6 +50,8 @@ public class ResourceMetaDataManagerTest {
     NicDetailsDao _nicDetailDao;
     @Mock
     TaggedResourceService _taggedResourceMgr;
+    @Mock
+    ResourceManagerUtil resourceManagerUtil;
 
     @Before
     public void setup() {
@@ -70,7 +73,7 @@ public class ResourceMetaDataManagerTest {
     public void testResourceDetails() throws ResourceAllocationException {
 
         //when(_resourceMetaDataMgr.getResourceId(anyString(), eq(ResourceTag.TaggedResourceType.Volume))).thenReturn(1L);
-        doReturn(1L).when(_taggedResourceMgr).getResourceId(anyString(), eq(ResourceTag.ResourceObjectType.Volume));
+        doReturn(1L).when(resourceManagerUtil).getResourceId(anyString(), eq(ResourceTag.ResourceObjectType.Volume));
         //           _volumeDetailDao.removeDetails(id, key);
 
         doNothing().when(_volumeDetailDao).removeDetail(anyLong(), anyString());
@@ -82,7 +85,7 @@ public class ResourceMetaDataManagerTest {
     // Test adding details
     public void testAddResourceDetails() throws ResourceAllocationException {
 
-        doReturn(1L).when(_taggedResourceMgr).getResourceId("1", ResourceTag.ResourceObjectType.Volume);
+        doReturn(1L).when(resourceManagerUtil).getResourceId("1", ResourceTag.ResourceObjectType.Volume);
         //           _volumeDetailDao.removeDetails(id, key);
 
         doNothing().when(_volumeDetailDao).removeDetail(anyLong(), anyString());
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index 7ed3e98..1899602 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -136,6 +136,7 @@ known_categories = {
     'Simulator': 'simulator',
     'StaticRoute': 'VPC',
     'Tags': 'Resource tags',
+    'Icon': 'Resource Icon',
     'NiciraNvpDevice': 'Nicira NVP',
     'BrocadeVcsDevice': 'Brocade VCS',
     'BigSwitchBcfDevice': 'BigSwitch BCF',
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index a2e8cb3..0679a9b 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -556,6 +556,7 @@
 "label.character": "Character",
 "label.chassis": "Chassis",
 "label.checksum": "Checksum",
+"label.choose.resource.icon": "Choose Icon",
 "label.choose.saml.indentity": "Choose SAML identity provider",
 "label.cidr": "CIDR",
 "label.cidr.account": "CIDR or Account/Security Group",
@@ -718,6 +719,7 @@
 "label.delete.events": "Delete events",
 "label.delete.f5": "Delete F5",
 "label.delete.gateway": "Delete gateway",
+"label.delete.icon": "Delete icon",
 "label.delete.instance.group": "Delete Instance Group",
 "label.delete.internal.lb": "Delete Internal LB",
 "label.delete.netscaler": "Delete NetScaler",
@@ -2249,7 +2251,9 @@
 "label.upgrade.router.newer.template": "Upgrade Router to Use Newer Template",
 "label.upload": "Upload",
 "label.upload.from.local": "Upload from Local",
+"label.upload.icon": "Upload Icon",
 "label.upload.iso.from.local": "Upload ISO from Local",
+"label.upload.resource.icon": "Upload Icon",
 "label.upload.template.from.local": "Upload Template from Local",
 "label.upload.volume": "Upload volume",
 "label.upload.volume.from.local": "Upload Volume from Local",
@@ -3248,6 +3252,7 @@
 "message.success.delete": "Delete success",
 "message.success.delete.acl.rule": "Successfully removed ACL rule",
 "message.success.delete.backup.schedule": "Successfully deleted Configure VM backup schedule",
+"message.success.delete.icon": "Successfully deleted icon of",
 "message.success.delete.snapshot.policy": "Successfully deleted snapshot policy",
 "message.success.delete.static.route": "Successfully deleted static route",
 "message.success.delete.tag": "Successfully deleted tag",
@@ -3285,6 +3290,7 @@
 "message.success.upgrade.kubernetes": "Successfully upgraded Kubernetes cluster",
 "message.success.upload": "Upload Successfully",
 "message.success.upload.description": "This ISO file has been uploaded. Please check its status at Templates menu",
+"message.success.upload.icon": "Successfully uploaded icon for ",
 "message.success.upload.iso.description": "This ISO file has been uploaded. Please check its status in the Images > ISOs menu",
 "message.success.upload.template.description": "This template file has been uploaded. Please check its status at Templates menu",
 "message.success.upload.volume.description": "This Volume has been uploaded. Please check its status in the Volumes menu",
@@ -3386,6 +3392,7 @@
 "message.volume.state.uploadinprogress": "Volume upload is in progress",
 "message.volume.state.uploadop": "The volume upload operation is in progress or in short the volume is on secondary storage",
 "message.waiting.for.builtin.templates.to.load": "Waiting for builtin templates to load...",
+"message.warn.filetype": "jpg, jpeg, png, bmp and svg are the only supported image formats",
 "message.xstools61plus.update.failed": "Failed to update Original XS Version is 6.1+ field. Error:",
 "message.you.must.have.at.least.one.physical.network": "You must have at least one physical network",
 "message.your.cloudstack.is.ready": "Your CloudStack is ready!",
diff --git a/ui/src/components/header/ProjectMenu.vue b/ui/src/components/header/ProjectMenu.vue
index dd5e614..3752bb0 100644
--- a/ui/src/components/header/ProjectMenu.vue
+++ b/ui/src/components/header/ProjectMenu.vue
@@ -39,6 +39,8 @@
       </a-tooltip>
 
       <a-select-option v-for="(project, index) in projects" :key="index">
+        <resource-icon v-if="project.icon && project.icon.base64image" :image="project.icon.base64image" size="1x" style="margin-right: 5px"/>
+        <a-icon v-else style="margin-right: 5px" type="project" />
         {{ project.displaytext || project.name }}
       </a-select-option>
     </a-select>
@@ -49,9 +51,13 @@
 import store from '@/store'
 import { api } from '@/api'
 import _ from 'lodash'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'ProjectMenu',
+  components: {
+    ResourceIcon
+  },
   data () {
     return {
       projects: [],
@@ -70,7 +76,7 @@ export default {
       const projects = []
       const getNextPage = () => {
         this.loading = true
-        api('listProjects', { listAll: true, details: 'min', page: page, pageSize: 500 }).then(json => {
+        api('listProjects', { listAll: true, details: 'min', page: page, pageSize: 500, showIcon: true }).then(json => {
           if (json && json.listprojectsresponse && json.listprojectsresponse.project) {
             projects.push(...json.listprojectsresponse.project)
           }
diff --git a/ui/src/components/header/UserMenu.vue b/ui/src/components/header/UserMenu.vue
index 33f4bc3..782fab4 100644
--- a/ui/src/components/header/UserMenu.vue
+++ b/ui/src/components/header/UserMenu.vue
@@ -26,7 +26,10 @@
     </label>
     <a-dropdown>
       <span class="user-menu-dropdown action">
-        <a-avatar class="user-menu-avatar avatar" size="small" :src="avatar()"/>
+        <span v-if="image">
+          <resource-icon :image="image" size="2x" style="margin-right: 5px"/>
+        </span>
+        <a-avatar v-else class="user-menu-avatar avatar" size="small" :src="avatar()"/>
         <span>{{ nickname() }}</span>
       </span>
       <a-menu slot="overlay" class="user-menu-wrapper">
@@ -64,16 +67,36 @@
 
 <script>
 import Vue from 'vue'
+import { api } from '@/api'
 import HeaderNotice from './HeaderNotice'
 import TranslationMenu from './TranslationMenu'
 import { mapActions, mapGetters } from 'vuex'
+import ResourceIcon from '@/components/view/ResourceIcon'
+import eventBus from '@/config/eventBus'
 import { SERVER_MANAGER } from '@/store/mutation-types'
 
 export default {
   name: 'UserMenu',
   components: {
     TranslationMenu,
-    HeaderNotice
+    HeaderNotice,
+    ResourceIcon
+  },
+  data () {
+    return {
+      image: ''
+    }
+  },
+  created () {
+    this.getIcon()
+    eventBus.$on('refresh-header', () => {
+      this.getIcon()
+    })
+  },
+  watch: {
+    image () {
+      this.getIcon()
+    }
   },
   computed: {
     server () {
@@ -86,6 +109,25 @@ export default {
     toggleUseBrowserTimezone () {
       this.$store.dispatch('SetUseBrowserTimezone', !this.$store.getters.usebrowsertimezone)
     },
+    async getIcon () {
+      await this.fetchResourceIcon(this.$store.getters.userInfo.id)
+    },
+    fetchResourceIcon (id) {
+      return new Promise((resolve, reject) => {
+        api('listUsers', {
+          id: id,
+          showicon: true
+        }).then(json => {
+          const response = json.listusersresponse.user || []
+          if (response?.[0]) {
+            this.image = response[0]?.icon?.base64image || ''
+            resolve(this.image)
+          }
+        }).catch(error => {
+          reject(error)
+        })
+      })
+    },
     handleLogout () {
       return this.Logout({}).then(() => {
         this.$router.push('/user/login')
diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue
index 2386076..8ad9839 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -14,7 +14,7 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-
+// $message.success(`${$t('label.copied.clipboard')} : ${name}`)
 <template>
   <a-spin :spinning="loading">
     <a-card class="spin-content" :bordered="bordered" :title="title">
@@ -23,12 +23,21 @@
           <div class="resource-details__name">
             <div
               class="avatar"
-              @click="$message.success(`${$t('label.copied.clipboard')} : ${name}`)"
+              @click="showUploadModal(true)"
               v-clipboard:copy="name" >
+              <upload-resource-icon v-if="'uploadResourceIcon' in $store.getters.apis" :visible="showUpload" :resource="resource" @handle-close="showUpload(false)"/>
+              <div class="ant-upload-preview" v-if="$showIcon()">
+                <a-icon type="camera" class="upload-icon"/>
+              </div>
               <slot name="avatar">
-                <os-logo v-if="resource.ostypeid || resource.ostypename" :osId="resource.ostypeid" :osName="resource.ostypename" size="4x" @update-osname="(name) => resource.ostypename = name"/>
-                <a-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :type="$route.meta.icon" />
-                <a-icon v-else style="font-size: 36px" :component="$route.meta.icon" />
+                <span v-if="(resource.icon && resource.icon.base64image || images.template || images.iso || resourceIcon) && !['router', 'systemvm', 'volume'].includes($route.path.split('/')[1])">
+                  <resource-icon :image="getImage(resource.icon && resource.icon.base64image || images.template || images.iso || resourceIcon)" size="4x" style="margin-right: 5px"/>
+                </span>
+                <span v-else>
+                  <os-logo v-if="resource.ostypeid || resource.ostypename" :osId="resource.ostypeid" :osName="resource.ostypename" size="4x" @update-osname="(name) => resource.ostypename = name"/>
+                  <a-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :type="$route.meta.icon"/>
+                  <a-icon v-else style="font-size: 36px" :component="$route.meta.icon" />
+                </span>
               </slot>
             </div>
             <slot name="name">
@@ -121,7 +130,10 @@
         <div class="resource-detail-item" v-if="resource.ostypename && resource.ostypeid">
           <div class="resource-detail-item__label">{{ $t('label.ostypename') }}</div>
           <div class="resource-detail-item__details">
-            <os-logo :osId="resource.ostypeid" :osName="resource.ostypename" size="lg" style="margin-left: -1px" />
+            <span v-if="resource.icon && resource.icon.base64image || images.template || images.iso">
+              <resource-icon :image="getImage(images.template || images.iso)" size="1x" style="margin-right: 5px"/>
+            </span>
+            <os-logo v-else :osId="resource.ostypeid" :osName="resource.ostypename" size="lg" style="margin-left: -1px" />
             <span style="margin-left: 8px">{{ resource.ostypename }}</span>
           </div>
         </div>
@@ -328,7 +340,10 @@
         <div class="resource-detail-item" v-if="resource.projectid || resource.projectname">
           <div class="resource-detail-item__label">{{ $t('label.project') }}</div>
           <div class="resource-detail-item__details">
-            <a-icon type="project" />
+            <span v-if="images.project">
+              <resource-icon :image="getImage(images.project)" size="1x" style="margin-right: 5px"/>
+            </span>
+            <a-icon v-else type="project" />
             <router-link v-if="!isStatic && resource.projectid" :to="{ path: '/project/' + resource.projectid }">{{ resource.project || resource.projectname || resource.projectid }}</router-link>
             <router-link v-else :to="{ path: '/project', query: { name: resource.projectname }}">{{ resource.projectname }}</router-link>
           </div>
@@ -392,7 +407,10 @@
         <div class="resource-detail-item" v-if="resource.vpcid">
           <div class="resource-detail-item__label">{{ $t('label.vpcname') }}</div>
           <div class="resource-detail-item__details">
-            <a-icon type="deployment-unit" />
+            <span v-if="images.vpc">
+              <resource-icon :image="getImage(images.vpc)" size="1x" style="margin-right: 5px"/>
+            </span>
+            <a-icon v-else type="deployment-unit" />
             <router-link :to="{ path: '/vpc/' + resource.vpcid }">{{ resource.vpcname || resource.vpcid }}</router-link>
           </div>
         </div>
@@ -410,7 +428,8 @@
         <div class="resource-detail-item" v-if="resource.templateid">
           <div class="resource-detail-item__label">{{ resource.isoid ? $t('label.iso') : $t('label.templatename') }}</div>
           <div class="resource-detail-item__details">
-            <a-icon type="picture" />
+            <resource-icon v-if="resource.icon" :image="getImage(resource.icon.base64image)" size="1x" style="margin-right: 5px"/>
+            <a-icon v-else type="picture" />
             <div v-if="resource.isoid">
               <router-link :to="{ path: '/iso/' + resource.isoid }">{{ resource.isodisplaytext || resource.isoname || resource.isoid }} </router-link>
             </div>
@@ -496,7 +515,10 @@
         <div class="resource-detail-item" v-if="resource.zoneid">
           <div class="resource-detail-item__label">{{ $t('label.zone') }}</div>
           <div class="resource-detail-item__details">
-            <a-icon type="global" />
+            <span v-if="images.zone">
+              <resource-icon :image="getImage(images.zone)" size="1x" style="margin-right: 5px"/>
+            </span>
+            <a-icon v-else type="global" />
             <router-link v-if="!isStatic && $router.resolve('/zone/' + resource.zoneid).route.name !== '404'" :to="{ path: '/zone/' + resource.zoneid }">{{ resource.zone || resource.zonename || resource.zoneid }}</router-link>
             <span v-else>{{ resource.zone || resource.zonename || resource.zoneid }}</span>
           </div>
@@ -519,7 +541,10 @@
         <div class="resource-detail-item" v-if="resource.account && !resource.account.startsWith('PrjAcct-')">
           <div class="resource-detail-item__label">{{ $t('label.account') }}</div>
           <div class="resource-detail-item__details">
-            <a-icon type="user" />
+            <span v-if="images.account">
+              <resource-icon :image="getImage(images.account)" size="1x" style="margin-right: 5px"/>
+            </span>
+            <a-icon v-else type="user" />
             <router-link v-if="!isStatic && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/account', query: { name: resource.account, domainid: resource.domainid } }">{{ resource.account }}</router-link>
             <span v-else>{{ resource.account }}</span>
           </div>
@@ -535,7 +560,8 @@
         <div class="resource-detail-item" v-if="resource.domainid">
           <div class="resource-detail-item__label">{{ $t('label.domain') }}</div>
           <div class="resource-detail-item__details">
-            <a-icon type="block" />
+            <resource-icon v-if="images.domain" :image="getImage(images.domain)" size="1x" style="margin-right: 5px"/>
+            <a-icon v-else type="block" />
             <router-link v-if="!isStatic && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/domain/' + resource.domainid + '?tab=details' }">{{ resource.domain || resource.domainid }}</router-link>
             <span v-else>{{ resource.domain || resource.domainid }}</span>
           </div>
@@ -663,6 +689,9 @@ import Console from '@/components/widgets/Console'
 import OsLogo from '@/components/widgets/OsLogo'
 import Status from '@/components/widgets/Status'
 import TooltipButton from '@/components/widgets/TooltipButton'
+import UploadResourceIcon from '@/components/view/UploadResourceIcon'
+import eventBus from '@/config/eventBus'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'InfoCard',
@@ -670,7 +699,9 @@ export default {
     Console,
     OsLogo,
     Status,
-    TooltipButton
+    TooltipButton,
+    UploadResourceIcon,
+    ResourceIcon
   },
   props: {
     resource: {
@@ -703,10 +734,26 @@ export default {
       inputValue: '',
       tags: [],
       showKeys: false,
-      loadingTags: false
+      showNotesInput: false,
+      loadingTags: false,
+      loadingAnnotations: false,
+      showUpload: false,
+      images: {
+        zone: '',
+        template: '',
+        iso: '',
+        domain: '',
+        account: '',
+        project: '',
+        vpc: '',
+        network: ''
+      }
     }
   },
   watch: {
+    $route: function () {
+      this.getIcons()
+    },
     resource: function (newItem, oldItem) {
       this.resource = newItem
       this.resourceType = this.$route.meta.resourceType
@@ -721,10 +768,18 @@ export default {
       if ('apikey' in this.resource) {
         this.getUserKeys()
       }
+      this.getIcons()
+    },
+    async templateIcon () {
+      this.getIcons()
     }
   },
-  created () {
+  async created () {
     this.setData()
+    eventBus.$on('handle-close', (showModal) => {
+      this.showUploadModal(showModal)
+    })
+    await this.getIcons()
   },
   computed: {
     name () {
@@ -737,9 +792,106 @@ export default {
       }
 
       return null
+    },
+    templateIcon () {
+      return this.resource.templateid
+    },
+    resourceIcon () {
+      if (this.$showIcon() && this.resource?.icon?.base64image) {
+        return this.resource.icon.base64image
+      }
+      return null
     }
   },
+  async mounted () {
+    this.getIcons()
+  },
   methods: {
+    showUploadModal (show) {
+      if (show) {
+        if (this.$showIcon()) {
+          this.showUpload = true
+        }
+      } else {
+        this.showUpload = false
+      }
+    },
+    getImage (image) {
+      return (image || this.resource?.icon?.base64image)
+    },
+    async getIcons () {
+      this.images = {
+        zone: '',
+        template: '',
+        iso: '',
+        domain: '',
+        account: '',
+        project: '',
+        vpc: '',
+        network: ''
+      }
+      if (this.resource.templateid) {
+        await this.fetchResourceIcon(this.resource.templateid, 'template')
+      }
+      if (this.resource.isoid) {
+        await this.fetchResourceIcon(this.resource.isoid, 'iso')
+      }
+      if (this.resource.zoneid) {
+        await this.fetchResourceIcon(this.resource.zoneid, 'zone')
+      }
+      if (this.resource.domainid) {
+        await this.fetchResourceIcon(this.resource.domainid, 'domain')
+      }
+      if (this.resource.account) {
+        await this.fetchAccount()
+      }
+      if (this.resource.projectid) {
+        await this.fetchResourceIcon(this.resource.projectid, 'project')
+      }
+      if (this.resource.vpcid) {
+        await this.fetchResourceIcon(this.resource.vpcid, 'vpc')
+      }
+      if (this.resource.networkid) {
+        await this.fetchResourceIcon(this.resource.networkid, 'network')
+      }
+    },
+    fetchAccount () {
+      return new Promise((resolve, reject) => {
+        api('listAccounts', {
+          name: this.resource.account,
+          domainid: this.resource.domainid,
+          showicon: true
+        }).then(async json => {
+          const response = json?.listaccountsresponse?.account || []
+          if (response?.[0]?.icon) {
+            this.images.account = response[0].icon.base64image
+          }
+        })
+      })
+    },
+    fetchResourceIcon (resourceid, type) {
+      if (resourceid) {
+        return new Promise((resolve, reject) => {
+          api('listResourceIcon', {
+            resourceids: resourceid,
+            resourcetype: type
+          }).then(json => {
+            const response = json.listresourceiconresponse.icon || []
+            if (response?.[0]) {
+              this.images[type] = response[0].base64image
+              resolve(this.images)
+            } else {
+              this.images[type] = ''
+              resolve(this.images)
+            }
+          }).catch(error => {
+            reject(error)
+          })
+        })
+      } else {
+        this.images.type = ''
+      }
+    },
     setData () {
       if (this.resource.nic && this.resource.nic.length > 0) {
         this.ipaddress = this.resource.nic.filter(e => { return e.ipaddress }).map(e => { return e.ipaddress }).join(', ')
@@ -950,4 +1102,16 @@ export default {
   }
 
 }
+
+.upload-icon {
+  position: absolute;
+  top: 70px;
+  opacity: 0.75;
+  left: 70px;
+  font-size: 0.75em;
+  padding: 0.25rem;
+  background: rgba(247, 245, 245, 0.767);
+  border-radius: 50%;
+  border: 1px solid rgba(177, 177, 177, 0.788);
+}
 </style>
diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue
index 3195240..7f18e60 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -71,7 +71,15 @@
         <span v-if="$route.path.startsWith('/project')" style="margin-right: 5px">
           <tooltip-button type="dashed" size="small" icon="login" @click="changeProject(record)" />
         </span>
-        <os-logo v-if="record.ostypename" :osName="record.ostypename" size="1x" style="margin-right: 5px" />
+        <span v-if="$showIcon() && !['vm'].includes($route.path.split('/')[1])">
+          <resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="1x" style="margin-right: 5px"/>
+          <os-logo v-else-if="record.ostypename" :osName="record.ostypename" size="1x" style="margin-right: 5px" />
+          <a-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 16px; margin-right: 5px" :type="$route.meta.icon"/>
+          <a-icon v-else style="font-size: 16px; margin-right: 5px" :component="$route.meta.icon" />
+        </span>
+        <span v-else>
+          <os-logo v-if="record.ostypename" :osName="record.ostypename" size="1x" style="margin-right: 5px" />
+        </span>
 
         <span v-if="record.hasannotations">
           <span v-if="record.id">
@@ -99,6 +107,14 @@
       <span v-else>{{ text }}</span>
     </template>
     <a slot="displayname" slot-scope="text, record" href="javascript:;">
+      <span v-if="['vm'].includes($route.path.split('/')[1])">
+        <span v-if="record.icon && record.icon.base64image">
+          <resource-icon :image="record.icon.base64image" size="1x" style="margin-right: 5px"/>
+        </span>
+        <span v-else>
+          <os-logo :osId="record.ostypeid" :osName="record.ostypename" size="lg" style="margin-right: 5px" />
+        </span>
+      </span>
       <QuickView
         style="margin-left: 5px"
         :actions="actions"
@@ -108,6 +124,10 @@
       <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
     </a>
     <span slot="username" slot-scope="text, record" href="javascript:;">
+      <span v-if="$showIcon() && !['vm'].includes($route.path.split('/')[1])">
+        <resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="1x" style="margin-right: 5px"/>
+        <a-icon v-else style="font-size: 16px; margin-right: 5px" type="user" />
+      </span>
       <router-link :to="{ path: $route.path + '/' + record.id }" v-if="['/accountuser', '/vpnuser'].includes($route.path)">{{ text }}</router-link>
       <router-link :to="{ path: '/accountuser', query: { username: record.username, domainid: record.domainid } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
       <span v-else>{{ text }}</span>
@@ -347,6 +367,7 @@ import Status from '@/components/widgets/Status'
 import InfoCard from '@/components/view/InfoCard'
 import QuickView from '@/components/view/QuickView'
 import TooltipButton from '@/components/widgets/TooltipButton'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'ListView',
@@ -356,7 +377,8 @@ export default {
     Status,
     InfoCard,
     QuickView,
-    TooltipButton
+    TooltipButton,
+    ResourceIcon
   },
   props: {
     columns: {
@@ -382,6 +404,7 @@ export default {
       selectedRowKeys: [],
       editableValueKey: null,
       editableValue: '',
+      resourceIcon: '',
       thresholdMapping: {
         cpuused: {
           notification: 'cputhreshold',
diff --git a/ui/src/components/view/ResourceIcon.vue b/ui/src/components/view/ResourceIcon.vue
new file mode 100644
index 0000000..954d9de
--- /dev/null
+++ b/ui/src/components/view/ResourceIcon.vue
@@ -0,0 +1,59 @@
+// 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.
+
+<template>
+  <img :src="getImg()" :height="getDimensions()" :width="getDimensions()" :style="{ marginTop: (getDimensions() === '56px' || ['deployVirtualMachine'].includes(this.$route.path.split('/')[2])) ? '' : '-5px' }"/>
+</template>
+<script>
+export default {
+  name: 'ResourceIcon',
+  props: {
+    image: {
+      type: String,
+      required: true
+    },
+    size: {
+      type: String,
+      default: '4x'
+    }
+  },
+  data () {
+    return {}
+  },
+  methods: {
+    getImg () {
+      if (this.image.startsWith('data:image/png')) {
+        return this.image
+      } else {
+        return 'data:image/png;charset=utf-8;base64, ' + this.image
+      }
+    },
+    getDimensions () {
+      switch (this.size) {
+        case '4x':
+          return '56px'
+        case '2x':
+          return '24px'
+        case '1x':
+          return '16px'
+        default:
+          return '16px'
+      }
+    }
+  }
+}
+</script>
diff --git a/ui/src/components/view/SearchView.vue b/ui/src/components/view/SearchView.vue
index 86ffe88..ad6a48b 100644
--- a/ui/src/components/view/SearchView.vue
+++ b/ui/src/components/view/SearchView.vue
@@ -67,7 +67,21 @@
                   <a-select-option
                     v-for="(opt, idx) in field.opts"
                     :key="idx"
-                    :value="opt.id">{{ $t(opt.name) }}</a-select-option>
+                    :value="opt.id">
+                    <span v-if="(field.name.startsWith('zone'))">
+                      <span v-if="opt.icon">
+                        <resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                      </span>
+                      <a-icon v-else type="global" style="margin-right: 5px" />
+                    </span>
+                    <span v-if="(field.name.startsWith('domain'))">
+                      <span v-if="opt.icon">
+                        <resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                      </span>
+                      <a-icon v-else type="block" style="margin-right: 5px" />
+                    </span>
+                    {{ $t(opt.name) }}
+                  </a-select-option>
                 </a-select>
                 <a-input
                   v-else-if="field.type==='input'"
@@ -124,11 +138,13 @@
 <script>
 import { api } from '@/api'
 import TooltipButton from '@/components/widgets/TooltipButton'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'SearchView',
   components: {
-    TooltipButton
+    TooltipButton,
+    ResourceIcon
   },
   props: {
     searchFilters: {
@@ -341,7 +357,7 @@ export default {
     },
     fetchZones () {
       return new Promise((resolve, reject) => {
-        api('listZones', { listAll: true }).then(json => {
+        api('listZones', { listAll: true, showicon: true }).then(json => {
           const zones = json.listzonesresponse.zone
           resolve({
             type: 'zoneid',
@@ -354,7 +370,7 @@ export default {
     },
     fetchDomains () {
       return new Promise((resolve, reject) => {
-        api('listDomains', { listAll: true }).then(json => {
+        api('listDomains', { listAll: true, showicon: true }).then(json => {
           const domain = json.listdomainsresponse.domain
           resolve({
             type: 'domainid',
diff --git a/ui/src/components/view/TreeView.vue b/ui/src/components/view/TreeView.vue
index feb0ad6..9bf1908 100644
--- a/ui/src/components/view/TreeView.vue
+++ b/ui/src/components/view/TreeView.vue
@@ -82,6 +82,7 @@ import { api } from '@/api'
 import DetailsTab from '@/components/view/DetailsTab'
 import ResourceView from '@/components/view/ResourceView'
 import ResourceLayout from '@/layouts/ResourceLayout'
+import eventBus from '@/config/eventBus'
 
 export default {
   name: 'TreeView',
@@ -153,6 +154,9 @@ export default {
     this.metaName = this.$route.meta.name
     this.apiList = this.$route.meta.permission[0] ? this.$route.meta.permission[0] : ''
     this.apiChildren = this.$route.meta.permission[1] ? this.$route.meta.permission[1] : ''
+    eventBus.$on('refresh-domain-icon', () => {
+      this.getDetailResource(this.selectedTreeKey)
+    })
   },
   watch: {
     loading () {
@@ -225,7 +229,8 @@ export default {
 
       const params = {
         listAll: true,
-        id: treeNode.eventKey
+        id: treeNode.eventKey,
+        showicon: true
       }
 
       return new Promise(resolve => {
@@ -341,7 +346,6 @@ export default {
       this.treeViewData = []
       this.loadingSearch = true
       this.$emit('change-tree-store', {})
-
       api(this.apiList, params).then(json => {
         const listDomains = this.getResponseJsonData(json)
         this.treeVerticalData = this.treeVerticalData.concat(listDomains)
@@ -393,11 +397,11 @@ export default {
       // set id to parameter
       params.id = selectedKey
       params.listAll = true
+      params.showicon = true
       params.page = 1
       params.pageSize = 1
 
       this.detailLoading = true
-
       api(apiName, params).then(json => {
         const jsonResponse = this.getResponseJsonData(json)
 
diff --git a/ui/src/components/view/UploadResourceIcon.vue b/ui/src/components/view/UploadResourceIcon.vue
new file mode 100644
index 0000000..ecf6d66
--- /dev/null
+++ b/ui/src/components/view/UploadResourceIcon.vue
@@ -0,0 +1,314 @@
+// 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.
+
+<template>
+  <div>
+    <a-modal
+      :title="$t('label.upload.resource.icon')"
+      :visible="visible"
+      :maskClosable="true"
+      :confirmLoading="confirmLoading"
+      :width="800"
+      :footer="null"
+      @cancel="handleClose">
+      <a-row>
+        <a-col :xs="24" :md="12" :style="{height: '350px'}">
+          <vue-cropper
+            ref="cropper"
+            :img="options.img"
+            :outputSize="1"
+            outputType="png"
+            :info="true"
+            :autoCrop="options.autoCrop"
+            :autoCropWidth="options.autoCropWidth"
+            :autoCropHeight="options.autoCropHeight"
+            :fixedBox="options.fixedBox"
+            @realTime="realTime"
+          >
+          </vue-cropper>
+        </a-col>
+        <a-col :xs="24" :md="12" :style="{height: '350px'}">
+          <div class="avatar-upload-preview">
+            <span v-if="previews.url">
+              <img :src="previews.url" :style="previews.img"/>
+            </span>
+            <span v-else-if="defaultImage">
+              <img :src="'data:image/png;charset=utf-8;base64, ' + defaultImage" style="height: 52px;width: 52px;marginLeft: 65px;marginTop: 55px"/>
+            </span>
+          </div>
+        </a-col>
+      </a-row>
+      <br>
+      <a-row>
+        <a-col :xs="2" :md="2">
+          <a-upload name="file" :beforeUpload="beforeUpload" :showUploadList="false">
+            <a-button><a-icon type="upload" />{{ $t('label.choose.resource.icon') }} </a-button>
+          </a-upload>
+        </a-col>
+        <a-col :xs="{span: 2, offset: 4}" :md="1">
+          <a-button icon="plus" @click="changeScale(5)"/>
+        </a-col>
+        <a-col :xs="{span: 1, offset: 0}" :md="2">
+          <a-button icon="minus" @click="changeScale(-5)"/>
+        </a-col>
+        <a-col :lg="{span: 1, offset: 0}" :md="2">
+          <a-button icon="undo" @click="rotateLeft"/>
+        </a-col>
+        <a-col :lg="{span: 1, offset: 0}" :md="2">
+          <a-button icon="redo" @click="rotateRight"/>
+        </a-col>
+        <a-col :xs="{span: 1, offset: 3}" :md="1">
+          <a-button type="primary" @click="uploadIcon('blob')"> {{ $t('label.upload') }} </a-button>
+        </a-col>
+        <a-col :xs="{span: 2, offset: 5}" :md="2">
+          <a-button v-if="resource.icon && resource.icon.resourcetype.toLowerCase() === $getResourceType().toLowerCase()" type="danger" @click="deleteIcon('blob')"> {{ $t('label.delete') }} </a-button>
+        </a-col>
+      </a-row>
+    </a-modal>
+    <a-modal
+      :visible="showAlert"
+      :footer="null"
+      style="top: 20px;"
+      centered
+      width="auto"
+      :maskClosable="false">
+      <span slot="title">
+        {{ $t('label.warning') }}
+      </span>
+      <a-alert type="warning">
+        <span slot="message" v-html="$t('message.warn.filetype')" />
+      </a-alert>
+      <a-divider style="margin-top: 0;"></a-divider>
+      <div :span="24" class="action-button">
+        <a-button key="submit" type="primary" @click="handleOk" style="textAlign: right">
+          {{ $t('label.ok') }}
+        </a-button>
+      </div>
+    </a-modal>
+  </div>
+</template>
+<script>
+import { api } from '@/api'
+import eventBus from '@/config/eventBus'
+
+export default {
+  name: 'UploadResourceIcon',
+  props: {
+    visible: {
+      type: Boolean,
+      required: false,
+      default: false
+    },
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  watch: {
+    resource: function (oldVal, newVal) {
+      if (oldVal === newVal) return
+      this.defaultImage = this.resource?.icon?.base64image || ''
+    },
+    preview: function (data) {
+      this.realTime(data)
+      return this.previews
+    }
+  },
+  data () {
+    return {
+      id: null,
+      confirmLoading: false,
+      fileList: [],
+      uploading: false,
+      options: {
+        img: '',
+        autoCrop: true,
+        autoCropWidth: 200,
+        autoCropHeight: 200,
+        fixedBox: true
+      },
+      previews: {},
+      defaultImage: '',
+      showAlert: false,
+      croppedImage: ''
+    }
+  },
+  methods: {
+    handleClose () {
+      this.options.img = ''
+      this.previews = {}
+      eventBus.$emit('handle-close')
+    },
+    realTime (data) {
+      if (data && data.url) {
+        this.previews = data
+      } else {
+        if (this.resource?.icon?.base64image) {
+          this.previews.url = 'data:image/png;charset=utf-8;base64, ' + (this.defaultImage || this.resource.icon.base64image || '')
+          this.previews.img = {
+            height: '52px',
+            width: '52px',
+            marginLeft: '65px',
+            marginTop: '55px'
+          }
+        } else {
+          this.previews = {}
+        }
+      }
+    },
+    handleOk () {
+      this.showAlert = false
+      this.options.img = ''
+    },
+    beforeUpload (file) {
+      if (!/\.(svg|jpg|jpeg|png|bmp|SVG|JPG|PNG)$/.test(file.name)) {
+        this.showAlert = true
+      }
+      const reader = new FileReader()
+      reader.readAsDataURL(file)
+      reader.onload = () => {
+        this.options.img = reader.result
+      }
+      return false
+    },
+    changeScale (num) {
+      num = num || 1
+      this.$refs.cropper.changeScale(num)
+    },
+    rotateLeft () {
+      this.$refs.cropper.rotateLeft()
+    },
+    rotateRight () {
+      this.$refs.cropper.rotateRight()
+    },
+    getResourceIcon () {
+      return new Promise((resolve, reject) => {
+        this.$refs.cropper.getCropData((data) => {
+          resolve(data)
+          return data
+        })
+      })
+    },
+    getNewImage (img) {
+      return new Promise((resolve, reject) => {
+        img.onload = function () {
+          var canvas = document.createElement('canvas')
+          var ctx = canvas.getContext('2d')
+          ctx.imageSmoothingQuality = 'high'
+          canvas.height = 52
+          canvas.width = 52
+          ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
+          var base64Canvas = canvas.toDataURL('image/png', 1).split(';base64,')[1]
+          resolve(base64Canvas)
+          return base64Canvas
+        }
+      })
+    },
+    async uploadIcon () {
+      var base64Canvas = ''
+      const resourceType = this.$getResourceType()
+      const resourceid = this.resource.id
+      if (this.options.img) {
+        var newImage = new Image()
+        newImage.src = await this.getResourceIcon()
+        base64Canvas = await this.getNewImage(newImage)
+      }
+      api('uploadResourceIcon', {}, 'POST', {
+        resourceids: resourceid,
+        resourcetype: resourceType,
+        base64image: base64Canvas
+      }).then(json => {
+        console.log(this.resource)
+        if (json?.uploadresourceiconresponse?.success) {
+          this.$notification.success({
+            message: this.$t('label.upload.icon'),
+            description: `${this.$t('message.success.upload.icon')} ${resourceType}: ` + (this.resource.name || this.resource.username || resourceid)
+          })
+        }
+      }).catch((error) => {
+        this.$notification.error({
+          message: this.$t('label.upload.icon'),
+          description: error?.response?.data?.uploadresourceiconresponse?.errortext || '',
+          duration: 0
+        })
+      }).finally(() => {
+        this.handleClose()
+        eventBus.$emit('refresh-icon')
+        if (['user', 'account'].includes(resourceType.toLowerCase())) {
+          eventBus.$emit('refresh-header')
+        }
+        if (['domain'].includes(this.$route.path.split('/')[1])) {
+          eventBus.$emit('refresh-domain-icon')
+        }
+      })
+    },
+    deleteIcon () {
+      const resourceType = this.$getResourceType()
+      const resourceid = this.resource.id
+      api('deleteResourceIcon', {
+        resourcetype: resourceType,
+        resourceids: resourceid
+      }).then(json => {
+        if (json?.deleteresourceiconresponse?.success) {
+          this.$notification.success({
+            message: this.$t('label.delete.icon'),
+            description: `${this.$t('message.success.delete.icon')} ${resourceType}: ` + (this.resource.name || this.resource.username || resourceid)
+          })
+        }
+      }).catch((error) => {
+        this.$notification.error({
+          message: this.$t('label.delete.icon'),
+          description: error?.response?.data?.deleteresourceiconresponse?.errortext || '',
+          duration: 0
+        })
+      }).finally(() => {
+        this.handleClose()
+        eventBus.$emit('refresh-icon')
+        if (['user', 'account'].includes(resourceType.toLowerCase())) {
+          eventBus.$emit('refresh-header')
+        }
+        if (['domain'].includes(this.$route.path.split('/')[1])) {
+          eventBus.$emit('refresh-domain-icon')
+        }
+      })
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+  .avatar-upload-preview {
+    position: absolute;
+    top: 50%;
+    transform: translate(50%, -50%);
+    width: 200px;
+    height: 200px;
+    box-shadow: 0 0 4px #ccc;
+    overflow: hidden;
+    img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+  .action-button {
+    text-align: right;
+    margin-top: 20px;
+
+    button {
+      margin-right: 5px;
+    }
+  }
+</style>
diff --git a/ui/src/core/lazy_lib/components_use.js b/ui/src/core/lazy_lib/components_use.js
index 50e2d96..a9adc5d 100644
--- a/ui/src/core/lazy_lib/components_use.js
+++ b/ui/src/core/lazy_lib/components_use.js
@@ -68,6 +68,7 @@ import {
   AutoComplete,
   Collapse
 } from 'ant-design-vue'
+import VueCropper from 'vue-cropper'
 
 Vue.use(ConfigProvider)
 Vue.use(Layout)
@@ -117,6 +118,7 @@ Vue.use(Calendar)
 Vue.use(Slider)
 Vue.use(AutoComplete)
 Vue.use(Collapse)
+Vue.use(VueCropper)
 
 Vue.prototype.$confirm = Modal.confirm
 Vue.prototype.$message = message
diff --git a/ui/src/main.js b/ui/src/main.js
index adc2022..0635b99 100644
--- a/ui/src/main.js
+++ b/ui/src/main.js
@@ -26,7 +26,7 @@ import './core/lazy_use'
 import './core/ext'
 import './permission' // permission control
 import './utils/filter' // global filter
-import { pollJobPlugin, notifierPlugin, toLocaleDatePlugin, configUtilPlugin, apiMetaUtilPlugin } from './utils/plugins'
+import { pollJobPlugin, notifierPlugin, toLocaleDatePlugin, configUtilPlugin, apiMetaUtilPlugin, showIconPlugin, resourceTypePlugin } from './utils/plugins'
 import { VueAxios } from './utils/request'
 import './utils/directives'
 
@@ -35,6 +35,8 @@ Vue.use(VueAxios, router)
 Vue.use(pollJobPlugin)
 Vue.use(notifierPlugin)
 Vue.use(toLocaleDatePlugin)
+Vue.use(showIconPlugin)
+Vue.use(resourceTypePlugin)
 
 fetch('config.json').then(response => response.json()).then(config => {
   Vue.prototype.$config = config
diff --git a/ui/src/utils/plugins.js b/ui/src/utils/plugins.js
index 8d19615..99db8f7 100644
--- a/ui/src/utils/plugins.js
+++ b/ui/src/utils/plugins.js
@@ -250,6 +250,39 @@ export const configUtilPlugin = {
   }
 }
 
+export const showIconPlugin = {
+  install (Vue) {
+    Vue.prototype.$showIcon = function (resource) {
+      var resourceType = this.$route.path.split('/')[1]
+      if (resource) {
+        resourceType = resource
+      }
+      if (['zone', 'template', 'iso', 'account', 'accountuser', 'vm', 'domain', 'project', 'vpc', 'guestnetwork'].includes(resourceType)) {
+        return true
+      } else {
+        return false
+      }
+    }
+  }
+}
+
+export const resourceTypePlugin = {
+  install (Vue) {
+    Vue.prototype.$getResourceType = function () {
+      const type = this.$route.path.split('/')[1]
+      if (type === 'vm') {
+        return 'UserVM'
+      } else if (type === 'accountuser') {
+        return 'User'
+      } else if (type === 'guestnetwork') {
+        return 'Network'
+      } else {
+        return type
+      }
+    }
+  }
+}
+
 export const apiMetaUtilPlugin = {
   install (Vue) {
     Vue.prototype.$getApiParams = function () {
diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue
index 31fc4da..c924486 100644
--- a/ui/src/views/AutogenView.vue
+++ b/ui/src/views/AutogenView.vue
@@ -271,6 +271,44 @@
                 >
                   <a-select-option key="">{{ }}</a-select-option>
                   <a-select-option v-for="opt in field.opts" :key="opt.id">
+                    <span>
+                      <span v-if="(field.name.startsWith('template') || field.name.startsWith('iso'))">
+                        <span v-if="opt.icon">
+                          <resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                        </span>
+                        <os-logo v-else :osId="opt.ostypeid" :osName="opt.ostypename" size="lg" style="margin-left: -1px" />
+                      </span>
+                      <span v-if="(field.name.startsWith('zone'))">
+                        <span v-if="opt.icon">
+                          <resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                        </span>
+                        <a-icon v-else type="global" style="margin-right: 5px" />
+                      </span>
+                      <span v-if="(field.name.startsWith('project'))">
+                        <span v-if="opt.icon">
+                          <resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                        </span>
+                        <a-icon v-else type="project" style="margin-right: 5px" />
+                      </span>
+                      <span v-if="(field.name.startsWith('account') || field.name.startsWith('user'))">
+                        <span v-if="opt.icon">
+                          <resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                        </span>
+                        <a-icon v-else type="user" style="margin-right: 5px"/>
+                      </span>
+                      <span v-if="(field.name.startsWith('network'))">
+                        <span v-if="opt.icon">
+                          <resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                        </span>
+                        <a-icon v-else type="apartment" style="margin-right: 5px"/>
+                      </span>
+                      <span v-if="(field.name.startsWith('domain'))">
+                        <span v-if="opt.icon">
+                          <resource-icon :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                        </span>
+                        <a-icon v-else type="block" style="margin-right: 5px"/>
+                      </span>
+                    </span>
                     {{ opt.name || opt.description || opt.traffictype || opt.publicip }}
                   </a-select-option>
                 </a-select>
@@ -411,6 +449,8 @@ import ListView from '@/components/view/ListView'
 import ResourceView from '@/components/view/ResourceView'
 import ActionButton from '@/components/view/ActionButton'
 import SearchView from '@/components/view/SearchView'
+import OsLogo from '@/components/widgets/OsLogo'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import BulkActionProgress from '@/components/view/BulkActionProgress'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
@@ -425,7 +465,9 @@ export default {
     ActionButton,
     SearchView,
     BulkActionProgress,
-    TooltipLabel
+    TooltipLabel,
+    OsLogo,
+    ResourceIcon
   },
   mixins: [mixinDevice],
   provide: function () {
@@ -493,6 +535,11 @@ export default {
         this.fetchData()
       }
     })
+    eventBus.$on('refresh-icon', () => {
+      if (this.$showIcon()) {
+        this.fetchData()
+      }
+    })
     eventBus.$on('async-job-complete', (action) => {
       if (this.$route.path.includes('/vm/')) {
         if (action && 'api' in action && ['destroyVirtualMachine'].includes(action.api)) {
@@ -785,7 +832,11 @@ export default {
 
       params.page = this.page
       params.pagesize = this.pageSize
+      this.searchParams = params
 
+      if (this.$showIcon()) {
+        params.showIcon = true
+      }
       api(this.apiName, params).then(json => {
         var responseName
         var objectName
@@ -986,6 +1037,10 @@ export default {
       var extractedParamName = paramName.replace('ids', '').replace('id', '').toLowerCase()
       var params = { listall: true }
       const possibleName = 'list' + extractedParamName + 's'
+      var showIcon = false
+      if (this.$showIcon(extractedParamName)) {
+        showIcon = true
+      }
       var possibleApi
       if (this.currentAction.mapping && param.name in this.currentAction.mapping && this.currentAction.mapping[param.name].api) {
         possibleApi = this.currentAction.mapping[param.name].api
@@ -1017,6 +1072,9 @@ export default {
       } else if (possibleApi === 'listHosts') {
         params.type = 'routing'
       }
+      if (showIcon) {
+        params.showicon = true
+      }
       api(possibleApi, params).then(json => {
         param.loading = false
         for (const obj in json) {
diff --git a/ui/src/views/compute/AssignInstance.vue b/ui/src/views/compute/AssignInstance.vue
index 085c53f..a486974 100644
--- a/ui/src/views/compute/AssignInstance.vue
+++ b/ui/src/views/compute/AssignInstance.vue
@@ -55,6 +55,8 @@
             return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option v-for="domain in domains" :key="domain.name" :value="domain.id">
+            <resource-icon v-if="domain && domain.icon" :image="domain.icon.base64image" size="1x" style="margin-right: 5px"/>
+            <a-icon v-else type="block" style="margin-right: 5px" />
             {{ domain.path || domain.name || domain.description }}
           </a-select-option>
         </a-select>
@@ -72,6 +74,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="account in accounts" :key="account.name" :value="account.name">
+              <resource-icon v-if="account && account.icon" :image="account.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="team" style="margin-right: 5px" />
               {{ account.name }}
             </a-select-option>
           </a-select>
@@ -91,6 +95,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="project in projects" :key="project.id" :value="project.id">
+              <resource-icon v-if="project && project.icon" :image="project.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="project" style="margin-right: 5px" />
               {{ project.name }}
             </a-select-option>
           </a-select>
@@ -108,6 +114,8 @@
             return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option v-for="network in networks" :key="network.id" :value="network.id">
+            <resource-icon v-if="network && network.icon" :image="network.icon.base64image" size="1x" style="margin-right: 5px"/>
+            <a-icon v-else type="apartment" style="margin-right: 5px" />
             {{ network.name ? network.name : '-' }}
           </a-select-option>
         </a-select>
@@ -129,6 +137,7 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'AssignInstance',
@@ -138,6 +147,9 @@ export default {
       required: true
     }
   },
+  components: {
+    ResourceIcon
+  },
   inject: ['parentFetchData'],
   data () {
     return {
@@ -164,6 +176,7 @@ export default {
       api('listDomains', {
         response: 'json',
         listAll: true,
+        showicon: true,
         details: 'min'
       }).then(response => {
         this.domains = response.listdomainsresponse.domain
@@ -181,6 +194,7 @@ export default {
       api('listAccounts', {
         response: 'json',
         domainId: this.selectedDomain,
+        showicon: true,
         state: 'Enabled',
         isrecursive: false
       }).then(response => {
@@ -197,6 +211,7 @@ export default {
         response: 'json',
         domainId: this.selectedDomain,
         state: 'Active',
+        showicon: true,
         details: 'min',
         isrecursive: false
       }).then(response => {
@@ -214,6 +229,7 @@ export default {
         domainId: this.selectedDomain,
         listAll: true,
         isrecursive: false,
+        showicon: true,
         account: this.selectedAccount,
         projectid: this.selectedProject
       }).then(response => {
diff --git a/ui/src/views/compute/CreateKubernetesCluster.vue b/ui/src/views/compute/CreateKubernetesCluster.vue
index 465d3a9..0749da1 100644
--- a/ui/src/views/compute/CreateKubernetesCluster.vue
+++ b/ui/src/views/compute/CreateKubernetesCluster.vue
@@ -55,6 +55,8 @@
             :placeholder="apiParams.zoneid.description"
             @change="val => { this.handleZoneChange(this.zones[val]) }">
             <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
+              <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px" />
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -242,12 +244,14 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'CreateKubernetesCluster',
   components: {
-    TooltipLabel
+    TooltipLabel,
+    ResourceIcon
   },
   props: {},
   data () {
@@ -306,6 +310,7 @@ export default {
     fetchZoneData () {
       const params = {}
       this.zoneLoading = true
+      params.showicon = true
       api('listZones', params).then(json => {
         const listZones = json.listzonesresponse.zone
         this.zones = this.zones.concat(listZones)
diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue
index 0d1512e..d7db275 100644
--- a/ui/src/views/compute/DeployVM.vue
+++ b/ui/src/views/compute/DeployVM.vue
@@ -32,18 +32,53 @@
                   <div style="margin-top: 15px">
                     <span>{{ $t('message.select.a.zone') }}</span><br/>
                     <a-form-item :label="this.$t('label.zoneid')">
+                      <div v-if="zones.length <= 8">
+                        <a-row type="flex" :gutter="5" justify="start">
+                          <div v-for="(zoneItem, idx) in zones" :key="idx">
+                            <a-radio-group
+                              :key="idx"
+                              v-decorator="['zoneid', {
+                                initialValue: selectedZone,
+                                rules: [{ required: true, message: `${$t('message.error.select')}` }]}]"
+                              @change="onSelectZoneId(zoneItem.id)">
+                              <a-col :span="8">
+                                <a-card-grid style="width:200px;" :title="zoneItem.name" :hoverable="false">
+                                  <a-radio :value="zoneItem.id">
+                                    <div>
+                                      <img
+                                        v-if="zoneItem && zoneItem.icon && zoneItem.icon.base64image"
+                                        :src="getImg(zoneItem.icon.base64image)"
+                                        style="marginTop: -30px; marginLeft: 60px"
+                                        width="36px"
+                                        height="36px" />
+                                      <a-icon v-else :style="{fontSize: '36px', marginLeft: '60px', marginTop: '-40px'}" type="global"/>
+                                    </div>
+                                  </a-radio>
+                                  <a-card-meta title="" :description="zoneItem.name" style="text-align:center; paddingTop: 10px;" />
+                                </a-card-grid>
+                              </a-col>
+                            </a-radio-group>
+                          </div>
+                        </a-row>
+                      </div>
                       <a-select
+                        v-else
                         v-decorator="['zoneid', {
-                          rules: [{ required: true, message: `${this.$t('message.error.select')}` }]
+                          rules: [{ required: true, message: `${$t('message.error.select')}` }]
                         }]"
                         showSearch
                         optionFilterProp="children"
                         :filterOption="filterOption"
-                        :options="zoneSelectOptions"
                         @change="onSelectZoneId"
                         :loading="loading.zones"
                         autoFocus
-                      ></a-select>
+                      >
+                        <a-select-option v-for="zone1 in zones" :key="zone1.id">
+                          <resource-icon v-if="zone1.icon && zone1.icon.base64image" :image="zone1.icon.base64image" size="1x" style="margin-right: 5px"/>
+                          <a-icon v-else style="margin-right: 5px" type="global" />
+                          {{ zone1.name }}
+                        </a-select-option>
+                      </a-select>
                     </a-form-item>
                     <a-form-item
                       v-if="!isNormalAndDomainUser"
@@ -657,6 +692,7 @@ import store from '@/store'
 import eventBus from '@/config/eventBus'
 
 import InfoCard from '@/components/view/InfoCard'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import ComputeOfferingSelection from '@views/compute/wizard/ComputeOfferingSelection'
 import ComputeSelection from '@views/compute/wizard/ComputeSelection'
 import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection'
@@ -685,6 +721,7 @@ export default {
     ComputeOfferingSelection,
     ComputeSelection,
     SecurityGroupSelection,
+    ResourceIcon,
     TooltipLabel
   },
   props: {
@@ -813,7 +850,9 @@ export default {
       diskIOpsMin: 0,
       diskIOpsMax: 0,
       minIops: 0,
-      maxIops: 0
+      maxIops: 0,
+      zones: [],
+      selectedZone: ''
     }
   },
   computed: {
@@ -898,7 +937,8 @@ export default {
             account: store.getters.project && store.getters.project.id ? null : store.getters.userInfo.account,
             page: 1,
             pageSize: 10,
-            keyword: undefined
+            keyword: undefined,
+            showIcon: true
           }
         },
         pods: {
@@ -1102,6 +1142,7 @@ export default {
       }
 
       if (this.iso) {
+        this.vm.isoid = this.iso.id
         this.vm.templateid = this.iso.id
         this.vm.templatename = this.iso.displaytext
         this.vm.ostypeid = this.iso.ostypeid
@@ -1226,6 +1267,7 @@ export default {
       this.form.getFieldDecorator([field], { initialValue: this.dataPreFill[field] })
     },
     fetchData () {
+      this.fetchZones()
       if (this.dataPreFill.zoneid) {
         this.fetchDataByZone(this.dataPreFill.zoneid)
       } else {
@@ -1247,6 +1289,9 @@ export default {
     isDynamicallyScalable () {
       return this.serviceOffering && this.serviceOffering.dynamicscalingenabled && this.template && this.template.isdynamicallyscalable && this.dynamicScalingVmConfigValue
     },
+    getImg (image) {
+      return 'data:image/png;charset=utf-8;base64, ' + image
+    },
     async fetchDataByZone (zoneId) {
       this.fillValue('zoneid')
       this.options.zones = await this.fetchZones()
@@ -1677,9 +1722,9 @@ export default {
       return new Promise((resolve) => {
         this.loading.zones = true
         const param = this.params.zones
-        api(param.list, { listall: true }).then(json => {
-          const zones = json.listzonesresponse.zone || []
-          resolve(zones)
+        api(param.list, { listall: true, showicon: true }).then(json => {
+          this.zones = json.listzonesresponse.zone || []
+          resolve(this.zones)
         }).catch(function (error) {
           console.log(error.stack)
         }).finally(() => {
@@ -1759,6 +1804,7 @@ export default {
       args.zoneid = _.get(this.zone, 'id')
       args.templatefilter = templateFilter
       args.details = 'all'
+      args.showicon = 'true'
 
       return new Promise((resolve, reject) => {
         api('listTemplates', args).then((response) => {
@@ -1778,6 +1824,7 @@ export default {
       args.zoneid = _.get(this.zone, 'id')
       args.isoFilter = isoFilter
       args.bootable = true
+      args.showicon = 'true'
 
       return new Promise((resolve, reject) => {
         api('listIsos', args).then((response) => {
@@ -1842,7 +1889,9 @@ export default {
       this.clusterId = null
       this.zone = _.find(this.options.zones, (option) => option.id === value)
       this.zoneSelected = true
+      this.selectedZone = this.zoneId
       this.form.setFieldsValue({
+        zoneid: this.zoneId,
         clusterid: undefined,
         podid: undefined,
         hostid: undefined,
diff --git a/ui/src/views/compute/InstanceTab.vue b/ui/src/views/compute/InstanceTab.vue
index 8f6ff62..b3b3f0c 100644
--- a/ui/src/views/compute/InstanceTab.vue
+++ b/ui/src/views/compute/InstanceTab.vue
@@ -171,6 +171,8 @@
               v-for="network in addNetworkData.allNetworks"
               :key="network.id"
               :value="network.id">
+              <resource-icon v-if="network.icon" :image="network.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="apartment" style="margin-right: 5px" />
               {{ network.name }}
             </a-select-option>
           </a-select>
@@ -302,6 +304,7 @@ import DetailSettings from '@/components/view/DetailSettings'
 import NicsTable from '@/views/network/NicsTable'
 import ListResourceTable from '@/components/view/ListResourceTable'
 import TooltipButton from '@/components/widgets/TooltipButton'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import AnnotationsTab from '@/components/view/AnnotationsTab'
 
 export default {
@@ -314,6 +317,7 @@ export default {
     Status,
     ListResourceTable,
     TooltipButton,
+    ResourceIcon,
     AnnotationsTab
   },
   mixins: [mixinDevice],
@@ -434,6 +438,7 @@ export default {
     listNetworks () {
       api('listNetworks', {
         listAll: 'true',
+        showicon: true,
         zoneid: this.vm.zoneid
       }).then(response => {
         this.addNetworkData.allNetworks = response.listnetworksresponse.network.filter(network => !this.vm.nic.map(nic => nic.networkid).includes(network.id))
diff --git a/ui/src/views/compute/wizard/NetworkSelection.vue b/ui/src/views/compute/wizard/NetworkSelection.vue
index c2ce608..c5f895b 100644
--- a/ui/src/views/compute/wizard/NetworkSelection.vue
+++ b/ui/src/views/compute/wizard/NetworkSelection.vue
@@ -34,6 +34,15 @@
       :rowSelection="rowSelection"
       :scroll="{ y: 225 }"
     >
+      <template slot="name" slot-scope="text, item">
+        <resource-icon
+          v-if="item.icon"
+          :image="item.icon.base64image"
+          size="1x"
+          style="margin-right: 5px"/>
+        <a-icon slot="name" v-else type="apartment" style="margin-right: 5px" />
+        {{ item.name }}
+      </template>
       <a-list
         slot="expandedRowRender"
         slot-scope="record"
@@ -91,11 +100,13 @@ import _ from 'lodash'
 import { api } from '@/api'
 import store from '@/store'
 import CreateNetwork from '@/views/network/CreateNetwork'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'NetworkSelection',
   components: {
-    CreateNetwork
+    CreateNetwork,
+    ResourceIcon
   },
   props: {
     items: {
@@ -158,6 +169,7 @@ export default {
         {
           dataIndex: 'name',
           title: this.$t('label.networks'),
+          scopedSlots: { customRender: 'name' },
           width: '40%'
         },
         {
diff --git a/ui/src/views/compute/wizard/TemplateIsoRadioGroup.vue b/ui/src/views/compute/wizard/TemplateIsoRadioGroup.vue
index f5496f9..d341d31 100644
--- a/ui/src/views/compute/wizard/TemplateIsoRadioGroup.vue
+++ b/ui/src/views/compute/wizard/TemplateIsoRadioGroup.vue
@@ -33,7 +33,13 @@
             class="radio-group__radio"
             :value="os.id">
             {{ os.displaytext }}&nbsp;
+            <resource-icon
+              v-if="os.icon && os.icon.base64image"
+              class="radio-group__os-logo"
+              :image="os.icon.base64image"
+              size="1x" />
             <os-logo
+              v-else
               class="radio-group__os-logo"
               :osId="os.ostypeid"
               :os-name="os.osName" />
@@ -63,10 +69,14 @@
 
 <script>
 import OsLogo from '@/components/widgets/OsLogo'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'TemplateIsoRadioGroup',
-  components: { OsLogo },
+  components: {
+    OsLogo,
+    ResourceIcon
+  },
   props: {
     osList: {
       type: Array,
@@ -92,6 +102,7 @@ export default {
   data () {
     return {
       value: '',
+      image: '',
       options: {
         page: 1,
         pageSize: 10
diff --git a/ui/src/views/dashboard/CapacityDashboard.vue b/ui/src/views/dashboard/CapacityDashboard.vue
index 704b803..de93d1e 100644
--- a/ui/src/views/dashboard/CapacityDashboard.vue
+++ b/ui/src/views/dashboard/CapacityDashboard.vue
@@ -31,6 +31,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="(zone, index) in zones" :key="index">
+              <resource-icon v-if="zone.icon && zone.icon.base64image" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else style="margin-right: 5px" type="global" />
               {{ zone.name }}
             </a-select-option>
           </a-select>
@@ -127,11 +129,13 @@
 import { api } from '@/api'
 
 import ChartCard from '@/components/widgets/ChartCard'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'CapacityDashboard',
   components: {
-    ChartCard
+    ChartCard,
+    ResourceIcon
   },
   data () {
     return {
@@ -245,7 +249,7 @@ export default {
       return 'blue'
     },
     listZones () {
-      api('listZones').then(json => {
+      api('listZones', { showicon: true }).then(json => {
         if (json && json.listzonesresponse && json.listzonesresponse.zone) {
           this.zones = json.listzonesresponse.zone
           if (this.zones.length > 0) {
diff --git a/ui/src/views/iam/AddAccount.vue b/ui/src/views/iam/AddAccount.vue
index 80fe37f..7ab09c9 100644
--- a/ui/src/views/iam/AddAccount.vue
+++ b/ui/src/views/iam/AddAccount.vue
@@ -115,6 +115,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="domain in domainsList" :key="domain.id">
+              <resource-icon v-if="domain && domain.icon" :image="domain.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="block" style="margin-right: 5px"/>
               {{ domain.path || domain.name || domain.description }}
             </a-select-option>
           </a-select>
@@ -179,12 +181,14 @@
 import { api } from '@/api'
 import { timeZone } from '@/utils/timezone'
 import debounce from 'lodash/debounce'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'AddAccountForm',
   components: {
-    TooltipLabel
+    TooltipLabel,
+    ResourceIcon
   },
   data () {
     this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
@@ -249,6 +253,7 @@ export default {
       this.domainLoading = true
       api('listDomains', {
         listAll: true,
+        showicon: true,
         details: 'min'
       }).then(response => {
         this.domainsList = response.listdomainsresponse.domain || []
diff --git a/ui/src/views/iam/AddUser.vue b/ui/src/views/iam/AddUser.vue
index 5205e99..a2838a3 100644
--- a/ui/src/views/iam/AddUser.vue
+++ b/ui/src/views/iam/AddUser.vue
@@ -95,6 +95,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="domain in domainsList" :key="domain.id">
+              <resource-icon v-if="domain && domain.icon" :image="domain.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="block" style="margin-right: 5px" />
               {{ domain.path || domain.name || domain.description }}
             </a-select-option>
           </a-select>
@@ -113,6 +115,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="(item, idx) in accountList" :key="idx">
+              <resource-icon v-if="item && item.icon" :image="item.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="team" style="margin-right: 5px" />
               {{ item.name }}
             </a-select-option>
           </a-select>
@@ -167,12 +171,14 @@
 import { api } from '@/api'
 import { timeZone } from '@/utils/timezone'
 import debounce from 'lodash/debounce'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'AddUser',
   components: {
-    TooltipLabel
+    TooltipLabel,
+    ResourceIcon
   },
   data () {
     this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
@@ -217,6 +223,7 @@ export default {
       this.domainLoading = true
       api('listDomains', {
         listAll: true,
+        showicon: true,
         details: 'min'
       }).then(response => {
         this.domainsList = response.listdomainsresponse.domain || []
@@ -233,7 +240,7 @@ export default {
     fetchAccount () {
       this.accountList = []
       this.loadingAccount = true
-      api('listAccounts', { listAll: true }).then(response => {
+      api('listAccounts', { listAll: true, showicon: true }).then(response => {
         this.accountList = response.listaccountsresponse.account || []
       }).catch(error => {
         this.$notification.error({
diff --git a/ui/src/views/iam/DomainView.vue b/ui/src/views/iam/DomainView.vue
index 90ed5a6..e69edc5 100644
--- a/ui/src/views/iam/DomainView.vue
+++ b/ui/src/views/iam/DomainView.vue
@@ -88,6 +88,7 @@ import ActionButton from '@/components/view/ActionButton'
 import TreeView from '@/components/view/TreeView'
 import DomainActionForm from '@/views/iam/DomainActionForm'
 import ResourceView from '@/components/view/ResourceView'
+import eventBus from '@/config/eventBus'
 
 export default {
   name: 'DomainView',
@@ -136,6 +137,11 @@ export default {
   created () {
     this.domainStore = store.getters.domainStore
     this.fetchData()
+    eventBus.$on('refresh-domain-icon', () => {
+      if (this.$showIcon()) {
+        this.fetchData()
+      }
+    })
   },
   watch: {
     '$route' (to, from) {
@@ -170,7 +176,7 @@ export default {
       }
 
       this.loading = true
-
+      params.showicon = true
       api('listDomains', params).then(json => {
         const domains = json.listdomainsresponse.domain || []
         this.treeData = this.generateTreeData(domains)
@@ -296,6 +302,9 @@ export default {
 
       rootItem[0].title = rootItem[0].title ? rootItem[0].title : rootItem[0].name
       rootItem[0].key = rootItem[0].id ? rootItem[0].id : 0
+      rootItem[0].slots = {
+        icon: 'leaf'
+      }
 
       if (!rootItem[0].haschild) {
         rootItem[0].isLeaf = true
diff --git a/ui/src/views/image/AddKubernetesSupportedVersion.vue b/ui/src/views/image/AddKubernetesSupportedVersion.vue
index 39d9810..0eaa6b8 100644
--- a/ui/src/views/image/AddKubernetesSupportedVersion.vue
+++ b/ui/src/views/image/AddKubernetesSupportedVersion.vue
@@ -63,6 +63,8 @@
             :loading="zoneLoading"
             :placeholder="apiParams.zoneid.description">
             <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
+              <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px"/>
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -129,11 +131,13 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'AddKubernetesSupportedVersion',
   components: {
+    ResourceIcon,
     TooltipLabel
   },
   data () {
@@ -169,6 +173,7 @@ export default {
     fetchZoneData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       this.zoneLoading = true
       api('listZones', params).then(json => {
         const listZones = json.listzonesresponse.zone
diff --git a/ui/src/views/image/IsoZones.vue b/ui/src/views/image/IsoZones.vue
index b59c2a4..c1ba0f2 100644
--- a/ui/src/views/image/IsoZones.vue
+++ b/ui/src/views/image/IsoZones.vue
@@ -34,6 +34,13 @@
       :pagination="false"
       :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
       :rowKey="record => record.zoneid">
+      <div slot="zonename" slot-scope="text, record">
+        <span v-if="fetchZoneIcon(record.zoneid)">
+          <resource-icon :image="zoneIcon" size="1x" style="margin-right: 5px"/>
+        </span>
+        <a-icon v-else type="global" style="margin-right: 5px" />
+        <span> {{ record.zonename }} </span>
+      </div>
       <div slot="isready" slot-scope="text, record">
         <span v-if="record.isready">{{ $t('label.yes') }}</span>
         <span v-else>{{ $t('label.no') }}</span>
@@ -119,6 +126,10 @@
               :loading="zoneLoading"
               autoFocus>
               <a-select-option v-for="zone in zones" :key="zone.id">
+                <span v-if="zone.icon && zone.icon.base64image">
+                  <resource-icon :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+                </span>
+                <a-icon v-else type="global" style="margin-right: 5px" />
                 {{ zone.name }}
               </a-select-option>
             </a-select>
@@ -152,6 +163,8 @@
 <script>
 import { api } from '@/api'
 import TooltipButton from '@/components/widgets/TooltipButton'
+import OsLogo from '@/components/widgets/OsLogo'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import BulkActionView from '@/components/view/BulkActionView'
 import eventBus from '@/config/eventBus'
 
@@ -159,6 +172,8 @@ export default {
   name: 'IsoZones',
   components: {
     TooltipButton,
+    OsLogo,
+    ResourceIcon,
     BulkActionView
   },
   props: {
@@ -206,7 +221,8 @@ export default {
     this.columns = [
       {
         title: this.$t('label.zonename'),
-        dataIndex: 'zonename'
+        dataIndex: 'zonename',
+        scopedSlots: { customRender: 'zonename' }
       },
       {
         title: this.$t('label.status'),
@@ -262,6 +278,15 @@ export default {
       }).finally(() => {
         this.fetchLoading = false
       })
+      this.fetchZoneData()
+    },
+    fetchZoneIcon (zoneid) {
+      const zoneItem = this.zones.filter(zone => zone.id === zoneid)
+      if (zoneItem?.[0]?.icon?.base64image) {
+        this.zoneIcon = zoneItem[0].icon.base64image
+        return true
+      }
+      return false
     },
     handleChangePage (page, pageSize) {
       this.page = page
@@ -381,7 +406,7 @@ export default {
     fetchZoneData () {
       this.zones = []
       this.zoneLoading = true
-      api('listZones', { listall: true }).then(json => {
+      api('listZones', { listall: true, showicon: true }).then(json => {
         const zones = json.listzonesresponse.zone || []
         this.zones = [...zones.filter((zone) => this.currentRecord.zoneid !== zone.id)]
       }).finally(() => {
diff --git a/ui/src/views/image/RegisterOrUploadIso.vue b/ui/src/views/image/RegisterOrUploadIso.vue
index 24a235e..ac69379 100644
--- a/ui/src/views/image/RegisterOrUploadIso.vue
+++ b/ui/src/views/image/RegisterOrUploadIso.vue
@@ -93,6 +93,8 @@
             :loading="zoneLoading"
             :placeholder="apiParams.zoneid.description">
             <a-select-option :value="opt.id" v-for="opt in zones" :key="opt.id">
+              <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px" />
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -121,6 +123,8 @@
             :loading="osTypeLoading"
             :placeholder="apiParams.ostypeid.description">
             <a-select-option :value="opt.id" v-for="(opt, optIndex) in osTypes" :key="optIndex">
+              <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px" />
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -162,6 +166,7 @@
 import { api } from '@/api'
 import store from '@/store'
 import { axios } from '../../utils/request'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'RegisterIso',
@@ -175,6 +180,9 @@ export default {
       required: true
     }
   },
+  components: {
+    ResourceIcon
+  },
   data () {
     return {
       fileList: [],
@@ -218,6 +226,7 @@ export default {
     fetchZoneData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
 
       this.zoneLoading = true
       if (store.getters.userInfo.roletype === this.rootAdmin) {
diff --git a/ui/src/views/image/RegisterOrUploadTemplate.vue b/ui/src/views/image/RegisterOrUploadTemplate.vue
index 874f60e..c6d5743 100644
--- a/ui/src/views/image/RegisterOrUploadTemplate.vue
+++ b/ui/src/views/image/RegisterOrUploadTemplate.vue
@@ -104,6 +104,8 @@
                   :placeholder="apiParams.zoneids.description"
                   @change="handlerSelectZone">
                   <a-select-option v-for="opt in zones.opts" :key="opt.id">
+                    <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                    <a-icon v-else type="global" style="margin-right: 5px" />
                     {{ opt.name || opt.description }}
                   </a-select-option>
                 </a-select>
@@ -137,6 +139,8 @@
                   :placeholder="apiParams.zoneid.description"
                   :loading="zones.loading">
                   <a-select-option :value="zone.id" v-for="zone in zones.opts" :key="zone.id">
+                    <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+                    <a-icon v-else type="global" style="margin-right: 5px" />
                     {{ zone.name || zone.description }}
                   </a-select-option>
                 </a-select>
@@ -394,6 +398,7 @@
 import { api } from '@/api'
 import store from '@/store'
 import { axios } from '../../utils/request'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'RegisterOrUploadTemplate',
@@ -407,6 +412,9 @@ export default {
       required: true
     }
   },
+  components: {
+    ResourceIcon
+  },
   data () {
     return {
       uploadPercentage: 0,
@@ -522,7 +530,7 @@ export default {
       const params = {}
       let listZones = []
       params.listAll = true
-
+      params.showicon = true
       this.allowed = false
 
       if (store.getters.userInfo.roletype === this.rootAdmin && this.currentForm === 'Create') {
diff --git a/ui/src/views/image/TemplateZones.vue b/ui/src/views/image/TemplateZones.vue
index 75026e9..397af47 100644
--- a/ui/src/views/image/TemplateZones.vue
+++ b/ui/src/views/image/TemplateZones.vue
@@ -34,6 +34,13 @@
       :pagination="false"
       :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
       :rowKey="record => record.zoneid">
+      <div slot="zonename" slot-scope="text, record">
+        <span v-if="fetchZoneIcon(record.zoneid)">
+          <resource-icon :image="zoneIcon" size="1x" style="margin-right: 5px"/>
+        </span>
+        <a-icon v-else type="global" style="margin-right: 5px" />
+        <span> {{ record.zonename }} </span>
+      </div>
       <div slot="isready" slot-scope="text, record">
         <span v-if="record.isready">{{ $t('label.yes') }}</span>
         <span v-else>{{ $t('label.no') }}</span>
@@ -120,6 +127,10 @@
               :loading="zoneLoading"
               autoFocus>
               <a-select-option v-for="zone in zones" :key="zone.id">
+                <span v-if="zone.icon && zone.icon.base64image">
+                  <resource-icon :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+                </span>
+                <a-icon v-else type="global" style="margin-right: 5px" />
                 {{ zone.name }}
               </a-select-option>
             </a-select>
@@ -186,6 +197,8 @@
 
 <script>
 import { api } from '@/api'
+import OsLogo from '@/components/widgets/OsLogo'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipButton from '@/components/widgets/TooltipButton'
 import BulkActionProgress from '@/components/view/BulkActionProgress'
 import Status from '@/components/widgets/Status'
@@ -195,6 +208,8 @@ export default {
   name: 'TemplateZones',
   components: {
     TooltipButton,
+    OsLogo,
+    ResourceIcon,
     BulkActionProgress,
     Status
   },
@@ -246,7 +261,8 @@ export default {
     this.columns = [
       {
         title: this.$t('label.zonename'),
-        dataIndex: 'zonename'
+        dataIndex: 'zonename',
+        scopedSlots: { customRender: 'zonename' }
       },
       {
         title: this.$t('label.status'),
@@ -315,6 +331,15 @@ export default {
       }).finally(() => {
         this.fetchLoading = false
       })
+      this.fetchZoneData()
+    },
+    fetchZoneIcon (zoneid) {
+      const zoneItem = this.zones.filter(zone => zone.id === zoneid)
+      if (zoneItem?.[0]?.icon?.base64image) {
+        this.zoneIcon = zoneItem[0].icon.base64image
+        return true
+      }
+      return false
     },
     handleChangePage (page, pageSize) {
       this.page = page
@@ -466,7 +491,7 @@ export default {
     fetchZoneData () {
       this.zones = []
       this.zoneLoading = true
-      api('listZones', { listall: true }).then(json => {
+      api('listZones', { listall: true, showicon: true }).then(json => {
         const zones = json.listzonesresponse.zone || []
         this.zones = [...zones.filter((zone) => this.currentRecord.zoneid !== zone.id)]
       }).finally(() => {
diff --git a/ui/src/views/image/UpdateTemplateIsoPermissions.vue b/ui/src/views/image/UpdateTemplateIsoPermissions.vue
index b341b39..40c6201 100644
--- a/ui/src/views/image/UpdateTemplateIsoPermissions.vue
+++ b/ui/src/views/image/UpdateTemplateIsoPermissions.vue
@@ -77,6 +77,8 @@
                 return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
               <a-select-option v-for="account in accountsList" :key="account.name">
+                <resource-icon v-if="account.icon" :image="account.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else type="team" style="margin-right: 5px" />
                 {{ account.name }}
               </a-select-option>
             </a-select>
@@ -104,6 +106,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="project in projectsList" :key="project.name">
+              <resource-icon v-if="project.icon" :image="project.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="project" style="margin-right: 5px" />
               {{ project.name }}
             </a-select-option>
           </a-select>
@@ -119,6 +123,7 @@
 </template>
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'UpdateTemplateIsoPermissions',
@@ -128,6 +133,9 @@ export default {
       required: true
     }
   },
+  components: {
+    ResourceIcon
+  },
   inject: ['parentFetchData'],
   data () {
     return {
@@ -188,7 +196,8 @@ export default {
     fetchAccounts () {
       this.loading = true
       api('listAccounts', {
-        domainid: this.resource.domainid
+        domainid: this.resource.domainid,
+        showicon: true
       }).then(response => {
         this.accounts = response.listaccountsresponse.account.filter(account => account.name !== this.resource.account)
       }).finally(e => {
@@ -198,6 +207,7 @@ export default {
     fetchProjects () {
       api('listProjects', {
         details: 'min',
+        showicon: true,
         listall: true
       }).then(response => {
         this.projects = response.listprojectsresponse.project
diff --git a/ui/src/views/infra/AddPrimaryStorage.vue b/ui/src/views/infra/AddPrimaryStorage.vue
index aee89d4..6e8977c 100644
--- a/ui/src/views/infra/AddPrimaryStorage.vue
+++ b/ui/src/views/infra/AddPrimaryStorage.vue
@@ -62,6 +62,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option :value="zone.id" v-for="(zone) in zones" :key="zone.id">
+              <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px" />
               {{ zone.name }}
             </a-select-option>
           </a-select>
@@ -278,12 +280,14 @@
 <script>
 import { api } from '@/api'
 import _ from 'lodash'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'AddPrimaryStorage',
   components: {
-    TooltipLabel
+    TooltipLabel,
+    ResourceIcon
   },
   props: {
     resource: {
@@ -332,7 +336,7 @@ export default {
     },
     getInfraData () {
       this.loading = true
-      api('listZones').then(json => {
+      api('listZones', { showicon: true }).then(json => {
         this.zones = json.listzonesresponse.zone || []
         this.changeZone(this.zones[0] ? this.zones[0].id : '')
       }).finally(() => {
diff --git a/ui/src/views/infra/AddSecondaryStorage.vue b/ui/src/views/infra/AddSecondaryStorage.vue
index d369cc3..7c2013d 100644
--- a/ui/src/views/infra/AddSecondaryStorage.vue
+++ b/ui/src/views/infra/AddSecondaryStorage.vue
@@ -60,8 +60,11 @@
               <a-select-option
                 :value="zone.id"
                 v-for="(zone) in zones"
-                :key="zone.id"
-              >{{ zone.name }}</a-select-option>
+                :key="zone.id">
+                <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else type="global" style="margin-right: 5px" />
+                {{ zone.name }}
+              </a-select-option>
             </a-select>
           </a-form-item>
           <a-form-item :label="$t('label.server')">
@@ -167,6 +170,7 @@
 </template>
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'AddSecondryStorage',
@@ -176,6 +180,9 @@ export default {
       required: true
     }
   },
+  components: {
+    ResourceIcon
+  },
   inject: ['parentFetchData'],
   data () {
     return {
@@ -200,7 +207,7 @@ export default {
       this.$parent.$parent.close()
     },
     listZones () {
-      api('listZones').then(json => {
+      api('listZones', { showicon: true }).then(json => {
         if (json && json.listzonesresponse && json.listzonesresponse.zone) {
           this.zones = json.listzonesresponse.zone
           if (this.zones.length > 0) {
diff --git a/ui/src/views/infra/ClusterAdd.vue b/ui/src/views/infra/ClusterAdd.vue
index 7b132e2..1b445c4 100644
--- a/ui/src/views/infra/ClusterAdd.vue
+++ b/ui/src/views/infra/ClusterAdd.vue
@@ -33,6 +33,8 @@
             v-for="zone in zonesList"
             :value="zone.id"
             :key="zone.id">
+            <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+            <a-icon v-else type="global" style="margin-right: 5px" />
             {{ zone.name }}
           </a-select-option>
         </a-select>
@@ -129,11 +131,13 @@
 <script>
 import { api } from '@/api'
 import DedicateDomain from '../../components/view/DedicateDomain'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'ClusterAdd',
   components: {
-    DedicateDomain
+    DedicateDomain,
+    ResourceIcon
   },
   props: {
     resource: {
@@ -183,7 +187,7 @@ export default {
     },
     fetchZones () {
       this.loading = true
-      api('listZones').then(response => {
+      api('listZones', { showicon: true }).then(response => {
         this.zonesList = response.listzonesresponse.zone || []
         this.zoneId = this.zonesList[0].id || null
         this.fetchPods()
diff --git a/ui/src/views/infra/HostAdd.vue b/ui/src/views/infra/HostAdd.vue
index 4928232..815e647 100644
--- a/ui/src/views/infra/HostAdd.vue
+++ b/ui/src/views/infra/HostAdd.vue
@@ -34,6 +34,8 @@
             v-for="zone in zonesList"
             :value="zone.id"
             :key="zone.id">
+            <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+            <a-icon v-else type="global" style="margin-right: 5px" />
             {{ zone.name }}
           </a-select-option>
         </a-select>
@@ -152,11 +154,13 @@
 <script>
 import { api } from '@/api'
 import DedicateDomain from '../../components/view/DedicateDomain'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'HostAdd',
   components: {
-    DedicateDomain
+    DedicateDomain,
+    ResourceIcon
   },
   props: {
     resource: {
@@ -209,7 +213,7 @@ export default {
     },
     fetchZones () {
       this.loading = true
-      api('listZones').then(response => {
+      api('listZones', { showicon: true }).then(response => {
         this.zonesList = response.listzonesresponse.zone || []
         this.zoneId = this.zonesList[0].id || null
         this.fetchPods()
diff --git a/ui/src/views/infra/PodAdd.vue b/ui/src/views/infra/PodAdd.vue
index d22a7af..17e9a34 100644
--- a/ui/src/views/infra/PodAdd.vue
+++ b/ui/src/views/infra/PodAdd.vue
@@ -35,6 +35,8 @@
             v-for="zone in zonesList"
             :value="zone.id"
             :key="zone.id">
+            <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+            <a-icon v-else type="global" style="margin-right: 5px" />
             {{ zone.name }}
           </a-select-option>
         </a-select>
@@ -117,11 +119,13 @@
 <script>
 import { api } from '@/api'
 import DedicateDomain from '../../components/view/DedicateDomain'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'ClusterAdd',
   components: {
-    DedicateDomain
+    DedicateDomain,
+    ResourceIcon
   },
   props: {
     resource: {
@@ -161,7 +165,7 @@ export default {
     },
     fetchZones () {
       this.loading = true
-      api('listZones').then(response => {
+      api('listZones', { showicon: true }).then(response => {
         this.zonesList = response.listzonesresponse.zone || []
         this.zoneId = this.zonesList[0].id
         this.params = this.$store.getters.apis.createPod.params
diff --git a/ui/src/views/infra/network/DedicatedVLANTab.vue b/ui/src/views/infra/network/DedicatedVLANTab.vue
index a7b8237..ed7be5f 100644
--- a/ui/src/views/infra/network/DedicatedVLANTab.vue
+++ b/ui/src/views/infra/network/DedicatedVLANTab.vue
@@ -106,7 +106,11 @@
               :filterOption="(input, option) => {
                 return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
               }" >
-              <a-select-option v-for="domain in domains" :key="domain.id" :value="domain.id">{{ domain.path || domain.name || domain.description }}</a-select-option>
+              <a-select-option v-for="domain in domains" :key="domain.id" :value="domain.id">
+                <resource-icon v-if="domain && domain.icon" :image="domain.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else type="block" style="margin-right: 5px" />
+                {{ domain.path || domain.name || domain.description }}
+              </a-select-option>
             </a-select>
           </a-form-item>
 
@@ -124,6 +128,8 @@
                 v-for="account in accounts"
                 :key="account.id"
                 :value="account.name">
+                <resource-icon v-if="account && account.icon" :image="account.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else type="team" style="margin-right: 5px" />
                 {{ account.name }}
               </a-select-option>
             </a-select>
@@ -143,6 +149,8 @@
                 v-for="project in projects"
                 :key="project.id"
                 :value="project.id">
+                <resource-icon v-if="project && project.icon" :image="project.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else type="project" style="margin-right: 5px" />
                 {{ project.name }}
               </a-select-option>
             </a-select>
@@ -162,11 +170,13 @@
 <script>
 import { api } from '@/api'
 import TooltipButton from '@/components/widgets/TooltipButton'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'DedicatedVLANTab',
   components: {
-    TooltipButton
+    TooltipButton,
+    ResourceIcon
   },
   props: {
     resource: {
@@ -250,6 +260,7 @@ export default {
     fetchDomains () {
       api('listDomains', {
         details: 'min',
+        showicon: true,
         listAll: true
       }).then(response => {
         this.domains = response.listdomainsresponse.domain || []
@@ -274,6 +285,7 @@ export default {
       api('listAccounts', {
         domainid: e,
         details: 'min',
+        showicon: true,
         listAll: true
       }).then(response => {
         this.accounts = response.listaccountsresponse.account
@@ -297,6 +309,7 @@ export default {
       this.formLoading = true
       api('listProjects', {
         domainid: e,
+        showicon: true,
         details: 'min'
       }).then(response => {
         this.projects = response.listprojectsresponse.project
diff --git a/ui/src/views/infra/network/IpRangesTabGuest.vue b/ui/src/views/infra/network/IpRangesTabGuest.vue
index def6626..4160268 100644
--- a/ui/src/views/infra/network/IpRangesTabGuest.vue
+++ b/ui/src/views/infra/network/IpRangesTabGuest.vue
@@ -35,6 +35,8 @@
       :pagination="false"
     >
       <template slot="name" slot-scope="text, item">
+        <resource-icon v-if="item.icon" :image="item.icon.base64image" size="1x" style="margin-right: 5px"/>
+        <a-icon v-else type="apartment" style="margin-right: 5px"/>
         <router-link :to="{ path: '/guestnetwork/' + item.id }">
           {{ text }}
         </router-link>
@@ -75,11 +77,13 @@
 <script>
 import { api } from '@/api'
 import CreateNetwork from '@/views/network/CreateNetwork'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'IpRangesTabGuest',
   components: {
-    CreateNetwork
+    CreateNetwork,
+    ResourceIcon
   },
   props: {
     resource: {
@@ -145,6 +149,7 @@ export default {
       api('listNetworks', {
         zoneid: this.resource.zoneid,
         physicalnetworkid: this.resource.id,
+        showicon: true,
         page: this.page,
         pagesize: this.pageSize
       }).then(response => {
diff --git a/ui/src/views/network/CreateIsolatedNetworkForm.vue b/ui/src/views/network/CreateIsolatedNetworkForm.vue
index 36393b3..462b581 100644
--- a/ui/src/views/network/CreateIsolatedNetworkForm.vue
+++ b/ui/src/views/network/CreateIsolatedNetworkForm.vue
@@ -60,6 +60,8 @@
               :placeholder="this.$t('label.zoneid')"
               @change="val => { this.handleZoneChange(this.zones[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
+                <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else type="global" style="margin-right: 5px" />
                 {{ opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -189,12 +191,14 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'CreateIsolatedNetworkForm',
   components: {
-    TooltipLabel
+    TooltipLabel,
+    ResourceIcon
   },
   props: {
     loading: {
@@ -273,6 +277,7 @@ export default {
         params.id = this.resource.zoneid
       }
       params.listAll = true
+      params.showicon = true
       this.zoneLoading = true
       api('listZones', params).then(json => {
         for (const i in json.listzonesresponse.zone) {
diff --git a/ui/src/views/network/CreateL2NetworkForm.vue b/ui/src/views/network/CreateL2NetworkForm.vue
index 8304b60..9ffb448 100644
--- a/ui/src/views/network/CreateL2NetworkForm.vue
+++ b/ui/src/views/network/CreateL2NetworkForm.vue
@@ -60,6 +60,8 @@
               :placeholder="this.$t('label.zoneid')"
               @change="val => { this.handleZoneChange(this.zones[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
+                <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else type="global" style="margin-right: 5px" />
                 {{ opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -77,6 +79,8 @@
               :placeholder="this.$t('label.domainid')"
               @change="val => { this.handleDomainChange(this.domains[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
+                <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else-if="optIndex !== 0" type="block" style="margin-right: 5px" />
                 {{ opt.path || opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -172,12 +176,14 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'CreateL2NetworkForm',
   components: {
-    TooltipLabel
+    TooltipLabel,
+    ResourceIcon
   },
   props: {
     loading: {
@@ -257,6 +263,7 @@ export default {
         params.id = this.resource.zoneid
       }
       params.listAll = true
+      params.showicon = true
       this.zoneLoading = true
       api('listZones', params).then(json => {
         for (const i in json.listzonesresponse.zone) {
@@ -281,6 +288,7 @@ export default {
     fetchDomainData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       params.details = 'min'
       this.domainLoading = true
       api('listDomains', params).then(json => {
diff --git a/ui/src/views/network/CreateSharedNetworkForm.vue b/ui/src/views/network/CreateSharedNetworkForm.vue
index d0977fa..8337341 100644
--- a/ui/src/views/network/CreateSharedNetworkForm.vue
+++ b/ui/src/views/network/CreateSharedNetworkForm.vue
@@ -60,6 +60,8 @@
               :placeholder="this.$t('label.zoneid')"
               @change="val => { this.handleZoneChange(this.zones[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
+                <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else type="global" style="margin-right: 5px" />
                 {{ opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -162,6 +164,8 @@
               :placeholder="this.$t('label.domainid')"
               @change="val => { this.handleDomainChange(this.domains[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
+                <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else-if="optIndex !== 0" type="block" style="margin-right: 5px" />
                 {{ opt.path || opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -196,6 +200,8 @@
               :placeholder="this.$t('label.projectid')"
               @change="val => { this.handleProjectChange(this.projects[val]) }">
               <a-select-option v-for="(opt, optIndex) in this.projects" :key="optIndex">
+                <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+                <a-icon v-else-if="optIndex !== 0" type="project" style="margin-right: 5px" />
                 {{ opt.name || opt.description }}
               </a-select-option>
             </a-select>
@@ -346,12 +352,14 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'CreateGuestNetworkForm',
   components: {
-    TooltipLabel
+    TooltipLabel,
+    ResourceIcon
   },
   props: {
     loading: {
@@ -452,6 +460,7 @@ export default {
           params.id = this.resource.zoneid
         }
         params.listAll = true
+        params.showicon = true
         this.zoneLoading = true
         api('listZones', params).then(json => {
           for (const i in json.listzonesresponse.zone) {
@@ -641,6 +650,7 @@ export default {
       } else {
         params.listall = true
       }
+      params.showicon = true
       this.domainLoading = true
       api('listDomains', params).then(json => {
         const listDomains = json.listdomainsresponse.domain
@@ -663,6 +673,7 @@ export default {
       this.projects = []
       const params = {}
       params.listall = true
+      params.showicon = true
       params.details = 'min'
       this.projectLoading = true
       api('listProjects', params).then(json => {
diff --git a/ui/src/views/network/CreateVpc.vue b/ui/src/views/network/CreateVpc.vue
index 50545bd..09cc6a3 100644
--- a/ui/src/views/network/CreateVpc.vue
+++ b/ui/src/views/network/CreateVpc.vue
@@ -52,6 +52,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option v-for="zone in zones" :key="zone.id">
+              <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px" />
               {{ zone.name }}
             </a-select-option>
           </a-select>
@@ -101,11 +103,13 @@
 </template>
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'CreateVpc',
   components: {
+    ResourceIcon,
     TooltipLabel
   },
   data () {
@@ -132,7 +136,7 @@ export default {
     },
     fetchZones () {
       this.loadingZone = true
-      api('listZones', { listAll: true }).then((response) => {
+      api('listZones', { listAll: true, showicon: true }).then((response) => {
         const listZones = response.listzonesresponse.zone || []
         this.zones = listZones.filter(zone => !zone.securitygroupsenabled)
         this.selectedZone = ''
diff --git a/ui/src/views/network/NicsTable.vue b/ui/src/views/network/NicsTable.vue
index 4689efb..ba2ca5c 100644
--- a/ui/src/views/network/NicsTable.vue
+++ b/ui/src/views/network/NicsTable.vue
@@ -61,7 +61,8 @@
       </a-descriptions>
     </p>
     <template slot="networkname" slot-scope="text, item">
-      <a-icon type="apartment" />
+      <resource-icon v-if="!networkIconLoading && networkicon[item.id]" :image="networkicon[item.id]" size="1x" style="margin-right: 5px"/>
+      <a-icon v-else type="apartment" style="margin-right: 5px" />
       <router-link :to="{ path: '/guestnetwork/' + item.networkid }">
         {{ text }}
       </router-link>
@@ -73,6 +74,8 @@
 </template>
 
 <script>
+import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'NicsTable',
@@ -86,6 +89,9 @@ export default {
       default: false
     }
   },
+  components: {
+    ResourceIcon
+  },
   inject: ['parentFetchData'],
   data () {
     return {
@@ -116,12 +122,56 @@ export default {
           title: this.$t('label.gateway'),
           dataIndex: 'gateway'
         }
-      ]
+      ],
+      networkicon: {},
+      networkIconLoading: false
     }
   },
   watch: {
-    resource: function (newItem, oldItem) {
+    resource (newItem, oldItem) {
       this.resource = newItem
+      if (newItem && (!oldItem || (newItem.id !== oldItem.id))) {
+        this.fetchNetworks()
+      }
+    }
+  },
+  created () {
+    this.fetchNetworks()
+  },
+  methods: {
+    fetchNetworks () {
+      if (!this.resource || !this.resource.nic) return
+      this.networkIconLoading = true
+      this.networkicon = {}
+      const promises = []
+      this.resource.nic.forEach((item, index) => {
+        promises.push(this.fetchNetworkIcon(item.id, item.networkid))
+      })
+      Promise.all(promises).catch((reason) => {
+        console.log(reason)
+      }).finally(() => {
+        this.networkIconLoading = false
+      })
+    },
+    fetchNetworkIcon (id, networkid) {
+      return new Promise((resolve, reject) => {
+        this.networkicon[id] = null
+        api('listNetworks', {
+          id: networkid,
+          showicon: true
+        }).then(json => {
+          const network = json.listnetworksresponse?.network || []
+          if (network?.[0]?.icon) {
+            this.networkicon[id] = network[0]?.icon?.base64image
+            resolve(this.networkicon)
+          } else {
+            this.networkicon[id] = ''
+            resolve(this.networkicon)
+          }
+        }).catch(error => {
+          reject(error)
+        })
+      })
     }
   }
 }
diff --git a/ui/src/views/offering/AddComputeOffering.vue b/ui/src/views/offering/AddComputeOffering.vue
index a4b1433..b7efff2 100644
--- a/ui/src/views/offering/AddComputeOffering.vue
+++ b/ui/src/views/offering/AddComputeOffering.vue
@@ -448,6 +448,8 @@
             :loading="domainLoading"
             :placeholder="apiParams.domainid.description">
             <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex">
+              <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="block" style="margin-right: 5px" />
               {{ opt.path || opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -478,6 +480,8 @@
             :loading="zoneLoading"
             :placeholder="apiParams.zoneid.description">
             <a-select-option v-for="(opt, optIndex) in zones" :key="optIndex">
+              <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px"/>
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -508,11 +512,13 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'AddServiceOffering',
   components: {
+    ResourceIcon,
     TooltipLabel
   },
   data () {
@@ -619,6 +625,7 @@ export default {
     fetchDomainData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       params.details = 'min'
       this.domainLoading = true
       api('listDomains', params).then(json => {
@@ -631,6 +638,7 @@ export default {
     fetchZoneData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       this.zoneLoading = true
       api('listZones', params).then(json => {
         const listZones = json.listzonesresponse.zone
diff --git a/ui/src/views/offering/AddDiskOffering.vue b/ui/src/views/offering/AddDiskOffering.vue
index c96d031..295e0bb 100644
--- a/ui/src/views/offering/AddDiskOffering.vue
+++ b/ui/src/views/offering/AddDiskOffering.vue
@@ -283,6 +283,8 @@
             :loading="domainLoading"
             :placeholder="apiParams.domainid.description">
             <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex">
+              <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="block" style="margin-right: 5px" />
               {{ opt.path || opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -313,6 +315,8 @@
             :loading="zoneLoading"
             :placeholder="apiParams.zoneid.description">
             <a-select-option v-for="(opt, optIndex) in zones" :key="optIndex">
+              <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px"/>
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -343,11 +347,13 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'AddDiskOffering',
   components: {
+    ResourceIcon,
     TooltipLabel
   },
   data () {
@@ -406,6 +412,7 @@ export default {
     fetchDomainData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       params.details = 'min'
       this.domainLoading = true
       api('listDomains', params).then(json => {
@@ -418,6 +425,7 @@ export default {
     fetchZoneData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       this.zoneLoading = true
       api('listZones', params).then(json => {
         const listZones = json.listzonesresponse.zone
diff --git a/ui/src/views/offering/AddNetworkOffering.vue b/ui/src/views/offering/AddNetworkOffering.vue
index 173e9c5..289619c 100644
--- a/ui/src/views/offering/AddNetworkOffering.vue
+++ b/ui/src/views/offering/AddNetworkOffering.vue
@@ -346,6 +346,8 @@
             :loading="domainLoading"
             :placeholder="apiParams.domainid.description">
             <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex">
+              <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="block" style="margin-right: 5px" />
               {{ opt.path || opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -375,6 +377,8 @@
             :loading="zoneLoading"
             :placeholder="apiParams.zoneid.description">
             <a-select-option v-for="(opt, optIndex) in zones" :key="optIndex">
+              <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px"/>
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -395,12 +399,14 @@
 <script>
 import { api } from '@/api'
 import CheckBoxSelectPair from '@/components/CheckBoxSelectPair'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'AddNetworkOffering',
   components: {
     CheckBoxSelectPair,
+    ResourceIcon,
     TooltipLabel
   },
   data () {
@@ -467,6 +473,7 @@ export default {
     fetchDomainData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       params.details = 'min'
       this.domainLoading = true
       api('listDomains', params).then(json => {
@@ -479,6 +486,7 @@ export default {
     fetchZoneData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       this.zoneLoading = true
       api('listZones', params).then(json => {
         const listZones = json.listzonesresponse.zone
diff --git a/ui/src/views/offering/AddVpcOffering.vue b/ui/src/views/offering/AddVpcOffering.vue
index ed0e1e4..490e506 100644
--- a/ui/src/views/offering/AddVpcOffering.vue
+++ b/ui/src/views/offering/AddVpcOffering.vue
@@ -88,6 +88,8 @@
             :loading="domainLoading"
             :placeholder="apiParams.domainid.description">
             <a-select-option v-for="(opt, optIndex) in domains" :key="optIndex">
+              <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="block" style="margin-right: 5px" />
               {{ opt.path || opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -117,6 +119,8 @@
             :loading="zoneLoading"
             :placeholder="apiParams.zoneid.description">
             <a-select-option v-for="(opt, optIndex) in zones" :key="optIndex">
+              <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px"/>
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -137,12 +141,14 @@
 <script>
 import { api } from '@/api'
 import CheckBoxSelectPair from '@/components/CheckBoxSelectPair'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'AddVpcOffering',
   components: {
     CheckBoxSelectPair,
+    ResourceIcon,
     TooltipLabel
   },
   data () {
@@ -190,6 +196,7 @@ export default {
     fetchDomainData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       params.details = 'min'
       this.domainLoading = true
       api('listDomains', params).then(json => {
@@ -202,6 +209,7 @@ export default {
     fetchZoneData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       this.zoneLoading = true
       api('listZones', params).then(json => {
         const listZones = json.listzonesresponse.zone
diff --git a/ui/src/views/offering/ImportBackupOffering.vue b/ui/src/views/offering/ImportBackupOffering.vue
index f06bc2a..04a1eef 100644
--- a/ui/src/views/offering/ImportBackupOffering.vue
+++ b/ui/src/views/offering/ImportBackupOffering.vue
@@ -51,6 +51,8 @@
             return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
           }" >
           <a-select-option v-for="zone in zones.opts" :key="zone.name">
+            <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+            <a-icon v-else type="global" style="margin-right: 5px"/>
             {{ zone.name }}
           </a-select-option>
         </a-select>
@@ -89,12 +91,14 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'ImportBackupOffering',
   components: {
-    TooltipLabel
+    TooltipLabel,
+    ResourceIcon
   },
   data () {
     return {
@@ -122,7 +126,7 @@ export default {
     },
     fetchZone () {
       this.zones.loading = true
-      api('listZones', { available: true }).then(json => {
+      api('listZones', { available: true, showicon: true }).then(json => {
         this.zones.opts = json.listzonesresponse.zone || []
         this.$forceUpdate()
       }).catch(error => {
diff --git a/ui/src/views/offering/UpdateOfferingAccess.vue b/ui/src/views/offering/UpdateOfferingAccess.vue
index b2f3031..ad87f6f 100644
--- a/ui/src/views/offering/UpdateOfferingAccess.vue
+++ b/ui/src/views/offering/UpdateOfferingAccess.vue
@@ -48,6 +48,8 @@
             :loading="domainLoading"
             :placeholder="this.apiParams.domainid.description">
             <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
+              <resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="block" style="margin-right: 5px" />
               {{ opt.path || opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -78,6 +80,8 @@
             :loading="zoneLoading"
             :placeholder="this.apiParams.zoneid.description">
             <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
+              <resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px"/>
               {{ opt.name || opt.description }}
             </a-select-option>
           </a-select>
@@ -95,6 +99,7 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'UpdateOfferingAccess',
@@ -104,6 +109,9 @@ export default {
       required: true
     }
   },
+  components: {
+    ResourceIcon
+  },
   data () {
     return {
       formOffering: {},
@@ -177,6 +185,7 @@ export default {
     fetchDomainData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       params.details = 'min'
       this.domainLoading = true
       api('listDomains', params).then(json => {
@@ -190,6 +199,7 @@ export default {
     fetchZoneData () {
       const params = {}
       params.listAll = true
+      params.showicon = true
       this.zoneLoading = true
       api('listZones', params).then(json => {
         const listZones = json.listzonesresponse.zone
diff --git a/ui/src/views/storage/CreateVolume.vue b/ui/src/views/storage/CreateVolume.vue
index 8197c4f..51d0d27 100644
--- a/ui/src/views/storage/CreateVolume.vue
+++ b/ui/src/views/storage/CreateVolume.vue
@@ -49,6 +49,8 @@
             v-for="(zone, index) in zones"
             :value="zone.id"
             :key="index">
+            <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+            <a-icon v-else type="global" style="margin-right: 5px"/>
             {{ zone.name }}
           </a-select-option>
         </a-select>
@@ -135,11 +137,13 @@
 
 <script>
 import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'CreateVolume',
   components: {
+    ResourceIcon,
     TooltipLabel
   },
   data () {
@@ -163,7 +167,7 @@ export default {
   methods: {
     fetchData () {
       this.loading = true
-      api('listZones').then(json => {
+      api('listZones', { showicon: true }).then(json => {
         this.zones = json.listzonesresponse.zone || []
         this.selectedZoneId = this.zones[0].id || ''
         this.fetchDiskOfferings(this.selectedZoneId)
diff --git a/ui/src/views/storage/UploadLocalVolume.vue b/ui/src/views/storage/UploadLocalVolume.vue
index 06d69b7..69a0f80 100644
--- a/ui/src/views/storage/UploadLocalVolume.vue
+++ b/ui/src/views/storage/UploadLocalVolume.vue
@@ -71,6 +71,8 @@
               return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
             }" >
             <a-select-option :value="zone.id" v-for="zone in zones" :key="zone.id">
+              <resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
+              <a-icon v-else type="global" style="margin-right: 5px"/>
               {{ zone.name || zone.description }}
             </a-select-option>
           </a-select>
@@ -116,11 +118,13 @@
 <script>
 import { api } from '@/api'
 import { axios } from '../../utils/request'
+import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
 
 export default {
   name: 'UploadLocalVolume',
   components: {
+    ResourceIcon,
     TooltipLabel
   },
   data () {
@@ -143,7 +147,7 @@ export default {
   },
   methods: {
     listZones () {
-      api('listZones').then(json => {
+      api('listZones', { showicon: true }).then(json => {
         if (json && json.listzonesresponse && json.listzonesresponse.zone) {
           this.zones = json.listzonesresponse.zone
           if (this.zones.length > 0) {
diff --git a/ui/src/views/tools/ImportUnmanagedInstance.vue b/ui/src/views/tools/ImportUnmanagedInstance.vue
index 00bc562..ef4e8cf 100644
--- a/ui/src/views/tools/ImportUnmanagedInstance.vue
+++ b/ui/src/views/tools/ImportUnmanagedInstance.vue
@@ -57,10 +57,15 @@
                   :filterOption="(input, option) => {
                     return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }"
-                  :options="domainSelectOptions"
                   :loading="optionsLoading.domains"
                   :placeholder="apiParams.domainid.description"
-                  @change="val => { this.selectedDomainId = val }" />
+                  @change="val => { this.selectedDomainId = val }">
+                  <a-select-option v-for="dom in domainSelectOptions" :key="dom.value">
+                    <resource-icon v-if="dom.icon" :image="dom.icon" size="1x" style="margin-right: 5px"/>
+                    <a-icon v-else-if="dom.value !== null" style="margin-right: 5px" type="block" />
+                    {{ dom.label }}
+                  </a-select-option>
+                </a-select>
               </a-form-item>
               <a-form-item v-if="selectedDomainId">
                 <tooltip-label slot="label" :title="$t('label.account')" :tooltip="apiParams.account.description"/>
@@ -77,9 +82,14 @@
                   :filterOption="(input, option) => {
                     return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
                   }"
-                  :options="projectSelectOptions"
                   :loading="optionsLoading.projects"
-                  :placeholder="apiParams.projectid.description" />
+                  :placeholder="apiParams.projectid.description">
+                  <a-select-option v-for="proj in projectSelectOptions" :key="proj.value">
+                    <resource-icon v-if="proj.icon" :image="proj.icon" size="1x" style="margin-right: 5px"/>
+                    <a-icon v-else-if="proj.value !== null" style="margin-right: 5px" type="project" />
+                    {{ proj.label }}
+                  </a-select-option>
+                </a-select>
               </a-form-item>
               <a-form-item>
                 <tooltip-label slot="label" :title="$t('label.templatename')" :tooltip="apiParams.templateid.description + '. ' + $t('message.template.import.vm.temporary')"/>
@@ -108,9 +118,14 @@
                         :filterOption="(input, option) => {
                           return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
                         }"
-                        :options="templateSelectOptions"
                         :loading="optionsLoading.templates"
-                        :placeholder="apiParams.templateid.description" />
+                        :placeholder="apiParams.templateid.description">
+                        <a-select-option v-for="temp in templateSelectOptions" :key="temp.value">
+                          <resource-icon v-if="temp.icon" :image="temp.icon" size="1x" style="margin-right: 5px"/>
+                          <os-logo v-else-if="temp.value !== null" :osId="temp.ostypeid" :osName="temp.ostypename" size="lg" style="margin-left: -1px" />
+                          {{ temp.label }}
+                        </a-select-option>
+                      </a-select>
                     </a-col>
                   </a-row>
                 </a-radio-group>
@@ -227,6 +242,8 @@ import ComputeOfferingSelection from '@views/compute/wizard/ComputeOfferingSelec
 import ComputeSelection from '@views/compute/wizard/ComputeSelection'
 import MultiDiskSelection from '@views/compute/wizard/MultiDiskSelection'
 import MultiNetworkSelection from '@views/compute/wizard/MultiNetworkSelection'
+import OsLogo from '@/components/widgets/OsLogo'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   name: 'ImportUnmanagedInstances',
@@ -236,7 +253,9 @@ export default {
     ComputeOfferingSelection,
     ComputeSelection,
     MultiDiskSelection,
-    MultiNetworkSelection
+    MultiNetworkSelection,
+    OsLogo,
+    ResourceIcon
   },
   props: {
     cluster: {
@@ -312,7 +331,8 @@ export default {
           isLoad: true,
           field: 'domainid',
           options: {
-            details: 'min'
+            details: 'min',
+            showicon: true
           }
         },
         projects: {
@@ -320,7 +340,8 @@ export default {
           isLoad: true,
           field: 'projectid',
           options: {
-            details: 'min'
+            details: 'min',
+            showicon: true
           }
         },
         templates: {
@@ -328,7 +349,8 @@ export default {
           isLoad: true,
           options: {
             templatefilter: 'all',
-            hypervisor: this.cluster.hypervisortype
+            hypervisor: this.cluster.hypervisortype,
+            showicon: true
           },
           field: 'templateid'
         }
@@ -344,7 +366,8 @@ export default {
       var domains = this.options.domains.map((domain) => {
         return {
           label: domain.path || domain.name,
-          value: domain.id
+          value: domain.id,
+          icon: domain?.icon?.base64image || ''
         }
       })
       domains.unshift({
@@ -357,7 +380,8 @@ export default {
       var projects = this.options.projects.map((project) => {
         return {
           label: project.name,
-          value: project.id
+          value: project.id,
+          icon: project?.icon?.base64image || ''
         }
       })
       projects.unshift({
@@ -370,7 +394,10 @@ export default {
       return this.options.templates.map((template) => {
         return {
           label: template.name,
-          value: template.id
+          value: template.id,
+          icon: template?.icon?.base64image || '',
+          ostypeid: template.ostypeid,
+          ostypename: template.ostypename
         }
       })
     },
diff --git a/ui/src/views/tools/ManageInstances.vue b/ui/src/views/tools/ManageInstances.vue
index 91180a2..e37987b 100644
--- a/ui/src/views/tools/ManageInstances.vue
+++ b/ui/src/views/tools/ManageInstances.vue
@@ -50,11 +50,16 @@
                   showSearch
                   optionFilterProp="children"
                   :filterOption="filterOption"
-                  :options="zoneSelectOptions"
                   @change="onSelectZoneId"
                   :loading="optionLoading.zones"
                   autoFocus
-                ></a-select>
+                >
+                  <a-select-option v-for="zoneitem in zoneSelectOptions" :key="zoneitem.value">
+                    <resource-icon v-if="zoneitem.icon" :image="zoneitem.icon" size="1x" style="margin-right: 5px"/>
+                    <a-icon v-else style="margin-right: 5px" type="global" />
+                    {{ zoneitem.label }}
+                  </a-select-option>
+                </a-select>
               </a-form-item>
             </a-col>
             <a-col :md="24" :lg="8">
@@ -258,13 +263,15 @@ import Breadcrumb from '@/components/widgets/Breadcrumb'
 import Status from '@/components/widgets/Status'
 import SearchView from '@/components/view/SearchView'
 import ImportUnmanagedInstances from '@/views/tools/ImportUnmanagedInstance'
+import ResourceIcon from '@/components/view/ResourceIcon'
 
 export default {
   components: {
     Breadcrumb,
     Status,
     SearchView,
-    ImportUnmanagedInstances
+    ImportUnmanagedInstances,
+    ResourceIcon
   },
   name: 'ManageVms',
   data () {
@@ -388,7 +395,10 @@ export default {
         zones: {
           list: 'listZones',
           isLoad: true,
-          field: 'zoneid'
+          field: 'zoneid',
+          options: {
+            showicon: true
+          }
         },
         pods: {
           list: 'listPods',
@@ -421,7 +431,8 @@ export default {
       return this.options.zones.map((zone) => {
         return {
           label: zone.name,
-          value: zone.id
+          value: zone.id,
+          icon: zone?.icon?.base64image || ''
         }
       })
     },