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

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

commit c7e2c748f74619e3b0454d6f2904b89abaf39003
Merge: 57671274d86 a4b102fb3f9
Author: Wei Zhou <[email protected]>
AuthorDate: Fri Jul 10 13:29:39 2026 +0200

    Merge remote-tracking branch 'apache/4.20' into 4.22

 .../service/NetworkOrchestrationService.java       |   3 +
 .../engine/orchestration/NetworkOrchestrator.java  |   2 +-
 .../com/cloud/upgrade/DatabaseUpgradeChecker.java  |   6 +-
 .../com/cloud/upgrade/dao/Upgrade42030to42040.java |  58 +++++++++
 .../resourcedetail/ResourceDetailsDao.java         |   2 +
 .../resourcedetail/ResourceDetailsDaoBase.java     |  14 +++
 .../framework/jobs/impl/AsyncJobManagerImpl.java   |  38 +++---
 .../framework/jobs/AsyncJobManagerTest.java        |  20 ++++
 .../kvm/storage/KVMStorageProcessor.java           |   2 +-
 .../org/apache/cloudstack/utils/qemu/QemuImg.java  |   6 +-
 .../adapter/flasharray/FlashArrayAdapter.java      |  38 +++++-
 server/src/main/java/com/cloud/api/ApiServer.java  |   8 +-
 server/src/main/java/com/cloud/api/ApiServlet.java |  22 ++--
 .../main/java/com/cloud/configuration/Config.java  |   9 --
 .../configuration/ConfigurationManagerImpl.java    |   2 +-
 .../consoleproxy/ConsoleProxyManagerImpl.java      |   9 +-
 .../network/element/VirtualRouterElement.java      |  24 ++--
 .../com/cloud/resource/ResourceManagerImpl.java    |   2 +-
 .../cloudstack/backup/BackupManagerImpl.java       |   2 +-
 .../cloudstack/cluster/ClusterDrsServiceImpl.java  |  34 +++---
 .../cluster/ClusterDrsServiceImplTest.java         |  46 +++----
 tools/marvin/setup.py                              |   2 +-
 ui/public/locales/en.json                          |   5 +
 ui/src/style/vars.less                             |   2 +-
 ui/src/utils/ssvmProbe.js                          |  30 +++++
 ui/src/views/image/RegisterOrUploadIso.vue         |  50 ++++++--
 ui/src/views/image/RegisterOrUploadTemplate.vue    |  45 ++++++-
 ui/src/views/infra/network/IpRangesTabPublic.vue   |   4 +
 ui/src/views/storage/UploadLocalVolume.vue         | 133 ++++++++++++++-------
 29 files changed, 451 insertions(+), 167 deletions(-)

