Merge remote-tracking branch 'apache/4.19'
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 6dfcf65..892903d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -454,6 +454,7 @@ public static final String TEMPLATE_IDS = "templateids"; public static final String TEMPLATE_NAME = "templatename"; public static final String TEMPLATE_TYPE = "templatetype"; + public static final String TEMPLATE_FORMAT = "templateformat"; public static final String TIMEOUT = "timeout"; public static final String TIMEZONE = "timezone"; public static final String TIMEZONEOFFSET = "timezoneoffset";
diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index 763265e..5a0ea77 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java
@@ -137,6 +137,10 @@ @Param(description = "the type of the template for the virtual machine", since = "4.19.0") private String templateType; + @SerializedName(ApiConstants.TEMPLATE_FORMAT) + @Param(description = "the format of the template for the virtual machine", since = "4.19.1") + private String templateFormat; + @SerializedName("templatedisplaytext") @Param(description = " an alternate display text of the template for the virtual machine") private String templateDisplayText; @@ -1076,6 +1080,14 @@ this.templateType = templateType; } + public String getTemplateFormat() { + return templateFormat; + } + + public void setTemplateFormat(String templateFormat) { + this.templateFormat = templateFormat; + } + public List<VnfNicResponse> getVnfNics() { return vnfNics; }
diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql index 7a057dc..25f9570 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql
@@ -74,6 +74,7 @@ `vm_template`.`uuid` AS `template_uuid`, `vm_template`.`name` AS `template_name`, `vm_template`.`type` AS `template_type`, + `vm_template`.`format` AS `template_format`, `vm_template`.`display_text` AS `template_display_text`, `vm_template`.`enable_password` AS `password_enabled`, `iso`.`id` AS `iso_id`,
diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 5d8c32f..e6ee132 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
@@ -197,6 +197,7 @@ userVmResponse.setTemplateDisplayText(userVm.getTemplateDisplayText()); userVmResponse.setPasswordEnabled(userVm.isPasswordEnabled()); userVmResponse.setTemplateType(userVm.getTemplateType().toString()); + userVmResponse.setTemplateFormat(userVm.getTemplateFormat().toString()); } if (details.contains(VMDetails.all) || details.contains(VMDetails.iso)) { userVmResponse.setIsoId(userVm.getIsoUuid());
diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java index bf6c05c..a3116d4 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
@@ -35,6 +35,7 @@ import com.cloud.network.Network.GuestType; import com.cloud.network.Networks.TrafficType; import com.cloud.resource.ResourceState; +import com.cloud.storage.Storage; import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.Volume; @@ -201,6 +202,9 @@ @Column(name = "template_display_text", length = 4096) private String templateDisplayText; + @Column(name = "template_format") + private Storage.ImageFormat templateFormat; + @Column(name = "password_enabled") private boolean passwordEnabled; @@ -647,6 +651,10 @@ return templateDisplayText; } + public Storage.ImageFormat getTemplateFormat() { + return templateFormat; + } + public boolean isPasswordEnabled() { return passwordEnabled; }
diff --git a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java index 00bce43..5881619 100644 --- a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java +++ b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
@@ -1172,6 +1172,7 @@ Long profileId = cmd.getProfileId(); Long zoneId = cmd.getZoneId(); Boolean forDisplay = cmd.getDisplay(); + String keyword = cmd.getKeyword(); SearchWrapper<AutoScaleVmGroupVO> searchWrapper = new SearchWrapper<>(autoScaleVmGroupDao, AutoScaleVmGroupVO.class, cmd, cmd.getId()); SearchBuilder<AutoScaleVmGroupVO> sb = searchWrapper.getSearchBuilder(); @@ -1182,6 +1183,7 @@ sb.and("profileId", sb.entity().getProfileId(), SearchCriteria.Op.EQ); sb.and("zoneId", sb.entity().getZoneId(), SearchCriteria.Op.EQ); sb.and("display", sb.entity().isDisplay(), SearchCriteria.Op.EQ); + sb.and("keyword", sb.entity().getName(), SearchCriteria.Op.LIKE); if (policyId != null) { SearchBuilder<AutoScaleVmGroupPolicyMapVO> asVmGroupPolicySearch = autoScaleVmGroupPolicyMapDao.createSearchBuilder(); @@ -1211,6 +1213,9 @@ if (forDisplay != null) { sc.setParameters("display", forDisplay); } + if (StringUtils.isNotBlank(keyword)) { + sc.setParameters("keyword", "%" + keyword + "%"); + } return searchWrapper.search(); }
diff --git a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java index fa95aef..28bb998 100755 --- a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java +++ b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java
@@ -16,20 +16,12 @@ // under the License. package com.cloud.api.query.dao; -import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.storage.Storage; -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.Account; -import com.cloud.user.AccountManager; -import com.cloud.user.UserStatisticsVO; -import com.cloud.user.dao.UserDao; -import com.cloud.user.dao.UserStatisticsDao; -import com.cloud.utils.db.SearchBuilder; -import com.cloud.utils.db.SearchCriteria; -import com.cloud.vm.dao.UserVmDetailsDao; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.MockitoAnnotations.openMocks; + +import java.util.Arrays; +import java.util.EnumSet; + import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ResponseObject; @@ -44,11 +36,20 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import java.util.Arrays; -import java.util.EnumSet; - -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.MockitoAnnotations.openMocks; +import com.cloud.api.query.vo.UserVmJoinVO; +import com.cloud.storage.Storage; +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.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.UserStatisticsVO; +import com.cloud.user.dao.UserDao; +import com.cloud.user.dao.UserStatisticsDao; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.dao.UserVmDetailsDao; @RunWith(MockitoJUnitRunner.class) public class UserVmJoinDaoImplTest extends GenericDaoBaseWithTagInformationBaseTest<UserVmJoinVO, UserVmResponse> { @@ -113,6 +114,7 @@ Mockito.when(userVmMock.getId()).thenReturn(vmId); Mockito.when(userVmMock.getTemplateId()).thenReturn(templateId); Mockito.when(userVmMock.getTemplateType()).thenReturn(Storage.TemplateType.VNF); + Mockito.when(userVmMock.getTemplateFormat()).thenReturn(Storage.ImageFormat.OVA); Mockito.when(caller.getId()).thenReturn(2L); Mockito.when(accountMgr.isRootAdmin(nullable(Long.class))).thenReturn(true);
diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue index 66c878d..a4fa119 100644 --- a/ui/src/components/view/InfoCard.vue +++ b/ui/src/components/view/InfoCard.vue
@@ -524,7 +524,7 @@ <div class="resource-detail-item__details"> <resource-icon v-if="resource.icon" :image="getImage(resource.icon.base64image)" size="1x" style="margin-right: 5px"/> <SaveOutlined v-else /> - <router-link :to="{ path: '/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> <div class="resource-detail-item" v-if="resource.isoid">
diff --git a/ui/src/config/section/image.js b/ui/src/config/section/image.js index e6095c4..1f7fa0f 100644 --- a/ui/src/config/section/image.js +++ b/ui/src/config/section/image.js
@@ -231,7 +231,7 @@ } return fields }, - details: ['name', 'id', 'displaytext', 'checksum', 'ostypename', 'size', 'bootable', 'isready', 'passwordenabled', 'directdownload', 'isextractable', 'ispublic', 'isfeatured', 'crosszones', 'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy', 'url'], + details: ['name', 'id', 'displaytext', 'checksum', 'ostypename', 'size', 'bootable', 'isready', 'passwordenabled', 'directdownload', 'isextractable', 'ispublic', 'isfeatured', 'isdynamicallyscalable', 'crosszones', 'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy', 'url'], searchFilters: () => { var filters = ['name', 'zoneid', 'tags'] if (['Admin', 'DomainAdmin'].includes(store.getters.userInfo.roletype)) {
diff --git a/ui/src/views/image/RegisterOrUploadIso.vue b/ui/src/views/image/RegisterOrUploadIso.vue index 746cdb2..edc6b82 100644 --- a/ui/src/views/image/RegisterOrUploadIso.vue +++ b/ui/src/views/image/RegisterOrUploadIso.vue
@@ -227,35 +227,45 @@ </a-col> </a-row> - <a-form-item ref="isextractable" name="isextractable"> - <template #label> - <tooltip-label :title="$t('label.isextractable')" :tooltip="apiParams.isextractable.description"/> - </template> - <a-switch v-model:checked="form.isextractable" /> - </a-form-item> - - <a-form-item - ref="ispublic" - name="ispublic" - v-if="$store.getters.userInfo.roletype === 'Admin' || $store.getters.features.userpublictemplateenabled" > - <template #label> - <tooltip-label :title="$t('label.ispublic')" :tooltip="apiParams.ispublic.description"/> - </template> - <a-switch v-model:checked="form.ispublic" /> - </a-form-item> - - <a-form-item ref="isfeatured" name="isfeatured" v-if="$store.getters.userInfo.roletype === 'Admin'"> - <template #label> - <tooltip-label :title="$t('label.isfeatured')" :tooltip="apiParams.isfeatured.description"/> - </template> - <a-switch v-model:checked="form.isfeatured" /> - </a-form-item> - <a-form-item ref="passwordenabled" name="passwordenabled" v-if="currentForm === 'Create'"> - <template #label> - <tooltip-label :title="$t('label.passwordenabled')" :tooltip="apiParams.passwordenabled.description"/> - </template> - <a-switch v-model:checked="form.passwordenabled" /> - </a-form-item> + <a-row :gutter="12"> + <a-col :md="24" :lg="12"> + <a-form-item ref="isdynamicallyscalable" name="isdynamicallyscalable"> + <template #label> + <tooltip-label :title="$t('label.isdynamicallyscalable')" :tooltip="apiParams.isdynamicallyscalable.description"/> + </template> + <a-switch v-model:checked="form.isdynamicallyscalable" /> + </a-form-item> + <a-form-item + ref="ispublic" + name="ispublic" + v-if="$store.getters.userInfo.roletype === 'Admin' || $store.getters.features.userpublictemplateenabled" > + <template #label> + <tooltip-label :title="$t('label.ispublic')" :tooltip="apiParams.ispublic.description"/> + </template> + <a-switch v-model:checked="form.ispublic" /> + </a-form-item> + <a-form-item ref="passwordenabled" name="passwordenabled" v-if="currentForm === 'Create'"> + <template #label> + <tooltip-label :title="$t('label.passwordenabled')" :tooltip="apiParams.passwordenabled.description"/> + </template> + <a-switch v-model:checked="form.passwordenabled" /> + </a-form-item> + </a-col> + <a-col :md="24" :lg="12"> + <a-form-item ref="isextractable" name="isextractable"> + <template #label> + <tooltip-label :title="$t('label.isextractable')" :tooltip="apiParams.isextractable.description"/> + </template> + <a-switch v-model:checked="form.isextractable" /> + </a-form-item> + <a-form-item ref="isfeatured" name="isfeatured" v-if="$store.getters.userInfo.roletype === 'Admin'"> + <template #label> + <tooltip-label :title="$t('label.isfeatured')" :tooltip="apiParams.isfeatured.description"/> + </template> + <a-switch v-model:checked="form.isfeatured" /> + </a-form-item> + </a-col> + </a-row> <div :span="24" class="action-button"> <a-button @click="closeAction">{{ $t('label.cancel') }}</a-button> @@ -339,7 +349,8 @@ bootable: true, isextractable: false, ispublic: false, - passwordenabled: false + passwordenabled: false, + isdynamicallyscalable: false }) this.rules = reactive({ url: [{ required: true, message: this.$t('label.upload.iso.from.local') }],
diff --git a/ui/src/views/image/UpdateISO.vue b/ui/src/views/image/UpdateISO.vue index f718f1c..e3594a3 100644 --- a/ui/src/views/image/UpdateISO.vue +++ b/ui/src/views/image/UpdateISO.vue
@@ -67,6 +67,12 @@ </a-select-option> </a-select> </a-form-item> + <a-form-item name="isdynamicallyscalable" ref="isdynamicallyscalable"> + <template #label> + <tooltip-label :title="$t('label.isdynamicallyscalable')" :tooltip="apiParams.isdynamicallyscalable.description"/> + </template> + <a-switch v-model:checked="form.isdynamicallyscalable" /> + </a-form-item> <a-row :gutter="12"> <a-col :md="24" :lg="12"> @@ -168,7 +174,7 @@ displaytext: [{ required: true, message: this.$t('message.error.required.input') }], ostypeid: [{ required: true, message: this.$t('message.error.select') }] }) - const resourceFields = ['name', 'displaytext', 'passwordenabled', 'ostypeid', 'userdataid', 'userdatapolicy'] + const resourceFields = ['name', 'displaytext', 'passwordenabled', 'isdynamicallyscalable', 'ostypeid', 'userdataid', 'userdatapolicy'] for (var field of resourceFields) { var fieldValue = this.resource[field]
diff --git a/ui/src/views/project/iam/ProjectRolePermissionTab.vue b/ui/src/views/project/iam/ProjectRolePermissionTab.vue index 7b24098a..dd628d7 100644 --- a/ui/src/views/project/iam/ProjectRolePermissionTab.vue +++ b/ui/src/views/project/iam/ProjectRolePermissionTab.vue
@@ -78,7 +78,7 @@ </div> <div class="rules-table__col rules-table__col--description"> <template v-if="element.description"> - {{ record.description }} + {{ element.description }} </template> <div v-else class="no-description"> {{ $t('message.no.description') }}