Thomas Lamprecht 788588cb86 utils: enable ARIA again
We had done so already in PBS and PMG since a bit without seeing
anything broken due to it, which makes sense as this was enabled for
*during* the step-by-step upgrade from ExtJS 4 to 5 (and then
directly 6).

Re-enabling  could help with some accessibility issues we have some
tangential reports[0].

[0]: https://github.com/nvaccess/nvda/issues/11530

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2021-07-04 18:23:47 +02:00

1857 lines
46 KiB
JavaScript

Ext.ns('PVE');
console.log("Starting Proxmox VE Manager");
Ext.Ajax.defaultHeaders = {
'Accept': 'application/json',
};
Ext.define('PVE.Utils', {
utilities: {
// this singleton contains miscellaneous utilities
toolkit: undefined, // (extjs|touch), set inside Toolkit.js
bus_match: /^(ide|sata|virtio|scsi)\d+$/,
log_severity_hash: {
0: "panic",
1: "alert",
2: "critical",
3: "error",
4: "warning",
5: "notice",
6: "info",
7: "debug",
},
support_level_hash: {
'c': gettext('Community'),
'b': gettext('Basic'),
's': gettext('Standard'),
'p': gettext('Premium'),
},
noSubKeyHtml: 'You do not have a valid subscription for this server. Please visit '
+'<a target="_blank" href="https://www.proxmox.com/products/proxmox-ve/subscription-service-plans">'
+'www.proxmox.com</a> to get a list of available options.',
kvm_ostypes: {
'Linux': [
{ desc: '5.x - 2.6 Kernel', val: 'l26' },
{ desc: '2.4 Kernel', val: 'l24' },
],
'Microsoft Windows': [
{ desc: '10/2016/2019', val: 'win10' },
{ desc: '8.x/2012/2012r2', val: 'win8' },
{ desc: '7/2008r2', val: 'win7' },
{ desc: 'Vista/2008', val: 'w2k8' },
{ desc: 'XP/2003', val: 'wxp' },
{ desc: '2000', val: 'w2k' },
],
'Solaris Kernel': [
{ desc: '-', val: 'solaris' },
],
'Other': [
{ desc: '-', val: 'other' },
],
},
is_windows: function(ostype) {
for (let entry of PVE.Utils.kvm_ostypes['Microsoft Windows']) {
if (entry.val === ostype) {
return true;
}
}
return false;
},
get_health_icon: function(state, circle) {
if (circle === undefined) {
circle = false;
}
if (state === undefined) {
state = 'uknown';
}
var icon = 'faded fa-question';
switch (state) {
case 'good':
icon = 'good fa-check';
break;
case 'upgrade':
icon = 'warning fa-upload';
break;
case 'old':
icon = 'warning fa-refresh';
break;
case 'warning':
icon = 'warning fa-exclamation';
break;
case 'critical':
icon = 'critical fa-times';
break;
default: break;
}
if (circle) {
icon += '-circle';
}
return icon;
},
parse_ceph_version: function(service) {
if (service.ceph_version_short) {
return service.ceph_version_short;
}
if (service.ceph_version) {
var match = service.ceph_version.match(/version (\d+(\.\d+)*)/);
if (match) {
return match[1];
}
}
return undefined;
},
compare_ceph_versions: function(a, b) {
let avers = [];
let bvers = [];
if (a === b) {
return 0;
}
if (Ext.isArray(a)) {
avers = a.slice(); // copy array
} else {
avers = a.toString().split('.');
}
if (Ext.isArray(b)) {
bvers = b.slice(); // copy array
} else {
bvers = b.toString().split('.');
}
for (;;) {
let av = avers.shift();
let bv = bvers.shift();
if (av === undefined && bv === undefined) {
return 0;
} else if (av === undefined) {
return -1;
} else if (bv === undefined) {
return 1;
} else {
let diff = parseInt(av, 10) - parseInt(bv, 10);
if (diff !== 0) return diff;
// else we need to look at the next parts
}
}
},
get_ceph_icon_html: function(health, fw) {
var state = PVE.Utils.map_ceph_health[health];
var cls = PVE.Utils.get_health_icon(state);
if (fw) {
cls += ' fa-fw';
}
return "<i class='fa " + cls + "'></i> ";
},
map_ceph_health: {
'HEALTH_OK': 'good',
'HEALTH_UPGRADE': 'upgrade',
'HEALTH_OLD': 'old',
'HEALTH_WARN': 'warning',
'HEALTH_ERR': 'critical',
},
render_sdn_pending: function(rec, value, key, index) {
if (rec.data.state === undefined || rec.data.state === null) {
return value;
}
if (rec.data.state === 'deleted') {
if (value === undefined) {
return ' ';
} else {
return '<div style="text-decoration: line-through;">'+ value +'</div>';
}
} else if (rec.data.pending[key] !== undefined && rec.data.pending[key] !== null) {
if (rec.data.pending[key] === 'deleted') {
return ' ';
} else {
return rec.data.pending[key];
}
}
return value;
},
render_sdn_pending_state: function(rec, value) {
if (value === undefined || value === null) {
return ' ';
}
let icon = `<i class="fa fa-fw fa-refresh warning"></i>`;
if (value === 'deleted') {
return '<span>' + icon + value + '</span>';
}
let tip = gettext('Pending Changes') + ': <br>';
for (const [key, keyvalue] of Object.entries(rec.data.pending)) {
if ((rec.data[key] !== undefined && rec.data.pending[key] !== rec.data[key]) ||
rec.data[key] === undefined
) {
tip += `${key}: ${keyvalue} <br>`;
}
}
return '<span data-qtip="' + tip + '">'+ icon + value + '</span>';
},
render_ceph_health: function(healthObj) {
var state = {
iconCls: PVE.Utils.get_health_icon(),
text: '',
};
if (!healthObj || !healthObj.status) {
return state;
}
var health = PVE.Utils.map_ceph_health[healthObj.status];
state.iconCls = PVE.Utils.get_health_icon(health, true);
state.text = healthObj.status;
return state;
},
render_zfs_health: function(value) {
if (typeof value === 'undefined') {
return "";
}
var iconCls = 'question-circle';
switch (value) {
case 'AVAIL':
case 'ONLINE':
iconCls = 'check-circle good';
break;
case 'REMOVED':
case 'DEGRADED':
iconCls = 'exclamation-circle warning';
break;
case 'UNAVAIL':
case 'FAULTED':
case 'OFFLINE':
iconCls = 'times-circle critical';
break;
default: //unknown
}
return '<i class="fa fa-' + iconCls + '"></i> ' + value;
},
render_pbs_fingerprint: fp => fp.substring(0, 23),
render_backup_encryption: function(v, meta, record) {
if (!v) {
return gettext('No');
}
let tip = '';
if (v.match(/^[a-fA-F0-9]{2}:/)) { // fingerprint
tip = `Key fingerprint ${PVE.Utils.render_pbs_fingerprint(v)}`;
}
let icon = `<i class="fa fa-fw fa-lock good"></i>`;
return `<span data-qtip="${tip}">${icon} ${gettext('Encrypted')}</span>`;
},
render_backup_verification: function(v, meta, record) {
let i = (cls, txt) => `<i class="fa fa-fw fa-${cls}"></i> ${txt}`;
if (v === undefined || v === null) {
return i('question-circle-o warning', gettext('None'));
}
let tip = "";
let txt = gettext('Failed');
let iconCls = 'times critical';
if (v.state === 'ok') {
txt = gettext('OK');
iconCls = 'check good';
let now = Date.now() / 1000;
let task = Proxmox.Utils.parse_task_upid(v.upid);
let verify_time = Proxmox.Utils.render_timestamp(task.starttime);
tip = `Last verify task started on ${verify_time}`;
if (now - v.starttime > 30 * 24 * 60 * 60) {
tip = `Last verify task over 30 days ago: ${verify_time}`;
iconCls = 'check warning';
}
}
return `<span data-qtip="${tip}"> ${i(iconCls, txt)} </span>`;
},
render_backup_status: function(value, meta, record) {
if (typeof value === 'undefined') {
return "";
}
let iconCls = 'check-circle good';
let text = gettext('Yes');
if (!PVE.Parser.parseBoolean(value.toString())) {
iconCls = 'times-circle critical';
text = gettext('No');
let reason = record.get('reason');
if (typeof reason !== 'undefined') {
if (reason in PVE.Utils.backup_reasons_table) {
reason = PVE.Utils.backup_reasons_table[record.get('reason')];
}
text = `${text} - ${reason}`;
}
}
return `<i class="fa fa-${iconCls}"></i> ${text}`;
},
render_backup_days_of_week: function(val) {
var dows = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
var selected = [];
var cur = -1;
val.split(',').forEach(function(day) {
cur++;
var dow = (dows.indexOf(day)+6)%7;
if (cur === dow) {
if (selected.length === 0 || selected[selected.length-1] === 0) {
selected.push(1);
} else {
selected[selected.length-1]++;
}
} else {
while (cur < dow) {
cur++;
selected.push(0);
}
selected.push(1);
}
});
cur = -1;
var days = [];
selected.forEach(function(item) {
cur++;
if (item > 2) {
days.push(Ext.Date.dayNames[cur+1] + '-' + Ext.Date.dayNames[(cur+item)%7]);
cur += item-1;
} else if (item === 2) {
days.push(Ext.Date.dayNames[cur+1]);
days.push(Ext.Date.dayNames[(cur+2)%7]);
cur++;
} else if (item === 1) {
days.push(Ext.Date.dayNames[(cur+1)%7]);
}
});
return days.join(', ');
},
render_backup_selection: function(value, metaData, record) {
let allExceptText = gettext('All except {0}');
let allText = '-- ' + gettext('All') + ' --';
if (record.data.all) {
if (record.data.exclude) {
return Ext.String.format(allExceptText, record.data.exclude);
}
return allText;
}
if (record.data.vmid) {
return record.data.vmid;
}
if (record.data.pool) {
return "Pool '"+ record.data.pool + "'";
}
return "-";
},
backup_reasons_table: {
'backup=yes': gettext('Enabled'),
'backup=no': gettext('Disabled'),
'enabled': gettext('Enabled'),
'disabled': gettext('Disabled'),
'not a volume': gettext('Not a volume'),
'efidisk but no OMVF BIOS': gettext('EFI Disk without OMVF BIOS'),
},
get_kvm_osinfo: function(value) {
var info = { base: 'Other' }; // default
if (value) {
Ext.each(Object.keys(PVE.Utils.kvm_ostypes), function(k) {
Ext.each(PVE.Utils.kvm_ostypes[k], function(e) {
if (e.val === value) {
info = { desc: e.desc, base: k };
}
});
});
}
return info;
},
render_kvm_ostype: function(value) {
var osinfo = PVE.Utils.get_kvm_osinfo(value);
if (osinfo.desc && osinfo.desc !== '-') {
return osinfo.base + ' ' + osinfo.desc;
} else {
return osinfo.base;
}
},
render_hotplug_features: function(value) {
var fa = [];
if (!value || value === '0') {
return gettext('Disabled');
}
if (value === '1') {
value = 'disk,network,usb';
}
Ext.each(value.split(','), function(el) {
if (el === 'disk') {
fa.push(gettext('Disk'));
} else if (el === 'network') {
fa.push(gettext('Network'));
} else if (el === 'usb') {
fa.push('USB');
} else if (el === 'memory') {
fa.push(gettext('Memory'));
} else if (el === 'cpu') {
fa.push(gettext('CPU'));
} else {
fa.push(el);
}
});
return fa.join(', ');
},
render_localtime: function(value) {
if (value === '__default__') {
return Proxmox.Utils.defaultText + ' (' + gettext('Enabled for Windows') + ')';
}
return Proxmox.Utils.format_boolean(value);
},
render_qga_features: function(config) {
if (!config) {
return Proxmox.Utils.defaultText + ' (' + Proxmox.Utils.disabledText + ')';
}
let qga = PVE.Parser.parsePropertyString(config, 'enabled');
if (!PVE.Parser.parseBoolean(qga.enabled)) {
return Proxmox.Utils.disabledText;
}
delete qga.enabled;
let agentstring = Proxmox.Utils.enabledText;
for (const [key, value] of Object.entries(qga)) {
let displayText = Proxmox.Utils.disabledText;
if (key === 'type') {
let map = {
isa: "ISA",
virtio: "VirtIO",
};
displayText = map[value] || Proxmox.Utils.unknownText;
} else if (PVE.Parser.parseBoolean(value)) {
displayText = Proxmox.Utils.enabledText;
}
agentstring += `, ${key}: ${displayText}`;
}
return agentstring;
},
render_qemu_machine: function(value) {
return value || Proxmox.Utils.defaultText + ' (i440fx)';
},
render_qemu_bios: function(value) {
if (!value) {
return Proxmox.Utils.defaultText + ' (SeaBIOS)';
} else if (value === 'seabios') {
return "SeaBIOS";
} else if (value === 'ovmf') {
return "OVMF (UEFI)";
} else {
return value;
}
},
render_dc_ha_opts: function(value) {
if (!value) {
return Proxmox.Utils.defaultText;
} else {
return PVE.Parser.printPropertyString(value);
}
},
render_as_property_string: function(value) {
return !value ? Proxmox.Utils.defaultText
: PVE.Parser.printPropertyString(value);
},
render_scsihw: function(value) {
if (!value) {
return Proxmox.Utils.defaultText + ' (LSI 53C895A)';
} else if (value === 'lsi') {
return 'LSI 53C895A';
} else if (value === 'lsi53c810') {
return 'LSI 53C810';
} else if (value === 'megasas') {
return 'MegaRAID SAS 8708EM2';
} else if (value === 'virtio-scsi-pci') {
return 'VirtIO SCSI';
} else if (value === 'virtio-scsi-single') {
return 'VirtIO SCSI single';
} else if (value === 'pvscsi') {
return 'VMware PVSCSI';
} else {
return value;
}
},
render_spice_enhancements: function(values) {
let props = PVE.Parser.parsePropertyString(values);
if (Ext.Object.isEmpty(props)) {
return Proxmox.Utils.noneText;
}
let output = [];
if (PVE.Parser.parseBoolean(props.foldersharing)) {
output.push('Folder Sharing: ' + gettext('Enabled'));
}
if (props.videostreaming === 'all' || props.videostreaming === 'filter') {
output.push('Video Streaming: ' + props.videostreaming);
}
return output.join(', ');
},
// fixme: auto-generate this
// for now, please keep in sync with PVE::Tools::kvmkeymaps
kvm_keymaps: {
//ar: 'Arabic',
da: 'Danish',
de: 'German',
'de-ch': 'German (Swiss)',
'en-gb': 'English (UK)',
'en-us': 'English (USA)',
es: 'Spanish',
//et: 'Estonia',
fi: 'Finnish',
//fo: 'Faroe Islands',
fr: 'French',
'fr-be': 'French (Belgium)',
'fr-ca': 'French (Canada)',
'fr-ch': 'French (Swiss)',
//hr: 'Croatia',
hu: 'Hungarian',
is: 'Icelandic',
it: 'Italian',
ja: 'Japanese',
lt: 'Lithuanian',
//lv: 'Latvian',
mk: 'Macedonian',
nl: 'Dutch',
//'nl-be': 'Dutch (Belgium)',
no: 'Norwegian',
pl: 'Polish',
pt: 'Portuguese',
'pt-br': 'Portuguese (Brazil)',
//ru: 'Russian',
sl: 'Slovenian',
sv: 'Swedish',
//th: 'Thai',
tr: 'Turkish',
},
kvm_vga_drivers: {
std: gettext('Standard VGA'),
vmware: gettext('VMware compatible'),
qxl: 'SPICE',
qxl2: 'SPICE dual monitor',
qxl3: 'SPICE three monitors',
qxl4: 'SPICE four monitors',
serial0: gettext('Serial terminal') + ' 0',
serial1: gettext('Serial terminal') + ' 1',
serial2: gettext('Serial terminal') + ' 2',
serial3: gettext('Serial terminal') + ' 3',
virtio: 'VirtIO-GPU',
none: Proxmox.Utils.noneText,
},
render_kvm_language: function(value) {
if (!value || value === '__default__') {
return Proxmox.Utils.defaultText;
}
var text = PVE.Utils.kvm_keymaps[value];
if (text) {
return text + ' (' + value + ')';
}
return value;
},
kvm_keymap_array: function() {
var data = [['__default__', PVE.Utils.render_kvm_language('')]];
Ext.Object.each(PVE.Utils.kvm_keymaps, function(key, value) {
data.push([key, PVE.Utils.render_kvm_language(value)]);
});
return data;
},
console_map: {
'__default__': Proxmox.Utils.defaultText + ' (xterm.js)',
'vv': 'SPICE (remote-viewer)',
'html5': 'HTML5 (noVNC)',
'xtermjs': 'xterm.js',
},
render_console_viewer: function(value) {
value = value || '__default__';
if (PVE.Utils.console_map[value]) {
return PVE.Utils.console_map[value];
}
return value;
},
console_viewer_array: function() {
return Ext.Array.map(Object.keys(PVE.Utils.console_map), function(v) {
return [v, PVE.Utils.render_console_viewer(v)];
});
},
render_kvm_vga_driver: function(value) {
if (!value) {
return Proxmox.Utils.defaultText;
}
var vga = PVE.Parser.parsePropertyString(value, 'type');
var text = PVE.Utils.kvm_vga_drivers[vga.type];
if (!vga.type) {
text = Proxmox.Utils.defaultText;
}
if (text) {
return text + ' (' + value + ')';
}
return value;
},
kvm_vga_driver_array: function() {
var data = [['__default__', PVE.Utils.render_kvm_vga_driver('')]];
Ext.Object.each(PVE.Utils.kvm_vga_drivers, function(key, value) {
data.push([key, PVE.Utils.render_kvm_vga_driver(value)]);
});
return data;
},
render_kvm_startup: function(value) {
var startup = PVE.Parser.parseStartup(value);
var res = 'order=';
if (startup.order === undefined) {
res += 'any';
} else {
res += startup.order;
}
if (startup.up !== undefined) {
res += ',up=' + startup.up;
}
if (startup.down !== undefined) {
res += ',down=' + startup.down;
}
return res;
},
extractFormActionError: function(action) {
var msg;
switch (action.failureType) {
case Ext.form.action.Action.CLIENT_INVALID:
msg = gettext('Form fields may not be submitted with invalid values');
break;
case Ext.form.action.Action.CONNECT_FAILURE:
msg = gettext('Connection error');
var resp = action.response;
if (resp.status && resp.statusText) {
msg += " " + resp.status + ": " + resp.statusText;
}
break;
case Ext.form.action.Action.LOAD_FAILURE:
case Ext.form.action.Action.SERVER_INVALID:
msg = Proxmox.Utils.extractRequestError(action.result, true);
break;
}
return msg;
},
contentTypes: {
'images': gettext('Disk image'),
'backup': gettext('VZDump backup file'),
'vztmpl': gettext('Container template'),
'iso': gettext('ISO image'),
'rootdir': gettext('Container'),
'snippets': gettext('Snippets'),
},
volume_is_qemu_backup: function(volid, format) {
return format === 'pbs-vm' || volid.match(':backup/vzdump-qemu-');
},
volume_is_lxc_backup: function(volid, format) {
return format === 'pbs-ct' || volid.match(':backup/vzdump-(lxc|openvz)-');
},
authSchema: {
ad: {
name: gettext('Active Directory Server'),
ipanel: 'pveAuthADPanel',
syncipanel: 'pveAuthLDAPSyncPanel',
add: true,
tfa: true,
pwchange: true,
},
ldap: {
name: gettext('LDAP Server'),
ipanel: 'pveAuthLDAPPanel',
syncipanel: 'pveAuthLDAPSyncPanel',
add: true,
tfa: true,
pwchange: true,
},
openid: {
name: gettext('OpenID Connect Server'),
ipanel: 'pveAuthOpenIDPanel',
add: true,
tfa: false,
pwchange: false,
iconCls: 'pmx-itype-icon-openid-logo',
},
pam: {
name: 'Linux PAM',
ipanel: 'pveAuthBasePanel',
add: false,
tfa: true,
pwchange: true,
},
pve: {
name: 'Proxmox VE authentication server',
ipanel: 'pveAuthBasePanel',
add: false,
pwchange: true,
},
},
storageSchema: {
dir: {
name: Proxmox.Utils.directoryText,
ipanel: 'DirInputPanel',
faIcon: 'folder',
backups: true,
},
lvm: {
name: 'LVM',
ipanel: 'LVMInputPanel',
faIcon: 'folder',
backups: false,
},
lvmthin: {
name: 'LVM-Thin',
ipanel: 'LvmThinInputPanel',
faIcon: 'folder',
backups: false,
},
btrfs: {
name: 'BTRFS',
ipanel: 'BTRFSInputPanel',
faIcon: 'folder',
backups: true,
},
nfs: {
name: 'NFS',
ipanel: 'NFSInputPanel',
faIcon: 'building',
backups: true,
},
cifs: {
name: 'CIFS',
ipanel: 'CIFSInputPanel',
faIcon: 'building',
backups: true,
},
glusterfs: {
name: 'GlusterFS',
ipanel: 'GlusterFsInputPanel',
faIcon: 'building',
backups: true,
},
iscsi: {
name: 'iSCSI',
ipanel: 'IScsiInputPanel',
faIcon: 'building',
backups: false,
},
cephfs: {
name: 'CephFS',
ipanel: 'CephFSInputPanel',
faIcon: 'building',
backups: true,
},
pvecephfs: {
name: 'CephFS (PVE)',
ipanel: 'CephFSInputPanel',
hideAdd: true,
faIcon: 'building',
backups: true,
},
rbd: {
name: 'RBD',
ipanel: 'RBDInputPanel',
faIcon: 'building',
backups: false,
},
pveceph: {
name: 'RBD (PVE)',
ipanel: 'RBDInputPanel',
hideAdd: true,
faIcon: 'building',
backups: false,
},
zfs: {
name: 'ZFS over iSCSI',
ipanel: 'ZFSInputPanel',
faIcon: 'building',
backups: false,
},
zfspool: {
name: 'ZFS',
ipanel: 'ZFSPoolInputPanel',
faIcon: 'folder',
backups: false,
},
pbs: {
name: 'Proxmox Backup Server',
ipanel: 'PBSInputPanel',
faIcon: 'floppy-o',
backups: true,
},
drbd: {
name: 'DRBD',
hideAdd: true,
backups: false,
},
},
sdnvnetSchema: {
vnet: {
name: 'vnet',
faIcon: 'folder',
},
},
sdnzoneSchema: {
zone: {
name: 'zone',
hideAdd: true,
},
simple: {
name: 'Simple',
ipanel: 'SimpleInputPanel',
faIcon: 'th',
},
vlan: {
name: 'VLAN',
ipanel: 'VlanInputPanel',
faIcon: 'th',
},
qinq: {
name: 'QinQ',
ipanel: 'QinQInputPanel',
faIcon: 'th',
},
vxlan: {
name: 'VXLAN',
ipanel: 'VxlanInputPanel',
faIcon: 'th',
},
evpn: {
name: 'EVPN',
ipanel: 'EvpnInputPanel',
faIcon: 'th',
},
},
sdncontrollerSchema: {
controller: {
name: 'controller',
hideAdd: true,
},
evpn: {
name: 'evpn',
ipanel: 'EvpnInputPanel',
faIcon: 'crosshairs',
},
bgp: {
name: 'bgp',
ipanel: 'BgpInputPanel',
faIcon: 'crosshairs',
},
},
sdnipamSchema: {
ipam: {
name: 'ipam',
hideAdd: true,
},
pve: {
name: 'PVE',
ipanel: 'PVEIpamInputPanel',
faIcon: 'th',
hideAdd: true,
},
netbox: {
name: 'Netbox',
ipanel: 'NetboxInputPanel',
faIcon: 'th',
},
phpipam: {
name: 'PhpIpam',
ipanel: 'PhpIpamInputPanel',
faIcon: 'th',
},
},
sdndnsSchema: {
dns: {
name: 'dns',
hideAdd: true,
},
powerdns: {
name: 'powerdns',
ipanel: 'PowerdnsInputPanel',
faIcon: 'th',
},
},
format_sdnvnet_type: function(value, md, record) {
var schema = PVE.Utils.sdnvnetSchema[value];
if (schema) {
return schema.name;
}
return Proxmox.Utils.unknownText;
},
format_sdnzone_type: function(value, md, record) {
var schema = PVE.Utils.sdnzoneSchema[value];
if (schema) {
return schema.name;
}
return Proxmox.Utils.unknownText;
},
format_sdncontroller_type: function(value, md, record) {
var schema = PVE.Utils.sdncontrollerSchema[value];
if (schema) {
return schema.name;
}
return Proxmox.Utils.unknownText;
},
format_sdnipam_type: function(value, md, record) {
var schema = PVE.Utils.sdnipamSchema[value];
if (schema) {
return schema.name;
}
return Proxmox.Utils.unknownText;
},
format_sdndns_type: function(value, md, record) {
var schema = PVE.Utils.sdndnsSchema[value];
if (schema) {
return schema.name;
}
return Proxmox.Utils.unknownText;
},
format_storage_type: function(value, md, record) {
if (value === 'rbd') {
value = !record || record.get('monhost') ? 'rbd' : 'pveceph';
} else if (value === 'cephfs') {
value = !record || record.get('monhost') ? 'cephfs' : 'pvecephfs';
}
var schema = PVE.Utils.storageSchema[value];
if (schema) {
return schema.name;
}
return Proxmox.Utils.unknownText;
},
format_ha: function(value) {
var text = Proxmox.Utils.noneText;
if (value.managed) {
text = value.state || Proxmox.Utils.noneText;
text += ', ' + Proxmox.Utils.groupText + ': ';
text += value.group || Proxmox.Utils.noneText;
}
return text;
},
format_content_types: function(value) {
return value.split(',').sort().map(function(ct) {
return PVE.Utils.contentTypes[ct] || ct;
}).join(', ');
},
render_storage_content: function(value, metaData, record) {
var data = record.data;
if (Ext.isNumber(data.channel) &&
Ext.isNumber(data.id) &&
Ext.isNumber(data.lun)) {
return "CH " +
Ext.String.leftPad(data.channel, 2, '0') +
" ID " + data.id + " LUN " + data.lun;
}
return data.volid.replace(/^.*?:(.*?\/)?/, '');
},
render_serverity: function(value) {
return PVE.Utils.log_severity_hash[value] || value;
},
calculate_hostcpu: function(data) {
if (!(data.uptime && Ext.isNumeric(data.cpu))) {
return -1;
}
if (data.type !== 'qemu' && data.type !== 'lxc') {
return -1;
}
var index = PVE.data.ResourceStore.findExact('id', 'node/' + data.node);
var node = PVE.data.ResourceStore.getAt(index);
if (!Ext.isDefined(node) || node === null) {
return -1;
}
var maxcpu = node.data.maxcpu || 1;
if (!Ext.isNumeric(maxcpu) && (maxcpu >= 1)) {
return -1;
}
return (data.cpu/maxcpu) * data.maxcpu;
},
render_hostcpu: function(value, metaData, record, rowIndex, colIndex, store) {
if (!(record.data.uptime && Ext.isNumeric(record.data.cpu))) {
return '';
}
if (record.data.type !== 'qemu' && record.data.type !== 'lxc') {
return '';
}
var index = PVE.data.ResourceStore.findExact('id', 'node/' + record.data.node);
var node = PVE.data.ResourceStore.getAt(index);
if (!Ext.isDefined(node) || node === null) {
return '';
}
var maxcpu = node.data.maxcpu || 1;
if (!Ext.isNumeric(maxcpu) && (maxcpu >= 1)) {
return '';
}
var per = (record.data.cpu/maxcpu) * record.data.maxcpu * 100;
return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
},
render_bandwidth: function(value) {
if (!Ext.isNumeric(value)) {
return '';
}
return Proxmox.Utils.format_size(value) + '/s';
},
render_timestamp_human_readable: function(value) {
return Ext.Date.format(new Date(value * 1000), 'l d F Y H:i:s');
},
calculate_mem_usage: function(data) {
if (!Ext.isNumeric(data.mem) ||
data.maxmem === 0 ||
data.uptime < 1) {
return -1;
}
return data.mem / data.maxmem;
},
calculate_hostmem_usage: function(data) {
if (data.type !== 'qemu' && data.type !== 'lxc') {
return -1;
}
var index = PVE.data.ResourceStore.findExact('id', 'node/' + data.node);
var node = PVE.data.ResourceStore.getAt(index);
if (!Ext.isDefined(node) || node === null) {
return -1;
}
var maxmem = node.data.maxmem || 0;
if (!Ext.isNumeric(data.mem) ||
maxmem === 0 ||
data.uptime < 1) {
return -1;
}
return data.mem / maxmem;
},
render_mem_usage_percent: function(value, metaData, record, rowIndex, colIndex, store) {
if (!Ext.isNumeric(value) || value === -1) {
return '';
}
if (value > 1) {
// we got no percentage but bytes
var mem = value;
var maxmem = record.data.maxmem;
if (!record.data.uptime ||
maxmem === 0 ||
!Ext.isNumeric(mem)) {
return '';
}
return (mem*100/maxmem).toFixed(1) + " %";
}
return (value*100).toFixed(1) + " %";
},
render_hostmem_usage_percent: function(value, metaData, record, rowIndex, colIndex, store) {
if (!Ext.isNumeric(record.data.mem) || value === -1) {
return '';
}
if (record.data.type !== 'qemu' && record.data.type !== 'lxc') {
return '';
}
var index = PVE.data.ResourceStore.findExact('id', 'node/' + record.data.node);
var node = PVE.data.ResourceStore.getAt(index);
var maxmem = node.data.maxmem || 0;
if (record.data.mem > 1) {
// we got no percentage but bytes
var mem = record.data.mem;
if (!record.data.uptime ||
maxmem === 0 ||
!Ext.isNumeric(mem)) {
return '';
}
return ((mem*100)/maxmem).toFixed(1) + " %";
}
return (value*100).toFixed(1) + " %";
},
render_mem_usage: function(value, metaData, record, rowIndex, colIndex, store) {
var mem = value;
var maxmem = record.data.maxmem;
if (!record.data.uptime) {
return '';
}
if (!(Ext.isNumeric(mem) && maxmem)) {
return '';
}
return Proxmox.Utils.render_size(value);
},
calculate_disk_usage: function(data) {
if (!Ext.isNumeric(data.disk) ||
((data.type === 'qemu' || data.type === 'lxc') && data.uptime === 0) ||
data.maxdisk === 0
) {
return -1;
}
return data.disk / data.maxdisk;
},
render_disk_usage_percent: function(value, metaData, record, rowIndex, colIndex, store) {
if (!Ext.isNumeric(value) || value === -1) {
return '';
}
return (value * 100).toFixed(1) + " %";
},
render_disk_usage: function(value, metaData, record, rowIndex, colIndex, store) {
var disk = value;
var maxdisk = record.data.maxdisk;
var type = record.data.type;
if (!Ext.isNumeric(disk) ||
maxdisk === 0 ||
((type === 'qemu' || type === 'lxc') && record.data.uptime === 0)
) {
return '';
}
return Proxmox.Utils.render_size(value);
},
get_object_icon_class: function(type, record) {
var status = '';
var objType = type;
if (type === 'type') {
// for folder view
objType = record.groupbyid;
} else if (record.template) {
// templates
objType = 'template';
status = type;
} else {
// everything else
status = record.status + ' ha-' + record.hastate;
}
if (record.lock) {
status += ' locked lock-' + record.lock;
}
var defaults = PVE.tree.ResourceTree.typeDefaults[objType];
if (defaults && defaults.iconCls) {
var retVal = defaults.iconCls + ' ' + status;
return retVal;
}
return '';
},
render_resource_type: function(value, metaData, record, rowIndex, colIndex, store) {
var cls = PVE.Utils.get_object_icon_class(value, record.data);
var fa = '<i class="fa-fw x-grid-icon-custom ' + cls + '"></i> ';
return fa + value;
},
render_support_level: function(value, metaData, record) {
return PVE.Utils.support_level_hash[value] || '-';
},
render_upid: function(value, metaData, record) {
var type = record.data.type;
var id = record.data.id;
return Proxmox.Utils.format_task_description(type, id);
},
render_optional_url: function(value) {
if (value && value.match(/^https?:\/\//)) {
return '<a target="_blank" href="' + value + '">' + value + '</a>';
}
return value;
},
render_san: function(value) {
var names = [];
if (Ext.isArray(value)) {
value.forEach(function(val) {
if (!Ext.isNumber(val)) {
names.push(val);
}
});
return names.join('<br>');
}
return value;
},
render_full_name: function(firstname, metaData, record) {
var first = firstname || '';
var last = record.data.lastname || '';
return Ext.htmlEncode(first + " " + last);
},
render_u2f_error: function(error) {
var ErrorNames = {
'1': gettext('Other Error'),
'2': gettext('Bad Request'),
'3': gettext('Configuration Unsupported'),
'4': gettext('Device Ineligible'),
'5': gettext('Timeout'),
};
return "U2F Error: " + ErrorNames[error] || Proxmox.Utils.unknownText;
},
windowHostname: function() {
return window.location.hostname.replace(Proxmox.Utils.IP6_bracket_match,
function(m, addr, offset, original) { return addr; });
},
openDefaultConsoleWindow: function(consoles, consoleType, vmid, nodename, vmname, cmd) {
var dv = PVE.Utils.defaultViewer(consoles, consoleType);
PVE.Utils.openConsoleWindow(dv, consoleType, vmid, nodename, vmname, cmd);
},
openConsoleWindow: function(viewer, consoleType, vmid, nodename, vmname, cmd) {
if (vmid === undefined && (consoleType === 'kvm' || consoleType === 'lxc')) {
throw "missing vmid";
}
if (!nodename) {
throw "no nodename specified";
}
if (viewer === 'html5') {
PVE.Utils.openVNCViewer(consoleType, vmid, nodename, vmname, cmd);
} else if (viewer === 'xtermjs') {
Proxmox.Utils.openXtermJsViewer(consoleType, vmid, nodename, vmname, cmd);
} else if (viewer === 'vv') {
let url = '/nodes/' + nodename + '/spiceshell';
let params = {
proxy: PVE.Utils.windowHostname(),
};
if (consoleType === 'kvm') {
url = '/nodes/' + nodename + '/qemu/' + vmid.toString() + '/spiceproxy';
} else if (consoleType === 'lxc') {
url = '/nodes/' + nodename + '/lxc/' + vmid.toString() + '/spiceproxy';
} else if (consoleType === 'upgrade') {
params.cmd = 'upgrade';
} else if (consoleType === 'cmd') {
params.cmd = cmd;
} else if (consoleType !== 'shell') {
throw `unknown spice viewer type '${consoleType}'`;
}
PVE.Utils.openSpiceViewer(url, params);
} else {
throw `unknown viewer type '${viewer}'`;
}
},
defaultViewer: function(consoles, type) {
var allowSpice, allowXtermjs;
if (consoles === true) {
allowSpice = true;
allowXtermjs = true;
} else if (typeof consoles === 'object') {
allowSpice = consoles.spice;
allowXtermjs = !!consoles.xtermjs;
}
let dv = PVE.VersionInfo.console || (type === 'kvm' ? 'vv' : 'xtermjs');
if (dv === 'vv' && !allowSpice) {
dv = allowXtermjs ? 'xtermjs' : 'html5';
} else if (dv === 'xtermjs' && !allowXtermjs) {
dv = allowSpice ? 'vv' : 'html5';
}
return dv;
},
openVNCViewer: function(vmtype, vmid, nodename, vmname, cmd) {
let scaling = 'off';
if (Proxmox.Utils.toolkit !== 'touch') {
var sp = Ext.state.Manager.getProvider();
scaling = sp.get('novnc-scaling', 'off');
}
var url = Ext.Object.toQueryString({
console: vmtype, // kvm, lxc, upgrade or shell
novnc: 1,
vmid: vmid,
vmname: vmname,
node: nodename,
resize: scaling,
cmd: cmd,
});
var nw = window.open("?" + url, '_blank', "innerWidth=745,innerheight=427");
if (nw) {
nw.focus();
}
},
openSpiceViewer: function(url, params) {
var downloadWithName = function(uri, name) {
var link = Ext.DomHelper.append(document.body, {
tag: 'a',
href: uri,
css: 'display:none;visibility:hidden;height:0px;',
});
// Note: we need to tell Android and Chrome the correct file name extension
// but we do not set 'download' tag for other environments, because
// It can have strange side effects (additional user prompt on firefox)
if (navigator.userAgent.match(/Android|Chrome/i)) {
link.download = name;
}
if (link.fireEvent) {
link.fireEvent('onclick');
} else {
let evt = document.createEvent("MouseEvents");
evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(evt);
}
};
Proxmox.Utils.API2Request({
url: url,
params: params,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response, opts) {
let cfg = response.result.data;
let raw = Object.entries(cfg).reduce((acc, [k, v]) => acc + `${k}=${v}\n`, "[virt-viewer]\n");
let spiceDownload = 'data:application/x-virt-viewer;charset=UTF-8,' + encodeURIComponent(raw);
downloadWithName(spiceDownload, "pve-spice.vv");
},
});
},
openTreeConsole: function(tree, record, item, index, e) {
e.stopEvent();
let nodename = record.data.node;
let vmid = record.data.vmid;
let vmname = record.data.name;
if (record.data.type === 'qemu' && !record.data.template) {
Proxmox.Utils.API2Request({
url: `/nodes/${nodename}/qemu/${vmid}/status/current`,
failure: response => Ext.Msg.alert('Error', response.htmlStatus),
success: function(response, opts) {
let conf = response.result.data;
let consoles = {
spice: !!conf.spice,
xtermjs: !!conf.serial,
};
PVE.Utils.openDefaultConsoleWindow(consoles, 'kvm', vmid, nodename, vmname);
},
});
} else if (record.data.type === 'lxc' && !record.data.template) {
PVE.Utils.openDefaultConsoleWindow(true, 'lxc', vmid, nodename, vmname);
}
},
// test automation helper
call_menu_handler: function(menu, text) {
let item = menu.query('menuitem').find(el => el.text === text);
if (item && item.handler) {
item.handler();
}
},
createCmdMenu: function(v, record, item, index, event) {
event.stopEvent();
if (!(v instanceof Ext.tree.View)) {
v.select(record);
}
let menu;
let type = record.data.type;
if (record.data.template) {
if (type === 'qemu' || type === 'lxc') {
menu = Ext.create('PVE.menu.TemplateMenu', {
pveSelNode: record,
});
}
} else if (type === 'qemu' || type === 'lxc' || type === 'node') {
menu = Ext.create('PVE.' + type + '.CmdMenu', {
pveSelNode: record,
nodename: record.data.node,
});
} else {
return undefined;
}
menu.showAt(event.getXY());
return menu;
},
// helper for deleting field which are set to there default values
delete_if_default: function(values, fieldname, default_val, create) {
if (values[fieldname] === '' || values[fieldname] === default_val) {
if (!create) {
if (values.delete) {
if (Ext.isArray(values.delete)) {
values.delete.push(fieldname);
} else {
values.delete += ',' + fieldname;
}
} else {
values.delete = fieldname;
}
}
delete values[fieldname];
}
},
loadSSHKeyFromFile: function(file, callback) {
// ssh-keygen produces ~ 740 bytes for a 4096 bit RSA key, current max is 16 kbit, so assume:
// 740 * 8 for max. 32kbit (5920 bytes), round upwards to 8192 bytes, leaves lots of comment space
PVE.Utils.loadFile(file, callback, 8192);
},
loadFile: function(file, callback, maxSize) {
maxSize = maxSize || 32 * 1024;
if (file.size > maxSize) {
Ext.Msg.alert(gettext('Error'), `${gettext("Invalid file size")}: ${file.size} > ${maxSize}`);
return;
}
let reader = new FileReader();
reader.onload = evt => callback(evt.target.result);
reader.readAsText(file);
},
loadTextFromFile: function(file, callback, maxBytes) {
let maxSize = maxBytes || 8192;
if (file.size > maxSize) {
Ext.Msg.alert(gettext('Error'), gettext("Invalid file size: ") + file.size);
return;
}
let reader = new FileReader();
reader.onload = evt => callback(evt.target.result);
reader.readAsText(file);
},
diskControllerMaxIDs: {
ide: 4,
sata: 6,
scsi: 31,
virtio: 16,
},
// types is either undefined (all busses), an array of busses, or a single bus
forEachBus: function(types, func) {
let busses = Object.keys(PVE.Utils.diskControllerMaxIDs);
if (Ext.isArray(types)) {
busses = types;
} else if (Ext.isDefined(types)) {
busses = [types];
}
// check if we only have valid busses
for (let i = 0; i < busses.length; i++) {
if (!PVE.Utils.diskControllerMaxIDs[busses[i]]) {
throw "invalid bus: '" + busses[i] + "'";
}
}
for (let i = 0; i < busses.length; i++) {
let count = PVE.Utils.diskControllerMaxIDs[busses[i]];
for (let j = 0; j < count; j++) {
let cont = func(busses[i], j);
if (!cont && cont !== undefined) {
return;
}
}
}
},
mp_counts: {
mps: 256,
unused: 256,
},
forEachMP: function(func, includeUnused) {
for (let i = 0; i < PVE.Utils.mp_counts.mps; i++) {
let cont = func('mp', i);
if (!cont && cont !== undefined) {
return;
}
}
if (!includeUnused) {
return;
}
for (let i = 0; i < PVE.Utils.mp_counts.unused; i++) {
let cont = func('unused', i);
if (!cont && cont !== undefined) {
return;
}
}
},
hardware_counts: { net: 32, usb: 5, hostpci: 16, audio: 1, efidisk: 1, serial: 4, rng: 1 },
cleanEmptyObjectKeys: function(obj) {
for (const propName of Object.keys(obj)) {
if (obj[propName] === null || obj[propName] === undefined) {
delete obj[propName];
}
}
},
acmedomain_count: 5,
add_domain_to_acme: function(acme, domain) {
if (acme.domains === undefined) {
acme.domains = [domain];
} else {
acme.domains.push(domain);
acme.domains = acme.domains.filter((value, index, self) => self.indexOf(value) === index);
}
return acme;
},
remove_domain_from_acme: function(acme, domain) {
if (acme.domains !== undefined) {
acme.domains = acme
.domains
.filter((value, index, self) => self.indexOf(value) === index && value !== domain);
}
return acme;
},
handleStoreErrorOrMask: function(view, store, regex, callback) {
view.mon(store, 'load', function(proxy, response, success, operation) {
if (success) {
Proxmox.Utils.setErrorMask(view, false);
return;
}
let msg;
if (operation.error.statusText) {
if (operation.error.statusText.match(regex)) {
callback(view, operation.error);
return;
} else {
msg = operation.error.statusText + ' (' + operation.error.status + ')';
}
} else {
msg = gettext('Connection error');
}
Proxmox.Utils.setErrorMask(view, msg);
});
},
showCephInstallOrMask: function(container, msg, nodename, callback) {
if (msg.match(/not (installed|initialized)/i)) {
if (Proxmox.UserName === 'root@pam') {
container.el.mask();
if (!container.down('pveCephInstallWindow')) {
var isInstalled = !!msg.match(/not initialized/i);
var win = Ext.create('PVE.ceph.Install', {
nodename: nodename,
});
win.getViewModel().set('isInstalled', isInstalled);
container.add(win);
win.show();
callback(win);
}
} else {
container.mask(Ext.String.format(gettext('{0} not installed.') +
' ' + gettext('Log in as root to install.'), 'Ceph'), ['pve-static-mask']);
}
return true;
} else {
return false;
}
},
monitor_ceph_installed: function(view, rstore, nodename, maskOwnerCt) {
PVE.Utils.handleStoreErrorOrMask(
view,
rstore,
/not (installed|initialized)/i,
(_, error) => {
nodename = nodename || 'localhost';
let maskTarget = maskOwnerCt ? view.ownerCt : view;
rstore.stopUpdate();
PVE.Utils.showCephInstallOrMask(maskTarget, error.statusText, nodename, win => {
view.mon(win, 'cephInstallWindowClosed', () => rstore.startUpdate());
});
},
);
},
propertyStringSet: function(target, source, name, value) {
if (source) {
if (value === undefined) {
target[name] = source;
} else {
target[name] = value;
}
} else {
delete target[name];
}
},
forEachCorosyncLink: function(nodeinfo, cb) {
let re = /(?:ring|link)(\d+)_addr/;
Ext.iterate(nodeinfo, (prop, val) => {
let match = re.exec(prop);
if (match) {
cb(Number(match[1]), val);
}
});
},
cpu_vendor_map: {
'default': 'QEMU',
'AuthenticAMD': 'AMD',
'GenuineIntel': 'Intel',
},
cpu_vendor_order: {
"AMD": 1,
"Intel": 2,
"QEMU": 3,
"Host": 4,
"_default_": 5, // includes custom models
},
verify_ip64_address_list: function(value, with_suffix) {
for (let addr of value.split(/[ ,;]+/)) {
if (addr === '') {
continue;
}
if (with_suffix) {
let parts = addr.split('%');
addr = parts[0];
if (parts.length > 2) {
return false;
}
if (parts.length > 1 && !addr.startsWith('fe80:')) {
return false;
}
}
if (!Proxmox.Utils.IP64_match.test(addr)) {
return false;
}
}
return true;
},
},
singleton: true,
constructor: function() {
var me = this;
Ext.apply(me, me.utilities);
Proxmox.Utils.override_task_descriptions({
acmedeactivate: ['ACME Account', gettext('Deactivate')],
acmenewcert: ['SRV', gettext('Order Certificate')],
acmerefresh: ['ACME Account', gettext('Refresh')],
acmeregister: ['ACME Account', gettext('Register')],
acmerenew: ['SRV', gettext('Renew Certificate')],
acmerevoke: ['SRV', gettext('Revoke Certificate')],
acmeupdate: ['ACME Account', gettext('Update')],
'auth-realm-sync': [gettext('Realm'), gettext('Sync')],
'auth-realm-sync-test': [gettext('Realm'), gettext('Sync Preview')],
cephcreatemds: ['Ceph Metadata Server', gettext('Create')],
cephcreatemgr: ['Ceph Manager', gettext('Create')],
cephcreatemon: ['Ceph Monitor', gettext('Create')],
cephcreateosd: ['Ceph OSD', gettext('Create')],
cephcreatepool: ['Ceph Pool', gettext('Create')],
cephdestroymds: ['Ceph Metadata Server', gettext('Destroy')],
cephdestroymgr: ['Ceph Manager', gettext('Destroy')],
cephdestroymon: ['Ceph Monitor', gettext('Destroy')],
cephdestroyosd: ['Ceph OSD', gettext('Destroy')],
cephdestroypool: ['Ceph Pool', gettext('Destroy')],
cephfscreate: ['CephFS', gettext('Create')],
cephsetpool: ['Ceph Pool', gettext('Edit')],
cephsetflags: ['', gettext('Change global Ceph flags')],
clustercreate: ['', gettext('Create Cluster')],
clusterjoin: ['', gettext('Join Cluster')],
dircreate: [gettext('Directory Storage'), gettext('Create')],
dirremove: [gettext('Directory'), gettext('Remove')],
download: [gettext('File'), gettext('Download')],
hamigrate: ['HA', gettext('Migrate')],
hashutdown: ['HA', gettext('Shutdown')],
hastart: ['HA', gettext('Start')],
hastop: ['HA', gettext('Stop')],
imgcopy: ['', gettext('Copy data')],
imgdel: ['', gettext('Erase data')],
lvmcreate: [gettext('LVM Storage'), gettext('Create')],
lvmthincreate: [gettext('LVM-Thin Storage'), gettext('Create')],
migrateall: ['', gettext('Migrate all VMs and Containers')],
'move_volume': ['CT', gettext('Move Volume')],
'pbs-download': ['VM/CT', gettext('File Restore Download')],
pull_file: ['CT', gettext('Pull file')],
push_file: ['CT', gettext('Push file')],
qmclone: ['VM', gettext('Clone')],
qmconfig: ['VM', gettext('Configure')],
qmcreate: ['VM', gettext('Create')],
qmdelsnapshot: ['VM', gettext('Delete Snapshot')],
qmdestroy: ['VM', gettext('Destroy')],
qmigrate: ['VM', gettext('Migrate')],
qmmove: ['VM', gettext('Move disk')],
qmpause: ['VM', gettext('Pause')],
qmreboot: ['VM', gettext('Reboot')],
qmreset: ['VM', gettext('Reset')],
qmrestore: ['VM', gettext('Restore')],
qmresume: ['VM', gettext('Resume')],
qmrollback: ['VM', gettext('Rollback')],
qmshutdown: ['VM', gettext('Shutdown')],
qmsnapshot: ['VM', gettext('Snapshot')],
qmstart: ['VM', gettext('Start')],
qmstop: ['VM', gettext('Stop')],
qmsuspend: ['VM', gettext('Hibernate')],
qmtemplate: ['VM', gettext('Convert to template')],
spiceproxy: ['VM/CT', gettext('Console') + ' (Spice)'],
spiceshell: ['', gettext('Shell') + ' (Spice)'],
startall: ['', gettext('Start all VMs and Containers')],
stopall: ['', gettext('Stop all VMs and Containers')],
unknownimgdel: ['', gettext('Destroy image from unknown guest')],
wipedisk: ['Device', gettext('Wipe Disk')],
vncproxy: ['VM/CT', gettext('Console')],
vncshell: ['', gettext('Shell')],
vzclone: ['CT', gettext('Clone')],
vzcreate: ['CT', gettext('Create')],
vzdelsnapshot: ['CT', gettext('Delete Snapshot')],
vzdestroy: ['CT', gettext('Destroy')],
vzdump: (type, id) => id ? `VM/CT ${id} - ${gettext('Backup')}` : gettext('Backup Job'),
vzmigrate: ['CT', gettext('Migrate')],
vzmount: ['CT', gettext('Mount')],
vzreboot: ['CT', gettext('Reboot')],
vzrestore: ['CT', gettext('Restore')],
vzresume: ['CT', gettext('Resume')],
vzrollback: ['CT', gettext('Rollback')],
vzshutdown: ['CT', gettext('Shutdown')],
vzsnapshot: ['CT', gettext('Snapshot')],
vzstart: ['CT', gettext('Start')],
vzstop: ['CT', gettext('Stop')],
vzsuspend: ['CT', gettext('Suspend')],
vztemplate: ['CT', gettext('Convert to template')],
vzumount: ['CT', gettext('Unmount')],
zfscreate: [gettext('ZFS Storage'), gettext('Create')],
});
},
});