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

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


The following commit(s) were added to refs/heads/4.22 by this push:
     new b869913529e noVNC: support Spanish Latin American keyboard on VMware 
(#12484)
b869913529e is described below

commit b869913529ec8e230abcb5d6b0924a36a7cdc7e3
Author: Wei Zhou <[email protected]>
AuthorDate: Mon Feb 2 10:46:54 2026 +0100

    noVNC: support Spanish Latin American keyboard on VMware (#12484)
    
    * noVNC: support Spanish Latin American keyboard
    
    * Update server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
---
 .../api/command/user/vm/BaseDeployVMCmd.java       |   2 +-
 .../cloudstack/api/response/UserVmResponse.java    |  12 ++
 .../java/com/cloud/api/query/QueryManagerImpl.java |   2 +-
 .../com/cloud/api/query/dao/UserVmJoinDaoImpl.java |   8 ++
 .../src/main/java/com/cloud/vm/UserVmManager.java  |   9 ++
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  |  29 ++++-
 .../cloud/api/query/dao/UserVmJoinDaoImplTest.java |   4 +
 systemvm/agent/noVNC/core/rfb.js                   | 124 +++++++++++++++----
 .../noVNC/keymaps/generate-language-keymaps.py     |   7 +-
 .../agent/noVNC/keymaps/keymap-es-latam-atset1.js  | 131 +++++++++++++++++++++
 ui/public/config.json                              |   3 +-
 ui/public/locales/en.json                          |   2 +
 ui/src/components/view/DetailSettings.vue          |  21 +++-
 13 files changed, 312 insertions(+), 42 deletions(-)

diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
index ecbde47692f..e71b4feea03 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java
@@ -188,7 +188,7 @@ public abstract class BaseDeployVMCmd extends 
BaseAsyncCreateCustomIdCmd impleme
     @Parameter(name = ApiConstants.MAC_ADDRESS, type = CommandType.STRING, 
description = "the mac address for default vm's network")
     private String macAddress;
 
-    @Parameter(name = ApiConstants.KEYBOARD, type = CommandType.STRING, 
description = "an optional keyboard device type for the virtual machine. valid 
value can be one of de,de-ch,es,fi,fr,fr-be,fr-ch,is,it,jp,nl-be,no,pt,uk,us")
+    @Parameter(name = ApiConstants.KEYBOARD, type = CommandType.STRING, 
description = "an optional keyboard device type for the virtual machine. valid 
value can be one of 
de,de-ch,es,es-latam,fi,fr,fr-be,fr-ch,is,it,jp,nl-be,no,pt,uk,us")
     private String keyboard;
 
     @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, 
entityType = ProjectResponse.class, description = "Deploy vm for the project")
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 745c0ba4683..a7f6dff96f8 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
@@ -340,6 +340,10 @@ public class UserVmResponse extends 
BaseResponseWithTagInformation implements Co
     @Param(description = "List of read-only Instance details as comma 
separated string.", since = "4.16.0")
     private String readOnlyDetails;
 
+    @SerializedName("alloweddetails")
+    @Param(description = "List of allowed Vm details as comma separated string 
if VM instance settings are read from OVA.", since = "4.22.1")
+    private String allowedDetails;
+
     @SerializedName(ApiConstants.SSH_KEYPAIRS)
     @Param(description = "SSH key-pairs")
     private String keyPairNames;
@@ -1091,6 +1095,10 @@ public class UserVmResponse extends 
BaseResponseWithTagInformation implements Co
         this.readOnlyDetails = readOnlyDetails;
     }
 
+    public void setAllowedDetails(String allowedDetails) {
+        this.allowedDetails = allowedDetails;
+    }
+
     public void setOsTypeId(String osTypeId) {
         this.osTypeId = osTypeId;
     }
@@ -1115,6 +1123,10 @@ public class UserVmResponse extends 
BaseResponseWithTagInformation implements Co
         return readOnlyDetails;
     }
 
+    public String getAllowedDetails() {
+        return allowedDetails;
+    }
+
     public Boolean getDynamicallyScalable() {
         return isDynamicallyScalable;
     }
diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java 
b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
index d42dbaec6de..ac9f8ee1433 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -5379,7 +5379,7 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
 
         options.put(ApiConstants.BootType.UEFI.toString(), 
Arrays.asList(ApiConstants.BootMode.LEGACY.toString(),
             ApiConstants.BootMode.SECURE.toString()));
-        options.put(VmDetailConstants.KEYBOARD, Arrays.asList("uk", "us", 
"jp", "fr"));
+        options.put(VmDetailConstants.KEYBOARD, Arrays.asList("uk", "us", 
"jp", "fr", "es-latam"));
         options.put(VmDetailConstants.CPU_CORE_PER_SOCKET, 
Collections.emptyList());
         options.put(VmDetailConstants.ROOT_DISK_SIZE, Collections.emptyList());
 
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 a2f9544de39..93dca8cc07a 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
@@ -69,9 +69,11 @@ import com.cloud.service.ServiceOfferingDetailsVO;
 import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.GuestOS;
 import com.cloud.storage.Storage.TemplateType;
+import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.VnfTemplateDetailVO;
 import com.cloud.storage.VnfTemplateNicVO;
 import com.cloud.storage.Volume;
+import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.storage.dao.VnfTemplateDetailsDao;
 import com.cloud.storage.dao.VnfTemplateNicDao;
 import com.cloud.user.Account;
@@ -124,6 +126,8 @@ public class UserVmJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<UserVmJo
     private ServiceOfferingDao serviceOfferingDao;
     @Inject
     private VgpuProfileDao vgpuProfileDao;
+    @Inject
+    VMTemplateDao vmTemplateDao;
 
     private final SearchBuilder<UserVmJoinVO> VmDetailSearch;
     private final SearchBuilder<UserVmJoinVO> activeVmByIsoSearch;
@@ -465,6 +469,10 @@ public class UserVmJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<UserVmJo
             if (caller.getType() != Account.Type.ADMIN) {
                 
userVmResponse.setReadOnlyDetails(QueryService.UserVMReadOnlyDetails.value());
             }
+            VMTemplateVO template = 
vmTemplateDao.findByIdIncludingRemoved(userVm.getTemplateId());
+            if (template != null && template.isDeployAsIs() && 
UserVmManager.VmwareAdditionalDetailsFromOvaEnabled.valueIn(userVm.getDataCenterId()))
 {
+                
userVmResponse.setAllowedDetails(UserVmManager.VmwareAllowedAdditionalDetailsFromOva.valueIn(userVm.getDataCenterId()));
+            }
         }
 
         userVmResponse.setObjectName(objectName);
diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java 
b/server/src/main/java/com/cloud/vm/UserVmManager.java
index 972c6cbea89..c035165a3fa 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManager.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManager.java
@@ -99,6 +99,15 @@ public interface UserVmManager extends UserVmService {
             ConfigKey.Scope.Account);
 
 
+    ConfigKey<Boolean> VmwareAdditionalDetailsFromOvaEnabled = new 
ConfigKey<Boolean>("Advanced", Boolean.class,
+            "vmware.additional.details.from.ova.enabled", "false",
+            "If true, allow users to add additional VM settings if VM instance 
settings are read from OVA.", true, ConfigKey.Scope.Zone);
+
+    ConfigKey<String> VmwareAllowedAdditionalDetailsFromOva = new 
ConfigKey<>(String.class,
+            "vmware.allowed.additional.details.from.ova", "Advanced", "",
+            "Comma separated list of allowed additional VM settings if VM 
instance settings are read from OVA.",
+            true, ConfigKey.Scope.Zone, null, null, null, null, null, 
ConfigKey.Kind.CSV, null);
+
     static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
 
     public  static  final String CKS_NODE = "cksnode";
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index eccea944fe6..f36c851e5bb 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -2886,11 +2886,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
 
         UserVmVO vmInstance = _vmDao.findById(cmd.getId());
         VMTemplateVO template = 
_templateDao.findById(vmInstance.getTemplateId());
-        if (MapUtils.isNotEmpty(details) || cmd.isCleanupDetails()) {
-            if (template != null && template.isDeployAsIs()) {
-                throw new CloudRuntimeException("Detail settings are read from 
OVA, it cannot be changed by API call.");
-            }
-        }
+
         UserVmVO userVm = _vmDao.findById(cmd.getId());
         if (userVm != null && 
UserVmManager.SHAREDFSVM.equals(userVm.getUserVmType())) {
             throw new InvalidParameterValueException("Operation not supported 
on Shared FileSystem Instance");
@@ -2920,6 +2916,9 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                 .collect(Collectors.toList());
         List<VMInstanceDetailVO> existingDetails = 
vmInstanceDetailsDao.listDetails(id);
         if (cleanupDetails){
+            if (template != null && template.isDeployAsIs()) {
+                throw new InvalidParameterValueException("Detail settings are 
read from OVA, it cannot be cleaned up by API call.");
+            }
             if (caller != null && caller.getType() == Account.Type.ADMIN) {
                 for (final VMInstanceDetailVO detail : existingDetails) {
                     if (detail != null && detail.isDisplay() && 
!isExtraConfig(detail.getName())) {
@@ -2948,6 +2947,23 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                     throw new InvalidParameterValueException("'extraconfig' 
should not be included in details as key");
                 }
 
+                if (template != null && template.isDeployAsIs()) {
+                    final List<String> vmwareAllowedDetailsFromOva = 
VmwareAdditionalDetailsFromOvaEnabled.valueIn(vmInstance.getDataCenterId()) ?
+                            
Stream.of(VmwareAllowedAdditionalDetailsFromOva.valueIn(vmInstance.getDataCenterId()).split(","))
+                            .map(String::trim)
+                            .collect(Collectors.toList()) : List.of();
+                    for (String detailKey : details.keySet()) {
+                        if (vmwareAllowedDetailsFromOva.contains(detailKey)) {
+                            continue;
+                        }
+                        VMInstanceDetailVO detailVO = 
existingDetails.stream().filter(d -> Objects.equals(d.getName(), 
detailKey)).findFirst().orElse(null);
+                        if (detailVO != null && 
ObjectUtils.allNotNull(detailVO.getValue(), details.get(detailKey)) && 
detailVO.getValue().equals(details.get(detailKey))) {
+                            continue;
+                        }
+                        throw new InvalidParameterValueException("Detail 
settings are read from OVA, it cannot be changed by API call.");
+                    }
+                }
+
                 details.entrySet().removeIf(detail -> 
isExtraConfig(detail.getKey()));
 
                 if (caller != null && caller.getType() != Account.Type.ADMIN) {
@@ -9342,7 +9358,8 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         return new ConfigKey<?>[] {EnableDynamicallyScaleVm, 
AllowDiskOfferingChangeDuringScaleVm, AllowUserExpungeRecoverVm, 
VmIpFetchWaitInterval, VmIpFetchTrialMax,
                 VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, 
AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties,
                 KvmAdditionalConfigAllowList, 
XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList, 
DestroyRootVolumeOnVmDestruction,
-                EnforceStrictResourceLimitHostTagCheck, StrictHostTags, 
AllowUserForceStopVm, VmDistinctHostNameScope};
+                EnforceStrictResourceLimitHostTagCheck, StrictHostTags, 
AllowUserForceStopVm, VmDistinctHostNameScope,
+                VmwareAdditionalDetailsFromOvaEnabled, 
VmwareAllowedAdditionalDetailsFromOva};
     }
 
     @Override
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 14074add021..34e5e48cc32 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
@@ -22,6 +22,7 @@ import static org.mockito.MockitoAnnotations.openMocks;
 import java.util.Arrays;
 import java.util.EnumSet;
 
+import com.cloud.storage.dao.VMTemplateDao;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ResponseObject;
@@ -78,6 +79,9 @@ public class UserVmJoinDaoImplTest extends 
GenericDaoBaseWithTagInformationBaseT
     @Mock
     private VnfTemplateDetailsDao vnfTemplateDetailsDao;
 
+    @Mock
+    private VMTemplateDao vmTemplateDao;
+
     private UserVmJoinVO userVm = new UserVmJoinVO();
     private UserVmResponse userVmResponse = new UserVmResponse();
 
diff --git a/systemvm/agent/noVNC/core/rfb.js b/systemvm/agent/noVNC/core/rfb.js
index 8faa993d425..59218b136b9 100644
--- a/systemvm/agent/noVNC/core/rfb.js
+++ b/systemvm/agent/noVNC/core/rfb.js
@@ -39,6 +39,7 @@ import ZRLEDecoder from "./decoders/zrle.js";
 import JPEGDecoder from "./decoders/jpeg.js";
 import H264Decoder from "./decoders/h264.js";
 import SCANCODES_JP from "../keymaps/keymap-ja-atset1.js"
+import SCANCODES_ES_LATAM from "../keymaps/keymap-es-latam-atset1.js"
 
 // How many seconds to wait for a disconnect to finish
 const DISCONNECT_TIMEOUT = 3;
@@ -127,6 +128,8 @@ export default class RFB extends EventTargetMixin {
         this._scancodes = {};
         if (this._language === "jp") {
             this._scancodes = SCANCODES_JP;
+        } else if (this._language === "es-latam") {
+            this._scancodes = SCANCODES_ES_LATAM;
         }
 
         // Internal state
@@ -197,6 +200,7 @@ export default class RFB extends EventTargetMixin {
         // Keys
         this._shiftPressed = false;
         this._shiftKey = KeyTable.XK_Shift_L;
+        this._altgrPressed = false;
 
         // Mouse state
         this._mousePos = {};
@@ -531,6 +535,10 @@ export default class RFB extends EventTargetMixin {
             this._shiftKey = down ? keysym : KeyTable.XK_Shift_L;
         }
 
+        if (keysym === KeyTable.XK_Alt_R) {
+            this._altgrPressed = down;
+        }
+
         if (this._qemuExtKeyEventSupported && scancode) {
             // 0 is NoSymbol
             keysym = keysym || 0;
@@ -538,31 +546,10 @@ export default class RFB extends EventTargetMixin {
             Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + 
keysym + ", scancode " + scancode);
 
             RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, 
scancode);
-        } else if (Object.keys(this._scancodes).length > 0) {
-            let vscancode = this._scancodes[keysym]
-            if (vscancode) {
-                let shifted = vscancode.includes("shift");
-                let vscancode_int = parseInt(vscancode);
-                let isLetter = (keysym >= 65 && keysym <=90) || (keysym >=97 
&& keysym <=122);
-                if (shifted && ! this._shiftPressed && ! isLetter) {
-                    RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
-                }
-                if (! shifted && this._shiftPressed && ! isLetter) {
-                    RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
-                }
-                RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, down, 
vscancode_int);
-                if (shifted && ! this._shiftPressed && ! isLetter) {
-                    RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
-                }
-                if (! shifted && this._shiftPressed && ! isLetter) {
-                    RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
-                }
-            } else {
-                if (this._language === "jp" && keysym === 65328) {
-                    keysym = 65509; // Caps lock
-                }
-                RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
-            }
+        } else if (Object.keys(this._scancodes).length > 0 && this._language 
=== "jp") {
+            this.sendKeyWithJapaneseKeyboard(keysym, down)
+        } else if (Object.keys(this._scancodes).length > 0 && this._language 
=== "es-latam") {
+            this.sendKeyWithSpanishLatamKeyboard(keysym, down)
         } else {
             if (!keysym) {
                 return;
@@ -572,6 +559,93 @@ export default class RFB extends EventTargetMixin {
         }
     }
 
+    sendKeyWithJapaneseKeyboard(keysym, down) {
+        let vscancode = this._scancodes[keysym]
+        if (vscancode) {
+            let shifted = vscancode.includes("shift");
+            let vscancode_int = parseInt(vscancode);
+            let isLetter = (keysym >= 65 && keysym <= 90) || (keysym >= 97 && 
keysym <= 122);
+            if (shifted && !this._shiftPressed && !isLetter) {
+                RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
+            }
+            if (!shifted && this._shiftPressed && !isLetter) {
+                RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
+            }
+            RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, down, 
vscancode_int);
+            if (shifted && !this._shiftPressed && !isLetter) {
+                RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
+            }
+            if (!shifted && this._shiftPressed && !isLetter) {
+                RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
+            }
+        } else {
+            if (keysym === 65328) {
+                keysym = 65509; // Caps lock
+            }
+            RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
+        }
+    }
+
+    sendKeyWithSpanishLatamKeyboard(keysym, down) {
+        const VSCODE_ACUTE_LATAM = 26;  // The ASCII code of acute is 180
+        let vscancode = this._scancodes[keysym]
+        if (vscancode) {
+            let shifted = vscancode.includes("shift");
+            let altgr = vscancode.includes("altgr");
+            let acute = vscancode.includes("acute");
+            let vscancode_int = parseInt(vscancode);
+            if (acute) {
+                let shifted_1 = vscancode.includes("shift1");   // Shift with 
Acute accent
+                let shifted_2 = vscancode.includes("shift2");   // Shift with 
a/e/i/o/u
+                if (down) {
+                    if (shifted_1 && ! this._shiftPressed) {
+                        RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
+                    } else if (! shifted_1 && this._shiftPressed) {
+                        RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
+                    }
+                    RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, 1, 
VSCODE_ACUTE_LATAM);
+                    RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, 0, 
VSCODE_ACUTE_LATAM);
+                    if (shifted_2) {
+                        RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
+                    } else {
+                        RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
+                    }
+                } else {
+                    RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, 0, 
VSCODE_ACUTE_LATAM);
+                    if (shifted_2 && ! this._shiftPressed) {
+                        RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
+                    } else if (! shifted_2 && this._shiftPressed) {
+                        RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
+                    }
+                }
+                RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, down, 
vscancode_int);
+                return;
+            }
+            let isLetter = (keysym >= 65 && keysym <= 90) || (keysym >= 97 && 
keysym <= 122);
+            if (shifted && !this._shiftPressed && !isLetter && down) {
+                RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
+            }
+            if (!shifted && this._shiftPressed && !isLetter && down) {
+                RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
+            }
+            if (altgr && !this._altgrPressed && down) {
+                RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_R, 1);
+            }
+            RFB.messages.VMwareExtendedKeyEvent(this._sock, keysym, down, 
vscancode_int);
+            if (altgr && !this._altgrPressed && !down) {
+                RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_R, 0);
+            }
+            if (shifted && !this._shiftPressed && !isLetter && !down) {
+                RFB.messages.keyEvent(this._sock, this._shiftKey, 0);
+            }
+            if (!shifted && this._shiftPressed && !isLetter && !down) {
+                RFB.messages.keyEvent(this._sock, this._shiftKey, 1);
+            }
+        } else {
+            RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
+        }
+    }
+
     focus(options) {
         this._canvas.focus(options);
     }
diff --git a/systemvm/agent/noVNC/keymaps/generate-language-keymaps.py 
b/systemvm/agent/noVNC/keymaps/generate-language-keymaps.py
index 4a88a05ef0d..604018c69a2 100755
--- a/systemvm/agent/noVNC/keymaps/generate-language-keymaps.py
+++ b/systemvm/agent/noVNC/keymaps/generate-language-keymaps.py
@@ -2,7 +2,7 @@
 
 # This script
 # (1) loads keysym name and keycode mappings from noVNC/core/input/keysym.js 
and
-# (2) loads keysyn name to atset1 code mappings from keymap files which can be 
downloadeded from https://github.com/qemu/qemu/blob/master/pc-bios/keymaps
+# (2) loads keysym name to atset1 code mappings from keymap files which can be 
downloadeded from https://github.com/qemu/qemu/blob/master/pc-bios/keymaps
 # (3) generates the mappings of keycode and atset1 code
 #
 # Note: please add language specific mappings if needed.
@@ -96,7 +96,10 @@ def generate_js_file(keymap_file):
     js_config.append(" */\n")
     js_config.append("export default {\n")
     for keycode in dict(sorted(list(result_mappings.items()), key=lambda item: 
int(item[0]))):
-        js_config.append("%10s : \"%s\",\n" % ("\"" + str(keycode) + "\"", 
result_mappings[keycode].strip()))
+        if keycode not in list(keycode_to_x11name.keys()):
+            js_config.append("%10s : \"%s\",\n" % ("\"" + str(keycode) + "\"", 
result_mappings[keycode].strip()))
+        else:
+            js_config.append("%10s : \"%s\",         // %s\n" % ("\"" + 
str(keycode) + "\"", result_mappings[keycode].strip(), 
keycode_to_x11name[keycode]))
     js_config.append("}\n")
     for line in js_config:
         handle.write(line)
diff --git a/systemvm/agent/noVNC/keymaps/keymap-es-latam-atset1.js 
b/systemvm/agent/noVNC/keymaps/keymap-es-latam-atset1.js
new file mode 100644
index 00000000000..91ef42642c4
--- /dev/null
+++ b/systemvm/agent/noVNC/keymaps/keymap-es-latam-atset1.js
@@ -0,0 +1,131 @@
+/* This file is auto-generated by generate-language-keymaps.py
+ * command    : generate-language-keymaps.py keymap-es
+ * layout     : es-latam
+ */
+export default {
+      "32" : "57",         // XK_space
+      "33" : "2          shift",         // XK_exclam
+      "34" : "3          shift",         // XK_quotedbl
+      "35" : "4          shift",         // XK_numbersign
+      "36" : "5          shift",         // XK_dollar
+      "37" : "6          shift",         // XK_percent
+      "38" : "7          shift",         // XK_ampersand
+      "39" : "12",         // XK_apostrophe
+      "40" : "9          shift",         // XK_parenleft
+      "41" : "10         shift",         // XK_parenright
+      "42" : "27         shift",         // XK_asterisk
+      "43" : "27",         // XK_plus
+      "44" : "51",         // XK_comma
+      "45" : "53",         // XK_minus
+      "46" : "52",         // XK_period
+      "47" : "8          shift",         // XK_slash
+      "48" : "11",         // XK_0
+      "49" : "2",         // XK_1
+      "50" : "3",         // XK_2
+      "51" : "4",         // XK_3
+      "52" : "5",         // XK_4
+      "53" : "6",         // XK_5
+      "54" : "7",         // XK_6
+      "55" : "8",         // XK_7
+      "56" : "9",         // XK_8
+      "57" : "10",         // XK_9
+      "58" : "52         shift",         // XK_colon
+      "59" : "51         shift",         // XK_semicolon
+      "60" : "86",         // XK_less
+      "61" : "11         shift",         // XK_equal
+      "62" : "86         shift",         // XK_greater
+      "63" : "12         shift",         // XK_question
+      "64" : "16         altgr",         // XK_at
+      "65" : "30         shift",         // XK_A
+      "66" : "48         shift",         // XK_B
+      "67" : "46         shift",         // XK_C
+      "68" : "32         shift",         // XK_D
+      "69" : "18         shift",         // XK_E
+      "70" : "33         shift",         // XK_F
+      "71" : "34         shift",         // XK_G
+      "72" : "35         shift",         // XK_H
+      "73" : "23         shift",         // XK_I
+      "74" : "36         shift",         // XK_J
+      "75" : "37         shift",         // XK_K
+      "76" : "38         shift",         // XK_L
+      "77" : "50         shift",         // XK_M
+      "78" : "49         shift",         // XK_N
+      "79" : "24         shift",         // XK_O
+      "80" : "25         shift",         // XK_P
+      "81" : "16         shift",         // XK_Q
+      "82" : "19         shift",         // XK_R
+      "83" : "31         shift",         // XK_S
+      "84" : "20         shift",         // XK_T
+      "85" : "22         shift",         // XK_U
+      "86" : "47         shift",         // XK_V
+      "87" : "17         shift",         // XK_W
+      "88" : "45         shift",         // XK_X
+      "89" : "21         shift",         // XK_Y
+      "90" : "44         shift",         // XK_Z
+      "91" : "40         shift",         // XK_bracketleft
+      "92" : "12         altgr",         // XK_backslash
+      "93" : "43         shift",         // XK_bracketright
+      "94":  "40         altgr",         // ^
+      "95" : "53         shift",         // XK_underscore
+      "96":  "43         altgr",         // `
+      "97" : "30",         // XK_a
+      "98" : "48",         // XK_b
+      "99" : "46",         // XK_c
+     "100" : "32",         // XK_d
+     "101" : "18",         // XK_e
+     "102" : "33",         // XK_f
+     "103" : "34",         // XK_g
+     "104" : "35",         // XK_h
+     "105" : "23",         // XK_i
+     "106" : "36",         // XK_j
+     "107" : "37",         // XK_k
+     "108" : "38",         // XK_l
+     "109" : "50",         // XK_m
+     "110" : "49",         // XK_n
+     "111" : "24",         // XK_o
+     "112" : "25",         // XK_p
+     "113" : "16",         // XK_q
+     "114" : "19",         // XK_r
+     "115" : "31",         // XK_s
+     "116" : "20",         // XK_t
+     "117" : "22",         // XK_u
+     "118" : "47",         // XK_v
+     "119" : "17",         // XK_w
+     "120" : "45",         // XK_x
+     "121" : "21",         // XK_y
+     "122" : "44",         // XK_z
+     "123" : "40",         // XK_braceleft
+     "124" : "41",         // XK_bar
+     "125" : "43",         // XK_braceright
+     "126" : "27         altgr",         // XK_asciitilde
+     "161" : "13         shift",         // XK_exclamdown
+     "168" : "26         shift",         // ¨
+     "171" : "44         altgr",         // XK_guillemotleft
+     "172" : "41         altgr",         // XK_notsign
+     "176" : "41         shift",         // XK_degree
+     "180" : "26",         // ´
+     "186" : "41",         // XK_masculine
+     "191" : "13",         // XK_questiondown
+     "193" : "30         acute shift2",         // Á
+     "196" : "30         shift1 acute shift2",         // Ä
+     "201" : "18         acute shift2",         // É
+     "203" : "18         shift1 acute shift2",         // Ë
+     "205" : "23         acute shift2",         // Í
+     "207" : "23         shift1 acute shift2",         // Ï
+     "209" : "39         shift",         // XK_Ntilde
+     "211" : "24         acute shift2",         // Ó
+     "214" : "24         shift1 acute shift2",         // Ö
+     "218" : "22         acute shift2",         // Ú
+     "220" : "22         shift1 acute shift2",         // Ü
+     "225" : "30         acute",         // á
+     "228" : "30         shift1 acute",         // ä
+     "233" : "18         acute",         // é
+     "235" : "18         shift1 acute",         // ë
+     "237" : "23         acute",         // í
+     "239" : "23         shift1 acute",         // ï
+     "241" : "39",         // XK_ntilde
+     "243" : "24         acute",         // ó
+     "246" : "24         shift1 acute",         // ö
+     "250" : "22         acute",         // ú
+     "252" : "22         shift1 acute",         // ü
+}
diff --git a/ui/public/config.json b/ui/public/config.json
index de3a3c39952..a067803ba7a 100644
--- a/ui/public/config.json
+++ b/ui/public/config.json
@@ -61,7 +61,8 @@
     "uk": "label.uk.keyboard",
     "fr": "label.french.azerty.keyboard",
     "jp": "label.japanese.keyboard",
-    "sc": "label.simplified.chinese.keyboard"
+    "sc": "label.simplified.chinese.keyboard",
+    "es-latam": "Spanish Latin American Keyboard"
   },
   "userCard": {
     "enabled": true,
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 0a9539abff3..300b8b3f9f0 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -19,6 +19,7 @@
 "error.release.dedicate.pod": "Failed to release dedicated Pod.",
 "error.release.dedicate.zone": "Failed to release dedicated Zone.",
 "error.unable.to.add.setting.extraconfig": "It is not allowed to add setting 
for extraconfig. Please update VirtualMachine with extraconfig parameter.",
+"error.unable.to.add.setting": "Unable to add or edit setting",
 "error.unable.to.proceed": "Unable to proceed. Please contact your 
administrator.",
 "firewall.close": "Firewall",
 "icmp.code.desc": "Please specify -1 if you want to allow all ICMP codes 
(except NSX zones).",
@@ -3401,6 +3402,7 @@
 "message.error.delete.tungsten.tag": "Removing Tag failed",
 "message.error.description": "Please enter description.",
 "message.error.discovering.feature": "Exception caught while discovering 
features.",
+"message.error.setting.deployasistemplate": "Settings are read directly from 
the template",
 "message.error.setup.2fa": "2FA setup failed while verifying the code, please 
retry.",
 "message.error.verifying.2fa": "Unable to verify 2FA, please retry.",
 "message.error.display.text": "Please enter display text.",
diff --git a/ui/src/components/view/DetailSettings.vue 
b/ui/src/components/view/DetailSettings.vue
index 987a8ac4213..fc3b4257311 100644
--- a/ui/src/components/view/DetailSettings.vue
+++ b/ui/src/components/view/DetailSettings.vue
@@ -100,7 +100,7 @@
             <tooltip-button
               :tooltip="$t('label.edit')"
               icon="edit-outlined"
-              :disabled="deployasistemplate === true || 
item.name.startsWith('extraconfig')"
+              :disabled="item.name.startsWith('extraconfig')"
               v-if="!item.edit"
               @onClick="showEditDetail(index)" />
           </div>
@@ -115,7 +115,7 @@
             >
               <tooltip-button
                 :tooltip="$t('label.delete')"
-                :disabled="deployasistemplate === true || 
item.name.startsWith('extraconfig')"
+                :disabled="item.name.startsWith('extraconfig')"
                 type="primary"
                 :danger="true"
                 icon="delete-outlined" />
@@ -213,11 +213,16 @@ export default {
         this.detailOptions = 
json.listdetailoptionsresponse.detailoptions.details
       })
       this.disableSettings = (this.$route.meta.name === 'vm' && resource.state 
!== 'Stopped')
-      getAPI('listTemplates', { templatefilter: 'all', id: resource.templateid 
}).then(json => {
-        this.deployasistemplate = 
json.listtemplatesresponse.template[0].deployasis
-      })
+      if (this.$route.meta.name === 'vm') {
+        getAPI('listTemplates', { templatefilter: 'all', id: 
resource.templateid }).then(json => {
+          this.deployasistemplate = 
json.listtemplatesresponse.template[0].deployasis
+        })
+      }
     },
     allowEditOfDetail (name) {
+      if (this.deployasistemplate) {
+        return this.resource.alloweddetails && 
this.resource.alloweddetails.split(',').map(item => item.trim()).includes(name)
+      }
       if (this.resource.readonlydetails) {
         if (this.resource.readonlydetails.split(',').map(item => 
item.trim()).includes(name)) {
           return false
@@ -320,7 +325,11 @@ export default {
         return
       }
       if (!this.allowEditOfDetail(this.newKey)) {
-        this.error = this.$t('error.unable.to.proceed')
+        if (this.deployasistemplate) {
+          this.error = this.$t('error.unable.to.add.setting') + ' : ' + 
this.newKey + '. ' + this.$t('message.error.setting.deployasistemplate')
+        } else {
+          this.error = this.$t('error.unable.to.add.setting') + ' : ' + 
this.newKey
+        }
         return
       }
       this.error = false

Reply via email to