ui,api,server: template categorization based on os (#10773)

Adds new interface for image selection (template/iso) for an instance in UI.
Old interface can still be used and it can be configured using UI configuration (config.json)

OS categories/Guest OS categories have been improved with ability to create new categories, delete an existing category, and marking a category as featured to allow it to show up in the UI in the image selection interface.

New APIs added:
- addOsCategory
- deleteOsCategory
- updateOsCategory

APIs updated:
- updateOsType
- listTemplates
- listOsCategories

Several improvements in UI especially related to forms - DeloyVM, ReinstallVM, CreateVnfAppliance, AddAutoscaleGroup.

DeployVM form can now be opened from template/ISO details view with query params.

Reorganized (removed and added some) OS categories to the following (in the same order):
```
1. Ubuntu
2. Debian
3. Fedora
4. CentOS
5. Rocky Linux
6. Alma Linux
7. Oracle
8. RedHat
9. SUSE
10. Windows
11. Other
```

Documentation PR: https://github.com/apache/cloudstack-documentation/pull/500

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2025-06-10 15:25:51 +05:30 committed by GitHub
parent 41b4f0afd5
commit bce17b627d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 5027 additions and 1289 deletions

View File

@ -688,6 +688,9 @@ public class EventTypes {
public static final String EVENT_EXTERNAL_OPENDAYLIGHT_CONFIGURE_CONTROLLER = "PHYSICAL.ODLCONTROLLER.CONFIGURE"; public static final String EVENT_EXTERNAL_OPENDAYLIGHT_CONFIGURE_CONTROLLER = "PHYSICAL.ODLCONTROLLER.CONFIGURE";
//Guest OS related events //Guest OS related events
public static final String EVENT_GUEST_OS_CATEGORY_ADD = "GUEST.OS.CATEGORY.ADD";
public static final String EVENT_GUEST_OS_CATEGORY_DELETE = "GUEST.OS.CATEGORY.DELETE";
public static final String EVENT_GUEST_OS_CATEGORY_UPDATE = "GUEST.OS.CATEGORY.UPDATE";
public static final String EVENT_GUEST_OS_ADD = "GUEST.OS.ADD"; public static final String EVENT_GUEST_OS_ADD = "GUEST.OS.ADD";
public static final String EVENT_GUEST_OS_REMOVE = "GUEST.OS.REMOVE"; public static final String EVENT_GUEST_OS_REMOVE = "GUEST.OS.REMOVE";
public static final String EVENT_GUEST_OS_UPDATE = "GUEST.OS.UPDATE"; public static final String EVENT_GUEST_OS_UPDATE = "GUEST.OS.UPDATE";

View File

@ -25,12 +25,15 @@ import org.apache.cloudstack.api.command.admin.cluster.ListClustersCmd;
import org.apache.cloudstack.api.command.admin.config.ListCfgGroupsByCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgGroupsByCmd;
import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd; import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd;
import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCategoryCmd;
import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd;
import org.apache.cloudstack.api.command.admin.guest.AddGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsMappingCmd;
import org.apache.cloudstack.api.command.admin.guest.DeleteGuestOsCategoryCmd;
import org.apache.cloudstack.api.command.admin.guest.GetHypervisorGuestOsNamesCmd; import org.apache.cloudstack.api.command.admin.guest.GetHypervisorGuestOsNamesCmd;
import org.apache.cloudstack.api.command.admin.guest.ListGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.ListGuestOsMappingCmd;
import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsCmd;
import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsMappingCmd;
import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCategoryCmd;
import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCmd;
import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsMappingCmd;
import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; import org.apache.cloudstack.api.command.admin.host.ListHostsCmd;
@ -168,6 +171,12 @@ public interface ManagementService {
*/ */
Pair<List<? extends GuestOsCategory>, Integer> listGuestOSCategoriesByCriteria(ListGuestOsCategoriesCmd cmd); Pair<List<? extends GuestOsCategory>, Integer> listGuestOSCategoriesByCriteria(ListGuestOsCategoriesCmd cmd);
GuestOsCategory addGuestOsCategory(AddGuestOsCategoryCmd cmd);
GuestOsCategory updateGuestOsCategory(UpdateGuestOsCategoryCmd cmd);
boolean deleteGuestOsCategory(DeleteGuestOsCategoryCmd cmd);
/** /**
* Obtains a list of all guest OS mappings * Obtains a list of all guest OS mappings
* *

View File

@ -16,7 +16,9 @@
// under the License. // under the License.
package com.cloud.server; package com.cloud.server;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
public interface ResourceIconManager { public interface ResourceIconManager {
@ -25,4 +27,8 @@ public interface ResourceIconManager {
boolean deleteResourceIcon(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType); boolean deleteResourceIcon(List<String> resourceIds, ResourceTag.ResourceObjectType resourceType);
ResourceIcon getByResourceTypeAndUuid(ResourceTag.ResourceObjectType type, String resourceId); ResourceIcon getByResourceTypeAndUuid(ResourceTag.ResourceObjectType type, String resourceId);
Map<Long, ResourceIcon> getByResourceTypeAndIds(ResourceTag.ResourceObjectType type, Collection<Long> resourceIds);
Map<String, ResourceIcon> getByResourceTypeAndUuids(ResourceTag.ResourceObjectType type, Collection<String> resourceUuids);
} }

View File

@ -66,6 +66,7 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit
LBStickinessPolicy(false, true), LBStickinessPolicy(false, true),
LBHealthCheckPolicy(false, true), LBHealthCheckPolicy(false, true),
SnapshotPolicy(true, true), SnapshotPolicy(true, true),
GuestOsCategory(false, false, true),
GuestOs(false, true), GuestOs(false, true),
NetworkOffering(false, true), NetworkOffering(false, true),
VpcOffering(true, false), VpcOffering(true, false),

View File

@ -16,6 +16,8 @@
// under the License. // under the License.
package com.cloud.storage; package com.cloud.storage;
import java.util.Date;
import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.api.InternalIdentity;
@ -27,4 +29,7 @@ public interface GuestOsCategory extends Identity, InternalIdentity {
void setName(String name); void setName(String name);
boolean isFeatured();
Date getCreated();
} }

View File

@ -61,6 +61,7 @@ public enum ApiCommandResourceType {
AffinityGroup(org.apache.cloudstack.affinity.AffinityGroup.class), AffinityGroup(org.apache.cloudstack.affinity.AffinityGroup.class),
InternalLbVm(com.cloud.network.router.VirtualRouter.class), InternalLbVm(com.cloud.network.router.VirtualRouter.class),
DedicatedGuestVlanRange(com.cloud.network.GuestVlan.class), DedicatedGuestVlanRange(com.cloud.network.GuestVlan.class),
GuestOsCategory(com.cloud.storage.GuestOsCategory.class),
GuestOs(com.cloud.storage.GuestOS.class), GuestOs(com.cloud.storage.GuestOS.class),
GuestOsMapping(com.cloud.storage.GuestOSHypervisor.class), GuestOsMapping(com.cloud.storage.GuestOSHypervisor.class),
Network(com.cloud.network.Network.class), Network(com.cloud.network.Network.class),

View File

@ -299,6 +299,7 @@ public class ApiConstants {
public static final String IS_EXTRACTABLE = "isextractable"; public static final String IS_EXTRACTABLE = "isextractable";
public static final String IS_FEATURED = "isfeatured"; public static final String IS_FEATURED = "isfeatured";
public static final String IS_IMPLICIT = "isimplicit"; public static final String IS_IMPLICIT = "isimplicit";
public static final String IS_ISO = "isiso";
public static final String IS_PORTABLE = "isportable"; public static final String IS_PORTABLE = "isportable";
public static final String IS_PUBLIC = "ispublic"; public static final String IS_PUBLIC = "ispublic";
public static final String IS_PERSISTENT = "ispersistent"; public static final String IS_PERSISTENT = "ispersistent";

View File

@ -22,20 +22,16 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import com.cloud.bgp.ASNumber;
import com.cloud.bgp.ASNumberRange;
import org.apache.cloudstack.storage.object.Bucket;
import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.HostDetails;
import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails;
import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse;
import org.apache.cloudstack.api.response.ASNRangeResponse; import org.apache.cloudstack.api.response.ASNRangeResponse;
import org.apache.cloudstack.api.response.ASNumberResponse; import org.apache.cloudstack.api.response.ASNumberResponse;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse;
import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.AsyncJobResponse;
import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScalePolicyResponse;
import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
@ -60,10 +56,10 @@ import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse; import org.apache.cloudstack.api.response.DomainRouterResponse;
import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.EventResponse;
import org.apache.cloudstack.api.response.ExtractResponse; import org.apache.cloudstack.api.response.ExtractResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.FirewallResponse; import org.apache.cloudstack.api.response.FirewallResponse;
import org.apache.cloudstack.api.response.FirewallRuleResponse; import org.apache.cloudstack.api.response.FirewallRuleResponse;
import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse; import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse;
import org.apache.cloudstack.api.response.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.GuestOSResponse;
import org.apache.cloudstack.api.response.GuestOsMappingResponse; import org.apache.cloudstack.api.response.GuestOsMappingResponse;
import org.apache.cloudstack.api.response.GuestVlanRangeResponse; import org.apache.cloudstack.api.response.GuestVlanRangeResponse;
@ -73,11 +69,11 @@ import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse; import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse;
import org.apache.cloudstack.api.response.HypervisorGuestOsNamesResponse; import org.apache.cloudstack.api.response.HypervisorGuestOsNamesResponse;
import org.apache.cloudstack.api.response.IPAddressResponse; import org.apache.cloudstack.api.response.IPAddressResponse;
import org.apache.cloudstack.api.response.IpQuarantineResponse;
import org.apache.cloudstack.api.response.ImageStoreResponse; import org.apache.cloudstack.api.response.ImageStoreResponse;
import org.apache.cloudstack.api.response.InstanceGroupResponse; import org.apache.cloudstack.api.response.InstanceGroupResponse;
import org.apache.cloudstack.api.response.InternalLoadBalancerElementResponse; import org.apache.cloudstack.api.response.InternalLoadBalancerElementResponse;
import org.apache.cloudstack.api.response.IpForwardingRuleResponse; import org.apache.cloudstack.api.response.IpForwardingRuleResponse;
import org.apache.cloudstack.api.response.IpQuarantineResponse;
import org.apache.cloudstack.api.response.IsolationMethodResponse; import org.apache.cloudstack.api.response.IsolationMethodResponse;
import org.apache.cloudstack.api.response.LBHealthCheckResponse; import org.apache.cloudstack.api.response.LBHealthCheckResponse;
import org.apache.cloudstack.api.response.LBStickinessResponse; import org.apache.cloudstack.api.response.LBStickinessResponse;
@ -115,6 +111,7 @@ import org.apache.cloudstack.api.response.SecondaryStorageHeuristicsResponse;
import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.ServiceResponse; import org.apache.cloudstack.api.response.ServiceResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse; import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse;
import org.apache.cloudstack.api.response.Site2SiteVpnConnectionResponse; import org.apache.cloudstack.api.response.Site2SiteVpnConnectionResponse;
import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse; import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse;
@ -159,10 +156,14 @@ import org.apache.cloudstack.region.PortableIp;
import org.apache.cloudstack.region.PortableIpRange; import org.apache.cloudstack.region.PortableIpRange;
import org.apache.cloudstack.region.Region; import org.apache.cloudstack.region.Region;
import org.apache.cloudstack.secstorage.heuristics.Heuristic; import org.apache.cloudstack.secstorage.heuristics.Heuristic;
import org.apache.cloudstack.storage.sharedfs.SharedFS; import org.apache.cloudstack.storage.object.Bucket;
import org.apache.cloudstack.storage.object.ObjectStore; import org.apache.cloudstack.storage.object.ObjectStore;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.usage.Usage; import org.apache.cloudstack.usage.Usage;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import com.cloud.bgp.ASNumber;
import com.cloud.bgp.ASNumberRange;
import com.cloud.capacity.Capacity; import com.cloud.capacity.Capacity;
import com.cloud.configuration.ResourceCount; import com.cloud.configuration.ResourceCount;
import com.cloud.configuration.ResourceLimit; import com.cloud.configuration.ResourceLimit;
@ -223,10 +224,11 @@ import com.cloud.projects.ProjectAccount;
import com.cloud.projects.ProjectInvitation; import com.cloud.projects.ProjectInvitation;
import com.cloud.region.ha.GlobalLoadBalancerRule; import com.cloud.region.ha.GlobalLoadBalancerRule;
import com.cloud.resource.RollingMaintenanceManager; import com.cloud.resource.RollingMaintenanceManager;
import com.cloud.server.ResourceTag;
import com.cloud.server.ResourceIcon; import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceTag;
import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOS;
import com.cloud.storage.GuestOSHypervisor; import com.cloud.storage.GuestOSHypervisor;
import com.cloud.storage.GuestOsCategory;
import com.cloud.storage.ImageStore; import com.cloud.storage.ImageStore;
import com.cloud.storage.Snapshot; import com.cloud.storage.Snapshot;
import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePool;
@ -240,14 +242,13 @@ import com.cloud.user.User;
import com.cloud.user.UserAccount; import com.cloud.user.UserAccount;
import com.cloud.user.UserData; import com.cloud.user.UserData;
import com.cloud.uservm.UserVm; import com.cloud.uservm.UserVm;
import com.cloud.utils.net.Ip;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.net.Ip;
import com.cloud.vm.InstanceGroup; import com.cloud.vm.InstanceGroup;
import com.cloud.vm.Nic; import com.cloud.vm.Nic;
import com.cloud.vm.NicSecondaryIp; import com.cloud.vm.NicSecondaryIp;
import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine;
import com.cloud.vm.snapshot.VMSnapshot; import com.cloud.vm.snapshot.VMSnapshot;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
public interface ResponseGenerator { public interface ResponseGenerator {
UserResponse createUserResponse(UserAccount user); UserResponse createUserResponse(UserAccount user);
@ -485,6 +486,10 @@ public interface ResponseGenerator {
AutoScaleVmGroupResponse createAutoScaleVmGroupResponse(AutoScaleVmGroup vmGroup); AutoScaleVmGroupResponse createAutoScaleVmGroupResponse(AutoScaleVmGroup vmGroup);
GuestOSCategoryResponse createGuestOSCategoryResponse(GuestOsCategory guestOsCategory);
GuestOSCategoryResponse createGuestOSCategoryResponse(GuestOsCategory guestOsCategory, boolean showIcon);
GuestOSResponse createGuestOSResponse(GuestOS os); GuestOSResponse createGuestOSResponse(GuestOS os);
GuestOsMappingResponse createGuestOSMappingResponse(GuestOSHypervisor osHypervisor); GuestOsMappingResponse createGuestOSMappingResponse(GuestOSHypervisor osHypervisor);
@ -572,4 +577,6 @@ public interface ResponseGenerator {
BackupRepositoryResponse createBackupRepositoryResponse(BackupRepository repository); BackupRepositoryResponse createBackupRepositoryResponse(BackupRepository repository);
SharedFSResponse createSharedFSResponse(ResponseView view, SharedFS sharedFS); SharedFSResponse createSharedFSResponse(ResponseView view, SharedFS sharedFS);
void updateTemplateIsoResponsesForIcons(List<TemplateResponse> responses, ResourceTag.ResourceObjectType type);
} }

View File

@ -0,0 +1,93 @@
// 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.guest;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
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.GuestOSCategoryResponse;
import com.cloud.storage.GuestOsCategory;
import com.cloud.user.Account;
@APICommand(name = "addOsCategory",
description = "Adds a new OS category",
responseObject = GuestOSCategoryResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.21.0",
authorized = {RoleType.Admin})
public class AddGuestOsCategoryCmd extends BaseCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name of the OS category",
required = true)
private String name;
@Parameter(name = ApiConstants.IS_FEATURED, type = CommandType.BOOLEAN,
description = "Whether the category is featured or not")
private Boolean featured;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public String getName() {
return name;
}
public boolean isFeatured() {
return Boolean.TRUE.equals(featured);
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
GuestOsCategory guestOs = _mgr.addGuestOsCategory(this);
if (guestOs != null) {
GuestOSCategoryResponse response = _responseGenerator.createGuestOSCategoryResponse(guestOs);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add new OS category");
}
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.GuestOsCategory;
}
}

View File

@ -0,0 +1,89 @@
// 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.guest;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
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.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import com.cloud.user.Account;
@APICommand(name = "deleteOsCategory",
description = "Deletes an OS category",
responseObject = SuccessResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.21.0",
authorized = {RoleType.Admin})
public class DeleteGuestOsCategoryCmd extends BaseCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class,
required = true, description = "ID of the OS category")
private Long id;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
boolean result = _mgr.deleteGuestOsCategory(this);
if (result) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to remove OS category");
}
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.GuestOsCategory;
}
@Override
public Long getApiResourceId() {
return getId();
}
}

View File

@ -0,0 +1,113 @@
// 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.guest;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
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.GuestOSCategoryResponse;
import com.cloud.storage.GuestOsCategory;
import com.cloud.user.Account;
@APICommand(name = "updateOsCategory",
description = "Updates an OS category",
responseObject = GuestOSCategoryResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.21.0",
authorized = {RoleType.Admin})
public class UpdateGuestOsCategoryCmd extends BaseCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class,
required = true, description = "ID of the OS category")
private Long id;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name for the OS category")
private String name;
@Parameter(name = ApiConstants.IS_FEATURED, type = CommandType.BOOLEAN,
description = "Whether the category is featured or not")
private Boolean featured;
@Parameter(name = ApiConstants.SORT_KEY, type = CommandType.INTEGER,
description = "sort key of the OS category for listing")
private Integer sortKey;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Boolean isFeatured() {
return featured;
}
public Integer getSortKey() {
return sortKey;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public void execute() {
GuestOsCategory guestOs = _mgr.updateGuestOsCategory(this);
if (guestOs != null) {
GuestOSCategoryResponse response = _responseGenerator.createGuestOSCategoryResponse(guestOs);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update OS category");
}
}
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.GuestOsCategory;
}
@Override
public Long getApiResourceId() {
return getId();
}
}

View File

@ -16,8 +16,12 @@
// under the License. // under the License.
package org.apache.cloudstack.api.command.admin.guest; package org.apache.cloudstack.api.command.admin.guest;
import org.apache.commons.collections.MapUtils; import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
@ -25,18 +29,14 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.GuestOSResponse;
import org.apache.cloudstack.acl.RoleType; import org.apache.commons.collections.MapUtils;
import com.cloud.event.EventTypes; import com.cloud.event.EventTypes;
import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOS;
import com.cloud.user.Account; import com.cloud.user.Account;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@APICommand(name = "updateGuestOs", description = "Updates the information about Guest OS", responseObject = GuestOSResponse.class, @APICommand(name = "updateGuestOs", description = "Updates the information about Guest OS", responseObject = GuestOSResponse.class,
since = "4.4.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) since = "4.4.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class UpdateGuestOsCmd extends BaseAsyncCmd { public class UpdateGuestOsCmd extends BaseAsyncCmd {
@ -50,7 +50,7 @@ public class UpdateGuestOsCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSResponse.class, required = true, description = "UUID of the Guest OS") @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSResponse.class, required = true, description = "UUID of the Guest OS")
private Long id; private Long id;
@Parameter(name = ApiConstants.OS_DISPLAY_NAME, type = CommandType.STRING, required = true, description = "Unique display name for Guest OS") @Parameter(name = ApiConstants.OS_DISPLAY_NAME, type = CommandType.STRING, description = "Unique display name for Guest OS")
private String osDisplayName; private String osDisplayName;
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs)") @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs)")
@ -59,6 +59,12 @@ public class UpdateGuestOsCmd extends BaseAsyncCmd {
@Parameter(name="forDisplay", type=CommandType.BOOLEAN, description="whether this guest OS is available for end users", authorized = {RoleType.Admin}) @Parameter(name="forDisplay", type=CommandType.BOOLEAN, description="whether this guest OS is available for end users", authorized = {RoleType.Admin})
private Boolean display; private Boolean display;
@Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class,
description = "the ID of the OS category", since = "4.21.0")
private Long osCategoryId;
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////////// Accessors /////////////////////// /////////////////// Accessors ///////////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -71,9 +77,9 @@ public class UpdateGuestOsCmd extends BaseAsyncCmd {
return osDisplayName; return osDisplayName;
} }
public Map getDetails() { public Map<String, String> getDetails() {
Map<String, String> detailsMap = new HashMap<>();; Map<String, String> detailsMap = new HashMap<>();
if (MapUtils.isNotEmpty(detailsMap)) { if (MapUtils.isNotEmpty(details)) {
Collection<?> servicesCollection = details.values(); Collection<?> servicesCollection = details.values();
Iterator<?> iter = servicesCollection.iterator(); Iterator<?> iter = servicesCollection.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
@ -90,6 +96,10 @@ public class UpdateGuestOsCmd extends BaseAsyncCmd {
return display; return display;
} }
public Long getOsCategoryId() {
return osCategoryId;
}
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////// API Implementation/////////////////// /////////////// API Implementation///////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////

View File

@ -26,7 +26,9 @@ import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import com.cloud.cpu.CPU;
import com.cloud.storage.GuestOsCategory; import com.cloud.storage.GuestOsCategory;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
@ -39,12 +41,48 @@ public class ListGuestOsCategoriesCmd extends BaseListCmd {
//////////////// API parameters ///////////////////// //////////////// API parameters /////////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, description = "list Os category by id") @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, description = "List OS category by id")
private Long id; private Long id;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "list os category by name", since = "3.0.1") @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List OS category by name", since = "3.0.1")
private String name; private String name;
@Parameter(name = ApiConstants.IS_FEATURED,
type = CommandType.BOOLEAN,
description = "List available OS categories by featured or not",
since = "4.21.0")
private Boolean featured;
@Parameter(name = ApiConstants.IS_ISO,
type = CommandType.BOOLEAN,
description = "List OS categories for which an ISO is available",
since = "4.21.0")
private Boolean iso;
@Parameter(name = ApiConstants.IS_VNF, type = CommandType.BOOLEAN,
description = "List OS categories for which a VNF template is available",
since = "4.21.0")
private Boolean vnf;
@Parameter(name = ApiConstants.ZONE_ID,
type = CommandType.UUID,
entityType = ZoneResponse.class,
description = "List available OS categories types for the zone",
since = "4.21.0")
private Long zoneId;
@Parameter(name = ApiConstants.ARCH,
type = CommandType.STRING,
description = "List OS categories types available for given CPU architecture",
since = "4.21.0")
private String arch;
@Parameter(name = ApiConstants.SHOW_RESOURCE_ICON,
type = CommandType.BOOLEAN,
description = "flag to display the resource image for the OS category",
since = "4.21.0")
private Boolean showIcon;
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////////// Accessors /////////////////////// /////////////////// Accessors ///////////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -57,6 +95,30 @@ public class ListGuestOsCategoriesCmd extends BaseListCmd {
return name; return name;
} }
public Boolean isFeatured() {
return featured;
}
public Boolean isIso() {
return iso;
}
public Boolean isVnf() {
return vnf;
}
public Long getZoneId() {
return zoneId;
}
public CPU.CPUArch getArch() {
return arch == null ? null : CPU.CPUArch.fromType(arch);
}
public boolean isShowIcon() {
return Boolean.TRUE.equals(showIcon);
}
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////// API Implementation/////////////////// /////////////// API Implementation///////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -64,14 +126,11 @@ public class ListGuestOsCategoriesCmd extends BaseListCmd {
@Override @Override
public void execute() { public void execute() {
Pair<List<? extends GuestOsCategory>, Integer> result = _mgr.listGuestOSCategoriesByCriteria(this); Pair<List<? extends GuestOsCategory>, Integer> result = _mgr.listGuestOSCategoriesByCriteria(this);
ListResponse<GuestOSCategoryResponse> response = new ListResponse<GuestOSCategoryResponse>(); ListResponse<GuestOSCategoryResponse> response = new ListResponse<>();
List<GuestOSCategoryResponse> osCatResponses = new ArrayList<GuestOSCategoryResponse>(); List<GuestOSCategoryResponse> osCatResponses = new ArrayList<>();
for (GuestOsCategory osCategory : result.first()) { for (GuestOsCategory osCategory : result.first()) {
GuestOSCategoryResponse categoryResponse = new GuestOSCategoryResponse(); GuestOSCategoryResponse categoryResponse = _responseGenerator.createGuestOSCategoryResponse(osCategory,
categoryResponse.setId(osCategory.getUuid()); isShowIcon());
categoryResponse.setName(osCategory.getName());
categoryResponse.setObjectName("oscategory");
osCatResponses.add(categoryResponse); osCatResponses.add(categoryResponse);
} }

View File

@ -16,11 +16,6 @@
// under the License. // under the License.
package org.apache.cloudstack.api.command.user.iso; package org.apache.cloudstack.api.command.user.iso;
import com.cloud.cpu.CPU;
import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceTag;
import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
@ -28,16 +23,17 @@ import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import com.cloud.template.VirtualMachineTemplate.TemplateFilter;
import com.cloud.user.Account;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.List; import com.cloud.cpu.CPU;
import com.cloud.server.ResourceTag;
import com.cloud.template.VirtualMachineTemplate.TemplateFilter;
import com.cloud.user.Account;
@APICommand(name = "listIsos", description = "Lists all available ISO files.", responseObject = TemplateResponse.class, responseView = ResponseView.Restricted, @APICommand(name = "listIsos", description = "Lists all available ISO files.", responseObject = TemplateResponse.class, responseView = ResponseView.Restricted,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@ -95,6 +91,11 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd {
since = "4.20") since = "4.20")
private String arch; private String arch;
@Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType= GuestOSCategoryResponse.class,
description = "the ID of the OS category for the ISO",
since = "4.21.0")
private Long osCategoryId;
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////////// Accessors /////////////////////// /////////////////// Accessors ///////////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -173,6 +174,10 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd {
return CPU.CPUArch.fromType(arch); return CPU.CPUArch.fromType(arch);
} }
public Long getOsCategoryId() {
return osCategoryId;
}
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////// API Implementation/////////////////// /////////////// API Implementation///////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -190,24 +195,14 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd {
@Override @Override
public void execute() { public void execute() {
ListResponse<TemplateResponse> response = _queryService.listIsos(this); ListResponse<TemplateResponse> response = _queryService.listIsos(this);
if (response != null && response.getCount() > 0 && getShowIcon()) { if (response != null && getShowIcon()) {
updateIsoResponse(response.getResponses()); _responseGenerator.updateTemplateIsoResponsesForIcons(response.getResponses(),
ResourceTag.ResourceObjectType.ISO);
} }
response.setResponseName(getCommandName()); response.setResponseName(getCommandName());
setResponseObject(response); 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);
}
}
public Long getStoragePoolId() { public Long getStoragePoolId() {
return null; return null;
}; };

View File

@ -16,17 +16,11 @@
// under the License. // under the License.
package org.apache.cloudstack.api.command.user.template; package org.apache.cloudstack.api.command.user.template;
import com.cloud.cpu.CPU;
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 java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
@ -34,15 +28,20 @@ import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.cpu.CPU;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.server.ResourceTag;
import com.cloud.template.VirtualMachineTemplate; import com.cloud.template.VirtualMachineTemplate;
import com.cloud.template.VirtualMachineTemplate.TemplateFilter; import com.cloud.template.VirtualMachineTemplate.TemplateFilter;
import com.cloud.user.Account; import com.cloud.user.Account;
import org.apache.commons.lang3.StringUtils;
@APICommand(name = "listTemplates", description = "List all public, private, and privileged templates.", responseObject = TemplateResponse.class, entityType = {VirtualMachineTemplate.class}, responseView = ResponseView.Restricted, @APICommand(name = "listTemplates", description = "List all public, private, and privileged templates.", responseObject = TemplateResponse.class, entityType = {VirtualMachineTemplate.class}, responseView = ResponseView.Restricted,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@ -111,6 +110,11 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User
since = "4.20") since = "4.20")
private String arch; private String arch;
@Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType= GuestOSCategoryResponse.class,
description = "the ID of the OS category for the template",
since = "4.21.0")
private Long osCategoryId;
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////////// Accessors /////////////////////// /////////////////// Accessors ///////////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -205,6 +209,10 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User
return CPU.CPUArch.fromType(arch); return CPU.CPUArch.fromType(arch);
} }
public Long getOsCategoryId() {
return osCategoryId;
}
@Override @Override
public String getCommandName() { public String getCommandName() {
return s_name; return s_name;
@ -218,24 +226,14 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User
@Override @Override
public void execute() { public void execute() {
ListResponse<TemplateResponse> response = _queryService.listTemplates(this); ListResponse<TemplateResponse> response = _queryService.listTemplates(this);
if (response != null && response.getCount() > 0 && getShowIcon()) { if (response != null && getShowIcon()) {
updateTemplateResponse(response.getResponses()); _responseGenerator.updateTemplateIsoResponsesForIcons(response.getResponses(),
ResourceTag.ResourceObjectType.Template);
} }
response.setResponseName(getCommandName()); response.setResponseName(getCommandName());
setResponseObject(response); 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() { public List<Long> getIds() {
if (ids == null) { if (ids == null) {
return Collections.emptyList(); return Collections.emptyList();

View File

@ -16,10 +16,15 @@
// under the License. // under the License.
package org.apache.cloudstack.api.command.user.vm; package org.apache.cloudstack.api.command.user.vm;
import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.affinity.AffinityGroupResponse;
@ -54,6 +59,7 @@ import com.cloud.cpu.CPU;
import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.InvalidParameterValueException;
import com.cloud.server.ResourceIcon; import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag;
import com.cloud.storage.GuestOS;
import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine;
@ -331,22 +337,75 @@ public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements
setResponseObject(response); setResponseObject(response);
} }
protected void updateVMResponse(List<UserVmResponse> response) { protected Map<String, ResourceIcon> getResourceIconsUsingOsCategory(List<UserVmResponse> responses) {
for (UserVmResponse vmResponse : response) { Set<String> guestOsIds = responses.stream().map(UserVmResponse::getGuestOsId).collect(Collectors.toSet());
ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.UserVm, vmResponse.getId()); List<GuestOS> guestOSList = _entityMgr.listByUuids(GuestOS.class, guestOsIds);
if (resourceIcon == null) { Map<String, GuestOS> guestOSMap = guestOSList.stream()
ResourceTag.ResourceObjectType type = ResourceTag.ResourceObjectType.Template; .collect(Collectors.toMap(GuestOS::getUuid, Function.identity()));
String uuid = vmResponse.getTemplateId(); Set<Long> guestOsCategoryIds = guestOSMap.values().stream()
if (vmResponse.getIsoId() != null) { .map(GuestOS::getCategoryId)
uuid = vmResponse.getIsoId(); .collect(Collectors.toSet());
type = ResourceTag.ResourceObjectType.ISO; Map<Long, ResourceIcon> guestOsCategoryIcons =
resourceIconManager.getByResourceTypeAndIds(ResourceTag.ResourceObjectType.GuestOsCategory,
guestOsCategoryIds);
Map<String, ResourceIcon> vmIcons = new HashMap<>();
for (UserVmResponse response : responses) {
GuestOS guestOS = guestOSMap.get(response.getGuestOsId());
if (guestOS != null) {
vmIcons.put(response.getId(), guestOsCategoryIcons.get(guestOS.getCategoryId()));
} }
resourceIcon = resourceIconManager.getByResourceTypeAndUuid(type, uuid); }
if (resourceIcon == null) { return vmIcons;
}
protected Map<String, ResourceIcon> getResourceIconsForUsingTemplateIso(List<UserVmResponse> responses) {
Map<String, String> vmTemplateIsoIdMap = new HashMap<>();
Set<String> templateUuids = new HashSet<>();
Set<String> isoUuids = new HashSet<>();
for (UserVmResponse vmResponse : responses) {
if (vmResponse.getTemplateId() != null) {
templateUuids.add(vmResponse.getTemplateId());
vmTemplateIsoIdMap.put(vmResponse.getId(), vmResponse.getTemplateId());
}
if (vmResponse.getIsoId() != null) {
isoUuids.add(vmResponse.getIsoId());
vmTemplateIsoIdMap.put(vmResponse.getId(), vmResponse.getIsoId());
}
}
Map<String, ResourceIcon> templateOrIsoIcons = resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.Template, templateUuids);
templateOrIsoIcons.putAll(resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.ISO, isoUuids));
Map<String, ResourceIcon> vmIcons = new HashMap<>();
List<UserVmResponse> noTemplateIsoIconResponses = new ArrayList<>();
for (UserVmResponse response : responses) {
String uuid = vmTemplateIsoIdMap.get(response.getId());
if (StringUtils.isNotBlank(uuid) && templateOrIsoIcons.containsKey(uuid)) {
vmIcons.put(response.getId(),
templateOrIsoIcons.get(vmTemplateIsoIdMap.get(response.getId())));
continue; continue;
} }
noTemplateIsoIconResponses.add(response);
} }
ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); vmIcons.putAll(getResourceIconsUsingOsCategory(noTemplateIsoIconResponses));
return vmIcons;
}
protected void updateVMResponse(List<UserVmResponse> responses) {
if (CollectionUtils.isEmpty(responses)) {
return;
}
Set<String> vmUuids = responses.stream().map(UserVmResponse::getId).collect(Collectors.toSet());
Map<String, ResourceIcon> vmIcons = resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.UserVm, vmUuids);
List<UserVmResponse> noVmIconResponses = responses
.stream()
.filter(r -> !vmIcons.containsKey(r.getId()))
.collect(Collectors.toList());
vmIcons.putAll(getResourceIconsForUsingTemplateIso(noVmIconResponses));
for (UserVmResponse vmResponse : responses) {
ResourceIcon icon = vmIcons.get(vmResponse.getId());
if (icon == null) {
continue;
}
ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(icon);
vmResponse.setResourceIconResponse(iconResponse); vmResponse.setResourceIconResponse(iconResponse);
} }
} }

View File

@ -16,6 +16,8 @@
// under the License. // under the License.
package org.apache.cloudstack.api.response; package org.apache.cloudstack.api.response;
import java.util.Date;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
@ -26,7 +28,7 @@ import com.cloud.serializer.Param;
import com.cloud.storage.GuestOsCategory; import com.cloud.storage.GuestOsCategory;
@EntityReference(value = GuestOsCategory.class) @EntityReference(value = GuestOsCategory.class)
public class GuestOSCategoryResponse extends BaseResponse { public class GuestOSCategoryResponse extends BaseResponse implements SetResourceIconResponse {
@SerializedName(ApiConstants.ID) @SerializedName(ApiConstants.ID)
@Param(description = "the ID of the OS category") @Param(description = "the ID of the OS category")
private String id; private String id;
@ -35,6 +37,18 @@ public class GuestOSCategoryResponse extends BaseResponse {
@Param(description = "the name of the OS category") @Param(description = "the name of the OS category")
private String name; private String name;
@SerializedName(ApiConstants.IS_FEATURED)
@Param(description = "Whether the OS category is featured", since = "4.21.0")
private Boolean featured;
@SerializedName(ApiConstants.RESOURCE_ICON)
@Param(description = "Base64 string representation of the resource icon", since = "4.21.0")
private ResourceIconResponse resourceIconResponse;
@SerializedName(ApiConstants.CREATED)
@Param(description = "Date when the OS category was created." )
private Date created;
public String getId() { public String getId() {
return id; return id;
} }
@ -50,4 +64,17 @@ public class GuestOSCategoryResponse extends BaseResponse {
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
public void setFeatured(Boolean featured) {
this.featured = featured;
}
@Override
public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) {
this.resourceIconResponse = resourceIconResponse;
}
public void setCreated(Date created) {
this.created = created;
}
} }

View File

@ -93,6 +93,8 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements
@Param(description = "the name of the OS type for this template.") @Param(description = "the name of the OS type for this template.")
private String osTypeName; private String osTypeName;
private transient Long osTypeCategoryId;
@SerializedName(ApiConstants.ACCOUNT_ID) @SerializedName(ApiConstants.ACCOUNT_ID)
@Param(description = "the account id to which the template belongs") @Param(description = "the account id to which the template belongs")
private String accountId; private String accountId;
@ -285,6 +287,14 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements
this.osTypeName = osTypeName; this.osTypeName = osTypeName;
} }
public Long getOsTypeCategoryId() {
return osTypeCategoryId;
}
public void setOsTypeCategoryId(Long osTypeCategoryId) {
this.osTypeCategoryId = osTypeCategoryId;
}
public void setId(String id) { public void setId(String id) {
this.id = id; this.id = id;
} }

View File

@ -0,0 +1,46 @@
// 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.guest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public class AddGuestOsCategoryCmdTest {
@Test
public void testGetArch() {
AddGuestOsCategoryCmd cmd = new AddGuestOsCategoryCmd();
Assert.assertNull(cmd.getName());
String name = "Name";
ReflectionTestUtils.setField(cmd, "name", name);
Assert.assertEquals(name, cmd.getName());
}
@Test
public void testIsFeatured() {
AddGuestOsCategoryCmd cmd = new AddGuestOsCategoryCmd();
Assert.assertFalse(cmd.isFeatured());
ReflectionTestUtils.setField(cmd, "featured", false);
Assert.assertFalse(cmd.isFeatured());
ReflectionTestUtils.setField(cmd, "featured", true);
Assert.assertTrue(cmd.isFeatured());
}
}

View File

@ -0,0 +1,55 @@
// 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.guest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public class UpdateGuestOsCategoryCmdTest {
@Test
public void testGetArch() {
UpdateGuestOsCategoryCmd cmd = new UpdateGuestOsCategoryCmd();
Assert.assertNull(cmd.getName());
String name = "Name";
ReflectionTestUtils.setField(cmd, "name", name);
Assert.assertEquals(name, cmd.getName());
}
@Test
public void testIsFeatured() {
UpdateGuestOsCategoryCmd cmd = new UpdateGuestOsCategoryCmd();
Assert.assertNull(cmd.isFeatured());
ReflectionTestUtils.setField(cmd, "featured", false);
Assert.assertFalse(cmd.isFeatured());
ReflectionTestUtils.setField(cmd, "featured", true);
Assert.assertTrue(cmd.isFeatured());
}
@Test
public void testGetSortKey() {
UpdateGuestOsCategoryCmd cmd = new UpdateGuestOsCategoryCmd();
Assert.assertNull(cmd.getSortKey());
Integer sortKey = 100;
ReflectionTestUtils.setField(cmd, "sortKey", sortKey);
Assert.assertEquals(sortKey, cmd.getSortKey());
}
}

View File

@ -0,0 +1,37 @@
// 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.guest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
public class UpdateGuestOsCmdTest {
@Test
public void testGetZoneId() {
UpdateGuestOsCmd cmd = new UpdateGuestOsCmd();
Assert.assertNull(cmd.getOsCategoryId());
Long osCategoryId = 100L;
ReflectionTestUtils.setField(cmd, "osCategoryId", osCategoryId);
Assert.assertEquals(osCategoryId, cmd.getOsCategoryId());
}
}

View File

@ -0,0 +1,87 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.user.guest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.cpu.CPU;
@RunWith(MockitoJUnitRunner.class)
public class ListGuestOsCategoriesCmdTest {
@Test
public void testIsFeatured() {
ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd();
Assert.assertNull(cmd.isFeatured());
ReflectionTestUtils.setField(cmd, "featured", false);
Assert.assertFalse(cmd.isFeatured());
ReflectionTestUtils.setField(cmd, "featured", true);
Assert.assertTrue(cmd.isFeatured());
}
@Test
public void testIsIso() {
ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd();
Assert.assertNull(cmd.isIso());
ReflectionTestUtils.setField(cmd, "iso", false);
Assert.assertFalse(cmd.isIso());
ReflectionTestUtils.setField(cmd, "iso", true);
Assert.assertTrue(cmd.isIso());
}
@Test
public void testIsVnf() {
ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd();
Assert.assertNull(cmd.isVnf());
ReflectionTestUtils.setField(cmd, "vnf", false);
Assert.assertFalse(cmd.isVnf());
ReflectionTestUtils.setField(cmd, "vnf", true);
Assert.assertTrue(cmd.isVnf());
}
@Test
public void testGetZoneId() {
ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd();
Assert.assertNull(cmd.getZoneId());
Long zoneId = 100L;
ReflectionTestUtils.setField(cmd, "zoneId", zoneId);
Assert.assertEquals(zoneId, cmd.getZoneId());
}
@Test
public void testGetArch() {
ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd();
Assert.assertNull(cmd.getArch());
CPU.CPUArch arch = CPU.CPUArch.getDefault();
ReflectionTestUtils.setField(cmd, "arch", arch.getType());
Assert.assertEquals(arch, cmd.getArch());
}
@Test
public void testIsShowIcon() {
ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd();
Assert.assertFalse(cmd.isShowIcon());
ReflectionTestUtils.setField(cmd, "showIcon", false);
Assert.assertFalse(cmd.isShowIcon());
ReflectionTestUtils.setField(cmd, "showIcon", true);
Assert.assertTrue(cmd.isShowIcon());
}
}

View File

@ -0,0 +1,223 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.user.vm;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.anySet;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.response.UserVmResponse;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceIconManager;
import com.cloud.server.ResourceTag;
import com.cloud.storage.GuestOS;
import com.cloud.utils.db.EntityManager;
public class ListVMsCmdTest {
EntityManager _entityMgr;
ResourceIconManager resourceIconManager;
ResponseGenerator _responseGenerator;
ListVMsCmd cmd;
@Before
public void setup() {
_entityMgr = mock(EntityManager.class);
resourceIconManager = mock(ResourceIconManager.class);
_responseGenerator = mock(ResponseGenerator.class);
cmd = spy(ListVMsCmd.class);
cmd._entityMgr = _entityMgr;
cmd.resourceIconManager = resourceIconManager;
cmd._responseGenerator = _responseGenerator;
}
@Test
public void testUpdateVMResponse_withMixedIcons() {
String vm1Uuid = UUID.randomUUID().toString();
UserVmResponse vm1 = mock(UserVmResponse.class);
when(vm1.getId()).thenReturn(vm1Uuid);
String vm2Uuid = UUID.randomUUID().toString();
UserVmResponse vm2 = mock(UserVmResponse.class);
when(vm2.getId()).thenReturn(vm2Uuid);
List<UserVmResponse> responses = Arrays.asList(vm1, vm2);
ResourceIcon icon1 = mock(ResourceIcon.class);
ResourceIcon icon2 = mock(ResourceIcon.class);
Map<String, ResourceIcon> initialIcons = new HashMap<>();
initialIcons.put(vm1Uuid, icon1);
when(resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.UserVm, Set.of(vm1Uuid, vm2Uuid)))
.thenReturn(initialIcons);
Map<String, ResourceIcon> fallbackIcons = Map.of(vm2Uuid, icon2);
doReturn(fallbackIcons).when(cmd).getResourceIconsForUsingTemplateIso(anyList());
ResourceIconResponse iconResponse1 = new ResourceIconResponse();
ResourceIconResponse iconResponse2 = new ResourceIconResponse();
when(_responseGenerator.createResourceIconResponse(icon1)).thenReturn(iconResponse1);
when(_responseGenerator.createResourceIconResponse(icon2)).thenReturn(iconResponse2);
cmd.updateVMResponse(responses);
verify(vm1).setResourceIconResponse(iconResponse1);
verify(vm2).setResourceIconResponse(iconResponse2);
}
@Test
public void testUpdateVMResponse_withEmptyList() {
cmd.updateVMResponse(Collections.emptyList());
verify(resourceIconManager, never()).getByResourceTypeAndIds(Mockito.any(), Mockito.anyCollection());
}
@Test
public void testGetResourceIconsForUsingTemplateIso_withValidData() {
String vm1Uuid = UUID.randomUUID().toString();
String template1Uuid = UUID.randomUUID().toString();
UserVmResponse vm1 = mock(UserVmResponse.class);
when(vm1.getId()).thenReturn(vm1Uuid);
when(vm1.getTemplateId()).thenReturn(template1Uuid);
when(vm1.getIsoId()).thenReturn(null);
String vm2Uuid = UUID.randomUUID().toString();
String iso2Uuid = UUID.randomUUID().toString();
UserVmResponse vm2 = mock(UserVmResponse.class);
when(vm2.getId()).thenReturn(vm2Uuid);
when(vm2.getTemplateId()).thenReturn(null);
when(vm2.getIsoId()).thenReturn(iso2Uuid);
List<UserVmResponse> responses = Arrays.asList(vm1, vm2);
Map<String, ResourceIcon> templateIcons = new HashMap<>();
templateIcons.put(template1Uuid, mock(ResourceIcon.class));
Map<String, ResourceIcon> isoIcons = new HashMap<>();
isoIcons.put(iso2Uuid, mock(ResourceIcon.class));
when(resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.Template, Set.of(template1Uuid)))
.thenReturn(templateIcons);
when(resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.ISO, Set.of(iso2Uuid)))
.thenReturn(isoIcons);
doReturn(Collections.emptyMap()).when(cmd).getResourceIconsUsingOsCategory(anyList());
Map<String, ResourceIcon> result = cmd.getResourceIconsForUsingTemplateIso(responses);
assertEquals(2, result.size());
assertTrue(result.containsKey(vm1Uuid));
assertTrue(result.containsKey(vm2Uuid));
assertEquals(templateIcons.get(template1Uuid), result.get(vm1Uuid));
assertEquals(isoIcons.get(iso2Uuid), result.get(vm2Uuid));
}
@Test
public void testGetResourceIconsForUsingTemplateIso_withMissingIcons() {
String vm1Uuid = UUID.randomUUID().toString();
String template1Uuid = UUID.randomUUID().toString();
UserVmResponse vm1 = mock(UserVmResponse.class);
when(vm1.getId()).thenReturn(vm1Uuid);
when(vm1.getTemplateId()).thenReturn(template1Uuid);
when(vm1.getIsoId()).thenReturn(null);
List<UserVmResponse> responses = List.of(vm1);
when(resourceIconManager.getByResourceTypeAndUuids(eq(ResourceTag.ResourceObjectType.Template), anySet()))
.thenReturn(Collections.emptyMap());
when(resourceIconManager.getByResourceTypeAndUuids(eq(ResourceTag.ResourceObjectType.ISO), anySet()))
.thenReturn(Collections.emptyMap());
Map<String, ResourceIcon> fallbackIcons = Map.of(vm1Uuid, mock(ResourceIcon.class));
doReturn(fallbackIcons).when(cmd).getResourceIconsUsingOsCategory(anyList());
Map<String, ResourceIcon> result = cmd.getResourceIconsForUsingTemplateIso(responses);
assertEquals(1, result.size());
assertEquals(fallbackIcons.get("vm1"), result.get("vm1"));
}
@Test
public void testGetResourceIconsUsingOsCategory_withValidData() {
String vm1Uuid = UUID.randomUUID().toString();
String os1Uuid = UUID.randomUUID().toString();
UserVmResponse vm1 = mock(UserVmResponse.class);
when(vm1.getGuestOsId()).thenReturn(os1Uuid);
when(vm1.getId()).thenReturn(vm1Uuid);
String vm2Uuid = UUID.randomUUID().toString();
String os2Uuid = UUID.randomUUID().toString();
UserVmResponse vm2 = mock(UserVmResponse.class);
when(vm2.getGuestOsId()).thenReturn(os2Uuid);
when(vm2.getId()).thenReturn(vm2Uuid);
List<UserVmResponse> responses = Arrays.asList(vm1, vm2);
GuestOS guestOS1 = mock(GuestOS.class);
when(guestOS1.getUuid()).thenReturn(os1Uuid);
when(guestOS1.getCategoryId()).thenReturn(10L);
GuestOS guestOS2 = mock(GuestOS.class);
when(guestOS2.getUuid()).thenReturn(os2Uuid);
when(guestOS2.getCategoryId()).thenReturn(20L);
when(_entityMgr.listByUuids(eq(GuestOS.class), anySet()))
.thenReturn(Arrays.asList(guestOS1, guestOS2));
ResourceIcon icon1 = mock(ResourceIcon.class);
ResourceIcon icon2 = mock(ResourceIcon.class);
Map<Long, ResourceIcon> categoryIcons = new HashMap<>();
categoryIcons.put(10L, icon1);
categoryIcons.put(20L, icon2);
when(resourceIconManager.getByResourceTypeAndIds(eq(ResourceTag.ResourceObjectType.GuestOsCategory), anySet()))
.thenReturn(categoryIcons);
Map<String, ResourceIcon> result = cmd.getResourceIconsUsingOsCategory(responses);
assertEquals(2, result.size());
assertEquals(icon1, result.get(vm1Uuid));
assertEquals(icon2, result.get(vm2Uuid));
}
@Test
public void testGetResourceIconsUsingOsCategory_missingGuestOS() {
String vm1Uuid = UUID.randomUUID().toString();
String os1Uuid = UUID.randomUUID().toString();
UserVmResponse vm1 = mock(UserVmResponse.class);
when(vm1.getGuestOsId()).thenReturn(vm1Uuid);
when(vm1.getId()).thenReturn(os1Uuid);
List<UserVmResponse> responses = Collections.singletonList(vm1);
when(_entityMgr.listByUuids(eq(GuestOS.class), anySet()))
.thenReturn(Collections.emptyList());
Map<String, ResourceIcon> result = cmd.getResourceIconsUsingOsCategory(responses);
assertTrue(result.isEmpty());
}
@Test
public void testGetResourceIconsUsingOsCategory_missingIcon() {
UserVmResponse vm1 = mock(UserVmResponse.class);
String vmUuid = UUID.randomUUID().toString();
String osUuid = UUID.randomUUID().toString();
when(vm1.getGuestOsId()).thenReturn(osUuid);
when(vm1.getId()).thenReturn(vmUuid);
List<UserVmResponse> responses = Collections.singletonList(vm1);
GuestOS guestOS1 = mock(GuestOS.class);
when(guestOS1.getCategoryId()).thenReturn(10L);
when(guestOS1.getUuid()).thenReturn(osUuid);
when(_entityMgr.listByUuids(eq(GuestOS.class), anySet()))
.thenReturn(Collections.singletonList(guestOS1));
when(resourceIconManager.getByResourceTypeAndIds(eq(ResourceTag.ResourceObjectType.GuestOsCategory), anySet()))
.thenReturn(Collections.emptyMap());
Map<String, ResourceIcon> result = cmd.getResourceIconsUsingOsCategory(responses);
assertTrue(result.containsKey(vmUuid));
assertNull(result.get(vmUuid));
}
}

View File

@ -22,10 +22,13 @@ import com.cloud.server.ResourceTag;
import com.cloud.utils.db.GenericDao; import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.ResourceIconResponse;
import java.util.Collection;
import java.util.List; import java.util.List;
public interface ResourceIconDao extends GenericDao<ResourceIconVO, Long> { public interface ResourceIconDao extends GenericDao<ResourceIconVO, Long> {
ResourceIconResponse newResourceIconResponse(ResourceIcon resourceIconVO); ResourceIconResponse newResourceIconResponse(ResourceIcon resourceIconVO);
ResourceIconVO findByResourceUuid(String resourceUuid, ResourceTag.ResourceObjectType resourceType); ResourceIconVO findByResourceUuid(String resourceUuid, ResourceTag.ResourceObjectType resourceType);
List<ResourceIconVO> listByResourceTypeAndIds(ResourceTag.ResourceObjectType resourceType, Collection<Long> resourceIds);
List<ResourceIconVO> listByResourceTypeAndUuids(ResourceTag.ResourceObjectType resourceType, Collection<String> resourceUuids);
List<ResourceIconResponse> listResourceIcons(List<String> resourceUuids, ResourceTag.ResourceObjectType resourceType); List<ResourceIconResponse> listResourceIcons(List<String> resourceUuids, ResourceTag.ResourceObjectType resourceType);
} }

View File

@ -24,8 +24,10 @@ import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.commons.collections.CollectionUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
public class ResourceIconDaoImpl extends GenericDaoBase<ResourceIconVO, Long> implements ResourceIconDao { public class ResourceIconDaoImpl extends GenericDaoBase<ResourceIconVO, Long> implements ResourceIconDao {
@ -58,11 +60,36 @@ public class ResourceIconDaoImpl extends GenericDaoBase<ResourceIconVO, Long> im
} }
@Override @Override
public List<ResourceIconResponse> listResourceIcons(List<String> resourceUuids, ResourceTag.ResourceObjectType resourceType) { public List<ResourceIconVO> listByResourceTypeAndIds(ResourceTag.ResourceObjectType resourceType,
Collection<Long> resourceIds) {
if (CollectionUtils.isEmpty(resourceIds)) {
return new ArrayList<>();
}
SearchBuilder<ResourceIconVO> sb = createSearchBuilder();
sb.and("resourceId", sb.entity().getResourceId(), SearchCriteria.Op.IN);
sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<ResourceIconVO> sc = sb.create();
sc.setParameters("resourceId", resourceIds.toArray());
sc.setParameters("resourceType", resourceType);
return listBy(sc);
}
@Override
public List<ResourceIconVO> listByResourceTypeAndUuids(ResourceTag.ResourceObjectType resourceType,
Collection<String> resourceUuids) {
if (CollectionUtils.isEmpty(resourceUuids)) {
return new ArrayList<>();
}
SearchCriteria<ResourceIconVO> sc = AllFieldsSearch.create(); SearchCriteria<ResourceIconVO> sc = AllFieldsSearch.create();
sc.setParameters("uuid", resourceUuids.toArray()); sc.setParameters("uuid", resourceUuids.toArray());
sc.setParameters("resourceType", resourceType); sc.setParameters("resourceType", resourceType);
List<ResourceIconVO> resourceIcons = listBy(sc); return listBy(sc);
}
@Override
public List<ResourceIconResponse> listResourceIcons(List<String> resourceUuids, ResourceTag.ResourceObjectType resourceType) {
List<ResourceIconVO> resourceIcons = listByResourceTypeAndUuids(resourceType, resourceUuids);
List<ResourceIconResponse> iconResponses = new ArrayList<>(); List<ResourceIconResponse> iconResponses = new ArrayList<>();
for (ResourceIconVO resourceIcon : resourceIcons) { for (ResourceIconVO resourceIcon : resourceIcons) {
ResourceIconResponse response = new ResourceIconResponse(); ResourceIconResponse response = new ResourceIconResponse();

View File

@ -16,6 +16,7 @@
// under the License. // under the License.
package com.cloud.storage; package com.cloud.storage;
import java.util.Date;
import java.util.UUID; import java.util.UUID;
import javax.persistence.Column; import javax.persistence.Column;
@ -25,6 +26,8 @@ import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table; import javax.persistence.Table;
import com.cloud.utils.db.GenericDao;
@Entity @Entity
@Table(name = "guest_os_category") @Table(name = "guest_os_category")
public class GuestOSCategoryVO implements GuestOsCategory { public class GuestOSCategoryVO implements GuestOsCategory {
@ -39,6 +42,26 @@ public class GuestOSCategoryVO implements GuestOsCategory {
@Column(name = "uuid") @Column(name = "uuid")
String uuid = UUID.randomUUID().toString(); String uuid = UUID.randomUUID().toString();
@Column(name = "featured")
boolean featured;
@Column(name = "sort_key")
private int sortKey;
@Column(name = GenericDao.CREATED_COLUMN)
private Date created;
@Column(name = GenericDao.REMOVED_COLUMN)
private Date removed;
public GuestOSCategoryVO() {
}
public GuestOSCategoryVO(String name, boolean featured) {
this.name = name;
this.featured = featured;
}
@Override @Override
public long getId() { public long getId() {
return id; return id;
@ -59,7 +82,25 @@ public class GuestOSCategoryVO implements GuestOsCategory {
return this.uuid; return this.uuid;
} }
public void setUuid(String uuid) { @Override
this.uuid = uuid; public boolean isFeatured() {
return featured;
}
public void setFeatured(Boolean featured) {
this.featured = featured;
}
public void setSortKey(int key) {
sortKey = key;
}
public int getSortKey() {
return sortKey;
}
@Override
public Date getCreated() {
return created;
} }
} }

View File

@ -27,7 +27,6 @@ import com.cloud.utils.db.GenericDaoBase;
public class GuestOSCategoryDaoImpl extends GenericDaoBase<GuestOSCategoryVO, Long> implements GuestOSCategoryDao { public class GuestOSCategoryDaoImpl extends GenericDaoBase<GuestOSCategoryVO, Long> implements GuestOSCategoryDao {
protected GuestOSCategoryDaoImpl() { protected GuestOSCategoryDaoImpl() {
} }
@Override @Override

View File

@ -16,14 +16,14 @@
// under the License. // under the License.
package com.cloud.storage.dao; package com.cloud.storage.dao;
import java.util.List;
import java.util.Set;
import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOS;
import com.cloud.storage.GuestOSVO; import com.cloud.storage.GuestOSVO;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.db.GenericDao; import com.cloud.utils.db.GenericDao;
import java.util.List;
import java.util.Set;
public interface GuestOSDao extends GenericDao<GuestOSVO, Long> { public interface GuestOSDao extends GenericDao<GuestOSVO, Long> {
GuestOSVO findOneByDisplayName(String displayName); GuestOSVO findOneByDisplayName(String displayName);
@ -36,4 +36,6 @@ public interface GuestOSDao extends GenericDao<GuestOSVO, Long> {
List<GuestOSVO> listByDisplayName(String displayName); List<GuestOSVO> listByDisplayName(String displayName);
Pair<List<? extends GuestOS>, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, Long id, Long osCategoryId, String description, String keyword, Boolean forDisplay); Pair<List<? extends GuestOS>, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, Long id, Long osCategoryId, String description, String keyword, Boolean forDisplay);
List<Long> listIdsByCategoryId(final long categoryId);
} }

View File

@ -25,19 +25,20 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import com.cloud.storage.GuestOS;
import com.cloud.utils.Pair;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.TransactionLegacy;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.cloud.storage.GuestOS;
import com.cloud.storage.GuestOSVO; import com.cloud.storage.GuestOSVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Filter; import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.TransactionLegacy;
import com.cloud.utils.exception.CloudRuntimeException;
@Component @Component
public class GuestOSDaoImpl extends GenericDaoBase<GuestOSVO, Long> implements GuestOSDao { public class GuestOSDaoImpl extends GenericDaoBase<GuestOSVO, Long> implements GuestOSDao {
@ -152,4 +153,14 @@ public class GuestOSDaoImpl extends GenericDaoBase<GuestOSVO, Long> implements G
return new Pair<>(result.first(), result.second()); return new Pair<>(result.first(), result.second());
} }
@Override
public List<Long> listIdsByCategoryId(final long categoryId) {
GenericSearchBuilder<GuestOSVO, Long> sb = createSearchBuilder(Long.class);
sb.selectFields(sb.entity().getId());
sb.and("categoryId", sb.entity().getCategoryId(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<Long> sc = sb.create();
sc.setParameters("categoryId", categoryId);
return customSearch(sc, null);
}
} }

View File

@ -58,6 +58,8 @@ public interface VMTemplateDao extends GenericDao<VMTemplateVO, Long>, StateDao<
public List<VMTemplateVO> listInZoneByState(long dataCenterId, VirtualMachineTemplate.State... states); public List<VMTemplateVO> listInZoneByState(long dataCenterId, VirtualMachineTemplate.State... states);
public List<Long> listTemplateIsoByArchVnfAndZone(Long dataCenterId, CPU.CPUArch arch, Boolean isIso, Boolean isVnf);
public List<VMTemplateVO> listAllActive(); public List<VMTemplateVO> listAllActive();
public List<VMTemplateVO> listByState(VirtualMachineTemplate.State... states); public List<VMTemplateVO> listByState(VirtualMachineTemplate.State... states);

View File

@ -17,6 +17,7 @@
package com.cloud.storage.dao; package com.cloud.storage.dao;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
@ -520,6 +521,48 @@ public class VMTemplateDaoImpl extends GenericDaoBase<VMTemplateVO, Long> implem
return listBy(sc); return listBy(sc);
} }
@Override
public List<Long> listTemplateIsoByArchVnfAndZone(Long dataCenterId, CPU.CPUArch arch, Boolean isIso,
Boolean isVnf) {
GenericSearchBuilder<VMTemplateVO, Long> sb = createSearchBuilder(Long.class);
sb.select(null, Func.DISTINCT, sb.entity().getGuestOSId());
sb.and("state", sb.entity().getState(), SearchCriteria.Op.IN);
sb.and("type", sb.entity().getTemplateType(), SearchCriteria.Op.IN);
sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ);
if (isIso != null) {
sb.and("isIso", sb.entity().getFormat(), isIso ? SearchCriteria.Op.EQ : SearchCriteria.Op.NEQ);
}
if (dataCenterId != null) {
SearchBuilder<VMTemplateZoneVO> templateZoneSearch = _templateZoneDao.createSearchBuilder();
templateZoneSearch.and("removed", templateZoneSearch.entity().getRemoved(), SearchCriteria.Op.NULL);
templateZoneSearch.and("zoneId", templateZoneSearch.entity().getZoneId(), SearchCriteria.Op.EQ);
sb.join("templateZoneSearch", templateZoneSearch, templateZoneSearch.entity().getTemplateId(),
sb.entity().getId(), JoinBuilder.JoinType.INNER);
templateZoneSearch.done();
}
sb.done();
SearchCriteria<Long> sc = sb.create();
List<TemplateType> types = new ArrayList<>(Arrays.asList(TemplateType.USER, TemplateType.BUILTIN,
TemplateType.PERHOST));
if (isVnf == null) {
types.add(TemplateType.VNF);
} else if (isVnf) {
types = Collections.singletonList(TemplateType.VNF);
}
sc.setParameters("type", types.toArray());
sc.setParameters("state", VirtualMachineTemplate.State.Active);
if (dataCenterId != null) {
sc.setJoinParameters("templateZoneSearch", "zoneId", dataCenterId);
}
if (arch != null) {
sc.setParameters("arch", arch);
}
if (isIso != null) {
sc.setParameters("isIso", ImageFormat.ISO);
}
return customSearch(sc, null);
}
@Override @Override
public List<VMTemplateVO> listAllActive() { public List<VMTemplateVO> listAllActive() {
SearchCriteria<VMTemplateVO> sc = ActiveTmpltSearch.create(); SearchCriteria<VMTemplateVO> sc = ActiveTmpltSearch.create();

View File

@ -80,3 +80,93 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host', 'storage_access_groups', 'var
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.cluster', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the cluster"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.cluster', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the cluster"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host_pod_ref', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the pod"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host_pod_ref', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the pod"');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.data_center', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the zone"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.data_center', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the zone"');
-- Add featured column for guest_os_category
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'featured', 'tinyint(1) NOT NULL DEFAULT 0 COMMENT "whether the category is featured or not" AFTER `uuid`');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'sort_key', 'int NOT NULL DEFAULT 0 COMMENT "sort key used for customising sort method" AFTER `featured`');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'created', 'datetime COMMENT "date on which the category was created" AFTER `sort_key`');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'removed', 'datetime COMMENT "date removed if not null" AFTER `created`');
UPDATE `cloud`.`guest_os_category` SET `featured` = 1 WHERE `name` NOT IN ('Novel', 'None');
-- Begin: Changes for Guest OS category cleanup
-- Add new OS categories if not present
DROP PROCEDURE IF EXISTS `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`;
CREATE PROCEDURE `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`(IN os_name VARCHAR(255))
BEGIN
IF NOT EXISTS ((SELECT 1 FROM `cloud`.`guest_os_category` WHERE name = os_name))
THEN
INSERT INTO `cloud`.`guest_os_category` (name, uuid)
VALUES (os_name, UUID())
; END IF
; END;
CALL `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`('Fedora');
CALL `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`('Rocky Linux');
CALL `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`('Alma Linux');
-- Move existing guest OS to new categories
DROP PROCEDURE IF EXISTS `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`;
CREATE PROCEDURE `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`(IN category_name VARCHAR(255), IN os_name VARCHAR(255))
BEGIN
DECLARE category_id BIGINT
; SELECT `id` INTO category_id
FROM `cloud`.`guest_os_category`
WHERE `name` = category_name
LIMIT 1
; IF category_id IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Category not found'
; END IF
; UPDATE `cloud`.`guest_os`
SET `category_id` = category_id
WHERE `display_name` LIKE CONCAT('%', os_name, '%')
; END;
CALL `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`('Rocky Linux', 'Rocky Linux');
CALL `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`('Alma Linux', 'Alma Linux');
CALL `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`('Fedora', 'Fedora');
-- Move existing guest OS whose category will be deleted to Other category
DROP PROCEDURE IF EXISTS `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`;
CREATE PROCEDURE `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`(IN to_category_name VARCHAR(255), IN from_category_name VARCHAR(255))
BEGIN
DECLARE done INT DEFAULT 0
; DECLARE to_category_id BIGINT
; SELECT id INTO to_category_id
FROM `cloud`.`guest_os_category`
WHERE `name` = to_category_name
LIMIT 1
; IF to_category_id IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'ToCategory not found'
; END IF
; UPDATE `cloud`.`guest_os`
SET `category_id` = to_category_id
WHERE `category_id` = (SELECT `id` FROM `cloud`.`guest_os_category` WHERE `name` = from_category_name)
; UPDATE `cloud`.`guest_os_category` SET `removed`=now() WHERE `name` = from_category_name
; END;
CALL `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`('Other', 'Novel');
CALL `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`('Other', 'None');
CALL `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`('Other', 'Unix');
CALL `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`('Other', 'Mac');
-- Add featured column for cloud.guest_os_category
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'featured', 'tinyint(1) NOT NULL DEFAULT 0 COMMENT "whether the category is featured or not" AFTER `uuid`');
UPDATE `cloud`.`guest_os_category` SET featured = 1;
-- Add sort_key column for cloud.guest_os_category
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'sort_key', 'int NOT NULL DEFAULT 0 COMMENT "sort key used for customising sort method" AFTER `featured`');
-- Update sort order for all guest OS categories
UPDATE `cloud`.`guest_os_category`
SET `sort_key` = CASE
WHEN `name` = 'Ubuntu' THEN 1
WHEN `name` = 'Debian' THEN 2
WHEN `name` = 'Fedora' THEN 3
WHEN `name` = 'CentOS' THEN 4
WHEN `name` = 'Rocky Linux' THEN 5
WHEN `name` = 'Alma Linux' THEN 6
WHEN `name` = 'Oracle' THEN 7
WHEN `name` = 'RedHat' THEN 8
WHEN `name` = 'SUSE' THEN 9
WHEN `name` = 'Windows' THEN 10
WHEN `name` = 'Other' THEN 11
ELSE `sort_key`
END;
-- End: Changes for Guest OS category cleanup

View File

@ -41,6 +41,7 @@ SELECT
`vm_template`.`guest_os_id` AS `guest_os_id`, `vm_template`.`guest_os_id` AS `guest_os_id`,
`guest_os`.`uuid` AS `guest_os_uuid`, `guest_os`.`uuid` AS `guest_os_uuid`,
`guest_os`.`display_name` AS `guest_os_name`, `guest_os`.`display_name` AS `guest_os_name`,
`guest_os`.`category_id` AS `guest_os_category_id`,
`vm_template`.`bootable` AS `bootable`, `vm_template`.`bootable` AS `bootable`,
`vm_template`.`prepopulate` AS `prepopulate`, `vm_template`.`prepopulate` AS `prepopulate`,
`vm_template`.`cross_zones` AS `cross_zones`, `vm_template`.`cross_zones` AS `cross_zones`,

View File

@ -21,7 +21,11 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.util.ArrayList; import java.util.ArrayList;
@ -44,8 +48,11 @@ import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.Storage; import com.cloud.storage.Storage;
import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VMTemplateZoneVO;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter; import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericSearchBuilder;
import com.cloud.utils.db.JoinBuilder;
import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria;
@ -55,6 +62,9 @@ public class VMTemplateDaoImplTest {
@Mock @Mock
HostDao hostDao; HostDao hostDao;
@Mock
VMTemplateZoneDao templateZoneDao;
@Spy @Spy
@InjectMocks @InjectMocks
VMTemplateDaoImpl templateDao = new VMTemplateDaoImpl(); VMTemplateDaoImpl templateDao = new VMTemplateDaoImpl();
@ -186,4 +196,107 @@ public class VMTemplateDaoImplTest {
VMTemplateVO result = templateDao.findLatestTemplateByTypeAndHypervisorAndArch(hypervisorType, arch, type); VMTemplateVO result = templateDao.findLatestTemplateByTypeAndHypervisorAndArch(hypervisorType, arch, type);
assertNull(result); assertNull(result);
} }
private void mockTemplateZoneJoin() {
VMTemplateZoneVO templateZoneVO = mock(VMTemplateZoneVO.class);
SearchBuilder<VMTemplateZoneVO> templateZoneVOSearchBuilder = mock(SearchBuilder.class);
when(templateZoneVOSearchBuilder.entity()).thenReturn(templateZoneVO);
when(templateZoneDao.createSearchBuilder()).thenReturn(templateZoneVOSearchBuilder);
}
@Test
public void testListTemplateIsoByArchAndZone_WithDataCenterId() {
Long dataCenterId = 1L;
CPU.CPUArch arch = CPU.CPUArch.getDefault();
Boolean isIso = true;
VMTemplateVO templateVO = mock(VMTemplateVO.class);
GenericSearchBuilder<VMTemplateVO, Long> searchBuilder = mock(GenericSearchBuilder.class);
when(searchBuilder.entity()).thenReturn(templateVO);
SearchCriteria<Long>searchCriteria = mock(SearchCriteria.class);
when(templateDao.createSearchBuilder(Long.class)).thenReturn(searchBuilder);
when(searchBuilder.create()).thenReturn(searchCriteria);
mockTemplateZoneJoin();
doReturn(new ArrayList<>()).when(templateDao).customSearch(searchCriteria, null);
List<Long> result = templateDao.listTemplateIsoByArchVnfAndZone(dataCenterId, arch, isIso, false);
assertNotNull(result);
verify(searchBuilder, times(1)).select(null, SearchCriteria.Func.DISTINCT, templateVO.getGuestOSId());
verify(searchBuilder, times(1)).and(eq("state"), any(), eq(SearchCriteria.Op.IN));
verify(searchBuilder, times(1)).and(eq("type"), any(), eq(SearchCriteria.Op.IN));
verify(searchBuilder, times(1)).and(eq("arch"), any(), eq(SearchCriteria.Op.EQ));
verify(searchBuilder, times(1)).and(eq("isIso"), any(), eq(SearchCriteria.Op.EQ));
verify(searchBuilder, times(1)).join(eq("templateZoneSearch"), any(), any(), any(), eq(JoinBuilder.JoinType.INNER));
verify(templateDao, times(1)).customSearch(searchCriteria, null);
}
@Test
public void testListTemplateIsoByArchAndZone_WithoutDataCenterId() {
Long dataCenterId = null;
CPU.CPUArch arch = CPU.CPUArch.getDefault();
Boolean isIso = false;
VMTemplateVO templateVO = mock(VMTemplateVO.class);
GenericSearchBuilder<VMTemplateVO, Long> searchBuilder = mock(GenericSearchBuilder.class);
when(searchBuilder.entity()).thenReturn(templateVO);
SearchCriteria<Long>searchCriteria = mock(SearchCriteria.class);
when(templateDao.createSearchBuilder(Long.class)).thenReturn(searchBuilder);
when(searchBuilder.create()).thenReturn(searchCriteria);
doReturn(new ArrayList<>()).when(templateDao).customSearch(searchCriteria, null);
List<Long> result = templateDao.listTemplateIsoByArchVnfAndZone(dataCenterId, arch, isIso, false);
assertNotNull(result);
verify(searchBuilder, times(1)).select(null, SearchCriteria.Func.DISTINCT, templateVO.getGuestOSId());
verify(searchBuilder, times(1)).and(eq("state"), any(), eq(SearchCriteria.Op.IN));
verify(searchBuilder, times(1)).and(eq("type"), any(), eq(SearchCriteria.Op.IN));
verify(searchBuilder, times(1)).and(eq("arch"), any(), eq(SearchCriteria.Op.EQ));
verify(searchBuilder, times(1)).and(eq("isIso"), any(), eq(SearchCriteria.Op.NEQ));
verify(searchBuilder, never()).join(eq("templateZoneSearch"), any(), any(), any(), eq(JoinBuilder.JoinType.INNER));
verify(templateDao, times(1)).customSearch(searchCriteria, null);
}
@Test
public void testListTemplateIsoByArchAndZone_WithoutArch() {
Long dataCenterId = 1L;
CPU.CPUArch arch = null;
Boolean isIso = true;
VMTemplateVO templateVO = mock(VMTemplateVO.class);
GenericSearchBuilder<VMTemplateVO, Long> searchBuilder = mock(GenericSearchBuilder.class);
when(searchBuilder.entity()).thenReturn(templateVO);
SearchCriteria<Long>searchCriteria = mock(SearchCriteria.class);
when(templateDao.createSearchBuilder(Long.class)).thenReturn(searchBuilder);
when(searchBuilder.create()).thenReturn(searchCriteria);
mockTemplateZoneJoin();
doReturn(new ArrayList<>()).when(templateDao).customSearch(searchCriteria, null);
List<Long> result = templateDao.listTemplateIsoByArchVnfAndZone(dataCenterId, arch, isIso, false);
assertNotNull(result);
verify(searchBuilder, times(1)).select(null, SearchCriteria.Func.DISTINCT, templateVO.getGuestOSId());
verify(searchBuilder, times(1)).and(eq("state"), any(), eq(SearchCriteria.Op.IN));
verify(searchBuilder, times(1)).and(eq("type"), any(), eq(SearchCriteria.Op.IN));
verify(searchBuilder, times(1)).and(eq("arch"), any(), eq(SearchCriteria.Op.EQ));
verify(searchBuilder, times(1)).and(eq("isIso"), any(), eq(SearchCriteria.Op.EQ));
verify(searchBuilder, times(1)).join(eq("templateZoneSearch"), any(), any(), any(), eq(JoinBuilder.JoinType.INNER));
verify(templateDao, times(1)).customSearch(searchCriteria, null);
}
@Test
public void testListTemplateIsoByArchAndZone_WithoutIsIso() {
Long dataCenterId = 1L;
CPU.CPUArch arch = CPU.CPUArch.getDefault();
Boolean isIso = null;
VMTemplateVO templateVO = mock(VMTemplateVO.class);
GenericSearchBuilder<VMTemplateVO, Long> searchBuilder = mock(GenericSearchBuilder.class);
when(searchBuilder.entity()).thenReturn(templateVO);
SearchCriteria<Long>searchCriteria = mock(SearchCriteria.class);
when(templateDao.createSearchBuilder(Long.class)).thenReturn(searchBuilder);
when(searchBuilder.create()).thenReturn(searchCriteria);
mockTemplateZoneJoin();
doReturn(new ArrayList<>()).when(templateDao).customSearch(searchCriteria, null);
List<Long> result = templateDao.listTemplateIsoByArchVnfAndZone(dataCenterId, arch, isIso, false);
assertNotNull(result);
verify(searchBuilder, times(1)).select(null, SearchCriteria.Func.DISTINCT, templateVO.getGuestOSId());
verify(searchBuilder, times(1)).and(eq("state"), any(), eq(SearchCriteria.Op.IN));
verify(searchBuilder, times(1)).and(eq("type"), any(), eq(SearchCriteria.Op.IN));
verify(searchBuilder, times(1)).and(eq("arch"), any(), eq(SearchCriteria.Op.EQ));
verify(searchBuilder, never()).and(eq("isIso"), any(), eq(SearchCriteria.Op.NEQ));
verify(searchBuilder, never()).and(eq("isIso"), any(), eq(SearchCriteria.Op.EQ));
verify(searchBuilder, times(1)).join(eq("templateZoneSearch"), any(), any(), any(), eq(JoinBuilder.JoinType.INNER));
verify(templateDao, times(1)).customSearch(searchCriteria, null);
}
} }

View File

@ -17,6 +17,7 @@
package com.cloud.dao; package com.cloud.dao;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -56,6 +57,13 @@ public class EntityManagerImpl extends ManagerBase implements EntityManager {
return dao.findByUuid(uuid); return dao.findByUuid(uuid);
} }
@Override
public <T> List<T> listByUuids(Class<T> entityType, Collection<String> uuids) {
// Finds and returns a unique VO using uuid, null if entity not found in db
GenericDao<? extends T, String> dao = (GenericDao<? extends T, String>)GenericDaoBase.getDao(entityType);
return (List<T>)dao.listByUuids(uuids);
}
@Override @Override
public <T> T findByUuidIncludingRemoved(Class<T> entityType, String uuid) { public <T> T findByUuidIncludingRemoved(Class<T> entityType, String uuid) {
// Finds and returns a unique VO using uuid, null if entity not found in db // Finds and returns a unique VO using uuid, null if entity not found in db

View File

@ -17,6 +17,8 @@
package com.cloud.utils.db; package com.cloud.utils.db;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -56,6 +58,10 @@ public interface GenericDao<T, ID extends Serializable> {
// Finds one unique VO using uuid // Finds one unique VO using uuid
T findByUuid(String uuid); T findByUuid(String uuid);
default List<T> listByUuids(Collection<String> uuids) {
return new ArrayList<>();
}
// Finds one unique VO using uuid including removed entities // Finds one unique VO using uuid including removed entities
T findByUuidIncludingRemoved(String uuid); T findByUuidIncludingRemoved(String uuid);

View File

@ -1006,6 +1006,17 @@ public abstract class GenericDaoBase<T, ID extends Serializable> extends Compone
return findOneBy(sc); return findOneBy(sc);
} }
@Override
@DB()
public List<T> listByUuids(final Collection<String> uuids) {
if (org.apache.commons.collections.CollectionUtils.isEmpty(uuids)) {
return Collections.emptyList();
}
SearchCriteria<T> sc = createSearchCriteria();
sc.addAnd("uuid", SearchCriteria.Op.IN, uuids.toArray());
return listBy(sc);
}
@Override @Override
@DB() @DB()
public T findByUuidIncludingRemoved(final String uuid) { public T findByUuidIncludingRemoved(final String uuid) {

View File

@ -39,16 +39,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.bgp.ASNumber;
import com.cloud.bgp.ASNumberRange;
import com.cloud.dc.ASNumberRangeVO;
import com.cloud.dc.ASNumberVO;
import com.cloud.dc.VlanDetailsVO;
import com.cloud.dc.dao.ASNumberDao;
import com.cloud.dc.dao.ASNumberRangeDao;
import com.cloud.dc.dao.VlanDetailsDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.BucketVO;
import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroup;
@ -63,12 +53,12 @@ import org.apache.cloudstack.api.BaseResponseWithAssociatedNetwork;
import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd;
import org.apache.cloudstack.api.response.ASNRangeResponse;
import org.apache.cloudstack.api.response.ASNumberResponse;
import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse;
import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse;
import org.apache.cloudstack.api.response.ApplicationLoadBalancerRuleResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerRuleResponse;
import org.apache.cloudstack.api.response.ASNRangeResponse;
import org.apache.cloudstack.api.response.ASNumberResponse;
import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.AsyncJobResponse;
import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScalePolicyResponse;
import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
@ -99,10 +89,10 @@ import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse; import org.apache.cloudstack.api.response.DomainRouterResponse;
import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.EventResponse;
import org.apache.cloudstack.api.response.ExtractResponse; import org.apache.cloudstack.api.response.ExtractResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.FirewallResponse; import org.apache.cloudstack.api.response.FirewallResponse;
import org.apache.cloudstack.api.response.FirewallRuleResponse; import org.apache.cloudstack.api.response.FirewallRuleResponse;
import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse; import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse;
import org.apache.cloudstack.api.response.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.GuestOSResponse;
import org.apache.cloudstack.api.response.GuestOsMappingResponse; import org.apache.cloudstack.api.response.GuestOsMappingResponse;
import org.apache.cloudstack.api.response.GuestVlanRangeResponse; import org.apache.cloudstack.api.response.GuestVlanRangeResponse;
@ -164,6 +154,7 @@ import org.apache.cloudstack.api.response.SecurityGroupResponse;
import org.apache.cloudstack.api.response.SecurityGroupRuleResponse; import org.apache.cloudstack.api.response.SecurityGroupRuleResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.ServiceResponse; import org.apache.cloudstack.api.response.ServiceResponse;
import org.apache.cloudstack.api.response.SharedFSResponse;
import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse; import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse;
import org.apache.cloudstack.api.response.Site2SiteVpnConnectionResponse; import org.apache.cloudstack.api.response.Site2SiteVpnConnectionResponse;
import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse; import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse;
@ -230,10 +221,10 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO;
import org.apache.cloudstack.storage.object.Bucket; import org.apache.cloudstack.storage.object.Bucket;
import org.apache.cloudstack.storage.object.ObjectStore; import org.apache.cloudstack.storage.object.ObjectStore;
import org.apache.cloudstack.storage.sharedfs.SharedFS;
import org.apache.cloudstack.storage.sharedfs.query.vo.SharedFSJoinVO;
import org.apache.cloudstack.usage.Usage; import org.apache.cloudstack.usage.Usage;
import org.apache.cloudstack.usage.UsageService; import org.apache.cloudstack.usage.UsageService;
import org.apache.cloudstack.usage.UsageTypes; import org.apache.cloudstack.usage.UsageTypes;
@ -241,8 +232,8 @@ import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.agent.api.VgpuTypesInfo;
import com.cloud.api.query.ViewResponseHelper; import com.cloud.api.query.ViewResponseHelper;
@ -271,6 +262,8 @@ import com.cloud.api.query.vo.UserVmJoinVO;
import com.cloud.api.query.vo.VolumeJoinVO; import com.cloud.api.query.vo.VolumeJoinVO;
import com.cloud.api.query.vo.VpcOfferingJoinVO; import com.cloud.api.query.vo.VpcOfferingJoinVO;
import com.cloud.api.response.ApiResponseSerializer; import com.cloud.api.response.ApiResponseSerializer;
import com.cloud.bgp.ASNumber;
import com.cloud.bgp.ASNumberRange;
import com.cloud.capacity.Capacity; import com.cloud.capacity.Capacity;
import com.cloud.capacity.CapacityVO; import com.cloud.capacity.CapacityVO;
import com.cloud.capacity.dao.CapacityDaoImpl.SummedCapacity; import com.cloud.capacity.dao.CapacityDaoImpl.SummedCapacity;
@ -279,6 +272,8 @@ import com.cloud.configuration.Resource.ResourceOwnerType;
import com.cloud.configuration.Resource.ResourceType; import com.cloud.configuration.Resource.ResourceType;
import com.cloud.configuration.ResourceCount; import com.cloud.configuration.ResourceCount;
import com.cloud.configuration.ResourceLimit; import com.cloud.configuration.ResourceLimit;
import com.cloud.dc.ASNumberRangeVO;
import com.cloud.dc.ASNumberVO;
import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsDao;
import com.cloud.dc.ClusterVO; import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter;
@ -289,7 +284,11 @@ import com.cloud.dc.Pod;
import com.cloud.dc.StorageNetworkIpRange; import com.cloud.dc.StorageNetworkIpRange;
import com.cloud.dc.Vlan; import com.cloud.dc.Vlan;
import com.cloud.dc.Vlan.VlanType; import com.cloud.dc.Vlan.VlanType;
import com.cloud.dc.VlanDetailsVO;
import com.cloud.dc.VlanVO; import com.cloud.dc.VlanVO;
import com.cloud.dc.dao.ASNumberDao;
import com.cloud.dc.dao.ASNumberRangeDao;
import com.cloud.dc.dao.VlanDetailsDao;
import com.cloud.domain.Domain; import com.cloud.domain.Domain;
import com.cloud.domain.DomainVO; import com.cloud.domain.DomainVO;
import com.cloud.event.Event; import com.cloud.event.Event;
@ -299,6 +298,7 @@ import com.cloud.gpu.GPU;
import com.cloud.host.ControlState; import com.cloud.host.ControlState;
import com.cloud.host.Host; import com.cloud.host.Host;
import com.cloud.host.HostVO; import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.HypervisorCapabilities; import com.cloud.hypervisor.HypervisorCapabilities;
import com.cloud.network.GuestVlan; import com.cloud.network.GuestVlan;
import com.cloud.network.GuestVlanRange; import com.cloud.network.GuestVlanRange;
@ -377,10 +377,13 @@ import com.cloud.projects.ProjectAccount;
import com.cloud.projects.ProjectInvitation; import com.cloud.projects.ProjectInvitation;
import com.cloud.region.ha.GlobalLoadBalancerRule; import com.cloud.region.ha.GlobalLoadBalancerRule;
import com.cloud.resource.RollingMaintenanceManager; import com.cloud.resource.RollingMaintenanceManager;
import com.cloud.resource.icon.ResourceIconVO;
import com.cloud.server.ResourceIcon; import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceIconManager;
import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag;
import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.server.ResourceTag.ResourceObjectType;
import com.cloud.service.ServiceOfferingVO; import com.cloud.service.ServiceOfferingVO;
import com.cloud.storage.BucketVO;
import com.cloud.storage.DataStoreRole; import com.cloud.storage.DataStoreRole;
import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOS;
@ -521,6 +524,8 @@ public class ApiResponseHelper implements ResponseGenerator {
BgpPeerDao bgpPeerDao; BgpPeerDao bgpPeerDao;
@Inject @Inject
RoutedIpv4Manager routedIpv4Manager; RoutedIpv4Manager routedIpv4Manager;
@Inject
ResourceIconManager resourceIconManager;
@Override @Override
public UserResponse createUserResponse(User user) { public UserResponse createUserResponse(User user) {
@ -3902,6 +3907,30 @@ public class ApiResponseHelper implements ResponseGenerator {
return response; return response;
} }
@Override
public GuestOSCategoryResponse createGuestOSCategoryResponse(GuestOsCategory guestOsCategory) {
return createGuestOSCategoryResponse(guestOsCategory, true);
}
@Override
public GuestOSCategoryResponse createGuestOSCategoryResponse(GuestOsCategory guestOsCategory, boolean showIcon) {
GuestOSCategoryResponse categoryResponse = new GuestOSCategoryResponse();
categoryResponse.setId(guestOsCategory.getUuid());
categoryResponse.setName(guestOsCategory.getName());
categoryResponse.setFeatured(guestOsCategory.isFeatured());
categoryResponse.setCreated(guestOsCategory.getCreated());
if (showIcon) {
ResourceIconVO resourceIcon = ApiDBUtils.getResourceIconByResourceUUID(guestOsCategory.getUuid(),
ResourceObjectType.GuestOsCategory);
if (resourceIcon != null) {
ResourceIconResponse iconResponse = ApiDBUtils.newResourceIconResponse(resourceIcon);
categoryResponse.setResourceIconResponse(iconResponse);
}
}
categoryResponse.setObjectName("oscategory");
return categoryResponse;
}
@Override @Override
public GuestOSResponse createGuestOSResponse(GuestOS guestOS) { public GuestOSResponse createGuestOSResponse(GuestOS guestOS) {
GuestOSResponse response = new GuestOSResponse(); GuestOSResponse response = new GuestOSResponse();
@ -5482,4 +5511,39 @@ public class ApiResponseHelper implements ResponseGenerator {
SharedFSJoinVO sharedFSView = ApiDBUtils.newSharedFSView(sharedFS); SharedFSJoinVO sharedFSView = ApiDBUtils.newSharedFSView(sharedFS);
return ApiDBUtils.newSharedFSResponse(view, sharedFSView); return ApiDBUtils.newSharedFSResponse(view, sharedFSView);
} }
protected Map<String, ResourceIcon> getResourceIconsUsingOsCategory(List<TemplateResponse> responses) {
Set<Long> guestOsCategoryIds = responses.stream().map(TemplateResponse::getOsTypeCategoryId).collect(Collectors.toSet());
Map<Long, ResourceIcon> guestOsCategoryIcons =
resourceIconManager.getByResourceTypeAndIds(ResourceTag.ResourceObjectType.GuestOsCategory,
guestOsCategoryIds);
Map<String, ResourceIcon> vmIcons = new HashMap<>();
for (TemplateResponse response : responses) {
vmIcons.put(response.getId(), guestOsCategoryIcons.get(response.getOsTypeCategoryId()));
}
return vmIcons;
}
@Override
public void updateTemplateIsoResponsesForIcons(List<TemplateResponse> responses,
ResourceTag.ResourceObjectType type) {
if (CollectionUtils.isEmpty(responses)) {
return;
}
Set<String> uuids = responses.stream().map(TemplateResponse::getId).collect(Collectors.toSet());
Map<String, ResourceIcon> templateIcons = resourceIconManager.getByResourceTypeAndUuids(type, uuids);
List<TemplateResponse> noTemplateIconResponses = responses
.stream()
.filter(r -> !templateIcons.containsKey(r.getId()))
.collect(Collectors.toList());
templateIcons.putAll(getResourceIconsUsingOsCategory(noTemplateIconResponses));
for (TemplateResponse response : responses) {
ResourceIcon icon = templateIcons.get(response.getId());
if (icon == null) {
continue;
}
ResourceIconResponse iconResponse = createResourceIconResponse(icon);
response.setResourceIconResponse(iconResponse);
}
}
} }

View File

@ -316,6 +316,7 @@ import com.cloud.storage.VolumeApiServiceImpl;
import com.cloud.storage.VolumeVO; import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.BucketDao; import com.cloud.storage.dao.BucketDao;
import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.storage.dao.StoragePoolTagsDao; import com.cloud.storage.dao.StoragePoolTagsDao;
import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateDao;
@ -643,6 +644,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
@Inject @Inject
HostPodDao podDao; HostPodDao podDao;
@Inject
GuestOSDao guestOSDao;
private SearchCriteria<ServiceOfferingJoinVO> getMinimumCpuServiceOfferingJoinSearchCriteria(int cpu) { private SearchCriteria<ServiceOfferingJoinVO> getMinimumCpuServiceOfferingJoinSearchCriteria(int cpu) {
SearchCriteria<ServiceOfferingJoinVO> sc = _srvOfferingJoinDao.createSearchCriteria(); SearchCriteria<ServiceOfferingJoinVO> sc = _srvOfferingJoinDao.createSearchCriteria();
SearchCriteria<ServiceOfferingJoinVO> sc1 = _srvOfferingJoinDao.createSearchCriteria(); SearchCriteria<ServiceOfferingJoinVO> sc1 = _srvOfferingJoinDao.createSearchCriteria();
@ -4791,7 +4795,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(),
cmd.getImageStoreId(), hypervisorType, showDomr, cmd.listInReadyState(), permittedAccounts, caller, cmd.getImageStoreId(), hypervisorType, showDomr, cmd.listInReadyState(), permittedAccounts, caller,
listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique(), listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique(),
templateType, isVnf, cmd.getArch()); templateType, isVnf, cmd.getArch(), cmd.getOsCategoryId());
} }
private Pair<List<TemplateJoinVO>, Integer> searchForTemplatesInternal(Long templateId, String name, String keyword, private Pair<List<TemplateJoinVO>, Integer> searchForTemplatesInternal(Long templateId, String name, String keyword,
@ -4800,7 +4804,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
boolean showDomr, boolean onlyReady, List<Account> permittedAccounts, Account caller, boolean showDomr, boolean onlyReady, List<Account> permittedAccounts, Account caller,
ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags, ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags,
boolean showRemovedTmpl, List<Long> ids, Long parentTemplateId, Boolean showUnique, String templateType, boolean showRemovedTmpl, List<Long> ids, Long parentTemplateId, Boolean showUnique, String templateType,
Boolean isVnf, CPU.CPUArch arch) { Boolean isVnf, CPU.CPUArch arch, Long osCategoryId) {
// check if zone is configured, if not, just return empty list // check if zone is configured, if not, just return empty list
List<HypervisorType> hypers = null; List<HypervisorType> hypers = null;
@ -4828,10 +4832,13 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
if (storagePoolId != null) { if (storagePoolId != null) {
SearchBuilder<VMTemplateStoragePoolVO> storagePoolSb = templatePoolDao.createSearchBuilder(); SearchBuilder<VMTemplateStoragePoolVO> storagePoolSb = templatePoolDao.createSearchBuilder();
storagePoolSb.and("pool_id", storagePoolSb.entity().getPoolId(), SearchCriteria.Op.EQ);
sb.join("storagePool", storagePoolSb, storagePoolSb.entity().getTemplateId(), sb.entity().getId(), JoinBuilder.JoinType.INNER); sb.join("storagePool", storagePoolSb, storagePoolSb.entity().getTemplateId(), sb.entity().getId(), JoinBuilder.JoinType.INNER);
} }
if (osCategoryId != null) {
sb.and("guestOsIdIN", sb.entity().getGuestOSId(), Op.IN);
}
SearchCriteria<TemplateJoinVO> sc = sb.create(); SearchCriteria<TemplateJoinVO> sc = sb.create();
if (imageStoreId != null) { if (imageStoreId != null) {
@ -4846,6 +4853,15 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
sc.setJoinParameters("storagePool", "pool_id", storagePoolId); sc.setJoinParameters("storagePool", "pool_id", storagePoolId);
} }
if (osCategoryId != null) {
List<Long> guestOsIds = guestOSDao.listIdsByCategoryId(osCategoryId);
if (CollectionUtils.isNotEmpty(guestOsIds)) {
sc.setParameters("guestOsIdIN", guestOsIds.toArray());
} else {
return new Pair<>(new ArrayList<>(), 0);
}
}
// verify templateId parameter and specially handle it // verify templateId parameter and specially handle it
if (templateId != null) { if (templateId != null) {
template = _templateDao.findByIdIncludingRemoved(templateId); // Done for backward compatibility - Bug-5221 template = _templateDao.findByIdIncludingRemoved(templateId); // Done for backward compatibility - Bug-5221
@ -5231,7 +5247,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
return searchForTemplatesInternal(cmd.getId(), cmd.getIsoName(), cmd.getKeyword(), isoFilter, true, cmd.isBootable(), return searchForTemplatesInternal(cmd.getId(), cmd.getIsoName(), cmd.getKeyword(), isoFilter, true, cmd.isBootable(),
cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), cmd.getImageStoreId(), cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), cmd.getImageStoreId(),
hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria,
tags, showRemovedISO, null, null, cmd.getShowUnique(), null, null, cmd.getArch()); tags, showRemovedISO, null, null, cmd.getShowUnique(), null, null,
cmd.getArch(), cmd.getOsCategoryId());
} }
@Override @Override

View File

@ -28,54 +28,53 @@ import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.deployasis.DeployAsIsConstants;
import com.cloud.deployasis.TemplateDeployAsIsDetailVO;
import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.VnfTemplateDetailVO;
import com.cloud.storage.VnfTemplateNicVO;
import com.cloud.storage.dao.VnfTemplateDetailsDao;
import com.cloud.storage.dao.VnfTemplateNicDao;
import com.cloud.user.dao.UserDataDao;
import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.response.VnfNicResponse;
import org.apache.cloudstack.api.response.VnfTemplateResponse;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.utils.security.DigestHelper;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.response.ChildTemplateResponse; import org.apache.cloudstack.api.response.ChildTemplateResponse;
import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.VnfNicResponse;
import org.apache.cloudstack.api.response.VnfTemplateResponse;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateState; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateState;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.cloudstack.utils.security.DigestHelper;
import org.springframework.stereotype.Component;
import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiDBUtils;
import com.cloud.api.ApiResponseHelper; import com.cloud.api.ApiResponseHelper;
import com.cloud.api.query.vo.ResourceTagJoinVO; import com.cloud.api.query.vo.ResourceTagJoinVO;
import com.cloud.api.query.vo.TemplateJoinVO; import com.cloud.api.query.vo.TemplateJoinVO;
import com.cloud.deployasis.DeployAsIsConstants;
import com.cloud.deployasis.TemplateDeployAsIsDetailVO;
import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao;
import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Storage; import com.cloud.storage.Storage;
import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.Storage.TemplateType;
import com.cloud.storage.VMTemplateStoragePoolVO;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VnfTemplateDetailVO;
import com.cloud.storage.VnfTemplateNicVO;
import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VMTemplateDetailsDao; import com.cloud.storage.dao.VMTemplateDetailsDao;
import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.storage.dao.VnfTemplateDetailsDao;
import com.cloud.storage.dao.VnfTemplateNicDao;
import com.cloud.template.VirtualMachineTemplate; import com.cloud.template.VirtualMachineTemplate;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.cloud.user.AccountService; import com.cloud.user.AccountService;
import com.cloud.user.dao.UserDataDao;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter; import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchBuilder;
@ -262,6 +261,7 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
templateResponse.setOsTypeId(template.getGuestOSUuid()); templateResponse.setOsTypeId(template.getGuestOSUuid());
templateResponse.setOsTypeName(template.getGuestOSName()); templateResponse.setOsTypeName(template.getGuestOSName());
templateResponse.setOsTypeCategoryId(template.getGuestOSCategoryId());
// populate owner. // populate owner.
ApiResponseHelper.populateOwner(templateResponse, template); ApiResponseHelper.populateOwner(templateResponse, template);
@ -402,6 +402,7 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
response.setFormat(result.getFormat()); response.setFormat(result.getFormat());
response.setOsTypeId(result.getGuestOSUuid()); response.setOsTypeId(result.getGuestOSUuid());
response.setOsTypeName(result.getGuestOSName()); response.setOsTypeName(result.getGuestOSName());
response.setOsTypeCategoryId(result.getGuestOSCategoryId());
response.setBootable(result.isBootable()); response.setBootable(result.isBootable());
response.setHypervisor(result.getHypervisorType().getHypervisorDisplayName()); response.setHypervisor(result.getHypervisorType().getHypervisorDisplayName());
response.setDynamicallyScalable(result.isDynamicallyScalable()); response.setDynamicallyScalable(result.isDynamicallyScalable());
@ -490,6 +491,7 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
isoResponse.setOsTypeId(iso.getGuestOSUuid()); isoResponse.setOsTypeId(iso.getGuestOSUuid());
isoResponse.setOsTypeName(iso.getGuestOSName()); isoResponse.setOsTypeName(iso.getGuestOSName());
isoResponse.setOsTypeCategoryId(iso.getGuestOSCategoryId());
isoResponse.setBits(iso.getBits()); isoResponse.setBits(iso.getBits());
isoResponse.setPasswordEnabled(iso.isEnablePassword()); isoResponse.setPasswordEnabled(iso.isEnablePassword());

View File

@ -27,19 +27,19 @@ import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import com.cloud.cpu.CPU;
import com.cloud.user.Account;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.util.CPUArchConverter;
import org.apache.cloudstack.util.HypervisorTypeConverter;
import com.cloud.cpu.CPU;
import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.storage.ScopeType; import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage; import com.cloud.storage.Storage;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
import com.cloud.template.VirtualMachineTemplate; import com.cloud.template.VirtualMachineTemplate;
import com.cloud.template.VirtualMachineTemplate.State; import com.cloud.template.VirtualMachineTemplate.State;
import com.cloud.user.Account;
import com.cloud.utils.db.GenericDao; import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.util.CPUArchConverter;
import org.apache.cloudstack.util.HypervisorTypeConverter;
@Entity @Entity
@Table(name = "template_view") @Table(name = "template_view")
@ -112,6 +112,9 @@ public class TemplateJoinVO extends BaseViewWithTagInformationVO implements Cont
@Column(name = "guest_os_name") @Column(name = "guest_os_name")
private String guestOSName; private String guestOSName;
@Column(name = "guest_os_category_id")
private Long guestOSCategoryId;
@Column(name = "bootable") @Column(name = "bootable")
private boolean bootable = true; private boolean bootable = true;
@ -405,6 +408,10 @@ public class TemplateJoinVO extends BaseViewWithTagInformationVO implements Cont
return guestOSName; return guestOSName;
} }
public Long getGuestOSCategoryId() {
return guestOSCategoryId;
}
public boolean isBootable() { public boolean isBootable() {
return bootable; return bootable;
} }

View File

@ -16,8 +16,11 @@
// under the License. // under the License.
package com.cloud.resourceicon; package com.cloud.resourceicon;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
@ -247,4 +250,18 @@ public class ResourceIconManagerImpl extends ManagerBase implements ResourceIcon
sc.setParameters("resourceType", resourceType); sc.setParameters("resourceType", resourceType);
return resourceIconDao.search(sc, null); return resourceIconDao.search(sc, null);
} }
@Override
public Map<Long, ResourceIcon> getByResourceTypeAndIds(ResourceTag.ResourceObjectType resourceType, Collection<Long> resourceIds) {
List<ResourceIconVO> icons = resourceIconDao.listByResourceTypeAndIds(resourceType, resourceIds);
return icons.stream().collect(Collectors.toMap(ResourceIconVO::getResourceId, Function.identity()));
}
@Override
public Map<String, ResourceIcon> getByResourceTypeAndUuids(ResourceTag.ResourceObjectType resourceType, Collection<String> resourceUuids) {
List<ResourceIconVO> icons = resourceIconDao.listByResourceTypeAndUuids(resourceType, resourceUuids);
return icons.stream().collect(Collectors.toMap(ResourceIconVO::getResourceUuid, Function.identity()));
}
} }

View File

@ -89,12 +89,15 @@ import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd;
import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmdByAdmin; import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmdByAdmin;
import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd; import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd;
import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd; import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCategoryCmd;
import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd;
import org.apache.cloudstack.api.command.admin.guest.AddGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsMappingCmd;
import org.apache.cloudstack.api.command.admin.guest.DeleteGuestOsCategoryCmd;
import org.apache.cloudstack.api.command.admin.guest.GetHypervisorGuestOsNamesCmd; import org.apache.cloudstack.api.command.admin.guest.GetHypervisorGuestOsNamesCmd;
import org.apache.cloudstack.api.command.admin.guest.ListGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.ListGuestOsMappingCmd;
import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsCmd;
import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsMappingCmd;
import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCategoryCmd;
import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCmd;
import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsMappingCmd;
import org.apache.cloudstack.api.command.admin.host.AddHostCmd; import org.apache.cloudstack.api.command.admin.host.AddHostCmd;
@ -643,6 +646,8 @@ import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.apache.cloudstack.vm.lease.VMLeaseManager; import org.apache.cloudstack.vm.lease.VMLeaseManager;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.cloud.agent.AgentManager; import com.cloud.agent.AgentManager;
@ -2752,29 +2757,114 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
@Override @Override
public Pair<List<? extends GuestOsCategory>, Integer> listGuestOSCategoriesByCriteria(final ListGuestOsCategoriesCmd cmd) { public Pair<List<? extends GuestOsCategory>, Integer> listGuestOSCategoriesByCriteria(final ListGuestOsCategoriesCmd cmd) {
final Filter searchFilter = new Filter(GuestOSCategoryVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal()); final Filter searchFilter = new Filter(GuestOSCategoryVO.class, "sortKey", true,
cmd.getStartIndex(), cmd.getPageSizeVal());
searchFilter.addOrderBy(GuestOSCategoryVO.class, "id", true);
final Long id = cmd.getId(); final Long id = cmd.getId();
final String name = cmd.getName(); final String name = cmd.getName();
final String keyword = cmd.getKeyword(); final String keyword = cmd.getKeyword();
final Boolean featured = cmd.isFeatured();
final Boolean isIso = cmd.isIso();
final Boolean isVnf = cmd.isVnf();
final Long zoneId = cmd.getZoneId();
final CPU.CPUArch arch = cmd.getArch();
final SearchCriteria<GuestOSCategoryVO> sc = _guestOSCategoryDao.createSearchCriteria(); final SearchBuilder<GuestOSCategoryVO> sb = _guestOSCategoryDao.createSearchBuilder();
sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
sb.and("name", sb.entity().getName(), SearchCriteria.Op.LIKE);
sb.and("keyword", sb.entity().getName(), SearchCriteria.Op.LIKE);
sb.and("featured", sb.entity().isFeatured(), SearchCriteria.Op.EQ);
if (ObjectUtils.anyNotNull(zoneId, arch, isIso, isVnf)) {
final SearchBuilder<GuestOSVO> guestOsSearch = _guestOSDao.createSearchBuilder();
guestOsSearch.and("ids", guestOsSearch.entity().getId(), SearchCriteria.Op.IN);
sb.join("guestOsSearch", guestOsSearch, guestOsSearch.entity().getCategoryId(), sb.entity().getId(),
JoinType.INNER);
guestOsSearch.done();
sb.groupBy(sb.entity().getId());
}
sb.done();
SearchCriteria<GuestOSCategoryVO> sc = sb.create();
if (id != null) { if (id != null) {
sc.addAnd("id", SearchCriteria.Op.EQ, id); sc.setParameters("id", id);
} }
if (name != null) { if (name != null) {
sc.addAnd("name", SearchCriteria.Op.LIKE, "%" + name + "%"); sc.setParameters("name", "%" + name + "%");
} }
if (keyword != null) { if (keyword != null) {
sc.addAnd("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); sc.setParameters("name", "%" + keyword + "%");
}
if (featured != null) {
sc.setParameters("featured", featured);
}
if (ObjectUtils.anyNotNull(zoneId, arch, isIso, isVnf)) {
List<Long> guestOsIds = templateDao.listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf);
if (CollectionUtils.isEmpty(guestOsIds)) {
return new Pair<>(Collections.emptyList(), 0);
}
sc.setJoinParameters("guestOsSearch", "ids", guestOsIds.toArray());
} }
final Pair<List<GuestOSCategoryVO>, Integer> result = _guestOSCategoryDao.searchAndCount(sc, searchFilter); final Pair<List<GuestOSCategoryVO>, Integer> result = _guestOSCategoryDao.searchAndCount(sc, searchFilter);
return new Pair<>(result.first(), result.second()); return new Pair<>(result.first(), result.second());
} }
@Override
@ActionEvent(eventType = EventTypes.EVENT_GUEST_OS_CATEGORY_ADD, eventDescription = "adding OS category")
public GuestOsCategory addGuestOsCategory(AddGuestOsCategoryCmd cmd) {
final String name = cmd.getName();
final boolean featured = cmd.isFeatured();
final GuestOSCategoryVO guestOSCategory = new GuestOSCategoryVO(name, featured);
GuestOsCategory guestOsCategory = _guestOSCategoryDao.persist(guestOSCategory);
CallContext.current().setEventResourceId(guestOsCategory.getId());
CallContext.current().setEventResourceType(ApiCommandResourceType.GuestOsCategory);
return guestOSCategory;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_GUEST_OS_CATEGORY_UPDATE, eventDescription = "updating OS category")
public GuestOsCategory updateGuestOsCategory(UpdateGuestOsCategoryCmd cmd) {
final long id = cmd.getId();
final String name = cmd.getName();
final Boolean featured = cmd.isFeatured();
Integer sortKey = cmd.getSortKey();
final GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(id);
if (guestOSCategory == null) {
throw new InvalidParameterValueException("Invalid OS category ID specified");
}
if (ObjectUtils.allNull(name, featured, sortKey)) {
return guestOSCategory;
}
if (StringUtils.isNotBlank(name)) {
guestOSCategory.setName(name);
}
if (featured != null) {
guestOSCategory.setFeatured(featured);
}
if (sortKey != null) {
guestOSCategory.setSortKey(sortKey);
}
if (!_guestOSCategoryDao.update(id, guestOSCategory)) {
return null;
}
return guestOSCategory;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_GUEST_OS_CATEGORY_DELETE, eventDescription = "deleting OS category")
public boolean deleteGuestOsCategory(DeleteGuestOsCategoryCmd cmd) {
final long id = cmd.getId();
final GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(id);
if (guestOSCategory == null) {
throw new InvalidParameterValueException("Invalid OS category ID specified");
}
List<Long> guestOses = _guestOSDao.listIdsByCategoryId(id);
if (!guestOses.isEmpty()) {
throw new InvalidParameterValueException(String.format(
"Unable to delete the OS category. %d guest OS exist for it.", guestOses.size()));
}
return _guestOSCategoryDao.remove(id);
}
@Override @Override
public Pair<List<? extends GuestOSHypervisor>, Integer> listGuestOSMappingByCriteria(final ListGuestOsMappingCmd cmd) { public Pair<List<? extends GuestOSHypervisor>, Integer> listGuestOSMappingByCriteria(final ListGuestOsMappingCmd cmd) {
final String guestOsId = "guestOsId"; final String guestOsId = "guestOsId";
@ -2992,6 +3082,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
public GuestOS updateGuestOs(final UpdateGuestOsCmd cmd) { public GuestOS updateGuestOs(final UpdateGuestOsCmd cmd) {
final Long id = cmd.getId(); final Long id = cmd.getId();
final String displayName = cmd.getOsDisplayName(); final String displayName = cmd.getOsDisplayName();
final Long osCategoryId = cmd.getOsCategoryId();
final Boolean display = cmd.getForDisplay();
final Map<String, String> details = cmd.getDetails();
boolean updateNeeded = false;
//check if guest OS exists //check if guest OS exists
final GuestOS guestOsHandle = ApiDBUtils.findGuestOSById(id); final GuestOS guestOsHandle = ApiDBUtils.findGuestOSById(id);
@ -2999,27 +3093,46 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
throw new InvalidParameterValueException("Guest OS not found. Please specify a valid ID for the Guest OS"); throw new InvalidParameterValueException("Guest OS not found. Please specify a valid ID for the Guest OS");
} }
if (!guestOsHandle.getIsUserDefined()) {
throw new InvalidParameterValueException("Unable to modify system defined guest OS");
}
persistGuestOsDetails(cmd.getDetails(), id);
//Check if update is needed //Check if update is needed
if (displayName.equals(guestOsHandle.getDisplayName())) { if (StringUtils.isNotBlank(displayName) && !displayName.equals(guestOsHandle.getDisplayName())) {
return guestOsHandle;
}
//Check if another Guest OS by same name exists //Check if another Guest OS by same name exists
final GuestOS duplicate = ApiDBUtils.findGuestOSByDisplayName(displayName); final GuestOS duplicate = ApiDBUtils.findGuestOSByDisplayName(displayName);
if (duplicate != null) { if (duplicate != null) {
throw new InvalidParameterValueException("The specified Guest OS name : " + displayName + " already exists. Please specify a unique guest OS name"); throw new InvalidParameterValueException("The specified Guest OS name : " + displayName + " already exists. Please specify a unique guest OS name");
} }
updateNeeded = true;
}
if (osCategoryId != null) {
if (_guestOSCategoryDao.findById(osCategoryId) == null) {
throw new InvalidParameterValueException("Invalid OS category ID specified");
}
updateNeeded = true;
}
if (!guestOsHandle.getIsUserDefined() && (StringUtils.isNotBlank(displayName) || MapUtils.isNotEmpty(details)
|| display != null)) {
throw new InvalidParameterValueException("Unable to modify system defined guest OS");
}
if (MapUtils.isNotEmpty(details)) {
persistGuestOsDetails(details, id);
}
if (!updateNeeded) {
return guestOsHandle;
}
final GuestOSVO guestOs = _guestOSDao.createForUpdate(id); final GuestOSVO guestOs = _guestOSDao.createForUpdate(id);
if (StringUtils.isNotBlank(displayName)) {
guestOs.setDisplayName(displayName); guestOs.setDisplayName(displayName);
}
if (cmd.getForDisplay() != null) { if (cmd.getForDisplay() != null) {
guestOs.setDisplay(cmd.getForDisplay()); guestOs.setDisplay(cmd.getForDisplay());
} }
if (osCategoryId != null) {
guestOs.setCategoryId(osCategoryId);
}
if (_guestOSDao.update(id, guestOs)) { if (_guestOSDao.update(id, guestOs)) {
return _guestOSDao.findById(id); return _guestOSDao.findById(id);
} else { } else {
@ -3722,6 +3835,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(ListPortForwardingRulesCmd.class); cmdList.add(ListPortForwardingRulesCmd.class);
cmdList.add(UpdatePortForwardingRuleCmd.class); cmdList.add(UpdatePortForwardingRuleCmd.class);
cmdList.add(ListGuestOsCategoriesCmd.class); cmdList.add(ListGuestOsCategoriesCmd.class);
cmdList.add(AddGuestOsCategoryCmd.class);
cmdList.add(UpdateGuestOsCategoryCmd.class);
cmdList.add(DeleteGuestOsCategoryCmd.class);
cmdList.add(ListGuestOsCmd.class); cmdList.add(ListGuestOsCmd.class);
cmdList.add(ListGuestOsMappingCmd.class); cmdList.add(ListGuestOsMappingCmd.class);
cmdList.add(AddGuestOsCmd.class); cmdList.add(AddGuestOsCmd.class);

View File

@ -58,6 +58,7 @@ import com.cloud.server.ResourceManagerUtil;
import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag;
import com.cloud.service.ServiceOfferingVO; import com.cloud.service.ServiceOfferingVO;
import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.GuestOsCategory;
import com.cloud.storage.SnapshotPolicyVO; import com.cloud.storage.SnapshotPolicyVO;
import com.cloud.storage.SnapshotVO; import com.cloud.storage.SnapshotVO;
import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VMTemplateVO;
@ -117,6 +118,7 @@ public class ResourceManagerUtilImpl implements ResourceManagerUtil {
s_typeMap.put(ResourceTag.ResourceObjectType.NetworkOffering, NetworkOfferingVO.class); s_typeMap.put(ResourceTag.ResourceObjectType.NetworkOffering, NetworkOfferingVO.class);
s_typeMap.put(ResourceTag.ResourceObjectType.VpcOffering, VpcOfferingVO.class); s_typeMap.put(ResourceTag.ResourceObjectType.VpcOffering, VpcOfferingVO.class);
s_typeMap.put(ResourceTag.ResourceObjectType.Domain, DomainVO.class); s_typeMap.put(ResourceTag.ResourceObjectType.Domain, DomainVO.class);
s_typeMap.put(ResourceTag.ResourceObjectType.GuestOsCategory, GuestOsCategory.class);
} }
@Inject @Inject

View File

@ -16,37 +16,36 @@
// under the License. // under the License.
package com.cloud.api; package com.cloud.api;
import com.cloud.capacity.Capacity; import static org.junit.Assert.assertEquals;
import com.cloud.configuration.Resource; import static org.junit.Assert.assertNull;
import com.cloud.domain.DomainVO; import static org.junit.Assert.assertTrue;
import com.cloud.network.PublicIpQuarantine; import static org.mockito.ArgumentMatchers.any;
import com.cloud.network.as.AutoScaleVmGroup; import static org.mockito.ArgumentMatchers.anyLong;
import com.cloud.network.as.AutoScaleVmGroupVO; import static org.mockito.Mockito.verify;
import com.cloud.network.as.AutoScaleVmProfileVO; import static org.mockito.Mockito.when;
import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao;
import com.cloud.network.dao.IPAddressDao; import java.lang.reflect.Field;
import com.cloud.network.dao.IPAddressVO; import java.text.ParseException;
import com.cloud.network.dao.LoadBalancerVO; import java.text.SimpleDateFormat;
import com.cloud.network.dao.NetworkServiceMapDao; import java.util.Arrays;
import com.cloud.network.dao.NetworkVO; import java.util.Collections;
import com.cloud.storage.VMTemplateVO; import java.util.Date;
import com.cloud.usage.UsageVO; import java.util.HashMap;
import com.cloud.user.Account; import java.util.List;
import com.cloud.user.AccountManager; import java.util.Map;
import com.cloud.user.AccountVO; import java.util.Set;
import com.cloud.user.User; import java.util.TimeZone;
import com.cloud.user.UserData; import java.util.UUID;
import com.cloud.user.UserDataVO;
import com.cloud.user.UserVO;
import com.cloud.user.dao.UserDataDao;
import com.cloud.utils.net.Ip;
import com.cloud.vm.NicSecondaryIp;
import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse; import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse; import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.IpQuarantineResponse; import org.apache.cloudstack.api.response.IpQuarantineResponse;
import org.apache.cloudstack.api.response.NicSecondaryIpResponse; import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.UnmanagedInstanceResponse; import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse; import org.apache.cloudstack.api.response.UsageRecordResponse;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
@ -63,21 +62,38 @@ import org.mockito.MockedStatic;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.lang.reflect.Field; import com.cloud.capacity.Capacity;
import java.text.ParseException; import com.cloud.configuration.Resource;
import java.text.SimpleDateFormat; import com.cloud.domain.DomainVO;
import java.util.Date; import com.cloud.network.PublicIpQuarantine;
import java.util.List; import com.cloud.network.as.AutoScaleVmGroup;
import java.util.TimeZone; import com.cloud.network.as.AutoScaleVmGroupVO;
import java.util.UUID; import com.cloud.network.as.AutoScaleVmProfileVO;
import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao;
import static org.junit.Assert.assertEquals; import com.cloud.network.dao.IPAddressDao;
import static org.junit.Assert.assertNull; import com.cloud.network.dao.IPAddressVO;
import static org.junit.Assert.assertTrue; import com.cloud.network.dao.LoadBalancerVO;
import static org.mockito.ArgumentMatchers.any; import com.cloud.network.dao.NetworkServiceMapDao;
import static org.mockito.ArgumentMatchers.anyLong; import com.cloud.network.dao.NetworkVO;
import static org.mockito.Mockito.when; import com.cloud.resource.icon.ResourceIconVO;
import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceIconManager;
import com.cloud.server.ResourceTag;
import com.cloud.storage.GuestOsCategory;
import com.cloud.storage.VMTemplateVO;
import com.cloud.usage.UsageVO;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserData;
import com.cloud.user.UserDataVO;
import com.cloud.user.UserVO;
import com.cloud.user.dao.UserDataDao;
import com.cloud.utils.net.Ip;
import com.cloud.vm.NicSecondaryIp;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class ApiResponseHelperTest { public class ApiResponseHelperTest {
@ -105,6 +121,9 @@ public class ApiResponseHelperTest {
@Mock @Mock
IPAddressDao ipAddressDaoMock; IPAddressDao ipAddressDaoMock;
@Mock
ResourceIconManager resourceIconManager;
@Spy @Spy
@InjectMocks @InjectMocks
ApiResponseHelper apiResponseHelper = new ApiResponseHelper(); ApiResponseHelper apiResponseHelper = new ApiResponseHelper();
@ -481,4 +500,135 @@ public class ApiResponseHelperTest {
Assert.assertTrue(apiResponseHelper.capacityListingForSingleNonGpuType(List.of(c1, c2))); Assert.assertTrue(apiResponseHelper.capacityListingForSingleNonGpuType(List.of(c1, c2)));
Assert.assertFalse(apiResponseHelper.capacityListingForSingleNonGpuType(List.of(c1, c2, c3))); Assert.assertFalse(apiResponseHelper.capacityListingForSingleNonGpuType(List.of(c1, c2, c3)));
} }
@Test
public void testCreateGuestOSCategoryResponse_WithResourceIcon() {
GuestOsCategory guestOsCategory = Mockito.mock(GuestOsCategory.class);
ResourceIconVO resourceIconVO = Mockito.mock(ResourceIconVO.class);
String uuid = UUID.randomUUID().toString();
String name = "Ubuntu";
boolean featured = true;
Mockito.when(guestOsCategory.getUuid()).thenReturn(uuid);
Mockito.when(guestOsCategory.getName()).thenReturn(name);
Mockito.when(guestOsCategory.isFeatured()).thenReturn(featured);
ResourceIconResponse mockIconResponse = Mockito.mock(ResourceIconResponse.class);
try (MockedStatic<ApiDBUtils> ignored = Mockito.mockStatic(ApiDBUtils.class)) {
Mockito.when(ApiDBUtils.getResourceIconByResourceUUID(uuid, ResourceTag.ResourceObjectType.GuestOsCategory)).thenReturn(resourceIconVO);
Mockito.when(ApiDBUtils.newResourceIconResponse(resourceIconVO)).thenReturn(mockIconResponse);
GuestOSCategoryResponse response = apiResponseHelper.createGuestOSCategoryResponse(guestOsCategory);
Assert.assertNotNull(response);
Assert.assertEquals(uuid, response.getId());
Assert.assertEquals(name, response.getName());
Object obj = ReflectionTestUtils.getField(response, "featured");
if (obj == null) {
Assert.fail("Invalid featured value");
}
Assert.assertTrue((Boolean)obj);
obj = ReflectionTestUtils.getField(response, "resourceIconResponse");
Assert.assertNotNull(obj);
Assert.assertEquals("oscategory", response.getObjectName());
}
}
@Test
public void testCreateGuestOSCategoryResponse_WithoutResourceIcon() {
GuestOsCategory guestOsCategory = Mockito.mock(GuestOsCategory.class);
String uuid = "1234";
String name = "Ubuntu";
boolean featured = false;
Mockito.when(guestOsCategory.getUuid()).thenReturn(uuid);
Mockito.when(guestOsCategory.getName()).thenReturn(name);
Mockito.when(guestOsCategory.isFeatured()).thenReturn(featured);
try (MockedStatic<ApiDBUtils> ignored = Mockito.mockStatic(ApiDBUtils.class)) {
when(ApiDBUtils.getResourceIconByResourceUUID(uuid, ResourceTag.ResourceObjectType.GuestOsCategory)).thenReturn(null);
GuestOSCategoryResponse response = apiResponseHelper.createGuestOSCategoryResponse(guestOsCategory);
Assert.assertNotNull(response);
Assert.assertEquals(uuid, response.getId());
Assert.assertEquals(name, response.getName());
Object obj = ReflectionTestUtils.getField(response, "featured");
if (obj == null) {
Assert.fail("Invalid featured value");
}
Assert.assertFalse((Boolean)obj);
obj = ReflectionTestUtils.getField(response, "resourceIconResponse");
Assert.assertNull(obj);
Assert.assertEquals("oscategory", response.getObjectName());
}
}
@Test
public void testCreateGuestOSCategoryResponse_WithShowIconFalse() {
GuestOsCategory guestOsCategory = Mockito.mock(GuestOsCategory.class);
Mockito.when(guestOsCategory.getUuid()).thenReturn(UUID.randomUUID().toString());
try (MockedStatic<ApiDBUtils> mockedStatic = Mockito.mockStatic(ApiDBUtils.class)) {
GuestOSCategoryResponse response = apiResponseHelper.createGuestOSCategoryResponse(guestOsCategory, false);
Assert.assertNotNull(response);
mockedStatic.verify(() -> ApiDBUtils.getResourceIconByResourceUUID(Mockito.any(), Mockito.any()),
Mockito.never());
}
}
@Test
public void testGetResourceIconsUsingOsCategory_withValidData() {
TemplateResponse template1 = Mockito.mock(TemplateResponse.class);
when(template1.getId()).thenReturn("t1");
when(template1.getOsTypeCategoryId()).thenReturn(100L);
TemplateResponse template2 = Mockito.mock(TemplateResponse.class);
when(template2.getId()).thenReturn("t2");
when(template2.getOsTypeCategoryId()).thenReturn(200L);
List<TemplateResponse> responses = Arrays.asList(template1, template2);
Map<Long, ResourceIcon> icons = new HashMap<>();
ResourceIcon icon1 = Mockito.mock(ResourceIcon.class);
ResourceIcon icon2 = Mockito.mock(ResourceIcon.class);
icons.put(100L, icon1);
icons.put(200L, icon2);
when(resourceIconManager.getByResourceTypeAndIds(Mockito.eq(ResourceTag.ResourceObjectType.GuestOsCategory), Mockito.anySet()))
.thenReturn(icons);
Map<String, ResourceIcon> result = apiResponseHelper.getResourceIconsUsingOsCategory(responses);
assertEquals(2, result.size());
assertEquals(icon1, result.get("t1"));
assertEquals(icon2, result.get("t2"));
}
@Test
public void testGetResourceIconsUsingOsCategory_missingIcons() {
TemplateResponse template1 = Mockito.mock(TemplateResponse.class);
when(template1.getId()).thenReturn("t1");
when(template1.getOsTypeCategoryId()).thenReturn(100L);
List<TemplateResponse> responses = List.of(template1);
when(resourceIconManager.getByResourceTypeAndIds(Mockito.eq(ResourceTag.ResourceObjectType.GuestOsCategory), Mockito.anySet())).thenReturn(Collections.emptyMap());
Map<String, ResourceIcon> result = apiResponseHelper.getResourceIconsUsingOsCategory(responses);
assertTrue(result.containsKey("t1"));
assertNull(result.get("t1"));
}
@Test
public void testUpdateTemplateIsoResponsesForIcons_withMixedIcons() {
TemplateResponse template1 = Mockito.mock(TemplateResponse.class);
when(template1.getId()).thenReturn("t1");
TemplateResponse template2 = Mockito.mock(TemplateResponse.class);
when(template2.getId()).thenReturn("t2");
List<TemplateResponse> responses = Arrays.asList(template1, template2);
Map<String, ResourceIcon> isoIcons = new HashMap<>();
isoIcons.put("t1", Mockito.mock(ResourceIcon.class));
when(resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.ISO, Set.of("t1", "t2")))
.thenReturn(isoIcons);
Map<String, ResourceIcon> fallbackIcons = Map.of("t2", Mockito.mock(ResourceIcon.class));
Mockito.doReturn(fallbackIcons).when(apiResponseHelper).getResourceIconsUsingOsCategory(Mockito.anyList());
ResourceIconResponse iconResponse1 = new ResourceIconResponse();
ResourceIconResponse iconResponse2 = new ResourceIconResponse();
Mockito.doReturn(iconResponse1).when(apiResponseHelper).createResourceIconResponse(isoIcons.get("t1"));
Mockito.doReturn(iconResponse2).when(apiResponseHelper).createResourceIconResponse(fallbackIcons.get("t2"));
apiResponseHelper.updateTemplateIsoResponsesForIcons(responses, ResourceTag.ResourceObjectType.ISO);
verify(template1).setResourceIconResponse(iconResponse1);
verify(template2).setResourceIconResponse(iconResponse2);
}
@Test
public void testUpdateTemplateIsoResponsesForIcons_emptyInput() {
apiResponseHelper.updateTemplateIsoResponsesForIcons(Collections.emptyList(),
ResourceTag.ResourceObjectType.Template);
Mockito.verify(resourceIconManager, Mockito.never()).getByResourceTypeAndUuids(Mockito.any(),
Mockito.anyCollection());
}
} }

View File

@ -16,6 +16,56 @@
// under the License. // under the License.
package com.cloud.server; package com.cloud.server;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd;
import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCategoryCmd;
import org.apache.cloudstack.api.command.admin.guest.DeleteGuestOsCategoryCmd;
import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCategoryCmd;
import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd;
import org.apache.cloudstack.api.command.user.guest.ListGuestOsCategoriesCmd;
import org.apache.cloudstack.api.command.user.ssh.RegisterSSHKeyPairCmd;
import org.apache.cloudstack.api.command.user.userdata.DeleteUserDataCmd;
import org.apache.cloudstack.api.command.user.userdata.ListUserDataCmd;
import org.apache.cloudstack.api.command.user.userdata.RegisterUserDataCmd;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.framework.config.ConfigDepot;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
import org.apache.cloudstack.userdata.UserDataManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.cpu.CPU;
import com.cloud.dc.Vlan.VlanType; import com.cloud.dc.Vlan.VlanType;
import com.cloud.domain.dao.DomainDao; import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.InvalidParameterValueException;
@ -26,7 +76,12 @@ import com.cloud.host.dao.HostDetailsDao;
import com.cloud.network.IpAddress; import com.cloud.network.IpAddress;
import com.cloud.network.IpAddressManagerImpl; import com.cloud.network.IpAddressManagerImpl;
import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.IPAddressVO;
import com.cloud.storage.GuestOSCategoryVO;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.GuestOsCategory;
import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.dao.GuestOSCategoryDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.cloud.user.AccountManager; import com.cloud.user.AccountManager;
@ -46,46 +101,6 @@ import com.cloud.vm.UserVmDetailVO;
import com.cloud.vm.UserVmVO; import com.cloud.vm.UserVmVO;
import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.UserVmDetailsDao;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd;
import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd;
import org.apache.cloudstack.api.command.user.ssh.RegisterSSHKeyPairCmd;
import org.apache.cloudstack.api.command.user.userdata.DeleteUserDataCmd;
import org.apache.cloudstack.api.command.user.userdata.ListUserDataCmd;
import org.apache.cloudstack.api.command.user.userdata.RegisterUserDataCmd;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.framework.config.ConfigDepot;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
import org.apache.cloudstack.userdata.UserDataManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class ManagementServerImplTest { public class ManagementServerImplTest {
@ -118,7 +133,7 @@ public class ManagementServerImplTest {
UserDataDao _userDataDao; UserDataDao _userDataDao;
@Mock @Mock
VMTemplateDao _templateDao; VMTemplateDao templateDao;
@Mock @Mock
AnnotationDao annotationDao; AnnotationDao annotationDao;
@ -129,9 +144,6 @@ public class ManagementServerImplTest {
@Mock @Mock
UserDataManager userDataManager; UserDataManager userDataManager;
@Spy
ManagementServerImpl spy = new ManagementServerImpl();
@Mock @Mock
UserVmDetailsDao userVmDetailsDao; UserVmDetailsDao userVmDetailsDao;
@ -147,23 +159,22 @@ public class ManagementServerImplTest {
@Mock @Mock
DomainDao domainDao; DomainDao domainDao;
@Mock
GuestOSCategoryDao _guestOSCategoryDao;
@Mock
GuestOSDao _guestOSDao;
@Spy
@InjectMocks
ManagementServerImpl spy = new ManagementServerImpl();
private AutoCloseable closeable; private AutoCloseable closeable;
@Before @Before
public void setup() throws IllegalAccessException, NoSuchFieldException { public void setup() throws IllegalAccessException, NoSuchFieldException {
closeable = MockitoAnnotations.openMocks(this); closeable = MockitoAnnotations.openMocks(this);
CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class)); CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
spy._accountMgr = _accountMgr;
spy.userDataDao = _userDataDao;
spy.templateDao = _templateDao;
spy._userVmDao = _userVmDao;
spy.annotationDao = annotationDao;
spy._UserVmDetailsDao = userVmDetailsDao;
spy._detailsDao = hostDetailsDao;
spy.userDataManager = userDataManager;
spy._configDao = configDao;
spy._configDepot = configDepot;
spy._domainDao = domainDao;
} }
@After @After
@ -407,7 +418,7 @@ public class ManagementServerImplTest {
Mockito.when(userData.getId()).thenReturn(1L); Mockito.when(userData.getId()).thenReturn(1L);
when(_userDataDao.findById(1L)).thenReturn(userData); when(_userDataDao.findById(1L)).thenReturn(userData);
when(_templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(new ArrayList<VMTemplateVO>()); when(templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(new ArrayList<VMTemplateVO>());
when(_userVmDao.findByUserDataId(1L)).thenReturn(new ArrayList<UserVmVO>()); when(_userVmDao.findByUserDataId(1L)).thenReturn(new ArrayList<UserVmVO>());
when(_userDataDao.remove(1L)).thenReturn(true); when(_userDataDao.remove(1L)).thenReturn(true);
@ -437,7 +448,7 @@ public class ManagementServerImplTest {
VMTemplateVO vmTemplateVO = Mockito.mock(VMTemplateVO.class); VMTemplateVO vmTemplateVO = Mockito.mock(VMTemplateVO.class);
List<VMTemplateVO> linkedTemplates = new ArrayList<>(); List<VMTemplateVO> linkedTemplates = new ArrayList<>();
linkedTemplates.add(vmTemplateVO); linkedTemplates.add(vmTemplateVO);
when(_templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(linkedTemplates); when(templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(linkedTemplates);
spy.deleteUserData(cmd); spy.deleteUserData(cmd);
} }
@ -461,7 +472,7 @@ public class ManagementServerImplTest {
Mockito.when(userData.getId()).thenReturn(1L); Mockito.when(userData.getId()).thenReturn(1L);
when(_userDataDao.findById(1L)).thenReturn(userData); when(_userDataDao.findById(1L)).thenReturn(userData);
when(_templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(new ArrayList<VMTemplateVO>()); when(templateDao.findTemplatesLinkedToUserdata(1L)).thenReturn(new ArrayList<VMTemplateVO>());
UserVmVO userVmVO = Mockito.mock(UserVmVO.class); UserVmVO userVmVO = Mockito.mock(UserVmVO.class);
List<UserVmVO> vms = new ArrayList<>(); List<UserVmVO> vms = new ArrayList<>();
@ -740,4 +751,272 @@ public class ManagementServerImplTest {
Assert.assertEquals("0.85", result.first().get(0).getValue()); Assert.assertEquals("0.85", result.first().get(0).getValue());
} }
@Test
public void testAddGuestOsCategory() {
AddGuestOsCategoryCmd addCmd = Mockito.mock(AddGuestOsCategoryCmd.class);
String name = "Ubuntu";
boolean featured = true;
Mockito.when(addCmd.getName()).thenReturn(name);
Mockito.when(addCmd.isFeatured()).thenReturn(featured);
Mockito.doAnswer((Answer<GuestOSCategoryVO>) invocation -> (GuestOSCategoryVO)invocation.getArguments()[0]).when(_guestOSCategoryDao).persist(Mockito.any(GuestOSCategoryVO.class));
GuestOsCategory result = spy.addGuestOsCategory(addCmd);
Assert.assertNotNull(result);
Assert.assertEquals(name, result.getName());
Assert.assertEquals(featured, result.isFeatured());
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).persist(any(GuestOSCategoryVO.class));
}
@Test
public void testUpdateGuestOsCategory() {
UpdateGuestOsCategoryCmd updateCmd = Mockito.mock(UpdateGuestOsCategoryCmd.class);
GuestOSCategoryVO guestOSCategory = new GuestOSCategoryVO("Old name", false);
long id = 1L;
String name = "Updated Name";
Boolean featured = true;
Integer sortKey = 10;
Mockito.when(updateCmd.getId()).thenReturn(id);
Mockito.when(updateCmd.getName()).thenReturn(name);
Mockito.when(updateCmd.isFeatured()).thenReturn(featured);
Mockito.when(updateCmd.getSortKey()).thenReturn(sortKey);
Mockito.when(_guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
Mockito.when(_guestOSCategoryDao.update(Mockito.eq(id), any(GuestOSCategoryVO.class))).thenReturn(true);
GuestOsCategory result = spy.updateGuestOsCategory(updateCmd);
Assert.assertNotNull(result);
Assert.assertEquals(name, result.getName());
Assert.assertEquals(featured, result.isFeatured());
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).findById(id);
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).update(Mockito.eq(id), any(GuestOSCategoryVO.class));
}
@Test(expected = InvalidParameterValueException.class)
public void testUpdateGuestOsCategory_ThrowsExceptionWhenCategoryNotFound() {
UpdateGuestOsCategoryCmd updateCmd = Mockito.mock(UpdateGuestOsCategoryCmd.class);
long id = 1L;
when(updateCmd.getId()).thenReturn(id);
when(_guestOSCategoryDao.findById(id)).thenReturn(null);
spy.updateGuestOsCategory(updateCmd);
}
@Test
public void testUpdateGuestOsCategory_NoChanges() {
UpdateGuestOsCategoryCmd updateCmd = Mockito.mock(UpdateGuestOsCategoryCmd.class);
GuestOSCategoryVO guestOSCategory = new GuestOSCategoryVO("Old name", false);
long id = 1L;
when(updateCmd.getId()).thenReturn(id);
when(updateCmd.getName()).thenReturn(null);
when(updateCmd.isFeatured()).thenReturn(null);
when(updateCmd.getSortKey()).thenReturn(null);
when(_guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
GuestOsCategory result = spy.updateGuestOsCategory(updateCmd);
Assert.assertNotNull(result);
Assert.assertNotNull(result.getName());
Assert.assertFalse(result.isFeatured());
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).findById(id);
Mockito.verify(_guestOSCategoryDao, Mockito.never()).update(Mockito.eq(id), any(GuestOSCategoryVO.class));
}
@Test
public void testUpdateGuestOsCategory_UpdateNameOnly() {
UpdateGuestOsCategoryCmd updateCmd = Mockito.mock(UpdateGuestOsCategoryCmd.class);
GuestOSCategoryVO guestOSCategory = new GuestOSCategoryVO("Old name", false);
long id = 1L;
String name = "Updated Name";
Mockito.when(updateCmd.getId()).thenReturn(id);
Mockito.when(updateCmd.getName()).thenReturn(name);
Mockito.when(updateCmd.isFeatured()).thenReturn(null);
Mockito.when(updateCmd.getSortKey()).thenReturn(null);
Mockito.when(_guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
Mockito.when(_guestOSCategoryDao.update(Mockito.eq(id), any(GuestOSCategoryVO.class))).thenReturn(true);
GuestOsCategory result = spy.updateGuestOsCategory(updateCmd);
Assert.assertNotNull(result);
Assert.assertEquals(name, result.getName());
Assert.assertFalse(result.isFeatured());
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).findById(id);
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).update(Mockito.eq(id), any(GuestOSCategoryVO.class));
}
@Test
public void testDeleteGuestOsCategory_Successful() {
DeleteGuestOsCategoryCmd deleteCmd = Mockito.mock(DeleteGuestOsCategoryCmd.class);
GuestOSCategoryVO guestOSCategory = Mockito.mock(GuestOSCategoryVO.class);
long id = 1L;
Mockito.when(deleteCmd.getId()).thenReturn(id);
Mockito.when(_guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
Mockito.when(_guestOSDao.listIdsByCategoryId(id)).thenReturn(Arrays.asList());
Mockito.when(_guestOSCategoryDao.remove(id)).thenReturn(true);
boolean result = spy.deleteGuestOsCategory(deleteCmd);
Assert.assertTrue(result);
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).findById(id);
Mockito.verify(_guestOSDao, Mockito.times(1)).listIdsByCategoryId(id);
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).remove(id);
}
@Test(expected = InvalidParameterValueException.class)
public void testDeleteGuestOsCategory_ThrowsExceptionWhenCategoryNotFound() {
DeleteGuestOsCategoryCmd deleteCmd = Mockito.mock(DeleteGuestOsCategoryCmd.class);
long id = 1L;
Mockito.when(deleteCmd.getId()).thenReturn(id);
Mockito.when(_guestOSCategoryDao.findById(id)).thenReturn(null);
spy.deleteGuestOsCategory(deleteCmd);
}
@Test(expected = InvalidParameterValueException.class)
public void testDeleteGuestOsCategory_ThrowsExceptionWhenGuestOsExists() {
DeleteGuestOsCategoryCmd deleteCmd = Mockito.mock(DeleteGuestOsCategoryCmd.class);
GuestOSCategoryVO guestOSCategory = Mockito.mock(GuestOSCategoryVO.class);
long id = 1L;
Mockito.when(deleteCmd.getId()).thenReturn(id);
Mockito.when(_guestOSCategoryDao.findById(id)).thenReturn(guestOSCategory);
Mockito.when(_guestOSDao.listIdsByCategoryId(id)).thenReturn(Arrays.asList(1L));
spy.deleteGuestOsCategory(deleteCmd);
}
private void mockGuestOsJoin() {
GuestOSVO vo = mock(GuestOSVO.class);
SearchBuilder<GuestOSVO> sb = mock(SearchBuilder.class);
when(sb.entity()).thenReturn(vo);
when(_guestOSDao.createSearchBuilder()).thenReturn(sb);
}
@Test
public void testListGuestOSCategoriesByCriteria_Success() {
ListGuestOsCategoriesCmd listCmd = Mockito.mock(ListGuestOsCategoriesCmd.class);
GuestOSCategoryVO guestOSCategory = Mockito.mock(GuestOSCategoryVO.class);
Filter filter = Mockito.mock(Filter.class);
Long id = 1L;
String name = "Ubuntu";
String keyword = "Linux";
Boolean featured = true;
Long zoneId = 1L;
CPU.CPUArch arch = CPU.CPUArch.getDefault();
Boolean isIso = true;
Boolean isVnf = false;
Mockito.when(listCmd.getId()).thenReturn(id);
Mockito.when(listCmd.getName()).thenReturn(name);
Mockito.when(listCmd.getKeyword()).thenReturn(keyword);
Mockito.when(listCmd.isFeatured()).thenReturn(featured);
Mockito.when(listCmd.getZoneId()).thenReturn(zoneId);
Mockito.when(listCmd.getArch()).thenReturn(arch);
Mockito.when(listCmd.isIso()).thenReturn(isIso);
Mockito.when(listCmd.isVnf()).thenReturn(isVnf);
SearchBuilder<GuestOSCategoryVO> searchBuilder = Mockito.mock(SearchBuilder.class);
Mockito.when(searchBuilder.entity()).thenReturn(guestOSCategory);
SearchCriteria<GuestOSCategoryVO> searchCriteria = Mockito.mock(SearchCriteria.class);
Mockito.when(_guestOSCategoryDao.createSearchBuilder()).thenReturn(searchBuilder);
Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
Mockito.when(templateDao.listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf)).thenReturn(Arrays.asList(1L, 2L));
Pair<List<GuestOSCategoryVO>, Integer> mockResult = new Pair<>(Arrays.asList(guestOSCategory), 1);
mockGuestOsJoin();
Mockito.when(_guestOSCategoryDao.searchAndCount(Mockito.eq(searchCriteria), Mockito.any())).thenReturn(mockResult);
Pair<List<? extends GuestOsCategory>, Integer> result = spy.listGuestOSCategoriesByCriteria(listCmd);
Assert.assertNotNull(result);
Assert.assertEquals(1, result.second().intValue());
Assert.assertEquals(1, result.first().size());
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).createSearchBuilder();
Mockito.verify(templateDao, Mockito.times(1)).listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf);
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).searchAndCount(Mockito.eq(searchCriteria), Mockito.any());
}
@Test
public void testListGuestOSCategoriesByCriteria_NoResults() {
ListGuestOsCategoriesCmd listCmd = Mockito.mock(ListGuestOsCategoriesCmd.class);
GuestOSCategoryVO guestOSCategory = Mockito.mock(GuestOSCategoryVO.class);
Long id = 1L;
String name = "CentOS";
String keyword = "Linux";
Boolean featured = false;
Long zoneId = 1L;
CPU.CPUArch arch = CPU.CPUArch.getDefault();
Boolean isIso = false;
Boolean isVnf = false;
Mockito.when(listCmd.getId()).thenReturn(id);
Mockito.when(listCmd.getName()).thenReturn(name);
Mockito.when(listCmd.getKeyword()).thenReturn(keyword);
Mockito.when(listCmd.isFeatured()).thenReturn(featured);
Mockito.when(listCmd.getZoneId()).thenReturn(zoneId);
Mockito.when(listCmd.getArch()).thenReturn(arch);
Mockito.when(listCmd.isIso()).thenReturn(isIso);
Mockito.when(listCmd.isVnf()).thenReturn(isVnf);
SearchBuilder<GuestOSCategoryVO> searchBuilder = Mockito.mock(SearchBuilder.class);
Mockito.when(searchBuilder.entity()).thenReturn(guestOSCategory);
SearchCriteria<GuestOSCategoryVO> searchCriteria = Mockito.mock(SearchCriteria.class);
Mockito.when(_guestOSCategoryDao.createSearchBuilder()).thenReturn(searchBuilder);
Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
Mockito.when(templateDao.listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf)).thenReturn(Arrays.asList(1L, 2L));
Pair<List<GuestOSCategoryVO>, Integer> mockResult = new Pair<>(Arrays.asList(), 0);
Mockito.when(_guestOSCategoryDao.searchAndCount(Mockito.eq(searchCriteria), Mockito.any())).thenReturn(mockResult);
mockGuestOsJoin();
Pair<List<? extends GuestOsCategory>, Integer> result = spy.listGuestOSCategoriesByCriteria(listCmd);
Assert.assertNotNull(result);
Assert.assertEquals(0, result.second().intValue());
Assert.assertEquals(0, result.first().size());
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).createSearchBuilder();
Mockito.verify(templateDao, Mockito.times(1)).listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf);
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).searchAndCount(Mockito.eq(searchCriteria), Mockito.any());
}
@Test
public void testListGuestOSCategoriesByCriteria_NoGuestOsIdsFound() {
ListGuestOsCategoriesCmd listCmd = Mockito.mock(ListGuestOsCategoriesCmd.class);
GuestOSCategoryVO guestOSCategory = Mockito.mock(GuestOSCategoryVO.class);
Long id = 1L;
String name = "Ubuntu";
String keyword = "Linux";
Boolean featured = true;
Long zoneId = 1L;
CPU.CPUArch arch = CPU.CPUArch.getDefault();
Boolean isIso = true;
Boolean isVnf = false;
Mockito.when(listCmd.getId()).thenReturn(id);
Mockito.when(listCmd.getName()).thenReturn(name);
Mockito.when(listCmd.getKeyword()).thenReturn(keyword);
Mockito.when(listCmd.isFeatured()).thenReturn(featured);
Mockito.when(listCmd.getZoneId()).thenReturn(zoneId);
Mockito.when(listCmd.getArch()).thenReturn(arch);
Mockito.when(listCmd.isIso()).thenReturn(isIso);
Mockito.when(listCmd.isVnf()).thenReturn(isVnf);
SearchBuilder<GuestOSCategoryVO> searchBuilder = Mockito.mock(SearchBuilder.class);
Mockito.when(searchBuilder.entity()).thenReturn(guestOSCategory);
SearchCriteria<GuestOSCategoryVO> searchCriteria = Mockito.mock(SearchCriteria.class);
Mockito.when(_guestOSCategoryDao.createSearchBuilder()).thenReturn(searchBuilder);
Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
Mockito.when(templateDao.listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf)).thenReturn(Arrays.asList(1L, 2L));
Pair<List<GuestOSCategoryVO>, Integer> mockResult = new Pair<>(Arrays.asList(), 0);
when(_guestOSCategoryDao.searchAndCount(Mockito.eq(searchCriteria), Mockito.any())).thenReturn(mockResult);
mockGuestOsJoin();
Pair<List<? extends GuestOsCategory>, Integer> result = spy.listGuestOSCategoriesByCriteria(listCmd);
Assert.assertNotNull(result);
Assert.assertEquals(0, result.second().intValue());
Assert.assertEquals(0, result.first().size());
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).createSearchBuilder();
Mockito.verify(templateDao, Mockito.times(1)).listTemplateIsoByArchVnfAndZone(zoneId, arch, isIso, isVnf);
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).searchAndCount(Mockito.eq(searchCriteria), Mockito.any());
}
@Test
public void testListGuestOSCategoriesByCriteria_FilterById() {
ListGuestOsCategoriesCmd listCmd = Mockito.mock(ListGuestOsCategoriesCmd.class);
GuestOSCategoryVO guestOSCategory = Mockito.mock(GuestOSCategoryVO.class);
Long id = 1L;
Mockito.when(listCmd.getId()).thenReturn(id);
Mockito.when(listCmd.getZoneId()).thenReturn(null);
Mockito.when(listCmd.isIso()).thenReturn(null);
Mockito.when(listCmd.isVnf()).thenReturn(null);
SearchBuilder<GuestOSCategoryVO> searchBuilder = Mockito.mock(SearchBuilder.class);
Mockito.when(searchBuilder.entity()).thenReturn(guestOSCategory);
SearchCriteria<GuestOSCategoryVO> searchCriteria = Mockito.mock(SearchCriteria.class);
Mockito.when(_guestOSCategoryDao.createSearchBuilder()).thenReturn(searchBuilder);
Mockito.when(searchBuilder.create()).thenReturn(searchCriteria);
Pair<List<GuestOSCategoryVO>, Integer> mockResult = new Pair<>(Arrays.asList(guestOSCategory), 1);
Mockito.when(_guestOSCategoryDao.searchAndCount(Mockito.eq(searchCriteria), Mockito.any())).thenReturn(mockResult);
mockGuestOsJoin();
Pair<List<? extends GuestOsCategory>, Integer> result = spy.listGuestOSCategoriesByCriteria(listCmd);
Assert.assertNotNull(result);
Assert.assertEquals(1, result.second().intValue());
Assert.assertEquals(1, result.first().size());
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).createSearchBuilder();
Mockito.verify(searchCriteria, Mockito.times(1)).setParameters("id", id);
Mockito.verify(_guestOSCategoryDao, Mockito.times(1)).searchAndCount(Mockito.eq(searchCriteria), Mockito.any());
}
} }

View File

@ -97,5 +97,8 @@
"basicZoneEnabled": true, "basicZoneEnabled": true,
"multipleServer": false, "multipleServer": false,
"allowSettingTheme": true, "allowSettingTheme": true,
"imageSelectionInterface": "modern",
"showUserCategoryForModernImageSelection": true,
"showAllCategoryForModernImageSelection": false,
"docHelpMappings": {} "docHelpMappings": {}
} }

View File

@ -87,8 +87,9 @@
"label.action.delete.egress.firewall": "Delete egress firewall rule", "label.action.delete.egress.firewall": "Delete egress firewall rule",
"label.action.delete.firewall": "Delete firewall rule", "label.action.delete.firewall": "Delete firewall rule",
"label.action.delete.interface.static.route": "Remove Tungsten Fabric interface static route", "label.action.delete.interface.static.route": "Remove Tungsten Fabric interface static route",
"label.action.delete.guest.os": "Delete guest os", "label.action.delete.guest.os": "Delete guest OS",
"label.action.delete.guest.os.hypervisor.mapping": "Delete guest os hypervisor mapping", "label.action.delete.guest.os.category": "Delete guest OS category",
"label.action.delete.guest.os.hypervisor.mapping": "Delete guest OS hypervisor mapping",
"label.action.delete.ip.range": "Delete IP range", "label.action.delete.ip.range": "Delete IP range",
"label.action.delete.iso": "Delete ISO", "label.action.delete.iso": "Delete ISO",
"label.action.delete.load.balancer": "Delete load balancer rule", "label.action.delete.load.balancer": "Delete load balancer rule",
@ -263,8 +264,9 @@
"label.add.firewall": "Add firewall rule", "label.add.firewall": "Add firewall rule",
"label.add.firewallrule": "Add Firewall Rule", "label.add.firewallrule": "Add Firewall Rule",
"label.add.guest.network": "Add guest Network", "label.add.guest.network": "Add guest Network",
"label.add.guest.os": "Add guest os", "label.add.guest.os": "Add guest OS",
"label.add.guest.os.hypervisor.mapping": "Add guest os hypervisor mapping", "label.add.guest.os.category": "Add guest OS category",
"label.add.guest.os.hypervisor.mapping": "Add guest OS hypervisor mapping",
"label.add.host": "Add host", "label.add.host": "Add host",
"label.add.ingress.rule": "Add ingress rule", "label.add.ingress.rule": "Add ingress rule",
"label.add.intermediate.certificate": "Add intermediate certificate", "label.add.intermediate.certificate": "Add intermediate certificate",
@ -1069,6 +1071,8 @@
"label.guest.netmask": "Guest netmask", "label.guest.netmask": "Guest netmask",
"label.guest.networks": "Guest Networks", "label.guest.networks": "Guest Networks",
"label.guest.os": "Guest OS", "label.guest.os": "Guest OS",
"label.guest.os.category": "Guest OS Category",
"label.guest.os.categories": "Guest OS Categories",
"label.guest.os.hypervisor.mappings": "Guest OS mappings", "label.guest.os.hypervisor.mappings": "Guest OS mappings",
"label.guest.start.ip": "Guest start IP", "label.guest.start.ip": "Guest start IP",
"label.guest.traffic": "Guest traffic", "label.guest.traffic": "Guest traffic",
@ -1143,6 +1147,8 @@
"label.ikelifetime": "IKE lifetime (second)", "label.ikelifetime": "IKE lifetime (second)",
"label.ikepolicy": "IKE policy", "label.ikepolicy": "IKE policy",
"label.ikeversion": "IKE version", "label.ikeversion": "IKE version",
"label.image": "Image",
"label.image.type": "Image type",
"label.images": "Images", "label.images": "Images",
"label.imagestoreid": "Secondary Storage", "label.imagestoreid": "Secondary Storage",
"label.import.backup.offering": "Import backup offering", "label.import.backup.offering": "Import backup offering",
@ -1651,6 +1657,7 @@
"label.operator.equal": "Equals to", "label.operator.equal": "Equals to",
"label.optional": "Optional", "label.optional": "Optional",
"label.order": "Order", "label.order": "Order",
"label.os": "Operating System",
"label.oscategoryid": "OS category", "label.oscategoryid": "OS category",
"label.oscategoryname": "OS category name", "label.oscategoryname": "OS category name",
"label.osname": "OS name", "label.osname": "OS name",
@ -1806,6 +1813,7 @@
"label.provisioningtype.fat": "Fat provisioning", "label.provisioningtype.fat": "Fat provisioning",
"label.provisioningtype.sparse": "Sparse provisioning", "label.provisioningtype.sparse": "Sparse provisioning",
"label.provisioningtype.thin": "Thin provisioning", "label.provisioningtype.thin": "Thin provisioning",
"label.public": "Public",
"label.publicmtu": "Public Interface MTU", "label.publicmtu": "Public Interface MTU",
"label.public.interface": "Public interface", "label.public.interface": "Public interface",
"label.public.ip": "Public IP address", "label.public.ip": "Public IP address",
@ -2305,7 +2313,8 @@
"label.tariffvalue": "Tariff value", "label.tariffvalue": "Tariff value",
"label.tcp": "TCP", "label.tcp": "TCP",
"label.tcp.proxy": "TCP proxy", "label.tcp.proxy": "TCP proxy",
"label.template": "Select a template", "label.template": "Template",
"label.template.select": "Select a template",
"label.templatetag": "Tag", "label.templatetag": "Tag",
"label.template.select.existing": "Select an existing template", "label.template.select.existing": "Select an existing template",
"label.template.temporary.import": "Use a temporary template for import", "label.template.temporary.import": "Use a temporary template for import",
@ -2698,6 +2707,7 @@
"message.action.delete.ingress.rule": "Please confirm that you want to delete this ingress rule.", "message.action.delete.ingress.rule": "Please confirm that you want to delete this ingress rule.",
"message.action.delete.ipv4.subnet": "Please confirm that you want to delete this IPv4 subnet.", "message.action.delete.ipv4.subnet": "Please confirm that you want to delete this IPv4 subnet.",
"message.action.delete.guest.os": "Please confirm that you want to delete this guest os. System defined entry cannot be deleted.", "message.action.delete.guest.os": "Please confirm that you want to delete this guest os. System defined entry cannot be deleted.",
"message.action.delete.guest.os.category": "Please confirm that you want to delete this guest os category.",
"message.action.delete.guest.os.hypervisor.mapping": "Please confirm that you want to delete this guest os hypervisor mapping. System defined entry cannot be deleted.", "message.action.delete.guest.os.hypervisor.mapping": "Please confirm that you want to delete this guest os hypervisor mapping. System defined entry cannot be deleted.",
"message.action.delete.instance.group": "Please confirm that you want to delete the Instance group.", "message.action.delete.instance.group": "Please confirm that you want to delete the Instance group.",
"message.action.delete.interface.static.route": "Please confirm that you want to remove this interface Static Route?", "message.action.delete.interface.static.route": "Please confirm that you want to remove this interface Static Route?",
@ -3306,6 +3316,7 @@
"message.installwizard.tooltip.tungsten.provider.name": "Tungsten provider name is required", "message.installwizard.tooltip.tungsten.provider.name": "Tungsten provider name is required",
"message.installwizard.tooltip.tungsten.provider.port": "Tungsten provider port is required", "message.installwizard.tooltip.tungsten.provider.port": "Tungsten provider port is required",
"message.installwizard.tooltip.tungsten.provider.vrouterport": "Tungsten provider vrouter port is required", "message.installwizard.tooltip.tungsten.provider.vrouterport": "Tungsten provider vrouter port is required",
"message.instance.architecture": "Please select Instance architecture",
"message.instances.managed": "Instances controlled by CloudStack.", "message.instances.managed": "Instances controlled by CloudStack.",
"message.instances.unmanaged": "Instances not controlled by CloudStack.", "message.instances.unmanaged": "Instances not controlled by CloudStack.",
"message.instances.migrate.vmware": "Instances that can be migrated from VMware.", "message.instances.migrate.vmware": "Instances that can be migrated from VMware.",

View File

@ -139,6 +139,10 @@ export default {
}, },
fetchResourceIcon (id) { fetchResourceIcon (id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.$store.getters.avatar) {
this.image = this.$store.getters.avatar
resolve(this.image)
}
api('listUsers', { api('listUsers', {
id: id, id: id,
showicon: true showicon: true
@ -146,6 +150,7 @@ export default {
const response = json.listusersresponse.user || [] const response = json.listusersresponse.user || []
if (response?.[0]) { if (response?.[0]) {
this.image = response[0]?.icon?.base64image || '' this.image = response[0]?.icon?.base64image || ''
this.$store.commit('SET_AVATAR', this.image)
resolve(this.image) resolve(this.image)
} }
}).catch(error => { }).catch(error => {

View File

@ -0,0 +1,150 @@
// 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>
<span>
<a-dropdown-button
type="primary"
:loading="loading"
v-if="allowed"
:disabled="!zones || zones.length === 0">
<rocket-outlined />
{{ $t('label.create.vm') }}
<template #icon><down-outlined /></template>
<template #overlay>
<a-menu type="primary" @click="handleDeployInstanceMenu">
<a-menu-item 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>
<global-outlined v-else style="margin-right: 5px" />
{{ zone.name }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown-button>
</span>
</template>
<script>
import { api } from '@/api'
import ResourceIcon from '@/components/view/ResourceIcon'
export default {
name: 'ImageDeployInstanceButton',
components: {
ResourceIcon
},
props: {
resource: {
type: Object,
required: true
},
osCategoryId: {
type: String,
default: null
}
},
emits: ['update-zones'],
data () {
return {
imageApi: 'listTemplates',
loading: false,
zones: []
}
},
mounted () {
if (this.$route.meta.name === 'iso') {
this.imageApi = 'listIsos'
}
setTimeout(() => {
this.fetchData()
}, 100)
},
computed: {
allowed () {
return (this.$route.meta.name === 'template' ||
(this.$route.meta.name === 'iso' && this.resource.bootable))
}
},
methods: {
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
fetchData () {
this.fetchResourceData()
},
fetchResourceData () {
const params = {}
params.id = this.resource.id
params.templatefilter = 'executable'
params.listall = true
params.page = this.page
params.pagesize = this.pageSize
this.dataSource = []
this.itemCount = 0
this.fetchLoading = true
this.zones = []
api(this.imageApi, params).then(json => {
const imageResponse = json?.[this.imageApi.toLowerCase() + 'response']?.[this.$route.meta.name] || []
this.zones = imageResponse.map(i => ({
id: i.zoneid,
name: i.zonename
}))
}).catch(error => {
this.$notifyError(error)
this.loading = false
}).finally(() => {
if (this.zones.length !== 0) {
this.$emit('update-zones', this.zones)
}
this.fetchZonesForIcon()
})
},
fetchZonesForIcon () {
if (!this.zones) {
return
}
const zoneids = this.zones.map(z => z.id)
this.loading = true
api('listZones', { showicon: true, ids: zoneids.join(',') }).then(json => {
this.zones = json.listzonesresponse.zone || []
}).finally(() => {
this.loading = false
if (this.zones.length !== 0) {
this.$emit('update-zones', this.zones)
}
})
},
handleDeployInstanceMenu (e) {
const query = { zoneid: e.key }
query[this.$route.meta.name + 'id'] = this.resource.id
if (this.resource.arch) {
query.arch = this.resource.arch
}
if (this.osCategoryId) {
query.oscategoryid = this.osCategoryId
}
this.$router.push({
path: '/action/deployVirtualMachine',
query: query
})
}
}
}
</script>

View File

@ -30,11 +30,11 @@
<edit-outlined class="upload-icon"/> <edit-outlined class="upload-icon"/>
</div> </div>
<slot name="avatar"> <slot name="avatar">
<span v-if="(resource.icon && resource.icon.base64image || images.template || images.iso || resourceIcon) && !['router', 'systemvm', 'volume'].includes($route.path.split('/')[1])"> <span v-if="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"/> <resource-icon :image="resourceIcon" size="4x" style="margin-right: 5px"/>
</span> </span>
<span v-else> <span v-else>
<os-logo v-if="resource.ostypeid || resource.ostypename" :osId="resource.ostypeid" :osName="resource.ostypename" size="3x" @update-osname="setResourceOsType"/> <os-logo v-if="resource.ostypeid || resource.ostypename || ['guestoscategory'].includes($route.path.split('/')[1])" :osId="resource.ostypeid" :osName="resource.ostypename || resource.name" size="3x" @update-osname="setResourceOsType"/>
<render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :icon="$route.meta.icon" /> <render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :icon="$route.meta.icon" />
<font-awesome-icon <font-awesome-icon
v-else-if="$route.meta.icon && Array.isArray($route.meta.icon)" v-else-if="$route.meta.icon && Array.isArray($route.meta.icon)"
@ -158,11 +158,16 @@
<div class="resource-detail-item" v-if="resource.ostypename && resource.ostypeid"> <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__label">{{ $t('label.ostypename') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<span v-if="resource.icon && resource.icon.base64image || images.template || images.iso"> <span v-if="images.guestoscategory">
<resource-icon :image="getImage(images.template || images.iso)" size="1x" style="margin-right: 5px"/> <resource-icon :image="images.guestoscategory" size="1x" style="margin-right: 5px"/>
</span> </span>
<os-logo v-else :osId="resource.ostypeid" :osName="resource.ostypename" size="lg" style="margin-left: -1px" /> <os-logo v-else :osId="resource.ostypeid" :osName="resource.ostypename" size="lg" style="margin-left: -1px" />
<span style="margin-left: 8px">{{ resource.ostypename }}</span> <span style="margin-left: 8px">
<router-link v-if="$router.resolve('/guestos/' + resource.ostypeid).matched[0].redirect !== '/exception/404'" :to="{ path: '/guestos/' + resource.ostypeid }">
{{ resource.ostypename }}
</router-link>
<span v-else>{{ resource.ostypename }}</span>
</span>
</div> </div>
</div> </div>
<div class="resource-detail-item" v-if="resource.ipaddress"> <div class="resource-detail-item" v-if="resource.ipaddress">
@ -449,7 +454,7 @@
<div class="resource-detail-item__label">{{ $t('label.project') }}</div> <div class="resource-detail-item__label">{{ $t('label.project') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<span v-if="images.project"> <span v-if="images.project">
<resource-icon :image="getImage(images.project)" size="1x" style="margin-right: 5px"/> <resource-icon :image="images.project" size="1x" style="margin-right: 5px"/>
</span> </span>
<project-outlined v-else /> <project-outlined v-else />
<router-link v-if="!isStatic && resource.projectid" :to="{ path: '/project/' + resource.projectid }">{{ resource.project || resource.projectname || resource.projectid }}</router-link> <router-link v-if="!isStatic && resource.projectid" :to="{ path: '/project/' + resource.projectid }">{{ resource.project || resource.projectname || resource.projectid }}</router-link>
@ -546,7 +551,7 @@
<div class="resource-detail-item__label">{{ $t('label.vpcname') }}</div> <div class="resource-detail-item__label">{{ $t('label.vpcname') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<span v-if="images.vpc"> <span v-if="images.vpc">
<resource-icon :image="getImage(images.vpc)" size="1x" style="margin-right: 5px"/> <resource-icon :image="images.vpc" size="1x" style="margin-right: 5px"/>
</span> </span>
<deployment-unit-outlined v-else /> <deployment-unit-outlined v-else />
<router-link :to="{ path: '/vpc/' + resource.vpcid }">{{ resource.vpcname || resource.vpcid }}</router-link> <router-link :to="{ path: '/vpc/' + resource.vpcid }">{{ resource.vpcname || resource.vpcid }}</router-link>
@ -557,7 +562,7 @@
<div class="resource-detail-item__label">{{ $t('label.aclid') }}</div> <div class="resource-detail-item__label">{{ $t('label.aclid') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<span v-if="images.acl"> <span v-if="images.acl">
<resource-icon :image="getImage(images.acl)" size="1x" style="margin-right: 5px"/> <resource-icon :image="images.acl" size="1x" style="margin-right: 5px"/>
</span> </span>
<deployment-unit-outlined v-else /> <deployment-unit-outlined v-else />
<router-link :to="{ path: '/acllist/' + resource.aclid }">{{ resource.aclname || resource.aclid }}</router-link> <router-link :to="{ path: '/acllist/' + resource.aclid }">{{ resource.aclname || resource.aclid }}</router-link>
@ -578,7 +583,7 @@
<div class="resource-detail-item" v-if="resource.templateid"> <div class="resource-detail-item" v-if="resource.templateid">
<div class="resource-detail-item__label">{{ resource.templateformat === 'ISO'? $t('label.iso') : $t('label.templatename') }}</div> <div class="resource-detail-item__label">{{ resource.templateformat === 'ISO'? $t('label.iso') : $t('label.templatename') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<resource-icon v-if="resource.icon" :image="getImage(resource.icon.base64image)" size="1x" style="margin-right: 5px"/> <resource-icon v-if="images.template || images.guestoscategory" :image="images.template || images.guestoscategory" size="1x" style="margin-right: 5px"/>
<SaveOutlined v-else /> <SaveOutlined v-else />
<router-link :to="{ path: (resource.templateformat === 'ISO' ? '/iso/' : '/template/') + resource.templateid }">{{ resource.templatedisplaytext || resource.templatename || resource.templateid }} </router-link> <router-link :to="{ path: (resource.templateformat === 'ISO' ? '/iso/' : '/template/') + resource.templateid }">{{ resource.templatedisplaytext || resource.templatename || resource.templateid }} </router-link>
</div> </div>
@ -586,7 +591,7 @@
<div class="resource-detail-item" v-if="resource.isoid"> <div class="resource-detail-item" v-if="resource.isoid">
<div class="resource-detail-item__label">{{ $t('label.isoname') }}</div> <div class="resource-detail-item__label">{{ $t('label.isoname') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<resource-icon v-if="resource.icon" :image="getImage(resource.icon.base64image)" size="1x" style="margin-right: 5px"/> <resource-icon v-if="images.iso || (resource.isoid === resource.templateid && images.guestoscategory)" :image="images.iso || images.guestoscategory" size="1x" style="margin-right: 5px"/>
<UsbOutlined v-else /> <UsbOutlined v-else />
<router-link :to="{ path: '/iso/' + resource.isoid }">{{ resource.isodisplaytext || resource.isoname || resource.isoid }} </router-link> <router-link :to="{ path: '/iso/' + resource.isoid }">{{ resource.isodisplaytext || resource.isoname || resource.isoid }} </router-link>
</div> </div>
@ -677,7 +682,7 @@
<div class="resource-detail-item__label">{{ $t('label.zone') }}</div> <div class="resource-detail-item__label">{{ $t('label.zone') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<span v-if="images.zone"> <span v-if="images.zone">
<resource-icon :image="getImage(images.zone)" size="1x" style="margin-right: 5px"/> <resource-icon :image="images.zone" size="1x" style="margin-right: 5px"/>
</span> </span>
<global-outlined v-else /> <global-outlined v-else />
<router-link v-if="!isStatic && $router.resolve('/zone/' + resource.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zone/' + resource.zoneid }">{{ resource.zone || resource.zonename || resource.zoneid }}</router-link> <router-link v-if="!isStatic && $router.resolve('/zone/' + resource.zoneid).matched[0].redirect !== '/exception/404'" :to="{ path: '/zone/' + resource.zoneid }">{{ resource.zone || resource.zonename || resource.zoneid }}</router-link>
@ -712,7 +717,7 @@
<div class="resource-detail-item__label">{{ $t('label.account') }}</div> <div class="resource-detail-item__label">{{ $t('label.account') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<span v-if="images.account"> <span v-if="images.account">
<resource-icon :image="getImage(images.account)" size="1x" style="margin-right: 5px"/> <resource-icon :image="images.account" size="1x" style="margin-right: 5px"/>
</span> </span>
<user-outlined v-else /> <user-outlined v-else />
<router-link v-if="!isStatic && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/account', query: { name: resource.account, domainid: resource.domainid } }">{{ resource.account }}</router-link> <router-link v-if="!isStatic && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/account', query: { name: resource.account, domainid: resource.domainid } }">{{ resource.account }}</router-link>
@ -730,7 +735,7 @@
<div class="resource-detail-item" v-if="resource.domainid"> <div class="resource-detail-item" v-if="resource.domainid">
<div class="resource-detail-item__label">{{ $t('label.domain') }}</div> <div class="resource-detail-item__label">{{ $t('label.domain') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<resource-icon v-if="images.domain" :image="getImage(images.domain)" size="1x" style="margin-right: 5px"/> <resource-icon v-if="images.domain" :image="images.domain" size="1x" style="margin-right: 5px"/>
<block-outlined v-else /> <block-outlined v-else />
<router-link v-if="!isStatic && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/domain/' + resource.domainid, query: { tab: 'details'} }">{{ resource.domain || resource.domainid }}</router-link> <router-link v-if="!isStatic && $store.getters.userInfo.roletype !== 'User'" :to="{ path: '/domain/' + resource.domainid, query: { tab: 'details'} }">{{ resource.domain || resource.domainid }}</router-link>
<span v-else>{{ resource.domain || resource.domainid }}</span> <span v-else>{{ resource.domain || resource.domainid }}</span>
@ -788,6 +793,10 @@
</a-button> </a-button>
</router-link> </router-link>
</div> </div>
<image-deploy-instance-button
v-if="'deployVirtualMachine' in $store.getters.apis && ['template', 'iso'].includes($route.meta.name)"
:resource="resource"
:osCategoryId="osCategoryId" />
</div> </div>
<div class="account-center-tags" v-if="showKeys || resource.apikeyaccess"> <div class="account-center-tags" v-if="showKeys || resource.apikeyaccess">
@ -894,6 +903,7 @@ import UploadResourceIcon from '@/components/view/UploadResourceIcon'
import eventBus from '@/config/eventBus' import eventBus from '@/config/eventBus'
import ResourceIcon from '@/components/view/ResourceIcon' import ResourceIcon from '@/components/view/ResourceIcon'
import ResourceLabel from '@/components/widgets/ResourceLabel' import ResourceLabel from '@/components/widgets/ResourceLabel'
import ImageDeployInstanceButton from '@/components/view/ImageDeployInstanceButton.vue'
export default { export default {
name: 'InfoCard', name: 'InfoCard',
@ -905,7 +915,8 @@ export default {
TooltipButton, TooltipButton,
UploadResourceIcon, UploadResourceIcon,
ResourceIcon, ResourceIcon,
ResourceLabel ResourceLabel,
ImageDeployInstanceButton
}, },
props: { props: {
resource: { resource: {
@ -951,7 +962,8 @@ export default {
network: '' network: ''
}, },
newResource: {}, newResource: {},
validLinks: {} validLinks: {},
osCategoryId: null
} }
}, },
watch: { watch: {
@ -975,9 +987,6 @@ export default {
} }
this.updateResourceAdditionalData() this.updateResourceAdditionalData()
} }
},
async templateIcon () {
this.getIcons()
} }
}, },
created () { created () {
@ -1007,22 +1016,31 @@ export default {
} }
return [this.resource.keypairs.toString()] return [this.resource.keypairs.toString()]
}, },
templateIcon () { isResourceShowingParentResourceIcon () {
return this.resource.templateid const resourcesShowParentResourceIcon = ['guestos']
const routeName = this.$route.path.split('/')?.[1] || null
if (!routeName) {
return false
}
return resourcesShowParentResourceIcon.includes(routeName)
}, },
resourceIcon () { resourceIcon () {
if (this.$showIcon()) { if (!this.$showIcon() && !this.isResourceShowingParentResourceIcon) {
return null
}
if (this.resource?.icon?.base64image) { if (this.resource?.icon?.base64image) {
return this.resource.icon.base64image return this.resource.icon.base64image
} }
if (this.resource?.resourceIcon?.base64image) { if (this.resource?.resourceIcon?.base64image) {
return this.resource.resourceIcon.base64image return this.resource.resourceIcon.base64image
} }
} return this.images.template || this.images.iso || this.images.guestoscategory || null
return null
}, },
routeFromResourceType () { routeFromResourceType () {
return this.$getRouteFromResourceType(this.resource.resourcetype) return this.$getRouteFromResourceType(this.resource.resourcetype)
},
isModernImageSelection () {
return this.$config.imageSelectionInterface === undefined || this.$config.imageSelectionInterface === 'modern'
} }
}, },
methods: { methods: {
@ -1037,6 +1055,15 @@ export default {
this.getTags() this.getTags()
} }
} }
const osId = this.resource.guestosid || this.resource.ostypeid
if (osId && 'listOsTypes' in this.$store.getters.apis) {
api('listOsTypes', { id: osId }).then(json => {
this.osCategoryId = json?.listostypesresponse?.ostype?.[0]?.oscategoryid || null
if (this.osCategoryId) {
this.fetchResourceIcon(this.osCategoryId, 'guestoscategory')
}
})
}
this.getIcons() this.getIcons()
}, },
showUploadModal (show) { showUploadModal (show) {
@ -1048,43 +1075,34 @@ export default {
this.showUpload = false this.showUpload = false
} }
}, },
getImage (image) { getIcons () {
return (image || this.resource?.icon?.base64image) this.images = {}
},
async getIcons () {
this.images = {
zone: '',
template: '',
iso: '',
domain: '',
account: '',
project: '',
vpc: '',
network: ''
}
if (this.resource.templateid) { if (this.resource.templateid) {
await this.fetchResourceIcon(this.resource.templateid, 'template') this.fetchResourceIcon(this.resource.templateid, 'template')
} }
if (this.resource.isoid) { if (this.resource.isoid) {
await this.fetchResourceIcon(this.resource.isoid, 'iso') this.fetchResourceIcon(this.resource.isoid, 'iso')
} }
if (this.resource.zoneid) { if (this.resource.zoneid) {
await this.fetchResourceIcon(this.resource.zoneid, 'zone') this.fetchResourceIcon(this.resource.zoneid, 'zone')
} }
if (this.resource.domainid) { if (this.resource.domainid) {
await this.fetchResourceIcon(this.resource.domainid, 'domain') this.fetchResourceIcon(this.resource.domainid, 'domain')
} }
if (this.resource.account) { if (this.resource.account) {
await this.fetchAccount() this.fetchAccount()
} }
if (this.resource.projectid) { if (this.resource.projectid) {
await this.fetchResourceIcon(this.resource.projectid, 'project') this.fetchResourceIcon(this.resource.projectid, 'project')
} }
if (this.resource.vpcid) { if (this.resource.vpcid) {
await this.fetchResourceIcon(this.resource.vpcid, 'vpc') this.fetchResourceIcon(this.resource.vpcid, 'vpc')
} }
if (this.resource.networkid) { if (this.resource.networkid) {
await this.fetchResourceIcon(this.resource.networkid, 'network') this.fetchResourceIcon(this.resource.networkid, 'network')
}
if (this.resource.oscategoryid) {
this.fetchResourceIcon(this.resource.oscategoryid, 'guestoscategory')
} }
}, },
fetchAccount () { fetchAccount () {
@ -1109,19 +1127,14 @@ export default {
resourcetype: type resourcetype: type
}).then(json => { }).then(json => {
const response = json.listresourceiconresponse.icon || [] const response = json.listresourceiconresponse.icon || []
if (response?.[0]) { this.images[type] = response?.[0]?.base64image || null
this.images[type] = response[0].base64image
resolve(this.images) resolve(this.images)
} else {
this.images[type] = ''
resolve(this.images)
}
}).catch(error => { }).catch(error => {
reject(error) reject(error)
}) })
}) })
} else { } else {
this.images.type = '' this.images.type = null
} }
}, },
setData () { setData () {
@ -1252,7 +1265,6 @@ export default {
query[item.param] = this.resource.id query[item.param] = this.resource.id
} }
} }
return query return query
} }
} }

View File

@ -58,7 +58,7 @@
</span> </span>
<span v-if="$showIcon() && !['vm', 'vnfapp'].includes($route.path.split('/')[1])" style="margin-right: 5px"> <span v-if="$showIcon() && !['vm', 'vnfapp'].includes($route.path.split('/')[1])" style="margin-right: 5px">
<resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="2x"/> <resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="2x"/>
<os-logo v-else-if="record.ostypename" :osName="record.ostypename" size="xl" /> <os-logo v-else-if="record.ostypename || ['guestoscategory'].includes($route.path.split('/')[1])" :osName="record.ostypename || record.name" size="xl" />
<render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 16px;" :icon="$route.meta.icon"/> <render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 16px;" :icon="$route.meta.icon"/>
<render-icon v-else style="font-size: 16px;" :svgIcon="$route.meta.icon" /> <render-icon v-else style="font-size: 16px;" :svgIcon="$route.meta.icon" />
</span> </span>
@ -241,6 +241,15 @@
</span> </span>
<span v-else>{{ text }}</span> <span v-else>{{ text }}</span>
</template> </template>
<template v-if="column.key === 'oscategoryname'">
<span v-if="('listOsCategories' in $store.getters.apis) && record.oscategoryid">
<router-link :to="{ path: '/guestoscategory/' + record.oscategoryid }">{{ text }}</router-link>
</span>
<span v-else>{{ text }}</span>
</template>
<template v-if="column.key === 'isuserdefined'">
<span>{{ text ? $t('label.yes') : $t('label.no') }}</span>
</template>
<template v-if="column.key === 'state'"> <template v-if="column.key === 'state'">
<status v-if="$route.path.startsWith('/host')" :text="getHostState(record)" displayText /> <status v-if="$route.path.startsWith('/host')" :text="getHostState(record)" displayText />
<status v-else :text="text ? text : ''" displayText :styles="{ 'min-width': '80px' }" /> <status v-else :text="text ? text : ''" displayText :styles="{ 'min-width': '80px' }" />
@ -485,6 +494,9 @@
<template v-if="['startdate', 'enddate'].includes(column.key) && ['usage'].includes($route.path.split('/')[1])"> <template v-if="['startdate', 'enddate'].includes(column.key) && ['usage'].includes($route.path.split('/')[1])">
{{ $toLocaleDate(text.replace('\'T\'', ' ')) }} {{ $toLocaleDate(text.replace('\'T\'', ' ')) }}
</template> </template>
<template v-if="['isfeatured'].includes(column.key) && ['guestoscategory'].includes($route.path.split('/')[1])">
{{ record.isfeatured ? $t('label.yes') : $t('label.no') }}
</template>
<template v-if="column.key === 'order'"> <template v-if="column.key === 'order'">
<div class="shift-btns"> <div class="shift-btns">
<a-tooltip :name="text" placement="top"> <a-tooltip :name="text" placement="top">
@ -756,7 +768,7 @@ export default {
'vmsnapshot', 'backup', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway', 'vnfapp', 'vmsnapshot', 'backup', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway', 'vnfapp',
'project', 'account', 'systemvm', 'router', 'computeoffering', 'systemoffering', 'project', 'account', 'systemvm', 'router', 'computeoffering', 'systemoffering',
'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment', 'buckets', 'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment', 'buckets',
'webhook', 'webhookdeliveries', 'sharedfs', 'ipv4subnets', 'asnumbers' 'webhook', 'webhookdeliveries', 'sharedfs', 'ipv4subnets', 'asnumbers', 'guestos'
].includes(this.$route.name) ].includes(this.$route.name)
}, },
getDateAtTimeZone (date, timezone) { getDateAtTimeZone (date, timezone) {
@ -859,8 +871,9 @@ export default {
case 'vpcoffering': case 'vpcoffering':
apiString = 'updateVPCOffering' apiString = 'updateVPCOffering'
break break
default: case 'guestoscategory':
apiString = 'updateTemplate' apiString = 'updateOsCategory'
break
} }
return apiString return apiString
}, },

View File

@ -16,7 +16,7 @@
// under the License. // under the License.
<template> <template>
<img :src="getImg()" :height="getDimensions()" :width="getDimensions()" :style="{ marginTop: (getDimensions() === 56 || ['deployVirtualMachine'].includes($route.path.split('/')[2])) ? '' : '-5px' }"/> <img :src="computedImage" :height="dimensions" :width="dimensions" :style="{ marginTop: (dimensions === 56 || ['deployVirtualMachine'].includes($route.path.split('/')[2])) ? '' : '-5px' }"/>
</template> </template>
<script> <script>
export default { export default {
@ -34,15 +34,17 @@ export default {
data () { data () {
return {} return {}
}, },
methods: { computed: {
getImg () { computedImage () {
if (!this.image) {
return null
}
if (this.image.startsWith('data:image/png')) { if (this.image.startsWith('data:image/png')) {
return this.image return this.image
} else {
return 'data:image/png;charset=utf-8;base64, ' + this.image
} }
return 'data:image/png;charset=utf-8;base64, ' + this.image
}, },
getDimensions () { dimensions () {
const num = Number(this.size) const num = Number(this.size)
if (Number.isInteger(num) && num > 0) { if (Number.isInteger(num) && num > 0) {
return num return num
@ -54,6 +56,8 @@ export default {
return 24 return 24
case '1x': case '1x':
return 16 return 16
case 'os':
return 28
default: default:
return 16 return 16
} }

View File

@ -309,7 +309,7 @@ export default {
'clusterid', 'podid', 'groupid', 'entitytype', 'accounttype', 'systemvmtype', 'scope', 'provider', 'clusterid', 'podid', 'groupid', 'entitytype', 'accounttype', 'systemvmtype', 'scope', 'provider',
'type', 'scope', 'managementserverid', 'serviceofferingid', 'type', 'scope', 'managementserverid', 'serviceofferingid',
'diskofferingid', 'networkid', 'usagetype', 'restartrequired', 'diskofferingid', 'networkid', 'usagetype', 'restartrequired',
'displaynetwork', 'guestiptype', 'usersource', 'arch'].includes(item) 'displaynetwork', 'guestiptype', 'usersource', 'arch', 'oscategoryid', 'templatetype'].includes(item)
) { ) {
type = 'list' type = 'list'
} else if (item === 'tags') { } else if (item === 'tags') {
@ -458,6 +458,13 @@ export default {
this.fields[typeIndex].opts = this.$fetchCpuArchitectureTypes() this.fields[typeIndex].opts = this.$fetchCpuArchitectureTypes()
this.fields[typeIndex].loading = false this.fields[typeIndex].loading = false
} }
if (arrayField.includes('templatetype')) {
const typeIndex = this.fields.findIndex(item => item.name === 'templatetype')
this.fields[typeIndex].loading = true
this.fields[typeIndex].opts = this.$fetchTemplateTypes()
this.fields[typeIndex].loading = false
}
}, },
async fetchDynamicFieldData (arrayField, searchKeyword) { async fetchDynamicFieldData (arrayField, searchKeyword) {
const promises = [] const promises = []
@ -477,6 +484,7 @@ export default {
let networkIndex = -1 let networkIndex = -1
let usageTypeIndex = -1 let usageTypeIndex = -1
let volumeIndex = -1 let volumeIndex = -1
let osCategoryIndex = -1
if (arrayField.includes('type')) { if (arrayField.includes('type')) {
if (this.$route.path === '/alert') { if (this.$route.path === '/alert') {
@ -580,6 +588,12 @@ export default {
promises.push(await this.fetchVolumes(searchKeyword)) promises.push(await this.fetchVolumes(searchKeyword))
} }
if (arrayField.includes('oscategoryid')) {
osCategoryIndex = this.fields.findIndex(item => item.name === 'oscategoryid')
this.fields[osCategoryIndex].loading = true
promises.push(await this.fetchOsCategories(searchKeyword))
}
Promise.all(promises).then(response => { Promise.all(promises).then(response => {
if (typeIndex > -1) { if (typeIndex > -1) {
const types = response.filter(item => item.type === 'type') const types = response.filter(item => item.type === 'type')
@ -676,6 +690,13 @@ export default {
this.fields[usageTypeIndex].opts = this.sortArray(usageTypes[0].data) this.fields[usageTypeIndex].opts = this.sortArray(usageTypes[0].data)
} }
} }
if (osCategoryIndex > -1) {
const osCategories = response.filter(item => item.type === 'oscategoryid')
if (osCategories && osCategories.length > 0) {
this.fields[osCategoryIndex].opts = this.sortArray(osCategories[0].data)
}
}
}).finally(() => { }).finally(() => {
if (typeIndex > -1) { if (typeIndex > -1) {
this.fields[typeIndex].loading = false this.fields[typeIndex].loading = false
@ -722,6 +743,9 @@ export default {
if (usageTypeIndex > -1) { if (usageTypeIndex > -1) {
this.fields[usageTypeIndex].loading = false this.fields[usageTypeIndex].loading = false
} }
if (osCategoryIndex > -1) {
this.fields[osCategoryIndex].loading = false
}
if (Array.isArray(arrayField)) { if (Array.isArray(arrayField)) {
this.fillFormFieldValues() this.fillFormFieldValues()
} }
@ -1006,6 +1030,19 @@ export default {
}) })
}) })
}, },
fetchOsCategories (searchKeyword) {
return new Promise((resolve, reject) => {
api('listOsCategories', { showicon: true, keyword: searchKeyword }).then(json => {
const osCategories = json.listoscategoriesresponse.oscategory
resolve({
type: 'oscategoryid',
data: osCategories
})
}).catch(error => {
reject(error.response.headers['x-description'])
})
})
},
fetchGuestNetworkTypes () { fetchGuestNetworkTypes () {
const types = [] const types = []
if (this.apiName.indexOf('listNetworks') > -1) { if (this.apiName.indexOf('listNetworks') > -1) {

View File

@ -0,0 +1,154 @@
// 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-radio-group
v-if="items.length <= maxBlocks"
v-model:value="localValue"
@change="handleChange()">
<a-row type="flex" :gutter="[horizontalGutter, verticalGutter]" justify="start">
<div v-for="item in items" :key="item.id">
<a-col :span="6">
<a-radio-button
:value="item.id"
style="border-width: 2px"
:class="blockRadioButtonClass">
<slot name="radio-option" :item="item"></slot>
</a-radio-button>
</a-col>
</div>
</a-row>
</a-radio-group>
<a-select
v-else
v-model:value="localValue"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="handleChange()"
:loading="loading"
v-focus="true">
<a-select-option v-for="item in items" :key="item.id" :label="item.name">
<slot name="select-option" :item="item"></slot>
</a-select-option>
</a-select>
</div>
</template>
<script>
export default {
name: 'BlockRadioGroupSelect',
props: {
blockSize: {
type: String,
default: 'large'
},
selectedValue: {
type: [String, Number],
default: ''
},
horizontalGutter: {
type: Number,
default: 16
},
verticalGutter: {
type: Number,
default: 18
},
items: {
type: Array,
default: () => []
},
maxBlocks: {
type: Number,
default: 8
},
loading: {
type: Boolean,
default: false
}
},
data () {
return {
localValue: this.selectedValue
}
},
watch: {
selectedValue (newValue) {
if (this.localValue === newValue) {
return
}
this.localValue = newValue
}
},
computed: {
blockRadioButtonClass () {
if (['square', 'medium', 'small'].includes(this.blockSize)) {
return this.blockSize + '-block-radio-button'
}
return 'large-block-radio-button'
}
},
emits: ['change'],
methods: {
handleChange () {
this.$emit('change', this.localValue)
}
}
}
</script>
<style lang="less" scoped>
.large-block-radio-button {
width:100%;
min-width: 345px;
height: 60px;
display: flex;
padding-left: 20px;
align-items: center;
}
.medium-block-radio-button {
width:100%;
min-width: 160px;
height: 60px;
display: flex;
padding-left: 20px;
align-items: center;
}
.small-block-radio-button {
width:100%;
min-width: 80px;
height: 60px;
display: flex;
padding-left: 20px;
align-items: center;
}
.square-block-radio-button {
width: 88px;
height: 88px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 0;
box-sizing: border-box;
}
</style>

View File

@ -21,7 +21,7 @@
{{ name }} {{ name }}
</template> </template>
<font-awesome-icon <font-awesome-icon
:icon="['fab', logo]" :icon="logo"
:size="size" :size="size"
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#666' }]" :style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#666' }]"
/> />
@ -50,7 +50,7 @@ export default {
data () { data () {
return { return {
name: '', name: '',
osLogo: 'linux' osLogo: ['fas', 'image']
} }
}, },
computed: { computed: {
@ -87,35 +87,34 @@ export default {
} }
}) })
}, },
getFontAwesomeIcon (name) {
return ['fab', name]
},
discoverOsLogo (name) { discoverOsLogo (name) {
this.name = name this.name = name
const osname = name.toLowerCase()
if (osname.includes('centos')) {
this.osLogo = 'centos'
} else if (osname.includes('debian')) {
this.osLogo = 'debian'
} else if (osname.includes('ubuntu')) {
this.osLogo = 'ubuntu'
} else if (osname.includes('suse')) {
this.osLogo = 'suse'
} else if (osname.includes('redhat')) {
this.osLogo = 'redhat'
} else if (osname.includes('fedora')) {
this.osLogo = 'fedora'
} else if (osname.includes('linux')) {
this.osLogo = 'linux'
} else if (osname.includes('bsd')) {
this.osLogo = 'freebsd'
} else if (osname.includes('apple')) {
this.osLogo = 'apple'
} else if (osname.includes('window') || osname.includes('dos')) {
this.osLogo = 'windows'
} else if (osname.includes('oracle')) {
this.osLogo = 'java'
} else {
this.osLogo = 'linux'
}
this.$emit('update-osname', this.name) this.$emit('update-osname', this.name)
const osname = name.toLowerCase()
const logos = [
{ name: 'centos' },
{ name: 'debian' },
{ name: 'ubuntu' },
{ name: 'suse' },
{ name: 'redhat' },
{ name: 'fedora' },
{ name: 'linux' },
{ name: 'bsd', alternate: 'freebsd' },
{ name: 'apple' },
{ name: 'macos', alternate: 'apple' },
{ name: 'window', alternate: 'windows' },
{ name: 'dos', alternate: 'windows' },
{ name: 'oracle', alternate: 'java' }
]
const match = logos.find(entry => osname.includes(entry.name))
if (match) {
this.osLogo = ['fab', match.alternate ? match.alternate : match.name]
return
}
this.osLogo = ['fas', 'image']
} }
} }
} }

View File

@ -187,6 +187,56 @@ export default {
} }
] ]
}, },
{
name: 'guestoscategory',
title: 'label.guest.os.categories',
docHelp: 'adminguide/guest_os.html#guest-os-categories',
icon: 'group-outlined',
permission: ['listOsCategories', 'addOsCategory'],
columns: ['name', 'isfeatured', 'created', 'order'],
details: ['name', 'isfeatured', 'created'],
related: [{
name: 'guestos',
title: 'label.guest.os',
param: 'oscategoryid'
},
{
name: 'template',
title: 'label.templates',
param: 'oscategoryid'
},
{
name: 'iso',
title: 'label.isos',
param: 'oscategoryid'
}],
actions: [
{
api: 'addOsCategory',
icon: 'plus-outlined',
label: 'label.add.guest.os.category',
listView: true,
dataView: false,
args: ['name', 'isfeatured']
},
{
api: 'updateOsCategory',
icon: 'edit-outlined',
label: 'label.edit',
dataView: true,
popup: true,
args: ['name', 'isfeatured']
},
{
api: 'deleteOsCategory',
icon: 'delete-outlined',
label: 'label.action.delete.guest.os.category',
message: 'message.action.delete.guest.os.category',
dataView: true,
popup: true
}
]
},
{ {
name: 'guestos', name: 'guestos',
title: 'label.guest.os', title: 'label.guest.os',
@ -195,6 +245,7 @@ export default {
permission: ['listOsTypes', 'listOsCategories'], permission: ['listOsTypes', 'listOsCategories'],
columns: ['name', 'oscategoryname', 'isuserdefined'], columns: ['name', 'oscategoryname', 'isuserdefined'],
details: ['name', 'oscategoryname', 'isuserdefined'], details: ['name', 'oscategoryname', 'isuserdefined'],
searchFilters: ['oscategoryid'],
related: [{ related: [{
name: 'guestoshypervisormapping', name: 'guestoshypervisormapping',
title: 'label.guest.os.hypervisor.mappings', title: 'label.guest.os.hypervisor.mappings',
@ -221,7 +272,14 @@ export default {
label: 'label.edit', label: 'label.edit',
dataView: true, dataView: true,
popup: true, popup: true,
args: ['osdisplayname'] groupAction: true,
groupMap: (selection, values) => { return selection.map(x => { return { id: x, oscategoryid: values.oscategoryid } }) },
args: (record, store, isGroupAction) => {
if (isGroupAction) {
return ['oscategoryid']
}
return ['osdisplayname', 'oscategoryid']
}
}, },
{ {
api: 'addGuestOsMapping', api: 'addGuestOsMapping',

View File

@ -67,7 +67,7 @@ export default {
return fields return fields
}, },
searchFilters: () => { searchFilters: () => {
var filters = ['name', 'zoneid', 'tags', 'arch'] var filters = ['name', 'zoneid', 'tags', 'arch', 'oscategoryid', 'templatetype']
if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) { if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
filters.push('storageid') filters.push('storageid')
filters.push('imagestoreid') filters.push('imagestoreid')
@ -235,7 +235,7 @@ export default {
}, },
details: ['name', 'id', 'displaytext', 'checksum', 'ostypename', 'size', 'arch', 'bootable', 'isready', 'passwordenabled', 'directdownload', 'isextractable', 'ispublic', 'isfeatured', 'isdynamicallyscalable', 'crosszones', 'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy', 'url'], details: ['name', 'id', 'displaytext', 'checksum', 'ostypename', 'size', 'arch', 'bootable', 'isready', 'passwordenabled', 'directdownload', 'isextractable', 'ispublic', 'isfeatured', 'isdynamicallyscalable', 'crosszones', 'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy', 'url'],
searchFilters: () => { searchFilters: () => {
var filters = ['name', 'zoneid', 'tags', 'arch'] var filters = ['name', 'zoneid', 'tags', 'arch', 'oscategoryid']
if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) { if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
filters.push('storageid') filters.push('storageid')
filters.push('imagestoreid') filters.push('imagestoreid')

View File

@ -48,6 +48,7 @@ import {
Divider, Divider,
DatePicker, DatePicker,
TimePicker, TimePicker,
Typography,
Upload, Upload,
Progress, Progress,
Skeleton, Skeleton,
@ -117,6 +118,7 @@ export default {
app.use(Divider) app.use(Divider)
app.use(DatePicker) app.use(DatePicker)
app.use(TimePicker) app.use(TimePicker)
app.use(Typography)
app.use(Upload) app.use(Upload)
app.use(Progress) app.use(Progress)
app.use(Skeleton) app.use(Skeleton)

View File

@ -83,6 +83,7 @@ import {
FileDoneOutlined, FileDoneOutlined,
FileProtectOutlined, FileProtectOutlined,
FileTextOutlined, FileTextOutlined,
FileZipOutlined,
FilterOutlined, FilterOutlined,
FilterTwoTone, FilterTwoTone,
FireOutlined, FireOutlined,
@ -99,6 +100,7 @@ import {
GlobalOutlined, GlobalOutlined,
GoldOutlined, GoldOutlined,
GoogleOutlined, GoogleOutlined,
GroupOutlined,
HddOutlined, HddOutlined,
HomeOutlined, HomeOutlined,
IdcardOutlined, IdcardOutlined,
@ -253,6 +255,7 @@ export default {
app.component('FileDoneOutlined', FileDoneOutlined) app.component('FileDoneOutlined', FileDoneOutlined)
app.component('FileProtectOutlined', FileProtectOutlined) app.component('FileProtectOutlined', FileProtectOutlined)
app.component('FileTextOutlined', FileTextOutlined) app.component('FileTextOutlined', FileTextOutlined)
app.component('FileZipOutlined', FileZipOutlined)
app.component('FilterOutlined', FilterOutlined) app.component('FilterOutlined', FilterOutlined)
app.component('FilterTwoTone', FilterTwoTone) app.component('FilterTwoTone', FilterTwoTone)
app.component('FireOutlined', FireOutlined) app.component('FireOutlined', FireOutlined)
@ -269,6 +272,7 @@ export default {
app.component('GlobalOutlined', GlobalOutlined) app.component('GlobalOutlined', GlobalOutlined)
app.component('GoldOutlined', GoldOutlined) app.component('GoldOutlined', GoldOutlined)
app.component('GoogleOutlined', GoogleOutlined) app.component('GoogleOutlined', GoogleOutlined)
app.component('GroupOutlined', GroupOutlined)
app.component('HddOutlined', HddOutlined) app.component('HddOutlined', HddOutlined)
app.component('HomeOutlined', HomeOutlined) app.component('HomeOutlined', HomeOutlined)
app.component('IdcardOutlined', IdcardOutlined) app.component('IdcardOutlined', IdcardOutlined)

View File

@ -37,7 +37,8 @@ import {
genericUtilPlugin, genericUtilPlugin,
localesPlugin, localesPlugin,
dialogUtilPlugin, dialogUtilPlugin,
cpuArchitectureUtilPlugin cpuArchitectureUtilPlugin,
imagesUtilPlugin
} from './utils/plugins' } from './utils/plugins'
import { VueAxios } from './utils/request' import { VueAxios } from './utils/request'
import directives from './utils/directives' import directives from './utils/directives'
@ -55,6 +56,7 @@ vueApp.use(localesPlugin)
vueApp.use(genericUtilPlugin) vueApp.use(genericUtilPlugin)
vueApp.use(dialogUtilPlugin) vueApp.use(dialogUtilPlugin)
vueApp.use(cpuArchitectureUtilPlugin) vueApp.use(cpuArchitectureUtilPlugin)
vueApp.use(imagesUtilPlugin)
vueApp.use(extensions) vueApp.use(extensions)
vueApp.use(directives) vueApp.use(directives)

View File

@ -404,10 +404,11 @@ const user = {
}).catch(ignored => {}) }).catch(ignored => {})
} }
api('listUsers', { id: Cookies.get('userid') }).then(response => { api('listUsers', { id: Cookies.get('userid'), showicon: true }).then(response => {
const result = response.listusersresponse.user[0] const result = response.listusersresponse.user[0]
commit('SET_INFO', result) commit('SET_INFO', result)
commit('SET_NAME', result.firstname + ' ' + result.lastname) commit('SET_NAME', result.firstname + ' ' + result.lastname)
commit('SET_AVATAR', result.icon?.base64image || '')
store.dispatch('SetCsLatestVersion', result.rolename) store.dispatch('SetCsLatestVersion', result.rolename)
}).catch(error => { }).catch(error => {
reject(error) reject(error)

View File

@ -338,7 +338,7 @@ export const showIconPlugin = {
if (resource) { if (resource) {
resourceType = resource resourceType = resource
} }
if (['zone', 'zones', 'template', 'iso', 'account', 'accountuser', 'vm', 'domain', 'project', 'vpc', 'guestnetwork'].includes(resourceType)) { if (['zone', 'zones', 'template', 'iso', 'account', 'accountuser', 'vm', 'domain', 'project', 'vpc', 'guestnetwork', 'guestoscategory'].includes(resourceType)) {
return true return true
} else { } else {
return false return false
@ -412,6 +412,7 @@ export const resourceTypePlugin = {
case 'VpnCustomerGateway': case 'VpnCustomerGateway':
case 'AutoScaleVmGroup': case 'AutoScaleVmGroup':
case 'QuotaTariff': case 'QuotaTariff':
case 'GuestOsCategory':
return resourceType.toLowerCase() return resourceType.toLowerCase()
} }
return '' return ''
@ -543,10 +544,24 @@ export const cpuArchitectureUtilPlugin = {
install (app) { install (app) {
app.config.globalProperties.$fetchCpuArchitectureTypes = function () { app.config.globalProperties.$fetchCpuArchitectureTypes = function () {
const architectures = [ const architectures = [
{ id: 'x86_64', name: 'AMD 64 bits (x86_64)' }, { id: 'x86_64', name: 'Intel/AMD 64 bits (x86_64)' },
{ id: 'aarch64', name: 'ARM 64 bits (aarch64)' } { id: 'aarch64', name: 'ARM 64 bits (aarch64)' }
] ]
return architectures.map(item => ({ ...item, description: item.name })) return architectures.map(item => ({ ...item, description: item.name }))
} }
} }
} }
export const imagesUtilPlugin = {
install (app) {
app.config.globalProperties.$fetchTemplateTypes = function () {
const baseTypes = ['USER', 'VNF']
const adminTypes = ['SYSTEM', 'BUILTIN', 'ROUTING']
const types = [...baseTypes]
if (store.getters.userInfo?.roletype === 'Admin') {
types.push(...adminTypes)
}
return types.map(type => ({ id: type, name: type, description: type }))
}
}
}

View File

@ -1261,6 +1261,9 @@ export default {
} }
var paramName = param.name var paramName = param.name
var extractedParamName = paramName.replace('ids', '').replace('id', '').toLowerCase() var extractedParamName = paramName.replace('ids', '').replace('id', '').toLowerCase()
if (extractedParamName.endsWith('ory')) {
extractedParamName = extractedParamName.slice(0, -3) + 'orie'
}
var params = { listall: true } var params = { listall: true }
for (const filter in filters) { for (const filter in filters) {
params[filter] = filters[filter] params[filter] = filters[filter]

View File

@ -175,7 +175,7 @@
class="auth-btn github-auth" class="auth-btn github-auth"
style="height: 38px; width: 185px; padding: 0; margin-bottom: 5px;" > style="height: 38px; width: 185px; padding: 0; margin-bottom: 5px;" >
<img src="/assets/github.svg" style="width: 32px; padding: 5px" /> <img src="/assets/github.svg" style="width: 32px; padding: 5px" />
<a-text>Sign in with Github</a-text> <a-typography-text>Sign in with Github</a-typography-text>
</a-button> </a-button>
</div> </div>
<div class="social-auth" v-if="googleprovider"> <div class="social-auth" v-if="googleprovider">
@ -187,7 +187,7 @@
class="auth-btn google-auth" class="auth-btn google-auth"
style="height: 38px; width: 185px; padding: 0" > style="height: 38px; width: 185px; padding: 0" >
<img src="/assets/google.svg" style="width: 32px; padding: 5px" /> <img src="/assets/google.svg" style="width: 32px; padding: 5px" />
<a-text>Sign in with Google</a-text> <a-typography-text>Sign in with Google</a-typography-text>
</a-button> </a-button>
</div> </div>
</div> </div>

View File

@ -34,72 +34,70 @@
<div style="margin-top: 15px"> <div style="margin-top: 15px">
<span>{{ $t('message.select.a.zone') }}</span><br/> <span>{{ $t('message.select.a.zone') }}</span><br/>
<a-form-item :label="$t('label.zoneid')" name="zoneid" ref="zoneid"> <a-form-item :label="$t('label.zoneid')" name="zoneid" ref="zoneid">
<div v-if="zones.length <= 8"> <zone-block-radio-group-select
<a-row type="flex" :gutter="[16,18]" justify="start"> :items="zones"
<div v-for="(zoneItem, idx) in zones" :key="idx"> :selectedValue="form.zoneid"
<a-radio-group @change="onSelectZoneId" />
:key="idx"
:size="large"
v-model:value="form.zoneid"
@change="onSelectZoneId(zoneItem.id)">
<a-col :span="6">
<a-radio-button
:value="zoneItem.id"
style="border-width: 2px"
class="zone-radio-button">
<span>
<resource-icon
v-if="zoneItem && zoneItem.icon && zoneItem.icon.base64image"
:image="zoneItem.icon.base64image"
size="2x" />
<global-outlined size="2x" v-else />
{{ zoneItem.name }}
</span>
</a-radio-button>
</a-col>
</a-radio-group>
</div>
</a-row>
</div>
<a-select
v-else
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="onSelectZoneId"
:loading="loading.zones"
v-focus="true"
>
<a-select-option v-for="zone1 in zones" :key="zone1.id" :label="zone1.name">
<span>
<resource-icon v-if="zone1.icon && zone1.icon.base64image" :image="zone1.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px" />
{{ zone1.name }}
</span>
</a-select-option>
</a-select>
</a-form-item> </a-form-item>
</div> </div>
</template> </template>
</a-step> </a-step>
<a-step <a-step
:title="$t('label.template')" v-if="!zoneSelected || isZoneSelectedMultiArch"
:title="$t('label.arch')"
:status="zoneSelected ? 'process' : 'wait'"> :status="zoneSelected ? 'process' : 'wait'">
<template #description> <template #description>
<div v-if="zoneSelected" style="margin-top: 15px"> <div v-if="zoneSelected" style="margin-top: 15px">
{{ $t('message.instance.architecture') }}
<block-radio-group-select
style="margin-top: 5px;"
:items="architectureTypes.opts"
:selectedValue="selectedArchitecture"
@change="changeArchitecture">
<template #radio-option="{ item }">
<span>{{ item.name || item.description }}</span>
</template>
<template #select-option="{ item }">
<span>{{ item.name || item.description }}</span>
</template>
</block-radio-group-select>
</div>
</template>
</a-step>
<a-step
:title="$t('label.template.select')"
:status="zoneSelected ? 'process' : 'wait'">
<template #description>
<div v-if="zoneSelected" style="margin-top: 15px">
<os-based-image-selection
v-if="isModernImageSelection"
:imageTypeSelectionAllowed="false"
:imagePreSelected="!!this.queryTemplateId"
:guestOsCategoriesSelectionDisallowed="!this.queryGuestOsCategoryId && !!this.queryTemplateId"
:guestOsCategories="options.guestOsCategories"
:guestOsCategoriesLoading="loading.guestOsCategories"
:selectedGuestOsCategoryId="form.guestoscategoryid"
:imageItems="options.templates"
:imagesLoading="loading.templates"
:diskSizeSelectionDeployAsIsMessageVisible="template && template.deployasis"
:rootDiskOverrideDisabled="rootDiskSizeFixed > 0 || (template && template.deployasis) || showOverrideDiskOfferingOption"
:rootDiskOverrideChecked="form.rootdisksizeitem"
:filterOption="filterOption"
:preFillContent="dataPreFill"
@change-guest-os-category="onSelectGuestOsCategory"
@handle-image-search-filter="($event) => fetchAllTemplates($event)"
@update-image="updateFieldValue"
@update-disk-size="updateFieldValue" />
<a-card <a-card
:tabList="tabList" v-else
:activeTabKey="tabKey" :tabList="imageTypeList"
@tabChange="key => onTabChange(key, 'tabKey')"> :activeTabKey="imageType">
<div v-if="tabKey === 'templateid'"> <div>
{{ $t('message.template.desc') }} {{ $t('message.template.desc') }}
<template-iso-selection <template-iso-selection
input-decorator="templateid" input-decorator="templateid"
:items="options.templates" :items="options.templates"
:selected="tabKey" :selected="imageType"
:loading="loading.templates" :loading="loading.templates"
:preFillContent="dataPreFill" :preFillContent="dataPreFill"
:key="templateKey" :key="templateKey"
@ -109,10 +107,10 @@
{{ $t('label.override.rootdisk.size') }} {{ $t('label.override.rootdisk.size') }}
<a-switch <a-switch
v-model:checked="form.rootdisksizeitem" v-model:checked="form.rootdisksizeitem"
:disabled="rootDiskSizeFixed > 0 || template.deployasis || showOverrideDiskOfferingOption" :disabled="rootDiskSizeFixed > 0 || (template && template.deployasis) || showOverrideDiskOfferingOption"
@change="val => { showRootDiskSizeChanger = val }" @change="val => { showRootDiskSizeChanger = val }"
style="margin-left: 10px;"/> style="margin-left: 10px;"/>
<div v-if="template.deployasis"> {{ $t('message.deployasis') }} </div> <div v-if="template && template.deployasis"> {{ $t('message.deployasis') }} </div>
</div> </div>
<disk-size-selection <disk-size-selection
v-if="showRootDiskSizeChanger" v-if="showRootDiskSizeChanger"
@ -220,7 +218,7 @@
<span v-if="serviceOffering && !serviceOffering.diskofferingstrictness"> <span v-if="serviceOffering && !serviceOffering.diskofferingstrictness">
<a-step <a-step
:status="zoneSelected ? 'process' : 'wait'" :status="zoneSelected ? 'process' : 'wait'"
v-if="!template.deployasis && template.childtemplates && template.childtemplates.length > 0" > v-if="template && !template.deployasis && template.childtemplates && template.childtemplates.length > 0" >
<template #description> <template #description>
<div v-if="zoneSelected"> <div v-if="zoneSelected">
<multi-disk-selection <multi-disk-selection
@ -244,7 +242,7 @@
:value="overrideDiskOffering ? overrideDiskOffering.id : ''" :value="overrideDiskOffering ? overrideDiskOffering.id : ''"
:loading="loading.diskOfferings" :loading="loading.diskOfferings"
:preFillContent="dataPreFill" :preFillContent="dataPreFill"
:isIsoSelected="tabKey==='isoid'" :isIsoSelected="imageType==='isoid'"
:isRootDiskOffering="true" :isRootDiskOffering="true"
@on-selected-root-disk-size="onSelectRootDiskSize" @on-selected-root-disk-size="onSelectRootDiskSize"
@select-disk-offering-item="($event) => updateOverrideDiskOffering($event)" @select-disk-offering-item="($event) => updateOverrideDiskOffering($event)"
@ -273,7 +271,7 @@
<a-step <a-step
:title="$t('label.data.disk')" :title="$t('label.data.disk')"
:status="zoneSelected ? 'process' : 'wait'" :status="zoneSelected ? 'process' : 'wait'"
v-if="!template.deployasis && template.childtemplates && template.childtemplates.length > 0" > v-if="template && !template.deployasis && template.childtemplates && template.childtemplates.length > 0" >
<template #description> <template #description>
<div v-if="zoneSelected"> <div v-if="zoneSelected">
<multi-disk-selection <multi-disk-selection
@ -286,7 +284,7 @@
</a-step> </a-step>
<a-step <a-step
v-else v-else
:title="tabKey === 'templateid' ? $t('label.data.disk') : $t('label.disk.size')" :title="imageType === 'templateid' ? $t('label.data.disk') : $t('label.disk.size')"
:status="zoneSelected ? 'process' : 'wait'"> :status="zoneSelected ? 'process' : 'wait'">
<template #description> <template #description>
<div v-if="zoneSelected"> <div v-if="zoneSelected">
@ -305,7 +303,7 @@
:value="diskOffering ? diskOffering.id : ''" :value="diskOffering ? diskOffering.id : ''"
:loading="loading.diskOfferings" :loading="loading.diskOfferings"
:preFillContent="dataPreFill" :preFillContent="dataPreFill"
:isIsoSelected="tabKey==='isoid'" :isIsoSelected="imageType==='isoid'"
@on-selected-disk-size="onSelectDiskSize" @on-selected-disk-size="onSelectDiskSize"
@select-disk-offering-item="($event) => updateDiskOffering($event)" @select-disk-offering-item="($event) => updateDiskOffering($event)"
@handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)" @handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)"
@ -768,13 +766,13 @@
</template> </template>
<a-card> <a-card>
<div v-if="this.template && this.template.userdataid"> <div v-if="this.template && this.template.userdataid">
<a-text type="primary"> <a-typography-text>
Userdata "{{ $t(this.template.userdataname) }}" is linked with template "{{ $t(this.template.name) }}" with override policy "{{ $t(this.template.userdatapolicy) }}" Userdata "{{ $t(this.template.userdataname) }}" is linked with template "{{ $t(this.template.name) }}" with override policy "{{ $t(this.template.userdatapolicy) }}"
</a-text><br/><br/> </a-typography-text><br/><br/>
<div v-if="templateUserDataParams.length > 0 && !doUserdataOverride"> <div v-if="templateUserDataParams.length > 0 && !doUserdataOverride">
<a-text type="primary" v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0"> <a-typography-text v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0">
Enter the values for the variables in userdata Enter the values for the variables in userdata
</a-text> </a-typography-text>
<a-input-group> <a-input-group>
<a-table <a-table
size="small" size="small"
@ -1034,12 +1032,15 @@ import eventBus from '@/config/eventBus'
import InfoCard from '@/components/view/InfoCard' import InfoCard from '@/components/view/InfoCard'
import ResourceIcon from '@/components/view/ResourceIcon' import ResourceIcon from '@/components/view/ResourceIcon'
import ZoneBlockRadioGroupSelect from '@views/compute/wizard/ZoneBlockRadioGroupSelect.vue'
import BlockRadioGroupSelect from '@/components/widgets/BlockRadioGroupSelect'
import ComputeOfferingSelection from '@views/compute/wizard/ComputeOfferingSelection' import ComputeOfferingSelection from '@views/compute/wizard/ComputeOfferingSelection'
import ComputeSelection from '@views/compute/wizard/ComputeSelection' import ComputeSelection from '@views/compute/wizard/ComputeSelection'
import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection' import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection'
import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection' import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection'
import MultiDiskSelection from '@views/compute/wizard/MultiDiskSelection' import MultiDiskSelection from '@views/compute/wizard/MultiDiskSelection'
import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection' import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection'
import OsBasedImageSelection from '@views/compute/wizard/OsBasedImageSelection'
import AffinityGroupSelection from '@views/compute/wizard/AffinityGroupSelection' import AffinityGroupSelection from '@views/compute/wizard/AffinityGroupSelection'
import NetworkSelection from '@views/compute/wizard/NetworkSelection' import NetworkSelection from '@views/compute/wizard/NetworkSelection'
import NetworkConfiguration from '@views/compute/wizard/NetworkConfiguration' import NetworkConfiguration from '@views/compute/wizard/NetworkConfiguration'
@ -1057,6 +1058,10 @@ const STATUS_FAILED = 'error'
export default { export default {
name: 'Wizard', name: 'Wizard',
components: { components: {
InfoCard,
ResourceIcon,
ZoneBlockRadioGroupSelect,
BlockRadioGroupSelect,
SshKeyPairSelection, SshKeyPairSelection,
UserDataSelection, UserDataSelection,
NetworkConfiguration, NetworkConfiguration,
@ -1064,14 +1069,13 @@ export default {
LoadBalancerSelection, LoadBalancerSelection,
AffinityGroupSelection, AffinityGroupSelection,
TemplateIsoSelection, TemplateIsoSelection,
OsBasedImageSelection,
DiskSizeSelection, DiskSizeSelection,
MultiDiskSelection, MultiDiskSelection,
DiskOfferingSelection, DiskOfferingSelection,
InfoCard,
ComputeOfferingSelection, ComputeOfferingSelection,
ComputeSelection, ComputeSelection,
SecurityGroupSelection, SecurityGroupSelection,
ResourceIcon,
TooltipLabel, TooltipLabel,
InstanceNicsNetworkSelectListView InstanceNicsNetworkSelectListView
}, },
@ -1103,7 +1107,10 @@ export default {
}, },
zoneId: '', zoneId: '',
zoneSelected: false, zoneSelected: false,
isZoneSelectedMultiArch: false,
dynamicscalingenabled: true, dynamicscalingenabled: true,
imageType: 'templateid',
imageSearchFilters: null,
templateKey: 0, templateKey: 0,
showRegisteredUserdata: true, showRegisteredUserdata: true,
doUserdataOverride: false, doUserdataOverride: false,
@ -1126,6 +1133,7 @@ export default {
disksize: null disksize: null
}, },
options: { options: {
guestOsCategories: [],
templates: {}, templates: {},
serviceOfferings: [], serviceOfferings: [],
diskOfferings: [], diskOfferings: [],
@ -1139,6 +1147,7 @@ export default {
rowCount: {}, rowCount: {},
loading: { loading: {
deploy: false, deploy: false,
guestOsCategories: false,
templates: false, templates: false,
serviceOfferings: false, serviceOfferings: false,
diskOfferings: false, diskOfferings: false,
@ -1251,16 +1260,9 @@ export default {
templateUserDataParams: [], templateUserDataParams: [],
templateUserDataValues: {}, templateUserDataValues: {},
overrideDiskOffering: {}, overrideDiskOffering: {},
templateFilter: [
'featured',
'community',
'selfexecutable',
'sharedexecutable'
],
initDataConfig: {}, initDataConfig: {},
defaultNetworkId: '', defaultNetworkId: '',
dataNetworkCreated: [], dataNetworkCreated: [],
tabKey: 'templateid',
userdataTabKey: 'userdataregistered', userdataTabKey: 'userdataregistered',
dataPreFill: {}, dataPreFill: {},
showDetails: false, showDetails: false,
@ -1280,7 +1282,9 @@ export default {
zones: [], zones: [],
selectedZone: '', selectedZone: '',
formModel: {}, formModel: {},
nicToNetworkSelection: [] nicToNetworkSelection: [],
selectedArchitecture: null,
architectureTypes: {}
} }
}, },
computed: { computed: {
@ -1380,7 +1384,7 @@ export default {
options: { options: {
zoneid: _.get(this.zone, 'id'), zoneid: _.get(this.zone, 'id'),
networkid: this.defaultNetworkId, networkid: this.defaultNetworkId,
id: this.lbRuleId, id: this.queryLbRuleId,
projectid: store.getters.project ? store.getters.project.id : null, projectid: store.getters.project ? store.getters.project.id : null,
domainid: store.getters.project && store.getters.project.id ? null : store.getters.userInfo.domainid, domainid: store.getters.project && store.getters.project.id ? null : store.getters.userInfo.domainid,
account: store.getters.project && store.getters.project.id ? null : store.getters.userInfo.account, account: store.getters.project && store.getters.project.id ? null : store.getters.userInfo.account,
@ -1389,6 +1393,18 @@ export default {
keyword: undefined, keyword: undefined,
showIcon: true showIcon: true
} }
},
guestOsCategories: {
list: 'listOsCategories',
options: {
zoneid: _.get(this.zone, 'id'),
isfeatured: true,
isiso: _.get(this.form, 'imagetype') === 'isoid',
arch: this.selectedArchitecture,
isvnf: false,
showicon: true
},
field: 'guestoscategoryid'
} }
} }
}, },
@ -1406,21 +1422,29 @@ export default {
templateConfigurationExists () { templateConfigurationExists () {
return this.vm.templateid && this.templateConfigurations && this.templateConfigurations.length > 0 return this.vm.templateid && this.templateConfigurations && this.templateConfigurations.length > 0
}, },
templateId () { queryZoneId () {
return this.$route.query.zoneid || null
},
queryArchId () {
return this.$route.query.arch || null
},
queryTemplateId () {
return this.$route.query.templateid || null return this.$route.query.templateid || null
}, },
networkId () { queryNetworkId () {
return this.$route.query.networkid || null return this.$route.query.networkid || null
}, },
lbRuleId () { queryGuestOsCategoryId () {
return this.$route.query.oscategoryid || null
},
queryLbRuleId () {
return this.$route.query.lbruleid || null return this.$route.query.lbruleid || null
}, },
tabList () { imageTypeList () {
const tabList = [{ return [{
key: 'templateid', key: 'templateid',
tab: this.$t('label.templates') tab: this.$t('label.templates')
}] }]
return tabList
}, },
userdataTabList () { userdataTabList () {
let tabList = [] let tabList = []
@ -1451,6 +1475,18 @@ export default {
}, },
isCustomizedIOPS () { isCustomizedIOPS () {
return this.rootDiskSelected?.iscustomizediops || this.serviceOffering?.iscustomizediops || false return this.rootDiskSelected?.iscustomizediops || this.serviceOffering?.iscustomizediops || false
},
isModernImageSelection () {
return this.$config.imageSelectionInterface === undefined || this.$config.imageSelectionInterface === 'modern'
},
imageSelection () {
return this.isModernImageSelection ? 'modern' : 'legacy'
},
showUserCategoryForModernImageSelection () {
return this.$config.showUserCategoryForModernImageSelection === undefined || this.$config.showUserCategoryForModernImageSelection
},
showAllCategoryForModernImageSelection () {
return this.$config.showAllCategoryForModernImageSelection
} }
}, },
watch: { watch: {
@ -1466,7 +1502,7 @@ export default {
Object.keys(vmgroupConfig).forEach(field => { Object.keys(vmgroupConfig).forEach(field => {
this.vm[field] = this.vmgroupConfig[field] this.vm[field] = this.vmgroupConfig[field]
}) })
this.template = '' this.template = null
for (const key in this.options.templates) { for (const key in this.options.templates) {
var template = _.find(_.get(this.options.templates[key], 'template', []), (option) => option.id === vmgroupConfig.templateid) var template = _.find(_.get(this.options.templates[key], 'template', []), (option) => option.id === vmgroupConfig.templateid)
if (template) { if (template) {
@ -1545,7 +1581,7 @@ export default {
} }
} }
if (!this.template.deployasis && this.template.childtemplates && this.template.childtemplates.length > 0) { if (this.template && !this.template.deployasis && this.template.childtemplates && this.template.childtemplates.length > 0) {
this.vm.diskofferingid = '' this.vm.diskofferingid = ''
this.vm.diskofferingname = '' this.vm.diskofferingname = ''
this.vm.diskofferingsize = '' this.vm.diskofferingsize = ''
@ -1766,6 +1802,27 @@ export default {
} }
} }
}, },
getImageFilters (params, forReset) {
if (this.isModernImageSelection) {
if (this.form.guestoscategoryid === '0') {
return ['self']
}
if (this.isModernImageSelection && params && !forReset) {
if (params.featured) {
return ['featured']
} else if (params.public) {
return ['community']
}
}
return this.isNormalAndDomainUser ? ['executable'] : ['all']
}
return [
'featured',
'community',
'selfexecutable',
'sharedexecutable'
]
},
getPropertyQualifiers (qualifiers, type) { getPropertyQualifiers (qualifiers, type) {
var result = '' var result = ''
switch (type) { switch (type) {
@ -1805,18 +1862,25 @@ export default {
let zones = [] let zones = []
let apiName = '' let apiName = ''
const params = {} const params = {}
if (this.templateId) { if (this.queryZoneId) {
zones.push(this.queryZoneId)
if (this.queryTemplateId) {
this.dataPreFill.templateid = this.queryTemplateId
}
return resolve(zones)
} else if (this.queryTemplateId) {
apiName = 'listTemplates' apiName = 'listTemplates'
params.listall = true params.listall = true
params.templatefilter = this.isNormalAndDomainUser ? 'executable' : 'all' params.templatefilter = this.isNormalAndDomainUser ? 'executable' : 'all'
params.id = this.templateId params.id = this.queryTemplateId
} else if (this.networkId) { this.dataPreFill.templateid = this.queryTemplateId
} else if (this.queryNetworkId) {
params.listall = true params.listall = true
params.id = this.networkId params.id = this.queryNetworkId
apiName = 'listNetworks' apiName = 'listNetworks'
} else if (this.lbRuleId) { } else if (this.queryLbRuleId) {
params.listall = true params.listall = true
params.id = this.lbRuleId params.id = this.queryLbRuleId
apiName = 'listLoadBalancerRules' apiName = 'listLoadBalancerRules'
} }
if (!apiName) return resolve(zones) if (!apiName) return resolve(zones)
@ -1840,6 +1904,10 @@ export default {
}) })
}, },
async fetchData () { async fetchData () {
this.architectureTypes.opts = this.$fetchCpuArchitectureTypes()
if (this.queryArchId) {
this.architectureTypes.opts = this.architectureTypes.opts.filter(o => o.id === this.queryArchId)
}
const zones = await this.fetchZoneByQuery() const zones = await this.fetchZoneByQuery()
if (zones && zones.length === 1) { if (zones && zones.length === 1) {
this.selectedZone = zones[0] this.selectedZone = zones[0]
@ -1986,25 +2054,18 @@ export default {
}, },
updateFieldValue (name, value) { updateFieldValue (name, value) {
if (name === 'templateid') { if (name === 'templateid') {
this.tabKey = 'templateid'
this.form.templateid = value this.form.templateid = value
this.resetFromTemplateConfiguration() this.resetFromTemplateConfiguration()
let template = '' let template = ''
for (const key in this.options.templates) { for (const entry of Object.values(this.options.templates)) {
var t = _.find(_.get(this.options.templates[key], 'template', []), (option) => option.id === value) template = entry?.template.find(option => option.id === value) || null
if (t) { if (template) {
this.template = t this.template = template
this.templateConfigurations = []
this.selectedTemplateConfiguration = {}
this.templateNics = []
this.templateLicenses = []
this.templateProperties = {}
this.updateTemplateParameters()
template = t
break break
} }
} }
if (template) { if (template) {
this.resetTemplateAssociatedResources()
var size = template.size / (1024 * 1024 * 1024) || 0 // bytes to GB var size = template.size / (1024 * 1024 * 1024) || 0 // bytes to GB
this.dataPreFill.minrootdisksize = Math.ceil(size) this.dataPreFill.minrootdisksize = Math.ceil(size)
this.updateTemplateLinkedUserData(this.template.userdataid) this.updateTemplateLinkedUserData(this.template.userdataid)
@ -2392,6 +2453,58 @@ export default {
getText (option) { getText (option) {
return _.get(option, 'displaytext', _.get(option, 'name')) return _.get(option, 'displaytext', _.get(option, 'name'))
}, },
fetchGuestOsCategories (skipFetchImages) {
const key = 'guestOsCategories'
const params = this.params[key]
if (this.queryGuestOsCategoryId) {
params.options.id = this.queryGuestOsCategoryId
} else if (this.queryTemplateId) {
this.fetchAllTemplates()
return Promise.resolve()
}
return this.fetchOptions(params, key, ['zones'])
.then((res) => {
if (!this.options.guestOsCategories) {
this.options.guestOsCategories = []
}
if (!this.queryGuestOsCategoryId) {
if (this.showUserCategoryForModernImageSelection) {
const userCategory = {
id: '0',
name: this.$t('label.user')
}
if (this.$store.getters.avatar) {
userCategory.icon = {
base64image: this.$store.getters.avatar
}
}
this.options.guestOsCategories.push(userCategory)
}
if (this.showAllCategoryForModernImageSelection) {
this.options.guestOsCategories.push({
id: '-1',
name: this.$t('label.all')
})
}
}
this.form.guestoscategoryid = this.options.guestOsCategories[0].id
if (skipFetchImages) {
return
}
this.fetchAllTemplates()
})
.catch((e) => {
console.error('Error fetching guestOsCategories:', e)
})
},
changeArchitecture (arch) {
this.selectedArchitecture = arch
if (this.isModernImageSelection) {
this.fetchGuestOsCategories()
return
}
this.fetchAllTemplates()
},
async handleSubmit (e) { async handleSubmit (e) {
console.log('wizard submit') console.log('wizard submit')
e.preventDefault() e.preventDefault()
@ -2552,7 +2665,7 @@ export default {
if (this.showRootDiskSizeChanger && values.rootdisksize && values.rootdisksize > 0) { if (this.showRootDiskSizeChanger && values.rootdisksize && values.rootdisksize > 0) {
createVmGroupData.rootdisksize = values.rootdisksize createVmGroupData.rootdisksize = values.rootdisksize
} else if (this.rootDiskSizeFixed > 0 && !this.template.deployasis) { } else if (this.rootDiskSizeFixed > 0 && this.template && !this.template.deployasis) {
createVmGroupData.rootdisksize = this.rootDiskSizeFixed createVmGroupData.rootdisksize = this.rootDiskSizeFixed
} }
@ -2799,10 +2912,9 @@ export default {
}) })
}, },
fetchOptions (param, name, exclude) { fetchOptions (param, name, exclude) {
if (exclude && exclude.length > 0) { return new Promise((resolve, reject) => {
if (exclude.includes(name)) { if (exclude && exclude.length > 0 && exclude.includes(name)) {
return return resolve(null)
}
} }
this.loading[name] = true this.loading[name] = true
param.loading = true param.loading = true
@ -2817,10 +2929,10 @@ export default {
if (Object.keys(responseItem).length === 0) { if (Object.keys(responseItem).length === 0) {
this.rowCount[name] = 0 this.rowCount[name] = 0
this.options[name] = [] this.options[name] = []
return return resolve(null)
} }
if (!responseKey.includes('response')) { if (!responseKey.includes('response')) {
return return resolve(null)
} }
_.map(responseItem, (response, key) => { _.map(responseItem, (response, key) => {
if (key === 'count') { if (key === 'count') {
@ -2848,23 +2960,38 @@ export default {
} }
} }
}) })
resolve(response)
}).catch(function (error) { }).catch(function (error) {
console.log(error.stack) console.log(error.stack)
param.loading = false param.loading = false
reject(error)
}).finally(() => { }).finally(() => {
this.loading[name] = false this.loading[name] = false
}) })
})
}, },
fetchTemplates (templateFilter, params) { fetchTemplates (templateFilter, params) {
const args = Object.assign({}, params) const args = Object.assign({}, params)
if (args.keyword || args.category !== templateFilter) { if (this.isModernImageSelection && this.form.guestoscategoryid && !['-1', '0'].includes(this.form.guestoscategoryid)) {
args.oscategoryid = this.form.guestoscategoryid
}
if (args.keyword || (args.category && args.category !== templateFilter)) {
args.page = 1 args.page = 1
args.pageSize = args.pageSize || 10 args.pageSize = args.pageSize || 10
} }
args.zoneid = _.get(this.zone, 'id') args.zoneid = _.get(this.zone, 'id')
if (this.isZoneSelectedMultiArch) {
args.arch = this.selectedArchitecture
}
args.templatefilter = templateFilter args.templatefilter = templateFilter
args.details = 'all' args.details = 'all'
args.showicon = 'true' args.showicon = 'true'
args.id = this.queryTemplateId
args.isvnf = false
delete args.category
delete args.public
delete args.featured
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
api('listTemplates', args).then((response) => { api('listTemplates', args).then((response) => {
@ -2879,14 +3006,16 @@ export default {
const promises = [] const promises = []
const templates = {} const templates = {}
this.loading.templates = true this.loading.templates = true
this.templateFilter.forEach((filter) => { this.imageSearchFilters = params
const templateFilters = this.getImageFilters(params)
templateFilters.forEach((filter) => {
templates[filter] = { count: 0, template: [] } templates[filter] = { count: 0, template: [] }
promises.push(this.fetchTemplates(filter, params)) promises.push(this.fetchTemplates(filter, params))
}) })
this.options.templates = templates this.options.templates = templates
Promise.all(promises).then((response) => { Promise.all(promises).then((response) => {
response.forEach((resItem, idx) => { response.forEach((resItem, idx) => {
templates[this.templateFilter[idx]] = _.isEmpty(resItem.listtemplatesresponse) ? { count: 0, template: [] } : resItem.listtemplatesresponse templates[templateFilters[idx]] = _.isEmpty(resItem.listtemplatesresponse) ? { count: 0, template: [] } : resItem.listtemplatesresponse
this.options.templates = { ...templates } this.options.templates = { ...templates }
}) })
}).catch((reason) => { }).catch((reason) => {
@ -2898,35 +3027,71 @@ export default {
filterOption (input, option) { filterOption (input, option) {
return option.label.toUpperCase().indexOf(input.toUpperCase()) >= 0 return option.label.toUpperCase().indexOf(input.toUpperCase()) >= 0
}, },
resetTemplatesList () {
const templates = {}
const templateFilters = this.getImageFilters(null, true)
templateFilters.forEach((filter) => {
templates[filter] = { count: 0, template: [] }
})
this.options.templates = templates
},
resetTemplateAssociatedResources () {
this.templateConfigurations = []
this.selectedTemplateConfiguration = {}
this.templateNics = []
this.templateLicenses = []
this.templateProperties = {}
},
async fetchZoneOptions () {
let guestOsFetch = null
for (const [name, param] of Object.entries(this.params)) {
if (this.queryNetworkId && name === 'networks') {
param.options = { id: this.queryNetworkId }
}
if (name === 'loadbalancers') {
if (!this.queryLbRuleId && !this.defaultNetworkId) {
continue
}
if (this.queryLbRuleId) {
param.options = { id: this.queryLbRuleId }
}
}
const shouldLoad = !('isLoad' in param) || param.isLoad
if (!shouldLoad) continue
if (this.isModernImageSelection && name === 'guestOsCategories') {
guestOsFetch = this.fetchGuestOsCategories(true)
} else {
this.fetchOptions(param, name, ['zones'])
}
}
if (this.isModernImageSelection && guestOsFetch) {
await guestOsFetch
}
this.fetchAllTemplates()
this.updateTemplateKey()
this.formModel = toRaw(this.form)
},
onSelectZoneId (value) { onSelectZoneId (value) {
if (this.dataPreFill.zoneid !== value) {
this.dataPreFill = {} this.dataPreFill = {}
}
this.zoneId = value this.zoneId = value
this.zone = _.find(this.options.zones, (option) => option.id === value) this.zone = _.find(this.options.zones, (option) => option.id === value)
this.zoneSelected = true this.zoneSelected = true
this.isZoneSelectedMultiArch = this.zone.ismultiarch
if (this.isZoneSelectedMultiArch) {
this.selectedArchitecture = this.architectureTypes.opts[0].id
}
this.selectedZone = this.zoneId this.selectedZone = this.zoneId
this.form.zoneid = this.zoneId this.form.zoneid = this.zoneId
this.form.templateid = undefined this.form.templateid = undefined
this.tabKey = 'templateid' this.resetTemplatesList()
_.each(this.params, (param, name) => { this.fetchZoneOptions()
if (this.networkId && name === 'networks') { },
param.options = { onSelectGuestOsCategory (value) {
id: this.networkId this.form.guestoscategoryid = value
} this.fetchAllTemplates(this.imageSearchFilters)
}
if (this.lbRuleId && name === 'loadbalancers') {
param.options = {
id: this.lbRuleId
}
}
if (!('isLoad' in param) || param.isLoad) {
this.fetchOptions(param, name, ['zones'])
}
})
if (this.tabKey === 'templateid') {
this.fetchAllTemplates()
}
this.updateTemplateKey()
this.formModel = toRaw(this.form)
}, },
handleSearchFilter (name, options) { handleSearchFilter (name, options) {
this.params[name].options = { ...this.params[name].options, ...options } this.params[name].options = { ...this.params[name].options, ...options }
@ -3184,7 +3349,7 @@ export default {
.vm-info-card { .vm-info-card {
.ant-card-body { .ant-card-body {
min-height: 250px; min-height: 250px;
max-height: calc(100vh - 150px); max-height: calc(100vh - 250px);
overflow-y: auto; overflow-y: auto;
scroll-behavior: smooth; scroll-behavior: smooth;
} }

File diff suppressed because it is too large Load Diff

View File

@ -34,72 +34,70 @@
<div style="margin-top: 15px"> <div style="margin-top: 15px">
<span>{{ $t('message.select.a.zone') }}</span><br/> <span>{{ $t('message.select.a.zone') }}</span><br/>
<a-form-item :label="$t('label.zoneid')" name="zoneid" ref="zoneid"> <a-form-item :label="$t('label.zoneid')" name="zoneid" ref="zoneid">
<div v-if="zones.length <= 8"> <zone-block-radio-group-select
<a-row type="flex" :gutter="[16, 18]" justify="start"> :items="zones"
<div v-for="(zoneItem, idx) in zones" :key="idx"> :selectedValue="form.zoneid"
<a-radio-group @change="onSelectZoneId" />
:key="idx"
:size="large"
v-model:value="form.zoneid"
@change="onSelectZoneId(zoneItem.id)">
<a-col :span="6">
<a-radio-button
:value="zoneItem.id"
style="border-width: 2px"
class="zone-radio-button">
<span>
<resource-icon
v-if="zoneItem && zoneItem.icon && zoneItem.icon.base64image"
:image="zoneItem.icon.base64image"
size="2x" />
<global-outlined size="2x" v-else />
{{ zoneItem.name }}
</span>
</a-radio-button>
</a-col>
</a-radio-group>
</div>
</a-row>
</div>
<a-select
v-else
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="onSelectZoneId"
:loading="loading.zones"
v-focus="true"
>
<a-select-option v-for="zone1 in zones" :key="zone1.id" :label="zone1.name">
<span>
<resource-icon v-if="zone1.icon && zone1.icon.base64image" :image="zone1.icon.base64image" size="2x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px" />
{{ zone1.name }}
</span>
</a-select-option>
</a-select>
</a-form-item> </a-form-item>
</div> </div>
</template> </template>
</a-step> </a-step>
<a-step <a-step
:title="$t('label.template')" v-if="!zoneSelected || isZoneSelectedMultiArch"
:title="$t('label.arch')"
:status="zoneSelected ? 'process' : 'wait'"> :status="zoneSelected ? 'process' : 'wait'">
<template #description> <template #description>
<div v-if="zoneSelected" style="margin-top: 15px"> <div v-if="zoneSelected" style="margin-top: 15px">
{{ $t('message.instance.architecture') }}
<block-radio-group-select
style="margin-top: 5px;"
:items="architectureTypes.opts"
:selectedValue="selectedArchitecture"
@change="changeArchitecture">
<template #radio-option="{ item }">
<span>{{ item.name || item.description }}</span>
</template>
<template #select-option="{ item }">
<span>{{ item.name || item.description }}</span>
</template>
</block-radio-group-select>
</div>
</template>
</a-step>
<a-step
:title="$t('label.template.select')"
:status="zoneSelected ? 'process' : 'wait'">
<template #description>
<div v-if="zoneSelected" style="margin-top: 15px">
<os-based-image-selection
v-if="isModernImageSelection"
:imageTypeSelectionAllowed="false"
:imagePreSelected="!!this.queryTemplateId"
:guestOsCategoriesSelectionDisallowed="guestOsCategoriesSelectionDisallowed"
:guestOsCategories="options.guestOsCategories"
:guestOsCategoriesLoading="loading.guestOsCategories"
:selectedGuestOsCategoryId="form.guestoscategoryid"
:imageItems="options.templates"
:imagesLoading="loading.templates"
:diskSizeSelectionDeployAsIsMessageVisible="template && template.deployasis"
:rootDiskOverrideDisabled="rootDiskSizeFixed > 0 || (template && template.deployasis) || showOverrideDiskOfferingOption"
:rootDiskOverrideChecked="form.rootdisksizeitem"
:filterOption="filterOption"
:preFillContent="dataPreFill"
@change-guest-os-category="onSelectGuestOsCategory"
@handle-image-search-filter="($event) => fetchAllTemplates($event)"
@update-image="updateFieldValue"
@update-disk-size="updateFieldValue" />
<a-card <a-card
:tabList="tabList" v-else
:activeTabKey="tabKey" :tabList="imageTypeList"
@tabChange="key => onTabChange(key, 'tabKey')"> :activeTabKey="imageType">
<div v-if="tabKey === 'templateid'"> <div>
{{ $t('message.template.desc') }} {{ $t('message.template.desc') }}
<template-iso-selection <template-iso-selection
input-decorator="templateid" input-decorator="templateid"
:items="options.templates" :items="options.templates"
:selected="tabKey" :selected="imageType"
:loading="loading.templates" :loading="loading.templates"
:preFillContent="dataPreFill" :preFillContent="dataPreFill"
:key="templateKey" :key="templateKey"
@ -109,10 +107,10 @@
{{ $t('label.override.rootdisk.size') }} {{ $t('label.override.rootdisk.size') }}
<a-switch <a-switch
v-model:checked="form.rootdisksizeitem" v-model:checked="form.rootdisksizeitem"
:disabled="rootDiskSizeFixed > 0 || template.deployasis || showOverrideDiskOfferingOption" :disabled="rootDiskSizeFixed > 0 || (template && template.deployasis) || showOverrideDiskOfferingOption"
@change="val => { showRootDiskSizeChanger = val }" @change="val => { showRootDiskSizeChanger = val }"
style="margin-left: 10px;"/> style="margin-left: 10px;"/>
<div v-if="template.deployasis"> {{ $t('message.deployasis') }} </div> <div v-if="template && template.deployasis"> {{ $t('message.deployasis') }} </div>
</div> </div>
<disk-size-selection <disk-size-selection
v-if="showRootDiskSizeChanger" v-if="showRootDiskSizeChanger"
@ -148,9 +146,7 @@
v-model:value="form.templateConfiguration" v-model:value="form.templateConfiguration"
defaultActiveFirstOption defaultActiveFirstOption
:placeholder="$t('message.ovf.configurations')" :placeholder="$t('message.ovf.configurations')"
:filterOption="(input, option) => { :filterOption="filterOption"
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="onSelectTemplateConfigurationId" @change="onSelectTemplateConfigurationId"
> >
<a-select-option v-for="opt in templateConfigurations" :key="opt.id" :label="opt.name || opt.description"> <a-select-option v-for="opt in templateConfigurations" :key="opt.id" :label="opt.name || opt.description">
@ -207,19 +203,10 @@
<a-input v-model:value="form.memory"/> <a-input v-model:value="form.memory"/>
</a-form-item> </a-form-item>
</span> </span>
<span v-if="tabKey!=='isoid'"> <span v-if="imageType!=='isoid' && serviceOffering && !serviceOffering.diskofferingstrictness">
{{ $t('label.override.root.diskoffering') }}
<a-switch
v-model:checked="showOverrideDiskOfferingOption"
:checked="serviceOffering && !serviceOffering.diskofferingstrictness && showOverrideDiskOfferingOption"
:disabled="(serviceOffering && serviceOffering.diskofferingstrictness)"
@change="val => { updateOverrideRootDiskShowParam(val) }"
style="margin-left: 10px;"/>
</span>
<span v-if="tabKey!=='isoid' && serviceOffering && !serviceOffering.diskofferingstrictness">
<a-step <a-step
:status="zoneSelected ? 'process' : 'wait'" :status="zoneSelected ? 'process' : 'wait'"
v-if="!template.deployasis && template.childtemplates && template.childtemplates.length > 0" > v-if="template && !template.deployasis && template.childtemplates && template.childtemplates.length > 0" >
<template #description> <template #description>
<div v-if="zoneSelected"> <div v-if="zoneSelected">
<multi-disk-selection <multi-disk-selection
@ -243,7 +230,7 @@
:value="overrideDiskOffering ? overrideDiskOffering.id : ''" :value="overrideDiskOffering ? overrideDiskOffering.id : ''"
:loading="loading.diskOfferings" :loading="loading.diskOfferings"
:preFillContent="dataPreFill" :preFillContent="dataPreFill"
:isIsoSelected="tabKey==='isoid'" :isIsoSelected="imageType==='isoid'"
:isRootDiskOffering="true" :isRootDiskOffering="true"
@on-selected-root-disk-size="onSelectRootDiskSize" @on-selected-root-disk-size="onSelectRootDiskSize"
@select-disk-offering-item="($event) => updateOverrideDiskOffering($event)" @select-disk-offering-item="($event) => updateOverrideDiskOffering($event)"
@ -272,7 +259,7 @@
<a-step <a-step
:title="$t('label.data.disk')" :title="$t('label.data.disk')"
:status="zoneSelected ? 'process' : 'wait'" :status="zoneSelected ? 'process' : 'wait'"
v-if="!template.deployasis && template.childtemplates && template.childtemplates.length > 0" > v-if="template && !template.deployasis && template.childtemplates && template.childtemplates.length > 0" >
<template #description> <template #description>
<div v-if="zoneSelected"> <div v-if="zoneSelected">
<multi-disk-selection <multi-disk-selection
@ -285,7 +272,7 @@
</a-step> </a-step>
<a-step <a-step
v-else-if="vm.templateid && template.templatetype !== 'VNF'" v-else-if="vm.templateid && template.templatetype !== 'VNF'"
:title="tabKey === 'templateid' ? $t('label.data.disk') : $t('label.disk.size')" :title="imageType === 'templateid' ? $t('label.data.disk') : $t('label.disk.size')"
:status="zoneSelected ? 'process' : 'wait'"> :status="zoneSelected ? 'process' : 'wait'">
<template #description> <template #description>
<div v-if="zoneSelected"> <div v-if="zoneSelected">
@ -304,7 +291,7 @@
:value="diskOffering ? diskOffering.id : ''" :value="diskOffering ? diskOffering.id : ''"
:loading="loading.diskOfferings" :loading="loading.diskOfferings"
:preFillContent="dataPreFill" :preFillContent="dataPreFill"
:isIsoSelected="tabKey==='isoid'" :isIsoSelected="imageType==='isoid'"
@on-selected-disk-size="onSelectDiskSize" @on-selected-disk-size="onSelectDiskSize"
@select-disk-offering-item="($event) => updateDiskOffering($event)" @select-disk-offering-item="($event) => updateDiskOffering($event)"
@handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)" @handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)"
@ -439,9 +426,7 @@
optionFilterProp="value" optionFilterProp="value"
v-model:value="form['properties.' + escapePropertyKey(property.key)]" v-model:value="form['properties.' + escapePropertyKey(property.key)]"
:placeholder="property.description" :placeholder="property.description"
:filterOption="(input, option) => { :filterOption="filterOption"
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
> >
<a-select-option v-for="opt in getPropertyQualifiers(property.qualifiers, 'select')" :key="opt"> <a-select-option v-for="opt in getPropertyQualifiers(property.qualifiers, 'select')" :key="opt">
{{ opt }} {{ opt }}
@ -520,7 +505,7 @@
</a-form-item> </a-form-item>
</div> </div>
<div <div
v-if="vm.templateid && ['KVM', 'VMware', 'XenServer'].includes(hypervisor) && !template.deployasis"> v-if="vm.templateid && ['KVM', 'VMware', 'XenServer'].includes(hypervisor) && this.template && !template.deployasis">
<a-form-item :label="$t('label.boottype')" name="boottype" ref="boottype"> <a-form-item :label="$t('label.boottype')" name="boottype" ref="boottype">
<a-select <a-select
v-model:value="form.boottype" v-model:value="form.boottype"
@ -547,7 +532,7 @@
</div> </div>
<a-form-item <a-form-item
:label="$t('label.bootintosetup')" :label="$t('label.bootintosetup')"
v-if="zoneSelected && ((tabKey === 'isoid' && hypervisor === 'VMware') || (tabKey === 'templateid' && template && template.hypervisor === 'VMware'))" v-if="zoneSelected && ((imageType === 'isoid' && hypervisor === 'VMware') || (imageType === 'templateid' && template && template.hypervisor === 'VMware'))"
name="bootintosetup" name="bootintosetup"
ref="bootintosetup"> ref="bootintosetup">
<a-switch v-model:checked="form.bootintosetup" /> <a-switch v-model:checked="form.bootintosetup" />
@ -567,13 +552,13 @@
<a-form-item :label="$t('label.userdata')"> <a-form-item :label="$t('label.userdata')">
<a-card> <a-card>
<div v-if="this.template && this.template.userdataid"> <div v-if="this.template && this.template.userdataid">
<a-text type="primary"> <a-typography-text>
Userdata "{{ $t(this.template.userdataname) }}" is linked with template "{{ $t(this.template.name) }}" with override policy "{{ $t(this.template.userdatapolicy) }}" Userdata "{{ $t(this.template.userdataname) }}" is linked with template "{{ $t(this.template.name) }}" with override policy "{{ $t(this.template.userdatapolicy) }}"
</a-text><br/><br/> </a-typography-text><br/><br/>
<div v-if="templateUserDataParams.length > 0 && !doUserdataOverride"> <div v-if="templateUserDataParams.length > 0 && !doUserdataOverride">
<a-text type="primary" v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0"> <a-typography-text v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0">
Enter the values for the variables in userdata Enter the values for the variables in userdata
</a-text> </a-typography-text>
<a-input-group> <a-input-group>
<a-table <a-table
size="small" size="small"
@ -592,13 +577,13 @@
</div> </div>
</div> </div>
<div v-if="this.iso && this.iso.userdataid"> <div v-if="this.iso && this.iso.userdataid">
<a-text type="primary"> <a-typography-text>
Userdata "{{ $t(this.iso.userdataname) }}" is linked with ISO "{{ $t(this.iso.name) }}" with override policy "{{ $t(this.iso.userdatapolicy) }}" Userdata "{{ $t(this.iso.userdataname) }}" is linked with ISO "{{ $t(this.iso.name) }}" with override policy "{{ $t(this.iso.userdatapolicy) }}"
</a-text><br/><br/> </a-typography-text><br/><br/>
<div v-if="templateUserDataParams.length > 0 && !doUserdataOverride"> <div v-if="templateUserDataParams.length > 0 && !doUserdataOverride">
<a-text type="primary" v-if="this.iso && this.iso.userdataid && templateUserDataParams.length > 0"> <a-typography-text v-if="this.iso && this.iso.userdataid && templateUserDataParams.length > 0">
Enter the values for the variables in userdata Enter the values for the variables in userdata
</a-text> </a-typography-text>
<a-input-group> <a-input-group>
<a-table <a-table
size="small" size="small"
@ -864,12 +849,15 @@ import eventBus from '@/config/eventBus'
import InfoCard from '@/components/view/InfoCard' import InfoCard from '@/components/view/InfoCard'
import ResourceIcon from '@/components/view/ResourceIcon' import ResourceIcon from '@/components/view/ResourceIcon'
import ZoneBlockRadioGroupSelect from '@views/compute/wizard/ZoneBlockRadioGroupSelect.vue'
import BlockRadioGroupSelect from '@/components/widgets/BlockRadioGroupSelect'
import ComputeOfferingSelection from '@views/compute/wizard/ComputeOfferingSelection' import ComputeOfferingSelection from '@views/compute/wizard/ComputeOfferingSelection'
import ComputeSelection from '@views/compute/wizard/ComputeSelection' import ComputeSelection from '@views/compute/wizard/ComputeSelection'
import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection' import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection'
import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection' import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection'
import MultiDiskSelection from '@views/compute/wizard/MultiDiskSelection' import MultiDiskSelection from '@views/compute/wizard/MultiDiskSelection'
import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection' import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection'
import OsBasedImageSelection from '@views/compute/wizard/OsBasedImageSelection'
import AffinityGroupSelection from '@views/compute/wizard/AffinityGroupSelection' import AffinityGroupSelection from '@views/compute/wizard/AffinityGroupSelection'
import NetworkSelection from '@views/compute/wizard/NetworkSelection' import NetworkSelection from '@views/compute/wizard/NetworkSelection'
import NetworkConfiguration from '@views/compute/wizard/NetworkConfiguration' import NetworkConfiguration from '@views/compute/wizard/NetworkConfiguration'
@ -883,21 +871,24 @@ import InstanceNicsNetworkSelectListView from '@/components/view/InstanceNicsNet
export default { export default {
name: 'Wizard', name: 'Wizard',
components: { components: {
InfoCard,
ResourceIcon,
ZoneBlockRadioGroupSelect,
BlockRadioGroupSelect,
SshKeyPairSelection, SshKeyPairSelection,
UserDataSelection, UserDataSelection,
NetworkConfiguration, NetworkConfiguration,
NetworkSelection, NetworkSelection,
AffinityGroupSelection, AffinityGroupSelection,
TemplateIsoSelection, TemplateIsoSelection,
OsBasedImageSelection,
DiskSizeSelection, DiskSizeSelection,
MultiDiskSelection, MultiDiskSelection,
DiskOfferingSelection, DiskOfferingSelection,
InfoCard,
ComputeOfferingSelection, ComputeOfferingSelection,
ComputeSelection, ComputeSelection,
SecurityGroupSelection, SecurityGroupSelection,
VnfNicsSelection, VnfNicsSelection,
ResourceIcon,
TooltipLabel, TooltipLabel,
InstanceNicsNetworkSelectListView InstanceNicsNetworkSelectListView
}, },
@ -917,7 +908,10 @@ export default {
podId: null, podId: null,
clusterId: null, clusterId: null,
zoneSelected: false, zoneSelected: false,
isZoneSelectedMultiArch: false,
dynamicscalingenabled: true, dynamicscalingenabled: true,
imageType: 'templateid',
imageSearchFilters: null,
templateKey: 0, templateKey: 0,
showRegisteredUserdata: true, showRegisteredUserdata: true,
doUserdataOverride: false, doUserdataOverride: false,
@ -943,6 +937,7 @@ export default {
disksize: null disksize: null
}, },
options: { options: {
guestOsCategories: [],
templates: {}, templates: {},
isos: {}, isos: {},
hypervisors: [], hypervisors: [],
@ -966,6 +961,7 @@ export default {
rowCount: {}, rowCount: {},
loading: { loading: {
deploy: false, deploy: false,
guestOsCategories: false,
templates: false, templates: false,
isos: false, isos: false,
hypervisors: false, hypervisors: false,
@ -1029,23 +1025,10 @@ export default {
templateUserDataParams: [], templateUserDataParams: [],
templateUserDataValues: {}, templateUserDataValues: {},
overrideDiskOffering: {}, overrideDiskOffering: {},
templateFilter: [
'featured',
'community',
'selfexecutable',
'sharedexecutable'
],
isoFilter: [
'featured',
'community',
'selfexecutable',
'sharedexecutable'
],
initDataConfig: {}, initDataConfig: {},
defaultnetworkid: '', defaultnetworkid: '',
networkConfig: [], networkConfig: [],
dataNetworkCreated: [], dataNetworkCreated: [],
tabKey: 'templateid',
userdataTabKey: 'userdataregistered', userdataTabKey: 'userdataregistered',
dataPreFill: {}, dataPreFill: {},
showDetails: false, showDetails: false,
@ -1065,7 +1048,9 @@ export default {
zones: [], zones: [],
selectedZone: '', selectedZone: '',
formModel: {}, formModel: {},
nicToNetworkSelection: [] nicToNetworkSelection: [],
selectedArchitecture: null,
architectureTypes: {}
} }
}, },
computed: { computed: {
@ -1201,6 +1186,18 @@ export default {
zoneid: _.get(this.zone, 'id'), zoneid: _.get(this.zone, 'id'),
name: 'enable.dynamic.scale.vm' name: 'enable.dynamic.scale.vm'
} }
},
guestOsCategories: {
list: 'listOsCategories',
options: {
zoneid: _.get(this.zone, 'id'),
isfeatured: true,
isiso: _.get(this.form, 'imagetype') === 'isoid',
arch: this.selectedArchitecture,
isvnf: true,
showicon: true
},
field: 'guestoscategoryid'
} }
} }
}, },
@ -1274,34 +1271,38 @@ export default {
templateConfigurationExists () { templateConfigurationExists () {
return this.vm.templateid && this.templateConfigurations && this.templateConfigurations.length > 0 return this.vm.templateid && this.templateConfigurations && this.templateConfigurations.length > 0
}, },
templateId () { queryZoneId () {
return this.$route.query.zoneid || null
},
queryArchId () {
return this.$route.query.arch || null
},
queryTemplateId () {
return this.$route.query.templateid || null return this.$route.query.templateid || null
}, },
isoId () { queryNetworkId () {
return this.$route.query.isoid || null
},
networkId () {
return this.$route.query.networkid || null return this.$route.query.networkid || null
}, },
tabList () { queryGuestOsCategoryId () {
const tabList = [{ return this.$route.query.oscategoryid || null
},
imageTypeList () {
return [{
key: 'templateid', key: 'templateid',
tab: this.$t('label.templates') tab: this.$t('label.templates')
}] }]
return tabList
}, },
userdataTabList () { userdataTabList () {
let tabList = [] return [
tabList = [{ {
key: 'userdataregistered', key: 'userdataregistered',
tab: this.$t('label.userdata.registered') tab: this.$t('label.userdata.registered')
}, },
{ {
key: 'userdatatext', key: 'userdatatext',
tab: this.$t('label.userdata.text') tab: this.$t('label.userdata.text')
}] }
]
return tabList
}, },
showVnfNicsSection () { showVnfNicsSection () {
return this.networks && this.networks.length > 0 && this.vm.templateid && this.templateVnfNics && this.templateVnfNics.length > 0 return this.networks && this.networks.length > 0 && this.vm.templateid && this.templateVnfNics && this.templateVnfNics.length > 0
@ -1339,6 +1340,21 @@ export default {
}, },
isCustomizedIOPS () { isCustomizedIOPS () {
return this.rootDiskSelected?.iscustomizediops || this.serviceOffering?.iscustomizediops || false return this.rootDiskSelected?.iscustomizediops || this.serviceOffering?.iscustomizediops || false
},
isModernImageSelection () {
return this.$config.imageSelectionInterface === undefined || this.$config.imageSelectionInterface === 'modern'
},
imageSelection () {
return this.isModernImageSelection ? 'modern' : 'legacy'
},
showUserCategoryForModernImageSelection () {
return this.$config.showUserCategoryForModernImageSelection === undefined || this.$config.showUserCategoryForModernImageSelection
},
showAllCategoryForModernImageSelection () {
return this.$config.showAllCategoryForModernImageSelection
},
guestOsCategoriesSelectionDisallowed () {
return (!this.queryGuestOsCategoryId || this.options.guestOsCategories.length === 0) && (!!this.queryTemplateId || !!this.queryIsoId)
} }
}, },
watch: { watch: {
@ -1354,7 +1370,7 @@ export default {
Object.keys(vnfAppConfig).forEach(field => { Object.keys(vnfAppConfig).forEach(field => {
this.vm[field] = this.vnfAppConfig[field] this.vm[field] = this.vnfAppConfig[field]
}) })
this.template = '' this.template = null
for (const key in this.options.templates) { for (const key in this.options.templates) {
var template = _.find(_.get(this.options.templates[key], 'template', []), (option) => option.id === vnfAppConfig.templateid) var template = _.find(_.get(this.options.templates[key], 'template', []), (option) => option.id === vnfAppConfig.templateid)
if (template) { if (template) {
@ -1363,7 +1379,7 @@ export default {
} }
} }
this.iso = '' this.iso = null
for (const key in this.options.isos) { for (const key in this.options.isos) {
var iso = _.find(_.get(this.options.isos[key], 'iso', []), (option) => option.id === vnfAppConfig.isoid) var iso = _.find(_.get(this.options.isos[key], 'iso', []), (option) => option.id === vnfAppConfig.isoid)
if (iso) { if (iso) {
@ -1487,7 +1503,7 @@ export default {
} }
} }
if (!this.template.deployasis && this.template.childtemplates && this.template.childtemplates.length > 0) { if (this.template && !this.template.deployasis && this.template.childtemplates && this.template.childtemplates.length > 0) {
this.vm.diskofferingid = '' this.vm.diskofferingid = ''
this.vm.diskofferingname = '' this.vm.diskofferingname = ''
this.vm.diskofferingsize = '' this.vm.diskofferingsize = ''
@ -1527,7 +1543,6 @@ export default {
provide () { provide () {
return { return {
vmFetchTemplates: this.fetchAllTemplates, vmFetchTemplates: this.fetchAllTemplates,
vmFetchIsos: this.fetchAllIsos,
vmFetchNetworks: this.fetchNetwork vmFetchNetworks: this.fetchNetwork
} }
}, },
@ -1626,6 +1641,27 @@ export default {
} }
} }
}, },
getImageFilters (params, forReset) {
if (this.isModernImageSelection) {
if (this.form.guestoscategoryid === '0') {
return ['self']
}
if (this.isModernImageSelection && params && !forReset) {
if (params.featured) {
return ['featured']
} else if (params.public) {
return ['community']
}
}
return this.isNormalAndDomainUser ? ['executable'] : ['all']
}
return [
'featured',
'community',
'selfexecutable',
'sharedexecutable'
]
},
getPropertyQualifiers (qualifiers, type) { getPropertyQualifiers (qualifiers, type) {
var result = '' var result = ''
switch (type) { switch (type) {
@ -1665,19 +1701,21 @@ export default {
let zones = [] let zones = []
let apiName = '' let apiName = ''
const params = {} const params = {}
if (this.templateId) { if (this.queryZoneId) {
zones.push(this.queryZoneId)
if (this.queryTemplateId) {
this.dataPreFill.templateid = this.queryTemplateId
}
return resolve(zones)
} else if (this.queryTemplateId) {
apiName = 'listVnfTemplates' apiName = 'listVnfTemplates'
params.listall = true params.listall = true
params.templatefilter = this.isNormalAndDomainUser ? 'executable' : 'all' params.templatefilter = this.isNormalAndDomainUser ? 'executable' : 'all'
params.id = this.templateId params.id = this.queryTemplateId
} else if (this.isoId) { this.dataPreFill.templateid = this.queryTemplateId
} else if (this.queryNetworkId) {
params.listall = true params.listall = true
params.isofilter = this.isNormalAndDomainUser ? 'executable' : 'all' params.id = this.queryNetworkId
params.id = this.isoId
apiName = 'listIsos'
} else if (this.networkId) {
params.listall = true
params.id = this.networkId
apiName = 'listNetworks' apiName = 'listNetworks'
} }
if (!apiName) return resolve(zones) if (!apiName) return resolve(zones)
@ -1701,6 +1739,10 @@ export default {
}) })
}, },
async fetchData () { async fetchData () {
this.architectureTypes.opts = this.$fetchCpuArchitectureTypes()
if (this.queryArchId) {
this.architectureTypes.opts = this.architectureTypes.opts.filter(o => o.id === this.queryArchId)
}
const zones = await this.fetchZoneByQuery() const zones = await this.fetchZoneByQuery()
if (zones && zones.length === 1) { if (zones && zones.length === 1) {
this.selectedZone = zones[0] this.selectedZone = zones[0]
@ -1816,27 +1858,20 @@ export default {
}, },
updateFieldValue (name, value) { updateFieldValue (name, value) {
if (name === 'templateid') { if (name === 'templateid') {
this.tabKey = 'templateid'
this.form.templateid = value this.form.templateid = value
this.form.isoid = null this.form.isoid = null
this.resetFromTemplateConfiguration() this.resetFromTemplateConfiguration()
let template = '' let template = ''
for (const key in this.options.templates) { for (const entry of Object.values(this.options.templates)) {
var t = _.find(_.get(this.options.templates[key], 'template', []), (option) => option.id === value) template = entry?.template.find(option => option.id === value) || null
if (t) { if (template) {
this.template = t this.template = template
this.templateConfigurations = []
this.selectedTemplateConfiguration = {}
this.templateNics = []
this.templateLicenses = []
this.templateProperties = {}
this.templateVnfNics = []
this.updateTemplateParameters()
template = t
break break
} }
} }
if (template) { if (template) {
this.resetTemplateAssociatedResources()
this.updateTemplateParameters()
var size = template.size / (1024 * 1024 * 1024) || 0 // bytes to GB var size = template.size / (1024 * 1024 * 1024) || 0 // bytes to GB
this.dataPreFill.minrootdisksize = Math.ceil(size) this.dataPreFill.minrootdisksize = Math.ceil(size)
this.updateTemplateLinkedUserData(template.userdataid) this.updateTemplateLinkedUserData(template.userdataid)
@ -1851,19 +1886,6 @@ export default {
this.form.iodriverpolicy = template.details?.['io.policy'] this.form.iodriverpolicy = template.details?.['io.policy']
this.form.keyboard = template.details?.keyboard this.form.keyboard = template.details?.keyboard
} }
} else if (name === 'isoid') {
this.templateConfigurations = []
this.selectedTemplateConfiguration = {}
this.templateNics = []
this.templateLicenses = []
this.templateProperties = {}
this.templateVnfNics = []
this.tabKey = 'isoid'
this.resetFromTemplateConfiguration()
this.form.isoid = value
this.form.templateid = null
this.updateTemplateLinkedUserData(this.iso.userdataid)
this.userdataDefaultOverridePolicy = this.iso.userdatapolicy
} else if (['cpuspeed', 'cpunumber', 'memory'].includes(name)) { } else if (['cpuspeed', 'cpunumber', 'memory'].includes(name)) {
this.vm[name] = value this.vm[name] = value
this.form[name] = value this.form[name] = value
@ -1977,6 +1999,60 @@ export default {
getText (option) { getText (option) {
return _.get(option, 'displaytext', _.get(option, 'name')) return _.get(option, 'displaytext', _.get(option, 'name'))
}, },
fetchGuestOsCategories (skipFetchImages) {
const key = 'guestOsCategories'
const params = this.params[key]
if (this.queryGuestOsCategoryId) {
params.options.id = this.queryGuestOsCategoryId
} else if (this.queryTemplateId) {
this.fetchAllTemplates()
return Promise.resolve()
}
return this.fetchOptions(params, key, ['zones'])
.then((res) => {
if (!this.options.guestOsCategories) {
this.options.guestOsCategories = []
}
if (!this.queryGuestOsCategoryId) {
if (this.showUserCategoryForModernImageSelection) {
const userCategory = {
id: '0',
name: this.$t('label.user')
}
if (this.$store.getters.avatar) {
userCategory.icon = {
base64image: this.$store.getters.avatar
}
}
this.options.guestOsCategories.push(userCategory)
}
if (this.showAllCategoryForModernImageSelection) {
this.options.guestOsCategories.push({
id: '-1',
name: this.$t('label.all')
})
}
}
if (this.options.guestOsCategories.length > 0) {
this.form.guestoscategoryid = this.options.guestOsCategories[0].id
}
if (skipFetchImages) {
return
}
this.fetchAllTemplates()
})
.catch((e) => {
console.error('Error fetching guestOsCategories:', e)
})
},
changeArchitecture (arch) {
this.selectedArchitecture = arch
if (this.isModernImageSelection) {
this.fetchGuestOsCategories()
return
}
this.fetchAllTemplates()
},
handleSubmitAndStay (e) { handleSubmitAndStay (e) {
this.form.stayonpage = true this.form.stayonpage = true
this.handleSubmit(e.domEvent) this.handleSubmit(e.domEvent)
@ -2096,7 +2172,7 @@ export default {
createVnfAppData.userdata = this.$toBase64AndURIEncoded(values.userdata) createVnfAppData.userdata = this.$toBase64AndURIEncoded(values.userdata)
} }
// step 2: select template/iso // step 2: select template/iso
if (this.tabKey === 'templateid') { if (this.imageType === 'templateid') {
createVnfAppData.templateid = values.templateid createVnfAppData.templateid = values.templateid
values.hypervisor = null values.hypervisor = null
} else { } else {
@ -2105,7 +2181,7 @@ export default {
if (this.showRootDiskSizeChanger && values.rootdisksize && values.rootdisksize > 0) { if (this.showRootDiskSizeChanger && values.rootdisksize && values.rootdisksize > 0) {
createVnfAppData.rootdisksize = values.rootdisksize createVnfAppData.rootdisksize = values.rootdisksize
} else if (this.rootDiskSizeFixed > 0 && !this.template.deployasis) { } else if (this.rootDiskSizeFixed > 0 && this.template && !this.template.deployasis) {
createVnfAppData.rootdisksize = this.rootDiskSizeFixed createVnfAppData.rootdisksize = this.rootDiskSizeFixed
} }
@ -2320,9 +2396,10 @@ export default {
} else { } else {
credentials.push(this.$t('message.vnf.no.credentials')) credentials.push(this.$t('message.vnf.no.credentials'))
} }
const credentialsDesc = credentials.join('<br>')
this.$notification.success({ this.$notification.success({
message: `${this.$t('message.vnf.credentials.default')} ` + name, message: `${this.$t('message.vnf.credentials.default')} ` + name,
description: (<span v-html={credentials.join('<br>')}></span>), description: (<span v-html={credentialsDesc}></span>),
duration: 0 duration: 0
}) })
eventBus.emit('vm-refresh-data') eventBus.emit('vm-refresh-data')
@ -2394,10 +2471,9 @@ export default {
}) })
}, },
fetchOptions (param, name, exclude) { fetchOptions (param, name, exclude) {
if (exclude && exclude.length > 0) { return new Promise((resolve, reject) => {
if (exclude.includes(name)) { if (exclude && exclude.length > 0 && exclude.includes(name)) {
return return resolve(null)
}
} }
this.loading[name] = true this.loading[name] = true
param.loading = true param.loading = true
@ -2412,10 +2488,10 @@ export default {
if (Object.keys(responseItem).length === 0) { if (Object.keys(responseItem).length === 0) {
this.rowCount[name] = 0 this.rowCount[name] = 0
this.options[name] = [] this.options[name] = []
return return resolve(null)
} }
if (!responseKey.includes('response')) { if (!responseKey.includes('response')) {
return return resolve(null)
} }
_.map(responseItem, (response, key) => { _.map(responseItem, (response, key) => {
if (key === 'count') { if (key === 'count') {
@ -2449,24 +2525,37 @@ export default {
} }
} }
}) })
resolve(response)
}).catch(function (error) { }).catch(function (error) {
console.log(error.stack) console.log(error.stack)
param.loading = false param.loading = false
reject(error)
}).finally(() => { }).finally(() => {
this.loading[name] = false this.loading[name] = false
}) })
})
}, },
fetchTemplates (templateFilter, params) { fetchTemplates (templateFilter, params) {
const args = Object.assign({}, params) const args = Object.assign({}, params)
if (args.keyword || args.category !== templateFilter) { if (this.isModernImageSelection && this.form.guestoscategoryid && !['-1', '0'].includes(this.form.guestoscategoryid)) {
args.oscategoryid = this.form.guestoscategoryid
}
if (args.keyword || (args.category && args.category !== templateFilter)) {
args.page = 1 args.page = 1
args.pageSize = args.pageSize || 10 args.pageSize = args.pageSize || 10
} }
args.zoneid = _.get(this.zone, 'id') args.zoneid = _.get(this.zone, 'id')
if (this.isZoneSelectedMultiArch) {
args.arch = this.selectedArchitecture
}
args.templatefilter = templateFilter args.templatefilter = templateFilter
args.details = 'all' args.details = 'all'
args.showicon = 'true' args.showicon = 'true'
args.id = this.templateId args.id = this.queryTemplateId
delete args.category
delete args.public
delete args.featured
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
api('listVnfTemplates', args).then((response) => { api('listVnfTemplates', args).then((response) => {
@ -2477,39 +2566,20 @@ export default {
}) })
}) })
}, },
fetchIsos (isoFilter, params) {
const args = Object.assign({}, params)
if (args.keyword || args.category !== isoFilter) {
args.page = 1
args.pageSize = args.pageSize || 10
}
args.zoneid = _.get(this.zone, 'id')
args.isoFilter = isoFilter
args.bootable = true
args.showicon = 'true'
args.id = this.isoId
return new Promise((resolve, reject) => {
api('listIsos', args).then((response) => {
resolve(response)
}).catch((reason) => {
// ToDo: Handle errors
reject(reason)
})
})
},
fetchAllTemplates (params) { fetchAllTemplates (params) {
const promises = [] const promises = []
const templates = {} const templates = {}
this.loading.templates = true this.loading.templates = true
this.templateFilter.forEach((filter) => { this.imageSearchFilters = params
const templateFilters = this.getImageFilters(params)
templateFilters.forEach((filter) => {
templates[filter] = { count: 0, template: [] } templates[filter] = { count: 0, template: [] }
promises.push(this.fetchTemplates(filter, params)) promises.push(this.fetchTemplates(filter, params))
}) })
this.options.templates = templates this.options.templates = templates
Promise.all(promises).then((response) => { Promise.all(promises).then((response) => {
response.forEach((resItem, idx) => { response.forEach((resItem, idx) => {
templates[this.templateFilter[idx]] = _.isEmpty(resItem.listtemplatesresponse) ? { count: 0, template: [] } : resItem.listtemplatesresponse templates[templateFilters[idx]] = _.isEmpty(resItem.listtemplatesresponse) ? { count: 0, template: [] } : resItem.listtemplatesresponse
this.options.templates = { ...templates } this.options.templates = { ...templates }
}) })
}).catch((reason) => { }).catch((reason) => {
@ -2518,36 +2588,60 @@ export default {
this.loading.templates = false this.loading.templates = false
}) })
}, },
fetchAllIsos (params) {
const promises = []
const isos = {}
this.loading.isos = true
this.isoFilter.forEach((filter) => {
isos[filter] = { count: 0, iso: [] }
promises.push(this.fetchIsos(filter, params))
})
this.options.isos = isos
Promise.all(promises).then((response) => {
response.forEach((resItem, idx) => {
isos[this.isoFilter[idx]] = _.isEmpty(resItem.listisosresponse) ? { count: 0, iso: [] } : resItem.listisosresponse
this.options.isos = { ...isos }
})
}).catch((reason) => {
console.log(reason)
}).finally(() => {
this.loading.isos = false
})
},
filterOption (input, option) { filterOption (input, option) {
return option.label.toUpperCase().indexOf(input.toUpperCase()) >= 0 return option.label.toUpperCase().indexOf(input.toUpperCase()) >= 0
}, },
resetTemplatesList () {
const templates = {}
const templateFilters = this.getImageFilters(null, true)
templateFilters.forEach((filter) => {
templates[filter] = { count: 0, template: [] }
})
this.options.templates = templates
},
resetTemplateAssociatedResources () {
this.templateConfigurations = []
this.selectedTemplateConfiguration = {}
this.templateNics = []
this.templateLicenses = []
this.templateProperties = {}
this.templateVnfNics = []
},
async fetchZoneOptions () {
let guestOsFetch = null
for (const [name, param] of Object.entries(this.params)) {
if (this.queryNetworkId && name === 'networks') {
param.options = { id: this.queryNetworkId }
}
const shouldLoad = !('isLoad' in param) || param.isLoad
if (!shouldLoad) continue
if (this.isModernImageSelection && name === 'guestOsCategories') {
guestOsFetch = this.fetchGuestOsCategories(true)
} else {
this.fetchOptions(param, name, ['zones'])
}
}
if (this.isModernImageSelection && guestOsFetch) {
await guestOsFetch
}
this.fetchAllTemplates()
this.updateTemplateKey()
this.formModel = toRaw(this.form)
},
onSelectZoneId (value) { onSelectZoneId (value) {
if (this.dataPreFill.zoneid !== value) {
this.dataPreFill = {} this.dataPreFill = {}
}
this.zoneId = value this.zoneId = value
this.podId = null this.podId = null
this.clusterId = null this.clusterId = null
this.zone = _.find(this.options.zones, (option) => option.id === value) this.zone = _.find(this.options.zones, (option) => option.id === value)
this.zoneSelected = true this.zoneSelected = true
this.isZoneSelectedMultiArch = this.zone.ismultiarch
if (this.isZoneSelectedMultiArch) {
this.selectedArchitecture = this.architectureTypes.opts[0].id
}
this.form.startvm = true this.form.startvm = true
this.form.vnfconfiguremanagement = false this.form.vnfconfiguremanagement = false
this.form.vnfcidrlist = '0.0.0.0/0' this.form.vnfcidrlist = '0.0.0.0/0'
@ -2558,27 +2652,8 @@ export default {
this.form.hostid = undefined this.form.hostid = undefined
this.form.templateid = undefined this.form.templateid = undefined
this.form.isoid = undefined this.form.isoid = undefined
this.tabKey = 'templateid' this.resetTemplatesList()
if (this.isoId) { this.fetchZoneOptions()
this.tabKey = 'isoid'
}
_.each(this.params, (param, name) => {
if (this.networkId && name === 'networks') {
param.options = {
id: this.networkId
}
}
if (!('isLoad' in param) || param.isLoad) {
this.fetchOptions(param, name, ['zones'])
}
})
if (this.tabKey === 'templateid') {
this.fetchAllTemplates()
} else {
this.fetchAllIsos()
}
this.updateTemplateKey()
this.formModel = toRaw(this.form)
}, },
onSelectPodId (value) { onSelectPodId (value) {
this.podId = value this.podId = value
@ -2603,15 +2678,16 @@ export default {
this.form.hostid = undefined this.form.hostid = undefined
} }
}, },
onSelectGuestOsCategory (value) {
this.form.guestoscategoryid = value
this.fetchAllTemplates(this.imageSearchFilters)
},
handleSearchFilter (name, options) { handleSearchFilter (name, options) {
this.params[name].options = { ...this.params[name].options, ...options } this.params[name].options = { ...this.params[name].options, ...options }
this.fetchOptions(this.params[name], name) this.fetchOptions(this.params[name], name)
}, },
onTabChange (key, type) { onTabChange (key, type) {
this[type] = key this[type] = key
if (key === 'isoid') {
this.fetchAllIsos()
}
}, },
onUserdataTabChange (key, type) { onUserdataTabChange (key, type) {
this[type] = key this[type] = key

View File

@ -19,28 +19,35 @@
<a-form <a-form
v-ctrl-enter="handleSubmit" v-ctrl-enter="handleSubmit"
@finish="handleSubmit" @finish="handleSubmit"
layout="vertical" layout="vertical">
> <a-alert type="warning" show-icon>
<a-alert <template #message>
type="warning" <span style="margin-bottom: 5px" v-html="$t('message.reinstall.vm')" />
show-icon </template>
>
<template #message><span
style="margin-bottom: 5px"
v-html="$t('message.reinstall.vm')"
/></template>
</a-alert> </a-alert>
<a-form-item> <os-based-image-selection
v-if="isModernImageSelection"
:imageTypeSelectionAllowed="false"
:guestOsCategories="options.guestOsCategories"
:guestOsCategoriesLoading="loading.guestOsCategories"
:selectedGuestOsCategoryId="selectedGuestOsCategoryId"
:imageItems="options.templates"
:imagesLoading="loading.templates"
:filterOption="filterOption"
:preFillContent="dataPreFill"
@change-guest-os-category="onSelectGuestOsCategory"
@handle-image-search-filter="($event) => fetchAllTemplates($event)"
@update-image="updateFieldValue" />
<a-form-item v-else>
<template-iso-selection <template-iso-selection
input-decorator="templateid" input-decorator="templateid"
:items="templates" :items="options.templates"
:selected="tabKey" :selected="tabKey"
:loading="loading.templates" :loading="loading.templates"
:preFillContent="dataPrefill" :preFillContent="dataPreFill"
:key="templateKey" :key="templateKey"
@handle-search-filter="($event) => fetchAllTemplates($event)" @handle-search-filter="($event) => fetchAllTemplates($event)"
@update-template-iso="updateFieldValue" @update-template-iso="updateFieldValue" />
/>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<template #label> <template #label>
@ -56,12 +63,12 @@
</a-form-item> </a-form-item>
<a-form-item v-if="overrideDiskOffering"> <a-form-item v-if="overrideDiskOffering">
<disk-offering-selection <disk-offering-selection
:items="diskOfferings" :items="options.diskOfferings"
:row-count="diskOfferingCount" :row-count="count.diskOfferings"
:zoneId="resource.zoneId" :zoneId="resource.zoneId"
:value="diskOffering ? diskOffering.id : ''" :value="diskOffering ? diskOffering.id : ''"
:loading="loading.diskOfferings" :loading="loading.diskOfferings"
:preFillContent="dataPrefill" :preFillContent="dataPreFill"
:isIsoSelected="false" :isIsoSelected="false"
:isRootDiskOffering="true" :isRootDiskOffering="true"
@on-selected-disk-size="onSelectDiskSize" @on-selected-disk-size="onSelectDiskSize"
@ -127,6 +134,7 @@
import { api } from '@/api' import { api } from '@/api'
import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection' import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection'
import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection' import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection'
import OsBasedImageSelection from '@views/compute/wizard/OsBasedImageSelection'
import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection' import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection'
import TooltipLabel from '@/components/widgets/TooltipLabel' import TooltipLabel from '@/components/widgets/TooltipLabel'
import _ from 'lodash' import _ from 'lodash'
@ -137,6 +145,7 @@ export default {
DiskOfferingSelection, DiskOfferingSelection,
DiskSizeSelection, DiskSizeSelection,
TemplateIsoSelection, TemplateIsoSelection,
OsBasedImageSelection,
TooltipLabel TooltipLabel
}, },
props: { props: {
@ -151,10 +160,19 @@ export default {
overrideDiskOffering: false, overrideDiskOffering: false,
overrideDiskSize: false, overrideDiskSize: false,
expungeDisk: false, expungeDisk: false,
selectedDiskOffering: {}, selectedGuestOsCategoryId: null,
options: {
templates: {},
diskOfferings: [],
guestOsCategories: []
},
loading: { loading: {
templates: false, templates: false,
diskOfferings: false diskOfferings: false,
guestOsCategories: false
},
count: {
diskOfferings: 0
}, },
rootDiskSizeKey: 'details[0].rootdisksize', rootDiskSizeKey: 'details[0].rootdisksize',
minIopsKey: 'details[0].minIops', minIopsKey: 'details[0].minIops',
@ -162,16 +180,11 @@ export default {
rootdisksize: 0, rootdisksize: 0,
minIops: 0, minIops: 0,
maxIops: 0, maxIops: 0,
templateFilter: [
'featured',
'community',
'selfexecutable',
'sharedexecutable'
],
diskOffering: {}, diskOffering: {},
diskOfferingCount: 0, imageSearchFilters: null,
templateid: null,
templateKey: 0, templateKey: 0,
dataPrefill: { dataPreFill: {
templateid: this.resource.templateid, templateid: this.resource.templateid,
diskofferingid: this.resource.diskofferingid diskofferingid: this.resource.diskofferingid
} }
@ -183,10 +196,52 @@ export default {
created () { created () {
this.fetchData() this.fetchData()
}, },
computed: {
isNormalAndDomainUser () {
return ['DomainAdmin', 'User'].includes(this.$store.getters.userInfo.roletype)
},
isModernImageSelection () {
return this.$config.imageSelectionInterface === undefined || this.$config.imageSelectionInterface === 'modern'
},
imageSelection () {
return this.isModernImageSelection ? 'modern' : 'legacy'
},
showUserCategoryForModernImageSelection () {
return this.$config.showUserCategoryForModernImageSelection === undefined || this.$config.showUserCategoryForModernImageSelection
},
showAllCategoryForModernImageSelection () {
return this.$config.showAllCategoryForModernImageSelection
}
},
methods: { methods: {
fetchData () { fetchData () {
this.fetchDiskOfferings({}) this.fetchDiskOfferings({})
if (this.isModernImageSelection) {
this.fetchGuestOsCategories()
} else {
this.fetchAllTemplates() this.fetchAllTemplates()
}
},
getImageFilters (params, forReset) {
if (this.isModernImageSelection) {
if (this.selectedGuestOsCategoryId === '0') {
return ['self']
}
if (this.isModernImageSelection && params && !forReset) {
if (params.featured) {
return ['featured']
} else if (params.public) {
return ['community']
}
}
return this.isNormalAndDomainUser ? ['executable'] : ['all']
}
return [
'featured',
'community',
'selfexecutable',
'sharedexecutable'
]
}, },
closeAction () { closeAction () {
this.$emit('close-action') this.$emit('close-action')
@ -244,19 +299,56 @@ export default {
this.closeAction() this.closeAction()
}) })
}, },
fetchGuestOsCategories () {
this.loading.guestOsCategories = true
const params = {
zoneid: this.resource.zoneid,
arch: this.resource.arch,
isiso: false,
featured: true,
showicon: true
}
api('listOsCategories', params).then((response) => {
this.options.guestOsCategories = response?.listoscategoriesresponse?.oscategory || []
if (this.showUserCategoryForModernImageSelection) {
const userCategory = {
id: '0',
name: this.$t('label.user')
}
if (this.$store.getters.avatar) {
userCategory.icon = {
base64image: this.$store.getters.avatar
}
}
this.options.guestOsCategories.push(userCategory)
}
if (this.showAllCategoryForModernImageSelection) {
this.options.guestOsCategories.push({
id: '-1',
name: this.$t('label.all')
})
}
this.selectedGuestOsCategoryId = this.options.guestOsCategories[0].id
}).finally(() => {
this.loading.guestOsCategories = false
this.fetchAllTemplates()
})
},
fetchAllTemplates (params) { fetchAllTemplates (params) {
const promises = [] const promises = []
const templates = {} const templates = {}
this.loading.templates = true this.loading.templates = true
this.templateFilter.forEach((filter) => { this.imageSearchFilters = params
const templateFilters = this.getImageFilters(params)
templateFilters.forEach((filter) => {
templates[filter] = { count: 0, template: [] } templates[filter] = { count: 0, template: [] }
promises.push(this.fetchTemplates(filter, params)) promises.push(this.fetchTemplates(filter, params))
}) })
this.templates = templates this.options.templates = templates
Promise.all(promises).then((response) => { Promise.all(promises).then((response) => {
response.forEach((resItem, idx) => { response.forEach((resItem, idx) => {
templates[this.templateFilter[idx]] = _.isEmpty(resItem.listtemplatesresponse) ? { count: 0, template: [] } : resItem.listtemplatesresponse templates[templateFilters[idx]] = _.isEmpty(resItem.listtemplatesresponse) ? { count: 0, template: [] } : resItem.listtemplatesresponse
this.templates = { ...templates } this.options.templates = { ...templates }
}) })
}).catch((reason) => { }).catch((reason) => {
console.log(reason) console.log(reason)
@ -266,12 +358,18 @@ export default {
}, },
fetchTemplates (templateFilter, params) { fetchTemplates (templateFilter, params) {
const args = Object.assign({}, params) const args = Object.assign({}, params)
if (args.keyword || args.category !== templateFilter) { if (this.isModernImageSelection && this.selectedGuestOsCategoryId && !['-1', '0'].includes(this.selectedGuestOsCategoryId)) {
args.oscategoryid = this.selectedGuestOsCategoryId
}
if (args.keyword || (args.category && args.category !== templateFilter)) {
args.page = 1 args.page = 1
args.pageSize = args.pageSize || 10 args.pageSize = args.pageSize || 10
} }
args.zoneid = _.get(this.zone, 'id') args.zoneid = this.resource.zoneid
args.templatefilter = templateFilter args.templatefilter = templateFilter
if (this.resource.arch) {
args.arch = this.resource.arch
}
args.details = 'all' args.details = 'all'
args.showicon = 'true' args.showicon = 'true'
@ -285,17 +383,24 @@ export default {
}, },
fetchDiskOfferings (params) { fetchDiskOfferings (params) {
api('listDiskOfferings', { zoneid: this.resource.zoneid, listall: true, ...params }).then((response) => { api('listDiskOfferings', { zoneid: this.resource.zoneid, listall: true, ...params }).then((response) => {
this.diskOfferings = response?.listdiskofferingsresponse?.diskoffering || [] this.options.diskOfferings = response?.listdiskofferingsresponse?.diskoffering || []
this.diskOfferingCount = response?.listdiskofferingsresponse?.count || 0 this.count.diskOfferings = response?.listdiskofferingsresponse?.count || 0
}) })
}, },
onSelectGuestOsCategory (value) {
this.selectedGuestOsCategoryId = value
this.fetchAllTemplates(this.imageSearchFilters)
},
onSelectDiskSize (rowSelected) { onSelectDiskSize (rowSelected) {
this.diskOffering = rowSelected this.diskOffering = rowSelected
this.dataPrefill.diskofferingid = rowSelected.id this.dataPreFill.diskofferingid = rowSelected.id
}, },
updateFieldValue (input, value) { updateFieldValue (input, value) {
this[input] = value this[input] = value
this.dataPrefill[input] = value this.dataPreFill[input] = value
},
filterOption (input, option) {
return option.label.toUpperCase().indexOf(input.toUpperCase()) >= 0
} }
} }
} }

View File

@ -26,13 +26,13 @@
v-ctrl-enter="handleSubmit" v-ctrl-enter="handleSubmit"
@finish="handleSubmit"> @finish="handleSubmit">
<div v-if="template && template.userdataid"> <div v-if="template && template.userdataid">
<a-text type="primary"> <a-typography-text>
The template "{{ $t(this.template.name) }}" is linked to Userdata "{{ $t(this.template.userdataname) }}" with override policy "{{ $t(this.template.userdatapolicy) }}" The template "{{ $t(this.template.name) }}" is linked to Userdata "{{ $t(this.template.userdataname) }}" with override policy "{{ $t(this.template.userdatapolicy) }}"
</a-text><br/><br/> </a-typography-text><br/><br/>
<div v-if="templateUserDataParams.length > 0 && !doUserdataOverride"> <div v-if="templateUserDataParams.length > 0 && !doUserdataOverride">
<a-text type="primary" v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0"> <a-typography-text v-if="this.template && this.template.userdataid && templateUserDataParams.length > 0">
Enter the values for the variables in userdata Enter the values for the variables in userdata
</a-text> </a-typography-text>
<a-input-group> <a-input-group>
<a-table <a-table
size="small" size="small"

View File

@ -179,6 +179,7 @@ export default {
} }
} }
}, },
emits: ['update-network-config', 'select-default-network-item', 'handler-error'],
methods: { methods: {
initForm () { initForm () {
this.formRef = ref() this.formRef = ref()

View File

@ -0,0 +1,208 @@
// 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>
<a-list
class="form-item-scroll"
itemLayout="vertical"
size="small"
:dataSource="imagesList"
:pagination="false">
<template #renderItem="{ item, index }">
<a-list-item :key="item.id" @click="onClickRow(item)">
<a-radio-group
class="radio-group"
:key="index"
v-model:value="value"
@change="($event) => updateSelectionTemplateIso($event.target.value)">
<a-radio
class="radio-group__radio"
:value="item.id">
<resource-icon
v-if="item.icon && item.icon.base64image"
class="radio-group__os-logo"
:image="item.icon.base64image"
size="2x" />
<os-logo
v-else
class="radio-group__os-logo"
size="2x"
:osId="item.ostypeid"
:os-name="item.osName" />
&nbsp;
{{ item.displaytext }}
<span v-if="item?.projectid">
| <project-outlined /> {{ item.project }}
</span>
<a-tooltip :title="$t('label.passwordenabled')" v-if="item.passwordenabled">
<lock-outlined style="margin-left: 8px;"/>
</a-tooltip>
<a-tooltip :title="$t('label.userdata')" v-if="item.userdataid">
<solution-outlined style="margin-left: 8px;"/>
</a-tooltip>
<a-tooltip :title="$t('label.isdynamicallyscalable')" v-if="item.isdynamicallyscalable">
<arrows-alt-outlined style="margin-left: 8px;"/>
</a-tooltip>
<a-tooltip :title="$t('label.isextractable')" v-if="item.isextractable">
<file-zip-outlined style="margin-left: 8px;"/>
</a-tooltip>
<a-tag v-if="item.isfeatured" style="margin-left: 8px;">
{{ $t('label.isfeatured') }}
</a-tag>
<a-tag v-if="item.ispublic" style="margin-left: 8px;">
{{ $t('label.ispublic') }}
</a-tag>
<a-tag v-if="item.directdownload" style="margin-left: 8px;">
{{ $t('label.directdownload') }}
</a-tag>
<a-tag v-if="item.requireshvm" style="margin-left: 8px;">
{{ $t('label.requireshvm') }}
</a-tag>
</a-radio>
</a-radio-group>
</a-list-item>
</template>
</a-list>
<div style="display: block; text-align: right;">
<a-pagination
size="small"
:current="options.page"
:pageSize="options.pageSize"
:total="itemCount"
:showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`"
:pageSizeOptions="['10', '20', '40', '80', '100', '200']"
@change="onChangePage"
@showSizeChange="onChangePageSize"
showSizeChanger>
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
</div>
</template>
<script>
import OsLogo from '@/components/widgets/OsLogo'
import ResourceIcon from '@/components/view/ResourceIcon'
export default {
name: 'OsBasedImageRadioGroup',
components: {
OsLogo,
ResourceIcon
},
props: {
imagesList: {
type: Array,
default: () => []
},
inputDecorator: {
type: String,
default: ''
},
selected: {
type: String,
default: ''
},
itemCount: {
type: Number,
default: 0
},
preFillContent: {
type: Object,
default: () => {}
}
},
data () {
return {
value: '',
image: '',
options: {
page: 1,
pageSize: 10
}
}
},
mounted () {
this.onSelectTemplateIso()
},
watch: {
selected (newVal, oldVal) {
if (newVal === oldVal) return
this.onSelectTemplateIso()
}
},
emits: ['emit-update-image', 'handle-search-filter'],
methods: {
onSelectTemplateIso () {
if (this.inputDecorator === 'templateid') {
this.value = this.preFillContent?.templateid ? this.preFillContent.templateid : this.selected
} else {
this.value = this.preFillContent?.isoid ? this.preFillContent.isoid : this.selected
}
this.$emit('emit-update-image', this.inputDecorator, this.value)
},
updateSelectionTemplateIso (id) {
this.$emit('emit-update-image', this.inputDecorator, id)
},
onChangePage (page, pageSize) {
this.options.page = page
this.options.pageSize = pageSize
this.$emit('handle-search-filter', this.options)
},
onChangePageSize (page, pageSize) {
this.options.page = page
this.options.pageSize = pageSize
this.$emit('handle-search-filter', this.options)
},
onClickRow (os) {
this.value = os.id
this.$emit('emit-update-image', this.inputDecorator, this.value)
}
}
}
</script>
<style lang="less" scoped>
.radio-group {
margin: 0.5rem 0;
:deep(.ant-radio) {
margin-right: 0px;
}
&__os-logo {
margin-top: -4px;
}
}
:deep(.ant-spin-container) {
max-height: 200px;
overflow-y: auto;
}
.pagination {
margin-top: 20px;
float: right;
}
:deep(.ant-list-split) .ant-list-item {
cursor: pointer;
}
</style>

View File

@ -0,0 +1,373 @@
// 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-form-item v-if="imageTypeSelectionAllowed" :label="$t('label.type')" name="imagetype" ref="imagetype">
<a-radio-group
v-model:value="localSelectedImageType"
button-style="solid"
:disabled="imagePreSelected"
@change="emitChangeImageType()">
<a-radio-button value="templateid">{{ $t('label.template') }}</a-radio-button>
<a-radio-button value="isoid">{{ $t('label.iso') }}</a-radio-button>
</a-radio-group>
<div style="margin-top: 5px; margin-bottom: 5px;">
{{ $t('message.' + localSelectedImageType.replace('id', '') + '.desc') }}
</div>
</a-form-item>
<a-form-item
:label="$t('label.os')"
name="guestoscategoryid"
ref="guestoscategoryid"
v-if="!guestOsCategoriesSelectionDisallowed">
<block-radio-group-select
:maxBlocks="16"
:items="guestOsCategories"
:selectedValue="localSelectedGuestOsCategoryId"
:horizontalGutter="6"
:verticalGutter="6"
blockSize="square"
@change="handleGuestOsCategoryChange">
<template #radio-option="{ item }">
<div class="radio-option">
<div class="radio-opion__icon">
<resource-icon v-if="item.icon && item.icon.base64image" :image="item.icon.base64image" size="os" style="margin-bottom: 2px; margin-left: 1px" />
<font-awesome-icon v-else-if="['-1', '0'].includes(item.id)" :icon="['fas', item.id === '0' ? 'user' : 'images']" size="2x" :style="categoryFontAwesomeIconStyle" />
<os-logo v-else size="2x" :os-name="item.name" />
</div>
<a-tooltip placement="top" :title="item.name">
<div class="ellipsis">{{ item.name }}</div>
</a-tooltip>
</div>
</template>
<template #select-option="{ item }">
<span>
<resource-icon v-if="item.icon && item.icon.base64image" :image="item.icon.base64image" size="2x" style="margin-right: 5px"/>
<font-awesome-icon
v-else-if="item.id === '0'"
:icon="['fas', 'user']"
size="2x"
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#666' }]"
/>
<os-logo v-else :os-name="item.name" style="margin-right: 5px" />
{{ item.name }}
</span>
</template>
</block-radio-group-select>
</a-form-item>
<a-card>
<os-based-image-selection-search-view
v-if="!imagePreSelected"
class="search-input"
:filtersDisabled="searchFiltersDisabled"
@search="handleImageSearch">
</os-based-image-selection-search-view>
<a-spin :spinning="imagesLoading">
<os-based-image-radio-group
:imagesList="imagesList"
:itemCount="imagesCount"
:input-decorator="localSelectedImageType"
:selected="selectedImageId"
:preFillContent="preFillContent"
@emit-update-image="updateImage"
@handle-search-filter="($event) => eventPagination($event)"
/>
</a-spin>
<div v-if="diskSizeSelectionAllowed">
<div>
{{ $t('label.override.rootdisk.size') }}
<a-switch
v-model:checked="localRootDiskOverrideChecked"
:disabled="rootDiskOverrideDisabled"
@change="handleRootDiskOverrideCheckedChange"
style="margin-left: 10px;"/>
<div v-if="diskSizeSelectionDeployAsIsMessageVisible"> {{ $t('message.deployasis') }} </div>
</div>
<disk-size-selection
v-if="showRootDiskSizeChanger"
:input-decorator="diskSizeSelectionInputDecorator"
:preFillContent="preFillContent"
:isCustomized="true"
:minDiskSize="preFillContent.minrootdisksize"
@update-disk-size="emitUpdateDiskSize"
style="margin-top: 10px;"/>
</div>
<a-form-item :label="$t('label.hypervisor')" v-if="localSelectedImageType === 'isoid'">
<a-select
v-model:value="localSelectedIsoHypervisor"
:preFillContent="preFillContent"
:options="isoHypervisorItems"
@change="handleIsoHypervisorChange()"
showSearch
optionFilterProp="label"
:filterOption="filterOption" />
</a-form-item>
</a-card>
</div>
</template>
<script>
import BlockRadioGroupSelect from '@/components/widgets/BlockRadioGroupSelect.vue'
import ResourceIcon from '@/components/view/ResourceIcon'
import OsLogo from '@/components/widgets/OsLogo'
import OsBasedImageSelectionSearchView from '@views/compute/wizard/OsBasedImageSelectionSearchView'
import OsBasedImageRadioGroup from '@views/compute/wizard/OsBasedImageRadioGroup'
import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection'
export default {
name: 'OsBasedImageSelection',
components: {
BlockRadioGroupSelect,
ResourceIcon,
OsLogo,
OsBasedImageSelectionSearchView,
OsBasedImageRadioGroup,
DiskSizeSelection
},
props: {
imageTypeSelectionAllowed: {
type: Boolean,
default: true
},
selectedImageType: {
type: String,
default: 'templateid'
},
imagePreSelected: {
type: Boolean,
default: false
},
guestOsCategoriesSelectionDisallowed: {
type: Boolean,
default: false
},
guestOsCategories: {
type: Array,
default: () => []
},
guestOsCategoriesLoading: {
type: Boolean,
default: false
},
selectedGuestOsCategoryId: {
type: String,
default: undefined
},
imageItems: {
type: Object,
default: () => {}
},
imagesLoading: {
type: Boolean,
default: false
},
diskSizeSelectionAllowed: {
type: Boolean,
default: true
},
diskSizeSelectionDeployAsIsMessageVisible: {
type: Boolean,
default: false
},
diskSizeSelectionInputDecorator: {
type: String,
default: 'rootdisksize'
},
rootDiskOverrideDisabled: {
type: Boolean,
default: false
},
rootDiskOverrideChecked: {
type: Boolean,
default: false
},
isoHypervisorItems: {
type: Array,
default: () => []
},
selectedIsoHypervisor: {
type: String,
default: undefined
},
filterOption: {
type: Function,
required: true
},
preFillContent: {
type: Object,
default: () => {}
}
},
data () {
return {
filterType: 'executable',
selectedImageId: '',
imageSearchFilters: {},
showRootDiskSizeChanger: false,
// Local data properties to mirror props
localSelectedImageType: this.selectedImageType,
localSelectedGuestOsCategoryId: this.selectedGuestOsCategoryId,
localRootDiskOverrideChecked: this.rootDiskOverrideChecked,
localSelectedIsoHypervisor: this.selectedIsoHypervisor
}
},
mounted () {
this.filterType = this.defaultImageFilter
},
watch: {
selectedImageType (newValue) {
this.localSelectedImageType = newValue
},
guestOsCategories (newValue) {
this.imageSearchFilters = {}
},
selectedGuestOsCategoryId (newValue) {
this.localSelectedGuestOsCategoryId = newValue
this.updateImageFilterType()
},
rootDiskOverrideChecked (newValue) {
this.localRootDiskOverrideChecked = newValue
},
selectedIsoHypervisor (newValue) {
this.localSelectedIsoHypervisor = newValue
}
},
computed: {
defaultImageFilter () {
return ['DomainAdmin', 'User'].includes(this.$store.getters.userInfo.roletype) ? 'executable' : 'all'
},
imagesList () {
if (!this.localSelectedImageType || !this.imageItems || !this.imageItems[this.filterType]) {
return []
}
const imageTypeKey = this.localSelectedImageType.slice(0, -2)
return this.imageItems[this.filterType][imageTypeKey] || []
},
imagesCount () {
return this.imageItems[this.filterType] ? this.imageItems[this.filterType].count || 0 : 0
},
selectedCategory () {
if (this.localSelectedGuestOsCategoryId && this.guestOsCategories) {
return this.guestOsCategories.find(option => option.id === this.localSelectedGuestOsCategoryId)
}
return null
},
categoryFontAwesomeIconStyle () {
return [this.$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#666' }]
},
searchFiltersDisabled () {
return this.selectedCategory?.disableimagefilters
}
},
emits: ['change-image-type', 'change-guest-os-category', 'update-image', 'handle-image-search-filter', 'change-root-disk-override-checked', 'update-disk-size', 'change-iso-hypervisor'],
methods: {
emitChangeImageType () {
this.$emit('change-image-type', this.localSelectedImageType)
},
handleGuestOsCategoryChange (value) {
this.localSelectedGuestOsCategoryId = value
this.$emit('change-guest-os-category', this.localSelectedGuestOsCategoryId)
},
updateImage (decorator, id) {
this.selectedImageId = id
this.$emit('update-image', decorator, id)
},
handleImageSearch (searchFilters) {
this.imageSearchFilters = {
page: 1,
pageSize: 10
}
Object.assign(this.imageSearchFilters, searchFilters)
this.updateImageFilterType()
this.emitSearchFilter()
},
updateImageFilterType () {
this.filterType = this.defaultImageFilter
if (this.localSelectedGuestOsCategoryId === '0') {
this.filterType = 'self'
} else {
if (this.imageSearchFilters?.featured) {
this.filterType = 'featured'
} else if (this.imageSearchFilters?.public) {
this.filterType = 'community'
}
}
},
eventPagination (pagination) {
Object.assign(this.imageSearchFilters, pagination)
this.emitSearchFilter()
},
emitSearchFilter () {
this.$emit('handle-image-search-filter', this.imageSearchFilters)
},
changeFilterType (value) {
this.filterType = value
},
handleRootDiskOverrideCheckedChange (value) {
this.showRootDiskSizeChanger = value
this.$emit('change-root-disk-override-checked', value)
},
emitUpdateDiskSize (decorator, value) {
this.$emit('update-disk-size', decorator, value)
},
handleIsoHypervisorChange () {
this.$emit('change-iso-hypervisor', this.localIsoHypervisor)
}
}
}
</script>
<style lang="less" scoped>
.search-input {
z-index: 8;
@media (max-width: 600px) {
position: relative;
width: 100%;
top: 0;
right: 0;
}
}
:deep(.ant-tabs-nav-scroll) {
min-height: 45px;
}
.radio-option {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding-top: 8px;
}
.ellipsis {
max-width: 80px;
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.radio-opion__icon {
width: 30px;
height: 30px;
object-fit: contain;
}
</style>

View File

@ -0,0 +1,119 @@
// 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>
<span class="filter-group">
<a-row type="flex">
<a-col flex="200px" v-if="!filtersDisabled">
<a-select
mode="multiple"
class="filter-select"
:placeholder="$t('label.filterby')"
v-model:value="filterValues"
@change="handleFilterChange"
showSearch
allowClear
:showArrow="true"
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<template #suffixIcon><filter-outlined class="ant-select-suffix" /></template>
<a-select-option
v-for="filter in filters"
:key="filter"
:label="$t('label.' + filter)">
{{ $t('label.' + filter) }}
</a-select-option>
</a-select>
</a-col>
<a-col flex="auto">
<a-input-search
class="filter-search"
:placeholder="$t('label.search')"
v-model:value="searchedText"
allowClear
@search="handleTextSearch" />
</a-col>
</a-row>
</span>
</template>
<script>
export default {
name: 'OsBasedImageSelectionSearchView',
props: {
filtersDisabled: {
type: Boolean,
default: false
}
},
data () {
return {
filters: [
'public',
'featured'
],
filterValues: undefined,
searchedText: null,
paramsFilter: {}
}
},
created () {
},
methods: {
handleTextSearch (value) {
this.searchedText = value
if (value) {
this.paramsFilter.keyword = value
} else {
delete this.paramsFilter.keyword
}
const params = this.paramsFilter
if (this.filtersDisabled) {
delete this.paramsFilter.public
delete this.paramsFilter.featured
}
this.$emit('search', params)
},
handleFilterChange () {
this.paramsFilter = {}
if (Array.isArray(this.filterValues)) {
this.filterValues.forEach(e => {
this.paramsFilter[e] = true
})
}
if (this.searchedText) {
this.paramsFilter.keyword = this.searchedText
}
this.$emit('search', this.paramsFilter)
}
}
}
</script>
<style lang="less" scoped>
.filter-group .ant-select,
.filter-group .ant-input-search {
margin-right: 12px;
}
.filter-select {
width: 200px;
}
</style>

View File

@ -0,0 +1,51 @@
// 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>
<block-radio-group-select>
<template #radio-option="{ item }">
<span>
<resource-icon
v-if="item && item.icon && item.icon.base64image"
:image="item.icon.base64image"
size="2x" />
<global-outlined size="2x" v-else />
{{ item.name }}
</span>
</template>
<template #select-option="{ item }">
<span>
<resource-icon v-if="item.icon && zone1.icon.base64image" :image="item.icon.base64image" size="2x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px" />
{{ item.name }}
</span>
</template>
</block-radio-group-select>
</template>
<script>
import BlockRadioGroupSelect from '@/components/widgets/BlockRadioGroupSelect.vue'
import ResourceIcon from '@/components/view/ResourceIcon'
export default {
name: 'ZoneBlockRadioGroupSelect',
components: {
BlockRadioGroupSelect,
ResourceIcon
}
}
</script>

View File

@ -38,8 +38,8 @@
:rowExpandable="(record) => record.downloaddetails.length > 0"> :rowExpandable="(record) => record.downloaddetails.length > 0">
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'zonename'"> <template v-if="column.key === 'zonename'">
<span v-if="fetchZoneIcon(record.zoneid)"> <span v-if="record.zoneicon && record.zoneicon.base64image">
<resource-icon :image="zoneIcon" size="1x" style="margin-right: 5px"/> <resource-icon :image="record.zoneicon.base64image" size="2x" style="margin-right: 5px"/>
</span> </span>
<global-outlined v-else style="margin-right: 5px" /> <global-outlined v-else style="margin-right: 5px" />
<span> {{ record.zonename }} </span> <span> {{ record.zonename }} </span>
@ -52,6 +52,14 @@
<span v-if="record.created">{{ $toLocaleDate(record.created) }}</span> <span v-if="record.created">{{ $toLocaleDate(record.created) }}</span>
</template> </template>
<template v-if="column.key === 'actions'"> <template v-if="column.key === 'actions'">
<span style="margin-right: 5px" v-if="'deployVirtualMachine' in $store.getters.apis">
<tooltip-button
:disabled="!record.isready"
:title="$t('label.vm.add')"
icon="rocket-outlined"
@onClick="onAddInstance(record)"/>
</span>
<span v-if="isActionsOnIsoPermitted">
<span style="margin-right: 5px"> <span style="margin-right: 5px">
<tooltip-button <tooltip-button
:tooltip="$t('label.action.copy.iso')" :tooltip="$t('label.action.copy.iso')"
@ -77,6 +85,7 @@
icon="delete-outlined" /> icon="delete-outlined" />
</a-popconfirm> </a-popconfirm>
</span> </span>
</span>
</template> </template>
</template> </template>
<template #expandedRowRender="{ record }"> <template #expandedRowRender="{ record }">
@ -162,7 +171,7 @@
}" }"
:loading="zoneLoading" :loading="zoneLoading"
v-focus="true"> v-focus="true">
<a-select-option v-for="zone in zones" :key="zone.id" :label="zone.name"> <a-select-option v-for="zone in copyZones" :key="zone.id" :label="zone.name">
<div> <div>
<span v-if="zone.icon && zone.icon.base64image"> <span v-if="zone.icon && zone.icon.base64image">
<resource-icon :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/> <resource-icon :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
@ -237,6 +246,7 @@ export default {
showCopyActionForm: false, showCopyActionForm: false,
currentRecord: {}, currentRecord: {},
zones: [], zones: [],
copyZones: [],
zoneLoading: false, zoneLoading: false,
copyLoading: false, copyLoading: false,
deleteLoading: false, deleteLoading: false,
@ -278,6 +288,13 @@ export default {
key: 'isready', key: 'isready',
title: this.$t('label.isready'), title: this.$t('label.isready'),
dataIndex: 'isready' dataIndex: 'isready'
},
{
key: 'actions',
title: '',
dataIndex: 'actions',
fixed: 'right',
width: 130
} }
] ]
this.storagePoolInnerColumns = [ this.storagePoolInnerColumns = [
@ -308,15 +325,6 @@ export default {
dataIndex: 'downloadState' dataIndex: 'downloadState'
} }
] ]
if (this.isActionPermitted()) {
this.columns.push({
key: 'actions',
title: '',
dataIndex: 'actions',
fixed: 'right',
width: 100
})
}
const userInfo = this.$store.getters.userInfo const userInfo = this.$store.getters.userInfo
if (!['Admin'].includes(userInfo.roletype) && if (!['Admin'].includes(userInfo.roletype) &&
@ -332,6 +340,16 @@ export default {
} }
} }
}, },
computed: {
isActionsOnIsoPermitted () {
return (['Admin'].includes(this.$store.getters.userInfo.roletype) || // If admin or owner or belongs to current project
(this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) ||
(this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.projectid && this.$store.getters.project && this.$store.getters.project.id && this.resource.projectid === this.$store.getters.project.id)) &&
(this.resource.isready || !this.resource.status || this.resource.status.indexOf('Downloaded') === -1) && // Iso is ready or downloaded
this.resource.account !== 'system'
}
},
emits: ['update-zones'],
methods: { methods: {
initForm () { initForm () {
this.formRef = ref() this.formRef = ref()
@ -358,16 +376,10 @@ export default {
this.$notifyError(error) this.$notifyError(error)
}).finally(() => { }).finally(() => {
this.fetchLoading = false this.fetchLoading = false
this.updateImageZones()
}) })
this.fetchZoneData() this.fetchZoneData()
}, this.fetchOsCategoryId()
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) { handleChangePage (page, pageSize) {
this.page = page this.page = page
@ -379,13 +391,6 @@ export default {
this.pageSize = pageSize this.pageSize = pageSize
this.fetchData() this.fetchData()
}, },
isActionPermitted () {
return (['Admin'].includes(this.$store.getters.userInfo.roletype) || // If admin or owner or belongs to current project
(this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) ||
(this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.projectid && this.$store.getters.project && this.$store.getters.project.id && this.resource.projectid === this.$store.getters.project.id)) &&
(this.resource.isready || !this.resource.status || this.resource.status.indexOf('Downloaded') === -1) && // Iso is ready or downloaded
this.resource.account !== 'system'
},
setSelection (selection) { setSelection (selection) {
this.selectedRowKeys = selection this.selectedRowKeys = selection
this.$emit('selection-change', this.selectedRowKeys) this.$emit('selection-change', this.selectedRowKeys)
@ -501,9 +506,41 @@ export default {
this.zoneLoading = true this.zoneLoading = true
api('listZones', { showicon: true }).then(json => { api('listZones', { showicon: true }).then(json => {
const zones = json.listzonesresponse.zone || [] const zones = json.listzonesresponse.zone || []
this.zones = [...zones.filter((zone) => this.currentRecord.zoneid !== zone.id)] this.zones = zones
this.copyZones = [...zones.filter((zone) => this.currentRecord.zoneid !== zone.id)]
}).finally(() => { }).finally(() => {
this.zoneLoading = false this.zoneLoading = false
this.updateImageZones()
})
},
updateImageZones () {
if (!Array.isArray(this.dataSource) || !Array.isArray(this.zones) ||
this.dataSource.length === 0 || this.zones.length === 0) {
return
}
const imageZones = []
this.dataSource.forEach(item => {
const zone = this.zones.find(zone => item.zoneid === zone.id)
if (zone && zone.icon) {
item.zoneicon = zone.icon
}
imageZones.push(zone)
})
if (imageZones.length !== 0) {
this.$emit('update-zones', imageZones)
}
},
fetchOsCategoryId () {
const needed = this.$route.meta.name === 'iso' &&
'listOsTypes' in this.$store.getters.apis &&
this.resource && this.resource.ostypeid &&
(this.$config.imageSelectionInterface === undefined ||
this.$config.imageSelectionInterface === 'modern')
if (!needed) {
return
}
api('listOsTypes', { id: this.resource.ostypeid }).then(json => {
this.osCategoryId = json?.listostypesresponse?.ostype?.[0]?.oscategoryid || null
}) })
}, },
showCopyIso (record) { showCopyIso (record) {
@ -558,6 +595,19 @@ export default {
}, },
closeModal () { closeModal () {
this.showConfirmationAction = false this.showConfirmationAction = false
},
onAddInstance (record) {
const query = { isoid: this.resource.id, zoneid: record.zoneid }
if (this.resource.arch) {
query.arch = this.resource.arch
}
if (this.osCategoryId) {
query.oscategoryid = this.osCategoryId
}
this.$router.push({
path: '/action/deployVirtualMachine',
query: query
})
} }
} }
} }

View File

@ -582,7 +582,7 @@ export default {
this.fetchCustomHypervisorName() this.fetchCustomHypervisorName()
this.fetchZone() this.fetchZone()
this.fetchOsTypes() this.fetchOsTypes()
this.fetchTemplateTypes() this.templateTypes.opts = this.$fetchTemplateTypes()
this.architectureTypes.opts = this.$fetchCpuArchitectureTypes() this.architectureTypes.opts = this.$fetchCpuArchitectureTypes()
this.fetchUserData() this.fetchUserData()
this.fetchUserdataPolicy() this.fetchUserdataPolicy()
@ -726,33 +726,6 @@ export default {
this.osTypes.loading = false this.osTypes.loading = false
}) })
}, },
fetchTemplateTypes () {
this.templateTypes.opts = []
const templatetypes = []
templatetypes.push({
id: 'USER',
description: 'USER'
})
templatetypes.push({
id: 'VNF',
description: 'VNF'
})
if (this.isAdminRole) {
templatetypes.push({
id: 'SYSTEM',
description: 'SYSTEM'
})
templatetypes.push({
id: 'BUILTIN',
description: 'BUILTIN'
})
templatetypes.push({
id: 'ROUTING',
description: 'ROUTING'
})
}
this.templateTypes.opts = templatetypes
},
fetchUserData () { fetchUserData () {
const params = {} const params = {}
params.listAll = true params.listAll = true

View File

@ -38,8 +38,8 @@
:rowExpandable="(record) => record.downloaddetails.length > 0"> :rowExpandable="(record) => record.downloaddetails.length > 0">
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'zonename'"> <template v-if="column.key === 'zonename'">
<span v-if="fetchZoneIcon(record.zoneid)"> <span v-if="record.zoneicon && record.zoneicon.base64image">
<resource-icon :image="zoneIcon" size="2x" style="margin-right: 5px"/> <resource-icon :image="record.zoneicon.base64image" size="2x" style="margin-right: 5px"/>
</span> </span>
<global-outlined v-else style="margin-right: 5px" /> <global-outlined v-else style="margin-right: 5px" />
<span> {{ record.zonename }} </span> <span> {{ record.zonename }} </span>
@ -52,21 +52,32 @@
<span v-if="record.created">{{ $toLocaleDate(record.created) }}</span> <span v-if="record.created">{{ $toLocaleDate(record.created) }}</span>
</template> </template>
<template v-if="column.key === 'actions'"> <template v-if="column.key === 'actions'">
<span style="margin-right: 5px" v-if="'deployVirtualMachine' in $store.getters.apis">
<tooltip-button
:disabled="!record.isready"
:title="$t('label.vm.add')"
icon="rocket-outlined"
@onClick="onAddInstance(record)"/>
</span>
<span v-if="isActionsOnTemplatePermitted">
<span style="margin-right: 5px">
<tooltip-button <tooltip-button
style="margin-right: 5px"
:disabled="!('copyTemplate' in $store.getters.apis && record.isready)" :disabled="!('copyTemplate' in $store.getters.apis && record.isready)"
:title="$t('label.action.copy.template')" :title="$t('label.action.copy.template')"
icon="copy-outlined" icon="copy-outlined"
:loading="copyLoading" :loading="copyLoading"
@onClick="showCopyTemplate(record)" /> @onClick="showCopyTemplate(record)" />
</span>
<span style="margin-right: 5px">
<tooltip-button <tooltip-button
style="margin-right: 5px"
:disabled="!('deleteTemplate' in $store.getters.apis)" :disabled="!('deleteTemplate' in $store.getters.apis)"
:title="$t('label.action.delete.template')" :title="$t('label.action.delete.template')"
type="primary" type="primary"
:danger="true" :danger="true"
icon="delete-outlined" icon="delete-outlined"
@onClick="onShowDeleteModal(record)"/> @onClick="onShowDeleteModal(record)"/>
</span>
</span>
</template> </template>
</template> </template>
<template #expandedRowRender="{ record }"> <template #expandedRowRender="{ record }">
@ -168,7 +179,7 @@
}" }"
:loading="zoneLoading" :loading="zoneLoading"
v-focus="true"> v-focus="true">
<a-select-option v-for="zone in zones" :key="zone.id" :label="zone.name"> <a-select-option v-for="zone in copyZones" :key="zone.id" :label="zone.name">
<div> <div>
<span v-if="zone.icon && zone.icon.base64image"> <span v-if="zone.icon && zone.icon.base64image">
<resource-icon :image="zone.icon.base64image" size="2x" style="margin-right: 5px"/> <resource-icon :image="zone.icon.base64image" size="2x" style="margin-right: 5px"/>
@ -282,6 +293,7 @@ export default {
showCopyActionForm: false, showCopyActionForm: false,
currentRecord: {}, currentRecord: {},
zones: [], zones: [],
copyZones: [],
zoneLoading: false, zoneLoading: false,
copyLoading: false, copyLoading: false,
deleteLoading: false, deleteLoading: false,
@ -298,7 +310,8 @@ export default {
confirmMessage: this.$t('label.confirm.delete.templates') confirmMessage: this.$t('label.confirm.delete.templates')
}, },
modalWidth: '30vw', modalWidth: '30vw',
showTable: false showTable: false,
osCategoryId: null
} }
}, },
beforeCreate () { beforeCreate () {
@ -324,6 +337,12 @@ export default {
key: 'isready', key: 'isready',
title: this.$t('label.isready'), title: this.$t('label.isready'),
dataIndex: 'isready' dataIndex: 'isready'
},
{
key: 'actions',
title: '',
dataIndex: 'actions',
width: 130
} }
] ]
this.imageStoreInnerColumns = [ this.imageStoreInnerColumns = [
@ -354,14 +373,6 @@ export default {
dataIndex: 'downloadState' dataIndex: 'downloadState'
} }
] ]
if (this.isActionPermitted()) {
this.columns.push({
key: 'actions',
title: '',
dataIndex: 'actions',
width: 100
})
}
const userInfo = this.$store.getters.userInfo const userInfo = this.$store.getters.userInfo
if (!['Admin'].includes(userInfo.roletype) && if (!['Admin'].includes(userInfo.roletype) &&
@ -378,6 +389,16 @@ export default {
} }
} }
}, },
computed: {
isActionsOnTemplatePermitted () {
return (['Admin'].includes(this.$store.getters.userInfo.roletype) || // If admin or owner or belongs to current project
(this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) ||
(this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.projectid && this.$store.getters.project && this.$store.getters.project.id && this.resource.projectid === this.$store.getters.project.id)) &&
(this.resource.isready || !this.resource.status || this.resource.status.indexOf('Downloaded') === -1) && // Template is ready or downloaded
this.resource.templatetype !== 'SYSTEM'
}
},
emits: ['update-zones'],
methods: { methods: {
initForm () { initForm () {
this.formRef = ref() this.formRef = ref()
@ -404,16 +425,10 @@ export default {
this.$notifyError(error) this.$notifyError(error)
}).finally(() => { }).finally(() => {
this.fetchLoading = false this.fetchLoading = false
this.updateImageZones()
}) })
this.fetchZoneData() this.fetchZoneData()
}, this.fetchOsCategoryId()
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) { handleChangePage (page, pageSize) {
this.page = page this.page = page
@ -425,13 +440,6 @@ export default {
this.pageSize = pageSize this.pageSize = pageSize
this.fetchData() this.fetchData()
}, },
isActionPermitted () {
return (['Admin'].includes(this.$store.getters.userInfo.roletype) || // If admin or owner or belongs to current project
(this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) ||
(this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.projectid && this.$store.getters.project && this.$store.getters.project.id && this.resource.projectid === this.$store.getters.project.id)) &&
(this.resource.isready || !this.resource.status || this.resource.status.indexOf('Downloaded') === -1) && // Template is ready or downloaded
this.resource.templatetype !== 'SYSTEM'
},
setSelection (selection) { setSelection (selection) {
this.selectedRowKeys = selection this.selectedRowKeys = selection
if (selection?.length > 0) { if (selection?.length > 0) {
@ -566,9 +574,41 @@ export default {
this.zoneLoading = true this.zoneLoading = true
api('listZones', { showicon: true }).then(json => { api('listZones', { showicon: true }).then(json => {
const zones = json.listzonesresponse.zone || [] const zones = json.listzonesresponse.zone || []
this.zones = [...zones.filter((zone) => this.currentRecord.zoneid !== zone.id)] this.zones = zones
this.copyZones = [...zones.filter((zone) => this.currentRecord.zoneid !== zone.id)]
}).finally(() => { }).finally(() => {
this.zoneLoading = false this.zoneLoading = false
this.updateImageZones()
})
},
updateImageZones () {
if (!Array.isArray(this.dataSource) || !Array.isArray(this.zones) ||
this.dataSource.length === 0 || this.zones.length === 0) {
return
}
const imageZones = []
this.dataSource.forEach(item => {
const zone = this.zones.find(zone => item.zoneid === zone.id)
if (zone && zone.icon) {
item.zoneicon = zone.icon
}
imageZones.push(zone)
})
if (imageZones.length !== 0) {
this.$emit('update-zones', imageZones)
}
},
fetchOsCategoryId () {
const needed = this.$route.meta.name === 'template' &&
'listOsTypes' in this.$store.getters.apis &&
this.resource && this.resource.ostypeid &&
(this.$config.imageSelectionInterface === undefined ||
this.$config.imageSelectionInterface === 'modern')
if (!needed) {
return
}
api('listOsTypes', { id: this.resource.ostypeid }).then(json => {
this.osCategoryId = json?.listostypesresponse?.ostype?.[0]?.oscategoryid || null
}) })
}, },
showCopyTemplate (record) { showCopyTemplate (record) {
@ -634,6 +674,19 @@ export default {
}).catch(error => { }).catch(error => {
this.formRef.value.scrollToField(error.errorFields[0].name) this.formRef.value.scrollToField(error.errorFields[0].name)
}) })
},
onAddInstance (record) {
const query = { templateid: this.resource.id, zoneid: record.zoneid }
if (this.resource.arch) {
query.arch = this.resource.arch
}
if (this.osCategoryId) {
query.oscategoryid = this.osCategoryId
}
this.$router.push({
path: '/action/deployVirtualMachine',
query: query
})
} }
} }
} }

View File

@ -20,6 +20,7 @@
package com.cloud.utils.db; package com.cloud.utils.db;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
@ -46,6 +47,8 @@ public interface EntityManager {
*/ */
public <T> T findByUuid(Class<T> entityType, String uuid); public <T> T findByUuid(Class<T> entityType, String uuid);
<T> List<T> listByUuids(Class<T> entityType, Collection<String> uuids);
/** /**
* Finds a unique entity by uuid string, including those removed entries * Finds a unique entity by uuid string, including those removed entries
* @param <T> entity class * @param <T> entity class