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