diff --cc 
engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
index b7b548fb940,97e2a979aa4..d851867ef13
--- 
a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
+++ 
b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
@@@ -76,61 -76,45 +76,64 @@@ public interface NetworkOrchestrationSe
       */
      Long RVRHandoverTime = 10000L;
  
 -    ConfigKey<String> MinVRVersion = new ConfigKey<String>(String.class, 
MinVRVersionCK, "Advanced", "4.10.0",
 +    ConfigKey<String> MinVRVersion = new ConfigKey<>(String.class, 
MinVRVersionCK, "Advanced", "4.10.0",
              "What version should the Virtual Routers report", true, 
ConfigKey.Scope.Zone, null);
  
 -    ConfigKey<Integer> NetworkLockTimeout = new 
ConfigKey<Integer>(Integer.class, NetworkLockTimeoutCK, "Network", "600",
 -        "Lock wait timeout (seconds) while implementing Network", true, 
Scope.Global, null);
 +    ConfigKey<Integer> NetworkLockTimeout = new ConfigKey<>(Integer.class, 
NetworkLockTimeoutCK, "Network", "600",
 +            "Lock wait timeout (seconds) while implementing Network", true, 
Scope.Global, null);
  
 -    ConfigKey<String> DeniedRoutes = new ConfigKey<String>(String.class, 
"denied.routes", "Network", "",
 +    ConfigKey<String> DeniedRoutes = new ConfigKey<>(String.class, 
"denied.routes", "Network", "",
              "Routes that are denied, can not be used for Static Routes 
creation for the VPC Private Gateway", true, ConfigKey.Scope.Zone, null);
  
 -    ConfigKey<String> GuestDomainSuffix = new ConfigKey<String>(String.class, 
GuestDomainSuffixCK, "Network", "cloud.internal",
 -        "Default domain name for vms inside virtualized networks fronted by 
router", true, ConfigKey.Scope.Zone, null);
 +    ConfigKey<String> GuestDomainSuffix = new ConfigKey<>(String.class, 
GuestDomainSuffixCK, "Network", "cloud.internal",
 +            "Default domain name for vms inside virtualized networks fronted 
by router", true, ConfigKey.Scope.Zone, null);
  
 -    ConfigKey<Integer> NetworkThrottlingRate = new 
ConfigKey<Integer>("Network", Integer.class, NetworkThrottlingRateCK, "200",
 -        "Default data transfer rate in megabits per second allowed in 
network.", true, ConfigKey.Scope.Zone);
 +    ConfigKey<Integer> NetworkThrottlingRate = new ConfigKey<>("Network", 
Integer.class, NetworkThrottlingRateCK, "200",
 +            "Default data transfer rate in megabits per second allowed in 
network.", true, ConfigKey.Scope.Zone);
  
 -    ConfigKey<Boolean> PromiscuousMode = new ConfigKey<Boolean>("Advanced", 
Boolean.class, "network.promiscuous.mode", "false",
 +    ConfigKey<Boolean> PromiscuousMode = new ConfigKey<>("Advanced", 
Boolean.class, "network.promiscuous.mode", "false",
              "Whether to allow or deny promiscuous mode on NICs for applicable 
network elements such as for vswitch/dvswitch portgroups.", true);
  
 -    ConfigKey<Boolean> MacAddressChanges = new ConfigKey<Boolean>("Advanced", 
Boolean.class, "network.mac.address.changes", "true",
 +    ConfigKey<Boolean> MacAddressChanges = new ConfigKey<>("Advanced", 
Boolean.class, "network.mac.address.changes", "true",
              "Whether to allow or deny mac address changes on NICs for 
applicable network elements such as for vswitch/dvswitch porgroups.", true);
  
 -    ConfigKey<Boolean> ForgedTransmits = new ConfigKey<Boolean>("Advanced", 
Boolean.class, "network.forged.transmits", "true",
 +    ConfigKey<Boolean> ForgedTransmits = new ConfigKey<>("Advanced", 
Boolean.class, "network.forged.transmits", "true",
              "Whether to allow or deny forged transmits on NICs for applicable 
network elements such as for vswitch/dvswitch portgroups.", true);
  
 -    ConfigKey<Boolean> MacLearning = new ConfigKey<Boolean>("Advanced", 
Boolean.class, "network.mac.learning", "false",
 +    ConfigKey<Boolean> MacLearning = new ConfigKey<>("Advanced", 
Boolean.class, "network.mac.learning", "false",
              "Whether to allow or deny MAC learning on nics for applicable 
network elements such as for dvswitch portgroups.", true);
  
 -    ConfigKey<Boolean> RollingRestartEnabled = new 
ConfigKey<Boolean>("Advanced", Boolean.class, "network.rolling.restart", "true",
 +    ConfigKey<Boolean> RollingRestartEnabled = new ConfigKey<>("Advanced", 
Boolean.class, "network.rolling.restart", "true",
              "Whether to allow or deny rolling restart of network routers.", 
true);
  
 -    static final ConfigKey<Boolean> TUNGSTEN_ENABLED = new 
ConfigKey<>(Boolean.class, "tungsten.plugin.enable", "Advanced", "false",
 +    ConfigKey<Boolean> TUNGSTEN_ENABLED = new ConfigKey<>(Boolean.class, 
"tungsten.plugin.enable", "Advanced", "false",
              "Indicates whether to enable the Tungsten plugin", false, 
ConfigKey.Scope.Zone, null);
  
 -    static final ConfigKey<Boolean> NSX_ENABLED = new 
ConfigKey<>(Boolean.class, "nsx.plugin.enable", "Advanced", "false",
 +    ConfigKey<Boolean> NSX_ENABLED = new ConfigKey<>(Boolean.class, 
"nsx.plugin.enable", "Advanced", "false",
              "Indicates whether to enable the NSX plugin", false, 
ConfigKey.Scope.Zone, null);
  
 +    ConfigKey<Boolean> NETRIS_ENABLED = new ConfigKey<>(Boolean.class, 
"netris.plugin.enable", "Advanced", "false",
 +            "Indicates whether to enable the Netris plugin", false, 
ConfigKey.Scope.Zone, null);
 +    ConfigKey<Integer> NETWORK_LB_HAPROXY_MAX_CONN = new ConfigKey<>(
 +                    "Network",
 +                    Integer.class,
 +                    "network.loadbalancer.haproxy.max.conn",
 +                    "4096",
 +                    "Load Balancer(haproxy) maximum number of concurrent 
connections(global max)",
 +                    true,
 +                    Scope.Global);
 +    ConfigKey<Long> NETWORK_LB_HAPROXY_IDLE_TIMEOUT = new ConfigKey<>(
 +                    "Network",
 +                    Long.class,
 +                    "network.loadbalancer.haproxy.idle.timeout",
 +                    "50000",
 +                    "Load Balancer(haproxy) idle timeout in milliseconds. Use 
0 for infinite.",
 +                    true,
 +                    Scope.Global);
 +
+     ConfigKey<Integer> VmNetworkThrottlingRate = new 
ConfigKey<Integer>("Network", Integer.class, "vm.network.throttling.rate", 
"200",
+             "Default data transfer rate in megabits per second allowed in 
User vm's default network.", true, ConfigKey.Scope.Zone);
+ 
      List<? extends Network> setupNetwork(Account owner, NetworkOffering 
offering, DeploymentPlan plan, String name, String displayText, boolean 
isDefault)
          throws ConcurrentOperationException;
  
diff --cc 
engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
index 6fc673cc2c9,f7aa957abb9..55079173dc9
--- 
a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
+++ 
b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
@@@ -4937,9 -4905,8 +4937,9 @@@ public class NetworkOrchestrator extend
      @Override
      public ConfigKey<?>[] getConfigKeys() {
          return new ConfigKey<?>[]{NetworkGcWait, NetworkGcInterval, 
NetworkLockTimeout, DeniedRoutes,
-                 GuestDomainSuffix, NetworkThrottlingRate, MinVRVersion,
+                 GuestDomainSuffix, NetworkThrottlingRate, 
VmNetworkThrottlingRate, MinVRVersion,
                  PromiscuousMode, MacAddressChanges, ForgedTransmits, 
MacLearning, RollingRestartEnabled,
 -                TUNGSTEN_ENABLED, NSX_ENABLED };
 +                TUNGSTEN_ENABLED, NSX_ENABLED, NETRIS_ENABLED, 
NETWORK_LB_HAPROXY_MAX_CONN,
 +                NETWORK_LB_HAPROXY_IDLE_TIMEOUT};
      }
  }
diff --cc 
engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java
index 296f80f4b5e,0d9807dcb2d..170a12f9374
--- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java
@@@ -89,9 -89,8 +89,11 @@@ import com.cloud.upgrade.dao.Upgrade418
  import com.cloud.upgrade.dao.Upgrade41900to41910;
  import com.cloud.upgrade.dao.Upgrade41910to42000;
  import com.cloud.upgrade.dao.Upgrade42000to42010;
 +import com.cloud.upgrade.dao.Upgrade42010to42100;
- import com.cloud.upgrade.dao.Upgrade42100to42200;
 +import com.cloud.upgrade.dao.Upgrade42200to42210;
+ import com.cloud.upgrade.dao.Upgrade42020to42030;
+ import com.cloud.upgrade.dao.Upgrade42030to42040;
++import com.cloud.upgrade.dao.Upgrade42100to42200;
  import com.cloud.upgrade.dao.Upgrade420to421;
  import com.cloud.upgrade.dao.Upgrade421to430;
  import com.cloud.upgrade.dao.Upgrade430to440;
@@@ -239,9 -238,8 +241,11 @@@ public class DatabaseUpgradeChecker imp
                  .next("4.19.0.0", new Upgrade41900to41910())
                  .next("4.19.1.0", new Upgrade41910to42000())
                  .next("4.20.0.0", new Upgrade42000to42010())
 +                .next("4.20.1.0", new Upgrade42010to42100())
+                 .next("4.20.2.0", new Upgrade42020to42030())
+                 .next("4.20.3.0", new Upgrade42030to42040())
 +                .next("4.21.0.0", new Upgrade42100to42200())
 +                .next("4.22.0.0", new Upgrade42200to42210())
                  .build();
      }
  
diff --cc 
engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java
index eafaed182ab,74c4f7bcfe0..8f376a71f66
--- 
a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java
+++ 
b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java
@@@ -21,9 -22,9 +22,10 @@@ import java.util.List
  import java.util.Map;
  import java.util.stream.Collectors;
  
+ import com.cloud.utils.StringUtils;
  import org.apache.commons.collections.CollectionUtils;
  
 +import com.cloud.utils.Pair;
  import com.cloud.utils.crypt.DBEncryptionUtil;
  import com.cloud.utils.db.GenericDaoBase;
  import com.cloud.utils.db.GenericSearchBuilder;
diff --cc 
plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java
index 1fec561dc89,48423b7b60b..80e44d8059a
--- 
a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java
@@@ -61,9 -55,8 +61,9 @@@ public class QemuImg 
      /* The qemu-img binary. We expect this to be in $PATH */
      public String _qemuImgPath = "qemu-img";
      private String cloudQemuImgPath = "cloud-qemu-img";
-     private int timeout;
+     private long timeout;
      private boolean skipZero = false;
 +    private boolean skipTargetVolumeCreation = false;
      private boolean noCache = false;
      private long version;
  
diff --cc server/src/main/java/com/cloud/api/ApiServlet.java
index 93d8e09520a,64308cc9e6b..3ac5bbb01a7
--- a/server/src/main/java/com/cloud/api/ApiServlet.java
+++ b/server/src/main/java/com/cloud/api/ApiServlet.java
@@@ -368,19 -318,6 +368,19 @@@ public class ApiServlet extends HttpSer
                  }
              }
  
 +            if (apiServer.isPostRequestsAndTimestampsEnforced() && 
!isStateChangingCommandUsingPOST(command, req.getMethod(), params)) {
 +                String errorText = String.format("State changing command %s 
needs to be sent using POST request", command);
 +                if (command.equalsIgnoreCase("updateConfiguration") && 
params.containsKey("name")) {
 +                    errorText = String.format("Changes for configuration %s 
needs to be sent using POST request", params.get("name")[0]);
 +                }
 +                auditTrailSb.append(" " + HttpServletResponse.SC_BAD_REQUEST 
+ " " + errorText);
 +                final String serializedResponse =
 +                        apiServer.getSerializedApiError(new 
ServerApiException(ApiErrorCode.BAD_REQUEST, errorText), params,
 +                                responseType);
-                 HttpUtils.writeHttpResponse(resp, serializedResponse, 
HttpServletResponse.SC_BAD_REQUEST, responseType, 
ApiServer.JSONcontentType.value());
++                HttpUtils.writeHttpResponse(resp, serializedResponse, 
HttpServletResponse.SC_BAD_REQUEST, responseType, 
ApiServer.JSONContentType.value());
 +                return;
 +            }
 +
              Long userId = null;
              if (!isNew) {
                  userId = (Long)session.getAttribute("userid");
diff --cc 
server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java
index 7e1011ff931,fbfed03aa1e..62075aae596
--- 
a/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java
+++ 
b/server/src/main/java/org/apache/cloudstack/cluster/ClusterDrsServiceImpl.java
@@@ -51,11 -51,13 +51,13 @@@ import com.cloud.utils.db.GlobalLock
  import com.cloud.utils.db.Transaction;
  import com.cloud.utils.db.TransactionCallback;
  import com.cloud.utils.exception.CloudRuntimeException;
 -import com.cloud.vm.UserVmDetailVO;
++import com.cloud.vm.VMInstanceDetailVO;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.VirtualMachine;
  import com.cloud.vm.VirtualMachineProfile;
  import com.cloud.vm.VirtualMachineProfileImpl;
  import com.cloud.vm.VmDetailConstants;
 -import com.cloud.vm.dao.UserVmDetailsDao;
++import com.cloud.vm.dao.VMInstanceDetailsDao;
  import com.cloud.vm.dao.VMInstanceDao;
  import org.apache.cloudstack.api.ApiCommandResourceType;
  import org.apache.cloudstack.api.ApiConstants;
@@@ -134,6 -135,9 +135,9 @@@ public class ClusterDrsServiceImpl exte
      @Inject
      ServiceOfferingDao serviceOfferingDao;
  
+     @Inject
 -    UserVmDetailsDao userVmDetailsDao;
++    VMInstanceDetailsDao vmInstanceDetailsDao;
+ 
      @Inject
      ManagementServer managementServer;
  
@@@ -475,12 -479,16 +479,16 @@@
          Map<Long, List<? extends Host>> vmToCompatibleHostsCache = new 
HashMap<>();
          Map<Long, Map<Host, Boolean>> vmToStorageMotionCache = new 
HashMap<>();
  
+         List<Long> vmIds = 
vmList.stream().map(VirtualMachine::getId).collect(Collectors.toList());
 -        Set<Long> skipDrsVmIds = 
userVmDetailsDao.listDetailsForResourceIdsAndKey(vmIds, 
VmDetailConstants.SKIP_DRS)
++        Set<Long> skipDrsVmIds = 
vmInstanceDetailsDao.listDetailsForResourceIdsAndKey(vmIds, 
VmDetailConstants.SKIP_DRS)
+                 .stream().filter(d -> "true".equalsIgnoreCase(d.getValue()))
 -                .map(UserVmDetailVO::getResourceId)
++                .map(VMInstanceDetailVO::getResourceId)
+                 .collect(Collectors.toSet());
+ 
          for (VirtualMachine vm : vmList) {
              // Skip ineligible VMs
-             if (vm.getType().isUsedBySystem() ||
-                 vm.getState() != VirtualMachine.State.Running ||
-                 (MapUtils.isNotEmpty(vm.getDetails()) &&
-                  
"true".equalsIgnoreCase(vm.getDetails().get(VmDetailConstants.SKIP_DRS)))) {
+             if (shouldSkipVMForDRS(vm, skipDrsVmIds)) {
+                 logger.debug("Skipping VM {} for DRS as it is ineligible.", 
vm);
                  continue;
              }
  
diff --cc 
server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java
index 98b18c66305,6dc73b7a630..6390b29097b
--- 
a/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java
+++ 
b/server/src/test/java/org/apache/cloudstack/cluster/ClusterDrsServiceImplTest.java
@@@ -40,9 -40,11 +40,11 @@@ import com.cloud.utils.Pair
  import com.cloud.utils.Ternary;
  import com.cloud.utils.db.GlobalLock;
  import com.cloud.utils.exception.CloudRuntimeException;
 -import com.cloud.vm.UserVmDetailVO;
++import com.cloud.vm.VMInstanceDetailVO;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.VirtualMachine;
  import com.cloud.vm.VmDetailConstants;
 -import com.cloud.vm.dao.UserVmDetailsDao;
++import com.cloud.vm.dao.VMInstanceDetailsDao;
  import com.cloud.vm.dao.VMInstanceDao;
  import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
  import 
org.apache.cloudstack.api.command.admin.cluster.GenerateClusterDrsPlanCmd;
@@@ -121,6 -123,9 +123,9 @@@ public class ClusterDrsServiceImplTest 
      @Mock
      private AffinityGroupVMMapDao affinityGroupVMMapDao;
  
+     @Mock
 -    private UserVmDetailsDao userVmDetailsDao;
++    private VMInstanceDetailsDao vmInstanceDetailsDao;
+ 
      @Spy
      @InjectMocks
      private ClusterDrsServiceImpl clusterDrsService = new 
ClusterDrsServiceImpl();
@@@ -370,6 -376,11 +376,11 @@@
          Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
          Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
  
+         // Return the SKIP_DRS detail for skippedVm so the flag is actually 
honoured
 -        UserVmDetailVO skipDrsDetail = new UserVmDetailVO(1L, 
VmDetailConstants.SKIP_DRS, "true", true);
 -        
Mockito.when(userVmDetailsDao.listDetailsForResourceIdsAndKey(Mockito.anyList(),
++        VMInstanceDetailVO skipDrsDetail = new VMInstanceDetailVO(1L, 
VmDetailConstants.SKIP_DRS, "true", true);
++        
Mockito.when(vmInstanceDetailsDao.listDetailsForResourceIdsAndKey(Mockito.anyList(),
+                 
Mockito.eq(VmDetailConstants.SKIP_DRS))).thenReturn(List.of(skipDrsDetail));
+ 
          Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
          Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
          Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), 
Mockito.anyList(), Mockito.anyList())).thenReturn(true);
diff --cc ui/src/views/image/RegisterOrUploadIso.vue
index e8ce5add61b,1984a6a6144..eb19379dc8e
--- a/ui/src/views/image/RegisterOrUploadIso.vue
+++ b/ui/src/views/image/RegisterOrUploadIso.vue
@@@ -583,9 -617,8 +617,8 @@@ export default 
            }
            params.format = 'ISO'
            this.loading = true
-           getAPI('getUploadParamsForIso', params).then(json => {
 -          api('getUploadParamsForIso', params).then(async json => {
++          getAPI('getUploadParamsForIso', params).then(async json => {
              this.uploadParams = (json.postuploadisoresponse && 
json.postuploadisoresponse.getuploadparams) ? 
json.postuploadisoresponse.getuploadparams : ''
-             const response = this.handleUpload()
              if (this.userdataid !== null) {
                this.linkUserdataToTemplate(this.userdataid, 
json.postuploadisoresponse.iso[0].id)
              }
diff --cc ui/src/views/image/RegisterOrUploadTemplate.vue
index 00b06072793,1267e5d45c1..13e6b50dcc7
--- a/ui/src/views/image/RegisterOrUploadTemplate.vue
+++ b/ui/src/views/image/RegisterOrUploadTemplate.vue
@@@ -505,9 -488,9 +521,10 @@@ import { getAPI, postAPI } from '@/api
  import store from '@/store'
  import { axios } from '../../utils/request'
  import { mixinForm } from '@/utils/mixin'
+ import { probeSsvmCert } from '@/utils/ssvmProbe'
  import ResourceIcon from '@/components/view/ResourceIcon'
  import TooltipLabel from '@/components/widgets/TooltipLabel'
 +import DetailsInput from '@/components/widgets/DetailsInput'
  
  export default {
    name: 'RegisterOrUploadTemplate',
@@@ -1175,9 -1157,8 +1208,8 @@@
                duration: 0
              })
            }
-           getAPI('getUploadParamsForTemplate', params).then(json => {
 -          api('getUploadParamsForTemplate', params).then(async json => {
++          getAPI('getUploadParamsForTemplate', params).then(async json => {
              this.uploadParams = (json.postuploadtemplateresponse && 
json.postuploadtemplateresponse.getuploadparams) ? 
json.postuploadtemplateresponse.getuploadparams : ''
-             this.handleUpload()
              if (this.userdataid !== null) {
                this.linkUserdataToTemplate(this.userdataid, 
json.postuploadtemplateresponse.template[0].id)
              }
diff --cc ui/src/views/storage/UploadLocalVolume.vue
index 5e6a5b4fc27,b7303117e5a..b936ebdfac7
--- a/ui/src/views/storage/UploadLocalVolume.vue
+++ b/ui/src/views/storage/UploadLocalVolume.vue
@@@ -153,9 -169,10 +169,10 @@@
  
  <script>
  import { ref, reactive, toRaw } from 'vue'
 -import { api } from '@/api'
 +import { getAPI } from '@/api'
  import { axios } from '../../utils/request'
  import { mixinForm } from '@/utils/mixin'
+ import { probeSsvmCert } from '@/utils/ssvmProbe'
  import ResourceIcon from '@/components/view/ResourceIcon'
  import TooltipLabel from '@/components/widgets/TooltipLabel'
  import InfiniteScrollSelect from 
'@/components/widgets/InfiniteScrollSelect.vue'
@@@ -286,49 -363,15 +363,15 @@@ export default 
          }
          params.domainId = this.domainId
          this.loading = true
-         getAPI('getUploadParamsForVolume', params).then(json => {
 -        api('getUploadParamsForVolume', params).then(async json => {
++        getAPI('getUploadParamsForVolume', params).then(async json => {
            this.uploadParams = json.postuploadvolumeresponse?.getuploadparams 
|| ''
-           const { fileList } = this
-           if (this.fileList.length > 1) {
-             this.$notification.error({
-               message: this.$t('message.upload.volume.failed'),
-               description: this.$t('message.upload.file.limit'),
-               duration: 0
-             })
+           this.ssvmOrigin = new URL(this.uploadParams.postURL).origin
+           const trusted = await probeSsvmCert(this.ssvmOrigin)
+           if (!trusted) {
+             this.ssvmCertUntrusted = true
+             return
            }
-           const formData = new FormData()
-           fileList.forEach(file => {
-             formData.append('files[]', file)
-           })
-           this.uploadPercentage = 0
-           axios.post(this.uploadParams.postURL,
-             formData,
-             {
-               headers: {
-                 'content-type': 'multipart/form-data',
-                 'x-signature': this.uploadParams.signature,
-                 'x-expires': this.uploadParams.expires,
-                 'x-metadata': this.uploadParams.metadata
-               },
-               onUploadProgress: (progressEvent) => {
-                 this.uploadPercentage = Number(parseFloat(100 * 
progressEvent.loaded / progressEvent.total).toFixed(1))
-               },
-               timeout: 86400000
-             }).then((json) => {
-             this.$notification.success({
-               message: this.$t('message.success.upload'),
-               description: 
this.$t('message.success.upload.volume.description')
-             })
-             this.closeAction()
-           }).catch(e => {
-             this.$notification.error({
-               message: this.$t('message.upload.failed'),
-               description: `${this.$t('message.upload.volume.failed')} -  
${e}`,
-               duration: 0
-             })
-           }).finally(() => {
-             this.loading = false
-           })
+           this.handleUpload()
          }).catch(e => {
            this.$notification.error({
              message: this.$t('message.upload.failed'),

Reply via email to