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') }}