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
This commit is contained in:
Wei Zhou 2026-02-02 10:46:54 +01:00 committed by GitHub
parent 30d306622a
commit b869913529
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 312 additions and 42 deletions

View File

@ -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")

View File

@ -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;
}

View File

@ -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());

View File

@ -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);

View File

@ -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";

View File

@ -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

View File

@ -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();

View File

@ -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);
}

View File

@ -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)

View File

@ -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", // ü
}

View File

@ -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,

View File

@ -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.",

View File

@ -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