'Accept': 'application/json'
};
-/*jslint confusion: true */
Ext.define('PVE.Utils', { utilities: {
// this singleton contains miscellaneous utilities
},
compare_ceph_versions: function(a, b) {
+ let avers = [];
+ let bvers = [];
+
if (a === b) {
return 0;
}
- let avers = a.toString().split('.');
- let bvers = b.toString().split('.');
+
+ 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('.');
+ }
while (true) {
let av = avers.shift();
},
+ 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) {
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(value) {
if (!value) {
return Proxmox.Utils.defaultText + ' (' + Proxmox.Utils.disabledText + ')';
if (key === 'type') {
let map = {
isa: "ISA",
+ virtio: "VirtIO",
};
agentstring += map[value] || Proxmox.Utils.unknownText;
} else {
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'),
'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,
+ },
+ ldap: {
+ name: gettext('LDAP Server'),
+ ipanel: 'pveAuthLDAPPanel',
+ syncipanel: 'pveAuthLDAPSyncPanel',
+ add: true,
+ },
+ pam: {
+ name: 'Linux PAM',
+ ipanel: 'pveAuthBasePanel',
+ add: false,
+ },
+ pve: {
+ name: 'Proxmox VE authentication server',
+ ipanel: 'pveAuthBasePanel',
+ add: false,
+ },
+ },
+
storageSchema: {
dir: {
name: Proxmox.Utils.directoryText,
ipanel: 'ZFSPoolInputPanel',
faIcon: 'folder'
},
+ pbs: {
+ name: 'Proxmox Backup Server',
+ ipanel: 'PBSInputPanel',
+ faIcon: 'floppy-o',
+ },
drbd: {
name: 'DRBD',
- hideAdd: true
+ hideAdd: true,
+ },
+ },
+
+ 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'
+ },
+ },
+
+ 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_storage_type: function(value, md, record) {
Ext.String.leftPad(data.channel,2, '0') +
" ID " + data.id + " LUN " + data.lun;
}
- return data.volid.replace(/^.*:(.*\/)?/,'');
+ return data.volid.replace(/^.*?:(.*?\/)?/,'');
},
render_serverity: function (value) {
},
render_size: function(value, metaData, record, rowIndex, colIndex, store) {
- /*jslint confusion: true */
if (!Ext.isNumeric(value)) {
return '';
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 ||
function(m, addr, offset, original) { return addr; });
},
- openDefaultConsoleWindow: function(consoles, vmtype, vmid, nodename, vmname, cmd) {
+ openDefaultConsoleWindow: function(consoles, consoleType, vmid, nodename, vmname, cmd) {
var dv = PVE.Utils.defaultViewer(consoles);
- PVE.Utils.openConsoleWindow(dv, vmtype, vmid, nodename, vmname, cmd);
+ PVE.Utils.openConsoleWindow(dv, consoleType, vmid, nodename, vmname, cmd);
},
- openConsoleWindow: function(viewer, vmtype, vmid, nodename, vmname, cmd) {
- // kvm, lxc, shell, upgrade
-
- if (vmid == undefined && (vmtype === 'kvm' || vmtype === 'lxc')) {
+ 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(vmtype, vmid, nodename, vmname, cmd);
+ PVE.Utils.openVNCViewer(consoleType, vmid, nodename, vmname, cmd);
} else if (viewer === 'xtermjs') {
- Proxmox.Utils.openXtermJsViewer(vmtype, vmid, nodename, vmname, cmd);
+ Proxmox.Utils.openXtermJsViewer(consoleType, vmid, nodename, vmname, cmd);
} else if (viewer === 'vv') {
- var url;
- var params = { proxy: PVE.Utils.windowHostname() };
- if (vmtype === 'kvm') {
+ let url = '/nodes/' + nodename + '/spiceshell';
+ let params = {
+ proxy: PVE.Utils.windowHostname(),
+ };
+ if (consoleType === 'kvm') {
url = '/nodes/' + nodename + '/qemu/' + vmid.toString() + '/spiceproxy';
- PVE.Utils.openSpiceViewer(url, params);
- } else if (vmtype === 'lxc') {
+ } else if (consoleType === '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';
+ } else if (consoleType === 'upgrade') {
+ params.cmd = 'upgrade';
+ } else if (consoleType === 'cmd') {
params.cmd = cmd;
- PVE.Utils.openSpiceViewer(url, params);
+ } else if (consoleType !== 'shell') {
+ throw `unknown spice viewer type '${consoleType}'`;
}
+ PVE.Utils.openSpiceViewer(url, params);
} else {
- throw "unknown viewer type";
+ throw `unknown viewer type '${viewer}'`;
}
},
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);
+ 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);
}
};
if (values[fieldname] === '' || values[fieldname] === default_val) {
if (!create) {
if (values['delete']) {
- values['delete'] += ',' + fieldname;
+ if (Ext.isArray(values['delete'])) {
+ values['delete'].push(fieldname);
+ } else {
+ values['delete'] += ',' + fieldname;
+ }
} else {
values['delete'] = fieldname;
}
reader.readAsText(file);
},
- bus_counts: { ide: 4, sata: 6, scsi: 16, virtio: 16 },
+ 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;
+ }
+ /*global
+ FileReader
+ */
+ 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) {
- var busses = Object.keys(PVE.Utils.bus_counts);
+ var busses = Object.keys(PVE.Utils.diskControllerMaxIDs);
var i, j, count, cont;
if (Ext.isArray(types)) {
// check if we only have valid busses
for (i = 0; i < busses.length; i++) {
- if (!PVE.Utils.bus_counts[busses[i]]) {
+ if (!PVE.Utils.diskControllerMaxIDs[busses[i]]) {
throw "invalid bus: '" + busses[i] + "'";
}
}
for (i = 0; i < busses.length; i++) {
- count = PVE.Utils.bus_counts[busses[i]];
+ count = PVE.Utils.diskControllerMaxIDs[busses[i]];
for (j = 0; j < count; j++) {
cont = func(busses[i], j);
if (!cont && cont !== undefined) {
}
},
+ hardware_counts: { net: 32, usb: 5, hostpci: 16, audio: 1, efidisk: 1, serial: 4, rng: 1 },
+
cleanEmptyObjectKeys: function (obj) {
var propName;
for (propName in obj) {
}
},
+ 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) => {
+ return 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) => {
+ return self.indexOf(value) === index && value !== domain;
+ });
+ }
+ return acme;
+ },
+
handleStoreErrorOrMask: function(me, store, regex, callback) {
me.mon(store, 'load', function (proxy, response, success, operation) {
} else {
delete target[name];
}
- }
+ },
+
+ updateColumns: function(container) {
+ let mode = Ext.state.Manager.get('summarycolumns') || 'auto';
+ let factor;
+ if (mode !== 'auto') {
+ factor = parseInt(mode, 10);
+ if (Number.isNaN(factor)) {
+ factor = 1;
+ }
+ } else {
+ factor = container.getSize().width < 1400 ? 1 : 2;
+ }
+
+ if (container.oldFactor === factor) {
+ return;
+ }
+
+ let items = container.query('>'); // direct childs
+ factor = Math.min(factor, items.length);
+ container.oldFactor = factor;
+
+ items.forEach((item) => {
+ item.columnWidth = 1 / factor;
+ });
+
+ // we have to update the layout twice, since the first layout change
+ // can trigger the scrollbar which reduces the amount of space left
+ container.updateLayout();
+ container.updateLayout();
+ },
+
+ 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
+ },
},
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')],
+ clustercreate: ['', gettext('Create Cluster')],
+ clusterjoin: ['', gettext('Join Cluster')],
+ dircreate: [gettext('Directory Storage'), gettext('Create')],
+ dirremove: [gettext('Directory'), gettext('Remove')],
+ download: ['', 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')],
+ 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')],
+ 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')],
+ });
}
});
-