pve-manager/www/manager6/Utils.js
Dominik Csapak f932cffa89 ui: improve console fallback logic if default is not available
instead of instantly defaulting to novnc when the chosen
default is not available, try the other (better variant)
before falling back to novnc

e.g. having no default set is equivalent to having it set
to xtermjs, and having a vm with spice then previously
defaulted to novnc, now to spice (the reverse is also true)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2019-07-12 14:00:51 +02:00

1248 lines
29 KiB
JavaScript

Ext.ns('PVE');
// avoid errors related to Accessible Rich Internet Applications
// (access for people with disabilities)
// TODO reenable after all components are upgraded
Ext.enableAria = false;
Ext.enableAriaButtons = false;
Ext.enableAriaPanels = false;
// avoid errors when running without development tools
if (!Ext.isDefined(Ext.global.console)) {
var console = {
log: function() {}
};
}
console.log("Starting PVE Manager");
Ext.Ajax.defaultHeaders = {
'Accept': 'application/json'
};
/*jslint confusion: true */
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', 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'}
]
},
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 '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) {
if (a === b) {
return 0;
}
let avers = a.toString().split('.');
let bvers = b.toString().split('.');
while (true) {
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_OLD':'old',
'HEALTH_WARN':'warning',
'HEALTH_ERR':'critical'
},
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;
},
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_qga_features: function(value) {
if (!value) {
return Proxmox.Utils.defaultText + ' (' + Proxmox.Utils.disabledText + ')';
}
var props = PVE.Parser.parsePropertyString(value, 'enabled');
if (!PVE.Parser.parseBoolean(props.enabled)) {
return Proxmox.Utils.disabledText;
}
delete props.enabled;
var agentstring = Proxmox.Utils.enabledText;
Ext.Object.each(props, function(key, value) {
var keystring = '' ;
agentstring += ', ' + key + ': ';
if (PVE.Parser.parseBoolean(value)) {
agentstring += Proxmox.Utils.enabledText;
} else {
agentstring += Proxmox.Utils.disabledText;
}
});
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;
}
},
// 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;
},
format_duration_short: function(ut) {
if (ut < 60) {
return ut.toFixed(1) + 's';
}
if (ut < 3600) {
var mins = ut / 60;
return mins.toFixed(1) + 'm';
}
if (ut < 86400) {
var hours = ut / 3600;
return hours.toFixed(1) + 'h';
}
var days = ut / 86400;
return days.toFixed(1) + 'd';
},
contentTypes: {
'images': gettext('Disk image'),
'backup': gettext('VZDump backup file'),
'vztmpl': gettext('Container template'),
'iso': gettext('ISO image'),
'rootdir': gettext('Container'),
'snippets': gettext('Snippets')
},
storageSchema: {
dir: {
name: Proxmox.Utils.directoryText,
ipanel: 'DirInputPanel',
faIcon: 'folder'
},
lvm: {
name: 'LVM',
ipanel: 'LVMInputPanel',
faIcon: 'folder'
},
lvmthin: {
name: 'LVM-Thin',
ipanel: 'LvmThinInputPanel',
faIcon: 'folder'
},
nfs: {
name: 'NFS',
ipanel: 'NFSInputPanel',
faIcon: 'building'
},
cifs: {
name: 'CIFS',
ipanel: 'CIFSInputPanel',
faIcon: 'building'
},
glusterfs: {
name: 'GlusterFS',
ipanel: 'GlusterFsInputPanel',
faIcon: 'building'
},
iscsi: {
name: 'iSCSI',
ipanel: 'IScsiInputPanel',
faIcon: 'building'
},
cephfs: {
name: 'CephFS',
ipanel: 'CephFSInputPanel',
faIcon: 'building'
},
pvecephfs: {
name: 'CephFS (PVE)',
ipanel: 'CephFSInputPanel',
hideAdd: true,
faIcon: 'building'
},
rbd: {
name: 'RBD',
ipanel: 'RBDInputPanel',
faIcon: 'building'
},
pveceph: {
name: 'RBD (PVE)',
ipanel: 'RBDInputPanel',
hideAdd: true,
faIcon: 'building'
},
zfs: {
name: 'ZFS over iSCSI',
ipanel: 'ZFSInputPanel',
faIcon: 'building'
},
zfspool: {
name: 'ZFS',
ipanel: 'ZFSPoolInputPanel',
faIcon: 'folder'
},
drbd: {
name: 'DRBD',
hideAdd: true
}
},
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;
},
render_cpu: function(value, metaData, record, rowIndex, colIndex, store) {
if (!(record.data.uptime && Ext.isNumeric(value))) {
return '';
}
var maxcpu = record.data.maxcpu || 1;
if (!Ext.isNumeric(maxcpu) && (maxcpu >= 1)) {
return '';
}
var per = value * 100;
return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
},
render_size: function(value, metaData, record, rowIndex, colIndex, store) {
/*jslint confusion: true */
if (!Ext.isNumeric(value)) {
return '';
}
return Proxmox.Utils.format_size(value);
},
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');
},
render_duration: function(value) {
if (value === undefined) {
return '-';
}
return PVE.Utils.format_duration_short(value);
},
calculate_mem_usage: function(data) {
if (!Ext.isNumeric(data.mem) ||
data.maxmem === 0 ||
data.uptime < 1) {
return -1;
}
return (data.mem / data.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_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 PVE.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) ||
type === 'qemu' ||
maxdisk === 0 ||
(type === 'lxc' && record.data.uptime === 0)) {
return '';
}
return PVE.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 functions for new status panel */
render_usage: function(val) {
return (val*100).toFixed(2) + '%';
},
render_cpu_usage: function(val, max) {
return Ext.String.format(gettext('{0}% of {1}') +
' ' + gettext('CPU(s)'), (val*100).toFixed(2), max);
},
render_size_usage: function(val, max) {
if (max === 0) {
return gettext('N/A');
}
return (val*100/max).toFixed(2) + '% '+ '(' +
Ext.String.format(gettext('{0} of {1}'),
PVE.Utils.render_size(val), PVE.Utils.render_size(max)) + ')';
},
/* this is different for nodes */
render_node_cpu_usage: function(value, record) {
return PVE.Utils.render_cpu_usage(value, record.cpus);
},
/* this is different for nodes */
render_node_size_usage: function(record) {
return PVE.Utils.render_size_usage(record.used, record.total);
},
render_optional_url: function(value) {
var match;
if (value && (match = value.match(/^https?:\/\//)) !== null) {
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, vmtype, vmid, nodename, vmname, cmd) {
var dv = PVE.Utils.defaultViewer(consoles);
PVE.Utils.openConsoleWindow(dv, vmtype, vmid, nodename, vmname, cmd);
},
openConsoleWindow: function(viewer, vmtype, vmid, nodename, vmname, cmd) {
// kvm, lxc, shell, upgrade
if (vmid == undefined && (vmtype === 'kvm' || vmtype === 'lxc')) {
throw "missing vmid";
}
if (!nodename) {
throw "no nodename specified";
}
if (viewer === 'html5') {
PVE.Utils.openVNCViewer(vmtype, vmid, nodename, vmname, cmd);
} else if (viewer === 'xtermjs') {
Proxmox.Utils.openXtermJsViewer(vmtype, vmid, nodename, vmname, cmd);
} else if (viewer === 'vv') {
var url;
var params = { proxy: PVE.Utils.windowHostname() };
if (vmtype === 'kvm') {
url = '/nodes/' + nodename + '/qemu/' + vmid.toString() + '/spiceproxy';
PVE.Utils.openSpiceViewer(url, params);
} else if (vmtype === 'lxc') {
url = '/nodes/' + nodename + '/lxc/' + vmid.toString() + '/spiceproxy';
PVE.Utils.openSpiceViewer(url, params);
} else if (vmtype === 'shell') {
url = '/nodes/' + nodename + '/spiceshell';
PVE.Utils.openSpiceViewer(url, params);
} else if (vmtype === 'upgrade') {
url = '/nodes/' + nodename + '/spiceshell';
params.upgrade = 1;
PVE.Utils.openSpiceViewer(url, params);
} else if (vmtype === 'cmd') {
url = '/nodes/' + nodename + '/spiceshell';
params.cmd = cmd;
PVE.Utils.openSpiceViewer(url, params);
}
} else {
throw "unknown viewer type";
}
},
defaultViewer: function(consoles) {
var allowSpice, allowXtermjs;
if (consoles === true) {
allowSpice = true;
allowXtermjs = true;
} else if (typeof consoles === 'object') {
allowSpice = consoles.spice;
allowXtermjs = !!consoles.xtermjs;
}
var dv = PVE.VersionInfo.console || '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) {
var url = Ext.Object.toQueryString({
console: vmtype, // kvm, lxc, upgrade or shell
novnc: 1,
vmid: vmid,
vmname: vmname,
node: nodename,
resize: 'off',
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 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)
var andriod = navigator.userAgent.match(/Android/i) ? true : false;
if (andriod) {
link.download = name;
}
if (link.fireEvent) {
link.fireEvent('onclick');
} else {
var 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){
var raw = "[virt-viewer]\n";
Ext.Object.each(response.result.data, function(k, v) {
raw += k + "=" + v + "\n";
});
var url = 'data:application/x-virt-viewer;charset=UTF-8,' +
encodeURIComponent(raw);
downloadWithName(url, "pve-spice.vv");
}
});
},
openTreeConsole: function(tree, record, item, index, e) {
e.stopEvent();
var nodename = record.data.node;
var vmid = record.data.vmid;
var vmname = record.data.name;
if (record.data.type === 'qemu' && !record.data.template) {
Proxmox.Utils.API2Request({
url: '/nodes/' + nodename + '/qemu/' + vmid + '/status/current',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response, opts) {
var consoles = {
spice: !!response.result.data.spice,
xtermjs: !!response.result.data.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) {
var list = menu.query('menuitem');
Ext.Array.each(list, function(item) {
if (item.text === text) {
if (item.handler) {
item.handler();
return 1;
} else {
return undefined;
}
}
});
},
createCmdMenu: function(v, record, item, index, event) {
event.stopEvent();
if (!(v instanceof Ext.tree.View)) {
v.select(record);
}
var menu;
var template = !!record.data.template;
var type = record.data.type;
if (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;
}
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']) {
values['delete'] += ',' + fieldname;
} else {
values['delete'] = fieldname;
}
}
delete values[fieldname];
}
},
loadSSHKeyFromFile: function(file, callback) {
// ssh-keygen produces 740 bytes for an average 4096 bit rsa key, with
// a user@host comment, 1420 for 8192 bits; current max is 16kbit
// assume: 740*8 for max. 32kbit (5920 byte file)
// round upwards to nearest nice number => 8192 bytes, leaves lots of comment space
if (file.size > 8192) {
Ext.Msg.alert(gettext('Error'), gettext("Invalid file size: ") + file.size);
return;
}
/*global
FileReader
*/
var reader = new FileReader();
reader.onload = function(evt) {
callback(evt.target.result);
};
reader.readAsText(file);
},
bus_counts: { ide: 4, sata: 6, scsi: 16, virtio: 16 },
// types is either undefined (all busses), an array of busses, or a single bus
forEachBus: function(types, func) {
var busses = Object.keys(PVE.Utils.bus_counts);
var i, j, count, cont;
if (Ext.isArray(types)) {
busses = types;
} else if (Ext.isDefined(types)) {
busses = [ types ];
}
// check if we only have valid busses
for (i = 0; i < busses.length; i++) {
if (!PVE.Utils.bus_counts[busses[i]]) {
throw "invalid bus: '" + busses[i] + "'";
}
}
for (i = 0; i < busses.length; i++) {
count = PVE.Utils.bus_counts[busses[i]];
for (j = 0; j < count; j++) {
cont = func(busses[i], j);
if (!cont && cont !== undefined) {
return;
}
}
}
},
mp_counts: { mps: 256, unused: 256 },
forEachMP: function(func, includeUnused) {
var i, cont;
for (i = 0; i < PVE.Utils.mp_counts.mps; i++) {
cont = func('mp', i);
if (!cont && cont !== undefined) {
return;
}
}
if (!includeUnused) {
return;
}
for (i = 0; i < PVE.Utils.mp_counts.unused; i++) {
cont = func('unused', i);
if (!cont && cont !== undefined) {
return;
}
}
},
cleanEmptyObjectKeys: function (obj) {
var propName;
for (propName in obj) {
if (obj.hasOwnProperty(propName)) {
if (obj[propName] === null || obj[propName] === undefined) {
delete obj[propName];
}
}
}
},
handleStoreErrorOrMask: function(me, store, regex, callback) {
me.mon(store, 'load', function (proxy, response, success, operation) {
if (success) {
Proxmox.Utils.setErrorMask(me, false);
return;
}
var msg;
if (operation.error.statusText) {
if (operation.error.statusText.match(regex)) {
callback(me, operation.error);
return;
} else {
msg = operation.error.statusText + ' (' + operation.error.status + ')';
}
} else {
msg = gettext('Connection error');
}
Proxmox.Utils.setErrorMask(me, msg);
});
},
showCephInstallOrMask: function(container, msg, nodename, callback){
var regex = new RegExp("not (installed|initialized)", "i");
if (msg.match(regex)) {
if (Proxmox.UserName === 'root@pam') {
container.el.mask();
if (!container.down('pveCephInstallWindow')){
var isInstalled = msg.match(/not initialized/i) ? true : false;
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;
}
}
},
singleton: true,
constructor: function() {
var me = this;
Ext.apply(me, me.utilities);
}
});