Skip to content

Instantly share code, notes, and snippets.

@ppmathis
Created July 14, 2016 20:59
Show Gist options
  • Select an option

  • Save ppmathis/c44608af6bfe52141e147e9a4e17bd1a to your computer and use it in GitHub Desktop.

Select an option

Save ppmathis/c44608af6bfe52141e147e9a4e17bd1a to your computer and use it in GitHub Desktop.
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 = {
dir: function() {},
log: function() {}
};
}
console.log("Starting PVE Manager");
Ext.Ajax.defaultHeaders = {
'Accept': 'application/json'
};
Ext.Ajax.on('beforerequest', function(conn, options) {
if (PVE.CSRFPreventionToken) {
if (!options.headers) {
options.headers = {};
}
options.headers.CSRFPreventionToken = PVE.CSRFPreventionToken;
}
});
var IPV4_OCTET = "(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])";
var IPV4_REGEXP = "(?:(?:" + IPV4_OCTET + "\\.){3}" + IPV4_OCTET + ")";
var IPV6_H16 = "(?:[0-9a-fA-F]{1,4})";
var IPV6_LS32 = "(?:(?:" + IPV6_H16 + ":" + IPV6_H16 + ")|" + IPV4_REGEXP + ")";
var IP4_match = new RegExp("^(?:" + IPV4_REGEXP + ")$");
var IP4_cidr_match = new RegExp("^(?:" + IPV4_REGEXP + ")\/([0-9]{1,2})$");
var IPV6_REGEXP = "(?:" +
"(?:(?:" + "(?:" + IPV6_H16 + ":){6})" + IPV6_LS32 + ")|" +
"(?:(?:" + "::" + "(?:" + IPV6_H16 + ":){5})" + IPV6_LS32 + ")|" +
"(?:(?:(?:" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){4})" + IPV6_LS32 + ")|" +
"(?:(?:(?:(?:" + IPV6_H16 + ":){0,1}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){3})" + IPV6_LS32 + ")|" +
"(?:(?:(?:(?:" + IPV6_H16 + ":){0,2}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){2})" + IPV6_LS32 + ")|" +
"(?:(?:(?:(?:" + IPV6_H16 + ":){0,3}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){1})" + IPV6_LS32 + ")|" +
"(?:(?:(?:(?:" + IPV6_H16 + ":){0,4}" + IPV6_H16 + ")?::" + ")" + IPV6_LS32 + ")|" +
"(?:(?:(?:(?:" + IPV6_H16 + ":){0,5}" + IPV6_H16 + ")?::" + ")" + IPV6_H16 + ")|" +
"(?:(?:(?:(?:" + IPV6_H16 + ":){0,7}" + IPV6_H16 + ")?::" + ")" + ")" +
")";
var IP6_match = new RegExp("^(?:" + IPV6_REGEXP + ")$");
var IP6_cidr_match = new RegExp("^(?:" + IPV6_REGEXP + ")\/([0-9]{1,3})$");
var IP6_bracket_match = new RegExp("^\\[(" + IPV6_REGEXP + ")\\]");
var IP64_match = new RegExp("^(?:" + IPV6_REGEXP + "|" + IPV4_REGEXP + ")$");
Ext.define('PVE.Utils', { statics: {
// this class only contains static functions
toolkit: undefined, // (extjs|touch), set inside Toolkit.js
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="http://www.proxmox.com/products/proxmox-ve/subscription-service-plans">www.proxmox.com</a> to get a list of available options.',
kvm_ostypes: {
other: gettext('Other OS types'),
wxp: 'Microsoft Windows XP/2003',
w2k: 'Microsoft Windows 2000',
w2k8: 'Microsoft Windows Vista/2008',
win7: 'Microsoft Windows 7/2008r2',
win8: 'Microsoft Windows 8.x/10/2012/r2',
l24: 'Linux 2.4 Kernel',
l26: 'Linux 4.X/3.X/2.6 Kernel',
solaris: 'Solaris Kernel'
},
render_kvm_ostype: function (value) {
if (!value) {
return gettext('Other OS types');
}
var text = PVE.Utils.kvm_ostypes[value];
if (text) {
return text;
}
return value;
},
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(gettext('USB'));
} else if (el === 'memory') {
fa.push(gettext('Memory'));
} else if (el === 'cpu') {
fa.push(gettext('CPU'));
} else {
fa.push(el);
}
});
return fa.join(', ');
},
network_iface_types: {
eth: gettext("Network Device"),
bridge: 'Linux Bridge',
bond: 'Linux Bond',
OVSBridge: 'OVS Bridge',
OVSBond: 'OVS Bond',
OVSPort: 'OVS Port',
OVSIntPort: 'OVS IntPort'
},
render_network_iface_type: function(value) {
return PVE.Utils.network_iface_types[value] ||
PVE.Utils.unknownText;
},
render_qemu_bios: function(value) {
if (!value) {
return PVE.Utils.defaultText + ' (SeaBIOS)';
} else if (value === 'seabios') {
return "SeaBIOS";
} else if (value === 'ovmf') {
return "OVMF (UEFI)";
} else {
return value;
}
},
render_scsihw: function(value) {
if (!value) {
return PVE.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'),
cirrus: 'Cirrus Logic GD5446',
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'
},
render_kvm_language: function (value) {
if (!value) {
return PVE.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;
},
render_console_viewer: function(value) {
if (!value || value === '__default__') {
return PVE.Utils.defaultText + ' (HTML5)';
} else if (value === 'vv') {
return 'SPICE (remote-viewer)';
} else if (value === 'html5') {
return 'HTML5 (noVNC)';
} else {
return value;
}
},
language_map: {
zh_CN: 'Chinese',
ca: 'Catalan',
da: 'Danish',
en: 'English',
eu: 'Euskera (Basque)',
fr: 'French',
de: 'German',
it: 'Italian',
ja: 'Japanese',
nb: 'Norwegian (Bokmal)',
nn: 'Norwegian (Nynorsk)',
fa: 'Persian (Farsi)',
pl: 'Polish',
pt_BR: 'Portuguese (Brazil)',
ru: 'Russian',
sl: 'Slovenian',
es: 'Spanish',
sv: 'Swedish',
tr: 'Turkish'
},
render_language: function (value) {
if (!value) {
return PVE.Utils.defaultText + ' (English)';
}
var text = PVE.Utils.language_map[value];
if (text) {
return text + ' (' + value + ')';
}
return value;
},
language_array: function() {
var data = [['__default__', PVE.Utils.render_language('')]];
Ext.Object.each(PVE.Utils.language_map, function(key, value) {
data.push([key, PVE.Utils.render_language(value)]);
});
return data;
},
render_kvm_vga_driver: function (value) {
if (!value) {
return PVE.Utils.defaultText;
}
var text = PVE.Utils.kvm_vga_drivers[value];
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;
},
authOK: function() {
return Ext.util.Cookies.get('PVEAuthCookie');
},
authClear: function() {
Ext.util.Cookies.clear("PVEAuthCookie");
},
// fixme: remove - not needed?
gridLineHeigh: function() {
return 21;
//if (Ext.isGecko)
//return 23;
//return 21;
},
extractRequestError: function(result, verbose) {
var msg = gettext('Successful');
if (!result.success) {
msg = gettext("Unknown error");
if (result.message) {
msg = result.message;
if (result.status) {
msg += ' (' + result.status + ')';
}
}
if (verbose && Ext.isObject(result.errors)) {
msg += "<br>";
Ext.Object.each(result.errors, function(prop, desc) {
msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
Ext.htmlEncode(desc);
});
}
}
return msg;
},
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 = PVE.Utils.extractRequestError(action.result, true);
break;
}
return msg;
},
// Ext.Ajax.request
API2Request: function(reqOpts) {
var newopts = Ext.apply({
waitMsg: gettext('Please wait...')
}, reqOpts);
if (!newopts.url.match(/^\/api2/)) {
newopts.url = '/api2/extjs' + newopts.url;
}
delete newopts.callback;
var createWrapper = function(successFn, callbackFn, failureFn) {
Ext.apply(newopts, {
success: function(response, options) {
if (options.waitMsgTarget) {
if (PVE.Utils.toolkit === 'touch') {
options.waitMsgTarget.setMasked(false);
} else {
options.waitMsgTarget.setLoading(false);
}
}
var result = Ext.decode(response.responseText);
response.result = result;
if (!result.success) {
response.htmlStatus = PVE.Utils.extractRequestError(result, true);
Ext.callback(callbackFn, options.scope, [options, false, response]);
Ext.callback(failureFn, options.scope, [response, options]);
return;
}
Ext.callback(callbackFn, options.scope, [options, true, response]);
Ext.callback(successFn, options.scope, [response, options]);
},
failure: function(response, options) {
if (options.waitMsgTarget) {
if (PVE.Utils.toolkit === 'touch') {
options.waitMsgTarget.setMasked(false);
} else {
options.waitMsgTarget.setLoading(false);
}
}
response.result = {};
try {
response.result = Ext.decode(response.responseText);
} catch(e) {}
var msg = gettext('Connection error') + ' - server offline?';
if (response.aborted) {
msg = gettext('Connection error') + ' - aborted.';
} else if (response.timedout) {
msg = gettext('Connection error') + ' - Timeout.';
} else if (response.status && response.statusText) {
msg = gettext('Connection error') + ' ' + response.status + ': ' + response.statusText;
}
response.htmlStatus = msg;
Ext.callback(callbackFn, options.scope, [options, false, response]);
Ext.callback(failureFn, options.scope, [response, options]);
}
});
};
createWrapper(reqOpts.success, reqOpts.callback, reqOpts.failure);
var target = newopts.waitMsgTarget;
if (target) {
if (PVE.Utils.toolkit === 'touch') {
target.setMasked({ xtype: 'loadmask', message: newopts.waitMsg} );
} else {
// Note: ExtJS bug - this does not work when component is not rendered
target.setLoading(newopts.waitMsg);
}
}
Ext.Ajax.request(newopts);
},
assemble_field_data: function(values, data) {
if (Ext.isObject(data)) {
Ext.Object.each(data, function(name, val) {
if (values.hasOwnProperty(name)) {
var bucket = values[name];
if (!Ext.isArray(bucket)) {
bucket = values[name] = [bucket];
}
if (Ext.isArray(val)) {
values[name] = bucket.concat(val);
} else {
bucket.push(val);
}
} else {
values[name] = val;
}
});
}
},
checked_command: function(orig_cmd) {
PVE.Utils.API2Request({
url: '/nodes/localhost/subscription',
method: 'GET',
//waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, opts) {
var data = response.result.data;
if (data.status !== 'Active') {
Ext.Msg.show({
title: gettext('No valid subscription'),
icon: Ext.Msg.WARNING,
msg: PVE.Utils.noSubKeyHtml,
buttons: Ext.Msg.OK,
callback: function(btn) {
if (btn !== 'ok') {
return;
}
orig_cmd();
}
});
} else {
orig_cmd();
}
}
});
},
task_desc_table: {
vncproxy: [ 'VM/CT', gettext('Console') ],
spiceproxy: [ 'VM/CT', gettext('Console') + ' (Spice)' ],
vncshell: [ '', gettext('Shell') ],
spiceshell: [ '', gettext('Shell') + ' (Spice)' ],
qmsnapshot: [ 'VM', gettext('Snapshot') ],
qmrollback: [ 'VM', gettext('Rollback') ],
qmdelsnapshot: [ 'VM', gettext('Delete Snapshot') ],
qmcreate: [ 'VM', gettext('Create') ],
qmrestore: [ 'VM', gettext('Restore') ],
qmdestroy: [ 'VM', gettext('Destroy') ],
qmigrate: [ 'VM', gettext('Migrate') ],
qmclone: [ 'VM', gettext('Clone') ],
qmmove: [ 'VM', gettext('Move disk') ],
qmtemplate: [ 'VM', gettext('Convert to template') ],
qmstart: [ 'VM', gettext('Start') ],
qmstop: [ 'VM', gettext('Stop') ],
qmreset: [ 'VM', gettext('Reset') ],
qmshutdown: [ 'VM', gettext('Shutdown') ],
qmsuspend: [ 'VM', gettext('Suspend') ],
qmresume: [ 'VM', gettext('Resume') ],
qmconfig: [ 'VM', gettext('Configure') ],
vzsnapshot: [ 'CT', gettext('Snapshot') ],
vzrollback: [ 'CT', gettext('Rollback') ],
vzdelsnapshot: [ 'CT', gettext('Delete Snapshot') ],
vzcreate: ['CT', gettext('Create') ],
vzrestore: ['CT', gettext('Restore') ],
vzdestroy: ['CT', gettext('Destroy') ],
vzmigrate: [ 'CT', gettext('Migrate') ],
vzclone: [ 'CT', gettext('Clone') ],
vztemplate: [ 'CT', gettext('Convert to template') ],
vzstart: ['CT', gettext('Start') ],
vzstop: ['CT', gettext('Stop') ],
vzmount: ['CT', gettext('Mount') ],
vzumount: ['CT', gettext('Unmount') ],
vzshutdown: ['CT', gettext('Shutdown') ],
vzsuspend: [ 'CT', gettext('Suspend') ],
vzresume: [ 'CT', gettext('Resume') ],
hamigrate: [ 'HA', gettext('Migrate') ],
hastart: [ 'HA', gettext('Start') ],
hastop: [ 'HA', gettext('Stop') ],
srvstart: ['SRV', gettext('Start') ],
srvstop: ['SRV', gettext('Stop') ],
srvrestart: ['SRV', gettext('Restart') ],
srvreload: ['SRV', gettext('Reload') ],
cephcreatemon: ['Ceph Monitor', gettext('Create') ],
cephdestroymon: ['Ceph Monitor', gettext('Destroy') ],
cephcreateosd: ['Ceph OSD', gettext('Create') ],
cephdestroyosd: ['Ceph OSD', gettext('Destroy') ],
imgcopy: ['', gettext('Copy data') ],
imgdel: ['', gettext('Erase data') ],
download: ['', gettext('Download') ],
vzdump: ['', gettext('Backup') ],
aptupdate: ['', gettext('Update package database') ],
startall: [ '', gettext('Start all VMs and Containers') ],
stopall: [ '', gettext('Stop all VMs and Containers') ],
migrateall: [ '', gettext('Migrate all VMs and Containers') ]
},
format_task_description: function(type, id) {
var farray = PVE.Utils.task_desc_table[type];
if (!farray) {
return type;
}
var prefix = farray[0];
var text = farray[1];
if (prefix) {
return prefix + ' ' + id + ' - ' + text;
}
return text;
},
parse_task_upid: function(upid) {
var task = {};
var res = upid.match(/^UPID:(\S+):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8,9}):([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/);
if (!res) {
throw "unable to parse upid '" + upid + "'";
}
task.node = res[1];
task.pid = parseInt(res[2], 16);
task.pstart = parseInt(res[3], 16);
task.starttime = parseInt(res[4], 16);
task.type = res[5];
task.id = res[6];
task.user = res[7];
task.desc = PVE.Utils.format_task_description(task.type, task.id);
return task;
},
format_size: function(size) {
/*jslint confusion: true */
var units = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
var num = 0;
while (size >= 1024 && ((num++)+1) < units.length) {
size = size / 1024;
}
return size.toFixed((num > 0)?2:0) + " " + units[num] + "B";
},
format_html_bar: function(per, text) {
return "<div class='pve-bar-wrap'>" + text + "<div class='pve-bar-border'>" +
"<div class='pve-bar-inner' style='width:" + per + "%;'></div>" +
"</div></div>";
},
format_cpu_bar: function(per1, per2, text) {
return "<div class='pve-bar-border'>" +
"<div class='pve-bar-inner' style='width:" + per1 + "%;'></div>" +
"<div class='pve-bar-inner2' style='width:" + per2 + "%;'></div>" +
"<div class='pve-bar-text'>" + text + "</div>" +
"</div>";
},
format_large_bar: function(per, text) {
if (!text) {
text = per.toFixed(1) + "%";
}
return "<div class='pve-largebar-border'>" +
"<div class='pve-largebar-inner' style='width:" + per + "%;'></div>" +
"<div class='pve-largebar-text'>" + text + "</div>" +
"</div>";
},
format_duration_long: function(ut) {
var days = Math.floor(ut / 86400);
ut -= days*86400;
var hours = Math.floor(ut / 3600);
ut -= hours*3600;
var mins = Math.floor(ut / 60);
ut -= mins*60;
var hours_str = '00' + hours.toString();
hours_str = hours_str.substr(hours_str.length - 2);
var mins_str = "00" + mins.toString();
mins_str = mins_str.substr(mins_str.length - 2);
var ut_str = "00" + ut.toString();
ut_str = ut_str.substr(ut_str.length - 2);
if (days) {
var ds = days > 1 ? PVE.Utils.daysText : PVE.Utils.dayText;
return days.toString() + ' ' + ds + ' ' +
hours_str + ':' + mins_str + ':' + ut_str;
} else {
return hours_str + ':' + mins_str + ':' + ut_str;
}
},
format_duration_short: function(ut) {
if (ut < 60) {
return ut.toString() + 's';
}
if (ut < 3600) {
var mins = ut / 60;
return mins.toFixed(0) + 'm';
}
if (ut < 86400) {
var hours = ut / 3600;
return hours.toFixed(0) + 'h';
}
var days = ut / 86400;
return days.toFixed(0) + 'd';
},
yesText: gettext('Yes'),
noText: gettext('No'),
noneText: gettext('none'),
errorText: gettext('Error'),
unknownText: gettext('Unknown'),
defaultText: gettext('Default'),
daysText: gettext('days'),
dayText: gettext('day'),
runningText: gettext('running'),
stoppedText: gettext('stopped'),
neverText: gettext('never'),
totalText: gettext('Total'),
usedText: gettext('Used'),
directoryText: gettext('Directory'),
imagesText: gettext('Disk image'),
backupFileText: gettext('VZDump backup file'),
vztmplText: gettext('Container template'),
isoImageText: gettext('ISO image'),
containersText: gettext('Container'),
stateText: gettext('State'),
groupText: gettext('Group'),
format_expire: function(date) {
if (!date) {
return PVE.Utils.neverText;
}
return Ext.Date.format(date, "Y-m-d");
},
format_storage_type: function(value) {
if (value === 'dir') {
return PVE.Utils.directoryText;
} else if (value === 'nfs') {
return 'NFS';
} else if (value === 'glusterfs') {
return 'GlusterFS';
} else if (value === 'lvm') {
return 'LVM';
} else if (value === 'lvmthin') {
return 'LVM-Thin';
} else if (value === 'iscsi') {
return 'iSCSI';
} else if (value === 'rbd') {
return 'RBD';
} else if (value === 'sheepdog') {
return 'Sheepdog';
} else if (value === 'zfs') {
return 'ZFS over iSCSI';
} else if (value === 'zfspool') {
return 'ZFS';
} else if (value === 'iscsidirect') {
return 'iSCSIDirect';
} else if (value === 'drbd') {
return 'DRBD';
} else {
return PVE.Utils.unknownText;
}
},
format_boolean_with_default: function(value) {
if (Ext.isDefined(value) && value !== '__default__') {
return value ? PVE.Utils.yesText : PVE.Utils.noText;
}
return PVE.Utils.defaultText;
},
format_boolean: function(value) {
return value ? PVE.Utils.yesText : PVE.Utils.noText;
},
format_neg_boolean: function(value) {
return !value ? PVE.Utils.yesText : PVE.Utils.noText;
},
format_ha: function(value) {
var text = PVE.Utils.format_boolean(value.managed);
if (value.managed) {
text += ', ' + PVE.Utils.stateText + ': ';
text += value.state || PVE.Utils.noneText;
text += ', ' + PVE.Utils.groupText + ': ';
text += value.group || PVE.Utils.noneText;
}
return text;
},
format_content_types: function(value) {
var cta = [];
Ext.each(value.split(',').sort(), function(ct) {
if (ct === 'images') {
cta.push(PVE.Utils.imagesText);
} else if (ct === 'backup') {
cta.push(PVE.Utils.backupFileText);
} else if (ct === 'vztmpl') {
cta.push(PVE.Utils.vztmplText);
} else if (ct === 'iso') {
cta.push(PVE.Utils.isoImageText);
} else if (ct === 'rootdir') {
cta.push(PVE.Utils.containersText);
}
});
return cta.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 PVE.Utils.format_size(value);
},
render_timestamp: function(value, metaData, record, rowIndex, colIndex, store) {
var servertime = new Date(value * 1000);
return Ext.Date.format(servertime, 'Y-m-d 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);
},
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);
},
render_resource_type: function(value, metaData, record, rowIndex, colIndex, store) {
var icon = '';
var gridcls = '';
switch (value) {
case 'lxc': icon = 'cube';
gridcls = '-stopped';
break;
case 'qemu': icon = 'desktop';
gridcls = '-stopped';
break;
case 'node': icon = 'building';
gridcls = '-offline';
break;
case 'storage': icon = 'database'; break;
case 'pool': icon = 'tags'; break;
default: icon = 'file';
}
if (value === 'lxc' || value === 'qemu') {
if (record.data.running && record.data.status !== 'paused') {
gridcls = '-running';
} else if (record.data.running) {
gridcls = '-paused';
}
if (record.data.template) {
icon = 'file-o';
gridcls = '-template-' + value;
}
} else if (value === 'node') {
if (record.data.running) {
gridcls = '-online';
}
}
var fa = '<i class="fa fa-fw x-fa-grid' + gridcls + ' fa-' + icon + '"></i> ';
return fa + value;
},
render_uptime: function(value, metaData, record, rowIndex, colIndex, store) {
var uptime = value;
if (uptime === undefined) {
return '';
}
if (uptime <= 0) {
return '-';
}
return PVE.Utils.format_duration_long(uptime);
},
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 PVE.Utils.format_task_description(type, id);
},
dialog_title: function(subject, create, isAdd) {
if (create) {
if (isAdd) {
return gettext('Add') + ': ' + subject;
} else {
return gettext('Create') + ': ' + subject;
}
} else {
return gettext('Edit') + ': ' + subject;
}
},
windowHostname: function() {
return window.location.hostname.replace(IP6_bracket_match,
function(m, addr, offset, original) { return addr; });
},
openDefaultConsoleWindow: function(allowSpice, vmtype, vmid, nodename, vmname) {
var dv = PVE.Utils.defaultViewer(allowSpice);
PVE.Utils.openConsoleWindow(dv, vmtype, vmid, nodename, vmname);
},
openConsoleWindow: function(viewer, vmtype, vmid, nodename, vmname) {
// 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);
} 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 {
throw "unknown viewer type";
}
},
defaultViewer: function(allowSpice) {
var vncdefault = 'html5';
var dv = PVE.VersionInfo.console || vncdefault;
if (dv === 'vv' && !allowSpice) {
dv = vncdefault;
}
return dv;
},
openVNCViewer: function(vmtype, vmid, nodename, vmname) {
var url = Ext.urlEncode({
console: vmtype, // kvm, lxc, upgrade or shell
novnc: 1,
vmid: vmid,
vmname: vmname,
node: nodename
});
var nw = window.open("?" + url, '_blank', "innerWidth=745,innerheight=427");
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);
}
};
PVE.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");
}
});
},
// comp.setLoading() is buggy in ExtJS 4.0.7, so we
// use el.mask() instead
setErrorMask: function(comp, msg) {
var el = comp.el;
if (!el) {
return;
}
if (!msg) {
el.unmask();
} else {
if (msg === true) {
el.mask(gettext("Loading..."));
} else {
el.mask(msg);
}
}
},
monStoreErrors: function(me, store) {
me.mon(store, 'beforeload', function(s, operation, eOpts) {
if (!me.loadCount) {
me.loadCount = 0; // make sure it is numeric
PVE.Utils.setErrorMask(me, true);
}
});
// only works with 'pve' proxy
me.mon(store.proxy, 'afterload', function(proxy, request, success) {
me.loadCount++;
if (success) {
PVE.Utils.setErrorMask(me, false);
return;
}
var msg;
/*jslint nomen: true */
var operation = request._operation;
var error = operation.getError();
if (error.statusText) {
msg = error.statusText + ' (' + error.status + ')';
} else {
msg = gettext('Connection error');
}
PVE.Utils.setErrorMask(me, msg);
});
},
createCmdMenu: function(v, record, item, index, event) {
event.stopEvent();
if (!(v instanceof Ext.tree.View)) {
v.select(record);
}
var menu;
if (record.data.type === 'qemu' && !record.data.template) {
menu = Ext.create('PVE.qemu.CmdMenu', {
pveSelNode: record
});
} else if (record.data.type === 'qemu' && record.data.template) {
menu = Ext.create('PVE.qemu.TemplateMenu', {
pveSelNode: record
});
} else if (record.data.type === 'lxc' && !record.data.template) {
menu = Ext.create('PVE.lxc.CmdMenu', {
pveSelNode: record
});
} else if (record.data.type === 'lxc' && record.data.template) {
/* since clone does not work reliably, disable for now
menu = Ext.create('PVE.lxc.TemplateMenu', {
pveSelNode: record
});
*/
return;
} else {
return;
}
menu.showAt(event.getXY());
}
}});
/*global IP4_match, IP4_cidr_match, IP6_match, IP6_cidr_match, IP64_match*/
// ExtJS related things
PVE.Utils.toolkit = 'extjs';
// do not send '_dc' parameter
Ext.Ajax.disableCaching = false;
// custom Vtypes
Ext.apply(Ext.form.field.VTypes, {
IPAddress: function(v) {
return IP4_match.test(v);
},
IPAddressText: gettext('Example') + ': 192.168.1.1',
IPAddressMask: /[\d\.]/i,
IPCIDRAddress: function(v) {
var result = IP4_cidr_match.exec(v);
// limits according to JSON Schema see
// pve-common/src/PVE/JSONSchema.pm
return (result !== null && result[1] >= 8 && result[1] <= 32);
},
IPCIDRAddressText: gettext('Example') + ': 192.168.1.1/24' + "<br>" + gettext('Valid CIDR Range') + ': 8-32',
IPCIDRAddressMask: /[\d\.\/]/i,
IP6Address: function(v) {
return IP6_match.test(v);
},
IP6AddressText: gettext('Example') + ': 2001:DB8::42',
IP6AddressMask: /[A-Fa-f0-9:]/,
IP6CIDRAddress: function(v) {
var result = IP6_cidr_match.exec(v);
// limits according to JSON Schema see
// pve-common/src/PVE/JSONSchema.pm
return (result !== null && result[1] >= 8 && result[1] <= 120);
},
IP6CIDRAddressText: gettext('Example') + ': 2001:DB8::42/64' + "<br>" + gettext('Valid CIDR Range') + ': 8-120',
IP6CIDRAddressMask: /[A-Fa-f0-9:\/]/,
IP6PrefixLength: function(v) {
return v >= 0 && v <= 128;
},
IP6PrefixLengthText: gettext('Example') + ': X, where 0 <= X <= 128',
IP6PrefixLengthMask: /[0-9]/,
IP64Address: function(v) {
return IP64_match.test(v);
},
IP64AddressText: gettext('Example') + ': 192.168.1.1 2001:DB8::42',
IP64AddressMask: /[A-Fa-f0-9\.:]/,
MacAddress: function(v) {
return (/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/).test(v);
},
MacAddressMask: /[a-fA-F0-9:]/,
MacAddressText: gettext('Example') + ': 01:23:45:67:89:ab',
BridgeName: function(v) {
return (/^vmbr\d{1,4}$/).test(v);
},
BridgeNameText: gettext('Format') + ': vmbr<b>N</b>, where 0 <= <b>N</b> <= 9999',
BondName: function(v) {
return (/^bond\d{1,4}$/).test(v);
},
BondNameText: gettext('Format') + ': bond<b>N</b>, where 0 <= <b>N</b> <= 9999',
InterfaceName: function(v) {
return (/^[a-z][a-z0-9_]{1,20}$/).test(v);
},
InterfaceNameText: gettext('Format') + ': [a-z][a-z0-9_]{1,20}',
QemuStartDate: function(v) {
return (/^(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)$/).test(v);
},
QemuStartDateText: gettext('Format') + ': "now" or "2006-06-17T16:01:21" or "2006-06-17"',
StorageId: function(v) {
return (/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i).test(v);
},
StorageIdText: gettext("Allowed characters") + ": 'A-Z', 'a-z', '0-9', '-', '_', '.'",
ConfigId: function(v) {
return (/^[a-z][a-z0-9\_]+$/i).test(v);
},
ConfigIdText: gettext("Allowed characters") + ": 'A-Z', 'a-z', '0-9', '_'",
HttpProxy: function(v) {
return (/^http:\/\/.*$/).test(v);
},
HttpProxyText: gettext('Example') + ": http://username:password&#64;host:port/",
DnsName: function(v) {
return (/^(([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)\.)*([A-Za-z0-9]([A-Za-z0-9\-]*[A-Za-z0-9])?)$/).test(v);
},
DnsNameText: gettext('This is not a valid DNS name'),
// workaround for https://www.sencha.com/forum/showthread.php?302150
pveMail: function(v) {
return (/^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,63}$/).test(v);
},
pveMailText: gettext('Example') + ": [email protected]"
});
// ExtJs 5-6 has an issue with caching
// see https://www.sencha.com/forum/showthread.php?308989
Ext.define('PVE.UnderlayPool', {
override: 'Ext.dom.UnderlayPool',
checkOut: function () {
var cache = this.cache,
len = cache.length,
el;
// do cleanup because some of the objects might have been destroyed
while (len--) {
if (cache[len].destroyed) {
cache.splice(len, 1);
}
}
// end do cleanup
el = cache.shift();
if (!el) {
el = Ext.Element.create(this.elementConfig);
el.setVisibilityMode(2);
//<debug>
// tell the spec runner to ignore this element when checking if the dom is clean
el.dom.setAttribute('data-sticky', true);
//</debug>
}
return el;
}
});
// if the order of the values are not the same in originalValue and value
// extjs will not overwrite value, but marks the field dirty and thus
// the reset button will be enabled (but clicking it changes nothing)
// so if the arrays are not the same after resetting, we
// clear and set it
Ext.define('PVE.form.ComboBox', {
override: 'Ext.form.field.ComboBox',
reset: function() {
// copied from combobox
var me = this;
me.callParent();
me.applyEmptyText();
// clear and set when not the same
if (Ext.isArray(me.originalValue) && !Ext.Array.equals(me.getValue(), me.originalValue)) {
me.clearValue();
me.setValue(me.originalValue);
}
}
});
// should be fixed with ExtJS 6.0.2, see:
// https://www.sencha.com/forum/showthread.php?307244-Bug-with-datefield-in-window-with-scroll
Ext.define('PVE.Datepicker', {
override: 'Ext.picker.Date',
hideMode: 'visibility'
});
// force alert boxes to be rendered with an Error Icon
// since Ext.Msg is an object and not a prototype, we need to override it
// after the framework has been initiated
Ext.onReady(function() {
/*jslint confusion: true */
Ext.override(Ext.Msg, {
alert: function(title, message, fn, scope) {
if (Ext.isString(title)) {
var config = {
title: title,
message: message,
icon: this.ERROR,
buttons: this.OK,
fn: fn,
scope : scope,
minWidth: this.minWidth
};
return this.show(config);
}
}
});
/*jslint confusion: false */
});
Ext.define('Ext.ux.IFrame', {
extend: 'Ext.Component',
alias: 'widget.uxiframe',
loadMask: 'Loading...',
src: 'about:blank',
renderTpl: [
'<iframe src="{src}" id="{id}-iframeEl" data-ref="iframeEl" name="{frameName}" width="100%" height="100%" frameborder="0" allowfullscreen="true"></iframe>'
],
childEls: ['iframeEl'],
initComponent: function () {
this.callParent();
this.frameName = this.frameName || this.id + '-frame';
},
initEvents : function() {
var me = this;
me.callParent();
me.iframeEl.on('load', me.onLoad, me);
},
initRenderData: function() {
return Ext.apply(this.callParent(), {
src: this.src,
frameName: this.frameName
});
},
getBody: function() {
var doc = this.getDoc();
return doc.body || doc.documentElement;
},
getDoc: function() {
try {
return this.getWin().document;
} catch (ex) {
return null;
}
},
getWin: function() {
var me = this,
name = me.frameName,
win = Ext.isIE
? me.iframeEl.dom.contentWindow
: window.frames[name];
return win;
},
getFrame: function() {
var me = this;
return me.iframeEl.dom;
},
beforeDestroy: function () {
this.cleanupListeners(true);
this.callParent();
},
cleanupListeners: function(destroying){
var doc, prop;
if (this.rendered) {
try {
doc = this.getDoc();
if (doc) {
/*jslint nomen: true*/
Ext.get(doc).un(this._docListeners);
/*jslint nomen: false*/
if (destroying && doc.hasOwnProperty) {
for (prop in doc) {
if (doc.hasOwnProperty(prop)) {
delete doc[prop];
}
}
}
}
} catch(e) { }
}
},
onLoad: function() {
var me = this,
doc = me.getDoc(),
fn = me.onRelayedEvent;
if (doc) {
try {
// These events need to be relayed from the inner document (where they stop
// bubbling) up to the outer document. This has to be done at the DOM level so
// the event reaches listeners on elements like the document body. The effected
// mechanisms that depend on this bubbling behavior are listed to the right
// of the event.
/*jslint nomen: true*/
Ext.get(doc).on(
me._docListeners = {
mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
mousemove: fn, // window resize drag detection
mouseup: fn, // window resize termination
click: fn, // not sure, but just to be safe
dblclick: fn, // not sure again
scope: me
}
);
/*jslint nomen: false*/
} catch(e) {
// cannot do this xss
}
// We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
Ext.get(this.getWin()).on('beforeunload', me.cleanupListeners, me);
this.el.unmask();
this.fireEvent('load', this);
} else if (me.src) {
this.el.unmask();
this.fireEvent('error', this);
}
},
onRelayedEvent: function (event) {
// relay event from the iframe's document to the document that owns the iframe...
var iframeEl = this.iframeEl,
// Get the left-based iframe position
iframeXY = iframeEl.getTrueXY(),
originalEventXY = event.getXY(),
// Get the left-based XY position.
// This is because the consumer of the injected event will
// perform its own RTL normalization.
eventXY = event.getTrueXY();
// the event from the inner document has XY relative to that document's origin,
// so adjust it to use the origin of the iframe in the outer document:
event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
event.injectEvent(iframeEl); // blame the iframe for the event...
event.xy = originalEventXY; // restore the original XY (just for safety)
},
load: function (src) {
var me = this,
text = me.loadMask,
frame = me.getFrame();
if (me.fireEvent('beforeload', me, src) !== false) {
if (text && me.el) {
me.el.mask(text);
}
frame.src = me.src = (src || me.src);
}
}
});
// Some configuration values are complex strings -
// so we need parsers/generators for them.
Ext.define('PVE.Parser', { statics: {
// this class only contains static functions
parseBoolean: function(value, default_value) {
if (!Ext.isDefined(value)) {
return default_value;
}
value = value.toLowerCase();
return value === '1' ||
value === 'on' ||
value === 'yes' ||
value === 'true';
},
parseQemuNetwork: function(key, value) {
if (!(key && value)) {
return;
}
var res = {};
var errors = false;
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
var match_res;
if ((match_res = p.match(/^(ne2k_pci|e1000|e1000-82540em|e1000-82544gc|e1000-82545em|vmxnet3|rtl8139|pcnet|virtio|ne2k_isa|i82551|i82557b|i82559er)(=([0-9a-f]{2}(:[0-9a-f]{2}){5}))?$/i)) !== null) {
res.model = match_res[1].toLowerCase();
if (match_res[3]) {
res.macaddr = match_res[3];
}
} else if ((match_res = p.match(/^bridge=(\S+)$/)) !== null) {
res.bridge = match_res[1];
} else if ((match_res = p.match(/^rate=(\d+(\.\d+)?)$/)) !== null) {
res.rate = match_res[1];
} else if ((match_res = p.match(/^tag=(\d+(\.\d+)?)$/)) !== null) {
res.tag = match_res[1];
} else if ((match_res = p.match(/^firewall=(\d+)$/)) !== null) {
res.firewall = match_res[1];
} else if ((match_res = p.match(/^link_down=(\d+)$/)) !== null) {
res.disconnect = match_res[1];
} else if ((match_res = p.match(/^queues=(\d+)$/)) !== null) {
res.queues = match_res[1];
} else if ((match_res = p.match(/^trunks=(\d+(?:-\d+)?(?:;\d+(?:-\d+)?)*)$/)) !== null) {
res.trunks = match_res[1];
} else {
errors = true;
return false; // break
}
});
if (errors || !res.model) {
return;
}
return res;
},
printQemuNetwork: function(net) {
var netstr = net.model;
if (net.macaddr) {
netstr += "=" + net.macaddr;
}
if (net.bridge) {
netstr += ",bridge=" + net.bridge;
if (net.tag) {
netstr += ",tag=" + net.tag;
}
if (net.firewall) {
netstr += ",firewall=" + net.firewall;
}
}
if (net.rate) {
netstr += ",rate=" + net.rate;
}
if (net.queues) {
netstr += ",queues=" + net.queues;
}
if (net.disconnect) {
netstr += ",link_down=" + net.disconnect;
}
if (net.trunks) {
netstr += ",trunks=" + net.trunks;
}
return netstr;
},
parseQemuDrive: function(key, value) {
if (!(key && value)) {
return;
}
var res = {};
var match_res = key.match(/^([a-z]+)(\d+)$/);
if (!match_res) {
return;
}
res['interface'] = match_res[1];
res.index = match_res[2];
var errors = false;
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
var match_res = p.match(/^([a-z_]+)=(\S+)$/);
if (!match_res) {
if (!p.match(/\=/)) {
res.file = p;
return; // continue
}
errors = true;
return false; // break
}
var k = match_res[1];
if (k === 'volume') {
k = 'file';
}
if (Ext.isDefined(res[k])) {
errors = true;
return false; // break
}
var v = match_res[2];
if (k === 'cache' && v === 'off') {
v = 'none';
}
res[k] = v;
});
if (errors || !res.file) {
return;
}
return res;
},
printQemuDrive: function(drive) {
var drivestr = drive.file;
Ext.Object.each(drive, function(key, value) {
if (!Ext.isDefined(value) || key === 'file' ||
key === 'index' || key === 'interface') {
return; // continue
}
drivestr += ',' + key + '=' + value;
});
return drivestr;
},
parseOpenVZNetIf: function(value) {
if (!value) {
return;
}
var res = {};
var errors = false;
Ext.Array.each(value.split(';'), function(item) {
if (!item || item.match(/^\s*$/)) {
return; // continue
}
var data = {};
Ext.Array.each(item.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
var match_res = p.match(/^(ifname|mac|bridge|host_ifname|host_mac|mac_filter)=(\S+)$/);
if (!match_res) {
errors = true;
return false; // break
}
if (match_res[1] === 'bridge'){
var bridgevlanf = match_res[2];
var bridge_res = bridgevlanf.match(/^(vmbr(\d+))(v(\d+))?(f)?$/);
if (!bridge_res) {
errors = true;
return false; // break
}
data.bridge = bridge_res[1];
data.tag = bridge_res[4];
/*jslint confusion: true*/
data.firewall = bridge_res[5] ? 1 : 0;
/*jslint confusion: false*/
} else {
data[match_res[1]] = match_res[2];
}
});
if (errors || !data.ifname) {
errors = true;
return false; // break
}
data.raw = item;
res[data.ifname] = data;
});
return errors ? undefined: res;
},
printOpenVZNetIf: function(netif) {
var netarray = [];
Ext.Object.each(netif, function(iface, data) {
var tmparray = [];
Ext.Array.each(['ifname', 'mac', 'bridge', 'host_ifname' , 'host_mac', 'mac_filter', 'tag', 'firewall'], function(key) {
var value = data[key];
if (key === 'bridge'){
if(data.tag){
value = value + 'v' + data.tag;
}
if (data.firewall){
value = value + 'f';
}
}
if (value) {
tmparray.push(key + '=' + value);
}
});
netarray.push(tmparray.join(','));
});
return netarray.join(';');
},
parseLxcNetwork: function(value) {
if (!value) {
return;
}
var data = {};
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
var match_res = p.match(/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag|rate)=(\S+)$/);
if (!match_res) {
// todo: simply ignore errors ?
return; // continue
}
data[match_res[1]] = match_res[2];
});
return data;
},
printLxcNetwork: function(data) {
var tmparray = [];
Ext.Array.each(['bridge', 'hwaddr', 'mtu', 'name', 'ip',
'gw', 'ip6', 'gw6', 'firewall', 'tag'], function(key) {
var value = data[key];
if (value) {
tmparray.push(key + '=' + value);
}
});
/*jslint confusion: true*/
if (data.rate > 0) {
tmparray.push('rate=' + data.rate);
}
/*jslint confusion: false*/
return tmparray.join(',');
},
parseLxcMountPoint: function(value) {
if (!value) {
return;
}
var res = {};
var errors = false;
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
var match_res = p.match(/^([a-z_]+)=(\S+)$/);
if (!match_res) {
if (!p.match(/\=/)) {
res.file = p;
return; // continue
}
errors = true;
return false; // break
}
var k = match_res[1];
if (k === 'volume') {
k = 'file';
}
if (Ext.isDefined(res[k])) {
errors = true;
return false; // break
}
var v = match_res[2];
res[k] = v;
});
if (errors || !res.file) {
return;
}
var m = res.file.match(/^([a-z][a-z0-9\-\_\.]*[a-z0-9]):/i);
if (m) {
res.storage = m[1];
res.type = 'volume';
} else if (res.file.match(/^\/dev\//)) {
res.type = 'device';
} else {
res.type = 'bind';
}
return res;
},
printLxcMountPoint: function(mp) {
var drivestr = mp.file;
Ext.Object.each(mp, function(key, value) {
if (!Ext.isDefined(value) || key === 'file' ||
key === 'type' || key === 'storage') {
return; // continue
}
drivestr += ',' + key + '=' + value;
});
return drivestr;
},
parseStartup: function(value) {
if (value === undefined) {
return;
}
var res = {};
var errors = false;
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
var match_res;
if ((match_res = p.match(/^(order)?=(\d+)$/)) !== null) {
res.order = match_res[2];
} else if ((match_res = p.match(/^up=(\d+)$/)) !== null) {
res.up = match_res[1];
} else if ((match_res = p.match(/^down=(\d+)$/)) !== null) {
res.down = match_res[1];
} else {
errors = true;
return false; // break
}
});
if (errors) {
return;
}
return res;
},
printStartup: function(startup) {
var arr = [];
if (startup.order !== undefined && startup.order !== '') {
arr.push('order=' + startup.order);
}
if (startup.up !== undefined && startup.up !== '') {
arr.push('up=' + startup.up);
}
if (startup.down !== undefined && startup.down !== '') {
arr.push('down=' + startup.down);
}
return arr.join(',');
},
parseQemuSmbios1: function(value) {
var res = {};
Ext.Array.each(value.split(','), function(p) {
var kva = p.split('=', 2);
res[kva[0]] = kva[1];
});
return res;
},
printQemuSmbios1: function(data) {
var datastr = '';
Ext.Object.each(data, function(key, value) {
if (value === '') { return; }
datastr += (datastr !== '' ? ',' : '') + key + '=' + value;
});
return datastr;
},
parseTfaConfig: function(value) {
var res = {};
Ext.Array.each(value.split(','), function(p) {
var kva = p.split('=', 2);
res[kva[0]] = kva[1];
});
return res;
},
parseQemuCpu: function(value) {
if (!value) {
return {};
}
var res = {};
var errors = false;
Ext.Array.each(value.split(','), function(p) {
if (!p || p.match(/^\s*$/)) {
return; // continue
}
if (!p.match(/\=/)) {
if (Ext.isDefined(res.cpu)) {
errors = true;
return false; // break
}
res.cputype = p;
return; // continue
}
var match_res = p.match(/^([a-z_]+)=(\S+)$/);
if (!match_res) {
errors = true;
return false; // break
}
var k = match_res[1];
if (Ext.isDefined(res[k])) {
errors = true;
return false; // break
}
res[k] = match_res[2];
});
if (errors || !res.cputype) {
return;
}
return res;
},
printQemuCpu: function(cpu) {
var cpustr = cpu.cputype;
var optstr = '';
Ext.Object.each(cpu, function(key, value) {
if (!Ext.isDefined(value) || key === 'cputype') {
return; // continue
}
optstr += ',' + key + '=' + value;
});
if (!cpustr) {
if (optstr) {
return 'kvm64' + optstr;
}
return;
}
return cpustr + optstr;
}
}});
/* This state provider keeps part of the state inside
* the browser history.
*
* We compress (shorten) url using dictionary based compression
* i.e. use column separated list instead of url encoded hash:
* #v\d* version/format
* := indicates string values
* :\d+ lookup value in dictionary hash
* #v1:=value1:5:=value2:=value3:...
*/
Ext.define('PVE.StateProvider', {
extend: 'Ext.state.LocalStorageProvider',
// private
setHV: function(name, newvalue, fireEvents) {
var me = this;
var changes = false;
var oldtext = Ext.encode(me.UIState[name]);
var newtext = Ext.encode(newvalue);
if (newtext != oldtext) {
changes = true;
me.UIState[name] = newvalue;
//console.log("changed old " + name + " " + oldtext);
//console.log("changed new " + name + " " + newtext);
if (fireEvents) {
me.fireEvent("statechange", me, name, { value: newvalue });
}
}
return changes;
},
// private
hslist: [
// order is important for notifications
// [ name, default ]
['view', 'server'],
['rid', 'root'],
['ltab', 'tasks'],
['nodetab', ''],
['storagetab', ''],
['pooltab', ''],
['kvmtab', ''],
['lxctab', ''],
['dctab', '']
],
hprefix: 'v1',
compDict: {
monitor: 49,
'ha-fencing': 48,
'ha-groups': 47,
'ha-resources': 46,
'ceph-log': 45,
'ceph-crushmap':44,
'ceph-pools': 43,
'ceph-osdtree': 42,
'ceph-disklist': 41,
'ceph-monlist': 40,
'ceph-config': 39,
ceph: 38,
'firewall-fwlog': 37,
'firewall-options': 36,
'firewall-ipset': 35,
'firewall-aliases': 34,
'firewall-sg': 33,
firewall: 32,
apt: 31,
members: 30,
snapshot: 29,
ha: 28,
support: 27,
pools: 26,
syslog: 25,
ubc: 24,
initlog: 23,
openvz: 22,
backup: 21,
resources: 20,
content: 19,
root: 18,
domains: 17,
roles: 16,
groups: 15,
users: 14,
time: 13,
dns: 12,
network: 11,
services: 10,
options: 9,
console: 8,
hardware: 7,
permissions: 6,
summary: 5,
tasks: 4,
clog: 3,
storage: 2,
folder: 1,
server: 0
},
decodeHToken: function(token) {
var me = this;
var state = {};
if (!token) {
Ext.Array.each(me.hslist, function(rec) {
state[rec[0]] = rec[1];
});
return state;
}
// return Ext.urlDecode(token);
var items = token.split(':');
var prefix = items.shift();
if (prefix != me.hprefix) {
return me.decodeHToken();
}
Ext.Array.each(me.hslist, function(rec) {
var value = items.shift();
if (value) {
if (value[0] === '=') {
value = decodeURIComponent(value.slice(1));
} else {
Ext.Object.each(me.compDict, function(key, cv) {
if (value == cv) {
value = key;
return false;
}
});
}
}
state[rec[0]] = value;
});
return state;
},
encodeHToken: function(state) {
var me = this;
// return Ext.urlEncode(state);
var ctoken = me.hprefix;
Ext.Array.each(me.hslist, function(rec) {
var value = state[rec[0]];
if (!Ext.isDefined(value)) {
value = rec[1];
}
value = encodeURIComponent(value);
if (!value) {
ctoken += ':';
} else {
var comp = me.compDict[value];
if (Ext.isDefined(comp)) {
ctoken += ":" + comp;
} else {
ctoken += ":=" + value;
}
}
});
return ctoken;
},
constructor: function(config){
var me = this;
me.callParent([config]);
me.UIState = me.decodeHToken(); // set default
var history_change_cb = function(token) {
//console.log("HC " + token);
if (!token) {
var res = window.confirm(gettext('Are you sure you want to navigate away from this page?'));
if (res){
// process text value and close...
Ext.History.back();
} else {
Ext.History.forward();
}
return;
}
var newstate = me.decodeHToken(token);
Ext.Array.each(me.hslist, function(rec) {
if (typeof newstate[rec[0]] == "undefined") {
return;
}
me.setHV(rec[0], newstate[rec[0]], true);
});
};
var start_token = Ext.History.getToken();
if (start_token) {
history_change_cb(start_token);
} else {
var htext = me.encodeHToken(me.UIState);
Ext.History.add(htext);
}
Ext.History.on('change', history_change_cb);
},
get: function(name, defaultValue){
/*jslint confusion: true */
var me = this;
var data;
if (typeof me.UIState[name] != "undefined") {
data = { value: me.UIState[name] };
} else {
data = me.callParent(arguments);
if (!data && name === 'GuiCap') {
data = { vms: {}, storage: {}, access: {}, nodes: {}, dc: {} };
}
}
//console.log("GET " + name + " " + Ext.encode(data));
return data;
},
clear: function(name){
var me = this;
if (typeof me.UIState[name] != "undefined") {
me.UIState[name] = null;
}
me.callParent(arguments);
},
set: function(name, value){
var me = this;
//console.log("SET " + name + " " + Ext.encode(value));
if (typeof me.UIState[name] != "undefined") {
var newvalue = value ? value.value : null;
if (me.setHV(name, newvalue, false)) {
var htext = me.encodeHToken(me.UIState);
Ext.History.add(htext);
}
} else {
me.callParent(arguments);
}
}
});
/* Button features:
* - observe selection changes to enable/disable the button using enableFn()
* - pop up confirmation dialog using confirmMsg()
*/
Ext.define('PVE.button.Button', {
extend: 'Ext.button.Button',
alias: 'widget.pveButton',
// the selection model to observe
selModel: undefined,
// if 'false' handler will not be called (button disabled)
enableFn: function(record) { },
// function(record) or text
confirmMsg: false,
// take special care in confirm box (select no as default).
dangerous: false,
initComponent: function() {
/*jslint confusion: true */
var me = this;
if (me.handler) {
me.realHandler = me.handler;
me.handler = function(button, event) {
var rec, msg;
if (me.selModel) {
rec = me.selModel.getSelection()[0];
if (!rec || (me.enableFn(rec) === false)) {
return;
}
}
if (me.confirmMsg) {
msg = me.confirmMsg;
if (Ext.isFunction(me.confirmMsg)) {
msg = me.confirmMsg(rec);
}
Ext.MessageBox.defaultButton = me.dangerous ? 2 : 1;
Ext.Msg.show({
title: gettext('Confirm'),
icon: me.dangerous ? Ext.Msg.WARNING : Ext.Msg.QUESTION,
msg: msg,
buttons: Ext.Msg.YESNO,
callback: function(btn) {
if (btn !== 'yes') {
return;
}
me.realHandler(button, event, rec);
}
});
} else {
me.realHandler(button, event, rec);
}
};
}
me.callParent();
if (me.selModel) {
me.mon(me.selModel, "selectionchange", function() {
var rec = me.selModel.getSelection()[0];
if (!rec || (me.enableFn(rec) === false)) {
me.setDisabled(true);
} else {
me.setDisabled(false);
}
});
}
}
});
Ext.define('PVE.button.ConsoleButton', {
extend: 'Ext.button.Split',
alias: 'widget.pveConsoleButton',
consoleType: 'shell', // one of 'shell', 'kvm', 'lxc', 'upgrade'
consoleName: undefined,
enableSpice: true,
nodename: undefined,
vmid: 0,
setEnableSpice: function(enable){
var me = this;
me.enableSpice = enable;
me.spiceMenu.setDisabled(!enable);
},
initComponent: function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
me.spiceMenu = Ext.create('Ext.menu.Item', {
text: 'SPICE',
iconCls: 'pve-itype-icon-virt-viewer',
handler: function() {
PVE.Utils.openConsoleWindow('vv', me.consoleType, me.vmid, me.nodename, me.consoleName);
}
});
var noVncMenu = Ext.create('Ext.menu.Item', {
text: 'noVNC',
iconCls: 'pve-itype-icon-novnc',
handler: function() {
PVE.Utils.openConsoleWindow('html5', me.consoleType, me.vmid, me.nodename, me.consoleName);
}
});
if (me.text === null) {
me.text = gettext('Console');
}
Ext.apply(me, {
handler: function() {
PVE.Utils.openDefaultConsoleWindow(me.enableSpice, me.consoleType, me.vmid,
me.nodename, me.consoleName);
},
menu: new Ext.menu.Menu({
items: [ noVncMenu, me.spiceMenu ]
})
});
me.callParent();
}
});
/* Button features:
* - observe selection changes to enable/disable the button using enableFn()
* - pop up confirmation dialog using confirmMsg()
*
* does this for the button and every menu item
*/
Ext.define('PVE.button.Split', {
extend: 'Ext.button.Split',
alias: 'widget.pveSplitButton',
// the selection model to observe
selModel: undefined,
// if 'false' handler will not be called (button disabled)
enableFn: function(record) { },
// function(record) or text
confirmMsg: false,
// take special care in confirm box (select no as default).
dangerous: false,
handlerWrapper: function(button, event) {
var me = this;
var rec, msg;
if (me.selModel) {
rec = me.selModel.getSelection()[0];
if (!rec || (me.enableFn(rec) === false)) {
return;
}
}
if (me.confirmMsg) {
msg = me.confirmMsg;
// confirMsg can be boolean or function
/*jslint confusion: true*/
if (Ext.isFunction(me.confirmMsg)) {
msg = me.confirmMsg(rec);
}
/*jslint confusion: false*/
Ext.MessageBox.defaultButton = me.dangerous ? 2 : 1;
Ext.Msg.show({
title: gettext('Confirm'),
icon: me.dangerous ? Ext.Msg.WARNING : Ext.Msg.QUESTION,
msg: msg,
buttons: Ext.Msg.YESNO,
callback: function(btn) {
if (btn !== 'yes') {
return;
}
me.realHandler(button, event, rec);
}
});
} else {
me.realHandler(button, event, rec);
}
},
initComponent: function() {
/*jslint confusion: true */
var me = this;
if (me.handler) {
me.realHandler = me.handler;
me.handler = me.handlerWrapper;
}
if (me.menu && me.menu.items) {
me.menu.items.forEach(function(item) {
if (item.handler) {
item.realHandler = item.handler;
item.handler = me.handlerWrapper;
}
if (item.selModel) {
me.mon(item.selModel, "selectionchange", function() {
var rec = item.selModel.getSelection()[0];
if (!rec || (item.enableFn(rec) === false )) {
item.setDisabled(true);
} else {
item.setDisabled(false);
}
});
}
});
}
me.callParent();
if (me.selModel) {
me.mon(me.selModel, "selectionchange", function() {
var rec = me.selModel.getSelection()[0];
if (!rec || (me.enableFn(rec) === false)) {
me.setDisabled(true);
} else {
me.setDisabled(false);
}
});
}
}
});
/* help button pointing to an online documentation
for components contained in a modal window
*/
Ext.define('PVE.button.Help', {
extend: 'Ext.button.Button',
alias: 'widget.pveHelpButton',
text: gettext('Help'),
// make help button less flashy by styling it like toolbar buttons
iconCls: ' x-btn-icon-el-default-toolbar-small fa fa-question-circle',
cls: 'x-btn-default-toolbar-small pve-help-button',
hidden: true,
controller: {
xclass: 'Ext.app.ViewController',
listen: {
global: {
pveShowHelp: 'onPveShowHelp',
pveHideHelp: 'onPveHideHelp'
}
},
onPveShowHelp: function(helpLink) {
this.getView().setHandler(function() {
var docsURI = window.location.origin +
'/pve-docs/' + helpLink;
window.open(docsURI);
});
this.getView().show();
},
onPveHideHelp: function() {
this.getView().hide();
}
}
});Ext.define('PVE.qemu.SendKeyMenu', {
extend: 'Ext.button.Button',
alias: ['widget.pveQemuSendKeyMenu'],
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var sendKey = function(key) {
PVE.Utils.API2Request({
params: { key: key },
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/sendkey",
method: 'PUT',
waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
};
Ext.apply(me, {
text: 'SendKey',
menu: new Ext.menu.Menu({
height: 200,
items: [
{
text: 'Tab', handler: function() {
sendKey('tab');
}
},
{
text: 'Ctrl-Alt-Delete', handler: function() {
sendKey('ctrl-alt-delete');
}
},
{
text: 'Ctrl-Alt-Backspace', handler: function() {
sendKey('ctrl-alt-backspace');
}
},
{
text: 'Ctrl-Alt-F1', handler: function() {
sendKey('ctrl-alt-f1');
}
},
{
text: 'Ctrl-Alt-F2', handler: function() {
sendKey('ctrl-alt-f2');
}
},
{
text: 'Ctrl-Alt-F3', handler: function() {
sendKey('ctrl-alt-f3');
}
},
{
text: 'Ctrl-Alt-F4', handler: function() {
sendKey('ctrl-alt-f4');
}
},
{
text: 'Ctrl-Alt-F5', handler: function() {
sendKey('ctrl-alt-f5');
}
},
{
text: 'Ctrl-Alt-F6', handler: function() {
sendKey('ctrl-alt-f6');
}
},
{
text: 'Ctrl-Alt-F7', handler: function() {
sendKey('ctrl-alt-f7');
}
},
{
text: 'Ctrl-Alt-F8', handler: function() {
sendKey('ctrl-alt-f8');
}
},
{
text: 'Ctrl-Alt-F9', handler: function() {
sendKey('ctrl-alt-f9');
}
},
{
text: 'Ctrl-Alt-F10', handler: function() {
sendKey('ctrl-alt-f10');
}
},
{
text: 'Ctrl-Alt-F11', handler: function() {
sendKey('ctrl-alt-f11');
}
},
{
text: 'Ctrl-Alt-F12', handler: function() {
sendKey('ctrl-alt-f12');
}
}
]
})
});
me.callParent();
}
});
Ext.define('PVE.qemu.CmdMenu', {
extend: 'Ext.menu.Menu',
showSeparator: false,
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var vmname = me.pveSelNode.data.name;
var vm_command = function(cmd, params) {
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + nodename + '/qemu/' + vmid + "/status/" + cmd,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
};
var running = false;
var stopped = true;
var suspended = false;
switch (me.pveSelNode.data.status) {
case 'running':
running = true;
stopped = false;
break;
case 'paused':
stopped = false;
suspended = true;
break;
default: break;
}
me.title = "VM " + vmid;
me.items = [
{
text: gettext('Start'),
iconCls: 'fa fa-fw fa-play',
disabled: running || suspended,
handler: function() {
vm_command('start');
}
},
{
text: gettext('Suspend'),
iconCls: 'fa fa-fw fa-pause',
hidden: suspended,
disabled: stopped || suspended,
handler: function() {
var msg = PVE.Utils.format_task_description('qmsuspend', vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command('suspend');
});
}
},
{
text: gettext('Resume'),
iconCls: 'fa fa-fw fa-play',
hidden: !suspended,
handler: function() {
vm_command('resume');
}
},
{
text: gettext('Shutdown'),
iconCls: 'fa fa-fw fa-power-off',
disabled: stopped || suspended,
handler: function() {
var msg = PVE.Utils.format_task_description('qmshutdown', vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command('shutdown');
});
}
},
{
text: gettext('Stop'),
iconCls: 'fa fa-fw fa-stop',
disabled: stopped,
handler: function() {
var msg = PVE.Utils.format_task_description('qmstop', vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("stop");
});
}
},
{ xtype: 'menuseparator' },
{
text: gettext('Migrate'),
iconCls: 'fa fa-fw fa-send-o',
handler: function() {
var win = Ext.create('PVE.window.Migrate', {
vmtype: 'qemu',
nodename: nodename,
vmid: vmid
});
win.show();
}
},
{
text: gettext('Clone'),
iconCls: 'fa fa-fw fa-clone',
handler: function() {
var win = Ext.create('PVE.window.Clone', {
nodename: nodename,
vmid: vmid
});
win.show();
}
},
{
text: gettext('Convert to template'),
iconCls: 'fa fa-fw fa-file-o',
handler: function() {
var msg = PVE.Utils.format_task_description('qmtemplate', vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
PVE.Utils.API2Request({
url: '/nodes/' + nodename + '/qemu/' + vmid + '/template',
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
});
}
},
{ xtype: 'menuseparator' },
{
text: gettext('Console'),
iconCls: 'fa fa-fw fa-terminal',
handler: function() {
PVE.Utils.API2Request({
url: '/nodes/' + nodename + '/qemu/' + vmid + '/status/current',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response, opts) {
var allowSpice = response.result.data.spice;
PVE.Utils.openDefaultConsoleWindow(allowSpice, 'kvm', vmid, nodename, vmname);
}
});
}
}
];
me.callParent();
}
});
Ext.define('PVE.qemu.TemplateMenu', {
extend: 'Ext.menu.Menu',
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var vmname = me.pveSelNode.data.name;
var template = me.pveSelNode.data.template;
var vm_command = function(cmd, params) {
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + nodename + '/qemu/' + vmid + "/status/" + cmd,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
};
me.title = "VM " + vmid;
me.items = [
{
text: gettext('Migrate'),
iconCls: 'fa fa-fw fa-send-o',
handler: function() {
var win = Ext.create('PVE.window.Migrate', {
vmtype: 'qemu',
nodename: nodename,
vmid: vmid
});
win.show();
}
},
{
text: gettext('Clone'),
iconCls: 'fa fa-fw fa-clone',
handler: function() {
var win = Ext.create('PVE.window.Clone', {
nodename: nodename,
vmid: vmid,
isTemplate: template
});
win.show();
}
}
];
me.callParent();
}
});
Ext.define('PVE.lxc.CmdMenu', {
extend: 'Ext.menu.Menu',
showSeparator: false,
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no CT ID specified";
}
var vmname = me.pveSelNode.data.name;
var vm_command = function(cmd, params) {
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + nodename + '/lxc/' + vmid + "/status/" + cmd,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
};
var running = false;
var stopped = true;
var suspended = false;
switch (me.pveSelNode.data.status) {
case 'running':
running = true;
stopped = false;
break;
case 'paused':
stopped = false;
suspended = true;
break;
default: break;
}
me.title = 'CT ' + vmid;
me.items = [
{
text: gettext('Start'),
iconCls: 'fa fa-fw fa-play',
disabled: running,
handler: function() {
vm_command('start');
}
},
// {
// text: gettext('Suspend'),
// iconCls: 'fa fa-fw fa-pause',
// hidde: suspended,
// disabled: stopped || suspended,
// handler: function() {
// var msg = PVE.Utils.format_task_description('vzsuspend', vmid);
// Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
// if (btn !== 'yes') {
// return;
// }
//
// vm_command('suspend');
// });
// }
// },
// {
// text: gettext('Resume'),
// iconCls: 'fa fa-fw fa-play',
// hidden: !suspended,
// handler: function() {
// vm_command('resume');
// }
// },
{
text: gettext('Shutdown'),
iconCls: 'fa fa-fw fa-power-off',
disabled: stopped || suspended,
handler: function() {
var msg = PVE.Utils.format_task_description('vzshutdown', vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command('shutdown');
});
}
},
{
text: gettext('Stop'),
iconCls: 'fa fa-fw fa-stop',
disabled: stopped,
handler: function() {
var msg = PVE.Utils.format_task_description('vzstop', vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("stop");
});
}
},
{ xtype: 'menuseparator' },
{
text: gettext('Migrate'),
iconCls: 'fa fa-fw fa-send-o',
handler: function() {
var win = Ext.create('PVE.window.Migrate', {
vmtype: 'lxc',
nodename: nodename,
vmid: vmid
});
win.show();
}
},
// {
// text: gettext('Convert to template'),
// icon: '/pve2/images/forward.png',
// handler: function() {
// var msg = PVE.Utils.format_task_description('vztemplate', vmid);
// Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
// if (btn !== 'yes') {
// return;
// }
//
// PVE.Utils.API2Request({
// url: '/nodes/' + nodename + '/lxc/' + vmid + '/template',
// method: 'POST',
// failure: function(response, opts) {
// Ext.Msg.alert('Error', response.htmlStatus);
// }
// });
// });
// }
// },
{ xtype: 'menuseparator' },
{
text: gettext('Console'),
iconCls: 'fa fa-fw fa-terminal',
handler: function() {
PVE.Utils.openDefaultConsoleWindow(true, 'lxc', vmid, nodename, vmname);
}
}
];
me.callParent();
}
});
Ext.define('PVE.noVncConsole', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveNoVncConsole',
nodename: undefined,
vmid: undefined,
consoleType: undefined, // lxc or kvm
layout: 'fit',
border: false,
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.consoleType) {
throw "no console type specified";
}
if (!me.vmid && me.consoleType !== 'shell') {
throw "no VM ID specified";
}
// always use same iframe, to avoid running several noVnc clients
// at same time (to avoid performance problems)
var box = Ext.create('Ext.ux.IFrame', { itemid : "vncconsole" });
Ext.apply(me, {
items: box,
listeners: {
activate: function() {
var url = '/?console=' + me.consoleType + '&novnc=1&node=' + me.nodename + '&resize=scale';
if (me.vmid) {
url += '&vmid='+ me.vmid;
}
box.load(url);
}
}
});
me.callParent();
}
});
Ext.define('PVE.VNCConsole', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveVNCConsole',
last_novnc_state: undefined,
last_novnc_msg: undefined,
layout: 'fit',
border: false,
detectMigratedVM: function() {
var me = this;
if (!me.vmid) {
return;
}
// try to detect migrated VM
PVE.Utils.API2Request({
url: '/cluster/resources',
method: 'GET',
success: function(response) {
var list = response.result.data;
Ext.Array.each(list, function(item) {
if (item.type === 'qemu' && item.vmid == me.vmid) {
if (item.node !== me.nodename) {
me.nodename = item.node;
me.url = "/nodes/" + me.nodename + "/" + item.type + "/" + me.vmid + "/vncproxy";
me.wsurl = "/nodes/" + me.nodename + "/" + item.type + "/" + me.vmid + "/vncwebsocket";
me.reloadApplet();
}
return false; // break
}
});
}
});
},
initComponent : function() {
var me = this;
if (!me.url) {
throw "no url specified";
}
var myid = me.id + "-vncapp";
me.appletID = myid;
var box;
if (!me.wsurl) {
throw "no web socket url specified";
}
box = Ext.create('Ext.ux.IFrame', { id: myid });
var resize_window = function() {
//console.log("resize");
var aw;
var ah;
var novnciframe = box.getFrame();
// noVNC_canvas
var innerDoc = novnciframe.contentDocument || novnciframe.contentWindow.document;
aw = innerDoc.getElementById('noVNC_canvas').width;
ah = innerDoc.getElementById('noVNC_canvas').height + 8;
var novnc_state = innerDoc.getElementById('noVNC_status_state').innerHTML;
var novnc_msg = innerDoc.getElementById('noVNC_status_msg').innerHTML;
if (novnc_state !== me.last_novnc_state || novnc_msg !== me.last_novnc_msg) {
me.last_novnc_state = novnc_state;
me.last_novnc_msg = novnc_msg;
if (novnc_state !== 'normal') {
PVE.Utils.setErrorMask(box, novnc_msg || 'unknown');
} else {
PVE.Utils.setErrorMask(box); // clear mask
}
if (novnc_state === 'disconnected') {
me.detectMigratedVM();
}
}
if (aw < 640) { aw = 640; }
if (ah < 400) { ah = 400; }
var tbar = me.getDockedItems("[dock=top]")[0];
var tbh = tbar ? tbar.getHeight() : 0;
var oh;
var ow;
//console.log("size0 " + aw + " " + ah + " tbh " + tbh);
if (window.innerHeight) {
oh = window.innerHeight;
ow = window.innerWidth;
} else if (document.documentElement &&
document.documentElement.clientHeight) {
oh = document.documentElement.clientHeight;
ow = document.documentElement.clientWidth;
} else if (document.body) {
oh = document.body.clientHeight;
ow = document.body.clientWidth;
} else {
throw "can't get window size";
}
var offsetw = aw - ow;
var offseth = ah + tbh - oh;
if (offsetw !== 0 || offseth !== 0) {
//console.log("try resize by " + offsetw + " " + offseth);
try { window.resizeBy(offsetw, offseth); } catch (e) {}
}
Ext.Function.defer(resize_window, 1000);
};
var start_vnc_viewer = function(param) {
var pveparams = Ext.urlEncode({
port: param.port,
vncticket: param.ticket
});
var urlparams = Ext.urlEncode({
encrypt: 1,
path: "api2/json" + me.wsurl + "?" + pveparams,
password: param.ticket
});
box.load('/novnc/vnc_pve.html?' + urlparams);
Ext.Function.defer(resize_window, 1000);
};
Ext.apply(me, {
scrollable: me.toplevel ? false : true,
items: box,
reloadApplet: function() {
var params = Ext.apply({}, me.params);
params.websocket = 1;
PVE.Utils.API2Request({
url: me.url,
params: params,
method: me.method || 'POST',
failure: function(response, opts) {
box.update(gettext('Error') + ' ' + response.htmlStatus);
},
success: function(response, opts) {
start_vnc_viewer(response.result.data);
}
});
}
});
me.callParent();
if (me.toplevel) {
me.on("render", me.reloadApplet);
} else {
me.on("activate", me.reloadApplet);
me.on("hide", function() { box.update(""); });
}
}
});
Ext.define('PVE.KVMConsole', {
extend: 'PVE.VNCConsole',
alias: 'widget.pveKVMConsole',
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var baseUrl = "/nodes/" + me.nodename + "/qemu/" + me.vmid;
var vm_command = function(cmd, params, reload_applet) {
PVE.Utils.API2Request({
params: params,
url: baseUrl + "/status/" + cmd,
method: 'POST',
waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function() {
if (reload_applet) {
Ext.Function.defer(me.reloadApplet, 1000, me);
}
}
});
};
var tbar = [
{
text: gettext('Start'),
handler: function() {
vm_command("start", {}, 1);
}
},
{
text: gettext('Shutdown'),
handler: function() {
var msg = PVE.Utils.format_task_description('qmshutdown', me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command('shutdown');
});
}
},
{
text: gettext('Stop'),
handler: function() {
var msg = PVE.Utils.format_task_description('qmstop', me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("stop");
});
}
},
{
xtype: 'pveQemuSendKeyMenu',
nodename: me.nodename,
vmid: me.vmid
},
{
text: gettext('Reset'),
handler: function() {
var msg = PVE.Utils.format_task_description('qmreset', me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("reset");
});
}
},
{
text: gettext('Suspend'),
handler: function() {
var msg = PVE.Utils.format_task_description('qmsuspend', me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("suspend");
});
}
},
{
text: gettext('Resume'),
handler: function() {
vm_command("resume");
}
},
// Note: no migrate here, because we can't display migrate log
{
text: gettext('Console'),
handler: function() {
PVE.Utils.openVNCViewer('kvm', me.vmid, me.nodename, me.vmname);
}
},
'->',
{
text: gettext('Reload'),
handler: function () {
me.reloadApplet();
}
}
];
Ext.apply(me, {
tbar: tbar,
url: baseUrl + "/vncproxy",
wsurl: baseUrl + "/vncwebsocket"
});
me.callParent();
}
});
Ext.define('PVE.LxcConsole', {
extend: 'PVE.VNCConsole',
alias: 'widget.pveLxcConsole',
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var baseUrl = "/nodes/" + me.nodename + "/lxc/" + me.vmid;
var vm_command = function(cmd, params, reload_applet) {
PVE.Utils.API2Request({
params: params,
url: baseUrl + "/status/" + cmd,
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function() {
if (reload_applet) {
Ext.Function.defer(me.reloadApplet, 1000, me);
}
}
});
};
var tbar = [
{
text: gettext('Start'),
handler: function() {
vm_command("start");
}
},
{
text: gettext('Shutdown'),
handler: function() {
var msg = PVE.Utils.format_task_description('vzshutdown', me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("shutdown");
});
}
},
{
text: gettext('Stop'),
handler: function() {
var msg = PVE.Utils.format_task_description('vzstop', me.vmid);
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
vm_command("stop");
});
}
},
// Note: no migrate here, because we can't display migrate log
'->',
{
text: gettext('Reload'),
handler: function () {
me.reloadApplet();
}
}
];
Ext.apply(me, {
tbar: tbar,
url: baseUrl + "/vncproxy",
wsurl: baseUrl + "/vncwebsocket"
});
me.callParent();
}
});
Ext.define('PVE.Shell', {
extend: 'PVE.VNCConsole',
alias: 'widget.pveShell',
ugradeSystem: false, // set to true to run "apt-get dist-upgrade"
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
var tbar = [ '->' ];
if (!me.ugradeSystem) {
// we dont want to restart the upgrade script
tbar.push({
text: gettext('Reload'),
handler: function () { me.reloadApplet(); }
});
}
tbar.push({
text: gettext('Shell'),
handler: function() {
PVE.Utils.openVNCViewer('shell', undefined, me.nodename, undefined);
}
});
var baseUrl = "/nodes/" + me.nodename;
Ext.apply(me, {
tbar: tbar,
url: baseUrl + "/vncshell",
wsurl: baseUrl + "/vncwebsocket"
});
if (me.ugradeSystem) {
me.params = { upgrade: 1 };
}
me.callParent();
}
});
Ext.define('Timezone', {
extend: 'Ext.data.Model',
fields: ['zone']
});
Ext.define('PVE.data.TimezoneStore', {
extend: 'Ext.data.Store',
model: 'Timezone',
data: [
['Africa/Abidjan'],
['Africa/Accra'],
['Africa/Addis_Ababa'],
['Africa/Algiers'],
['Africa/Asmara'],
['Africa/Bamako'],
['Africa/Bangui'],
['Africa/Banjul'],
['Africa/Bissau'],
['Africa/Blantyre'],
['Africa/Brazzaville'],
['Africa/Bujumbura'],
['Africa/Cairo'],
['Africa/Casablanca'],
['Africa/Ceuta'],
['Africa/Conakry'],
['Africa/Dakar'],
['Africa/Dar_es_Salaam'],
['Africa/Djibouti'],
['Africa/Douala'],
['Africa/El_Aaiun'],
['Africa/Freetown'],
['Africa/Gaborone'],
['Africa/Harare'],
['Africa/Johannesburg'],
['Africa/Kampala'],
['Africa/Khartoum'],
['Africa/Kigali'],
['Africa/Kinshasa'],
['Africa/Lagos'],
['Africa/Libreville'],
['Africa/Lome'],
['Africa/Luanda'],
['Africa/Lubumbashi'],
['Africa/Lusaka'],
['Africa/Malabo'],
['Africa/Maputo'],
['Africa/Maseru'],
['Africa/Mbabane'],
['Africa/Mogadishu'],
['Africa/Monrovia'],
['Africa/Nairobi'],
['Africa/Ndjamena'],
['Africa/Niamey'],
['Africa/Nouakchott'],
['Africa/Ouagadougou'],
['Africa/Porto-Novo'],
['Africa/Sao_Tome'],
['Africa/Tripoli'],
['Africa/Tunis'],
['Africa/Windhoek'],
['America/Adak'],
['America/Anchorage'],
['America/Anguilla'],
['America/Antigua'],
['America/Araguaina'],
['America/Argentina/Buenos_Aires'],
['America/Argentina/Catamarca'],
['America/Argentina/Cordoba'],
['America/Argentina/Jujuy'],
['America/Argentina/La_Rioja'],
['America/Argentina/Mendoza'],
['America/Argentina/Rio_Gallegos'],
['America/Argentina/Salta'],
['America/Argentina/San_Juan'],
['America/Argentina/San_Luis'],
['America/Argentina/Tucuman'],
['America/Argentina/Ushuaia'],
['America/Aruba'],
['America/Asuncion'],
['America/Atikokan'],
['America/Bahia'],
['America/Bahia_Banderas'],
['America/Barbados'],
['America/Belem'],
['America/Belize'],
['America/Blanc-Sablon'],
['America/Boa_Vista'],
['America/Bogota'],
['America/Boise'],
['America/Cambridge_Bay'],
['America/Campo_Grande'],
['America/Cancun'],
['America/Caracas'],
['America/Cayenne'],
['America/Cayman'],
['America/Chicago'],
['America/Chihuahua'],
['America/Costa_Rica'],
['America/Cuiaba'],
['America/Curacao'],
['America/Danmarkshavn'],
['America/Dawson'],
['America/Dawson_Creek'],
['America/Denver'],
['America/Detroit'],
['America/Dominica'],
['America/Edmonton'],
['America/Eirunepe'],
['America/El_Salvador'],
['America/Fortaleza'],
['America/Glace_Bay'],
['America/Godthab'],
['America/Goose_Bay'],
['America/Grand_Turk'],
['America/Grenada'],
['America/Guadeloupe'],
['America/Guatemala'],
['America/Guayaquil'],
['America/Guyana'],
['America/Halifax'],
['America/Havana'],
['America/Hermosillo'],
['America/Indiana/Indianapolis'],
['America/Indiana/Knox'],
['America/Indiana/Marengo'],
['America/Indiana/Petersburg'],
['America/Indiana/Tell_City'],
['America/Indiana/Vevay'],
['America/Indiana/Vincennes'],
['America/Indiana/Winamac'],
['America/Inuvik'],
['America/Iqaluit'],
['America/Jamaica'],
['America/Juneau'],
['America/Kentucky/Louisville'],
['America/Kentucky/Monticello'],
['America/La_Paz'],
['America/Lima'],
['America/Los_Angeles'],
['America/Maceio'],
['America/Managua'],
['America/Manaus'],
['America/Marigot'],
['America/Martinique'],
['America/Matamoros'],
['America/Mazatlan'],
['America/Menominee'],
['America/Merida'],
['America/Mexico_City'],
['America/Miquelon'],
['America/Moncton'],
['America/Monterrey'],
['America/Montevideo'],
['America/Montreal'],
['America/Montserrat'],
['America/Nassau'],
['America/New_York'],
['America/Nipigon'],
['America/Nome'],
['America/Noronha'],
['America/North_Dakota/Center'],
['America/North_Dakota/New_Salem'],
['America/Ojinaga'],
['America/Panama'],
['America/Pangnirtung'],
['America/Paramaribo'],
['America/Phoenix'],
['America/Port-au-Prince'],
['America/Port_of_Spain'],
['America/Porto_Velho'],
['America/Puerto_Rico'],
['America/Rainy_River'],
['America/Rankin_Inlet'],
['America/Recife'],
['America/Regina'],
['America/Resolute'],
['America/Rio_Branco'],
['America/Santa_Isabel'],
['America/Santarem'],
['America/Santiago'],
['America/Santo_Domingo'],
['America/Sao_Paulo'],
['America/Scoresbysund'],
['America/Shiprock'],
['America/St_Barthelemy'],
['America/St_Johns'],
['America/St_Kitts'],
['America/St_Lucia'],
['America/St_Thomas'],
['America/St_Vincent'],
['America/Swift_Current'],
['America/Tegucigalpa'],
['America/Thule'],
['America/Thunder_Bay'],
['America/Tijuana'],
['America/Toronto'],
['America/Tortola'],
['America/Vancouver'],
['America/Whitehorse'],
['America/Winnipeg'],
['America/Yakutat'],
['America/Yellowknife'],
['Antarctica/Casey'],
['Antarctica/Davis'],
['Antarctica/DumontDUrville'],
['Antarctica/Macquarie'],
['Antarctica/Mawson'],
['Antarctica/McMurdo'],
['Antarctica/Palmer'],
['Antarctica/Rothera'],
['Antarctica/South_Pole'],
['Antarctica/Syowa'],
['Antarctica/Vostok'],
['Arctic/Longyearbyen'],
['Asia/Aden'],
['Asia/Almaty'],
['Asia/Amman'],
['Asia/Anadyr'],
['Asia/Aqtau'],
['Asia/Aqtobe'],
['Asia/Ashgabat'],
['Asia/Baghdad'],
['Asia/Bahrain'],
['Asia/Baku'],
['Asia/Bangkok'],
['Asia/Beirut'],
['Asia/Bishkek'],
['Asia/Brunei'],
['Asia/Choibalsan'],
['Asia/Chongqing'],
['Asia/Colombo'],
['Asia/Damascus'],
['Asia/Dhaka'],
['Asia/Dili'],
['Asia/Dubai'],
['Asia/Dushanbe'],
['Asia/Gaza'],
['Asia/Harbin'],
['Asia/Ho_Chi_Minh'],
['Asia/Hong_Kong'],
['Asia/Hovd'],
['Asia/Irkutsk'],
['Asia/Jakarta'],
['Asia/Jayapura'],
['Asia/Jerusalem'],
['Asia/Kabul'],
['Asia/Kamchatka'],
['Asia/Karachi'],
['Asia/Kashgar'],
['Asia/Kathmandu'],
['Asia/Kolkata'],
['Asia/Krasnoyarsk'],
['Asia/Kuala_Lumpur'],
['Asia/Kuching'],
['Asia/Kuwait'],
['Asia/Macau'],
['Asia/Magadan'],
['Asia/Makassar'],
['Asia/Manila'],
['Asia/Muscat'],
['Asia/Nicosia'],
['Asia/Novokuznetsk'],
['Asia/Novosibirsk'],
['Asia/Omsk'],
['Asia/Oral'],
['Asia/Phnom_Penh'],
['Asia/Pontianak'],
['Asia/Pyongyang'],
['Asia/Qatar'],
['Asia/Qyzylorda'],
['Asia/Rangoon'],
['Asia/Riyadh'],
['Asia/Sakhalin'],
['Asia/Samarkand'],
['Asia/Seoul'],
['Asia/Shanghai'],
['Asia/Singapore'],
['Asia/Taipei'],
['Asia/Tashkent'],
['Asia/Tbilisi'],
['Asia/Tehran'],
['Asia/Thimphu'],
['Asia/Tokyo'],
['Asia/Ulaanbaatar'],
['Asia/Urumqi'],
['Asia/Vientiane'],
['Asia/Vladivostok'],
['Asia/Yakutsk'],
['Asia/Yekaterinburg'],
['Asia/Yerevan'],
['Atlantic/Azores'],
['Atlantic/Bermuda'],
['Atlantic/Canary'],
['Atlantic/Cape_Verde'],
['Atlantic/Faroe'],
['Atlantic/Madeira'],
['Atlantic/Reykjavik'],
['Atlantic/South_Georgia'],
['Atlantic/St_Helena'],
['Atlantic/Stanley'],
['Australia/Adelaide'],
['Australia/Brisbane'],
['Australia/Broken_Hill'],
['Australia/Currie'],
['Australia/Darwin'],
['Australia/Eucla'],
['Australia/Hobart'],
['Australia/Lindeman'],
['Australia/Lord_Howe'],
['Australia/Melbourne'],
['Australia/Perth'],
['Australia/Sydney'],
['Europe/Amsterdam'],
['Europe/Andorra'],
['Europe/Athens'],
['Europe/Belgrade'],
['Europe/Berlin'],
['Europe/Bratislava'],
['Europe/Brussels'],
['Europe/Bucharest'],
['Europe/Budapest'],
['Europe/Chisinau'],
['Europe/Copenhagen'],
['Europe/Dublin'],
['Europe/Gibraltar'],
['Europe/Guernsey'],
['Europe/Helsinki'],
['Europe/Isle_of_Man'],
['Europe/Istanbul'],
['Europe/Jersey'],
['Europe/Kaliningrad'],
['Europe/Kiev'],
['Europe/Lisbon'],
['Europe/Ljubljana'],
['Europe/London'],
['Europe/Luxembourg'],
['Europe/Madrid'],
['Europe/Malta'],
['Europe/Mariehamn'],
['Europe/Minsk'],
['Europe/Monaco'],
['Europe/Moscow'],
['Europe/Oslo'],
['Europe/Paris'],
['Europe/Podgorica'],
['Europe/Prague'],
['Europe/Riga'],
['Europe/Rome'],
['Europe/Samara'],
['Europe/San_Marino'],
['Europe/Sarajevo'],
['Europe/Simferopol'],
['Europe/Skopje'],
['Europe/Sofia'],
['Europe/Stockholm'],
['Europe/Tallinn'],
['Europe/Tirane'],
['Europe/Uzhgorod'],
['Europe/Vaduz'],
['Europe/Vatican'],
['Europe/Vienna'],
['Europe/Vilnius'],
['Europe/Volgograd'],
['Europe/Warsaw'],
['Europe/Zagreb'],
['Europe/Zaporozhye'],
['Europe/Zurich'],
['Indian/Antananarivo'],
['Indian/Chagos'],
['Indian/Christmas'],
['Indian/Cocos'],
['Indian/Comoro'],
['Indian/Kerguelen'],
['Indian/Mahe'],
['Indian/Maldives'],
['Indian/Mauritius'],
['Indian/Mayotte'],
['Indian/Reunion'],
['Pacific/Apia'],
['Pacific/Auckland'],
['Pacific/Chatham'],
['Pacific/Chuuk'],
['Pacific/Easter'],
['Pacific/Efate'],
['Pacific/Enderbury'],
['Pacific/Fakaofo'],
['Pacific/Fiji'],
['Pacific/Funafuti'],
['Pacific/Galapagos'],
['Pacific/Gambier'],
['Pacific/Guadalcanal'],
['Pacific/Guam'],
['Pacific/Honolulu'],
['Pacific/Johnston'],
['Pacific/Kiritimati'],
['Pacific/Kosrae'],
['Pacific/Kwajalein'],
['Pacific/Majuro'],
['Pacific/Marquesas'],
['Pacific/Midway'],
['Pacific/Nauru'],
['Pacific/Niue'],
['Pacific/Norfolk'],
['Pacific/Noumea'],
['Pacific/Pago_Pago'],
['Pacific/Palau'],
['Pacific/Pitcairn'],
['Pacific/Pohnpei'],
['Pacific/Port_Moresby'],
['Pacific/Rarotonga'],
['Pacific/Saipan'],
['Pacific/Tahiti'],
['Pacific/Tarawa'],
['Pacific/Tongatapu'],
['Pacific/Wake'],
['Pacific/Wallis']
]
});
/* A reader to store a single JSON Object (hash) into a storage.
* Also accepts an array containing a single hash.
*
* So it can read:
*
* example1: {data1: "xyz", data2: "abc"}
* returns [{key: "data1", value: "xyz"}, {key: "data2", value: "abc"}]
*
* example2: [ {data1: "xyz", data2: "abc"} ]
* returns [{key: "data1", value: "xyz"}, {key: "data2", value: "abc"}]
*
* If you set 'readArray', the reader expexts the object as array:
*
* example3: [ { key: "data1", value: "xyz", p2: "cde" }, { key: "data2", value: "abc", p2: "efg" }]
* returns [{key: "data1", value: "xyz", p2: "cde}, {key: "data2", value: "abc", p2: "efg"}]
*
* Note: The records can contain additional properties (like 'p2' above) when you use 'readArray'
*
* Additional feature: specify allowed properties with default values with 'rows' object
*
* var rows = {
* memory: {
* required: true,
* defaultValue: 512
* }
* }
*
*/
Ext.define('PVE.data.reader.JsonObject', {
extend: 'Ext.data.reader.Json',
alias : 'reader.jsonobject',
readArray: false,
rows: undefined,
constructor: function(config) {
var me = this;
Ext.apply(me, config || {});
me.callParent([config]);
},
getResponseData: function(response) {
var me = this;
var data = [];
try {
var result = Ext.decode(response.responseText);
// get our data items inside the server response
var root = result[me.getRootProperty()];
if (me.readArray) {
var rec_hash = {};
Ext.Array.each(root, function(rec) {
if (Ext.isDefined(rec.key)) {
rec_hash[rec.key] = rec;
}
});
if (me.rows) {
Ext.Object.each(me.rows, function(key, rowdef) {
var rec = rec_hash[key];
if (Ext.isDefined(rec)) {
if (!Ext.isDefined(rec.value)) {
rec.value = rowdef.defaultValue;
}
data.push(rec);
} else if (Ext.isDefined(rowdef.defaultValue)) {
data.push({key: key, value: rowdef.defaultValue} );
} else if (rowdef.required) {
data.push({key: key, value: undefined });
}
});
} else {
Ext.Array.each(root, function(rec) {
if (Ext.isDefined(rec.key)) {
data.push(rec);
}
});
}
} else {
var org_root = root;
if (Ext.isArray(org_root)) {
if (root.length == 1) {
root = org_root[0];
} else {
root = {};
}
}
if (me.rows) {
Ext.Object.each(me.rows, function(key, rowdef) {
if (Ext.isDefined(root[key])) {
data.push({key: key, value: root[key]});
} else if (Ext.isDefined(rowdef.defaultValue)) {
data.push({key: key, value: rowdef.defaultValue});
} else if (rowdef.required) {
data.push({key: key, value: undefined});
}
});
} else {
Ext.Object.each(root, function(key, value) {
data.push({key: key, value: value });
});
}
}
}
catch (ex) {
Ext.Error.raise({
response: response,
json: response.responseText,
parseError: ex,
msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
});
}
return data;
}
});
Ext.define('PVE.RestProxy', {
extend: 'Ext.data.RestProxy',
alias : 'proxy.pve',
pageParam : null,
startParam: null,
limitParam: null,
groupParam: null,
sortParam: null,
filterParam: null,
noCache : false,
afterRequest: function(request, success) {
this.fireEvent('afterload', this, request, success);
return;
},
constructor: function(config) {
Ext.applyIf(config, {
reader: {
type: 'json',
rootProperty: config.root || 'data'
}
});
this.callParent([config]);
}
}, function() {
Ext.define('pve-domains', {
extend: "Ext.data.Model",
fields: [ 'realm', 'type', 'comment', 'default', 'tfa',
{
name: 'descr',
// Note: We use this in the RealmComboBox.js
// (see Bug #125)
convert: function(value, record) {
var info = record.data;
var text;
if (value) {
return value;
}
// return realm if there is no comment
text = info.comment || info.realm;
if (info.tfa) {
text += " (+ " + info.tfa + ")";
}
return Ext.String.htmlEncode(text);
}
}
],
proxy: {
type: 'pve',
url: "/api2/json/access/domains"
}
});
Ext.define('KeyValue', {
extend: "Ext.data.Model",
fields: [ 'key', 'value' ],
idProperty: 'key'
});
Ext.define('KeyValuePendingDelete', {
extend: "Ext.data.Model",
fields: [ 'key', 'value', 'pending', 'delete' ],
idProperty: 'key'
});
Ext.define('pve-string-list', {
extend: 'Ext.data.Model',
fields: [ 'n', 't' ],
idProperty: 'n'
});
Ext.define('pve-tasks', {
extend: 'Ext.data.Model',
fields: [
{ name: 'starttime', type : 'date', dateFormat: 'timestamp' },
{ name: 'endtime', type : 'date', dateFormat: 'timestamp' },
{ name: 'pid', type: 'int' },
'node', 'upid', 'user', 'status', 'type', 'id'
],
idProperty: 'upid'
});
Ext.define('pve-cluster-log', {
extend: 'Ext.data.Model',
fields: [
{ name: 'uid' , type: 'int' },
{ name: 'time', type : 'date', dateFormat: 'timestamp' },
{ name: 'pri', type: 'int' },
{ name: 'pid', type: 'int' },
'node', 'user', 'tag', 'msg',
{
name: 'id',
convert: function(value, record) {
var info = record.data;
var text;
if (value) {
return value;
}
// compute unique ID
return info.uid + ':' + info.node;
}
}
],
idProperty: 'id'
});
});
// Serialize load (avoid too many parallel connections)
Ext.define('PVE.data.UpdateQueue', {
singleton: true,
constructor : function(){
var me = this;
var queue = [];
var queue_idx = {};
var idle = true;
var start_update = function() {
if (!idle) {
return;
}
var storeid = queue.shift();
if (!storeid) {
return;
}
var info = queue_idx[storeid];
queue_idx[storeid] = null;
info.updatestart = new Date();
idle = false;
info.store.load({
callback: function(records, operation, success) {
idle = true;
if (info.callback) {
var runtime = (new Date()).getTime() - info.updatestart.getTime();
info.callback(runtime, success);
}
start_update();
}
});
};
Ext.apply(me, {
queue: function(store, cb) {
var storeid = store.storeid;
if (!storeid) {
throw "unable to queue store without storeid";
}
if (!queue_idx[storeid]) {
queue_idx[storeid] = {
store: store,
callback: cb
};
queue.push(storeid);
}
start_update();
},
unqueue: function(store) {
var storeid = store.storeid;
if (!storeid) {
throw "unabel to unqueue store without storeid";
}
if (queue_idx[storeid]) {
Ext.Array.remove(queue,storeid);
queue_idx[storeid] = null;
}
}
});
}
});
/* Extends the Ext.data.Store type
* with startUpdate() and stopUpdate() methods
* to refresh the store data in the background
* Components using this store directly will flicker
* due to the redisplay of the element ater 'config.interval' ms
*
* Note that you have to call yourself startUpdate() for the background load
* to begin
*/
Ext.define('PVE.data.UpdateStore', {
extend: 'Ext.data.Store',
constructor: function(config) {
var me = this;
config = config || {};
if (!config.interval) {
config.interval = 3000;
}
if (!config.storeid) {
throw "no storeid specified";
}
var load_task = new Ext.util.DelayedTask();
var run_load_task = function() {
if (PVE.Utils.authOK()) {
PVE.data.UpdateQueue.queue(me, function(runtime, success) {
var interval = config.interval + runtime*2;
load_task.delay(interval, run_load_task);
});
} else {
load_task.delay(200, run_load_task);
}
};
Ext.apply(config, {
startUpdate: function() {
run_load_task();
},
stopUpdate: function() {
load_task.cancel();
PVE.data.UpdateQueue.unqueue(me);
}
});
me.callParent([config]);
me.on('destroy', function() {
load_task.cancel();
PVE.data.UpdateQueue.unqueue(me);
});
}
});
/*
* The DiffStore is a in-memory store acting as proxy between a real store
* instance and a component.
* Its purpose is to redisplay the component *only* if the data has been changed
* inside the real store, to avoid the annoying visual flickering of using
* the real store directly.
*
* Implementation:
* The DiffStore monitors via mon() the 'load' events sent by the real store.
* On each 'load' event, the DiffStore compares its own content with the target
* store (call to cond_add_item()) and then fires a 'refresh' event.
* The 'refresh' event will automatically trigger a view refresh on the component
* who binds to this store.
*/
/* Config properties:
* rstore: the realstore which will autorefresh its content from the API
* Only works if rstore has a model and use 'idProperty'
* sortAfterUpdate: sort the diffstore before rendering the view
*/
Ext.define('PVE.data.DiffStore', {
extend: 'Ext.data.Store',
sortAfterUpdate: false,
constructor: function(config) {
var me = this;
config = config || {};
if (!config.rstore) {
throw "no rstore specified";
}
if (!config.rstore.model) {
throw "no rstore model specified";
}
var rstore = config.rstore;
Ext.apply(config, {
model: rstore.model,
proxy: { type: 'memory' }
});
me.callParent([config]);
var first_load = true;
var cond_add_item = function(data, id) {
var olditem = me.getById(id);
if (olditem) {
olditem.beginEdit();
Ext.Array.each(me.model.prototype.fields, function(field) {
if (olditem.data[field.name] !== data[field.name]) {
olditem.set(field.name, data[field.name]);
}
});
olditem.endEdit(true);
olditem.commit();
} else {
var newrec = Ext.create(me.model, data);
var pos = (me.appendAtStart && !first_load) ? 0 : me.data.length;
me.insert(pos, newrec);
}
};
me.mon(rstore, 'load', function(s, records, success) {
if (!success) {
return;
}
me.suspendEvents();
// getSource returns null if data is not filtered
// if it is filtered it returns all records
var allItems = me.getData().getSource() || me.getData();
// remove vanished items
allItems.each(function(olditem) {
var item = rstore.getById(olditem.getId());
if (!item) {
me.remove(olditem);
}
});
rstore.each(function(item) {
cond_add_item(item.data, item.getId());
});
me.filter();
if (me.sortAfterUpdate) {
me.sort();
}
first_load = false;
me.resumeEvents();
me.fireEvent('refresh', me);
me.fireEvent('datachanged', me);
});
}
});
/* This store encapsulates data items which are organized as an Array of key-values Objects
* ie data[0] contains something like {key: "keyboard", value: "da"}
*
* Designed to work with the KeyValue model and the JsonObject data reader
*/
Ext.define('PVE.data.ObjectStore', {
extend: 'PVE.data.UpdateStore',
constructor: function(config) {
var me = this;
config = config || {};
if (!config.storeid) {
config.storeid = 'pve-store-' + (++Ext.idSeed);
}
Ext.applyIf(config, {
model: 'KeyValue',
proxy: {
type: 'pve',
url: config.url,
extraParams: config.extraParams,
reader: {
type: 'jsonobject',
rows: config.rows,
readArray: config.readArray,
rootProperty: config.root || 'data'
}
}
});
me.callParent([config]);
}
});
Ext.define('PVE.data.ResourceStore', {
extend: 'PVE.data.UpdateStore',
singleton: true,
findVMID: function(vmid) {
var me = this, i;
return (me.findExact('vmid', parseInt(vmid, 10)) >= 0);
},
constructor: function(config) {
// fixme: how to avoid those warnings
/*jslint confusion: true */
var me = this;
config = config || {};
var field_defaults = {
type: {
header: gettext('Type'),
type: 'string',
renderer: PVE.Utils.render_resource_type,
sortable: true,
hideable: false,
width: 100
},
id: {
header: 'ID',
type: 'string',
hidden: true,
sortable: true,
width: 80
},
running: {
header: gettext('Online'),
type: 'boolean',
renderer: PVE.Utils.format_boolean,
hidden: true,
convert: function(value, record) {
var info = record.data;
if (info.type === 'qemu' || info.type === 'lxc' || info.type === 'node') {
return (Ext.isNumeric(info.uptime) && (info.uptime > 0));
} else {
return false;
}
}
},
text: {
header: gettext('Description'),
type: 'string',
sortable: true,
width: 200,
convert: function(value, record) {
var info = record.data;
var text;
if (value) {
return value;
}
if (info.type === 'node') {
text = info.node;
} else if (info.type === 'pool') {
text = info.pool;
} else if (info.type === 'storage') {
text = info.storage + ' (' + info.node + ')';
} else if (info.type === 'qemu' || info.type === 'lxc') {
text = String(info.vmid);
if (info.name) {
text += " (" + info.name + ')';
}
} else {
text = info.id;
}
return text;
}
},
vmid: {
header: 'VMID',
type: 'integer',
hidden: true,
sortable: true,
width: 80
},
name: {
header: gettext('Name'),
hidden: true,
sortable: true,
type: 'string'
},
disk: {
header: gettext('Disk usage'),
type: 'integer',
renderer: PVE.Utils.render_disk_usage,
sortable: true,
width: 100,
hidden: true
},
diskuse: {
header: gettext('Disk usage') + " %",
type: 'number',
sortable: true,
renderer: PVE.Utils.render_disk_usage_percent,
width: 100,
calculate: PVE.Utils.calculate_disk_usage,
sortType: 'asFloat'
},
maxdisk: {
header: gettext('Disk size'),
type: 'integer',
renderer: PVE.Utils.render_size,
sortable: true,
hidden: true,
width: 100
},
mem: {
header: gettext('Memory usage'),
type: 'integer',
renderer: PVE.Utils.render_mem_usage,
sortable: true,
hidden: true,
width: 100
},
memuse: {
header: gettext('Memory usage') + " %",
type: 'number',
renderer: PVE.Utils.render_mem_usage_percent,
calculate: PVE.Utils.calculate_mem_usage,
sortType: 'asFloat',
sortable: true,
width: 100
},
maxmem: {
header: gettext('Memory size'),
type: 'integer',
renderer: PVE.Utils.render_size,
hidden: true,
sortable: true,
width: 100
},
cpu: {
header: gettext('CPU usage'),
type: 'float',
renderer: PVE.Utils.render_cpu,
sortable: true,
width: 100
},
maxcpu: {
header: gettext('maxcpu'),
type: 'integer',
hidden: true,
sortable: true,
width: 60
},
diskread: {
header: gettext('Total Disk Read'),
type: 'integer',
hidden: true,
sortable: true,
renderer: PVE.Utils.format_size,
width: 100
},
diskwrite: {
header: gettext('Total Disk Write'),
type: 'integer',
hidden: true,
sortable: true,
renderer: PVE.Utils.format_size,
width: 100
},
netin: {
header: gettext('Total NetIn'),
type: 'integer',
hidden: true,
sortable: true,
renderer: PVE.Utils.format_size,
width: 100
},
netout: {
header: gettext('Total NetOut'),
type: 'integer',
hidden: true,
sortable: true,
renderer: PVE.Utils.format_size,
width: 100
},
template: {
header: gettext('Template'),
type: 'integer',
hidden: true,
sortable: true,
width: 60
},
uptime: {
header: gettext('Uptime'),
type: 'integer',
renderer: PVE.Utils.render_uptime,
sortable: true,
width: 110
},
node: {
header: gettext('Node'),
type: 'string',
hidden: true,
sortable: true,
width: 110
},
storage: {
header: gettext('Storage'),
type: 'string',
hidden: true,
sortable: true,
width: 110
},
pool: {
header: gettext('Pool'),
type: 'string',
hidden: true,
sortable: true,
width: 110
}
};
var fields = [];
var fieldNames = [];
Ext.Object.each(field_defaults, function(key, value) {
if (!Ext.isDefined(value.convert) && !Ext.isDefined(value.calculate)) {
fields.push({name: key, type: value.type});
fieldNames.push(key);
} else if (key === 'text' || key === 'running') {
fields.push({name: key, type: value.type, convert: value.convert});
fieldNames.push(key);
} else {
value.name = key;
fields.push(value);
}
});
Ext.define('PVEResources', {
extend: "Ext.data.Model",
fields: fields,
proxy: {
type: 'pve',
url: '/api2/json/cluster/resources'
}
});
Ext.define('PVETree', {
extend: "Ext.data.Model",
fields: fields,
proxy: { type: 'memory' }
});
Ext.apply(config, {
storeid: 'PVEResources',
model: 'PVEResources',
defaultColums: function() {
var res = [];
Ext.Object.each(field_defaults, function(field, info) {
var fi = Ext.apply({ dataIndex: field }, info);
res.push(fi);
});
return res;
},
fieldNames: fieldNames
});
me.callParent([config]);
}
});
/* Extends the PVE.data.UpdateStore type
*
*
*/
Ext.define('PVE.data.RRDStore', {
extend: 'PVE.data.UpdateStore',
alias: 'store.pveRRDStore',
setRRDUrl: function(timeframe, cf) {
var me = this;
if (!timeframe) {
timeframe = me.timeframe;
}
if (!cf) {
cf = me.cf;
}
me.proxy.url = me.rrdurl + "?timeframe=" + timeframe + "&cf=" + cf;
},
proxy: {
type: 'pve'
},
fields: [
// node rrd fields
{
name:'cpu',
// percentage
convert: function(value) {
return value*100;
}
},
{
name:'iowait',
// percentage
convert: function(value) {
return value*100;
}
},
'loadavg',
'maxcpu',
'memtotal',
'memused',
'netin',
'netout',
'roottotal',
'rootused',
'swaptotal',
'swapused',
'time',
// missing qemu/lxc fields
'maxmem',
'mem',
'disk',
'diskread',
'diskwrite',
'maxdisk',
// missing storage fields
'used',
'total',
// for time we generate unix timestamps, javascript uses milliseconds instead of seconds
{ name:'time', convert: function(value) { return value*1000; }}
],
sorters: 'time',
timeframe: 'hour',
cf: 'AVERAGE',
constructor: function(config) {
var me = this;
config = config || {};
// set default interval to 30seconds
if (!config.interval) {
config.interval = 30000;
}
// set a new storeid
if (!config.storeid) {
config.storeid = 'rrdstore-' + (++Ext.idSeed);
}
// rrdurl is required
if (!config.rrdurl) {
throw "no rrdurl specified";
}
var stateid = 'pveRRDTypeSelection';
var sp = Ext.state.Manager.getProvider();
var stateinit = sp.get(stateid);
if (stateinit) {
if(stateinit.timeframe !== me.timeframe || stateinit.cf !== me.rrdcffn){
me.timeframe = stateinit.timeframe;
me.rrdcffn = stateinit.cf;
}
}
me.callParent([config]);
me.setRRDUrl();
me.mon(sp, 'statechange', function(prov, key, state){
if (key === stateid) {
if (state && state.id) {
if (state.timeframe !== me.timeframe || state.cf !== me.cf) {
me.timeframe = state.timeframe;
me.cf = state.cf;
me.setRRDUrl();
me.reload();
}
}
}
});
}
});
Ext.define('PVE.form.VlanField', {
extend: 'Ext.form.field.Number',
alias: ['widget.pveVlanField'],
deleteEmpty: false,
emptyText: 'no VLAN',
fieldLabel: gettext('VLAN Tag'),
allowBlank: true,
getSubmitData: function() {
var me = this,
data = null,
val;
if (!me.disabled && me.submitValue) {
val = me.getSubmitValue();
if (val) {
data = {};
data[me.getName()] = val;
} else if (me.deleteEmpty) {
data = {};
data['delete'] = me.getName();
}
}
return data;
},
initComponent: function() {
var me = this;
Ext.apply(me, {
minValue: 1,
maxValue: 4094
});
me.callParent();
}
});
Ext.define('PVE.form.Checkbox', {
extend: 'Ext.form.field.Checkbox',
alias: ['widget.pvecheckbox'],
defaultValue: undefined,
deleteDefaultValue: false,
deleteEmpty: false,
inputValue: '1',
getSubmitData: function() {
var me = this,
data = null,
val;
if (!me.disabled && me.submitValue) {
val = me.getSubmitValue();
if (val !== null) {
data = {};
if ((val == me.defaultValue) && me.deleteDefaultValue) {
data['delete'] = me.getName();
} else {
data[me.getName()] = val;
}
} else if (me.deleteEmpty) {
data = {};
data['delete'] = me.getName();
}
}
return data;
},
// also accept integer 1 as true
setRawValue: function(value) {
var me = this;
if (value === 1) {
me.callParent([true]);
} else {
me.callParent([value]);
}
}
});Ext.define('PVE.form.Textfield', {
extend: 'Ext.form.field.Text',
alias: ['widget.pvetextfield'],
skipEmptyText: true,
deleteEmpty: false,
getSubmitData: function() {
var me = this,
data = null,
val;
if (!me.disabled && me.submitValue && !me.isFileUpload()) {
val = me.getSubmitValue();
if (val !== null) {
data = {};
data[me.getName()] = val;
} else if (me.deleteEmpty) {
data = {};
data['delete'] = me.getName();
}
}
return data;
},
getSubmitValue: function() {
var me = this;
var value = this.processRawValue(this.getRawValue());
if (value !== '') {
return value;
}
return me.skipEmptyText ? null: value;
}
});Ext.define('PVE.form.RRDTypeSelector', {
extend: 'Ext.form.field.ComboBox',
alias: ['widget.pveRRDTypeSelector'],
displayField: 'text',
valueField: 'id',
editable: false,
queryMode: 'local',
value: 'hour',
stateEvents: [ 'select' ],
stateful: true,
stateId: 'pveRRDTypeSelection',
store: {
type: 'array',
fields: [ 'id', 'timeframe', 'cf', 'text' ],
data : [
[ 'hour', 'hour', 'AVERAGE', "Hour (average)" ],
[ 'hourmax', 'hour', 'MAX', "Hour (max)" ],
[ 'day', 'day', 'AVERAGE', "Day (average)" ],
[ 'daymax', 'day', 'MAX', "Day (max)" ],
[ 'week', 'week', 'AVERAGE', "Week (average)" ],
[ 'weekmax', 'week', 'MAX', "Week (max)" ],
[ 'month', 'month', 'AVERAGE', "Month (average)" ],
[ 'monthmax', 'month', 'MAX', "Month (max)" ],
[ 'year', 'year', 'AVERAGE', "Year (average)" ],
[ 'yearmax', 'year', 'MAX', "Year (max)" ]
]
},
// save current selection in the state Provider so RRDView can read it
getState: function() {
var ind = this.getStore().findExact('id', this.getValue());
var rec = this.getStore().getAt(ind);
if (!rec) {
return;
}
return {
id: rec.data.id,
timeframe: rec.data.timeframe,
cf: rec.data.cf
};
},
// set selection based on last saved state
applyState : function(state) {
if (state && state.id) {
this.setValue(state.id);
}
}
});
/*
* ComboGrid component: a ComboBox where the dropdown menu (the
* "Picker") is a Grid with Rows and Columns expects a listConfig
* object with a columns property roughly based on the GridPicker from
* https://www.sencha.com/forum/showthread.php?299909
*
*/
Ext.define('PVE.form.ComboGrid', {
extend: 'Ext.form.field.ComboBox',
alias: ['widget.PVE.form.ComboGrid'],
// this value is used as default value after load()
preferredValue: undefined,
// hack: allow to select empty value
// seems extjs does not allow that when 'editable == false'
onKeyUp: function(e, t) {
var me = this;
var key = e.getKey();
if (!me.editable && me.allowBlank && !me.multiSelect &&
(key == e.BACKSPACE || key == e.DELETE)) {
me.setValue('');
}
me.callParent(arguments);
},
// needed to trigger onKeyUp etc.
enableKeyEvents: true,
// override ExtJS method
// if the field has multiSelect enabled, the store is not loaded, and
// the displayfield == valuefield, it saves the rawvalue as an array
// but the getRawValue method is only defined in the textfield class
// (which has not to deal with arrays) an returns the string in the
// field (not an array)
//
// so if we have multiselect enabled, return the rawValue (which
// should be an array) and else we do callParent so
// it should not impact any other use of the class
getRawValue: function() {
var me = this;
if (me.multiSelect) {
return me.rawValue;
} else {
return me.callParent();
}
},
// override ExtJS protected method
onBindStore: function(store, initial) {
var me = this,
picker = me.picker,
extraKeySpec,
valueCollectionConfig;
// We're being bound, not unbound...
if (store) {
// If store was created from a 2 dimensional array with generated field names 'field1' and 'field2'
if (store.autoCreated) {
me.queryMode = 'local';
me.valueField = me.displayField = 'field1';
if (!store.expanded) {
me.displayField = 'field2';
}
// displayTpl config will need regenerating with the autogenerated displayField name 'field1'
me.setDisplayTpl(null);
}
if (!Ext.isDefined(me.valueField)) {
me.valueField = me.displayField;
}
// Add a byValue index to the store so that we can efficiently look up records by the value field
// when setValue passes string value(s).
// The two indices (Ext.util.CollectionKeys) are configured unique: false, so that if duplicate keys
// are found, they are all returned by the get call.
// This is so that findByText and findByValue are able to return the *FIRST* matching value. By default,
// if unique is true, CollectionKey keeps the *last* matching value.
extraKeySpec = {
byValue: {
rootProperty: 'data',
unique: false
}
};
extraKeySpec.byValue.property = me.valueField;
store.setExtraKeys(extraKeySpec);
if (me.displayField === me.valueField) {
store.byText = store.byValue;
} else {
extraKeySpec.byText = {
rootProperty: 'data',
unique: false
};
extraKeySpec.byText.property = me.displayField;
store.setExtraKeys(extraKeySpec);
}
// We hold a collection of the values which have been selected, keyed by this field's valueField.
// This collection also functions as the selected items collection for the BoundList's selection model
valueCollectionConfig = {
rootProperty: 'data',
extraKeys: {
byInternalId: {
property: 'internalId'
},
byValue: {
property: me.valueField,
rootProperty: 'data'
}
},
// Whenever this collection is changed by anyone, whether by this field adding to it,
// or the BoundList operating, we must refresh our value.
listeners: {
beginupdate: me.onValueCollectionBeginUpdate,
endupdate: me.onValueCollectionEndUpdate,
scope: me
}
};
// This becomes our collection of selected records for the Field.
me.valueCollection = new Ext.util.Collection(valueCollectionConfig);
// We use the selected Collection as our value collection and the basis
// for rendering the tag list.
//pve override: since the picker is represented by a grid panel,
// we changed here the selection to RowModel
me.pickerSelectionModel = new Ext.selection.RowModel({
mode: me.multiSelect ? 'SIMPLE' : 'SINGLE',
// There are situations when a row is selected on mousedown but then the mouse is dragged to another row
// and released. In these situations, the event target for the click event won't be the row where the mouse
// was released but the boundview. The view will then determine that it should fire a container click, and
// the DataViewModel will then deselect all prior selections. Setting `deselectOnContainerClick` here will
// prevent the model from deselecting.
deselectOnContainerClick: false,
enableInitialSelection: false,
pruneRemoved: false,
selected: me.valueCollection,
store: store,
listeners: {
scope: me,
lastselectedchanged: me.updateBindSelection
}
});
if (!initial) {
me.resetToDefault();
}
if (picker) {
picker.setSelectionModel(me.pickerSelectionModel);
if (picker.getStore() !== store) {
picker.bindStore(store);
}
}
}
},
// copied from ComboBox
createPicker: function() {
var me = this;
var picker;
var pickerCfg = Ext.apply({
// pve overrides: display a grid for selection
xtype: 'gridpanel',
id: me.pickerId,
pickerField: me,
floating: true,
hidden: true,
store: me.store,
displayField: me.displayField,
preserveScrollOnRefresh: true,
pageSize: me.pageSize,
tpl: me.tpl,
selModel: me.pickerSelectionModel,
focusOnToFront: false
}, me.listConfig, me.defaultListConfig);
picker = me.picker || Ext.widget(pickerCfg);
if (picker.getStore() !== me.store) {
picker.bindStore(me.store);
}
if (me.pageSize) {
picker.pagingToolbar.on('beforechange', me.onPageChange, me);
}
// pve overrides: pass missing method in gridPanel to its view
picker.refresh = function() {
picker.getSelectionModel().select(me.valueCollection.getRange());
picker.getView().refresh();
};
picker.getNodeByRecord = function() {
picker.getView().getNodeByRecord(arguments);
};
// We limit the height of the picker to fit in the space above
// or below this field unless the picker has its own ideas about that.
if (!picker.initialConfig.maxHeight) {
picker.on({
beforeshow: me.onBeforePickerShow,
scope: me
});
}
picker.getSelectionModel().on({
beforeselect: me.onBeforeSelect,
beforedeselect: me.onBeforeDeselect,
focuschange: me.onFocusChange,
selectionChange: function (sm, selectedRecords) {
var me = this;
if (selectedRecords.length) {
me.setValue(selectedRecords);
me.fireEvent('select', me, selectedRecords);
}
},
scope: me
});
// hack for extjs6
// when the clicked item is the same as the previously selected,
// it does not select the item
// instead we hide the picker
if (!me.multiSelect) {
picker.on('itemclick', function (sm,record) {
if (picker.getSelection()[0] === record) {
picker.hide();
}
});
}
// when our store is not yet loaded, we increase
// the height of the gridpanel, so that we can see
// the loading mask
//
// we save the minheight to reset it after the load
picker.on('show', function() {
if (me.enableLoadMask) {
me.savedMinHeight = picker.getMinHeight();
picker.setMinHeight(100);
}
});
picker.getNavigationModel().navigateOnSpace = false;
return picker;
},
initComponent: function() {
var me = this;
if (me.initialConfig.editable === undefined) {
me.editable = false;
}
Ext.apply(me, {
queryMode: 'local',
matchFieldWidth: false
});
Ext.applyIf(me, { value: ''}); // hack: avoid ExtJS validate() bug
Ext.applyIf(me.listConfig, { width: 400 });
me.callParent();
// Create the picker at an early stage, so it is available to store the previous selection
if (!me.picker) {
me.createPicker();
}
me.mon(me.store, 'beforeload', function() {
if (!me.isDisabled()) {
me.enableLoadMask = true;
}
});
// hack: autoSelect does not work
me.mon(me.store, 'load', function(store, r, success, o) {
if (success) {
me.clearInvalid();
if (me.enableLoadMask) {
delete me.enableLoadMask;
// if the picker exists,
// we reset its minheight to the saved var/0
// we have to update the layout, otherwise the height
// gets not recalculated
if (me.picker) {
me.picker.setMinHeight(me.savedMinHeight || 0);
delete me.savedMinHeight;
me.picker.updateLayout();
}
}
var def = me.getValue() || me.preferredValue;
if (def) {
me.setValue(def, true); // sync with grid
}
var found = false;
if (def) {
if (Ext.isArray(def)) {
Ext.Array.each(def, function(v) {
if (store.findRecord(me.valueField, v)) {
found = true;
return false; // break
}
});
} else {
found = store.findRecord(me.valueField, def);
}
}
if (!found) {
var rec = me.store.first();
if (me.autoSelect && rec && rec.data) {
def = rec.data[me.valueField];
me.setValue(def, true);
} else {
me.setValue(me.editable ? def : '', true);
}
}
}
});
}
});
/* Key-Value ComboBox
*
* config properties:
* comboItems: an array of Key - Value pairs
* deleteEmpty: if set to true (default), an empty value received from the
* comboBox will reset the property to its default value
*/
Ext.define('PVE.form.KVComboBox', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.pveKVComboBox',
deleteEmpty: true,
comboItems: undefined,
displayField: 'value',
valueField: 'key',
queryMode: 'local',
// overide framework function to implement deleteEmpty behaviour
getSubmitData: function() {
var me = this,
data = null,
val;
if (!me.disabled && me.submitValue) {
val = me.getSubmitValue();
if (val !== null && val !== '' && val !== '__default__') {
data = {};
data[me.getName()] = val;
} else if (me.deleteEmpty) {
data = {};
data['delete'] = me.getName();
}
}
return data;
},
initComponent: function() {
var me = this;
me.store = Ext.create('Ext.data.ArrayStore', {
model: 'KeyValue',
data : me.comboItems
});
if (me.initialConfig.editable === undefined) {
me.editable = false;
}
me.callParent();
}
});
// boolean type including 'Default' (delete property from file)
Ext.define('PVE.form.Boolean', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.booleanfield'],
comboItems: [
['__default__', gettext('Default')],
[1, gettext('Yes')],
[0, gettext('No')]
]
});
Ext.define('PVE.form.CompressionSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveCompressionSelector'],
comboItems: [
['0', PVE.Utils.noneText],
['lzo', 'LZO (' + gettext('fast') + ')'],
['gzip', 'GZIP (' + gettext('good') + ')']
]
});
Ext.define('PVE.form.PoolSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pvePoolSelector'],
allowBlank: false,
valueField: 'poolid',
displayField: 'poolid',
initComponent: function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-pools'
});
Ext.apply(me, {
store: store,
autoSelect: false,
listConfig: {
columns: [
{
header: gettext('Pool'),
sortable: true,
dataIndex: 'poolid',
flex: 1
},
{
header: gettext('Comment'),
sortable: false,
dataIndex: 'comment',
renderer: Ext.String.htmlEncode,
flex: 1
}
]
}
});
me.callParent();
store.load();
}
}, function() {
Ext.define('pve-pools', {
extend: 'Ext.data.Model',
fields: [ 'poolid', 'comment' ],
proxy: {
type: 'pve',
url: "/api2/json/pools"
},
idProperty: 'poolid'
});
});
Ext.define('PVE.form.GroupSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveGroupSelector'],
allowBlank: false,
autoSelect: false,
valueField: 'groupid',
displayField: 'groupid',
listConfig: {
columns: [
{
header: gettext('Group'),
sortable: true,
dataIndex: 'groupid',
flex: 1
},
{
header: gettext('Comment'),
sortable: false,
dataIndex: 'comment',
renderer: Ext.String.htmlEncode,
flex: 1
}
]
},
initComponent: function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-groups'
});
Ext.apply(me, {
store: store
});
me.callParent();
store.load();
}
}, function() {
Ext.define('pve-groups', {
extend: 'Ext.data.Model',
fields: [ 'groupid', 'comment' ],
proxy: {
type: 'pve',
url: "/api2/json/access/groups"
},
idProperty: 'groupid'
});
});
Ext.define('PVE.form.UserSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveUserSelector'],
allowBlank: false,
autoSelect: false,
valueField: 'userid',
displayField: 'userid',
initComponent: function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-users'
});
var render_full_name = function(firstname, metaData, record) {
var first = firstname || '';
var last = record.data.lastname || '';
return first + " " + last;
};
Ext.apply(me, {
store: store,
listConfig: {
columns: [
{
header: gettext('User'),
sortable: true,
dataIndex: 'userid',
flex: 1
},
{
header: gettext('Name'),
sortable: true,
renderer: render_full_name,
dataIndex: 'firstname',
flex: 1
},
{
header: gettext('Comment'),
sortable: false,
dataIndex: 'comment',
renderer: Ext.String.htmlEncode,
flex: 1
}
]
}
});
me.callParent();
store.load({ params: { enabled: 1 }});
}
}, function() {
Ext.define('pve-users', {
extend: 'Ext.data.Model',
fields: [
'userid', 'firstname', 'lastname' , 'email', 'comment',
{ type: 'boolean', name: 'enable' },
{ type: 'date', dateFormat: 'timestamp', name: 'expire' }
],
proxy: {
type: 'pve',
url: "/api2/json/access/users"
},
idProperty: 'userid'
});
});
Ext.define('PVE.form.RoleSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveRoleSelector'],
allowBlank: false,
autoSelect: false,
valueField: 'roleid',
displayField: 'roleid',
initComponent: function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-roles'
});
Ext.apply(me, {
store: store,
listConfig: {
columns: [
{
header: gettext('Role'),
sortable: true,
dataIndex: 'roleid',
flex: 1
}
]
}
});
me.callParent();
store.load();
}
}, function() {
Ext.define('pve-roles', {
extend: 'Ext.data.Model',
fields: [ 'roleid', 'privs' ],
proxy: {
type: 'pve',
url: "/api2/json/access/roles"
},
idProperty: 'roleid'
});
});
Ext.define('PVE.form.VMIDSelector', {
extend: 'Ext.form.field.Number',
alias: 'widget.pveVMIDSelector',
allowBlank: false,
minValue: 100,
maxValue: 999999999,
validateExists: undefined,
loadNextFreeVMID: false,
initComponent: function() {
var me = this;
Ext.applyIf(me, {
fieldLabel: 'VM ID',
listeners: {
'change': function(field, newValue, oldValue) {
if (!Ext.isDefined(me.validateExists)) {
return;
}
PVE.Utils.API2Request({
params: { vmid: newValue },
url: '/cluster/nextid',
method: 'GET',
success: function(response, opts) {
if (me.validateExists === true) {
me.markInvalid(gettext('This VM ID does not exists'));
}
},
failure: function(response, opts) {
if (me.validateExists === false) {
me.markInvalid(gettext('This VM ID is already in use'));
}
}
});
}
}
});
me.callParent();
if (me.loadNextFreeVMID) {
PVE.Utils.API2Request({
url: '/cluster/nextid',
method: 'GET',
success: function(response, opts) {
me.setRawValue(response.result.data);
}
});
}
}
});
Ext.define('PVE.form.MemoryField', {
extend: 'Ext.form.field.Number',
alias: 'widget.pveMemoryField',
allowBlank: false,
hotplug: false,
minValue: 32,
maxValue: 4178944,
step: 32,
value: '512', // qm default
computeUpDown: function(value) {
var me = this;
if (!me.hotplug) {
return { up: value + me.step, down: value - me.step };
}
var dimm_size = 512;
var prev_dimm_size = 0;
var min_size = 1024;
var current_size = min_size;
var value_up = min_size;
var value_down = min_size;
var value_start = min_size;
var i, j;
for (j = 0; j < 9; j++) {
for (i = 0; i < 32; i++) {
if ((value >= current_size) && (value < (current_size + dimm_size))) {
value_start = current_size;
value_up = current_size + dimm_size;
value_down = current_size - ((i === 0) ? prev_dimm_size : dimm_size);
}
current_size += dimm_size;
}
prev_dimm_size = dimm_size;
dimm_size = dimm_size*2;
}
return { up: value_up, down: value_down, start: value_start };
},
onSpinUp: function() {
var me = this;
if (!me.readOnly) {
var res = me.computeUpDown(me.getValue());
me.setValue(Ext.Number.constrain(res.up, me.minValue, me.maxValue));
}
},
onSpinDown: function() {
var me = this;
if (!me.readOnly) {
var res = me.computeUpDown(me.getValue());
me.setValue(Ext.Number.constrain(res.down, me.minValue, me.maxValue));
}
},
initComponent: function() {
var me = this;
if (me.hotplug) {
me.minValue = 1024;
me.on('blur', function(field) {
var value = me.getValue();
var res = me.computeUpDown(value);
if (value === res.start || value === res.up || value === res.down) {
return;
}
field.setValue(res.up);
});
}
me.callParent();
}
});
Ext.define('PVE.form.NetworkCardSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.PVE.form.NetworkCardSelector'],
comboItems: [
['e1000', 'Intel E1000'],
['virtio', 'VirtIO (' + gettext('paravirtualized') + ')'],
['rtl8139', 'Realtek RTL8139'],
['vmxnet3', 'VMware vmxnet3']
]
});
Ext.define('PVE.form.DiskFormatSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.PVE.form.DiskFormatSelector'],
comboItems: [
['raw', gettext('Raw disk image') + ' (raw)'],
['qcow2', gettext('QEMU image format') + ' (qcow2)'],
['vmdk', gettext('VMware image format') + ' (vmdk)']
]
});
Ext.define('PVE.form.BusTypeSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.PVE.form.BusTypeSelector'],
noVirtIO: false,
noScsi: false,
initComponent: function() {
var me = this;
me.comboItems = [['ide', 'IDE'], ['sata', 'SATA']];
if (!me.noVirtIO) {
me.comboItems.push(['virtio', 'VirtIO']);
}
if (!me.noScsi) {
me.comboItems.push(['scsi', 'SCSI']);
}
me.callParent();
}
});
Ext.define('PVE.form.ControllerSelector', {
extend: 'Ext.form.FieldContainer',
alias: ['widget.PVE.form.ControllerSelector'],
statics: {
maxIds: {
ide: 3,
sata: 5,
virtio: 15,
scsi: 13
}
},
noVirtIO: false,
noScsi: false,
vmconfig: {}, // used to check for existing devices
setVMConfig: function(vmconfig, autoSelect) {
var me = this;
me.vmconfig = Ext.apply({}, vmconfig);
if (autoSelect) {
var clist = ['ide', 'virtio', 'scsi', 'sata'];
if (autoSelect === 'cdrom') {
clist = ['ide', 'scsi', 'sata'];
if (!Ext.isDefined(me.vmconfig.ide2)) {
me.down('field[name=controller]').setValue('ide');
me.down('field[name=deviceid]').setValue(2);
return;
}
} else if (me.vmconfig.ostype === 'l26') {
clist = ['virtio', 'ide', 'scsi', 'sata'];
}
Ext.Array.each(clist, function(controller) {
var confid, i;
if ((controller === 'virtio' && me.noVirtIO) ||
(controller === 'scsi' && me.noScsi)) {
return; //continue
}
me.down('field[name=controller]').setValue(controller);
for (i = 0; i <= PVE.form.ControllerSelector.maxIds[controller]; i++) {
confid = controller + i.toString();
if (!Ext.isDefined(me.vmconfig[confid])) {
me.down('field[name=deviceid]').setValue(i);
return false; // break
}
}
});
}
me.down('field[name=deviceid]').validate();
},
initComponent: function() {
var me = this;
Ext.apply(me, {
fieldLabel: gettext('Bus/Device'),
layout: 'hbox',
defaults: {
flex: 1,
hideLabel: true
},
items: [
{
xtype: 'PVE.form.BusTypeSelector',
name: 'controller',
value: 'ide',
noVirtIO: me.noVirtIO,
noScsi: me.noScsi,
allowBlank: false,
listeners: {
change: function(t, value) {
if (!me.rendered || !value) {
return;
}
var field = me.down('field[name=deviceid]');
field.setMaxValue(PVE.form.ControllerSelector.maxIds[value]);
field.validate();
}
}
},
{
xtype: 'numberfield',
name: 'deviceid',
minValue: 0,
maxValue: PVE.form.ControllerSelector.maxIds.ide,
value: '0',
validator: function(value) {
/*jslint confusion: true */
if (!me.rendered) {
return;
}
var field = me.down('field[name=controller]');
var controller = field.getValue();
var confid = controller + value;
if (Ext.isDefined(me.vmconfig[confid])) {
return "This device is already in use.";
}
return true;
}
}
]
});
me.callParent();
}
});
Ext.define('PVE.form.EmailNotificationSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveEmailNotificationSelector'],
comboItems: [
['always', gettext('Always')],
['failure', gettext('On failure only')]
]
});
Ext.define('PVE.form.RealmComboBox', {
extend: 'Ext.form.field.ComboBox',
alias: ['widget.pveRealmComboBox'],
fieldLabel: gettext('Realm'),
name: 'realm',
queryMode: 'local',
allowBlank: false,
forceSelection: true,
autoSelect: false,
triggerAction: 'all',
valueField: 'realm',
displayField: 'descr',
getState: function() {
return { value: this.getValue() };
},
applyState : function(state) {
if (state && state.value) {
this.setValue(state.value);
}
},
stateEvents: [ 'select' ],
stateful: true, // last chosen auth realm is saved between page reloads
id: 'pveloginrealm', // We need stable ids when using stateful, not autogenerated
stateID: 'pveloginrealm',
needOTP: function(realm) {
var me = this;
var rec = me.store.findRecord('realm', realm);
return rec && rec.data && rec.data.tfa ? rec.data.tfa : undefined;
},
initComponent: function() {
var me = this;
me.store = Ext.create('Ext.data.Store', {
model: 'pve-domains'
});
me.callParent();
me.store.load({
callback: function(r, o, success) {
if (success) {
var def = me.getValue();
if (!def || !me.store.findRecord('realm', def)) {
def = 'pam';
Ext.each(r, function(record) {
if (record.data && record.data["default"]) {
def = record.data.realm;
}
});
}
if (def) {
me.setValue(def);
}
}
}
});
}
});
Ext.define('PVE.form.BondModeSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.bondModeSelector'],
openvswitch: false,
initComponent: function() {
var me = this;
if (me.openvswitch) {
me.comboItems = [
['active-backup', 'active-backup'],
['balance-slb', 'balance-slb'],
['lacp-balance-slb', 'LACP (balance-slb)'],
['lacp-balance-tcp', 'LACP (balance-tcp)']
];
} else {
me.comboItems = [
['balance-rr', 'balance-rr'],
['active-backup', 'active-backup'],
['balance-xor', 'balance-xor'],
['broadcast', 'broadcast'],
['802.3ad', 'LACP (802.3ad)'],
['balance-tlb', 'balance-tlb'],
['balance-alb', 'balance-alb']
];
}
me.callParent();
}
});
Ext.define('PVE.form.BondPolicySelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.bondPolicySelector'],
comboItems: [
['layer2', 'layer2'],
['layer2+3', 'layer2+3'],
['layer3+4', 'layer3+4']
]
});
/*
* Top left combobox, used to select a view of the underneath RessourceTree
*/
Ext.define('PVE.form.ViewSelector', {
extend: 'Ext.form.field.ComboBox',
alias: ['widget.pveViewSelector'],
editable: false,
allowBlank: false,
forceSelection: true,
autoSelect: false,
valueField: 'key',
displayField: 'value',
hideLabel: true,
queryMode: 'local',
initComponent: function() {
var me = this;
var default_views = {
server: {
text: gettext('Server View'),
groups: ['node']
},
folder: {
text: gettext('Folder View'),
groups: ['type']
},
storage: {
text: gettext('Storage View'),
groups: ['node'],
filterfn: function(node) {
return node.data.type === 'storage' || node.data.type === 'node';
}
},
pool: {
text: gettext('Pool View'),
groups: ['pool'],
// Pool View only lists VMs and Containers
filterfn: function(node) {
return node.data.type === 'qemu' || node.data.type === 'lxc' || node.data.type === 'openvz' ||
node.data.type === 'pool';
}
}
};
var groupdef = [];
Ext.Object.each(default_views, function(viewname, value) {
groupdef.push([viewname, value.text]);
});
var store = Ext.create('Ext.data.Store', {
model: 'KeyValue',
proxy: {
type: 'memory',
reader: 'array'
},
data: groupdef,
autoload: true
});
Ext.apply(me, {
store: store,
value: groupdef[0][0],
getViewFilter: function() {
var view = me.getValue();
return Ext.apply({ id: view }, default_views[view] || default_views.server);
},
getState: function() {
return { value: me.getValue() };
},
applyState : function(state, doSelect) {
var view = me.getValue();
if (state && state.value && (view != state.value)) {
var record = store.findRecord('key', state.value);
if (record) {
me.setValue(state.value, true);
if (doSelect) {
me.fireEvent('select', me, [record]);
}
}
}
},
stateEvents: [ 'select' ],
stateful: true,
stateId: 'pveview',
id: 'view'
});
me.callParent();
var statechange = function(sp, key, value) {
if (key === me.id) {
me.applyState(value, true);
}
};
var sp = Ext.state.Manager.getProvider();
me.mon(sp, 'statechange', statechange, me);
}
});
Ext.define('PVE.form.NodeSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveNodeSelector'],
// invalidate nodes which are offline
onlineValidator: false,
selectCurNode: false,
// only allow those nodes (array)
allowedNodes: undefined,
// set default value to empty array, else it inits it with
// null and after the store load it is an empty array,
// triggering dirtychange
value: [],
valueField: 'node',
displayField: 'node',
store: {
fields: [ 'node', 'cpu', 'maxcpu', 'mem', 'maxmem', 'uptime' ],
proxy: {
type: 'pve',
url: '/api2/json/nodes'
},
sorters: [
{
property : 'node',
direction: 'ASC'
},
{
property : 'mem',
direction: 'DESC'
}
]
},
listConfig: {
columns: [
{
header: gettext('Node'),
dataIndex: 'node',
sortable: true,
hideable: false,
flex: 1
},
{
header: gettext('Memory usage') + " %",
renderer: PVE.Utils.render_mem_usage_percent,
sortable: true,
width: 100,
dataIndex: 'mem'
},
{
header: gettext('CPU usage'),
renderer: PVE.Utils.render_cpu,
sortable: true,
width: 100,
dataIndex: 'cpu'
}
]
},
validator: function(value) {
/*jslint confusion: true */
var me = this;
if (!me.onlineValidator || (me.allowBlank && !value)) {
return true;
}
var offline = [];
var notAllowed = [];
Ext.Array.each(value.split(/\s*,\s*/), function(node) {
var rec = me.store.findRecord(me.valueField, node);
if (!(rec && rec.data) || !Ext.isNumeric(rec.data.mem)) {
offline.push(node);
} else if (me.allowedNodes && !Ext.Array.contains(me.allowedNodes, node)) {
notAllowed.push(node);
}
});
if (notAllowed.length !== 0) {
return "Node " + notAllowed.join(', ') + " is not allowed for this action!";
}
if (offline.length !== 0) {
return "Node " + offline.join(', ') + " seems to be offline!";
}
return true;
},
initComponent: function() {
var me = this;
if (me.selectCurNode && PVE.curSelectedNode.data.node) {
me.preferredValue = PVE.curSelectedNode.data.node;
}
me.callParent();
me.getStore().load();
}
});
Ext.define('PVE.form.FileSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveFileSelector'],
setStorage: function(storage, nodename) {
var me = this;
var change = false;
if (storage && (me.storage !== storage)) {
me.storage = storage;
change = true;
}
if (nodename && (me.nodename !== nodename)) {
me.nodename = nodename;
change = true;
}
if (!(me.storage && me.nodename && change)) {
return;
}
var url = '/api2/json/nodes/' + me.nodename + '/storage/' + me.storage + '/content';
if (me.storageContent) {
url += '?content=' + me.storageContent;
}
me.store.setProxy({
type: 'pve',
url: url
});
me.store.load();
},
initComponent: function() {
var me = this;
var store = Ext.create('Ext.data.Store', {
model: 'pve-storage-content'
});
Ext.apply(me, {
store: store,
allowBlank: false,
autoSelect: false,
valueField: 'volid',
displayField: 'text',
listConfig: {
columns: [
{
header: gettext('Name'),
dataIndex: 'text',
hideable: false,
flex: 1
},
{
header: gettext('Format'),
width: 60,
dataIndex: 'format'
},
{
header: gettext('Size'),
width: 60,
dataIndex: 'size',
renderer: PVE.Utils.format_size
}
]
}
});
me.callParent();
me.setStorage(me.storage, me.nodename);
}
});
Ext.define('PVE.form.StorageSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.PVE.form.StorageSelector'],
allowBlank: false,
valueField: 'storage',
displayField: 'storage',
listConfig: {
columns: [
{
header: gettext('Name'),
dataIndex: 'storage',
hideable: false,
flex: 1
},
{
header: gettext('Type'),
width: 60,
dataIndex: 'type'
},
{
header: gettext('Avail'),
width: 80,
dataIndex: 'avail',
renderer: PVE.Utils.format_size
},
{
header: gettext('Capacity'),
width: 80,
dataIndex: 'total',
renderer: PVE.Utils.format_size
}
]
},
reloadStorageList: function() {
var me = this;
if (!me.nodename) {
return;
}
var params = {};
var url = '/api2/json/nodes/' + me.nodename + '/storage';
if (me.storageContent) {
params.content = me.storageContent;
}
if (me.targetNode) {
params.target = me.targetNode;
params.enabled = 1; // skip disabled storages
}
me.store.setProxy({
type: 'pve',
url: url,
extraParams: params
});
me.store.load();
},
setTargetNode: function(targetNode) {
var me = this;
if (!targetNode || (me.targetNode === targetNode)) {
return;
}
me.targetNode = targetNode;
me.reloadStorageList();
},
setNodename: function(nodename) {
var me = this;
if (!nodename || (me.nodename === nodename)) {
return;
}
me.nodename = nodename;
me.reloadStorageList();
},
initComponent: function() {
var me = this;
var nodename = me.nodename;
me.nodename = undefined;
var store = Ext.create('Ext.data.Store', {
model: 'pve-storage-status',
sorters: {
property: 'storage',
order: 'DESC'
}
});
Ext.apply(me, {
store: store
});
me.callParent();
if (nodename) {
me.setNodename(nodename);
}
}
}, function() {
Ext.define('pve-storage-status', {
extend: 'Ext.data.Model',
fields: [ 'storage', 'active', 'type', 'avail', 'total' ],
idProperty: 'storage'
});
});
Ext.define('PVE.form.BridgeSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.PVE.form.BridgeSelector'],
bridgeType: 'any_bridge', // bridge, OVSBridge or any_bridge
store: {
fields: [ 'iface', 'active', 'type' ],
filterOnLoad: true,
sorters: [
{
property : 'iface',
direction: 'ASC'
}
]
},
valueField: 'iface',
displayField: 'iface',
listConfig: {
columns: [
{
header: gettext('Bridge'),
dataIndex: 'iface',
hideable: false,
flex: 1
},
{
header: gettext('Active'),
width: 60,
dataIndex: 'active',
renderer: PVE.Utils.format_boolean
}
]
},
setNodename: function(nodename) {
var me = this;
if (!nodename || (me.nodename === nodename)) {
return;
}
me.nodename = nodename;
me.store.setProxy({
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/network?type=' +
me.bridgeType
});
me.store.load();
},
initComponent: function() {
var me = this;
var nodename = me.nodename;
me.nodename = undefined;
me.callParent();
me.setNodename(nodename);
}
});
Ext.define('PVE.form.SecurityGroupsSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveSecurityGroupsSelector'],
valueField: 'group',
displayField: 'group',
initComponent: function() {
var me = this;
var store = Ext.create('Ext.data.Store', {
autoLoad: true,
fields: [ 'group', 'comment' ],
idProperty: 'group',
proxy: {
type: 'pve',
url: "/api2/json/cluster/firewall/groups"
},
sorters: {
property: 'group',
order: 'DESC'
}
});
Ext.apply(me, {
store: store,
listConfig: {
columns: [
{
header: gettext('Security Group'),
dataIndex: 'group',
hideable: false,
width: 100
},
{
header: gettext('Comment'),
dataIndex: 'comment',
renderer: Ext.String.htmlEncode,
flex: 1
}
]
}
});
me.callParent();
}
});
Ext.define('PVE.form.IPRefSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveIPRefSelector'],
base_url: undefined,
preferredValue: '', // hack: else Form sets dirty flag?
ref_type: undefined, // undefined = any [undefined, 'ipset' or 'alias']
valueField: 'ref',
displayField: 'ref',
initComponent: function() {
var me = this;
if (!me.base_url) {
throw "no base_url specified";
}
var url = "/api2/json" + me.base_url;
if (me.ref_type) {
url += "?type=" + me.ref_type;
}
var store = Ext.create('Ext.data.Store', {
autoLoad: true,
fields: [ 'type', 'name', 'ref', 'comment' ],
idProperty: 'ref',
proxy: {
type: 'pve',
url: url
},
sorters: {
property: 'ref',
order: 'DESC'
}
});
var disable_query_for_ips = function(f, value) {
if (value === null ||
value.match(/^\d/)) { // IP address starts with \d
f.queryDelay = 9999999999; // hack: disbale with long delay
} else {
f.queryDelay = 10;
}
};
var columns = [];
if (!me.ref_type) {
columns.push({
header: gettext('Type'),
dataIndex: 'type',
hideable: false,
width: 60
});
}
columns.push(
{
header: gettext('Name'),
dataIndex: 'ref',
hideable: false,
width: 140
},
{
header: gettext('Comment'),
dataIndex: 'comment',
renderer: Ext.String.htmlEncode,
flex: 1
}
);
Ext.apply(me, {
store: store,
listConfig: { columns: columns }
});
me.on('change', disable_query_for_ips);
me.callParent();
}
});
Ext.define('PVE.form.IPProtocolSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveIPProtocolSelector'],
valueField: 'p',
displayField: 'p',
listConfig: {
columns: [
{
header: gettext('Protocol'),
dataIndex: 'p',
hideable: false,
sortable: false,
width: 100
},
{
header: gettext('Number'),
dataIndex: 'n',
hideable: false,
sortable: false,
width: 50
},
{
header: gettext('Description'),
dataIndex: 'd',
hideable: false,
sortable: false,
flex: 1
}
]
},
store: {
fields: [ 'p', 'd', 'n'],
data: [
{ p: 'tcp', n: 6, d: 'Transmission Control Protocol' },
{ p: 'udp', n: 17, d: 'User Datagram Protocol' },
{ p: 'icmp', n: 1, d: 'Internet Control Message Protocol' },
{ p: 'igmp', n: 2, d: 'Internet Group Management' },
{ p: 'ggp', n: 3, d: 'gateway-gateway protocol' },
{ p: 'ipencap', n: 4, d: 'IP encapsulated in IP' },
{ p: 'st', n: 5, d: 'ST datagram mode' },
{ p: 'egp', n: 8, d: 'exterior gateway protocol' },
{ p: 'igp', n: 9, d: 'any private interior gateway (Cisco)' },
{ p: 'pup', n: 12, d: 'PARC universal packet protocol' },
{ p: 'hmp', n: 20, d: 'host monitoring protocol' },
{ p: 'xns-idp', n: 22, d: 'Xerox NS IDP' },
{ p: 'rdp', n: 27, d: '"reliable datagram" protocol' },
{ p: 'iso-tp4', n: 29, d: 'ISO Transport Protocol class 4 [RFC905]' },
{ p: 'dccp', n: 33, d: 'Datagram Congestion Control Prot. [RFC4340]' },
{ p: 'xtp', n: 36, d: 'Xpress Transfer Protocol' },
{ p: 'ddp', n: 37, d: 'Datagram Delivery Protocol' },
{ p: 'idpr-cmtp', n: 38, d: 'IDPR Control Message Transport' },
{ p: 'ipv6', n: 41, d: 'Internet Protocol, version 6' },
{ p: 'ipv6-route', n: 43, d: 'Routing Header for IPv6' },
{ p: 'ipv6-frag', n: 44, d: 'Fragment Header for IPv6' },
{ p: 'idrp', n: 45, d: 'Inter-Domain Routing Protocol' },
{ p: 'rsvp', n: 46, d: 'Reservation Protocol' },
{ p: 'gre', n: 47, d: 'General Routing Encapsulation' },
{ p: 'esp', n: 50, d: 'Encap Security Payload [RFC2406]' },
{ p: 'ah', n: 51, d: 'Authentication Header [RFC2402]' },
{ p: 'skip', n: 57, d: 'SKIP' },
{ p: 'ipv6-icmp', n: 58, d: 'ICMP for IPv6' },
{ p: 'ipv6-nonxt', n: 59, d: 'No Next Header for IPv6' },
{ p: 'ipv6-opts', n: 60, d: 'Destination Options for IPv6' },
{ p: 'vmtp', n: 81, d: 'Versatile Message Transport' },
{ p: 'eigrp', n: 88, d: 'Enhanced Interior Routing Protocol (Cisco)' },
{ p: 'ospf', n: 89, d: 'Open Shortest Path First IGP' },
{ p: 'ax.25', n: 93, d: 'AX.25 frames' },
{ p: 'ipip', n: 94, d: 'IP-within-IP Encapsulation Protocol' },
{ p: 'etherip', n: 97, d: 'Ethernet-within-IP Encapsulation [RFC3378]' },
{ p: 'encap', n: 98, d: 'Yet Another IP encapsulation [RFC1241]' },
{ p: 'pim', n: 103, d: 'Protocol Independent Multicast' },
{ p: 'ipcomp', n: 108, d: 'IP Payload Compression Protocol' },
{ p: 'vrrp', n: 112, d: 'Virtual Router Redundancy Protocol [RFC5798]' },
{ p: 'l2tp', n: 115, d: 'Layer Two Tunneling Protocol [RFC2661]' },
{ p: 'isis', n: 124, d: 'IS-IS over IPv4' },
{ p: 'sctp', n: 132, d: 'Stream Control Transmission Protocol' },
{ p: 'fc', n: 133, d: 'Fibre Channel' },
{ p: 'mobility-header', n: 135, d: 'Mobility Support for IPv6 [RFC3775]' },
{ p: 'udplite', n: 136, d: 'UDP-Lite [RFC3828]' },
{ p: 'mpls-in-ip', n: 137, d: 'MPLS-in-IP [RFC4023]' },
{ p: 'hip', n: 139, d: 'Host Identity Protocol' },
{ p: 'shim6', n: 140, d: 'Shim6 Protocol [RFC5533]' },
{ p: 'wesp', n: 141, d: 'Wrapped Encapsulating Security Payload' },
{ p: 'rohc', n: 142, d: 'Robust Header Compression' }
]
}
});
Ext.define('PVE.form.CPUModelSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.CPUModelSelector'],
comboItems: [
['__default__', PVE.Utils.defaultText + ' (kvm64)'],
['486', '486'],
['athlon', 'athlon'],
['core2duo', 'core2duo'],
['coreduo', 'coreduo'],
['kvm32', 'kvm32'],
['kvm64', 'kvm64'],
['pentium', 'pentium'],
['pentium2', 'pentium2'],
['pentium3', 'pentium3'],
['phenom', 'phenom'],
['qemu32', 'qemu32'],
['qemu64', 'qemu64'],
['Conroe', 'Conroe'],
['Penryn', 'Penryn'],
['Nehalem', 'Nehalem'],
['Westmere', 'Westmere'],
['SandyBridge', 'SandyBridge'],
['IvyBridge', 'IvyBridge'],
['Haswell', 'Haswell'],
['Haswell-noTSX','Haswell-noTSX'],
['Broadwell', 'Broadwell'],
['Broadwell-noTSX','Broadwell-noTSX'],
['Opteron_G1', 'Opteron_G1'],
['Opteron_G2', 'Opteron_G2'],
['Opteron_G3', 'Opteron_G3'],
['Opteron_G4', 'Opteron_G4'],
['Opteron_G5', 'Opteron_G5'],
['host', 'host']
]
});
Ext.define('PVE.form.VNCKeyboardSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.VNCKeyboardSelector'],
comboItems: PVE.Utils.kvm_keymap_array()
});
Ext.define('PVE.form.LanguageSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveLanguageSelector'],
comboItems: PVE.Utils.language_array()
});
Ext.define('PVE.form.DisplaySelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.DisplaySelector'],
comboItems: PVE.Utils.kvm_vga_driver_array()
});
Ext.define('PVE.form.CacheTypeSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.CacheTypeSelector'],
comboItems: [
['__default__', PVE.Utils.defaultText + " (" + gettext('No cache') + ")"],
['directsync', 'Direct sync'],
['writethrough', 'Write through'],
['writeback', 'Write back'],
['unsafe', 'Write back (' + gettext('unsafe') + ')'],
['none', gettext('No cache')]
]
});
Ext.define('PVE.form.SnapshotSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.PVE.form.SnapshotSelector'],
valueField: 'name',
displayField: 'name',
loadStore: function(nodename, vmid) {
var me = this;
if (!nodename) {
return;
}
me.nodename = nodename;
if (!vmid) {
return;
}
me.vmid = vmid;
me.store.setProxy({
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/qemu/' + me.vmid +'/snapshot'
});
me.store.load();
},
initComponent: function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var store = Ext.create('Ext.data.Store', {
fields: [ 'name'],
filterOnLoad: true
});
Ext.apply(me, {
store: store,
listConfig: {
columns: [
{
header: gettext('Snapshot'),
dataIndex: 'name',
hideable: false,
flex: 1
}
]
}
});
me.callParent();
me.loadStore(me.nodename, me.vmid);
}
});
Ext.define('PVE.form.ContentTypeSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveContentTypeSelector'],
cts: undefined,
initComponent: function() {
var me = this;
me.comboItems = [];
if (me.cts === undefined) {
me.cts = ['images', 'iso', 'vztmpl', 'backup', 'rootdir'];
}
Ext.Array.each(me.cts, function(ct) {
me.comboItems.push([ct, PVE.Utils.format_content_types(ct)]);
});
me.callParent();
}
});
Ext.define('PVE.form.HotplugFeatureSelector', {
extend: 'Ext.form.CheckboxGroup',
alias: 'widget.pveHotplugFeatureSelector',
columns: 1,
vertical: true,
items: [
{ boxLabel: gettext('Disk'), name: 'hotplug', inputValue: 'disk', submitValue: false, checked: true },
{ boxLabel: gettext('Network'), name: 'hotplug', inputValue: 'network',submitValue: false, checked: true },
{ boxLabel: gettext('USB'), name: 'hotplug', inputValue: 'usb', submitValue: false, checked: true },
{ boxLabel: gettext('Memory'), name: 'hotplug', inputValue: 'memory', submitValue: false },
{ boxLabel: gettext('CPU'), name: 'hotplug', inputValue: 'cpu', submitValue: false }
],
setValue: function(value) {
var me = this;
var newVal = [];
if (value === '1') {
newVal = ['disk', 'network', 'usb'];
} else if (value !== '0') {
newVal = value.split(',');
}
me.callParent([{ hotplug: newVal }]);
},
// overide framework function to
// assemble the hotplug value
getSubmitData: function() {
var me = this,
boxes = me.getBoxes(),
data = [];
Ext.Array.forEach(boxes, function(box){
if (box.getValue()) {
data.push(box.inputValue);
}
});
/* because above is hotplug an array */
/*jslint confusion: true*/
if (data.length === 0) {
return { 'hotplug':'0' };
} else {
return { 'hotplug': data.join(',') };
}
}
});
Ext.define('PVE.form.iScsiProviderSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveiScsiProviderSelector'],
comboItems: [
['comstar', 'Comstar'],
[ 'istgt', 'istgt'],
[ 'iet', 'IET']
]
});
Ext.define('PVE.form.DayOfWeekSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveDayOfWeekSelector'],
comboItems:[],
initComponent: function(){
var me = this;
me.comboItems = [
['mon', Ext.util.Format.htmlDecode(Ext.Date.dayNames[1])],
['tue', Ext.util.Format.htmlDecode(Ext.Date.dayNames[2])],
['wed', Ext.util.Format.htmlDecode(Ext.Date.dayNames[3])],
['thu', Ext.util.Format.htmlDecode(Ext.Date.dayNames[4])],
['fri', Ext.util.Format.htmlDecode(Ext.Date.dayNames[5])],
['sat', Ext.util.Format.htmlDecode(Ext.Date.dayNames[6])],
['sun', Ext.util.Format.htmlDecode(Ext.Date.dayNames[0])]
];
this.callParent();
}
});
Ext.define('PVE.form.BackupModeSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveBackupModeSelector'],
comboItems: [
['snapshot', gettext('Snapshot')],
['suspend', gettext('Suspend')],
['stop', gettext('Stop')]
]
});
Ext.define('PVE.form.ScsiHwSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveScsiHwSelector'],
comboItems: [
['__default__', PVE.Utils.render_scsihw('')],
['lsi', PVE.Utils.render_scsihw('lsi')],
['lsi53c810', PVE.Utils.render_scsihw('lsi53c810')],
['megasas', PVE.Utils.render_scsihw('megasas')],
['virtio-scsi-pci', PVE.Utils.render_scsihw('virtio-scsi-pci')],
['virtio-scsi-single', PVE.Utils.render_scsihw('virtio-scsi-single')],
['pvscsi', PVE.Utils.render_scsihw('pvscsi')]
]
});
Ext.define('PVE.form.FirewallPolicySelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveFirewallPolicySelector'],
comboItems: [
['ACCEPT', 'ACCEPT'],
['REJECT', 'REJECT'],
[ 'DROP', 'DROP']
]
});
Ext.define('PVE.form.QemuBiosSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.pveQemuBiosSelector'],
initComponent: function() {
var me = this;
me.comboItems = [
['__default__', PVE.Utils.render_qemu_bios('')],
['seabios', PVE.Utils.render_qemu_bios('seabios')],
['ovmf', PVE.Utils.render_qemu_bios('ovmf')]
];
me.callParent();
}
});
/* This class defines the "Tasks" tab of the bottom status panel
* Tasks are jobs with a start, end and log output
*/
Ext.define('PVE.dc.Tasks', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveClusterTasks'],
initComponent : function() {
var me = this;
var taskstore = new PVE.data.UpdateStore({
storeid: 'pve-cluster-tasks',
model: 'pve-tasks',
proxy: {
type: 'pve',
url: '/api2/json/cluster/tasks'
}
});
var store = Ext.create('PVE.data.DiffStore', {
rstore: taskstore,
sortAfterUpdate: true,
appendAtStart: true,
sorters: [
{
property : 'pid',
direction: 'DESC'
},
{
property : 'starttime',
direction: 'DESC'
}
]
});
var run_task_viewer = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.window.TaskViewer', {
upid: rec.data.upid
});
win.show();
};
Ext.apply(me, {
store: store,
stateful: false,
viewConfig: {
trackOver: false,
stripeRows: true, // does not work with getRowClass()
getRowClass: function(record, index) {
var status = record.get('status');
if (status && status != 'OK') {
return "x-form-invalid-field";
}
}
},
sortableColumns: false,
columns: [
{
header: gettext("Start Time"),
dataIndex: 'starttime',
width: 150,
renderer: function(value) {
return Ext.Date.format(value, "M d H:i:s");
}
},
{
header: gettext("End Time"),
dataIndex: 'endtime',
width: 150,
renderer: function(value, metaData, record) {
if (record.data.pid) {
if (record.data.type == "vncproxy" ||
record.data.type == "vncshell" ||
record.data.type == "spiceproxy") {
metaData.tdCls = "x-grid-row-console";
} else {
metaData.tdCls = "x-grid-row-loading";
}
return "";
}
return Ext.Date.format(value, "M d H:i:s");
}
},
{
header: gettext("Node"),
dataIndex: 'node',
width: 100
},
{
header: gettext("User name"),
dataIndex: 'user',
width: 150
},
{
header: gettext("Description"),
dataIndex: 'upid',
flex: 1,
renderer: PVE.Utils.render_upid
},
{
header: gettext("Status"),
dataIndex: 'status',
width: 200,
renderer: function(value, metaData, record) {
if (record.data.pid) {
if (record.data.type != "vncproxy") {
metaData.tdCls = "x-grid-row-loading";
}
return "";
}
if (value == 'OK') {
return 'OK';
}
// metaData.attr = 'style="color:red;"';
return PVE.Utils.errorText + ': ' + value;
}
}
],
listeners: {
itemdblclick: run_task_viewer,
show: taskstore.startUpdate,
hide: taskstore.stopUpdate,
destroy: taskstore.stopUpdate
}
});
me.callParent();
}
});
/* This class defines the "Cluster log" tab of the bottom status panel
* A log entry is a timestamp associated with an action on a cluster
*/
Ext.define('PVE.dc.Log', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveClusterLog'],
initComponent : function() {
var me = this;
var logstore = new PVE.data.UpdateStore({
storeid: 'pve-cluster-log',
model: 'pve-cluster-log',
proxy: {
type: 'pve',
url: '/api2/json/cluster/log'
}
});
var store = Ext.create('PVE.data.DiffStore', {
rstore: logstore,
appendAtStart: true
});
Ext.apply(me, {
store: store,
stateful: false,
viewConfig: {
trackOver: false,
stripeRows: true,
getRowClass: function(record, index) {
var pri = record.get('pri');
if (pri && pri <= 3) {
return "x-form-invalid-field";
}
}
},
sortableColumns: false,
columns: [
{
header: gettext("Time"),
dataIndex: 'time',
width: 150,
renderer: function(value) {
return Ext.Date.format(value, "M d H:i:s");
}
},
{
header: gettext("Node"),
dataIndex: 'node',
width: 150
},
{
header: gettext("Service"),
dataIndex: 'tag',
width: 100
},
{
header: "PID",
dataIndex: 'pid',
width: 100
},
{
header: gettext("User name"),
dataIndex: 'user',
width: 150
},
{
header: gettext("Severity"),
dataIndex: 'pri',
renderer: PVE.Utils.render_serverity,
width: 100
},
{
header: gettext("Message"),
dataIndex: 'msg',
flex: 1
}
],
listeners: {
activate: logstore.startUpdate,
deactivate: logstore.stopUpdate,
destroy: logstore.stopUpdate
}
});
me.callParent();
}
});/*
* This class describes the bottom panel
*/
Ext.define('PVE.panel.StatusPanel', {
extend: 'Ext.tab.Panel',
alias: 'widget.pveStatusPanel',
//title: "Logs",
//tabPosition: 'bottom',
initComponent: function() {
var me = this;
var stateid = 'ltab';
var sp = Ext.state.Manager.getProvider();
var state = sp.get(stateid);
if (state && state.value) {
me.activeTab = state.value;
}
Ext.apply(me, {
listeners: {
tabchange: function() {
var atab = me.getActiveTab().itemId;
var state = { value: atab };
sp.set(stateid, state);
}
},
items: [
{
itemId: 'tasks',
title: gettext('Tasks'),
xtype: 'pveClusterTasks'
},
{
itemId: 'clog',
title: gettext('Cluster log'),
xtype: 'pveClusterLog'
}
]
});
me.callParent();
me.items.get(0).fireEvent('show', me.items.get(0));
var statechange = function(sp, key, state) {
if (key === stateid) {
var atab = me.getActiveTab().itemId;
var ntab = state.value;
if (state && ntab && (atab != ntab)) {
me.setActiveTab(ntab);
}
}
};
sp.on('statechange', statechange);
me.on('destroy', function() {
sp.un('statechange', statechange);
});
}
});
Ext.define('PVE.panel.RRDView', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveRRDView',
initComponent : function() {
var me = this;
if (!me.datasource) {
throw "no datasource specified";
}
if (!me.rrdurl) {
throw "no rrdurl specified";
}
var stateid = 'pveRRDTypeSelection';
var sp = Ext.state.Manager.getProvider();
var stateinit = sp.get(stateid);
if (stateinit) {
if(stateinit.timeframe !== me.timeframe || stateinit.cf !== me.rrdcffn){
me.timeframe = stateinit.timeframe;
me.rrdcffn = stateinit.cf;
}
}
if (!me.timeframe) {
if(stateinit && stateinit.timeframe){
me.timeframe = stateinit.timeframe;
}else{
me.timeframe = 'hour';
}
}
if (!me.rrdcffn) {
if(stateinit && stateinit.rrdcffn){
me.rrdcffn = stateinit.cf;
}else{
me.rrdcffn = 'AVERAGE';
}
}
var datasource = me.datasource;
// fixme: dcindex??
var dcindex = 0;
var create_url = function() {
var url = me.rrdurl + "?ds=" + datasource +
"&timeframe=" + me.timeframe + "&cf=" + me.rrdcffn +
"&_dc=" + dcindex.toString();
dcindex++;
return url;
};
Ext.apply(me, {
layout: 'fit',
html: {
tag: 'img',
width: 800,
height: 200,
src: create_url()
},
applyState : function(state) {
if (state && state.id) {
if(state.timeframe !== me.timeframe || state.cf !== me.rrdcffn){
me.timeframe = state.timeframe;
me.rrdcffn = state.cf;
me.reload_task.delay(10);
}
}
}
});
me.callParent();
me.reload_task = new Ext.util.DelayedTask(function() {
if (me.rendered) {
try {
var html = {
tag: 'img',
width: 800,
height: 200,
src: create_url()
};
me.update(html);
} catch (e) {
// fixme:
console.log(e);
}
me.reload_task.delay(30000);
} else {
me.reload_task.delay(1000);
}
});
me.reload_task.delay(30000);
me.on('destroy', function() {
me.reload_task.cancel();
});
var state_change_fn = function(prov, key, value) {
if (key == stateid) {
me.applyState(value);
}
};
me.mon(sp, 'statechange', state_change_fn);
}
});
Ext.define('PVE.widget.RRDChart', {
extend: 'Ext.chart.CartesianChart',
alias: 'widget.pveRRDChart',
width: 800,
height: 300,
animation: false,
interactions: [{
type: 'crosszoom'
}],
axes: [{
type: 'numeric',
position: 'left',
grid: true,
renderer: 'leftAxisRenderer',
minimum: 0
}, {
type: 'time',
position: 'bottom',
grid: true,
fields: ['time']
}],
legend: {
docked: 'right',
// we set this that all graphs have same width
width: 140
},
listeners: {
afterrender: 'onAfterRender',
animationend: 'onAfterAnimation'
},
bytesArr : [
'memtotal',
'memused',
'roottotal',
'rootused',
'swaptotal',
'swapused',
'maxmem',
'mem',
'disk',
'maxdisk',
'total',
'used'
],
bytespersArr: [
'netin',
'netout',
'diskread',
'diskwrite'
],
percentArr: [
'cpu',
'iowait'
],
convertToUnits: function(value) {
var units = ['', 'k','M','G','T', 'P'];
var si = 0;
while(value >= 1000 && si < (units.length -1)){
value = value / 1000;
si++;
}
// javascript floating point weirdness
value = Ext.Number.correctFloat(value);
// limit to 2 decimal points
value = Ext.util.Format.number(value, "0.##");
return value.toString() + " " + units[si];
},
leftAxisRenderer: function(axis, label, layoutContext) {
var me = this;
return me.convertToUnits(label);
},
onSeriesTooltipRender: function (tooltip, record, item) {
var me = this;
var suffix = '';
if (me.percentArr.indexOf(item.field) != -1) {
suffix = '%';
} else if (me.bytesArr.indexOf(item.field) != -1) {
suffix = 'B';
} else if (me.bytespersArr.indexOf(item.field) != -1) {
suffix = 'B/s';
}
var prefix = item.field;
if (me.fieldTitles && me.fieldTitles[me.fields.indexOf(item.field)]) {
prefix = me.fieldTitles[me.fields.indexOf(item.field)];
}
tooltip.setHtml(prefix + ': ' + this.convertToUnits(record.get(item.field)) + suffix +
'<br>' + new Date(record.get('time')));
},
onAfterRender: function(){
var me = this;
// add correct label for left axis
var axisTitle = "";
if (me.percentArr.indexOf(me.fields[0]) != -1) {
axisTitle = "%";
} else if (me.bytesArr.indexOf(me.fields[0]) != -1) {
axisTitle = "Bytes";
} else if (me.bytespersArr.indexOf(me.fields[0]) != -1) {
axisTitle = "Bytes/s";
}
me.axes[0].setTitle(axisTitle);
me.addTool({
type: 'minus',
disabled: true,
tooltip: gettext('Undo Zoom'),
handler: function(){
var undoButton = me.interactions[0].getUndoButton();
if (undoButton.handler) {
undoButton.handler();
}
}
});
// add a series for each field we get
me.fields.forEach(function(item, index){
var title = item;
if (me.fieldTitles && me.fieldTitles[index]) {
title = me.fieldTitles[index];
}
me.addSeries({
type: 'line',
xField: 'time',
yField: item,
title: title,
fill: true,
style: {
lineWidth: 1.5,
opacity: 0.60
},
marker: {
opacity: 0,
scaling: 0.01,
fx: {
duration: 200,
easing: 'easeOut'
}
},
highlightCfg: {
opacity: 1,
scaling: 1.5
},
tooltip: {
trackMouse: true,
renderer: 'onSeriesTooltipRender'
}
});
});
},
onAfterAnimation: function(chart, eopts) {
// if the undobuton is disabled,
// disable our tool
var ourUndoZoomButton = chart.tools[0];
var undoButton = chart.interactions[0].getUndoButton();
ourUndoZoomButton.setDisabled(undoButton.isDisabled());
},
initComponent: function() {
var me = this;
if (!me.store) {
throw "cannot work without store";
}
if (!me.fields) {
throw "cannot work without fields";
}
me.callParent();
// enable animation after the store is loaded
me.store.onAfter('load', function() {
me.setAnimation(true);
}, this, {single: true});
}
});
Ext.define('PVE.panel.InputPanel', {
extend: 'Ext.panel.Panel',
alias: ['widget.inputpanel'],
listeners: {
activate: function() {
// notify owning container that it should display a help button
if (this.onlineHelp) {
Ext.GlobalEvents.fireEvent('pveShowHelp', this.onlineHelp);
}
},
deactivate: function() {
if (this.onlineHelp) {
Ext.GlobalEvents.fireEvent('pveHideHelp', this.onlineHelp);
}
}
},
border: false,
// override this with an URL to a relevant chapter of the pve manual
// setting this will display a help button in our parent panel
onlineHelp: undefined,
// overwrite this to modify submit data
onGetValues: function(values) {
return values;
},
getValues: function(dirtyOnly) {
var me = this;
if (Ext.isFunction(me.onGetValues)) {
dirtyOnly = false;
}
var values = {};
Ext.Array.each(me.query('[isFormField]'), function(field) {
if (!dirtyOnly || field.isDirty()) {
PVE.Utils.assemble_field_data(values, field.getSubmitData());
}
});
return me.onGetValues(values);
},
setValues: function(values) {
var me = this;
var form = me.up('form');
Ext.iterate(values, function(fieldId, val) {
var field = me.query('[isFormField][name=' + fieldId + ']')[0];
if (field) {
field.setValue(val);
if (form.trackResetOnLoad) {
field.resetOriginalValue();
}
}
});
},
initComponent: function() {
var me = this;
var items;
if (me.items) {
me.columns = 1;
items = [
{
columnWidth: 1,
layout: 'anchor',
items: me.items
}
];
me.items = undefined;
} else if (me.column1) {
me.columns = 2;
items = [
{
columnWidth: 0.5,
padding: '0 10 0 0',
layout: 'anchor',
items: me.column1
},
{
columnWidth: 0.5,
padding: '0 0 0 10',
layout: 'anchor',
items: me.column2 || [] // allow empty column
}
];
if (me.columnB) {
items.push({
columnWidth: 1,
padding: '10 0 0 0',
layout: 'anchor',
items: me.columnB
});
}
} else {
throw "unsupported config";
}
if (me.useFieldContainer) {
Ext.apply(me, {
layout: 'fit',
items: Ext.apply(me.useFieldContainer, {
layout: 'column',
defaultType: 'container',
items: items
})
});
} else {
Ext.apply(me, {
layout: 'column',
defaultType: 'container',
items: items
});
}
me.callParent();
}
});
// fixme: how can we avoid those lint errors?
/*jslint confusion: true */
Ext.define('PVE.window.Edit', {
extend: 'Ext.window.Window',
alias: 'widget.pveWindowEdit',
resizable: false,
// use this tio atimatically generate a title like
// Create: <subject>
subject: undefined,
// set create to true if you want a Create button (instead
// OK and RESET)
create: false,
// set to true if you want an Add button (instead of Create)
isAdd: false,
// set to true if you want an Remove button (instead of Create)
isRemove: false,
backgroundDelay: 0,
showProgress: false,
isValid: function() {
var me = this;
var form = me.formPanel.getForm();
return form.isValid();
},
getValues: function(dirtyOnly) {
var me = this;
var values = {};
var form = me.formPanel.getForm();
form.getFields().each(function(field) {
if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
PVE.Utils.assemble_field_data(values, field.getSubmitData());
}
});
Ext.Array.each(me.query('inputpanel'), function(panel) {
PVE.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
});
return values;
},
setValues: function(values) {
var me = this;
var form = me.formPanel.getForm();
Ext.iterate(values, function(fieldId, val) {
var field = form.findField(fieldId);
if (field && !field.up('inputpanel')) {
field.setValue(val);
if (form.trackResetOnLoad) {
field.resetOriginalValue();
}
}
});
Ext.Array.each(me.query('inputpanel'), function(panel) {
panel.setValues(values);
});
},
submit: function() {
var me = this;
var form = me.formPanel.getForm();
var values = me.getValues();
Ext.Object.each(values, function(name, val) {
if (values.hasOwnProperty(name)) {
if (Ext.isArray(val) && !val.length) {
values[name] = '';
}
}
});
if (me.digest) {
values.digest = me.digest;
}
if (me.backgroundDelay) {
values.background_delay = me.backgroundDelay;
}
var url = me.url;
if (me.method === 'DELETE') {
url = url + "?" + Ext.Object.toQueryString(values);
values = undefined;
}
PVE.Utils.API2Request({
url: url,
waitMsgTarget: me,
method: me.method || (me.backgroundDelay ? 'POST' : 'PUT'),
params: values,
failure: function(response, options) {
if (response.result && response.result.errors) {
form.markInvalid(response.result.errors);
}
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
me.close();
if ((me.backgroundDelay || me.showProgress) &&
response.result.data) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', {
upid: upid
});
win.show();
}
}
});
},
load: function(options) {
var me = this;
var form = me.formPanel.getForm();
options = options || {};
var newopts = Ext.apply({
waitMsgTarget: me
}, options);
var createWrapper = function(successFn) {
Ext.apply(newopts, {
url: me.url,
method: 'GET',
success: function(response, opts) {
form.clearInvalid();
me.digest = response.result.data.digest;
if (successFn) {
successFn(response, opts);
} else {
me.setValues(response.result.data);
}
// hack: fix ExtJS bug
Ext.Array.each(me.query('radiofield'), function(f) {
f.resetOriginalValue();
});
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus, function() {
me.close();
});
}
});
};
createWrapper(options.success);
PVE.Utils.API2Request(newopts);
},
initComponent : function() {
var me = this;
if (!me.url) {
throw "no url specified";
}
var items = Ext.isArray(me.items) ? me.items : [ me.items ];
me.items = undefined;
me.formPanel = Ext.create('Ext.form.Panel', {
url: me.url,
method: me.method || 'PUT',
trackResetOnLoad: true,
bodyPadding: 10,
border: false,
defaults: {
border: false
},
fieldDefaults: Ext.apply({}, me.fieldDefaults, {
labelWidth: 100,
anchor: '100%'
}),
items: items
});
var form = me.formPanel.getForm();
var submitBtn = Ext.create('Ext.Button', {
text: me.create ? (me.isAdd ? gettext('Add') : ( me.isRemove ? gettext('Remove') : gettext('Create'))) : gettext('OK'),
disabled: !me.create,
handler: function() {
me.submit();
}
});
var resetBtn = Ext.create('Ext.Button', {
text: 'Reset',
disabled: true,
handler: function(){
form.reset();
}
});
var set_button_status = function() {
var valid = form.isValid();
var dirty = form.isDirty();
submitBtn.setDisabled(!valid || !(dirty || me.create));
resetBtn.setDisabled(!dirty);
};
form.on('dirtychange', set_button_status);
form.on('validitychange', set_button_status);
var colwidth = 300;
if (me.fieldDefaults && me.fieldDefaults.labelWidth) {
colwidth += me.fieldDefaults.labelWidth - 100;
}
var twoColumn = items[0].column1 || items[0].column2;
if (me.subject && !me.title) {
me.title = PVE.Utils.dialog_title(me.subject, me.create, me.isAdd);
}
if (me.create) {
me.buttons = [ submitBtn ] ;
} else {
me.buttons = [ submitBtn, resetBtn ];
}
if (items[0].onlineHelp) {
var helpButton = Ext.create('PVE.button.Help');
me.buttons.unshift(helpButton, '->');
Ext.GlobalEvents.fireEvent('pveShowHelp', items[0].onlineHelp);
}
Ext.applyIf(me, {
modal: true,
width: twoColumn ? colwidth*2 : colwidth,
border: false,
items: [ me.formPanel ]
});
me.callParent();
// always mark invalid fields
me.on('afterlayout', function() {
me.isValid();
});
}
});
Ext.define('PVE.window.LoginWindow', {
extend: 'Ext.window.Window',
controller: {
xclass: 'Ext.app.ViewController',
onLogon: function() {
var me = this;
var form = this.lookupReference('loginForm');
var view = this.getView();
if(form.isValid()){
view.el.mask(gettext('Please wait...'), 'x-mask-loading');
form.submit({
failure: function(f, resp){
view.el.unmask();
var handler = function() {
var uf = me.lookupReference('usernameField');
uf.focus(true, true);
};
Ext.MessageBox.alert(gettext('Error'),
gettext("Login failed. Please try again"),
handler);
},
success: function(f, resp){
view.el.unmask();
var handler = view.handler || Ext.emptyFn;
handler.call(me, resp.result.data);
view.close();
}
});
}
},
control: {
'field[name=username]': {
specialkey: function(f, e) {
if (e.getKey() === e.ENTER) {
var pf = this.lookupReference('passwordField');
if (pf.getValue()) {
this.onLogon();
} else {
pf.focus(false);
}
}
}
},
'field[name=password]': {
specialkey: function(f, e) {
if (e.getKey() === e.ENTER) {
this.onLogon();
}
}
},
'field[name=realm]': {
change: function(f, value) {
var otp_field = this.lookupReference('otpField');
if (f.needOTP(value)) {
otp_field.setVisible(true);
otp_field.setDisabled(false);
} else {
otp_field.setVisible(false);
otp_field.setDisabled(true);
}
}
},
'field[name=lang]': {
change: function(f, value) {
var dt = Ext.Date.add(new Date(), Ext.Date.YEAR, 10);
Ext.util.Cookies.set('PVELangCookie', value, dt);
this.getView().mask(gettext('Please wait...'), 'x-mask-loading');
window.location.reload();
}
},
'button[reference=loginButton]': {
click: 'onLogon'
}
}
},
width: 400,
modal: true,
border: false,
draggable: true,
closable: false,
resizable: false,
layout: 'auto',
title: gettext('Proxmox VE Login'),
defaultFocus: 'usernameField',
items: [{
xtype: 'form',
layout: 'form',
url: '/api2/extjs/access/ticket',
reference: 'loginForm',
fieldDefaults: {
labelAlign: 'right',
allowBlank: false
},
items: [
{
xtype: 'textfield',
fieldLabel: gettext('User name'),
name: 'username',
itemId: 'usernameField',
reference: 'usernameField',
blankText: gettext("Enter your user name")
},
{
xtype: 'textfield',
inputType: 'password',
fieldLabel: gettext('Password'),
name: 'password',
reference: 'passwordField',
blankText: gettext("Enter your password")
},
{
xtype: 'textfield',
fieldLabel: gettext('OTP'),
name: 'otp',
reference: 'otpField',
allowBlank: false,
hidden: true
},
{
xtype: 'pveRealmComboBox',
name: 'realm'
},
{
xtype: 'pveLanguageSelector',
fieldLabel: gettext('Language'),
value: Ext.util.Cookies.get('PVELangCookie') || 'en',
name: 'lang',
reference: 'langField',
submitValue: false
}
],
buttons: [
{
text: gettext('Login'),
reference: 'loginButton'
}
]
}]
});
Ext.define('PVE.window.TaskProgress', {
extend: 'Ext.window.Window',
alias: 'widget.pveTaskProgress',
initComponent: function() {
var me = this;
if (!me.upid) {
throw "no task specified";
}
var task = PVE.Utils.parse_task_upid(me.upid);
var statstore = Ext.create('PVE.data.ObjectStore', {
url: "/api2/json/nodes/" + task.node + "/tasks/" + me.upid + "/status",
interval: 1000,
rows: {
status: { defaultValue: 'unknown' },
exitstatus: { defaultValue: 'unknown' }
}
});
me.on('destroy', statstore.stopUpdate);
var getObjectValue = function(key, defaultValue) {
var rec = statstore.getById(key);
if (rec) {
return rec.data.value;
}
return defaultValue;
};
var pbar = Ext.create('Ext.ProgressBar', { text: 'running...' });
me.mon(statstore, 'load', function() {
var status = getObjectValue('status');
if (status === 'stopped') {
var exitstatus = getObjectValue('exitstatus');
if (exitstatus == 'OK') {
pbar.reset();
pbar.updateText("Done!");
Ext.Function.defer(me.close, 1000, me);
} else {
me.close();
Ext.Msg.alert('Task failed', exitstatus);
}
}
});
var descr = PVE.Utils.format_task_description(task.type, task.id);
Ext.apply(me, {
title: "Task: " + descr,
width: 300,
layout: 'auto',
modal: true,
bodyPadding: 5,
items: pbar,
buttons: [
{
text: gettext('Details'),
handler: function() {
var win = Ext.create('PVE.window.TaskViewer', {
upid: me.upid
});
win.show();
me.close();
}
}
]
});
me.callParent();
statstore.startUpdate();
pbar.wait();
}
});
// fixme: how can we avoid those lint errors?
/*jslint confusion: true */
Ext.define('PVE.window.TaskViewer', {
extend: 'Ext.window.Window',
alias: 'widget.pveTaskViewer',
initComponent: function() {
var me = this;
if (!me.upid) {
throw "no task specified";
}
var task = PVE.Utils.parse_task_upid(me.upid);
var statgrid;
var rows = {
status: {
header: gettext('Status'),
defaultValue: 'unknown',
renderer: function(value) {
if (value != 'stopped') {
return value;
}
var es = statgrid.getObjectValue('exitstatus');
if (es) {
return value + ': ' + es;
}
}
},
exitstatus: {
visible: false
},
type: {
header: gettext('Task type'),
required: true
},
user: {
header: gettext('User name'),
required: true
},
node: {
header: gettext('Node'),
required: true
},
pid: {
header: gettext('Process ID'),
required: true
},
starttime: {
header: gettext('Start Time'),
required: true,
renderer: PVE.Utils.render_timestamp
},
upid: {
header: gettext('Unique task ID')
}
};
var statstore = Ext.create('PVE.data.ObjectStore', {
url: "/api2/json/nodes/" + task.node + "/tasks/" + me.upid + "/status",
interval: 1000,
rows: rows
});
me.on('destroy', statstore.stopUpdate);
var stop_task = function() {
PVE.Utils.API2Request({
url: "/nodes/" + task.node + "/tasks/" + me.upid,
waitMsgTarget: me,
method: 'DELETE',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
};
var stop_btn1 = new Ext.Button({
text: gettext('Stop'),
disabled: true,
handler: stop_task
});
var stop_btn2 = new Ext.Button({
text: gettext('Stop'),
disabled: true,
handler: stop_task
});
statgrid = Ext.create('PVE.grid.ObjectGrid', {
title: gettext('Status'),
layout: 'fit',
tbar: [ stop_btn1 ],
rstore: statstore,
rows: rows,
border: false
});
var logView = Ext.create('PVE.panel.LogView', {
title: gettext('Output'),
tbar: [ stop_btn2 ],
border: false,
url: "/api2/extjs/nodes/" + task.node + "/tasks/" + me.upid + "/log"
});
me.mon(statstore, 'load', function() {
var status = statgrid.getObjectValue('status');
if (status === 'stopped') {
logView.requestUpdate(undefined, true);
logView.scrollToEnd = false;
statstore.stopUpdate();
}
stop_btn1.setDisabled(status !== 'running');
stop_btn2.setDisabled(status !== 'running');
});
statstore.startUpdate();
Ext.apply(me, {
title: "Task viewer: " + task.desc,
width: 800,
height: 400,
layout: 'fit',
modal: true,
items: [{
xtype: 'tabpanel',
region: 'center',
items: [ logView, statgrid ]
}]
});
me.callParent();
logView.fireEvent('show', logView);
}
});
Ext.define('PVE.window.Wizard', {
extend: 'Ext.window.Window',
getValues: function(dirtyOnly) {
var me = this;
var values = {};
var form = me.down('form').getForm();
form.getFields().each(function(field) {
if (!field.up('inputpanel') && (!dirtyOnly || field.isDirty())) {
PVE.Utils.assemble_field_data(values, field.getSubmitData());
}
});
Ext.Array.each(me.query('inputpanel'), function(panel) {
PVE.Utils.assemble_field_data(values, panel.getValues(dirtyOnly));
});
return values;
},
initComponent: function() {
var me = this;
var tabs = me.items || [];
delete me.items;
/*
* Items may have the following functions:
* validator(): per tab custom validation
* onSubmit(): submit handler
* onGetValues(): overwrite getValues results
*/
Ext.Array.each(tabs, function(tab) {
tab.disabled = true;
});
tabs[0].disabled = false;
var check_card = function(card) {
var valid = true;
var fields = card.query('field, fieldcontainer');
if (card.isXType('fieldcontainer')) {
fields.unshift(card);
}
Ext.Array.each(fields, function(field) {
// Note: not all fielcontainer have isValid()
if (Ext.isFunction(field.isValid) && !field.isValid()) {
valid = false;
}
});
if (Ext.isFunction(card.validator)) {
return card.validator();
}
return valid;
};
var tbar = Ext.create('Ext.toolbar.Toolbar', {
ui: 'footer',
region: 'south',
margins: '0 5 5 5',
items: [
{
xtype: 'pveHelpButton',
itemId: 'help'
},
'->',
{
text: gettext('Back'),
disabled: true,
itemId: 'back',
minWidth: 60,
handler: function() {
var tp = me.down('#wizcontent');
var atab = tp.getActiveTab();
var prev = tp.items.indexOf(atab) - 1;
if (prev < 0) {
return;
}
var ntab = tp.items.getAt(prev);
if (ntab) {
tp.setActiveTab(ntab);
}
}
},
{
text: gettext('Next'),
disabled: true,
itemId: 'next',
minWidth: 60,
handler: function() {
var form = me.down('form').getForm();
var tp = me.down('#wizcontent');
var atab = tp.getActiveTab();
if (!check_card(atab)) {
return;
}
var next = tp.items.indexOf(atab) + 1;
var ntab = tp.items.getAt(next);
if (ntab) {
ntab.enable();
tp.setActiveTab(ntab);
}
}
},
{
text: gettext('Finish'),
minWidth: 60,
hidden: true,
itemId: 'submit',
handler: function() {
var tp = me.down('#wizcontent');
var atab = tp.getActiveTab();
atab.onSubmit();
}
}
]
});
var display_header = function(newcard) {
var html = '<h1>' + newcard.title + '</h1>';
if (newcard.descr) {
html += newcard.descr;
}
me.down('#header').update(html);
};
var disable_at = function(card) {
var tp = me.down('#wizcontent');
var idx = tp.items.indexOf(card);
for(;idx < tp.items.getCount();idx++) {
var nc = tp.items.getAt(idx);
if (nc) {
nc.disable();
}
}
};
var tabchange = function(tp, newcard, oldcard) {
if (newcard.onSubmit) {
me.down('#next').setVisible(false);
me.down('#submit').setVisible(true);
} else {
me.down('#next').setVisible(true);
me.down('#submit').setVisible(false);
}
var valid = check_card(newcard);
me.down('#next').setDisabled(!valid);
me.down('#submit').setDisabled(!valid);
me.down('#back').setDisabled(tp.items.indexOf(newcard) == 0);
var next = tp.items.indexOf(newcard) + 1;
var ntab = tp.items.getAt(next);
if (valid && ntab && !newcard.onSubmit) {
ntab.enable();
}
};
if (me.subject && !me.title) {
me.title = PVE.Utils.dialog_title(me.subject, true, false);
}
Ext.apply(me, {
width: 620,
height: 450,
modal: true,
border: false,
draggable: true,
closable: true,
resizable: false,
layout: 'border',
items: [
{
// disabled for now - not really needed
hidden: true,
region: 'north',
itemId: 'header',
layout: 'fit',
margins: '5 5 0 5',
bodyPadding: 10,
html: ''
},
{
xtype: 'form',
region: 'center',
layout: 'fit',
border: false,
margins: '5 5 0 5',
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: [{
itemId: 'wizcontent',
xtype: 'tabpanel',
activeItem: 0,
bodyPadding: 10,
listeners: {
afterrender: function(tp) {
var atab = this.getActiveTab();
tabchange(tp, atab);
},
tabchange: function(tp, newcard, oldcard) {
display_header(newcard);
tabchange(tp, newcard, oldcard);
}
},
items: tabs
}]
},
tbar
]
});
me.callParent();
display_header(tabs[0]);
Ext.Array.each(me.query('field'), function(field) {
field.on('change', function(f) {
var tp = me.down('#wizcontent');
var atab = tp.getActiveTab();
var valid = check_card(atab);
me.down('#next').setDisabled(!valid);
me.down('#submit').setDisabled(!valid);
var next = tp.items.indexOf(atab) + 1;
var ntab = tp.items.getAt(next);
if (!valid) {
disable_at(ntab);
} else if (ntab && !atab.onSubmit) {
ntab.enable();
}
});
});
}
});
Ext.define('PVE.window.NotesEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.apply(me, {
title: gettext('Notes'),
width: 600,
layout: 'fit',
items: {
xtype: 'textarea',
name: 'description',
height: 200,
value: '',
hideLabel: true
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.window.Backup', {
extend: 'Ext.window.Window',
resizable: false,
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
if (!me.vmtype) {
throw "no VM type specified";
}
var storagesel = Ext.create('PVE.form.StorageSelector', {
nodename: me.nodename,
name: 'storage',
value: me.storage,
fieldLabel: gettext('Storage'),
storageContent: 'backup',
allowBlank: false
});
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: [
storagesel,
{
xtype: 'pveBackupModeSelector',
fieldLabel: gettext('Mode'),
value: 'snapshot',
name: 'mode'
},
{
xtype: 'pveCompressionSelector',
name: 'compress',
value: 'lzo',
fieldLabel: gettext('Compression')
}
]
});
var form = me.formPanel.getForm();
var submitBtn = Ext.create('Ext.Button', {
text: gettext('Backup'),
handler: function(){
var storage = storagesel.getValue();
var values = form.getValues();
var params = {
storage: storage,
vmid: me.vmid,
mode: values.mode,
remove: 0
};
if (values.compress) {
params.compress = values.compress;
}
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/vzdump',
params: params,
method: 'POST',
failure: function (response, opts) {
Ext.Msg.alert('Error',response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskViewer', {
upid: upid
});
win.show();
me.close();
}
});
}
});
var title = gettext('Backup') + " " +
((me.vmtype === 'openvz') ? "CT" : "VM") +
" " + me.vmid;
Ext.apply(me, {
title: title,
width: 350,
modal: true,
layout: 'auto',
border: false,
items: [ me.formPanel ],
buttons: [ submitBtn ]
});
me.callParent();
}
});
Ext.define('PVE.window.Restore', {
extend: 'Ext.window.Window', // fixme: PVE.window.Edit?
resizable: false,
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.volid) {
throw "no volume ID specified";
}
if (!me.vmtype) {
throw "no vmtype specified";
}
var storagesel = Ext.create('PVE.form.StorageSelector', {
nodename: me.nodename,
name: 'storage',
value: '',
fieldLabel: gettext('Storage'),
storageContent: (me.vmtype === 'lxc') ? 'rootdir' : 'images',
allowBlank: true
});
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: [
{
xtype: 'displayfield',
value: me.volidText || me.volid,
fieldLabel: gettext('Source')
},
storagesel,
{
xtype: me.vmid ? 'displayfield' : 'pveVMIDSelector',
name: 'vmid',
fieldLabel: 'VM ID',
value: me.vmid,
loadNextFreeVMID: me.vmid ? false: true,
validateExists: false
}
]
});
var form = me.formPanel.getForm();
var doRestore = function(url, params) {
PVE.Utils.API2Request({
url: url,
params: params,
method: 'POST',
waitMsgTarget: me,
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskViewer', {
upid: upid
});
win.show();
me.close();
}
});
};
var submitBtn = Ext.create('Ext.Button', {
text: gettext('Restore'),
handler: function(){
var storage = storagesel.getValue();
var values = form.getValues();
var params = {
storage: storage,
vmid: me.vmid || values.vmid,
force: me.vmid ? 1 : 0
};
var url;
var msg;
if (me.vmtype === 'lxc') {
url = '/nodes/' + me.nodename + '/lxc';
params.ostemplate = me.volid;
params.restore = 1;
msg = PVE.Utils.format_task_description('vzrestore', params.vmid);
} else if (me.vmtype === 'qemu') {
url = '/nodes/' + me.nodename + '/qemu';
params.archive = me.volid;
msg = PVE.Utils.format_task_description('qmrestore', params.vmid);
} else {
throw 'unknown VM type';
}
if (me.vmid) {
msg += '. ' + gettext('This will permanently erase current VM data.');
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
doRestore(url, params);
});
} else {
doRestore(url, params);
}
}
});
form.on('validitychange', function(f, valid) {
submitBtn.setDisabled(!valid);
});
var title = gettext('Restore') + ": " + (
(me.vmtype === 'lxc') ? 'CT' : 'VM');
if (me.vmid) {
title += " " + me.vmid;
}
Ext.apply(me, {
title: title,
width: 500,
modal: true,
layout: 'auto',
border: false,
items: [ me.formPanel ],
buttons: [ submitBtn ]
});
me.callParent();
}
});
/* Popup a message window
* where the user has to manually enter the ressource ID
* to enable the destroy button
*/
Ext.define('PVE.window.SafeDestroy', {
extend: 'Ext.window.Window',
alias: 'widget.pveSafeDestroy',
title: gettext('Confirm'),
modal: true,
buttonAlign: 'center',
bodyPadding: 10,
width: 450,
layout: { type:'hbox' },
defaultFocus: 'confirmField',
config: {
item: {
id: undefined,
type: undefined
},
url: undefined
},
controller: {
xclass: 'Ext.app.ViewController',
control: {
'field[name=confirm]': {
change: function(f, value) {
var view = this.getView();
var removeButton = this.lookupReference('removeButton');
if (value === view.getItem().id) {
removeButton.enable();
} else {
removeButton.disable();
}
},
specialkey: function (field, event) {
var removeButton = this.lookupReference('removeButton');
if (!removeButton.isDisabled() && event.getKey() == event.ENTER) {
removeButton.fireEvent('click', removeButton, event);
}
}
},
'button[reference=removeButton]': {
click: function() {
var view = this.getView();
PVE.Utils.API2Request({
url: view.getUrl(),
method: 'DELETE',
waitMsgTarget: view,
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
callback: function() {
view.close();
}
});
}
}
}
},
items: [
{
xtype: 'component',
cls: [ Ext.baseCSSPrefix + 'message-box-icon',
Ext.baseCSSPrefix + 'message-box-warning',
Ext.baseCSSPrefix + 'dlg-icon']
},
{
xtype: 'container',
flex: 1,
layout: {
type: 'vbox',
align: 'stretch'
},
items: [
{
xtype: 'component',
reference: 'messageCmp'
},
{
itemId: 'confirmField',
reference: 'confirmField',
xtype: 'numberfield',
name: 'confirm',
labelWidth: 300,
hideTrigger: true,
allowBlank: false
}
]
}
],
buttons: [
{
reference: 'removeButton',
text: gettext('Remove'),
disabled: true
}
],
initComponent : function() {
var me = this;
me.callParent();
var item = me.getItem();
if (!Ext.isDefined(item.id)) {
throw "no ID specified";
}
if (!Ext.isDefined(item.type)) {
throw "no VM type specified";
}
var messageCmp = me.lookupReference('messageCmp');
var msg;
if (item.type === 'VM') {
msg = PVE.Utils.format_task_description('qmdestroy', item.id);
} else if (item.type === 'CT') {
msg = PVE.Utils.format_task_description('vzdestroy', item.id);
} else {
throw "unknown VM type specified";
}
messageCmp.setHtml(msg);
var confirmField = me.lookupReference('confirmField');
msg = gettext('Please enter the ID to confirm') +
' (' + item.id + ')';
confirmField.setFieldLabel(msg);
}
});
Ext.define('PVE.panel.NotesView', {
extend: 'Ext.panel.Panel',
title: gettext("Notes"),
bodyStyle: 'white-space:pre',
bodyPadding: 10,
scrollable: true,
load: function() {
var me = this;
PVE.Utils.API2Request({
url: me.url,
waitMsgTarget: me,
failure: function(response, opts) {
me.update(gettext('Error') + " " + response.htmlStatus);
},
success: function(response, opts) {
var data = response.result.data.description || '';
me.update(Ext.htmlEncode(data));
}
});
},
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var vmtype = me.pveSelNode.data.type;
var url;
if (vmtype === 'qemu') {
me.url = '/api2/extjs/nodes/' + nodename + '/qemu/' + vmid + '/config';
} else if (vmtype === 'lxc') {
me.url = '/api2/extjs/nodes/' + nodename + '/lxc/' + vmid + '/config';
} else {
throw "unknown vm type '" + vmtype + "'";
}
Ext.apply(me, {
listeners: {
render: function(c) {
c.el.on('dblclick', function() {
var win = Ext.create('PVE.window.NotesEdit', {
pveSelNode: me.pveSelNode,
url: me.url
});
win.show();
win.on('destroy', me.load, me);
});
}
},
tools: [{
type: 'gear',
handler: function() {
var win = Ext.create('PVE.window.NotesEdit', {
pveSelNode: me.pveSelNode,
url: me.url
});
win.show();
win.on('destroy', me.load, me);
}
}]
});
me.callParent();
}
});
Ext.override(Ext.view.Table, {
afterRender: function() {
var me = this;
me.callParent();
// EXT5DEBUG
// me.mon(me.el, {
// scroll: me.fireBodyScroll,
// scope: me
// });
// if (!me.featuresMC ||
// (me.featuresMC.findIndex('ftype', 'selectable') < 0)) {
// me.el.unselectable();
// }
//
// me.attachEventsForFeatures();
}
});
Ext.define('PVE.grid.SelectFeature', {
extend: 'Ext.grid.feature.Feature',
alias: 'feature.selectable',
mutateMetaRowTpl: function(metaRowTpl) {
var tpl, i,
ln = metaRowTpl.length;
for (i = 0; i < ln; i++) {
tpl = metaRowTpl[i];
tpl = tpl.replace(/x-grid-row/, 'x-grid-row x-selectable');
tpl = tpl.replace(/x-grid-cell-inner x-unselectable/g, 'x-grid-cell-inner');
tpl = tpl.replace(/unselectable="on"/g, '');
metaRowTpl[i] = tpl;
}
}
});
/* Renders a list of key values objets
mandatory config parameters:
rows: an object container where each propery is a key-value object we want to render
var rows = {
keyboard: {
header: gettext('Keyboard Layout'),
editor: 'PVE.dc.KeyboardEdit',
renderer: PVE.Utils.render_kvm_language,
required: true
},
optional:
disabled: setting this parameter to true will disable selection and focus on the
pveObjectGrid as well as greying out input elements.
Useful for a readonly tabular display
*/
Ext.define('PVE.grid.ObjectGrid', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveObjectGrid'],
disabled: false,
getObjectValue: function(key, defaultValue) {
var me = this;
var rec = me.store.getById(key);
if (rec) {
return rec.data.value;
}
return defaultValue;
},
renderKey: function(key, metaData, record, rowIndex, colIndex, store) {
var me = this;
var rows = me.rows;
var rowdef = (rows && rows[key]) ? rows[key] : {};
return rowdef.header || key;
},
renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
var me = this;
var rows = me.rows;
var key = record.data.key;
var rowdef = (rows && rows[key]) ? rows[key] : {};
var renderer = rowdef.renderer;
if (renderer) {
return renderer(value, metaData, record, rowIndex, colIndex, store);
}
return value;
},
initComponent : function() {
var me = this;
var rows = me.rows;
if (!me.rstore) {
if (!me.url) {
throw "no url specified";
}
me.rstore = Ext.create('PVE.data.ObjectStore', {
url: me.url,
interval: me.interval,
extraParams: me.extraParams,
rows: me.rows
});
}
var rstore = me.rstore;
var store = Ext.create('PVE.data.DiffStore', { rstore: rstore,
sorters: [],
filters: []
});
if (rows) {
Ext.Object.each(rows, function(key, rowdef) {
if (Ext.isDefined(rowdef.defaultValue)) {
store.add({ key: key, value: rowdef.defaultValue });
} else if (rowdef.required) {
store.add({ key: key, value: undefined });
}
});
}
if (me.sorterFn) {
store.sorters.add(Ext.create('Ext.util.Sorter', {
sorterFn: me.sorterFn
}));
}
store.filters.add(Ext.create('Ext.util.Filter', {
filterFn: function(item) {
if (rows) {
var rowdef = rows[item.data.key];
if (!rowdef || (rowdef.visible === false)) {
return false;
}
}
return true;
}
}));
PVE.Utils.monStoreErrors(me, rstore);
Ext.applyIf(me, {
store: store,
hideHeaders: true,
stateful: false,
columns: [
{
header: gettext('Name'),
width: me.cwidth1 || 200,
dataIndex: 'key',
renderer: me.renderKey
},
{
flex: 1,
header: gettext('Value'),
dataIndex: 'value',
renderer: me.renderValue
}
]
});
me.callParent();
}
});
Ext.define('PVE.grid.PendingObjectGrid', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pvePendingObjectGrid'],
getObjectValue: function(key, defaultValue, pending) {
var me = this;
var rec = me.store.getById(key);
if (rec) {
var value = (pending && Ext.isDefined(rec.data.pending) && (rec.data.pending !== '')) ?
rec.data.pending : rec.data.value;
if (Ext.isDefined(value) && (value !== '')) {
return value;
} else {
return defaultValue;
}
}
return defaultValue;
},
hasPendingChanges: function(key) {
var me = this;
var rows = me.rows;
var rowdef = (rows && rows[key]) ? rows[key] : {};
var keys = rowdef.multiKey || [ key ];
var pending = false;
Ext.Array.each(keys, function(k) {
var rec = me.store.getById(k);
if (rec && rec.data && Ext.isDefined(rec.data.pending) && (rec.data.pending !== '')) {
pending = true;
return false; // break
}
});
return pending;
},
renderValue: function(value, metaData, record, rowIndex, colIndex, store) {
var me = this;
var rows = me.rows;
var key = record.data.key;
var rowdef = (rows && rows[key]) ? rows[key] : {};
var renderer = rowdef.renderer;
var current = '';
var pendingdelete = '';
var pending = '';
if (renderer) {
current = renderer(value, metaData, record, rowIndex, colIndex, store, false);
if (me.hasPendingChanges(key)) {
pending = renderer(record.data.pending, metaData, record, rowIndex, colIndex, store, true);
}
if (pending == current) {
pending = undefined;
}
} else {
current = value || '';
pending = record.data.pending;
}
if (record.data['delete']) {
pendingdelete = '<div style="text-decoration: line-through;">'+ current +'</div>';
}
if (pending || pendingdelete) {
return current + '<div style="color:red">' + (pending || '') + pendingdelete + '</div>';
} else {
return current;
}
},
initComponent : function() {
var me = this;
var rows = me.rows;
if (!me.rstore) {
if (!me.url) {
throw "no url specified";
}
me.rstore = Ext.create('PVE.data.ObjectStore', {
model: 'KeyValuePendingDelete',
readArray: true,
url: me.url,
interval: me.interval,
extraParams: me.extraParams,
rows: me.rows
});
}
me.callParent();
}
});
Ext.define('PVE.grid.ResourceGrid', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveResourceGrid'],
border: false,
saveCurrentState: function(){
var me = this;
me.saveState();
},
defaultSorter: {
property: 'type',
direction: 'ASC'
},
initComponent : function() {
var me = this;
var rstore = PVE.data.ResourceStore;
var sp = Ext.state.Manager.getProvider();
var coldef = rstore.defaultColums();
var store = Ext.create('Ext.data.Store', {
model: 'PVEResources',
sorters: me.defaultSorter,
proxy: { type: 'memory' }
});
var textfilter = '';
var textfilter_match = function(item) {
var match = false;
Ext.each(['name', 'storage', 'node', 'type', 'text'], function(field) {
var v = item.data[field];
if (v !== undefined) {
v = v.toLowerCase();
if (v.indexOf(textfilter) >= 0) {
match = true;
return false;
}
}
});
return match;
};
var updateGrid = function() {
var filterfn = me.viewFilter ? me.viewFilter.filterfn : null;
//console.log("START GRID UPDATE " + me.viewFilter);
store.suspendEvents();
var nodeidx = {};
var gather_child_nodes = function(cn) {
if (!cn) {
return;
}
var cs = cn.childNodes;
if (!cs) {
return;
}
var len = cs.length, i = 0, n, res;
for (; i < len; i++) {
var child = cs[i];
var orgnode = rstore.data.get(child.data.id);
if (orgnode) {
if ((!filterfn || filterfn(child)) &&
(!textfilter || textfilter_match(child))) {
nodeidx[child.data.id] = orgnode;
}
}
gather_child_nodes(child);
}
};
gather_child_nodes(me.pveSelNode);
// remove vanished items
var rmlist = [];
store.each(function(olditem) {
var item = nodeidx[olditem.data.id];
if (!item) {
//console.log("GRID REM UID: " + olditem.data.id);
rmlist.push(olditem);
}
});
if (rmlist.length) {
store.remove(rmlist);
}
// add new items
var addlist = [];
var key;
for (key in nodeidx) {
if (nodeidx.hasOwnProperty(key)) {
var item = nodeidx[key];
// getById() use find(), which is slow (ExtJS4 DP5)
//var olditem = store.getById(item.data.id);
var olditem = store.data.get(item.data.id);
if (!olditem) {
//console.log("GRID ADD UID: " + item.data.id);
var info = Ext.apply({}, item.data);
var child = Ext.create(store.model, info);
addlist.push(item);
continue;
}
// try to detect changes
var changes = false;
var fieldkeys = PVE.data.ResourceStore.fieldNames;
var fieldcount = fieldkeys.length;
var fieldind;
for (fieldind = 0; fieldind < fieldcount; fieldind++) {
var field = fieldkeys[fieldind];
if (field != 'id' && item.data[field] != olditem.data[field]) {
changes = true;
//console.log("changed item " + item.id + " " + field + " " + item.data[field] + " != " + olditem.data[field]);
olditem.beginEdit();
olditem.set(field, item.data[field]);
}
}
if (changes) {
olditem.endEdit(true);
olditem.commit(true);
}
}
}
if (addlist.length) {
store.add(addlist);
}
store.sort();
store.resumeEvents();
store.fireEvent('refresh', store);
//console.log("END GRID UPDATE");
};
var filter_task = new Ext.util.DelayedTask(function(){
updateGrid();
});
var load_cb = function() {
updateGrid();
};
Ext.apply(me, {
store: store,
stateful: true,
stateId: 'resourcegrid',
tbar: [
{
xtype: 'button',
itemId: 'resetbutton',
tooltip: gettext("Reset Columns"),
iconCls: 'fa fa-fw fa-columns x-button-reset',
handler: function(button, e, opts) {
var sp = Ext.state.Manager.getProvider();
me.getStore().sort(me.defaultSorter);
me.reconfigure(null, PVE.data.ResourceStore.defaultColums());
sp.clear(me.stateId);
}
},
'->',
gettext('Search') + ':', ' ',
{
xtype: 'textfield',
width: 200,
value: textfilter,
enableKeyEvents: true,
listeners: {
keyup: function(field, e) {
var v = field.getValue();
textfilter = v.toLowerCase();
filter_task.delay(500);
}
}
}
],
viewConfig: {
stripeRows: true
},
listeners: {
itemcontextmenu: PVE.Utils.createCmdMenu,
itemdblclick: function(v, record) {
var ws = me.up('pveStdWorkspace');
ws.selectById(record.data.id);
},
destroy: function() {
rstore.un("load", load_cb);
},
columnschanged: 'saveCurrentState',
columnresize: 'saveCurrentState',
columnmove: 'saveCurrentState',
sortchange: 'saveCurrentState'
},
columns: coldef
});
me.callParent();
updateGrid();
rstore.on("load", load_cb);
}
});
// Ext.create is a function
// but we defined create as a bool in PVE.window.Edit
/*jslint confusion: true*/
Ext.define('PVE.pool.AddVM', {
extend: 'PVE.window.Edit',
width: 600,
height: 400,
isAdd: true,
create: true,
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.pool) {
throw "no pool specified";
}
me.url = "/pools/" + me.pool;
me.method = 'PUT';
var vmsField = Ext.create('Ext.form.field.Text', {
name: 'vms',
hidden: true,
allowBlank: false
});
var vmStore = Ext.create('Ext.data.Store', {
model: 'PVEResources',
sorters: {
property: 'vmid',
order: 'ASC'
},
filters: [
function(item) {
return ((item.data.type === 'lxc' || item.data.type === 'qemu') && item.data.pool === '');
}
]
});
var vmGrid = Ext.create('widget.grid',{
store: vmStore,
border: true,
height: 300,
scrollable: true,
selModel: {
selType: 'checkboxmodel',
mode: 'SIMPLE',
listeners: {
selectionchange: function(model, selected, opts) {
var selectedVms = [];
selected.forEach(function(vm) {
selectedVms.push(vm.data.vmid);
});
vmsField.setValue(selectedVms);
}
}
},
columns: [
{
header: 'ID',
dataIndex: 'vmid',
width: 60
},
{
header: gettext('Node'),
dataIndex: 'node'
},
{
header: gettext('Status'),
dataIndex: 'uptime',
renderer: function(value) {
if (value) {
return PVE.Utils.runningText;
} else {
return PVE.Utils.stoppedText;
}
}
},
{
header: gettext('Name'),
dataIndex: 'name',
flex: 1
},
{
header: gettext('Type'),
dataIndex: 'type'
}
]
});
Ext.apply(me, {
subject: gettext('Virtual Machine'),
items: [ vmsField, vmGrid ]
});
me.callParent();
vmStore.load();
}
});
Ext.define('PVE.pool.AddStorage', {
extend: 'PVE.window.Edit',
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.pool) {
throw "no pool specified";
}
me.create = true;
me.isAdd = true;
me.url = "/pools/" + me.pool;
me.method = 'PUT';
Ext.apply(me, {
subject: gettext('Storage'),
width: 350,
items: [
{
xtype: 'PVE.form.StorageSelector',
name: 'storage',
nodename: 'localhost',
autoSelect: false,
value: '',
fieldLabel: gettext("Storage")
}
]
});
me.callParent();
}
});
Ext.define('PVE.grid.PoolMembers', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pvePoolMembers'],
// fixme: dynamic status update ?
initComponent : function() {
var me = this;
if (!me.pool) {
throw "no pool specified";
}
var store = Ext.create('Ext.data.Store', {
model: 'PVEResources',
sorters: [
{
property : 'type',
direction: 'ASC'
}
],
proxy: {
type: 'pve',
root: 'data.members',
url: "/api2/json/pools/" + me.pool
}
});
var coldef = PVE.data.ResourceStore.defaultColums();
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
confirmMsg: function (rec) {
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.id + "'");
},
handler: function(btn, event, rec) {
var params = { 'delete': 1 };
if (rec.data.type === 'storage') {
params.storage = rec.data.storage;
} else if (rec.data.type === 'qemu' || rec.data.type === 'lxc' || rec.data.type === 'openvz') {
params.vms = rec.data.vmid;
} else {
throw "unknown resource type";
}
PVE.Utils.API2Request({
url: '/pools/' + me.pool,
method: 'PUT',
params: params,
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
Ext.apply(me, {
store: store,
selModel: sm,
tbar: [
{
text: gettext('Add'),
menu: new Ext.menu.Menu({
items: [
{
text: gettext('Virtual Machine'),
iconCls: 'pve-itype-icon-qemu',
handler: function() {
var win = Ext.create('PVE.pool.AddVM', { pool: me.pool });
win.on('destroy', reload);
win.show();
}
},
{
text: gettext('Storage'),
iconCls: 'pve-itype-icon-storage',
handler: function() {
var win = Ext.create('PVE.pool.AddStorage', { pool: me.pool });
win.on('destroy', reload);
win.show();
}
}
]
})
},
remove_btn
],
viewConfig: {
stripeRows: true
},
columns: coldef,
listeners: {
itemcontextmenu: PVE.Utils.createCmdMenu,
activate: reload
}
});
me.callParent();
}
});
Ext.define('PVE.form.FWMacroSelector', {
extend: 'PVE.form.ComboGrid',
alias: 'widget.pveFWMacroSelector',
allowBlank: true,
autoSelect: false,
valueField: 'macro',
displayField: 'macro',
listConfig: {
columns: [
{
header: gettext('Macro'),
dataIndex: 'macro',
hideable: false,
width: 100
},
{
header: gettext('Description'),
renderer: Ext.String.htmlEncode,
flex: 1,
dataIndex: 'descr'
}
]
},
initComponent: function() {
var me = this;
var store = Ext.create('Ext.data.Store', {
autoLoad: true,
fields: [ 'macro', 'descr' ],
idProperty: 'macro',
proxy: {
type: 'pve',
url: "/api2/json/cluster/firewall/macros"
},
sorters: {
property: 'macro',
order: 'DESC'
}
});
Ext.apply(me, {
store: store
});
me.callParent();
}
});
Ext.define('PVE.FirewallRulePanel', {
extend: 'PVE.panel.InputPanel',
allow_iface: false,
list_refs_url: undefined,
onGetValues: function(values) {
var me = this;
// hack: editable ComboGrid returns nothing when empty, so we need to set ''
// Also, disabled text fields return nothing, so we need to set ''
Ext.Array.each(['source', 'dest', 'macro', 'proto', 'sport', 'dport'], function(key) {
if (values[key] === undefined) {
values[key] = '';
}
});
delete values.modified_marker;
return values;
},
initComponent : function() {
var me = this;
if (!me.list_refs_url) {
throw "no list_refs_url specified";
}
me.column1 = [
{
// hack: we use this field to mark the form 'dirty' when the
// record has errors- so that the user can safe the unmodified
// form again.
xtype: 'hiddenfield',
name: 'modified_marker',
value: ''
},
{
xtype: 'pveKVComboBox',
name: 'type',
value: 'in',
comboItems: [['in', 'in'], ['out', 'out']],
fieldLabel: gettext('Direction'),
allowBlank: false
},
{
xtype: 'pveKVComboBox',
name: 'action',
value: 'ACCEPT',
comboItems: [['ACCEPT', 'ACCEPT'], ['DROP', 'DROP'], ['REJECT', 'REJECT']],
fieldLabel: gettext('Action'),
allowBlank: false
}
];
if (me.allow_iface) {
me.column1.push({
xtype: 'pvetextfield',
name: 'iface',
deleteEmpty: !me.create,
value: '',
fieldLabel: gettext('Interface')
});
} else {
me.column1.push({
xtype: 'displayfield',
fieldLabel: '',
value: ''
});
}
me.column1.push(
{
xtype: 'displayfield',
fieldLabel: '',
height: 7,
value: ''
},
{
xtype: 'pveIPRefSelector',
name: 'source',
autoSelect: false,
editable: true,
base_url: me.list_refs_url,
value: '',
fieldLabel: gettext('Source')
},
{
xtype: 'pveIPRefSelector',
name: 'dest',
autoSelect: false,
editable: true,
base_url: me.list_refs_url,
value: '',
fieldLabel: gettext('Destination')
}
);
me.column2 = [
{
xtype: 'pvecheckbox',
name: 'enable',
checked: false,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
},
{
xtype: 'pveFWMacroSelector',
name: 'macro',
fieldLabel: gettext('Macro'),
editable: true,
allowBlank: true,
listeners: {
change: function(f, value) {
if (value === null) {
me.down('field[name=proto]').setDisabled(false);
me.down('field[name=sport]').setDisabled(false);
me.down('field[name=dport]').setDisabled(false);
} else {
me.down('field[name=proto]').setDisabled(true);
me.down('field[name=proto]').setValue('');
me.down('field[name=sport]').setDisabled(true);
me.down('field[name=sport]').setValue('');
me.down('field[name=dport]').setDisabled(true);
me.down('field[name=dport]').setValue('');
}
}
}
},
{
xtype: 'pveIPProtocolSelector',
name: 'proto',
autoSelect: false,
editable: true,
value: '',
fieldLabel: gettext('Protocol')
},
{
xtype: 'displayfield',
fieldLabel: '',
height: 7,
value: ''
},
{
xtype: 'textfield',
name: 'sport',
value: '',
fieldLabel: gettext('Source port')
},
{
xtype: 'textfield',
name: 'dport',
value: '',
fieldLabel: gettext('Dest. port')
}
];
me.columnB = [
{
xtype: 'textfield',
name: 'comment',
value: '',
fieldLabel: gettext('Comment')
}
];
me.callParent();
}
});
Ext.define('PVE.FirewallRuleEdit', {
extend: 'PVE.window.Edit',
base_url: undefined,
list_refs_url: undefined,
allow_iface: false,
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.base_url) {
throw "no base_url specified";
}
if (!me.list_refs_url) {
throw "no list_refs_url specified";
}
me.create = (me.rule_pos === undefined);
if (me.create) {
me.url = '/api2/extjs' + me.base_url;
me.method = 'POST';
} else {
me.url = '/api2/extjs' + me.base_url + '/' + me.rule_pos.toString();
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.FirewallRulePanel', {
create: me.create,
list_refs_url: me.list_refs_url,
allow_iface: me.allow_iface,
rule_pos: me.rule_pos
});
Ext.apply(me, {
subject: gettext('Rule'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
ipanel.setValues(values);
if (values.errors) {
var field = me.query('[isFormField][name=modified_marker]')[0];
field.setValue(1);
Ext.Function.defer(function() {
var form = ipanel.up('form').getForm();
form.markInvalid(values.errors);
}, 100);
}
}
});
}
}
});
Ext.define('PVE.FirewallGroupRuleEdit', {
extend: 'PVE.window.Edit',
base_url: undefined,
allow_iface: false,
initComponent : function() {
/*jslint confusion: true */
var me = this;
me.create = (me.rule_pos === undefined);
if (me.create) {
me.url = '/api2/extjs' + me.base_url;
me.method = 'POST';
} else {
me.url = '/api2/extjs' + me.base_url + '/' + me.rule_pos.toString();
me.method = 'PUT';
}
var column1 = [
{
xtype: 'hiddenfield',
name: 'type',
value: 'group'
},
{
xtype: 'pveSecurityGroupsSelector',
name: 'action',
value: '',
fieldLabel: gettext('Security Group'),
allowBlank: false
}
];
if (me.allow_iface) {
column1.push({
xtype: 'pvetextfield',
name: 'iface',
deleteEmpty: !me.create,
value: '',
fieldLabel: gettext('Interface')
});
}
var ipanel = Ext.create('PVE.panel.InputPanel', {
create: me.create,
column1: column1,
column2: [
{
xtype: 'pvecheckbox',
name: 'enable',
checked: false,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
}
],
columnB: [
{
xtype: 'textfield',
name: 'comment',
value: '',
fieldLabel: gettext('Comment')
}
]
});
Ext.apply(me, {
subject: gettext('Rule'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.FirewallRules', {
extend: 'Ext.grid.Panel',
alias: 'widget.pveFirewallRules',
base_url: undefined,
list_refs_url: undefined,
addBtn: undefined,
removeBtn: undefined,
editBtn: undefined,
groupBtn: undefined,
tbar_prefix: undefined,
allow_groups: true,
allow_iface: false,
setBaseUrl: function(url) {
var me = this;
me.base_url = url;
if (url === undefined) {
me.addBtn.setDisabled(true);
if (me.groupBtn) {
me.groupBtn.setDisabled(true);
}
me.store.removeAll();
} else {
me.addBtn.setDisabled(false);
if (me.groupBtn) {
me.groupBtn.setDisabled(false);
}
me.store.setProxy({
type: 'pve',
url: '/api2/json' + url
});
me.store.load();
}
},
moveRule: function(from, to) {
var me = this;
if (!me.base_url) {
return;
}
PVE.Utils.API2Request({
url: me.base_url + "/" + from,
method: 'PUT',
params: { moveto: to },
waitMsgTarget: me,
failure: function(response, options) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
callback: function() {
me.store.load();
}
});
},
updateRule: function(rule) {
var me = this;
if (!me.base_url) {
return;
}
rule.enable = rule.enable ? 1 : 0;
var pos = rule.pos;
delete rule.pos;
delete rule.errors;
PVE.Utils.API2Request({
url: me.base_url + '/' + pos.toString(),
method: 'PUT',
params: rule,
waitMsgTarget: me,
failure: function(response, options) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
callback: function() {
me.store.load();
}
});
},
deleteRule: function(rule) {
var me = this;
if (!me.base_url) {
return;
}
PVE.Utils.API2Request({
url: me.base_url + '/' + rule.pos.toString() +
'?digest=' + encodeURIComponent(rule.digest),
method: 'DELETE',
waitMsgTarget: me,
failure: function(response, options) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
callback: function() {
me.store.load();
}
});
},
initComponent: function() {
/*jslint confusion: true */
var me = this;
if (!me.list_refs_url) {
throw "no list_refs_url specified";
}
var store = Ext.create('Ext.data.Store',{
model: 'pve-fw-rule'
});
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var type = rec.data.type;
var editor;
if (type === 'in' || type === 'out') {
editor = 'PVE.FirewallRuleEdit';
} else if (type === 'group') {
editor = 'PVE.FirewallGroupRuleEdit';
} else {
return;
}
var win = Ext.create(editor, {
digest: rec.data.digest,
allow_iface: me.allow_iface,
base_url: me.base_url,
list_refs_url: me.list_refs_url,
rule_pos: rec.data.pos
});
win.show();
win.on('destroy', reload);
};
me.editBtn = Ext.create('PVE.button.Button',{
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
me.addBtn = Ext.create('Ext.Button', {
text: gettext('Add'),
disabled: true,
handler: function() {
var win = Ext.create('PVE.FirewallRuleEdit', {
allow_iface: me.allow_iface,
base_url: me.base_url,
list_refs_url: me.list_refs_url
});
win.on('destroy', reload);
win.show();
}
});
if (me.allow_groups) {
me.groupBtn = Ext.create('Ext.Button', {
text: gettext('Insert') + ': ' +
gettext('Security Group'),
disabled: true,
handler: function() {
var win = Ext.create('PVE.FirewallGroupRuleEdit', {
allow_iface: me.allow_iface,
base_url: me.base_url
});
win.on('destroy', reload);
win.show();
}
});
}
me.removeBtn = Ext.create('PVE.button.Button',{
text: gettext('Remove'),
selModel: sm,
disabled: true,
handler: function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
me.deleteRule(rec.data);
}
});
var tbar = me.tbar_prefix ? [ me.tbar_prefix ] : [];
tbar.push(me.addBtn);
if (me.groupBtn) {
tbar.push(me.groupBtn);
}
tbar.push(me.removeBtn, me.editBtn);
var render_errors = function(name, value, metaData, record) {
var errors = record.data.errors;
if (errors && errors[name]) {
metaData.tdCls = 'x-form-invalid-field';
var html = '<p>' + Ext.htmlEncode(errors[name]) + '</p>';
metaData.tdAttr = 'data-qwidth=600 data-qtitle="ERROR" data-qtip="' +
html.replace(/\"/g,'&quot;') + '"';
}
return value;
};
var columns = [
{
// similar to xtype: 'rownumberer',
dataIndex: 'pos',
resizable: false,
width: 23,
sortable: false,
align: 'right',
hideable: false,
menuDisabled: true,
renderer: function(value, metaData, record, rowIdx, colIdx, store) {
metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
if (value >= 0) {
return value;
}
return '';
}
},
{
xtype: 'checkcolumn',
header: gettext('Enable'),
dataIndex: 'enable',
listeners: {
checkchange: function(column, recordIndex, checked) {
var record = me.getStore().getData().items[recordIndex];
record.commit();
var data = {};
Ext.Array.forEach(record.getFields(), function(field) {
data[field.name] = record.get(field.name);
});
if (!me.allow_iface || !data.iface) {
delete data.iface;
}
me.updateRule(data);
}
},
width: 50
},
{
header: gettext('Type'),
dataIndex: 'type',
renderer: function(value, metaData, record) {
return render_errors('type', value, metaData, record);
},
width: 50
},
{
header: gettext('Action'),
dataIndex: 'action',
renderer: function(value, metaData, record) {
return render_errors('action', value, metaData, record);
},
width: 80
},
{
header: gettext('Macro'),
dataIndex: 'macro',
renderer: function(value, metaData, record) {
return render_errors('macro', value, metaData, record);
},
width: 80
}
];
if (me.allow_iface) {
columns.push({
header: gettext('Interface'),
dataIndex: 'iface',
renderer: function(value, metaData, record) {
return render_errors('iface', value, metaData, record);
},
width: 80
});
}
columns.push(
{
header: gettext('Source'),
dataIndex: 'source',
renderer: function(value, metaData, record) {
return render_errors('source', value, metaData, record);
},
width: 100
},
{
header: gettext('Destination'),
dataIndex: 'dest',
renderer: function(value, metaData, record) {
return render_errors('dest', value, metaData, record);
},
width: 100
},
{
header: gettext('Protocol'),
dataIndex: 'proto',
renderer: function(value, metaData, record) {
return render_errors('proto', value, metaData, record);
},
width: 100
},
{
header: gettext('Dest. port'),
dataIndex: 'dport',
renderer: function(value, metaData, record) {
return render_errors('dport', value, metaData, record);
},
width: 100
},
{
header: gettext('Source port'),
dataIndex: 'sport',
renderer: function(value, metaData, record) {
return render_errors('sport', value, metaData, record);
},
width: 100
},
{
header: gettext('Comment'),
dataIndex: 'comment',
flex: 1,
renderer: function(value, metaData, record) {
return render_errors('comment', Ext.util.Format.htmlEncode(value), metaData, record);
}
}
);
Ext.apply(me, {
store: store,
selModel: sm,
tbar: tbar,
viewConfig: {
plugins: [
{
ptype: 'gridviewdragdrop',
dragGroup: 'FWRuleDDGroup',
dropGroup: 'FWRuleDDGroup'
}
],
listeners: {
beforedrop: function(node, data, dropRec, dropPosition) {
if (!dropRec) {
return false; // empty view
}
var moveto = dropRec.get('pos');
if (dropPosition === 'after') {
moveto++;
}
var pos = data.records[0].get('pos');
me.moveRule(pos, moveto);
return 0;
},
itemdblclick: run_editor
}
},
sortableColumns: false,
columns: columns
});
me.callParent();
if (me.base_url) {
me.setBaseUrl(me.base_url); // load
}
}
}, function() {
Ext.define('pve-fw-rule', {
extend: 'Ext.data.Model',
fields: [ { name: 'enable', type: 'boolean' },
'type', 'action', 'macro', 'source', 'dest', 'proto', 'iface',
'dport', 'sport', 'comment', 'pos', 'digest', 'errors' ],
idProperty: 'pos'
});
});
Ext.define('PVE.FirewallAliasEdit', {
extend: 'PVE.window.Edit',
base_url: undefined,
alias_name: undefined,
initComponent : function() {
/*jslint confusion: true */
var me = this;
me.create = (me.alias_name === undefined);
if (me.create) {
me.url = '/api2/extjs' + me.base_url;
me.method = 'POST';
} else {
me.url = '/api2/extjs' + me.base_url + '/' + me.alias_name;
me.method = 'PUT';
}
var items = [
{
xtype: 'textfield',
name: me.create ? 'name' : 'rename',
fieldLabel: gettext('Name'),
allowBlank: false
},
{
xtype: 'textfield',
name: 'cidr',
fieldLabel: gettext('IP/CIDR'),
allowBlank: false
},
{
xtype: 'textfield',
name: 'comment',
fieldLabel: gettext('Comment')
}
];
var ipanel = Ext.create('PVE.panel.InputPanel', {
create: me.create,
items: items
});
Ext.apply(me, {
subject: gettext('Alias'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
values.rename = values.name;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.FirewallAliases', {
extend: 'Ext.grid.Panel',
alias: ['widget.pveFirewallAliases'],
base_url: undefined,
title: gettext('Alias'),
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.base_url) {
throw "missing base_url configuration";
}
var store = new Ext.data.Store({
fields: [ 'name', 'cidr', 'comment', 'digest' ],
proxy: {
type: 'pve',
url: "/api2/json" + me.base_url
},
idProperty: 'name',
sorters: {
property: 'name',
order: 'DESC'
}
});
var sm = Ext.create('Ext.selection.RowModel', {});
var reload = function() {
var oldrec = sm.getSelection()[0];
store.load(function(records, operation, success) {
if (oldrec) {
var rec = store.findRecord('name', oldrec.data.name);
if (rec) {
sm.select(rec);
}
}
});
};
var run_editor = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.FirewallAliasEdit', {
base_url: me.base_url,
alias_name: rec.data.name
});
win.show();
win.on('destroy', reload);
};
me.editBtn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
me.addBtn = Ext.create('Ext.Button', {
text: gettext('Add'),
handler: function() {
var win = Ext.create('PVE.FirewallAliasEdit', {
base_url: me.base_url
});
win.on('destroy', reload);
win.show();
}
});
me.removeBtn = new PVE.button.Button({
text: gettext('Remove'),
selModel: sm,
disabled: true,
handler: function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
PVE.Utils.API2Request({
url: me.base_url + '/' + rec.data.name,
method: 'DELETE',
waitMsgTarget: me,
failure: function(response, options) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
callback: reload
});
}
});
Ext.apply(me, {
store: store,
tbar: [ me.addBtn, me.removeBtn, me.editBtn ],
selModel: sm,
columns: [
{ header: gettext('Name'), dataIndex: 'name', width: 100 },
{ header: gettext('IP/CIDR'), dataIndex: 'cidr', width: 100 },
{ header: gettext('Comment'), dataIndex: 'comment', renderer: Ext.String.htmlEncode, flex: 1 }
],
listeners: {
itemdblclick: run_editor
}
});
me.callParent();
me.on('show', reload);
}
});
Ext.define('PVE.FirewallOptions', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveFirewallOptions'],
fwtype: undefined, // 'dc', 'node' or 'vm'
base_url: undefined,
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.base_url) {
throw "missing base_url configuration";
}
if (me.fwtype === 'dc' || me.fwtype === 'node' || me.fwtype === 'vm') {
if (me.fwtype === 'node') {
me.cwidth1 = 250;
}
} else {
throw "unknown firewall option type";
}
var rows = {};
var add_boolean_row = function(name, text, defaultValue, labelWidth) {
rows[name] = {
header: text,
required: true,
defaultValue: defaultValue || 0,
renderer: PVE.Utils.format_boolean,
editor: {
xtype: 'pveWindowEdit',
subject: text,
fieldDefaults: { labelWidth: labelWidth || 100 },
items: {
xtype: 'pvecheckbox',
defaultValue: defaultValue || 0,
checked: defaultValue ? true : false,
name: name,
uncheckedValue: 0,
fieldLabel: text
}
}
};
};
var add_integer_row = function(name, text, labelWidth, minValue) {
rows[name] = {
header: text,
required: true,
renderer: function(value) {
return value || PVE.Utils.defaultText;
},
editor: {
xtype: 'pveWindowEdit',
subject: text,
fieldDefaults: { labelWidth: labelWidth || 100 },
items: {
xtype: 'numberfield',
name: name,
minValue: minValue,
decimalPrecision: 0,
fieldLabel: text,
emptyText: gettext('Default'),
getSubmitData: function() {
var me = this;
var val = me.getSubmitValue();
if (val !== null && val !== '') {
var data = {};
data[name] = val;
return data;
} else {
return { 'delete' : name };
}
}
}
}
};
};
var add_log_row = function(name, labelWidth) {
rows[name] = {
header: name,
required: true,
defaultValue: 'nolog',
editor: {
xtype: 'pveWindowEdit',
subject: name,
fieldDefaults: { labelWidth: labelWidth || 100 },
items: {
xtype: 'pveKVComboBox',
name: name,
fieldLabel: name,
comboItems: [['nolog', 'nolog'], ['info', 'info'], ['err', 'err'],
['warning', 'warning'], ['crit', 'crit'], ['alert', 'alert'],
['emerg', 'emerg'], ['debug', 'debug']]
}
}
};
};
if (me.fwtype === 'node') {
add_boolean_row('enable', gettext('Enable Firewall'), 1);
add_boolean_row('nosmurfs', gettext('SMURFS filter'), 1);
add_boolean_row('tcpflags', gettext('TCP flags filter'), 0);
add_boolean_row('ndp', gettext('Enable NDP'), 1);
add_integer_row('nf_conntrack_max', 'nf_conntrack_max', 120, 32768);
add_integer_row('nf_conntrack_tcp_timeout_established',
'nf_conntrack_tcp_timeout_established', 250, 7875);
add_log_row('log_level_in');
add_log_row('log_level_out');
add_log_row('tcp_flags_log_level', 120);
add_log_row('smurf_log_level');
} else if (me.fwtype === 'vm') {
add_boolean_row('enable', gettext('Enable Firewall'), 0);
add_boolean_row('dhcp', gettext('Enable DHCP'), 0);
add_boolean_row('ndp', gettext('Enable NDP'), 1);
add_boolean_row('radv', gettext('Allow Router Advertisement'), 0);
add_boolean_row('macfilter', gettext('MAC filter'), 1);
add_boolean_row('ipfilter', gettext('IP filter'), 0);
add_log_row('log_level_in');
add_log_row('log_level_out');
} else if (me.fwtype === 'dc') {
add_boolean_row('enable', gettext('Enable Firewall'), 0);
}
if (me.fwtype === 'dc' || me.fwtype === 'vm') {
rows.policy_in = {
header: gettext('Input Policy'),
required: true,
defaultValue: 'DROP',
editor: {
xtype: 'pveWindowEdit',
subject: gettext('Input Policy'),
items: {
xtype: 'pveFirewallPolicySelector',
name: 'policy_in',
value: 'DROP',
fieldLabel: gettext('Input Policy')
}
}
};
rows.policy_out = {
header: gettext('Output Policy'),
required: true,
defaultValue: 'ACCEPT',
editor: {
xtype: 'pveWindowEdit',
subject: gettext('Output Policy'),
items: {
xtype: 'pveFirewallPolicySelector',
name: 'policy_out',
value: 'ACCEPT',
fieldLabel: gettext('Output Policy')
}
}
};
}
var reload = function() {
me.rstore.load();
};
var run_editor = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var rowdef = rows[rec.data.key];
if (!rowdef.editor) {
return;
}
var win;
if (Ext.isString(rowdef.editor)) {
win = Ext.create(rowdef.editor, {
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs' + me.base_url
});
} else {
var config = Ext.apply({
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs' + me.base_url
}, rowdef.editor);
win = Ext.createWidget(rowdef.editor.xtype, config);
win.load();
}
win.show();
win.on('destroy', reload);
};
var edit_btn = new Ext.Button({
text: gettext('Edit'),
disabled: true,
handler: run_editor
});
var set_button_status = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
edit_btn.disable();
return;
}
var rowdef = rows[rec.data.key];
edit_btn.setDisabled(!rowdef.editor);
};
Ext.apply(me, {
url: "/api2/json" + me.base_url,
tbar: [ edit_btn ],
rows: rows,
listeners: {
itemdblclick: run_editor,
selectionchange: set_button_status
}
});
me.callParent();
me.on('activate', reload);
}
});
/*
* Left Treepanel, containing all the ressources we manage in this datacenter: server nodes, server storages, VMs and Containers
*/
Ext.define('PVE.tree.ResourceTree', {
extend: 'Ext.tree.TreePanel',
alias: ['widget.pveResourceTree'],
statics: {
typeDefaults: {
node: {
iconCls: 'fa fa-building x-fa-tree',
text: gettext('Nodes')
},
pool: {
iconCls: 'fa fa-tags fa-dark x-fa-tree',
text: gettext('Resource Pool')
},
storage: {
iconCls: 'fa fa-database fa-dark x-fa-tree',
text: gettext('Storage')
},
qemu: {
iconCls: 'fa fa-desktop x-fa-tree',
text: gettext('Virtual Machine')
},
lxc: {
//iconCls: 'x-tree-node-lxc',
iconCls: 'fa fa-cube x-fa-tree',
text: gettext('LXC Container')
},
template: {
iconCls: 'fa fa-file-o fa-dark x-fa-tree-template'
},
datacenter: {
iconCls: 'fa fa-server x-fa-tree-datacenter'
}
}
},
useArrows: true,
// private
nodeSortFn: function(node1, node2) {
var n1 = node1.data;
var n2 = node2.data;
if ((n1.groupbyid && n2.groupbyid) ||
!(n1.groupbyid || n2.groupbyid)) {
var tcmp;
var v1 = n1.type;
var v2 = n2.type;
if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
return tcmp;
}
// numeric compare for VM IDs
// sort templates after regular VMs
if (v1 === 'qemu' || v1 === 'lxc') {
if (n1.template && !n2.template) {
return 1;
} else if (n2.template && !n1.template) {
return -1;
}
v1 = n1.vmid;
v2 = n2.vmid;
if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
return tcmp;
}
}
return n1.text > n2.text ? 1 : (n1.text < n2.text ? -1 : 0);
} else if (n1.groupbyid) {
return -1;
} else if (n2.groupbyid) {
return 1;
}
},
// private: fast binary search
findInsertIndex: function(node, child, start, end) {
var me = this;
var diff = end - start;
var mid = start + (diff>>1);
if (diff <= 0) {
return start;
}
var res = me.nodeSortFn(child, node.childNodes[mid]);
if (res <= 0) {
return me.findInsertIndex(node, child, start, mid);
} else {
return me.findInsertIndex(node, child, mid + 1, end);
}
},
setIconCls: function(info) {
var me = this;
var defaults = PVE.tree.ResourceTree.typeDefaults[info.type];
if (info.id === 'root') {
defaults = PVE.tree.ResourceTree.typeDefaults.datacenter;
} else if (info.type === 'type') {
defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
}
if (defaults && defaults.iconCls) {
var iconClsAdd = '';
if (info.running && info.type === 'node') {
iconClsAdd = '-online';
} else if (info.running) {
iconClsAdd = '-running';
if (info.status === 'paused') {
iconClsAdd = '-paused';
}
} else if (info.type === 'lxc' || info.type === 'qemu') {
iconClsAdd = '-stopped';
} else if (info.type === 'node') {
iconClsAdd = '-offline';
}
info.iconCls = defaults.iconCls + iconClsAdd;
if (info.template) {
iconClsAdd = '-template';
info.iconCls = PVE.tree.ResourceTree.typeDefaults.template.iconCls + '-' + info.type;
}
}
},
// private
addChildSorted: function(node, info) {
var me = this;
me.setIconCls(info);
var defaults;
if (info.groupbyid) {
info.text = info.groupbyid;
if (info.type === 'type') {
defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
if (defaults && defaults.text) {
info.text = defaults.text;
}
}
}
var child = Ext.create('PVETree', info);
var cs = node.childNodes;
var pos;
if (cs) {
pos = cs[me.findInsertIndex(node, child, 0, cs.length)];
}
node.insertBefore(child, pos);
return child;
},
// private
groupChild: function(node, info, groups, level) {
var me = this;
var groupby = groups[level];
var v = info[groupby];
if (v) {
var group = node.findChild('groupbyid', v);
if (!group) {
var groupinfo;
if (info.type === groupby) {
groupinfo = info;
} else {
groupinfo = {
type: groupby,
id : groupby + "/" + v
};
if (groupby !== 'type') {
groupinfo[groupby] = v;
}
}
groupinfo.leaf = false;
groupinfo.groupbyid = v;
group = me.addChildSorted(node, groupinfo);
// fixme: remove when EXTJS has fixed those bugs?!
group.expand(); group.collapse();
}
if (info.type === groupby) {
return group;
}
if (group) {
return me.groupChild(group, info, groups, level + 1);
}
}
return me.addChildSorted(node, info);
},
initComponent : function() {
var me = this;
var rstore = PVE.data.ResourceStore;
var sp = Ext.state.Manager.getProvider();
if (!me.viewFilter) {
me.viewFilter = {};
}
var pdata = {
dataIndex: {},
updateCount: 0
};
var store = Ext.create('Ext.data.TreeStore', {
model: 'PVETree',
root: {
expanded: true,
id: 'root',
text: gettext('Datacenter')
}
});
var stateid = 'rid';
var updateTree = function() {
var tmp;
// fixme: suspend events ?
var rootnode = me.store.getRootNode();
me.setIconCls(rootnode.data);
// remember selected node (and all parents)
var sm = me.getSelectionModel();
var lastsel = sm.getSelection()[0];
var parents = [];
var p = lastsel;
while (p && !!(p = p.parentNode)) {
parents.push(p);
}
var index = pdata.dataIndex;
var groups = me.viewFilter.groups || [];
var filterfn = me.viewFilter.filterfn;
// remove vanished or moved items
// update in place changed items
var key;
for (key in index) {
if (index.hasOwnProperty(key)) {
var olditem = index[key];
// getById() use find(), which is slow (ExtJS4 DP5)
//var item = rstore.getById(olditem.data.id);
var item = rstore.data.get(olditem.data.id);
var changed = false;
var moved = false;
if (item) {
// test if any grouping attributes changed
// this will also catch migrated nodes
// in server view
var i, len;
for (i = 0, len = groups.length; i < len; i++) {
var attr = groups[i];
if (item.data[attr] != olditem.data[attr]) {
//console.log("changed " + attr);
moved = true;
break;
}
}
// explicitely check for node, since
// in some views, node is not a grouping
// attribute
if (!moved && item.data.node !== olditem.data.node) {
moved = true;
}
// tree item has been updated
if ((item.data.text !== olditem.data.text) ||
(item.data.running !== olditem.data.running) ||
(item.data.template !== olditem.data.template) ||
(item.data.status !== olditem.data.status)) {
//console.log("changed node/text/running " + olditem.data.id);
changed = true;
}
// fixme: also test filterfn()?
}
if (changed) {
olditem.beginEdit();
//console.log("REM UPDATE UID: " + key + " ITEM " + item.data.running);
var info = olditem.data;
Ext.apply(info, item.data);
me.setIconCls(info);
olditem.commit();
}
if ((!item || moved) && olditem.isLeaf()) {
//console.log("REM UID: " + key + " ITEM " + olditem.data.id);
delete index[key];
var parentNode = olditem.parentNode;
parentNode.removeChild(olditem, true);
}
}
}
// add new items
rstore.each(function(item) {
var olditem = index[item.data.id];
if (olditem) {
return;
}
if (filterfn && !filterfn(item)) {
return;
}
//console.log("ADD UID: " + item.data.id);
var info = Ext.apply({ leaf: true }, item.data);
var child = me.groupChild(rootnode, info, groups, 0);
if (child) {
index[item.data.id] = child;
}
});
// select parent node is selection vanished
if (lastsel && !rootnode.findChild('id', lastsel.data.id, true)) {
lastsel = rootnode;
while (!!(p = parents.shift())) {
if (!!(tmp = rootnode.findChild('id', p.data.id, true))) {
lastsel = tmp;
break;
}
}
me.selectById(lastsel.data.id);
}
// on first tree load set the selection from the stateful provider
if (!pdata.updateCount) {
rootnode.collapse();
rootnode.expand();
me.applyState(sp.get(stateid));
}
pdata.updateCount++;
};
var statechange = function(sp, key, value) {
if (key === stateid) {
me.applyState(value);
}
};
sp.on('statechange', statechange);
Ext.apply(me, {
allowSelection: true,
store: store,
viewConfig: {
// note: animate cause problems with applyState
animate: false
},
//useArrows: true,
//rootVisible: false,
//title: 'Resource Tree',
listeners: {
itemcontextmenu: PVE.Utils.createCmdMenu,
destroy: function() {
rstore.un("load", updateTree);
},
beforecellmousedown: function (tree, td, cellIndex, record, tr, rowIndex, ev) {
// disable selection when right clicking
me.allowSelection = (ev.button !== 2);
},
beforeselect: function (tree, record, index, eopts) {
var allow = me.allowSelection;
me.allowSelection = true;
return allow;
}
},
setViewFilter: function(view) {
me.viewFilter = view;
me.clearTree();
updateTree();
},
clearTree: function() {
pdata.updateCount = 0;
var rootnode = me.store.getRootNode();
rootnode.collapse();
rootnode.removeAll();
pdata.dataIndex = {};
me.getSelectionModel().deselectAll();
},
selectExpand: function(node) {
var sm = me.getSelectionModel();
if (!sm.isSelected(node)) {
sm.select(node);
var cn = node;
while (!!(cn = cn.parentNode)) {
if (!cn.isExpanded()) {
cn.expand();
}
}
}
},
selectById: function(nodeid) {
var rootnode = me.store.getRootNode();
var sm = me.getSelectionModel();
var node;
if (nodeid === 'root') {
node = rootnode;
} else {
node = rootnode.findChild('id', nodeid, true);
}
if (node) {
me.selectExpand(node);
}
},
checkVmMigration: function(record) {
if (!(record.data.type === 'qemu' || record.data.type === 'lxc')) {
throw "not a vm type";
}
var rootnode = me.store.getRootNode();
var node = rootnode.findChild('id', record.data.id, true);
if (node && node.data.type === record.data.type &&
node.data.node !== record.data.node) {
// defer select (else we get strange errors)
Ext.defer(function() { me.selectExpand(node); }, 100, me);
}
},
applyState : function(state) {
var sm = me.getSelectionModel();
if (state && state.value) {
me.selectById(state.value);
} else {
sm.deselectAll();
}
}
});
me.callParent();
var sm = me.getSelectionModel();
sm.on('select', function(sm, n) {
sp.set(stateid, { value: n.data.id});
});
rstore.on("load", updateTree);
rstore.startUpdate();
//rstore.stopUpdate();
}
});
Ext.define('PVE.IPSetList', {
extend: 'Ext.grid.Panel',
alias: 'widget.pveIPSetList',
ipset_panel: undefined,
base_url: undefined,
addBtn: undefined,
removeBtn: undefined,
editBtn: undefined,
initComponent: function() {
/*jslint confusion: true */
var me = this;
if (me.ipset_panel == undefined) {
throw "no rule panel specified";
}
if (me.base_url == undefined) {
throw "no base_url specified";
}
var store = new Ext.data.Store({
fields: [ 'name', 'comment', 'digest' ],
proxy: {
type: 'pve',
url: "/api2/json" + me.base_url
},
idProperty: 'name',
sorters: {
property: 'name',
order: 'DESC'
}
});
var sm = Ext.create('Ext.selection.RowModel', {});
var reload = function() {
var oldrec = sm.getSelection()[0];
store.load(function(records, operation, success) {
if (oldrec) {
var rec = store.findRecord('name', oldrec.data.name);
if (rec) {
sm.select(rec);
}
}
});
};
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.window.Edit', {
subject: "IPSet '" + rec.data.name + "'",
url: me.base_url,
method: 'POST',
digest: rec.data.digest,
items: [
{
xtype: 'hiddenfield',
name: 'rename',
value: rec.data.name
},
{
xtype: 'textfield',
name: 'name',
value: rec.data.name,
fieldLabel: gettext('Name'),
allowBlank: false
},
{
xtype: 'textfield',
name: 'comment',
value: rec.data.comment,
fieldLabel: gettext('Comment')
}
]
});
win.show();
win.on('destroy', reload);
};
me.editBtn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
me.addBtn = new PVE.button.Button({
text: gettext('Create'),
handler: function() {
sm.deselectAll();
var win = Ext.create('PVE.window.Edit', {
subject: 'IPSet',
url: me.base_url,
method: 'POST',
items: [
{
xtype: 'textfield',
name: 'name',
value: '',
fieldLabel: gettext('Name'),
allowBlank: false
},
{
xtype: 'textfield',
name: 'comment',
value: '',
fieldLabel: gettext('Comment')
}
]
});
win.show();
win.on('destroy', reload);
}
});
me.removeBtn = new PVE.button.Button({
text: gettext('Remove'),
selModel: sm,
disabled: true,
handler: function() {
var rec = sm.getSelection()[0];
if (!rec || !me.base_url) {
return;
}
PVE.Utils.API2Request({
url: me.base_url + '/' + rec.data.name,
method: 'DELETE',
waitMsgTarget: me,
failure: function(response, options) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
callback: reload
});
}
});
Ext.apply(me, {
store: store,
tbar: [ '<b>IPSet:</b>', me.addBtn, me.removeBtn, me.editBtn ],
selModel: sm,
columns: [
{ header: 'IPSet', dataIndex: 'name', width: 100 },
{ header: gettext('Comment'), dataIndex: 'comment', renderer: Ext.String.htmlEncode, flex: 1 }
],
listeners: {
itemdblclick: run_editor,
select: function(sm, rec) {
var url = me.base_url + '/' + rec.data.name;
me.ipset_panel.setBaseUrl(url);
},
deselect: function() {
me.ipset_panel.setBaseUrl(undefined);
},
show: reload
}
});
me.callParent();
store.load();
}
});
Ext.define('PVE.IPSetCidrEdit', {
extend: 'PVE.window.Edit',
cidr: undefined,
initComponent : function() {
/*jslint confusion: true */
var me = this;
me.create = (me.cidr === undefined);
if (me.create) {
me.url = '/api2/extjs' + me.base_url;
me.method = 'POST';
} else {
me.url = '/api2/extjs' + me.base_url + '/' + me.cidr;
me.method = 'PUT';
}
var column1 = [];
if (me.create) {
if (!me.list_refs_url) {
throw "no alias_base_url specified";
}
column1.push({
xtype: 'pveIPRefSelector',
name: 'cidr',
ref_type: 'alias',
autoSelect: false,
editable: true,
base_url: me.list_refs_url,
value: '',
fieldLabel: gettext('IP/CIDR')
});
} else {
column1.push({
xtype: 'displayfield',
name: 'cidr',
value: '',
fieldLabel: gettext('IP/CIDR')
});
}
var ipanel = Ext.create('PVE.panel.InputPanel', {
create: me.create,
column1: column1,
column2: [
{
xtype: 'pvecheckbox',
name: 'nomatch',
checked: false,
uncheckedValue: 0,
fieldLabel: 'nomatch'
}
],
columnB: [
{
xtype: 'textfield',
name: 'comment',
value: '',
fieldLabel: gettext('Comment')
}
]
});
Ext.apply(me, {
subject: gettext('IP/CIDR'),
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.IPSetGrid', {
extend: 'Ext.grid.Panel',
alias: 'widget.pveIPSetGrid',
base_url: undefined,
list_refs_url: undefined,
addBtn: undefined,
removeBtn: undefined,
editBtn: undefined,
setBaseUrl: function(url) {
var me = this;
me.base_url = url;
if (url === undefined) {
me.addBtn.setDisabled(true);
me.store.removeAll();
} else {
me.addBtn.setDisabled(false);
me.store.setProxy({
type: 'pve',
url: '/api2/json' + url
});
me.store.load();
}
},
initComponent: function() {
/*jslint confusion: true */
var me = this;
if (!me.list_refs_url) {
throw "no1 list_refs_url specified";
}
var store = new Ext.data.Store({
model: 'pve-ipset'
});
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.IPSetCidrEdit', {
base_url: me.base_url,
cidr: rec.data.cidr
});
win.show();
win.on('destroy', reload);
};
me.editBtn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
me.addBtn = new PVE.button.Button({
text: gettext('Add'),
disabled: true,
handler: function() {
if (!me.base_url) {
return;
}
var win = Ext.create('PVE.IPSetCidrEdit', {
base_url: me.base_url,
list_refs_url: me.list_refs_url
});
win.show();
win.on('destroy', reload);
}
});
me.removeBtn = new PVE.button.Button({
text: gettext('Remove'),
selModel: sm,
disabled: true,
handler: function() {
var rec = sm.getSelection()[0];
if (!rec || !me.base_url) {
return;
}
PVE.Utils.API2Request({
url: me.base_url + '/' + rec.data.cidr,
method: 'DELETE',
waitMsgTarget: me,
failure: function(response, options) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
callback: reload
});
}
});
var render_errors = function(value, metaData, record) {
var errors = record.data.errors;
if (errors) {
var msg = errors.cidr || errors.nomatch;
if (msg) {
metaData.tdCls = 'x-form-invalid-field';
var html = '<p>' + Ext.htmlEncode(msg) + '</p>';
metaData.tdAttr = 'data-qwidth=600 data-qtitle="ERROR" data-qtip="' +
html.replace(/\"/g,'&quot;') + '"';
}
}
return value;
};
Ext.apply(me, {
tbar: [ '<b>IP/CIDR:</b>', me.addBtn, me.removeBtn, me.editBtn ],
store: store,
selModel: sm,
listeners: {
itemdblclick: run_editor
},
columns: [
{
xtype: 'rownumberer'
},
{
header: gettext('IP/CIDR'),
dataIndex: 'cidr',
width: 150,
renderer: function(value, metaData, record) {
value = render_errors(value, metaData, record);
if (record.data.nomatch) {
return '<b>! </b>' + value;
}
return value;
}
},
{
header: gettext('Comment'),
dataIndex: 'comment',
flex: 1,
renderer: function(value) {
return Ext.util.Format.htmlEncode(value);
}
}
]
});
me.callParent();
if (me.base_url) {
me.setBaseUrl(me.base_url); // load
}
}
}, function() {
Ext.define('pve-ipset', {
extend: 'Ext.data.Model',
fields: [ { name: 'nomatch', type: 'boolean' },
'cidr', 'comment', 'errors' ],
idProperty: 'cidr'
});
});
Ext.define('PVE.IPSet', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveIPSet',
title: 'IPSet',
list_refs_url: undefined,
initComponent: function() {
var me = this;
if (!me.list_refs_url) {
throw "no list_refs_url specified";
}
var ipset_panel = Ext.createWidget('pveIPSetGrid', {
region: 'center',
list_refs_url: me.list_refs_url,
flex: 0.5,
border: false
});
var ipset_list = Ext.createWidget('pveIPSetList', {
region: 'west',
ipset_panel: ipset_panel,
base_url: me.base_url,
flex: 0.5,
border: false,
split: true
});
Ext.apply(me, {
layout: 'border',
items: [ ipset_list, ipset_panel ],
listeners: {
show: function() {
ipset_list.fireEvent('show', ipset_list);
}
}
});
me.callParent();
}
});
/*
* Base class for all the multitab config panels
*/
Ext.define('PVE.panel.Config', {
extend: 'Ext.panel.Panel',
alias: 'widget.pvePanelConfig',
showSearch: true, // add a ressource grid with a search button as first tab
viewFilter: undefined, // a filter to pass to that ressource grid
initComponent: function() {
var me = this;
var stateid = me.hstateid;
var sp = Ext.state.Manager.getProvider();
var activeTab; // leaving this undefined means items[0] will be the default tab
var hsregex = /^([^\-\s]+)(-\S+)?$/;
if (stateid) {
var state = sp.get(stateid);
if (state && state.value) {
var res = hsregex.exec(state.value);
if (res && res[1] && Ext.isArray(me.items)) {
me.items.forEach(function(item) {
if (item.itemId === res[1]) {
activeTab = res[1];
}
});
} else if (res && res[1] && me.items && me.items.itemId === res[1]) {
activeTab = res[1];
}
}
}
var items = me.items || [];
me.items = undefined;
var tbar = me.tbar || [];
me.tbar = undefined;
var title = me.title || me.pveSelNode.data.text;
me.title = undefined;
tbar.unshift('->');
tbar.unshift({
xtype: 'tbtext',
text: title,
baseCls: 'x-panel-header-text'
});
if (me.showSearch) {
items.unshift({
itemId: 'search',
title: gettext('Search'),
layout: { type:'fit' },
plugins: [{
ptype: 'lazyitems',
items: [{
xtype: 'pveResourceGrid',
pveSelNode: me.pveSelNode
}]
}]
});
}
var toolbar = Ext.create('Ext.toolbar.Toolbar', {
items: tbar,
border: false,
height: 36
});
var tab = Ext.create('Ext.tab.Panel', {
flex: 1,
border: true,
activeTab: activeTab,
defaults: Ext.apply(me.defaults || {}, {
pveSelNode: me.pveSelNode,
viewFilter: me.viewFilter,
workspace: me.workspace,
border: false
}),
items: items,
listeners: {
tabchange: function(tp, newcard, oldcard) {
var ntab = newcard.itemId;
// Note: '' is alias for first tab.
// First tab can be 'search' or something else
if (newcard.itemId === items[0].itemId) {
ntab = '';
}
if (stateid) {
if (newcard.phstateid) {
sp.set(newcard.phstateid, newcard.getHState());
} else {
sp.set(stateid, { value: ntab });
}
}
// if we have a tabpanel which we declared lazy (with ptype: lazyitems)
// then we have the actual item in items.items[0]
// and there we need to fire the event hide
// because some tabs use this event (which is not fired in this case)
if (oldcard.plugins && oldcard.plugins[0] && oldcard.plugins[0].ptype == 'lazyitems') {
oldcard.items.items[0].fireEvent('hide');
}
}
}
});
Ext.apply(me, {
layout: { type: 'vbox', align: 'stretch' },
items: [ toolbar, tab]
});
me.callParent();
var statechange = function(sp, key, state) {
if (stateid && (key === stateid) && state) {
var atab = tab.getActiveTab().itemId;
var res = hsregex.exec(state.value);
var ntab = (res && res[1]) ? res[1] : items[0].itemId;
if (ntab && (atab != ntab)) {
tab.setActiveTab(ntab);
}
}
};
if (stateid) {
me.mon(sp, 'statechange', statechange);
}
}
});
Ext.define('PVE.panel.SubConfig', {
extend: 'Ext.tab.Panel',
alias: ['widget.pvePanelSubConfig'],
configPrefix: undefined,
tabPosition: 'left',
tabRotation: 0,
savedTab: undefined,
getHState: function(itemId) {
/*jslint confusion: true */
var me = this;
if (!itemId) {
if (me.getActiveTab() === undefined) {
me.setActiveTab(0);
}
itemId = me.getActiveTab().itemId;
}
var first = me.items.get(0);
var ntab;
// Note: '' is alias for first tab.
if (itemId === first.itemId) {
ntab = me.configPrefix;
} else {
ntab = me.configPrefix + '-' + itemId;
}
return { value: ntab };
},
initComponent: function() {
var me = this;
if (!me.phstateid) {
throw "no parent history state specified";
}
var sp = Ext.state.Manager.getProvider();
var state = sp.get(me.phstateid);
var hsregex = /^([^\-\s]+)-(\S+)?$/;
if (state && state.value) {
var res = hsregex.exec(state.value);
if (res && res[1] && res[2] && res[1] === me.configPrefix) {
me.activeTab = res[2]; // if we have lazy items, this does nothing
me.savedTab = res[2]; // so we save it here
}
}
Ext.apply(me, {
listeners: {
afterrender: function(tp) {
// if we have lazy items,
// we saved the tabname in savedTab
if (me.activeTab === undefined && me.savedTab) {
me.setActiveTab(me.savedTab);
} else if (me.activeTab === undefined) {
// if we have nothing else, we set 0 as active
me.setActiveTab(0);
}
},
tabchange: function(tp, newcard, oldcard) {
var state = me.getHState(newcard.itemId);
sp.set(me.phstateid, state);
}
}
});
me.callParent();
var statechange = function(sp, key, state) {
if ((key === me.phstateid) && state) {
var first = me.items.get(0);
var atab = me.getActiveTab().itemId;
var res = hsregex.exec(state.value);
var ntab = (res && res[1]) ? res[1] : first.itemId;
if (ntab && (atab != ntab)) {
me.setActiveTab(ntab);
}
}
};
me.mon(sp, 'statechange', statechange);
}
});
Ext.define('PVE.grid.BackupView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveBackupView'],
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var vmtype = me.pveSelNode.data.type;
if (!vmtype) {
throw "no VM type specified";
}
var filterFn;
if (vmtype === 'openvz') {
filterFn = function(item) {
return item.data.volid.match(':backup/vzdump-openvz-');
};
} else if (vmtype === 'lxc') {
filterFn = function(item) {
return item.data.volid.match(':backup/vzdump-lxc-');
};
} else if (vmtype === 'qemu') {
filterFn = function(item) {
return item.data.volid.match(':backup/vzdump-qemu-');
};
} else {
throw "unsupported VM type '" + vmtype + "'";
}
me.store = Ext.create('Ext.data.Store', {
model: 'pve-storage-content',
sorters: {
property: 'volid',
order: 'DESC'
},
filters: { filterFn: filterFn }
});
var reload = Ext.Function.createBuffered(function() {
if (me.store && me.store.proxy.url) {
me.store.load();
}
}, 100);
var setStorage = function(storage) {
var url = '/api2/json/nodes/' + nodename + '/storage/' + storage + '/content';
url += '?content=backup';
me.store.setProxy({
type: 'pve',
url: url
});
reload();
};
var storagesel = Ext.create('PVE.form.StorageSelector', {
nodename: nodename,
fieldLabel: gettext('Storage'),
labelAlign: 'right',
storageContent: 'backup',
allowBlank: false,
listeners: {
change: function(f, value) {
setStorage(value);
}
}
});
var storagefilter = Ext.create('Ext.form.field.Text', {
fieldLabel: gettext('Search'),
labelWidth: 50,
labelAlign: 'right',
enableKeyEvents: true,
listeners: {
buffer: 500,
keyup: function(field) {
me.store.clearFilter(true);
me.store.filter([
filterFn,
{
property: 'volid',
value: field.getValue(),
anyMatch: true,
caseSensitive: false
}
]);
}
}
});
var sm = Ext.create('Ext.selection.RowModel', {});
var backup_btn = Ext.create('Ext.button.Button', {
text: gettext('Backup now'),
handler: function() {
var win = Ext.create('PVE.window.Backup', {
nodename: nodename,
vmid: vmid,
vmtype: vmtype,
storage: storagesel.getValue()
});
win.show();
}
});
var restore_btn = Ext.create('PVE.button.Button', {
text: gettext('Restore'),
disabled: true,
selModel: sm,
enableFn: function(rec) {
return !!rec;
},
handler: function(b, e, rec) {
var volid = rec.data.volid;
var win = Ext.create('PVE.window.Restore', {
nodename: nodename,
vmid: vmid,
volid: rec.data.volid,
volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
vmtype: vmtype
});
win.show();
win.on('destroy', reload);
}
});
var delete_btn = Ext.create('PVE.button.Button', {
text: gettext('Remove'),
disabled: true,
selModel: sm,
dangerous: true,
confirmMsg: function(rec) {
var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.volid + "'");
msg += " " + gettext('This will permanently erase all data.');
return msg;
},
enableFn: function(rec) {
return !!rec;
},
handler: function(b, e, rec){
var storage = storagesel.getValue();
if (!storage) {
return;
}
var volid = rec.data.volid;
PVE.Utils.API2Request({
url: "/nodes/" + nodename + "/storage/" + storage + "/content/" + volid,
method: 'DELETE',
waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response, options) {
reload();
}
});
}
});
var config_btn = Ext.create('PVE.button.Button', {
text: gettext('Show Configuration'),
disabled: true,
selModel: sm,
enableFn: function(rec) {
return !!rec;
},
handler: function(b, e, rec) {
var storage = storagesel.getValue();
if (!storage) {
return;
}
var win = Ext.create('Ext.window.Window', {
title: gettext('Configuration'),
width: 600,
height: 400,
layout: 'fit',
modal: true,
items: [{
itemId: 'configtext',
autoScroll: true,
style: {
'background-color': 'white',
'white-space': 'pre',
'font-family': 'monospace',
padding: '5px'
}
}]
});
PVE.Utils.API2Request({
url: "/nodes/" + nodename + "/vzdump/extractconfig",
method: 'GET',
params: {
volume: rec.data.volid
},
failure: function(response, opts) {
win.close();
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response,options) {
win.show();
win.down('#configtext').update(Ext.htmlEncode(response.result.data));
}
});
}
});
Ext.apply(me, {
stateful: false,
selModel: sm,
tbar: [ backup_btn, restore_btn, delete_btn,config_btn, '->', storagesel, storagefilter ],
columns: [
{
header: gettext('Name'),
flex: 1,
sortable: true,
renderer: PVE.Utils.render_storage_content,
dataIndex: 'volid'
},
{
header: gettext('Format'),
width: 100,
dataIndex: 'format'
},
{
header: gettext('Size'),
width: 100,
renderer: PVE.Utils.format_size,
dataIndex: 'size'
}
],
listeners: {
activate: reload
}
});
me.callParent();
}
});
/*
* Display log entries in a panel with scrollbar
* The log entries are automatically refreshed via a background task,
* with newest entries comming at the bottom
*/
Ext.define('PVE.panel.LogView', {
extend: 'Ext.panel.Panel',
alias: ['widget.pveLogView'],
pageSize: 500,
lineHeight: 16,
viewInfo: undefined,
scrollToEnd: true,
autoScroll: true,
layout: 'auto',
bodyPadding: 5,
getMaxDown: function(scrollToEnd) {
var me = this;
var target = me.getTargetEl();
var dom = target.dom;
if (scrollToEnd) {
dom.scrollTop = dom.scrollHeight - dom.clientHeight;
}
var maxDown = dom.scrollHeight - dom.clientHeight -
dom.scrollTop;
return maxDown;
},
updateView: function(start, end, total, text) {
var me = this;
var el = me.dataCmp.el;
if (me.destroyed) { // return if element is not there anymore
return;
}
if (me.viewInfo && me.viewInfo.start === start &&
me.viewInfo.end === end && me.viewInfo.total === total &&
me.viewInfo.textLength === text.length) {
return; // same content
}
var maxDown = me.getMaxDown();
var scrollToEnd = (maxDown <= 0) && me.scrollToEnd;
el.setStyle('padding-top', (start*me.lineHeight).toString() + 'px');
el.update(text);
me.dataCmp.setHeight(total*me.lineHeight);
if (scrollToEnd) {
me.getMaxDown(true);
}
me.viewInfo = {
start: start,
end: end,
total: total,
textLength: text.length
};
},
doAttemptLoad: function(start) {
var me = this;
var req_params = {
start: start,
limit: me.pageSize
};
if (me.log_select_timespan) {
// always show log until the end of the selected day
req_params.until = Ext.Date.format(me.until_date, 'Y-m-d') + ' 23:59:59';
req_params.since = Ext.Date.format(me.since_date, 'Y-m-d');
}
PVE.Utils.API2Request({
url: me.url,
params: req_params,
method: 'GET',
success: function(response) {
PVE.Utils.setErrorMask(me, false);
var list = response.result.data;
var total = response.result.total;
var first = 0, last = 0;
var text = '';
Ext.Array.each(list, function(item) {
if (!first|| item.n < first) {
first = item.n;
}
if (!last || item.n > last) {
last = item.n;
}
text = text + Ext.htmlEncode(item.t) + "<br>";
});
if (first && last && total) {
me.updateView(first -1 , last -1, total, text);
} else {
me.updateView(0, 0, 0, '');
}
},
failure: function(response) {
var msg = response.htmlStatus;
PVE.Utils.setErrorMask(me, msg);
}
});
},
attemptLoad: function(start) {
var me = this;
if (!me.loadTask) {
me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
}
me.loadTask.delay(200, me.doAttemptLoad, me, [start]);
},
requestUpdate: function(top, force) {
var me = this;
if (top === undefined) {
var target = me.getTargetEl();
top = target.dom.scrollTop;
}
var viewStart = parseInt((top / me.lineHeight) - 1, 10);
if (viewStart < 0) {
viewStart = 0;
}
var viewEnd = parseInt(((top + me.getHeight())/ me.lineHeight) + 1, 10);
var info = me.viewInfo;
if (info && !force) {
if (viewStart >= info.start && viewEnd <= info.end) {
return;
}
}
var line = parseInt((top / me.lineHeight) - (me.pageSize / 2) + 10, 10);
if (line < 0) {
line = 0;
}
me.attemptLoad(line);
},
afterRender: function() {
var me = this;
me.callParent(arguments);
Ext.Function.defer(function() {
var target = me.getTargetEl();
target.on('scroll', function(e) {
me.requestUpdate();
});
me.requestUpdate(0);
}, 20);
},
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.url) {
throw "no url specified";
}
// show logs from today back to 3 days ago per default
me.until_date = new Date();
me.since_date = new Date();
me.since_date.setDate(me.until_date.getDate() - 3);
me.dataCmp = Ext.create('Ext.Component', {
style: 'font:normal 11px tahoma, arial, verdana, sans-serif;' +
'line-height: ' + me.lineHeight.toString() + 'px; white-space: pre;'
});
me.task = Ext.TaskManager.start({
run: function() {
if (!me.isVisible() || !me.scrollToEnd || !me.viewInfo) {
return;
}
var maxDown = me.getMaxDown();
if (maxDown > 0) {
return;
}
me.requestUpdate(undefined, true);
},
interval: 1000
});
Ext.apply(me, {
items: me.dataCmp,
listeners: {
destroy: function() {
Ext.TaskManager.stop(me.task);
}
}
});
if (me.log_select_timespan) {
me.tbar = ['->','Since: ',
{
xtype: 'datefield',
maxValue: me.until_date,
value: me.since_date,
name: 'since_date',
format: 'Y-m-d',
listeners: {
select: function(field, date) {
me.since_date_selected = date;
var until_field = field.up().down('field[name=until_date]');
if (date > until_field.getValue()) {
until_field.setValue(date);
}
}
}
},
'Until: ',
{
xtype: 'datefield',
maxValue: me.until_date,
value: me.until_date,
name: 'until_date',
format: 'Y-m-d',
listeners: {
select: function(field, date) {
var since_field = field.up().down('field[name=since_date]');
if (date < since_field.getValue()) {
since_field.setValue(date);
}
}
}
},
{
xtype: 'button',
text: 'Update',
handler: function() {
var until_field = me.down('field[name=until_date]');
var since_field = me.down('field[name=since_date]');
if (until_field.getValue() < since_field.getValue()) {
Ext.Msg.alert('Error',
'Since date must be less equal than Until date.');
until_field.setValue(me.until_date);
since_field.setValue(me.since_date);
} else {
me.until_date = until_field.getValue();
me.since_date = since_field.getValue();
me.requestUpdate();
}
}
}
];
}
me.callParent();
}
});
Ext.define('PVE.panel.Firewall', {
extend: 'PVE.panel.SubConfig',
alias: 'widget.pveFirewallPanel',
configPrefix: 'firewall',
fwtype: undefined, // 'dc', 'node' or 'vm'
base_url: undefined,
initComponent: function() {
/*jslint confusion: true */
var me = this;
if (!me.base_url) {
throw "no base_url specified";
}
if (!(me.fwtype === 'dc' || me.fwtype === 'node' || me.fwtype === 'vm')) {
throw "unknown firewall panel type";
}
var list_refs_url = me.fwtype === 'vm' ? (me.base_url + '/refs') :
'/cluster/firewall/refs';
var items = [
{
xtype: 'pveFirewallRules',
title: gettext('Rules'),
allow_iface: true,
base_url: me.base_url + '/rules',
list_refs_url: list_refs_url,
itemId: 'rules'
}
];
if (me.fwtype === 'dc') {
items.push({
xtype: 'pveSecurityGroups',
title: gettext('Security Group'),
itemId: 'sg'
});
items.push({
xtype: 'pveFirewallAliases',
base_url: '/cluster/firewall/aliases',
itemId: 'aliases'
});
items.push({
xtype: 'pveIPSet',
base_url: '/cluster/firewall/ipset',
list_refs_url: list_refs_url,
itemId: 'ipset'
});
}
if (me.fwtype === 'vm') {
items.push({
xtype: 'pveFirewallAliases',
base_url: me.base_url + '/aliases',
itemId: 'aliases'
});
items.push({
xtype: 'pveIPSet',
base_url: me.base_url + '/ipset',
list_refs_url: list_refs_url,
itemId: 'ipset'
});
}
items.push({
xtype: 'pveFirewallOptions',
title: gettext('Options'),
base_url: me.base_url + '/options',
fwtype: me.fwtype,
itemId: 'options'
});
if (me.fwtype !== 'dc') {
items.push({
title: gettext('Log'),
itemId: 'fwlog',
xtype: 'pveLogView',
url: '/api2/extjs' + me.base_url + '/log'
});
}
Ext.apply(me, {
defaults: {
border: false,
pveSelNode: me.pveSelNode
},
plugins: [{
ptype: 'lazyitems',
items: items
}]
});
me.callParent();
}
});
// Ext.create is a function, but
// we defined create a bool in PVE.window.Edit
/*jslint confusion: true*/
Ext.define('PVE.CephCreatePool', {
extend: 'PVE.window.Edit',
alias: ['widget.pveCephCreatePool'],
subject: 'Ceph Pool',
create: true,
method: 'POST',
items: [
{
xtype: 'textfield',
fieldLabel: gettext('Name'),
name: 'name',
allowBlank: false
},
{
xtype: 'numberfield',
fieldLabel: gettext('Size'),
name: 'size',
value: 2,
minValue: 1,
maxValue: 3,
allowBlank: false
},
{
xtype: 'numberfield',
fieldLabel: gettext('Min. Size'),
name: 'min_size',
value: 1,
minValue: 1,
maxValue: 3,
allowBlank: false
},
{
xtype: 'numberfield',
fieldLabel: gettext('Crush RuleSet'),
name: 'crush_ruleset',
value: 0,
minValue: 0,
maxValue: 32768,
allowBlank: false
},
{
xtype: 'numberfield',
fieldLabel: 'pg_num',
name: 'pg_num',
value: 64,
minValue: 8,
maxValue: 32768,
allowBlank: false
}
],
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
Ext.applyIf(me, {
url: "/nodes/" + me.nodename + "/ceph/pools"
});
me.callParent();
}
});
Ext.define('PVE.node.CephPoolList', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveNodeCephPoolList'],
stateful: false,
bufferedRenderer: false,
features: [ { ftype: 'summary'} ],
columns: [
{
header: gettext('Name'),
width: 100,
sortable: true,
dataIndex: 'pool_name'
},
{
header: gettext('Size') + '/min',
width: 80,
sortable: false,
renderer: function(v, meta, rec) {
return v + '/' + rec.data.min_size;
},
dataIndex: 'size'
},
{
header: 'pg_num',
width: 100,
sortable: false,
dataIndex: 'pg_num'
},
{
header: 'ruleset',
width: 50,
sortable: false,
dataIndex: 'crush_ruleset'
},
{
header: gettext('Used'),
columns: [
{
header: '%',
width: 80,
sortable: true,
align: 'right',
renderer: Ext.util.Format.numberRenderer('0.00'),
dataIndex: 'percent_used',
summaryType: 'sum',
summaryRenderer: Ext.util.Format.numberRenderer('0.00')
},
{
header: gettext('Total'),
width: 100,
sortable: true,
renderer: PVE.Utils.render_size,
align: 'right',
dataIndex: 'bytes_used',
summaryType: 'sum',
summaryRenderer: PVE.Utils.render_size
}
]
}
],
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var sm = Ext.create('Ext.selection.RowModel', {});
var rstore = Ext.create('PVE.data.UpdateStore', {
interval: 3000,
storeid: 'ceph-pool-list' + nodename,
model: 'ceph-pool-list',
proxy: {
type: 'pve',
url: "/api2/json/nodes/" + nodename + "/ceph/pools"
}
});
var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
PVE.Utils.monStoreErrors(me, rstore);
var create_btn = new Ext.Button({
text: gettext('Create'),
handler: function() {
var win = Ext.create('PVE.CephCreatePool', {
nodename: nodename
});
win.show();
}
});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
selModel: sm,
disabled: true,
confirmMsg: function(rec) {
var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.pool_name + "'");
msg += " " + gettext('This will permanently erase all data.');
return msg;
},
handler: function() {
var rec = sm.getSelection()[0];
if (!rec.data.pool_name) {
return;
}
PVE.Utils.API2Request({
url: "/nodes/" + nodename + "/ceph/pools/" +
rec.data.pool_name,
method: 'DELETE',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
Ext.apply(me, {
store: store,
selModel: sm,
tbar: [ create_btn, remove_btn ],
listeners: {
activate: rstore.startUpdate,
hide: rstore.stopUpdate,
destroy: rstore.stopUpdate
}
});
me.callParent();
}
}, function() {
Ext.define('ceph-pool-list', {
extend: 'Ext.data.Model',
fields: [ 'pool_name',
{ name: 'pool', type: 'integer'},
{ name: 'size', type: 'integer'},
{ name: 'min_size', type: 'integer'},
{ name: 'pg_num', type: 'integer'},
{ name: 'bytes_used', type: 'integer'},
{ name: 'percent_used', type: 'number'},
{ name: 'crush_ruleset', type: 'integer'}
],
idProperty: 'pool_name'
});
});
Ext.define('PVE.CephCreateOsd', {
extend: 'PVE.window.Edit',
alias: ['widget.pveCephCreateOsd'],
subject: 'Ceph OSD',
showProgress: true,
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
me.create = true;
Ext.applyIf(me, {
url: "/nodes/" + me.nodename + "/ceph/osd",
method: 'POST',
items: [
{
xtype: 'pveCephDiskSelector',
name: 'dev',
nodename: me.nodename,
value: me.dev,
diskType: 'unused',
fieldLabel: gettext('Disk'),
allowBlank: false
},
{
xtype: 'pveCephDiskSelector',
name: 'journal_dev',
nodename: me.nodename,
diskType: 'journal_disks',
fieldLabel: gettext('Journal Disk'),
value: '',
autoSelect: false,
allowBlank: true,
emptyText: 'use OSD disk'
}
]
});
me.callParent();
}
});
Ext.define('PVE.CephRemoveOsd', {
extend: 'PVE.window.Edit',
alias: ['widget.pveCephRemoveOsd'],
isRemove: true,
showProgress: true,
method: 'DELETE',
items: [
{
xtype: 'pvecheckbox',
name: 'cleanup',
checked: true,
labelWidth: 130,
fieldLabel: gettext('Remove Partitions')
}
],
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (me.osdid === undefined || me.osdid < 0) {
throw "no osdid specified";
}
me.create = true;
me.title = gettext('Remove') + ': ' + 'Ceph OSD osd.' + me.osdid;
Ext.applyIf(me, {
url: "/nodes/" + me.nodename + "/ceph/osd/" + me.osdid
});
me.callParent();
}
});
Ext.define('PVE.node.CephOsdTree', {
extend: 'Ext.tree.Panel',
alias: ['widget.pveNodeCephOsdTree'],
columns: [
{
xtype: 'treecolumn',
text: 'Name',
dataIndex: 'name',
width: 150
},
{
text: 'Type',
dataIndex: 'type',
align: 'right',
width: 60
},
{
text: 'Status',
dataIndex: 'status',
align: 'right',
renderer: function(value, metaData, rec) {
if (!value) {
return value;
}
var data = rec.data;
return value + '/' + (data['in'] ? 'in' : 'out');
},
width: 80
},
{
text: 'weight',
dataIndex: 'crush_weight',
align: 'right',
renderer: function(value, metaData, rec) {
if (rec.data.type !== 'osd') {
return '';
}
return value;
},
width: 80
},
{
text: 'reweight',
dataIndex: 'reweight',
align: 'right',
renderer: function(value, metaData, rec) {
if (rec.data.type !== 'osd') {
return '';
}
return value;
},
width: 90
},
{
header: gettext('Used'),
columns: [
{
text: '%',
dataIndex: 'percent_used',
align: 'right',
renderer: function(value, metaData, rec) {
if (rec.data.type !== 'osd') {
return '';
}
return Ext.util.Format.number(value, '0.00');
},
width: 80
},
{
text: gettext('Total'),
dataIndex: 'total_space',
align: 'right',
renderer: function(value, metaData, rec) {
if (rec.data.type !== 'osd') {
return '';
}
return PVE.Utils.render_size(value);
},
width: 100
}
]
},
{
header: gettext('Latency (ms)'),
columns: [
{
text: 'Apply',
dataIndex: 'apply_latency_ms',
align: 'right',
renderer: function(value, metaData, rec) {
if (rec.data.type !== 'osd') {
return '';
}
return value;
},
width: 60
},
{
text: 'Commit',
dataIndex: 'commit_latency_ms',
align: 'right',
renderer: function(value, metaData, rec) {
if (rec.data.type !== 'osd') {
return '';
}
return value;
},
width: 60
}
]
}
],
initComponent: function() {
/*jslint confusion: true */
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var sm = Ext.create('Ext.selection.TreeModel', {});
var set_button_status; // defined later
var reload = function() {
PVE.Utils.API2Request({
url: "/nodes/" + nodename + "/ceph/osd",
waitMsgTarget: me,
method: 'GET',
failure: function(response, opts) {
PVE.Utils.setErrorMask(me, response.htmlStatus);
},
success: function(response, opts) {
sm.deselectAll();
me.setRootNode(response.result.data.root);
me.expandAll();
set_button_status();
}
});
};
var osd_cmd = function(cmd) {
var rec = sm.getSelection()[0];
if (!(rec && (rec.data.id >= 0) && rec.data.host)) {
return;
}
PVE.Utils.API2Request({
url: "/nodes/" + rec.data.host + "/ceph/osd/" +
rec.data.id + '/' + cmd,
waitMsgTarget: me,
method: 'POST',
success: reload,
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
};
var service_cmd = function(cmd) {
var rec = sm.getSelection()[0];
if (!(rec && rec.data.name && rec.data.host)) {
return;
}
PVE.Utils.API2Request({
url: "/nodes/" + rec.data.host + "/ceph/" + cmd,
params: { service: rec.data.name },
waitMsgTarget: me,
method: 'POST',
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', { upid: upid });
win.show();
me.mon(win, 'close', reload, me);
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
};
var start_btn = new Ext.Button({
text: gettext('Start'),
disabled: true,
handler: function(){ service_cmd('start'); }
});
var stop_btn = new Ext.Button({
text: gettext('Stop'),
disabled: true,
handler: function(){ service_cmd('stop'); }
});
var osd_out_btn = new Ext.Button({
text: 'Out',
disabled: true,
handler: function(){ osd_cmd('out'); }
});
var osd_in_btn = new Ext.Button({
text: 'In',
disabled: true,
handler: function(){ osd_cmd('in'); }
});
var remove_btn = new Ext.Button({
text: gettext('Remove'),
disabled: true,
handler: function(){
var rec = sm.getSelection()[0];
if (!(rec && (rec.data.id >= 0) && rec.data.host)) {
return;
}
var win = Ext.create('PVE.CephRemoveOsd', {
nodename: rec.data.host,
osdid: rec.data.id
});
win.show();
me.mon(win, 'close', reload, me);
}
});
set_button_status = function() {
var rec = sm.getSelection()[0];
if (!rec) {
start_btn.setDisabled(true);
stop_btn.setDisabled(true);
remove_btn.setDisabled(true);
osd_out_btn.setDisabled(true);
osd_in_btn.setDisabled(true);
return;
}
var isOsd = (rec.data.host && (rec.data.type === 'osd') && (rec.data.id >= 0));
start_btn.setDisabled(!(isOsd && (rec.data.status !== 'up')));
stop_btn.setDisabled(!(isOsd && (rec.data.status !== 'down')));
remove_btn.setDisabled(!(isOsd && (rec.data.status === 'down')));
osd_out_btn.setDisabled(!(isOsd && rec.data['in']));
osd_in_btn.setDisabled(!(isOsd && !rec.data['in']));
};
sm.on('selectionchange', set_button_status);
var reload_btn = new Ext.Button({
text: gettext('Reload'),
handler: reload
});
Ext.apply(me, {
tbar: [ reload_btn, start_btn, stop_btn, osd_out_btn, osd_in_btn, remove_btn ],
rootVisible: false,
fields: ['name', 'type', 'status', 'host', 'in', 'id' ,
{ type: 'number', name: 'reweight' },
{ type: 'number', name: 'percent_used' },
{ type: 'integer', name: 'bytes_used' },
{ type: 'integer', name: 'total_space' },
{ type: 'integer', name: 'apply_latency_ms' },
{ type: 'integer', name: 'commit_latency_ms' },
{ type: 'number', name: 'crush_weight' }],
stateful: false,
selModel: sm,
listeners: {
activate: function() {
reload();
}
}
});
me.callParent();
reload();
}
});
Ext.define('PVE.node.CephDiskList', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveNodeCephDiskList'],
columns: [
{
header: gettext('Device'),
width: 100,
sortable: true,
dataIndex: 'dev'
},
{
header: gettext('Usage'),
width: 80,
sortable: false,
renderer: function(v, metaData, rec) {
if (rec && (rec.data.osdid >= 0)) {
return "osd." + rec.data.osdid.toString();
}
return v || PVE.Utils.noText;
},
dataIndex: 'used'
},
{
header: gettext('Size'),
width: 100,
align: 'right',
sortable: false,
renderer: PVE.Utils.format_size,
dataIndex: 'size'
},
{
header: gettext('Vendor'),
width: 100,
sortable: true,
dataIndex: 'vendor'
},
{
header: gettext('Model'),
width: 200,
sortable: true,
dataIndex: 'model'
},
{
header: gettext('Serial'),
flex: 1,
sortable: true,
dataIndex: 'serial'
}
],
initComponent: function() {
/*jslint confusion: true */
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var sm = Ext.create('Ext.selection.RowModel', {});
var rstore = Ext.create('PVE.data.UpdateStore', {
interval: 3000,
storeid: 'ceph-disk-list' + nodename,
model: 'ceph-disk-list',
proxy: {
type: 'pve',
url: "/api2/json/nodes/" + nodename + "/ceph/disks"
},
sorters: [
{
property : 'dev',
direction: 'ASC'
}
]
});
var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
PVE.Utils.monStoreErrors(me, rstore);
var create_btn = new PVE.button.Button({
text: gettext('Create') + ': OSD',
selModel: sm,
disabled: true,
enableFn: function(rec) {
return !rec.data.used;
},
handler: function() {
var rec = sm.getSelection()[0];
var win = Ext.create('PVE.CephCreateOsd', {
nodename: nodename,
dev: rec.data.dev
});
win.show();
}
});
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
tbar: [ create_btn ],
listeners: {
activate: rstore.startUpdate,
hide: rstore.stopUpdate,
destroy: rstore.stopUpdate
}
});
me.callParent();
}
}, function() {
Ext.define('ceph-disk-list', {
extend: 'Ext.data.Model',
fields: [ 'dev', 'used', { name: 'size', type: 'number'},
{name: 'osdid', type: 'number'},
'vendor', 'model', 'serial'],
idProperty: 'dev'
});
});
Ext.define('PVE.form.CephDiskSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveCephDiskSelector'],
diskType: 'journal_disks',
valueField: 'dev',
displayField: 'dev',
listConfig: {
columns: [
{
header: gettext('Device'),
width: 80,
sortable: true,
dataIndex: 'dev'
},
{
header: gettext('Size'),
width: 60,
sortable: false,
renderer: PVE.Utils.format_size,
dataIndex: 'size'
},
{
header: gettext('Serial'),
flex: 1,
sortable: true,
dataIndex: 'serial'
}
]
},
initComponent: function() {
var me = this;
var nodename = me.nodename;
if (!nodename) {
throw "no node name specified";
}
var store = Ext.create('Ext.data.Store', {
filterOnLoad: true,
model: 'ceph-disk-list',
proxy: {
type: 'pve',
url: "/api2/json/nodes/" + nodename + "/ceph/disks",
extraParams: { type: me.diskType }
},
sorters: [
{
property : 'dev',
direction: 'ASC'
}
]
});
Ext.apply(me, {
store: store
});
me.callParent();
store.load();
}
});
Ext.define('PVE.CephCreateMon', {
extend: 'PVE.window.Edit',
alias: ['widget.pveCephCreateMon'],
subject: 'Ceph Monitor',
showProgress: true,
setNode: function(nodename) {
var me = this;
me.nodename = nodename;
me.url = "/nodes/" + nodename + "/ceph/mon";
},
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
me.setNode(me.nodename);
me.create = true;
Ext.applyIf(me, {
method: 'POST',
items: [
{
xtype: 'pveNodeSelector',
submitValue: false,
fieldLabel: gettext('Host'),
selectCurNode: true,
allowBlank: false,
listeners: {
change: function(f, value) {
me.setNode(value);
}
}
}
]
});
me.callParent();
}
});
Ext.define('PVE.node.CephMonList', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveNodeCephMonList'],
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var sm = Ext.create('Ext.selection.RowModel', {});
var rstore = Ext.create('PVE.data.UpdateStore', {
interval: 3000,
storeid: 'ceph-mon-list' + nodename,
model: 'ceph-mon-list',
proxy: {
type: 'pve',
url: "/api2/json/nodes/" + nodename + "/ceph/mon"
}
});
var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
PVE.Utils.monStoreErrors(me, rstore);
var service_cmd = function(cmd) {
var rec = sm.getSelection()[0];
if (!rec.data.host) {
Ext.Msg.alert(gettext('Error'), "entry has no host");
return;
}
PVE.Utils.API2Request({
url: "/nodes/" + rec.data.host + "/ceph/" + cmd,
method: 'POST',
params: { service: "mon." + rec.data.name },
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', { upid: upid });
win.show();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
};
var start_btn = new PVE.button.Button({
text: gettext('Start'),
selModel: sm,
disabled: true,
handler: function(){
service_cmd("start");
}
});
var stop_btn = new PVE.button.Button({
text: gettext('Stop'),
selModel: sm,
disabled: true,
handler: function(){
service_cmd("stop");
}
});
var create_btn = new Ext.Button({
text: gettext('Create'),
handler: function(){
var win = Ext.create('PVE.CephCreateMon', {
nodename: nodename
});
win.show();
}
});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
selModel: sm,
disabled: true,
handler: function() {
var rec = sm.getSelection()[0];
if (!rec.data.host) {
Ext.Msg.alert(gettext('Error'), "entry has no host");
return;
}
PVE.Utils.API2Request({
url: "/nodes/" + rec.data.host + "/ceph/mon/" +
rec.data.name,
method: 'DELETE',
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', { upid: upid });
win.show();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
tbar: [ start_btn, stop_btn, create_btn, remove_btn ],
columns: [
{
header: gettext('Name'),
width: 50,
sortable: true,
renderer: function(v) { return "mon." + v; },
dataIndex: 'name'
},
{
header: gettext('Host'),
width: 100,
sortable: true,
renderer: function(v) {
return v || 'unknown';
},
dataIndex: 'host'
},
{
header: gettext('Quorum'),
width: 70,
sortable: false,
renderer: PVE.Utils.format_boolean,
dataIndex: 'quorum'
},
{
header: gettext('Address'),
flex: 1,
sortable: true,
dataIndex: 'addr'
}
],
listeners: {
activate: rstore.startUpdate,
hide: rstore.stopUpdate,
destroy: rstore.stopUpdate
}
});
me.callParent();
}
}, function() {
Ext.define('ceph-mon-list', {
extend: 'Ext.data.Model',
fields: [ 'addr', 'name', 'rank', 'host', 'quorum' ],
idProperty: 'name'
});
});
Ext.define('PVE.node.CephCrushMap', {
extend: 'Ext.panel.Panel',
alias: ['widget.pveNodeCephCrushMap'],
bodyStyle: 'white-space:pre',
bodyPadding: 5,
scrollable: true,
load: function() {
var me = this;
PVE.Utils.API2Request({
url: me.url,
waitMsgTarget: me,
failure: function(response, opts) {
me.update(gettext('Error') + " " + response.htmlStatus);
},
success: function(response, opts) {
var data = response.result.data;
me.update(Ext.htmlEncode(data));
}
});
},
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
Ext.apply(me, {
url: '/nodes/' + nodename + '/ceph/crush',
listeners: {
activate: function() {
me.load();
}
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.node.CephStatus', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveNodeCephStatus'],
cwidth1: 150,
interval: 3000,
initComponent: function() {
/*jslint confusion: true */
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var renderquorum = function(value) {
if (!value || value.length < 0) {
return 'No';
}
return 'Yes {' + value.join(' ') + '}';
};
var rendermonmap = function(d) {
if (!d) {
return '';
}
var txt = 'e' + d.epoch + ': ' + d.mons.length + " mons at ";
Ext.Array.each(d.mons, function(d) {
txt += d.name + '=' + d.addr + ',';
});
return txt;
};
var renderosdmap = function(value) {
if (!value || !value.osdmap) {
return '';
}
var d = value.osdmap;
var txt = 'e' + d.epoch + ': ';
txt += d.num_osds + ' osds: ' + d.num_up_osds + ' up, ' +
d.num_in_osds + " in";
return txt;
};
var renderhealth = function(value) {
if (!value || !value.overall_status) {
return '';
}
var txt = value.overall_status;
Ext.Array.each(value.summary, function(d) {
txt += " " + d.summary + ';';
});
return txt;
};
var renderpgmap = function(d) {
if (!d) {
return '';
}
var txt = 'v' + d.version + ': ';
txt += d.num_pgs + " pgs:";
Ext.Array.each(d.pgs_by_state, function(s) {
txt += " " + s.count + " " + s.state_name;
});
txt += '; ';
txt += PVE.Utils.format_size(d.data_bytes) + " data, ";
txt += PVE.Utils.format_size(d.bytes_used) + " used, ";
txt += PVE.Utils.format_size(d.bytes_avail) + " avail";
return txt;
};
Ext.applyIf(me, {
url: "/api2/json/nodes/" + nodename + "/ceph/status",
rows: {
health: {
header: 'health',
renderer: renderhealth,
required: true
},
quorum_names: {
header: 'quorum',
renderer: renderquorum,
required: true
},
fsid: {
header: 'cluster',
required: true
},
monmap: {
header: 'monmap',
renderer: rendermonmap,
required: true
},
osdmap: {
header: 'osdmap',
renderer: renderosdmap,
required: true
},
pgmap: {
header: 'pgmap',
renderer: renderpgmap,
required: true
}
}
});
me.callParent();
me.on('activate', me.rstore.startUpdate);
me.on('hide', me.rstore.stopUpdate);
me.on('destroy', me.rstore.stopUpdate);
}
});
Ext.define('PVE.node.CephConfig', {
extend: 'Ext.panel.Panel',
alias: ['widget.pveNodeCephConfig'],
bodyStyle: 'white-space:pre',
bodyPadding: 5,
scrollable: true,
load: function() {
var me = this;
PVE.Utils.API2Request({
url: me.url,
waitMsgTarget: me,
failure: function(response, opts) {
me.update(gettext('Error') + " " + response.htmlStatus);
},
success: function(response, opts) {
var data = response.result.data;
me.update(Ext.htmlEncode(data));
}
});
},
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
Ext.apply(me, {
url: '/nodes/' + nodename + '/ceph/config',
listeners: {
activate: function() {
me.load();
}
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.node.Ceph', {
extend: 'PVE.panel.SubConfig',
alias: ['widget.pveNodeCeph'],
minTabWidth: 80,
configPrefix: 'ceph',
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
Ext.apply(me, {
defaults: {
border: false,
pveSelNode: me.pveSelNode
},
plugins: {
ptype: 'lazyitems',
items: [
{
xtype: 'pveNodeCephStatus',
title: gettext('Status'),
itemId: 'status'
},
{
xtype: 'pveNodeCephConfig',
title: gettext('Config'),
itemId: 'config'
},
{
xtype: 'pveNodeCephMonList',
title: gettext('Monitor'),
itemId: 'monlist'
},
{
xtype: 'pveNodeCephDiskList',
title: gettext('Disks'),
itemId: 'disklist'
},
{
xtype: 'pveNodeCephOsdTree',
title: 'OSD',
itemId: 'osdtree'
},
{
xtype: 'pveNodeCephPoolList',
title: gettext('Pools'),
itemId: 'pools'
},
{
title: 'Crush',
xtype: 'pveNodeCephCrushMap',
itemId: 'crushmap'
},
{
title: gettext('Log'),
itemId: 'log',
xtype: 'pveLogView',
url: "/api2/extjs/nodes/" + nodename + "/ceph/log"
}
]
}
});
me.callParent();
}
});
Ext.define('PVE.node.DNSEdit', {
extend: 'PVE.window.Edit',
alias: ['widget.pveNodeDNSEdit'],
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
me.items = [
{
xtype: 'textfield',
fieldLabel: gettext('Search domain'),
name: 'search',
allowBlank: false
},
{
xtype: 'pvetextfield',
fieldLabel: gettext('DNS server') + " 1",
vtype: 'IP64Address',
skipEmptyText: true,
name: 'dns1'
},
{
xtype: 'pvetextfield',
fieldLabel: gettext('DNS server') + " 2",
vtype: 'IP64Address',
skipEmptyText: true,
name: 'dns2'
},
{
xtype: 'pvetextfield',
fieldLabel: gettext('DNS server') + " 3",
vtype: 'IP64Address',
skipEmptyText: true,
name: 'dns3'
}
];
Ext.applyIf(me, {
subject: gettext('DNS'),
url: "/api2/extjs/nodes/" + nodename + "/dns",
fieldDefaults: {
labelWidth: 120
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.node.DNSView', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveNodeDNSView'],
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var run_editor = function() {
var win = Ext.create('PVE.node.DNSEdit', {
pveSelNode: me.pveSelNode
});
win.show();
};
Ext.apply(me, {
url: "/api2/json/nodes/" + nodename + "/dns",
cwidth1: 130,
interval: 1000,
rows: {
search: { header: 'Search domain', required: true },
dns1: { header: gettext('DNS server') + " 1", required: true },
dns2: { header: gettext('DNS server') + " 2" },
dns3: { header: gettext('DNS server') + " 3" }
},
tbar: [
{
text: gettext("Edit"),
handler: run_editor
}
],
listeners: {
itemdblclick: run_editor
}
});
me.callParent();
me.on('activate', me.rstore.startUpdate);
me.on('hide', me.rstore.stopUpdate);
me.on('destroy', me.rstore.stopUpdate);
}
});
Ext.define('PVE.node.TimeView', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveNodeTimeView'],
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var tzoffset = (new Date()).getTimezoneOffset()*60000;
var renderlocaltime = function(value) {
var servertime = new Date((value * 1000) + tzoffset);
return Ext.Date.format(servertime, 'Y-m-d H:i:s');
};
var run_editor = function() {
var win = Ext.create('PVE.node.TimeEdit', {
pveSelNode: me.pveSelNode
});
win.show();
};
Ext.apply(me, {
url: "/api2/json/nodes/" + nodename + "/time",
cwidth1: 150,
interval: 1000,
rows: {
timezone: {
header: gettext('Time zone'),
required: true
},
localtime: {
header: gettext('Server time'),
required: true,
renderer: renderlocaltime
}
},
tbar: [
{
text: gettext("Edit"),
handler: run_editor
}
],
listeners: {
itemdblclick: run_editor
}
});
me.callParent();
me.on('activate', me.rstore.startUpdate);
me.on('hide', me.rstore.stopUpdate);
me.on('destroy', me.rstore.stopUpdate);
}
});
Ext.define('PVE.node.TimeEdit', {
extend: 'PVE.window.Edit',
alias: ['widget.pveNodeTimeEdit'],
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
Ext.applyIf(me, {
subject: gettext('Time zone'),
url: "/api2/extjs/nodes/" + nodename + "/time",
fieldDefaults: {
labelWidth: 70
},
width: 400,
items: {
xtype: 'combo',
fieldLabel: gettext('Time zone'),
name: 'timezone',
queryMode: 'local',
store: Ext.create('PVE.data.TimezoneStore'),
valueField: 'zone',
displayField: 'zone',
triggerAction: 'all',
forceSelection: true,
editable: false,
allowBlank: false
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.node.StatusView', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveNodeStatusView'],
disabled: true,
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var socketText = gettext('Socket');
var socketsText = gettext('Sockets');
var render_cpuinfo = function(value) {
return value.cpus + " x " + value.model + " (" +
value.sockets.toString() + " " +
(value.sockets > 1 ? socketsText : socketText) + ")";
};
var render_loadavg = function(value) {
return value[0] + ", " + value[1] + ", " + value[2];
};
var render_cpu = function(value) {
var per = value * 100;
return per.toFixed(2) + "%";
};
var render_ksm = function(value) {
return PVE.Utils.format_size(value.shared);
};
var render_meminfo = function(value) {
var per = (value.used / value.total)*100;
var text = "<div>" + PVE.Utils.totalText + ": " + PVE.Utils.format_size(value.total) + "</div>" +
"<div>" + PVE.Utils.usedText + ": " + PVE.Utils.format_size(value.used) + "</div>";
return text;
};
var rows = {
uptime: { header: gettext('Uptime'), required: true, renderer: PVE.Utils.format_duration_long },
loadavg: { header: gettext('Load average'), required: true, renderer: render_loadavg },
cpuinfo: { header: gettext('CPUs'), required: true, renderer: render_cpuinfo },
cpu: { header: gettext('CPU usage'),required: true, renderer: render_cpu },
wait: { header: gettext('IO delay'), required: true, renderer: render_cpu },
memory: { header: gettext('RAM usage'), required: true, renderer: render_meminfo },
swap: { header: gettext('SWAP usage'), required: true, renderer: render_meminfo },
ksm: { header: gettext('KSM sharing'), required: true, renderer: render_ksm },
rootfs: { header: gettext('HD space') + ' (root)', required: true, renderer: render_meminfo },
pveversion: { header: gettext('PVE Manager version'), required: true },
kversion: { header: gettext('Kernel version'), required: true }
};
Ext.applyIf(me, {
cwidth1: 150,
//height: 276,
rows: rows
});
me.callParent();
}
});
Ext.define('PVE.node.Summary', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveNodeSummary',
scrollable: true,
bodyStyle: 'padding:10px',
showVersions: function() {
var me = this;
// Note: we use simply text/html here, because ExtJS grid has problems
// with cut&paste
var nodename = me.pveSelNode.data.node;
var view = Ext.createWidget('component', {
autoScroll: true,
style: {
'background-color': 'white',
'white-space': 'pre',
'font-family': 'monospace',
padding: '5px'
}
});
var win = Ext.create('Ext.window.Window', {
title: gettext('Package versions'),
width: 600,
height: 400,
layout: 'fit',
modal: true,
items: [ view ]
});
PVE.Utils.API2Request({
waitMsgTarget: me,
url: "/nodes/" + nodename + "/apt/versions",
method: 'GET',
failure: function(response, opts) {
win.close();
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, opts) {
win.show();
var text = '';
Ext.Array.each(response.result.data, function(rec) {
var version = "not correctly installed";
var pkg = rec.Package;
if (rec.OldVersion && rec.CurrentState === 'Installed') {
version = rec.OldVersion;
}
if (rec.RunningKernel) {
text += pkg + ': ' + version + ' (running kernel: ' +
rec.RunningKernel + ')\n';
} else if (rec.ManagerVersion) {
text += pkg + ': ' + version + ' (running version: ' +
rec.ManagerVersion + ')\n';
} else {
text += pkg + ': ' + version + '\n';
}
});
view.update(Ext.htmlEncode(text));
}
});
},
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
if (!me.statusStore) {
throw "no status storage specified";
}
var rstore = me.statusStore;
var statusview = Ext.create('PVE.node.StatusView', {
title: gettext('Status'),
pveSelNode: me.pveSelNode,
width: 800,
rstore: rstore
});
var version_btn = new Ext.Button({
text: gettext('Package versions'),
handler: function(){
PVE.Utils.checked_command(function() { me.showVersions(); });
}
});
var rrdstore = Ext.create('PVE.data.RRDStore', {
rrdurl: "/api2/json/nodes/" + nodename + "/rrddata"
});
Ext.apply(me, {
tbar: [version_btn, '->', { xtype: 'pveRRDTypeSelector' } ],
plugins: {
ptype: 'lazyitems',
items: [
{
xtype: 'container',
layout: 'column',
defaults: {
padding: '0 10 10 0'
},
items: [
statusview,
{
xtype: 'pveRRDChart',
title: gettext('CPU usage'),
fields: ['cpu','iowait'],
fieldTitles: [gettext('CPU usage'), gettext('IO delay')],
store: rrdstore
},
{
xtype: 'pveRRDChart',
title: gettext('Server load'),
fields: ['loadavg'],
fieldTitles: [gettext('Load average')],
store: rrdstore
},
{
xtype: 'pveRRDChart',
title: gettext('Memory usage'),
fields: ['memtotal','memused'],
fieldTitles: [gettext('Total'), gettext('RAM usage')],
store: rrdstore
},
{
xtype: 'pveRRDChart',
title: gettext('Network traffic'),
fields: ['netin','netout'],
store: rrdstore
}
]
}
]
},
listeners: {
activate: function() { rstore.startUpdate(); rrdstore.startUpdate(); },
hide: function() { rstore.stopUpdate(); rrdstore.stopUpdate(); },
destroy: function() { rstore.stopUpdate(); rrdstore.stopUpdate(); }
}
});
me.callParent();
}
});
Ext.define('PVE.node.ServiceView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveNodeServiceView'],
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var rstore = Ext.create('PVE.data.UpdateStore', {
interval: 1000,
storeid: 'pve-services' + nodename,
model: 'pve-services',
proxy: {
type: 'pve',
url: "/api2/json/nodes/" + nodename + "/services"
}
});
var store = Ext.create('PVE.data.DiffStore', {
rstore: rstore,
sortAfterUpdate: true,
sorters: [
{
property : 'name',
direction: 'ASC'
}
]
});
var service_cmd = function(cmd) {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
PVE.Utils.API2Request({
url: "/nodes/" + nodename + "/services/" + rec.data.service + "/" + cmd,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
me.loading = true;
},
success: function(response, opts) {
rstore.startUpdate();
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', {
upid: upid
});
win.show();
}
});
};
var start_btn = new Ext.Button({
text: gettext('Start'),
disabled: true,
handler: function(){
service_cmd("start");
}
});
var stop_btn = new Ext.Button({
text: gettext('Stop'),
disabled: true,
handler: function(){
service_cmd("stop");
}
});
var restart_btn = new Ext.Button({
text: gettext('Restart'),
disabled: true,
handler: function(){
service_cmd("restart");
}
});
var set_button_status = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
start_btn.disable();
stop_btn.disable();
restart_btn.disable();
return;
}
var service = rec.data.service;
var state = rec.data.state;
if (service == 'pveproxy' ||
service == 'pvecluster' ||
service == 'pvedaemon') {
if (state == 'running') {
start_btn.disable();
restart_btn.enable();
} else {
start_btn.enable();
restart_btn.disable();
}
stop_btn.disable();
} else {
if (state == 'running') {
start_btn.disable();
restart_btn.enable();
stop_btn.enable();
} else {
start_btn.enable();
restart_btn.disable();
stop_btn.disable();
}
}
};
me.mon(store, 'refresh', set_button_status);
PVE.Utils.monStoreErrors(me, rstore);
Ext.apply(me, {
store: store,
stateful: false,
tbar: [ start_btn, stop_btn, restart_btn ],
columns: [
{
header: gettext('Name'),
width: 100,
sortable: true,
dataIndex: 'name'
},
{
header: gettext('Status'),
width: 100,
sortable: true,
dataIndex: 'state'
},
{
header: gettext('Description'),
renderer: Ext.String.htmlEncode,
dataIndex: 'desc',
flex: 1
}
],
listeners: {
selectionchange: set_button_status,
activate: rstore.startUpdate,
hide: rstore.stopUpdate,
destroy: rstore.stopUpdate
}
});
me.callParent();
}
}, function() {
Ext.define('pve-services', {
extend: 'Ext.data.Model',
fields: [ 'service', 'name', 'desc', 'state' ],
idProperty: 'service'
});
});
Ext.define('PVE.node.NetworkEdit', {
extend: 'PVE.window.Edit',
alias: ['widget.pveNodeNetworkEdit'],
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
if (!me.iftype) {
throw "no network device type specified";
}
me.create = !me.iface;
var iface_vtype;
if (me.iftype === 'bridge') {
iface_vtype = 'BridgeName';
} else if (me.iftype === 'bond') {
iface_vtype = 'BondName';
} else if (me.iftype === 'eth' && !me.create) {
iface_vtype = 'InterfaceName';
} else if (me.iftype === 'vlan' && !me.create) {
iface_vtype = 'InterfaceName';
} else if (me.iftype === 'OVSBridge') {
iface_vtype = 'BridgeName';
} else if (me.iftype === 'OVSBond') {
iface_vtype = 'BondName';
} else if (me.iftype === 'OVSIntPort') {
iface_vtype = 'InterfaceName';
} else if (me.iftype === 'OVSPort') {
iface_vtype = 'InterfaceName';
} else {
console.log(me.iftype);
throw "unknown network device type specified";
}
me.subject = PVE.Utils.render_network_iface_type(me.iftype);
var column2 = [];
if (!(me.iftype === 'OVSIntPort' || me.iftype === 'OVSPort' ||
me.iftype === 'OVSBond')) {
column2.push({
xtype: 'pvecheckbox',
fieldLabel: gettext('Autostart'),
name: 'autostart',
uncheckedValue: 0,
checked: me.create ? true : undefined
});
}
if (me.iftype === 'bridge') {
column2.push({
xtype: 'pvecheckbox',
fieldLabel: gettext('VLAN aware'),
name: 'bridge_vlan_aware',
deleteEmpty: !me.create
});
column2.push({
xtype: 'textfield',
fieldLabel: gettext('Bridge ports'),
name: 'bridge_ports'
});
} else if (me.iftype === 'OVSBridge') {
column2.push({
xtype: 'textfield',
fieldLabel: gettext('Bridge ports'),
name: 'ovs_ports'
});
column2.push({
xtype: 'textfield',
fieldLabel: gettext('OVS options'),
name: 'ovs_options'
});
} else if (me.iftype === 'OVSPort' || me.iftype === 'OVSIntPort') {
column2.push({
xtype: me.create ? 'PVE.form.BridgeSelector' : 'displayfield',
fieldLabel: PVE.Utils.render_network_iface_type('OVSBridge'),
allowBlank: false,
nodename: nodename,
bridgeType: 'OVSBridge',
name: 'ovs_bridge'
});
column2.push({
xtype: 'pveVlanField',
deleteEmpty: !me.create,
name: 'ovs_tag',
value: ''
});
column2.push({
xtype: 'textfield',
fieldLabel: gettext('OVS options'),
name: 'ovs_options'
});
} else if (me.iftype === 'bond') {
column2.push({
xtype: 'textfield',
fieldLabel: gettext('Slaves'),
name: 'slaves'
});
var policySelector = Ext.createWidget('bondPolicySelector', {
fieldLabel: gettext('Hash policy'),
name: 'bond_xmit_hash_policy',
deleteEmpty: !me.create,
disabled: true
});
column2.push({
xtype: 'bondModeSelector',
fieldLabel: gettext('Mode'),
name: 'bond_mode',
value: me.create ? 'balance-rr' : undefined,
listeners: {
change: function(f, value) {
if (value === 'balance-xor' ||
value === '802.3ad') {
policySelector.setDisabled(false);
} else {
policySelector.setDisabled(true);
policySelector.setValue('');
}
}
},
allowBlank: false
});
column2.push(policySelector);
} else if (me.iftype === 'OVSBond') {
column2.push({
xtype: me.create ? 'PVE.form.BridgeSelector' : 'displayfield',
fieldLabel: PVE.Utils.render_network_iface_type('OVSBridge'),
allowBlank: false,
nodename: nodename,
bridgeType: 'OVSBridge',
name: 'ovs_bridge'
});
column2.push({
xtype: 'pveVlanField',
deleteEmpty: !me.create,
name: 'ovs_tag',
value: ''
});
column2.push({
xtype: 'textfield',
fieldLabel: gettext('OVS options'),
name: 'ovs_options'
});
}
var url;
var method;
if (me.create) {
url = "/api2/extjs/nodes/" + nodename + "/network";
method = 'POST';
} else {
url = "/api2/extjs/nodes/" + nodename + "/network/" + me.iface;
method = 'PUT';
}
var column1 = [
{
xtype: 'hiddenfield',
name: 'type',
value: me.iftype
},
{
xtype: me.create ? 'textfield' : 'displayfield',
fieldLabel: gettext('Name'),
name: 'iface',
value: me.iface,
vtype: iface_vtype,
allowBlank: false
}
];
if (me.iftype === 'OVSBond') {
column1.push(
{
xtype: 'bondModeSelector',
fieldLabel: gettext('Mode'),
name: 'bond_mode',
openvswitch: true,
value: me.create ? 'active-backup' : undefined,
allowBlank: false
},
{
xtype: 'textfield',
fieldLabel: gettext('Slaves'),
name: 'ovs_bonds'
}
);
} else {
column1.push(
{
xtype: 'pvetextfield',
deleteEmpty: !me.create,
fieldLabel: gettext('IP address'),
vtype: 'IPAddress',
name: 'address'
},
{
xtype: 'pvetextfield',
deleteEmpty: !me.create,
fieldLabel: gettext('Subnet mask'),
vtype: 'IPAddress',
name: 'netmask',
validator: function(value) {
/*jslint confusion: true */
if (!me.items) {
return true;
}
var address = me.down('field[name=address]').getValue();
if (value !== '') {
if (address === '') {
return "Subnet mask requires option 'IP address'";
}
} else {
if (address !== '') {
return "Option 'IP address' requires a subnet mask";
}
}
return true;
}
},
{
xtype: 'pvetextfield',
deleteEmpty: !me.create,
fieldLabel: gettext('Gateway'),
vtype: 'IPAddress',
name: 'gateway'
},
{
xtype: 'pvetextfield',
deleteEmpty: !me.create,
fieldLabel: gettext('IPv6 address'),
vtype: 'IP6Address',
name: 'address6'
},
{
xtype: 'pvetextfield',
deleteEmpty: !me.create,
fieldLabel: gettext('Prefix length'),
vtype: 'IP6PrefixLength',
name: 'netmask6',
value: '',
allowBlank: true,
validator: function(value) {
/*jslint confusion: true */
if (!me.items) {
return true;
}
var address = me.down('field[name=address6]').getValue();
if (value !== '') {
if (address === '') {
return "IPv6 prefix length requires option 'IPv6 address'";
}
} else {
if (address !== '') {
return "Option 'IPv6 address' requires an IPv6 prefix length";
}
}
return true;
}
},
{
xtype: 'pvetextfield',
deleteEmpty: !me.create,
fieldLabel: gettext('Gateway'),
vtype: 'IP6Address',
name: 'gateway6'
}
);
}
Ext.applyIf(me, {
url: url,
method: method,
items: {
xtype: 'inputpanel',
column1: column1,
column2: column2
}
});
me.callParent();
if (me.create) {
me.down('field[name=iface]').setValue(me.iface_default);
} else {
me.load({
success: function(response, options) {
var data = response.result.data;
if (data.type !== me.iftype) {
var msg = "Got unexpected device type";
Ext.Msg.alert(gettext('Error'), msg, function() {
me.close();
});
return;
}
me.setValues(data);
me.isValid(); // trigger validation
}
});
}
}
});
Ext.define('PVE.node.NetworkView', {
extend: 'Ext.panel.Panel',
alias: ['widget.pveNodeNetworkView'],
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var store = Ext.create('Ext.data.Store', {
model: 'pve-networks',
proxy: {
type: 'pve',
url: "/api2/json/nodes/" + nodename + "/network"
},
sorters: [
{
property : 'iface',
direction: 'ASC'
}
]
});
var reload = function() {
var changeitem = me.down('#changes');
PVE.Utils.API2Request({
url: '/nodes/' + nodename + '/network',
failure: function(response, opts) {
changeitem.update(gettext('Error') + ': ' + response.htmlStatus);
store.loadData({});
},
success: function(response, opts) {
var result = Ext.decode(response.responseText);
store.loadData(result.data);
var changes = result.changes;
if (changes === undefined || changes === '') {
changes = gettext("No changes");
}
changeitem.update("<pre>" + Ext.htmlEncode(changes) + "</pre>");
}
});
};
var run_editor = function() {
var grid = me.down('gridpanel');
var sm = grid.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iface: rec.data.iface,
iftype: rec.data.type
});
win.show();
win.on('destroy', reload);
};
var edit_btn = new Ext.Button({
text: gettext('Edit'),
disabled: true,
handler: run_editor
});
var del_btn = new Ext.Button({
text: gettext('Remove'),
disabled: true,
handler: function(){
var grid = me.down('gridpanel');
var sm = grid.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var iface = rec.data.iface;
PVE.Utils.API2Request({
url: '/nodes/' + nodename + '/network/' + iface,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
var set_button_status = function() {
var grid = me.down('gridpanel');
var sm = grid.getSelectionModel();
var rec = sm.getSelection()[0];
edit_btn.setDisabled(!rec);
del_btn.setDisabled(!rec);
};
PVE.Utils.monStoreErrors(me, store);
var render_ports = function(value, metaData, record) {
if (value === 'bridge') {
return record.data.bridge_ports;
} else if (value === 'bond') {
return record.data.slaves;
} else if (value === 'OVSBridge') {
return record.data.ovs_ports;
} else if (value === 'OVSBond') {
return record.data.ovs_bonds;
}
};
var find_next_iface_id = function(prefix) {
var next;
for (next = 0; next <= 9999; next++) {
if (!store.getById(prefix + next.toString())) {
break;
}
}
return prefix + next.toString();
};
Ext.apply(me, {
layout: 'border',
tbar: [
{
text: gettext('Create'),
menu: new Ext.menu.Menu({
plain: true,
items: [
{
text: PVE.Utils.render_network_iface_type('bridge'),
handler: function() {
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'bridge',
iface_default: find_next_iface_id('vmbr')
});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.render_network_iface_type('bond'),
handler: function() {
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'bond',
iface_default: find_next_iface_id('bond')
});
win.on('destroy', reload);
win.show();
}
}, '-',
{
text: PVE.Utils.render_network_iface_type('OVSBridge'),
handler: function() {
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'OVSBridge',
iface_default: find_next_iface_id('vmbr')
});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.render_network_iface_type('OVSBond'),
handler: function() {
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'OVSBond',
iface_default: find_next_iface_id('bond')
});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.render_network_iface_type('OVSIntPort'),
handler: function() {
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'OVSIntPort'
});
win.on('destroy', reload);
win.show();
}
}
]
})
}, ' ',
{
text: gettext('Revert'),
handler: function() {
PVE.Utils.API2Request({
url: '/nodes/' + nodename + '/network',
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
},
edit_btn,
del_btn
],
items: [
{
xtype: 'gridpanel',
stateful: false,
store: store,
region: 'center',
border: false,
columns: [
{
header: gettext('Name'),
width: 100,
sortable: true,
dataIndex: 'iface'
},
{
header: gettext('Type'),
width: 100,
sortable: true,
renderer: PVE.Utils.render_network_iface_type,
dataIndex: 'type'
},
{
xtype: 'booleancolumn',
header: gettext('Active'),
width: 80,
sortable: true,
dataIndex: 'active',
trueText: 'Yes',
falseText: 'No',
undefinedText: 'No'
},
{
xtype: 'booleancolumn',
header: gettext('Autostart'),
width: 80,
sortable: true,
dataIndex: 'autostart',
trueText: 'Yes',
falseText: 'No',
undefinedText: 'No'
},
{
header: gettext('Ports/Slaves'),
dataIndex: 'type',
renderer: render_ports
},
{
header: gettext('IP address'),
sortable: true,
dataIndex: 'address',
renderer: function(value, metaData, rec) {
if (rec.data.address && rec.data.address6) {
return rec.data.address + "<br>"
+ rec.data.address6 + '/' + rec.data.netmask6;
} else if (rec.data.address6) {
return rec.data.address6 + '/' + rec.data.netmask6;
} else {
return rec.data.address;
}
}
},
{
header: gettext('Subnet mask'),
sortable: true,
dataIndex: 'netmask'
},
{
header: gettext('Gateway'),
sortable: true,
dataIndex: 'gateway',
renderer: function(value, metaData, rec) {
if (rec.data.gateway && rec.data.gateway6) {
return rec.data.gateway + "<br>" + rec.data.gateway6;
} else if (rec.data.gateway6) {
return rec.data.gateway6;
} else {
return rec.data.gateway;
}
}
}
],
listeners: {
selectionchange: set_button_status,
itemdblclick: run_editor
}
},
{
border: false,
region: 'south',
autoScroll: true,
itemId: 'changes',
tbar: [
gettext('Pending changes') + ' (' +
gettext('Please reboot to activate changes') + ')'
],
split: true,
bodyPadding: 5,
flex: 0.6,
html: gettext("No changes")
}
],
listeners: {
activate: reload
}
});
me.callParent();
}
}, function() {
Ext.define('pve-networks', {
extend: 'Ext.data.Model',
fields: [
'iface', 'type', 'active', 'autostart',
'bridge_ports', 'slaves',
'address', 'netmask', 'gateway',
'address6', 'netmask6', 'gateway6'
],
idProperty: 'iface'
});
});
Ext.define('PVE.node.Tasks', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveNodeTasks'],
stateful: false,
loadMask: true,
sortableColumns: false,
vmidFilter: 0,
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var store = Ext.create('Ext.data.BufferedStore', {
pageSize: 500,
autoLoad: true,
remoteFilter: true,
model: 'pve-tasks',
proxy: {
type: 'pve',
startParam: 'start',
limitParam: 'limit',
url: "/api2/json/nodes/" + nodename + "/tasks"
}
});
var userfilter = '';
var filter_errors = 0;
var updateProxyParams = function() {
var params = {
errors: filter_errors
};
if (userfilter) {
params.userfilter = userfilter;
}
if (me.vmidFilter) {
params.vmid = me.vmidFilter;
}
store.proxy.extraParams = params;
};
updateProxyParams();
var reload_task = Ext.create('Ext.util.DelayedTask',function() {
updateProxyParams();
store.reload();
});
var run_task_viewer = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.window.TaskViewer', {
upid: rec.data.upid
});
win.show();
};
var view_btn = new Ext.Button({
text: gettext('View'),
disabled: true,
handler: run_task_viewer
});
Ext.apply(me, {
store: store,
viewConfig: {
trackOver: false,
stripeRows: false, // does not work with getRowClass()
getRowClass: function(record, index) {
var status = record.get('status');
if (status && status != 'OK') {
return "x-form-invalid-field";
}
}
},
tbar: [
view_btn, '->', gettext('User name') +':', ' ',
{
xtype: 'textfield',
width: 200,
value: userfilter,
enableKeyEvents: true,
listeners: {
keyup: function(field, e) {
userfilter = field.getValue();
reload_task.delay(500);
}
}
}, ' ', gettext('Only Errors') + ':', ' ',
{
xtype: 'checkbox',
hideLabel: true,
checked: filter_errors,
listeners: {
change: function(field, checked) {
filter_errors = checked ? 1 : 0;
reload_task.delay(10);
}
}
}, ' '
],
columns: [
{
header: gettext("Start Time"),
dataIndex: 'starttime',
width: 100,
renderer: function(value) {
return Ext.Date.format(value, "M d H:i:s");
}
},
{
header: gettext("End Time"),
dataIndex: 'endtime',
width: 100,
renderer: function(value, metaData, record) {
return Ext.Date.format(value,"M d H:i:s");
}
},
{
header: gettext("Node"),
dataIndex: 'node',
width: 100
},
{
header: gettext("User name"),
dataIndex: 'user',
width: 150
},
{
header: gettext("Description"),
dataIndex: 'upid',
flex: 1,
renderer: PVE.Utils.render_upid
},
{
header: gettext("Status"),
dataIndex: 'status',
width: 200,
renderer: function(value, metaData, record) {
if (value == 'OK') {
return 'OK';
}
// metaData.attr = 'style="color:red;"';
return "ERROR: " + value;
}
}
],
listeners: {
itemdblclick: run_task_viewer,
selectionchange: function(v, selections) {
view_btn.setDisabled(!(selections && selections[0]));
},
show: function() { reload_task.delay(10); },
destroy: function() { reload_task.cancel(); }
}
});
me.callParent();
}
});
/*global Blob*/
Ext.define('PVE.node.SubscriptionKeyEdit', {
extend: 'PVE.window.Edit',
title: gettext('Upload Subscription Key'),
width: 300,
items: {
xtype: 'textfield',
name: 'key',
value: '',
fieldLabel: gettext('Subscription Key')
},
initComponent : function() {
var me = this;
me.callParent();
me.load();
}
});
Ext.define('PVE.node.Subscription', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveNodeSubscription'],
features: [ {ftype: 'selectable'}],
showReport: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
var getReportFileName = function() {
var now = Ext.Date.format(new Date(), 'D-d-F-Y-G-i');
return me.nodename + '-report-' + now + '.txt';
};
var view = Ext.createWidget('component', {
itemId: 'system-report-view',
scrollable: true,
style: {
'background-color': 'white',
'white-space': 'pre',
'font-family': 'monospace',
padding: '5px'
}
});
var reportWindow = Ext.create('Ext.window.Window', {
title: gettext('System Report'),
width: 1024,
height: 600,
layout: 'fit',
modal: true,
buttons: [
'->',
{
text: gettext('Download'),
handler: function() {
var fileContent = reportWindow.getComponent('system-report-view').html;
var fileName = getReportFileName();
// Internet Explorer
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(new Blob([fileContent]), fileName);
} else {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,'
+ encodeURIComponent(fileContent));
element.setAttribute('download', fileName);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
}
],
items: view
});
PVE.Utils.API2Request({
url: '/api2/extjs/nodes/' + me.nodename + '/report',
method: 'GET',
waitMsgTarget: me,
failure: function(response) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response) {
var report = Ext.htmlEncode(response.result.data);
reportWindow.show();
view.update(report);
}
});
},
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
var reload = function() {
me.rstore.load();
};
var baseurl = '/nodes/' + me.nodename + '/subscription';
var render_status = function(value) {
var message = me.getObjectValue('message');
if (message) {
return value + ": " + message;
}
return value;
};
var rows = {
productname: {
header: gettext('Type')
},
key: {
header: gettext('Subscription Key')
},
status: {
header: gettext('Status'),
renderer: render_status
},
message: {
visible: false
},
serverid: {
header: gettext('Server ID')
},
sockets: {
header: gettext('Sockets')
},
checktime: {
header: gettext('Last checked'),
renderer: PVE.Utils.render_timestamp
},
nextduedate: {
header: gettext('Next due date')
}
};
Ext.apply(me, {
url: '/api2/json' + baseurl,
cwidth1: 170,
tbar: [
{
text: gettext('Upload Subscription Key'),
handler: function() {
var win = Ext.create('PVE.node.SubscriptionKeyEdit', {
url: '/api2/extjs/' + baseurl
});
win.show();
win.on('destroy', reload);
}
},
{
text: gettext('Check'),
handler: function() {
PVE.Utils.API2Request({
params: { force: 1 },
url: baseurl,
method: 'POST',
waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
callback: reload
});
}
},
{
text: gettext('System Report'),
handler: function() {
PVE.Utils.checked_command(function (){ me.showReport(); });
}
}
],
rows: rows,
listeners: {
activate: reload
}
});
me.callParent();
}
});
Ext.define('PVE.node.APT', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveNodeAPT'],
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var store = Ext.create('Ext.data.Store', {
model: 'apt-pkglist',
groupField: 'Origin',
proxy: {
type: 'pve',
url: "/api2/json/nodes/" + nodename + "/apt/update"
},
sorters: [
{
property : 'Package',
direction: 'ASC'
}
]
});
var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
groupHeaderTpl: '{[ "Origin: " + values.name ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})',
enableGroupingMenu: false
});
var rowBodyFeature = Ext.create('Ext.grid.feature.RowBody', {
getAdditionalData: function (data, rowIndex, record, orig) {
var headerCt = this.view.headerCt;
var colspan = headerCt.getColumnCount();
// Usually you would style the my-body-class in CSS file
return {
rowBody: '<div style="padding: 1em">' + Ext.String.htmlEncode(data.Description) + '</div>',
rowBodyColspan: colspan
};
}
});
var reload = function() {
store.load();
};
me.loadCount = 1; // avoid duplicate load mask
PVE.Utils.monStoreErrors(me, store);
var apt_command = function(cmd){
PVE.Utils.API2Request({
url: "/nodes/" + nodename + "/apt/" + cmd,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, opts) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskViewer', {
upid: upid
});
win.show();
me.mon(win, 'close', reload);
}
});
};
var sm = Ext.create('Ext.selection.RowModel', {});
var update_btn = new Ext.Button({
text: gettext('Refresh'),
handler: function(){
PVE.Utils.checked_command(function() { apt_command('update'); });
}
});
var upgrade_btn = Ext.create('PVE.button.ConsoleButton', {
disabled: !(PVE.UserName && PVE.UserName === 'root@pam'),
text: gettext('Upgrade'),
consoleType: 'upgrade',
nodename: nodename
});
var show_changelog = function(rec) {
if (!rec || !rec.data || !(rec.data.ChangeLogUrl && rec.data.Package)) {
return;
}
var view = Ext.createWidget('component', {
autoScroll: true,
style: {
'background-color': 'white',
'white-space': 'pre',
'font-family': 'monospace',
padding: '5px'
}
});
var win = Ext.create('Ext.window.Window', {
title: gettext('Changelog') + ": " + rec.data.Package,
width: 800,
height: 400,
layout: 'fit',
modal: true,
items: [ view ]
});
PVE.Utils.API2Request({
waitMsgTarget: me,
url: "/nodes/" + nodename + "/apt/changelog",
params: {
name: rec.data.Package,
version: rec.data.Version
},
method: 'GET',
failure: function(response, opts) {
win.close();
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, opts) {
win.show();
view.update(Ext.htmlEncode(response.result.data));
}
});
};
var changelog_btn = new PVE.button.Button({
text: gettext('Changelog'),
selModel: sm,
disabled: true,
enableFn: function(rec) {
if (!rec || !rec.data || !(rec.data.ChangeLogUrl && rec.data.Package)) {
return false;
}
return true;
},
handler: function(b, e, rec) {
show_changelog(rec);
}
});
Ext.apply(me, {
store: store,
stateful: false,
selModel: sm,
viewConfig: {
stripeRows: false,
emptyText: '<div style="display:table; width:100%; height:100%;"><div style="display:table-cell; vertical-align: middle; text-align:center;"><b>' + gettext('No updates available.') + '</div></div>'
},
tbar: [ update_btn, upgrade_btn, changelog_btn ],
features: [ groupingFeature, rowBodyFeature ],
columns: [
{
header: gettext('Package'),
width: 200,
sortable: true,
dataIndex: 'Package'
},
{
text: gettext('Version'),
columns: [
{
header: gettext('current'),
width: 100,
sortable: false,
dataIndex: 'OldVersion'
},
{
header: gettext('new'),
width: 100,
sortable: false,
dataIndex: 'Version'
}
]
},
{
header: gettext('Description'),
sortable: false,
dataIndex: 'Title',
flex: 1
}
],
listeners: {
activate: reload,
itemdblclick: function(v, rec) {
show_changelog(rec);
}
}
});
me.callParent();
}
}, function() {
Ext.define('apt-pkglist', {
extend: 'Ext.data.Model',
fields: [ 'Package', 'Title', 'Description', 'Section', 'Arch',
'Priority', 'Version', 'OldVersion', 'ChangeLogUrl', 'Origin' ],
idProperty: 'Package'
});
});
Ext.define('PVE.node.Config', {
extend: 'PVE.panel.Config',
alias: 'widget.PVE.node.Config',
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var caps = Ext.state.Manager.get('GuiCap');
me.statusStore = Ext.create('PVE.data.ObjectStore', {
url: "/api2/json/nodes/" + nodename + "/status",
interval: 1000
});
var node_command = function(cmd) {
PVE.Utils.API2Request({
params: { command: cmd },
url: '/nodes/' + nodename + '/status',
method: 'POST',
waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
};
var actionBtn = Ext.create('Ext.Button', {
text: gettext('More'),
iconCls: 'fa fa-fw fa-ellipsis-v',
disabled: !caps.nodes['Sys.PowerMgmt'],
menu: new Ext.menu.Menu({
items: [
{
text: gettext('Start all VMs and Containers'),
iconCls: 'fa fa-fw fa-play',
handler: function() {
var msg = gettext('Start all VMs and Containers') + ' (' + gettext('Node') + " '" + nodename + "')";
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
PVE.Utils.API2Request({
params: { force: 1 },
url: '/nodes/' + nodename + '/startall',
method: 'POST',
waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
});
}
},
{
text: gettext('Stop all VMs and Containers'),
iconCls: 'fa fa-fw fa-stop fa-red',
handler: function() {
var msg = gettext('Stop all VMs and Containers') + ' (' + gettext('Node') + " '" + nodename + "')";
Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
if (btn !== 'yes') {
return;
}
PVE.Utils.API2Request({
url: '/nodes/' + nodename + '/stopall',
method: 'POST',
waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
});
}
},
{
text: gettext('Migrate all VMs and Containers'),
iconCls: 'fa fa-fw fa-send-o',
handler: function() {
var win = Ext.create('PVE.window.MigrateAll', {
nodename: nodename
});
win.show();
}
}
]
})
});
var restartBtn = Ext.create('PVE.button.Button', {
text: gettext('Restart'),
disabled: !caps.nodes['Sys.PowerMgmt'],
confirmMsg: gettext('Node') + " '" + nodename + "' - " + gettext('Restart'),
handler: function() {
node_command('reboot');
},
iconCls: 'fa fa-undo'
});
var shutdownBtn = Ext.create('PVE.button.Button', {
text: gettext('Shutdown'),
disabled: !caps.nodes['Sys.PowerMgmt'],
confirmMsg: gettext('Node') + " '" + nodename + "' - " + gettext('Shutdown'),
handler: function() {
node_command('shutdown');
},
iconCls: 'fa fa-power-off'
});
var shellBtn = Ext.create('PVE.button.ConsoleButton', {
disabled: !caps.nodes['Sys.Console'],
text: gettext('Shell'),
consoleType: 'shell',
nodename: nodename,
iconCls: 'fa fa-terminal'
});
me.items = [];
Ext.apply(me, {
title: gettext('Node') + " '" + nodename + "'",
hstateid: 'nodetab',
defaults: { statusStore: me.statusStore },
tbar: [ restartBtn, shutdownBtn, shellBtn, actionBtn]
});
if (caps.nodes['Sys.Audit']) {
me.items.push(
{
title: gettext('Summary'),
itemId: 'summary',
xtype: 'pveNodeSummary'
},
{
title: gettext('Services'),
itemId: 'services',
xtype: 'pveNodeServiceView'
},
{
title: gettext('Network'),
itemId: 'network',
xtype: 'pveNodeNetworkView'
},
{
title: gettext('DNS'),
itemId: 'dns',
xtype: 'pveNodeDNSView'
},
{
title: gettext('Time'),
itemId: 'time',
xtype: 'pveNodeTimeView'
}
);
}
if (caps.nodes['Sys.Syslog']) {
me.items.push(
{
title: 'Syslog',
itemId: 'syslog',
xtype: 'pveLogView',
url: "/api2/extjs/nodes/" + nodename + "/syslog",
log_select_timespan: 1
}
);
}
me.items.push(
{
title: gettext('Task History'),
itemId: 'tasks',
xtype: 'pveNodeTasks'
}
);
if (caps.nodes['Sys.Console']) {
me.items.push(
{
xtype: 'pveFirewallPanel',
title: gettext('Firewall'),
base_url: '/nodes/' + nodename + '/firewall',
fwtype: 'node',
phstateid: me.hstateid,
itemId: 'firewall'
},
{
title: gettext('Updates'),
itemId: 'apt',
xtype: 'pveNodeAPT',
nodename: nodename
},
{
title: gettext('Console'),
itemId: 'console',
xtype: 'pveNoVncConsole',
consoleType: 'shell',
nodename: nodename
},
{
title: 'Ceph',
itemId: 'ceph',
xtype: 'pveNodeCeph',
phstateid: me.hstateid,
nodename: nodename
}
);
}
me.items.push(
{
title: gettext('Subscription'),
itemId: 'support',
xtype: 'pveNodeSubscription',
nodename: nodename
}
);
me.callParent();
me.mon(me.statusStore, 'load', function(s, records, success) {
var uptimerec = s.data.get('uptime');
var powermgmt = uptimerec ? uptimerec.data.value : false;
if (!caps.nodes['Sys.PowerMgmt']) {
powermgmt = false;
}
restartBtn.setDisabled(!powermgmt);
shutdownBtn.setDisabled(!powermgmt);
shellBtn.setDisabled(!powermgmt);
});
me.on('afterrender', function() {
me.statusStore.startUpdate();
});
me.on('destroy', function() {
me.statusStore.stopUpdate();
});
}
});
Ext.define('PVE.qemu.StatusView', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveQemuStatusView'],
disabled: true,
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var template = !!me.pveSelNode.data.template;
var render_cpu = function(value, metaData, record, rowIndex, colIndex, store) {
if (!me.getObjectValue('uptime')) {
return '-';
}
var maxcpu = me.getObjectValue('cpus', 1);
if (!(Ext.isNumeric(value) && Ext.isNumeric(maxcpu) && (maxcpu >= 1))) {
return '-';
}
var per = (value * 100);
return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
};
var render_mem = function(value, metaData, record, rowIndex, colIndex, store) {
var maxmem = me.getObjectValue('maxmem', 0);
var per = (value / maxmem)*100;
var text = "<div>" + PVE.Utils.totalText + ": " + PVE.Utils.format_size(maxmem) + "</div>" +
"<div>" + PVE.Utils.usedText + ": " + PVE.Utils.format_size(value) + "</div>";
return text;
};
var rows = {};
if (template) {
rows = {
name: { header: gettext('Name'), defaultValue: 'no name specified' },
cpus: { header: gettext('Processors'), required: true},
maxmem: { header: gettext('Memory'), renderer: PVE.Utils.render_size, required: true},
maxdisk: { header: gettext('Bootdisk size'), renderer: PVE.Utils.render_size, required: true}
};
} else {
rows = {
name: { header: gettext('Name'), defaultValue: 'no name specified' },
qmpstatus: { header: gettext('Status'), defaultValue: 'unknown' },
cpu: { iconCls: 'fa fa-up', header: gettext('CPU usage'), required: true, renderer: render_cpu },
cpus: { visible: false },
mem: { header: gettext('Memory usage'), required: true, renderer: render_mem },
maxmem: { visible: false },
maxdisk: { header: gettext('Bootdisk size'), renderer: PVE.Utils.render_size, required: true},
uptime: { header: gettext('Uptime'), required: true, renderer: PVE.Utils.render_uptime },
ha: { header: gettext('Managed by HA'), required: true, renderer: PVE.Utils.format_ha }
};
}
Ext.applyIf(me, {
cwidth1: 150,
rows: rows
});
me.callParent();
}
});
Ext.define('PVE.window.Migrate', {
extend: 'Ext.window.Window',
resizable: false,
migrate: function(target, online) {
var me = this;
PVE.Utils.API2Request({
params: { target: target, online: online },
url: '/nodes/' + me.nodename + '/' + me.vmtype + '/' + me.vmid + "/migrate",
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskViewer', {
upid: upid
});
win.show();
me.close();
}
});
},
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
if (!me.vmtype) {
throw "no VM type specified";
}
var running = false;
var vmrec = PVE.data.ResourceStore.findRecord('vmid', me.vmid,
0, false, false, true);
if (vmrec && vmrec.data && vmrec.data.running) {
running = true;
}
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: [
{
xtype: 'pveNodeSelector',
name: 'target',
fieldLabel: gettext('Target node'),
allowBlank: false,
onlineValidator: true
},
{
xtype: 'pvecheckbox',
name: 'online',
uncheckedValue: 0,
defaultValue: 0,
checked: running,
fieldLabel: gettext('Online')
}
]
});
var form = me.formPanel.getForm();
var submitBtn = Ext.create('Ext.Button', {
text: gettext('Migrate'),
handler: function() {
var values = form.getValues();
me.migrate(values.target, values.online);
}
});
Ext.apply(me, {
title: gettext('Migrate') + ((me.vmtype === 'qemu')?' VM ':' CT ') + me.vmid,
width: 350,
modal: true,
layout: 'auto',
border: false,
items: [ me.formPanel ],
buttons: [ submitBtn ]
});
me.callParent();
}
});
Ext.define('PVE.window.MigrateAll', {
extend: 'Ext.window.Window',
resizable: false,
migrate: function(target, maxworkers) {
var me = this;
PVE.Utils.API2Request({
params: { target: target, maxworkers: maxworkers},
url: '/nodes/' + me.nodename + '/' + "/migrateall",
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskViewer', {
upid: upid
});
win.show();
me.close();
}
});
},
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: [
{
xtype: 'pveNodeSelector',
name: 'target',
fieldLabel: 'Target node',
allowBlank: false,
onlineValidator: true
},
{
xtype: 'numberfield',
name: 'maxworkers',
minValue: 1,
maxValue: 100,
value: 1,
fieldLabel: 'Parallel jobs',
allowBlank: false
}
]
});
var form = me.formPanel.getForm();
var submitBtn = Ext.create('Ext.Button', {
text: 'Migrate',
handler: function() {
var values = form.getValues();
me.migrate(values.target, values.maxworkers);
}
});
Ext.apply(me, {
title: "Migrate All VMs",
width: 350,
modal: true,
layout: 'auto',
border: false,
items: [ me.formPanel ],
buttons: [ submitBtn ]
});
me.callParent();
}
});
Ext.define('PVE.qemu.Monitor', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveQemuMonitor',
maxLines: 500,
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var lines = [];
var textbox = Ext.createWidget('panel', {
region: 'center',
xtype: 'panel',
autoScroll: true,
border: true,
margins: '5 5 5 5',
bodyStyle: 'font-family: monospace;'
});
var scrollToEnd = function() {
var el = textbox.getTargetEl();
var dom = Ext.getDom(el);
var clientHeight = dom.clientHeight;
// BrowserBug: clientHeight reports 0 in IE9 StrictMode
// Instead we are using offsetHeight and hardcoding borders
if (Ext.isIE9 && Ext.isStrict) {
clientHeight = dom.offsetHeight + 2;
}
dom.scrollTop = dom.scrollHeight - clientHeight;
};
var refresh = function() {
textbox.update('<pre>' + lines.join('\n') + '</pre>');
scrollToEnd();
};
var addLine = function(line) {
lines.push(line);
if (lines.length > me.maxLines) {
lines.shift();
}
};
var executeCmd = function(cmd) {
addLine("# " + Ext.htmlEncode(cmd));
refresh();
PVE.Utils.API2Request({
params: { command: cmd },
url: '/nodes/' + nodename + '/qemu/' + vmid + "/monitor",
method: 'POST',
waitMsgTarget: me,
success: function(response, opts) {
var res = response.result.data;
Ext.Array.each(res.split('\n'), function(line) {
addLine(Ext.htmlEncode(line));
});
refresh();
},
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
};
Ext.apply(me, {
layout: { type: 'border' },
border: false,
items: [
textbox,
{
region: 'south',
margins:'0 5 5 5',
border: false,
xtype: 'textfield',
name: 'cmd',
value: '',
fieldStyle: 'font-family: monospace;',
allowBlank: true,
listeners: {
afterrender: function(f) {
f.focus(false);
addLine("Type 'help' for help.");
refresh();
},
specialkey: function(f, e) {
if (e.getKey() === e.ENTER) {
var cmd = f.getValue();
f.setValue('');
executeCmd(cmd);
} else if (e.getKey() === e.PAGE_UP) {
textbox.scrollBy(0, -0.9*textbox.getHeight(), false);
} else if (e.getKey() === e.PAGE_DOWN) {
textbox.scrollBy(0, 0.9*textbox.getHeight(), false);
}
}
}
}
],
listeners: {
show: function() {
var field = me.query('textfield[name="cmd"]')[0];
field.focus(false, true);
}
}
});
me.callParent();
}
});
Ext.define('PVE.qemu.Summary', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveQemuSummary',
scrollable: true,
bodyPadding: 10,
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
if (!me.workspace) {
throw "no workspace specified";
}
if (!me.statusStore) {
throw "no status storage specified";
}
var template = !!me.pveSelNode.data.template;
var rstore = me.statusStore;
var statusview = Ext.create('PVE.qemu.StatusView', {
title: gettext('Status'),
pveSelNode: me.pveSelNode,
width: template ? 800 : 400,
rstore: rstore
});
var notesview = Ext.create('PVE.panel.NotesView', {
pveSelNode: me.pveSelNode,
padding: template? '10 0 0 0' : '0 0 0 10',
flex: 1
});
if (template) {
Ext.apply(me, {
plugins: {
ptype: 'lazyitems',
items: [
{
xtype: 'container',
layout: {
type: 'column'
},
defaults: {
padding: '0 10 10 0'
},
items: [{
width: 800,
layout: {
type: 'vbox',
align: 'stretch'
},
border: false,
items: [ statusview, notesview ]
}]
}
]
},
listeners: {
activate: function() { notesview.load(); }
}
});
} else {
var rrdstore = Ext.create('PVE.data.RRDStore', {
rrdurl: "/api2/json/nodes/" + nodename + "/qemu/" + vmid + "/rrddata"
});
Ext.apply(me, {
tbar: [ '->', { xtype: 'pveRRDTypeSelector' } ],
plugins: {
ptype: 'lazyitems',
items: [
{
xtype: 'container',
layout: {
type: 'column'
},
defaults: {
padding: '0 10 10 0'
},
items: [
{
width: 800,
height: 300,
layout: {
type: 'hbox',
align: 'stretch'
},
border: false,
items: [ statusview, notesview ]
},
{
xtype: 'pveRRDChart',
title: gettext('CPU usage'),
pveSelNode: me.pveSelNode,
fields: ['cpu'],
fieldTitles: [gettext('CPU usage')],
store: rrdstore
},
{
xtype: 'pveRRDChart',
title: gettext('Memory usage'),
pveSelNode: me.pveSelNode,
fields: ['maxmem', 'mem'],
fieldTitles: [gettext('Total'), gettext('RAM usage')],
store: rrdstore
},
{
xtype: 'pveRRDChart',
title: gettext('Network traffic'),
pveSelNode: me.pveSelNode,
fields: ['netin','netout'],
store: rrdstore
},
{
xtype: 'pveRRDChart',
title: gettext('Disk IO'),
pveSelNode: me.pveSelNode,
fields: ['diskread','diskwrite'],
store: rrdstore
}
]
}
]
},
listeners: {
activate: function() {notesview.load(); rrdstore.startUpdate();},
hide: rrdstore.stopUpdate,
destroy: rrdstore.stopUpdate
}
});
}
me.callParent();
}
});
Ext.define('PVE.qemu.OSTypeInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.PVE.qemu.OSTypeInputPanel',
onlineHelp: 'chapter-qm.html#_os_settings',
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: 'component',
html: 'Microsoft Windows',
cls:'x-form-check-group-label'
},
{
xtype: 'radiofield',
name: 'ostype',
inputValue: 'win8'
},
{
xtype: 'radiofield',
name: 'ostype',
inputValue: 'win7'
},
{
xtype: 'radiofield',
name: 'ostype',
inputValue: 'w2k8'
},
{
xtype: 'radiofield',
name: 'ostype',
inputValue: 'wxp'
},
{
xtype: 'radiofield',
name: 'ostype',
inputValue: 'w2k'
}
];
me.column2 = [
{
xtype: 'component',
html: 'Linux/' + gettext('Other OS types'),
cls:'x-form-check-group-label'
},
{
xtype: 'radiofield',
name: 'ostype',
inputValue: 'l26'
},
{
xtype: 'radiofield',
name: 'ostype',
inputValue: 'l24'
},
{
xtype: 'radiofield',
name: 'ostype',
inputValue: 'solaris'
},
{
xtype: 'radiofield',
name: 'ostype',
inputValue: 'other'
}
];
Ext.Array.each(me.column1, function(def) {
if (def.inputValue) {
def.boxLabel = PVE.Utils.render_kvm_ostype(def.inputValue);
}
});
Ext.Array.each(me.column2, function(def) {
if (def.inputValue) {
def.boxLabel = PVE.Utils.render_kvm_ostype(def.inputValue);
}
});
Ext.apply(me, {
useFieldContainer: {
xtype: 'radiogroup',
allowBlank: false
}
});
me.callParent();
}
});
Ext.define('PVE.qemu.OSTypeEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.apply(me, {
subject: 'OS Type',
items: Ext.create('PVE.qemu.OSTypeInputPanel')
});
me.callParent();
me.load({
success: function(response, options) {
var value = response.result.data.ostype || 'other';
me.setValues({ ostype: value});
}
});
}
});
Ext.define('PVE.qemu.ProcessorInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.PVE.qemu.ProcessorInputPanel',
onlineHelp: 'chapter-qm.html#_cpu',
onGetValues: function(values) {
var me = this;
// build the cpu options:
me.cpu.cputype = values.cputype;
delete values.cputype;
var cpustring = PVE.Parser.printQemuCpu(me.cpu);
// remove cputype delete request:
var del = values['delete'];
delete values['delete'];
if (del) {
del = del.split(',');
Ext.Array.remove(del, 'cputype');
} else {
del = [];
}
if (cpustring) {
values.cpu = cpustring;
} else {
del.push('cpu');
}
var delarr = del.join(',');
if (delarr) {
values['delete'] = delarr;
}
return values;
},
initComponent : function() {
var me = this;
me.cpu = {};
me.column1 = [
{
xtype: 'numberfield',
name: 'sockets',
minValue: 1,
maxValue: 4,
value: '1',
fieldLabel: gettext('Sockets'),
allowBlank: false,
listeners: {
change: function(f, value) {
var sockets = me.down('field[name=sockets]').getValue();
var cores = me.down('field[name=cores]').getValue();
me.down('field[name=totalcores]').setValue(sockets*cores);
}
}
},
{
xtype: 'numberfield',
name: 'cores',
minValue: 1,
maxValue: 128,
value: '1',
fieldLabel: gettext('Cores'),
allowBlank: false,
listeners: {
change: function(f, value) {
var sockets = me.down('field[name=sockets]').getValue();
var cores = me.down('field[name=cores]').getValue();
me.down('field[name=totalcores]').setValue(sockets*cores);
}
}
},
{
xtype: 'pvecheckbox',
fieldLabel: gettext('Enable NUMA'),
name: 'numa',
uncheckedValue: 0
}
];
me.column2 = [
{
xtype: 'CPUModelSelector',
name: 'cputype',
value: '__default__',
fieldLabel: gettext('Type')
},
{
xtype: 'displayfield',
fieldLabel: gettext('Total cores'),
name: 'totalcores',
value: '1'
}
];
me.callParent();
}
});
Ext.define('PVE.qemu.ProcessorEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
var ipanel = Ext.create('PVE.qemu.ProcessorInputPanel');
Ext.apply(me, {
subject: gettext('Processors'),
items: ipanel
});
me.callParent();
me.load({
success: function(response, options) {
var data = response.result.data;
var value = data.cpu;
if (value) {
var cpu = PVE.Parser.parseQemuCpu(value);
ipanel.cpu = cpu;
data.cputype = cpu.cputype;
}
me.setValues(data);
}
});
}
});
Ext.define('PVE.qemu.BootOrderPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.pveQemuBootOrderPanel',
vmconfig: {}, // store loaded vm config
bootdisk: undefined,
selection: [],
list: [],
comboboxes: [],
isBootDisk: function(value) {
return (/^(ide|sata|scsi|virtio)\d+$/).test(value);
},
setVMConfig: function(vmconfig) {
var me = this;
me.vmconfig = vmconfig;
var order = me.vmconfig.boot || 'cdn';
me.bootdisk = me.vmconfig.bootdisk || undefined;
// get the first 3 characters
// ignore the rest (there should never be more than 3)
me.selection = order.split('').slice(0,3);
// build bootdev list
me.list = [];
Ext.Object.each(me.vmconfig, function(key, value) {
if (me.isBootDisk(key) &&
!(/media=cdrom/).test(value)) {
me.list.push([key, "Disk '" + key + "'"]);
}
});
me.list.push(['d', 'CD-ROM']);
me.list.push(['n', gettext('Network')]);
me.list.push(['__none__', PVE.Utils.noneText]);
me.recomputeList();
me.comboboxes.forEach(function(box) {
box.resetOriginalValue();
});
},
onGetValues: function(values) {
var me = this;
var order = me.selection.join('');
var res = { boot: order };
if (me.bootdisk && order.indexOf('c') !== -1) {
res.bootdisk = me.bootdisk;
} else {
res['delete'] = 'bootdisk';
}
return res;
},
recomputeSelection: function(combobox, newVal, oldVal) {
var me = this.up('#inputpanel');
me.selection = [];
me.comboboxes.forEach(function(item) {
var val = item.getValue();
// when selecting an already selected item,
// switch it around
if ((val === newVal || (me.isBootDisk(val) && me.isBootDisk(newVal))) &&
item.name !== combobox.name &&
newVal !== '__none__') {
// swap items
val = oldVal;
}
// push 'c','d' or 'n' in the array
if (me.isBootDisk(val)) {
me.selection.push('c');
me.bootdisk = val;
} else if (val === 'd' ||
val === 'n') {
me.selection.push(val);
}
});
me.recomputeList();
},
recomputeList: function(){
var me = this;
// set the correct values in the kvcomboboxes
var cnt = 0;
me.comboboxes.forEach(function(item) {
if (cnt === 0) {
// never show 'none' on first combobox
item.store.loadData(me.list.slice(0, me.list.length-1));
} else {
item.store.loadData(me.list);
}
item.suspendEvent('change');
if (cnt < me.selection.length) {
item.setValue((me.selection[cnt] !== 'c')?me.selection[cnt]:me.bootdisk);
} else if (cnt === 0){
item.setValue('');
} else {
item.setValue('__none__');
}
cnt++;
item.resumeEvent('change');
item.validate();
});
},
initComponent : function() {
var me = this;
// this has to be done here, because of
// the way our inputPanel class handles items
me.comboboxes = [
Ext.createWidget('pveKVComboBox', {
fieldLabel: gettext('Boot device') + " 1",
labelWidth: 120,
name: 'bd1',
allowBlank: false,
listeners: {
change: me.recomputeSelection
}
}),
Ext.createWidget('pveKVComboBox', {
fieldLabel: gettext('Boot device') + " 2",
labelWidth: 120,
name: 'bd2',
allowBlank: false,
listeners: {
change: me.recomputeSelection
}
}),
Ext.createWidget('pveKVComboBox', {
fieldLabel: gettext('Boot device') + " 3",
labelWidth: 120,
name: 'bd3',
allowBlank: false,
listeners: {
change: me.recomputeSelection
}
})
];
Ext.apply(me, { items: me.comboboxes });
me.callParent();
}
});
Ext.define('PVE.qemu.BootOrderEdit', {
extend: 'PVE.window.Edit',
items: [{
xtype: 'pveQemuBootOrderPanel',
itemId: 'inputpanel'
}],
subject: gettext('Boot Order'),
initComponent : function() {
var me = this;
me.callParent();
me.load({
success: function(response, options) {
me.down('#inputpanel').setVMConfig(response.result.data);
}
});
}
});
Ext.define('PVE.qemu.MemoryInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.PVE.qemu.MemoryInputPanel',
onlineHelp: 'chapter-qm.html#_memory',
insideWizard: false,
onGetValues: function(values) {
var me = this;
var res;
if (values.memoryType === 'fixed') {
res = { memory: values.memory };
res['delete'] = "balloon,shares";
} else {
res = {
memory: values.maxmemory,
balloon: values.balloon
};
if (Ext.isDefined(values.shares) && (values.shares !== "")) {
res.shares = values.shares;
} else {
res['delete'] = "shares";
}
}
return res;
},
initComponent : function() {
var me = this;
var labelWidth = 160;
var items = [
{
xtype: 'radiofield',
name: 'memoryType',
inputValue: 'fixed',
boxLabel: gettext('Use fixed size memory'),
checked: true,
listeners: {
change: function(f, value) {
if (!me.rendered) {
return;
}
me.down('field[name=memory]').setDisabled(!value);
me.down('field[name=maxmemory]').setDisabled(value);
me.down('field[name=balloon]').setDisabled(value);
me.down('field[name=shares]').setDisabled(value);
}
}
},
{
xtype: 'pveMemoryField',
name: 'memory',
hotplug: me.hotplug,
fieldLabel: gettext('Memory') + ' (MB)',
labelAlign: 'right',
labelWidth: labelWidth
},
{
xtype: 'radiofield',
name: 'memoryType',
inputValue: 'dynamic',
boxLabel: gettext('Automatically allocate memory within this range'),
listeners: {
change: function(f, value) {
if (!me.rendered) {
return;
}
}
}
},
{
xtype: 'pveMemoryField',
name: 'maxmemory',
hotplug: me.hotplug,
disabled: true,
value: '1024',
fieldLabel: gettext('Maximum memory') + ' (MB)',
labelAlign: 'right',
labelWidth: labelWidth,
listeners: {
change: function(f, value) {
var bf = me.down('field[name=balloon]');
var balloon = bf.getValue();
if (balloon > value) {
bf.setValue(value);
}
bf.setMaxValue(value);
}
}
},
{
xtype: 'numberfield',
name: 'balloon',
disabled: true,
minValue: 0,
maxValue: 512*1024,
value: '512',
step: 32,
fieldLabel: gettext('Minimum memory') + ' (MB)',
labelAlign: 'right',
labelWidth: labelWidth,
allowBlank: false
},
{
xtype: 'numberfield',
name: 'shares',
disabled: true,
minValue: 0,
maxValue: 50000,
value: '',
step: 10,
fieldLabel: gettext('Shares'),
labelAlign: 'right',
labelWidth: labelWidth,
allowBlank: true,
emptyText: PVE.Utils.defaultText + ' (1000)',
submitEmptyText: false
}
];
if (me.insideWizard) {
me.column1 = items;
} else {
me.items = items;
}
me.callParent();
}
});
Ext.define('PVE.qemu.MemoryEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
var memoryhotplug;
if(me.hotplug) {
Ext.each(me.hotplug.split(','), function(el) {
if (el === 'memory') {
memoryhotplug = 1;
}
});
}
var ipanel = Ext.create('PVE.qemu.MemoryInputPanel', {
hotplug: memoryhotplug
});
Ext.apply(me, {
subject: gettext('Memory'),
items: [ ipanel ],
// uncomment the following to use the async configiguration API
// backgroundDelay: 5,
width: 400
});
me.callParent();
me.load({
success: function(response, options) {
var data = response.result.data;
var values = {
memory: data.memory,
maxmemory: data.memory,
balloon: data.balloon,
shares: data.shares,
memoryType: data.balloon ? 'dynamic' : 'fixed'
};
ipanel.setValues(values);
}
});
}
});
Ext.define('PVE.qemu.NetworkInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.PVE.qemu.NetworkInputPanel',
onlineHelp: 'chapter-qm.html#_network_device',
insideWizard: false,
onGetValues: function(values) {
var me = this;
me.network.model = values.model;
if (values.networkmode === 'none') {
return {};
} else if (values.networkmode === 'bridge') {
me.network.bridge = values.bridge;
me.network.tag = values.tag;
me.network.firewall = values.firewall;
} else {
me.network.bridge = undefined;
}
me.network.macaddr = values.macaddr;
me.network.disconnect = values.disconnect;
me.network.queues = values.queues;
if (values.rate) {
me.network.rate = values.rate;
} else {
delete me.network.rate;
}
var params = {};
params[me.confid] = PVE.Parser.printQemuNetwork(me.network);
return params;
},
setNetwork: function(confid, data) {
var me = this;
me.confid = confid;
if (data) {
data.networkmode = data.bridge ? 'bridge' : 'nat';
} else {
data = {};
data.networkmode = 'bridge';
}
me.network = data;
me.setValues(me.network);
},
setNodename: function(nodename) {
var me = this;
me.bridgesel.setNodename(nodename);
},
initComponent : function() {
var me = this;
me.network = {};
me.confid = 'net0';
me.bridgesel = Ext.create('PVE.form.BridgeSelector', {
name: 'bridge',
fieldLabel: gettext('Bridge'),
nodename: me.nodename,
labelAlign: 'right',
autoSelect: true,
allowBlank: false
});
me.column1 = [
{
xtype: 'radiofield',
name: 'networkmode',
inputValue: 'bridge',
boxLabel: gettext('Bridged mode'),
checked: true,
listeners: {
change: function(f, value) {
if (!me.rendered) {
return;
}
me.down('field[name=bridge]').setDisabled(!value);
me.down('field[name=bridge]').validate();
me.down('field[name=tag]').setDisabled(!value);
me.down('field[name=firewall]').setDisabled(!value);
}
}
},
me.bridgesel,
{
xtype: 'pveVlanField',
name: 'tag',
value: '',
labelAlign: 'right'
},
me.bridgesel,
{
xtype: 'pvecheckbox',
fieldLabel: gettext('Firewall'),
name: 'firewall',
labelAlign: 'right'
},
{
xtype: 'radiofield',
name: 'networkmode',
inputValue: 'nat',
boxLabel: gettext('NAT mode')
}
];
if (me.insideWizard) {
me.column1.push({
xtype: 'radiofield',
name: 'networkmode',
inputValue: 'none',
boxLabel: gettext('No network device')
});
}
me.column2 = [
{
xtype: 'PVE.form.NetworkCardSelector',
name: 'model',
fieldLabel: gettext('Model'),
value: 'e1000',
allowBlank: false
},
{
xtype: 'textfield',
name: 'macaddr',
fieldLabel: gettext('MAC address'),
vtype: 'MacAddress',
allowBlank: true,
emptyText: 'auto'
},
{
xtype: 'numberfield',
name: 'rate',
fieldLabel: gettext('Rate limit') + ' (MB/s)',
minValue: 0,
maxValue: 10*1024,
value: '',
emptyText: 'unlimited',
allowBlank: true
},
{
xtype: 'numberfield',
name: 'queues',
fieldLabel: gettext('Multiqueues'),
minValue: 1,
maxValue: 8,
value: '',
allowBlank: true
},
{
xtype: 'pvecheckbox',
fieldLabel: gettext('Disconnect'),
name: 'disconnect'
}
];
me.callParent();
}
});
Ext.define('PVE.qemu.NetworkEdit', {
extend: 'PVE.window.Edit',
isAdd: true,
initComponent : function() {
/*jslint confusion: true */
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
me.create = me.confid ? false : true;
var ipanel = Ext.create('PVE.qemu.NetworkInputPanel', {
confid: me.confid,
nodename: nodename
});
Ext.applyIf(me, {
subject: gettext('Network Device'),
items: ipanel
});
me.callParent();
me.load({
success: function(response, options) {
var i, confid;
me.vmconfig = response.result.data;
if (!me.create) {
var value = me.vmconfig[me.confid];
var network = PVE.Parser.parseQemuNetwork(me.confid, value);
if (!network) {
Ext.Msg.alert(gettext('Error'), 'Unable to parse network options');
me.close();
return;
}
ipanel.setNetwork(me.confid, network);
} else {
for (i = 0; i < 100; i++) {
confid = 'net' + i.toString();
if (!Ext.isDefined(me.vmconfig[confid])) {
me.confid = confid;
break;
}
}
ipanel.setNetwork(me.confid);
}
}
});
}
});
Ext.define('PVE.qemu.Smbios1InputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.PVE.qemu.Smbios1InputPanel',
insideWizard: false,
smbios1: {},
onGetValues: function(values) {
var me = this;
var params = {
smbios1: PVE.Parser.printQemuSmbios1(values)
};
return params;
},
setSmbios1: function(data) {
var me = this;
me.smbios1 = data;
me.setValues(me.smbios1);
},
initComponent : function() {
var me = this;
me.items = [
{
xtype: 'textfield',
fieldLabel: 'UUID',
regex: /^[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$/,
name: 'uuid'
},
{
xtype: 'textfield',
fieldLabel: gettext('Manufacturer'),
regex: /^\S+$/,
name: 'manufacturer'
},
{
xtype: 'textfield',
fieldLabel: gettext('Product'),
regex: /^\S+$/,
name: 'product'
},
{
xtype: 'textfield',
fieldLabel: gettext('Version'),
regex: /^\S+$/,
name: 'version'
},
{
xtype: 'textfield',
fieldLabel: gettext('Serial'),
regex: /^\S+$/,
name: 'serial'
},
{
xtype: 'textfield',
fieldLabel: gettext('SKU'),
regex: /^\S+$/,
name: 'sku'
},
{
xtype: 'textfield',
fieldLabel: gettext('Family'),
regex: /^\S+$/,
name: 'family'
}
];
me.callParent();
}
});
Ext.define('PVE.qemu.Smbios1Edit', {
extend: 'PVE.window.Edit',
initComponent : function() {
/*jslint confusion: true */
var me = this;
var ipanel = Ext.create('PVE.qemu.Smbios1InputPanel', {});
Ext.applyIf(me, {
subject: gettext('SMBIOS settings (type1)'),
width: 450,
items: ipanel
});
me.callParent();
me.load({
success: function(response, options) {
var i, confid;
me.vmconfig = response.result.data;
var value = me.vmconfig.smbios1;
if (value) {
var data = PVE.Parser.parseQemuSmbios1(value);
if (!data) {
Ext.Msg.alert(gettext('Error'), 'Unable to parse smbios options');
me.close();
return;
}
ipanel.setSmbios1(data);
}
}
});
}
});
// fixme: howto avoid jslint type confusion?
/*jslint confusion: true */
Ext.define('PVE.qemu.CDInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.PVE.qemu.CDInputPanel',
insideWizard: false,
onGetValues: function(values) {
var me = this;
var confid = me.confid || (values.controller + values.deviceid);
me.drive.media = 'cdrom';
if (values.mediaType === 'iso') {
me.drive.file = values.cdimage;
} else if (values.mediaType === 'cdrom') {
me.drive.file = 'cdrom';
} else {
me.drive.file = 'none';
}
var params = {};
params[confid] = PVE.Parser.printQemuDrive(me.drive);
return params;
},
setVMConfig: function(vmconfig) {
var me = this;
if (me.bussel) {
me.bussel.setVMConfig(vmconfig, 'cdrom');
}
},
setDrive: function(drive) {
var me = this;
var values = {};
if (drive.file === 'cdrom') {
values.mediaType = 'cdrom';
} else if (drive.file === 'none') {
values.mediaType = 'none';
} else {
values.mediaType = 'iso';
var match = drive.file.match(/^([^:]+):/);
if (match) {
values.cdstorage = match[1];
values.cdimage = drive.file;
}
}
me.drive = drive;
me.setValues(values);
},
setNodename: function(nodename) {
var me = this;
me.cdstoragesel.setNodename(nodename);
me.cdfilesel.setStorage(undefined, nodename);
},
initComponent : function() {
var me = this;
me.drive = {};
var items = [];
if (!me.confid) {
me.bussel = Ext.createWidget('PVE.form.ControllerSelector', {
noVirtIO: true
});
items.push(me.bussel);
}
items.push({
xtype: 'radiofield',
name: 'mediaType',
inputValue: 'iso',
boxLabel: gettext('Use CD/DVD disc image file (iso)'),
checked: true,
listeners: {
change: function(f, value) {
if (!me.rendered) {
return;
}
me.down('field[name=cdstorage]').setDisabled(!value);
me.down('field[name=cdimage]').setDisabled(!value);
me.down('field[name=cdimage]').validate();
}
}
});
me.cdfilesel = Ext.create('PVE.form.FileSelector', {
name: 'cdimage',
nodename: me.nodename,
storageContent: 'iso',
fieldLabel: gettext('ISO image'),
labelAlign: 'right',
allowBlank: false
});
me.cdstoragesel = Ext.create('PVE.form.StorageSelector', {
name: 'cdstorage',
nodename: me.nodename,
fieldLabel: gettext('Storage'),
labelAlign: 'right',
storageContent: 'iso',
allowBlank: false,
autoSelect: me.insideWizard,
listeners: {
change: function(f, value) {
me.cdfilesel.setStorage(value);
}
}
});
items.push(me.cdstoragesel);
items.push(me.cdfilesel);
items.push({
xtype: 'radiofield',
name: 'mediaType',
inputValue: 'cdrom',
boxLabel: gettext('Use physical CD/DVD Drive')
});
items.push({
xtype: 'radiofield',
name: 'mediaType',
inputValue: 'none',
boxLabel: gettext('Do not use any media')
});
if (me.insideWizard) {
me.column1 = items;
} else {
me.items = items;
}
me.callParent();
}
});
Ext.define('PVE.qemu.CDEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
me.create = me.confid ? false : true;
var ipanel = Ext.create('PVE.qemu.CDInputPanel', {
confid: me.confid,
nodename: nodename
});
Ext.applyIf(me, {
subject: 'CD/DVD Drive',
items: [ ipanel ]
});
me.callParent();
me.load({
success: function(response, options) {
ipanel.setVMConfig(response.result.data);
if (me.confid) {
var value = response.result.data[me.confid];
var drive = PVE.Parser.parseQemuDrive(me.confid, value);
if (!drive) {
Ext.Msg.alert('Error', 'Unable to parse drive options');
me.close();
return;
}
ipanel.setDrive(drive);
}
}
});
}
});
// fixme: howto avoid jslint type confusion?
/*jslint confusion: true */
Ext.define('PVE.qemu.HDInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.PVE.qemu.HDInputPanel',
onlineHelp: 'chapter-qm.html#_hard_disk',
insideWizard: false,
unused: false, // ADD usused disk imaged
vmconfig: {}, // used to select usused disks
controller: {
xclass: 'Ext.app.ViewController',
onControllerChange: function(field) {
var value = field.getValue();
this.lookup('iothread').setDisabled(!value.match(/^(virtio|scsi)/));
},
control: {
'field[name=controller]': {
change: 'onControllerChange',
afterrender: 'onControllerChange'
},
'field[name=hdstorage]': {
change: function(f, value) {
if (!value) { // initial store loading fires an unwanted 'change'
return;
}
var me = this.getView();
var rec = f.store.getById(value);
if (rec.data.type === 'iscsi') {
me.hdfilesel.setStorage(value);
me.hdfilesel.setDisabled(false);
me.formatsel.setValue('raw');
me.formatsel.setDisabled(true);
me.hdfilesel.setVisible(true);
me.hdsizesel.setDisabled(true);
me.hdsizesel.setVisible(false);
} else if (rec.data.type === 'lvm' ||
rec.data.type === 'lvmthin' ||
rec.data.type === 'drbd' ||
rec.data.type === 'rbd' ||
rec.data.type === 'sheepdog' ||
rec.data.type === 'zfs' ||
rec.data.type === 'zfspool') {
me.hdfilesel.setDisabled(true);
me.hdfilesel.setVisible(false);
me.formatsel.setValue('raw');
me.formatsel.setDisabled(true);
me.hdsizesel.setDisabled(false);
me.hdsizesel.setVisible(true);
} else {
me.hdfilesel.setDisabled(true);
me.hdfilesel.setVisible(false);
me.formatsel.setValue('qcow2');
me.formatsel.setDisabled(false);
me.hdsizesel.setDisabled(false);
me.hdsizesel.setVisible(true);
}
}
}
}
},
onGetValues: function(values) {
var me = this;
var confid = me.confid || (values.controller + values.deviceid);
if (me.unused) {
me.drive.file = me.vmconfig[values.unusedId];
confid = values.controller + values.deviceid;
} else if (me.create) {
if (values.hdimage) {
me.drive.file = values.hdimage;
} else {
me.drive.file = values.hdstorage + ":" + values.disksize;
}
me.drive.format = values.diskformat;
}
if (values.nobackup) {
me.drive.backup = 'no';
} else {
delete me.drive.backup;
}
if (values.discard) {
me.drive.discard = 'on';
} else {
delete me.drive.discard;
}
if (values.iothread && confid.match(/^(virtio|scsi)\d+$/)) {
me.drive.iothread = 'on';
} else {
delete me.drive.iothread;
}
if (values.cache) {
me.drive.cache = values.cache;
} else {
delete me.drive.cache;
}
var params = {};
params[confid] = PVE.Parser.printQemuDrive(me.drive);
return params;
},
setVMConfig: function(vmconfig) {
var me = this;
me.vmconfig = vmconfig;
if (me.bussel) {
me.bussel.setVMConfig(vmconfig, true);
}
if (me.unusedDisks) {
var disklist = [];
Ext.Object.each(vmconfig, function(key, value) {
if (key.match(/^unused\d+$/)) {
disklist.push([key, value]);
}
});
me.unusedDisks.store.loadData(disklist);
me.unusedDisks.setValue(me.confid);
}
},
setDrive: function(drive) {
var me = this;
me.drive = drive;
var values = {};
var match = drive.file.match(/^([^:]+):/);
if (match) {
values.hdstorage = match[1];
}
values.hdimage = drive.file;
values.nobackup = !PVE.Parser.parseBoolean(drive.backup, 1);
values.diskformat = drive.format || 'raw';
values.cache = drive.cache || '__default__';
values.discard = (drive.discard === 'on');
values.iothread = PVE.Parser.parseBoolean(drive.iothread);
me.setValues(values);
},
setNodename: function(nodename) {
var me = this;
me.hdstoragesel.setNodename(nodename);
me.hdfilesel.setStorage(undefined, nodename);
},
initComponent : function() {
var me = this;
me.drive = {};
me.column1 = [];
me.column2 = [];
if (!me.confid || me.unused) {
me.bussel = Ext.createWidget('PVE.form.ControllerSelector', {
vmconfig: me.insideWizard ? {ide2: 'cdrom'} : {}
});
me.column1.push(me.bussel);
}
if (me.unused) {
me.unusedDisks = Ext.create('PVE.form.KVComboBox', {
name: 'unusedId',
fieldLabel: gettext('Disk image'),
matchFieldWidth: false,
listConfig: {
width: 350
},
data: [],
allowBlank: false
});
me.column1.push(me.unusedDisks);
} else if (me.create) {
me.formatsel = Ext.create('PVE.form.DiskFormatSelector', {
name: 'diskformat',
fieldLabel: gettext('Format'),
value: 'qcow2',
allowBlank: false
});
me.hdfilesel = Ext.create('PVE.form.FileSelector', {
name: 'hdimage',
nodename: me.nodename,
storageContent: 'images',
fieldLabel: gettext('Disk image'),
disabled: true,
hidden: true,
allowBlank: false
});
me.hdsizesel = Ext.createWidget('numberfield', {
name: 'disksize',
minValue: 0.001,
maxValue: 128*1024,
decimalPrecision: 3,
value: '32',
fieldLabel: gettext('Disk size') + ' (GB)',
allowBlank: false
});
me.hdstoragesel = Ext.create('PVE.form.StorageSelector', {
name: 'hdstorage',
nodename: me.nodename,
fieldLabel: gettext('Storage'),
storageContent: 'images',
autoSelect: me.insideWizard,
allowBlank: false
});
me.column1.push(me.hdstoragesel);
me.column1.push(me.hdfilesel);
me.column1.push(me.hdsizesel);
me.column1.push(me.formatsel);
} else {
me.column1.push({
xtype: 'textfield',
disabled: true,
submitValue: false,
fieldLabel: gettext('Disk image'),
name: 'hdimage'
});
}
me.column2.push({
xtype: 'CacheTypeSelector',
name: 'cache',
value: '__default__',
fieldLabel: gettext('Cache')
});
me.column2.push({
xtype: 'pvecheckbox',
fieldLabel: gettext('No backup'),
name: 'nobackup'
});
me.column2.push({
xtype: 'pvecheckbox',
fieldLabel: gettext('Discard'),
name: 'discard'
});
me.column2.push({
xtype: 'pvecheckbox',
disabled: me.insideWizard || (me.confid && !me.confid.match(/^(virtio|scsi)/)),
fieldLabel: gettext('IO thread'),
reference: 'iothread',
name: 'iothread'
});
me.callParent();
}
});
Ext.define('PVE.qemu.HDEdit', {
extend: 'PVE.window.Edit',
isAdd: true,
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var unused = me.confid && me.confid.match(/^unused\d+$/);
me.create = me.confid ? unused : true;
var ipanel = Ext.create('PVE.qemu.HDInputPanel', {
confid: me.confid,
nodename: nodename,
unused: unused,
create: me.create
});
var subject;
if (unused) {
me.subject = gettext('Unused Disk');
} else if (me.create) {
me.subject = gettext('Hard Disk');
} else {
me.subject = gettext('Hard Disk') + ' (' + me.confid + ')';
}
me.items = [ ipanel ];
me.callParent();
me.load({
success: function(response, options) {
ipanel.setVMConfig(response.result.data);
if (me.confid) {
var value = response.result.data[me.confid];
var drive = PVE.Parser.parseQemuDrive(me.confid, value);
if (!drive) {
Ext.Msg.alert(gettext('Error'), 'Unable to parse drive options');
me.close();
return;
}
ipanel.setDrive(drive);
me.isValid(); // trigger validation
}
}
});
}
});
Ext.define('PVE.window.HDResize', {
extend: 'Ext.window.Window',
resizable: false,
resize_disk: function(disk, size) {
var me = this;
var params = { disk: disk, size: '+' + size + 'G' };
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/resize',
waitMsgTarget: me,
method: 'PUT',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
me.close();
}
});
},
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var items = [
{
xtype: 'displayfield',
name: 'disk',
value: me.disk,
fieldLabel: gettext('Disk'),
vtype: 'StorageId',
allowBlank: false
}
];
me.hdsizesel = Ext.createWidget('numberfield', {
name: 'size',
minValue: 0,
maxValue: 128*1024,
decimalPrecision: 3,
value: '0',
fieldLabel: gettext('Size Increment') + ' (GB)',
allowBlank: false
});
items.push(me.hdsizesel);
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 140,
anchor: '100%'
},
items: items
});
var form = me.formPanel.getForm();
var submitBtn;
me.title = gettext('Resize disk');
submitBtn = Ext.create('Ext.Button', {
text: gettext('Resize disk'),
handler: function() {
if (form.isValid()) {
var values = form.getValues();
me.resize_disk(me.disk, values.size);
}
}
});
Ext.apply(me, {
modal: true,
width: 250,
height: 150,
border: false,
layout: 'fit',
buttons: [ submitBtn ],
items: [ me.formPanel ]
});
me.callParent();
if (!me.disk) {
return;
}
}
});
Ext.define('PVE.window.HDMove', {
extend: 'Ext.window.Window',
resizable: false,
move_disk: function(disk, storage, format, delete_disk) {
var me = this;
var params = { disk: disk, storage: storage };
if (format) {
params.format = format;
}
if (delete_disk) {
params['delete'] = 1;
}
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/move_disk',
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskViewer', { upid: upid });
win.show();
me.close();
}
});
},
initComponent : function() {
var me = this;
var diskarray = [];
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var items = [
{
xtype: 'displayfield',
name: 'disk',
value: me.disk,
fieldLabel: gettext('Disk'),
vtype: 'StorageId',
allowBlank: false
}
];
me.hdstoragesel = Ext.create('PVE.form.StorageSelector', {
name: 'hdstorage',
nodename: me.nodename,
fieldLabel: gettext('Target Storage'),
storageContent: 'images',
autoSelect: me.insideWizard,
allowBlank: true,
disabled: false,
hidden: false,
listeners: {
change: function(f, value) {
if (!value) { // initial store loading fires an unwanted 'change
return;
}
var rec = f.store.getById(value);
if (rec.data.type === 'iscsi' ||
rec.data.type === 'lvm' ||
rec.data.type === 'lvmthin' ||
rec.data.type === 'rbd' ||
rec.data.type === 'sheepdog' ||
rec.data.type === 'zfs' ||
rec.data.type === 'zfspool'
) {
me.formatsel.setValue('raw');
me.formatsel.setDisabled(true);
} else {
me.formatsel.setDisabled(false);
}
}
}
});
me.formatsel = Ext.create('PVE.form.DiskFormatSelector', {
name: 'diskformat',
fieldLabel: gettext('Format'),
value: 'raw',
disabled: true,
hidden: false,
allowBlank: false
});
items.push(me.hdstoragesel);
items.push(me.formatsel);
items.push({
xtype: 'pvecheckbox',
fieldLabel: gettext('Delete source'),
name: 'deleteDisk',
uncheckedValue: 0,
checked: false
});
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: items
});
var form = me.formPanel.getForm();
var submitBtn;
me.title = gettext("Move disk");
submitBtn = Ext.create('Ext.Button', {
text: gettext('Move disk'),
handler: function() {
if (form.isValid()) {
var values = form.getValues();
me.move_disk(me.disk, values.hdstorage, values.diskformat,
values.deleteDisk);
}
}
});
Ext.apply(me, {
modal: true,
width: 350,
border: false,
layout: 'fit',
buttons: [ submitBtn ],
items: [ me.formPanel ]
});
me.callParent();
}
});
// fixme: howto avoid jslint type confusion?
/*jslint confusion: true */
Ext.define('PVE.qemu.HDThrottleInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.PVE.qemu.HDThrottleInputPanel',
insideWizard: false,
unused: false, // ADD usused disk imaged
vmconfig: {}, // used to select usused disks
onGetValues: function(values) {
var me = this;
var confid = me.confid;
var names = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr'];
Ext.Array.each(names, function(name) {
if (values[name]) {
me.drive[name] = values[name];
} else {
delete me.drive[name];
}
var burst_name = name + '_max';
if (values[burst_name] && values[name]) {
me.drive[burst_name] = values[burst_name];
} else {
delete me.drive[burst_name];
}
});
var params = {};
params[confid] = PVE.Parser.printQemuDrive(me.drive);
return params;
},
setDrive: function(drive) {
var me = this;
me.drive = drive;
var values = {};
values.mbps_rd = drive.mbps_rd;
values.mbps_wr = drive.mbps_wr;
values.iops_rd = drive.iops_rd;
values.iops_wr = drive.iops_wr;
values.mbps_rd_max = drive.mbps_rd_max;
values.mbps_wr_max = drive.mbps_wr_max;
values.iops_rd_max = drive.iops_rd_max;
values.iops_wr_max = drive.iops_wr_max;
me.setValues(values);
},
initComponent : function() {
var me = this;
me.drive = {};
me.column1 = [];
me.column2 = [];
var width2 = 140;
me.mbps_rd = Ext.widget('numberfield', {
name: 'mbps_rd',
minValue: 1,
step: 1,
fieldLabel: gettext('Read limit') + ' (MB/s)',
labelWidth: width2,
emptyText: gettext('unlimited')
});
me.column1.push(me.mbps_rd);
me.mbps_rd_max = Ext.widget('numberfield', {
name: 'mbps_rd_max',
minValue: 1,
step: 1,
fieldLabel: gettext('Read max burst') + ' (MB)',
labelWidth: width2,
emptyText: gettext('default')
});
me.column2.push(me.mbps_rd_max);
me.mbps_wr = Ext.widget('numberfield', {
name: 'mbps_wr',
minValue: 1,
step: 1,
fieldLabel: gettext('Write limit') + ' (MB/s)',
labelWidth: width2,
emptyText: gettext('unlimited')
});
me.column1.push(me.mbps_wr);
me.mbps_wr_max = Ext.widget('numberfield', {
name: 'mbps_wr_max',
minValue: 1,
step: 1,
fieldLabel: gettext('Write max burst') + ' (MB)',
labelWidth: width2,
emptyText: gettext('default')
});
me.column2.push(me.mbps_wr_max);
me.iops_rd = Ext.widget('numberfield', {
name: 'iops_rd',
minValue: 10,
step: 10,
fieldLabel: gettext('Read limit') + ' (ops/s)',
labelWidth: width2,
emptyText: gettext('unlimited')
});
me.column1.push(me.iops_rd);
me.iops_rd_max = Ext.widget('numberfield', {
name: 'iops_rd_max',
minValue: 10,
step: 10,
fieldLabel: gettext('Read max burst') + ' (ops)',
labelWidth: width2,
emptyText: gettext('default')
});
me.column2.push(me.iops_rd_max);
me.iops_wr = Ext.widget('numberfield', {
name: 'iops_wr',
minValue: 10,
step: 10,
fieldLabel: gettext('Write limit') + ' (ops/s)',
labelWidth: width2,
emptyText: gettext('unlimited')
});
me.column1.push(me.iops_wr);
me.iops_wr_max = Ext.widget('numberfield', {
name: 'iops_wr_max',
minValue: 10,
step: 10,
fieldLabel: gettext('Write max burst') + ' (ops)',
labelWidth: width2,
emptyText: gettext('default')
});
me.column2.push(me.iops_wr_max);
me.callParent();
}
});
Ext.define('PVE.qemu.HDThrottle', {
extend: 'PVE.window.Edit',
isAdd: true,
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var unused = me.confid && me.confid.match(/^unused\d+$/);
me.create = me.confid ? unused : true;
var ipanel = Ext.create('PVE.qemu.HDThrottleInputPanel', {
confid: me.confid,
nodename: nodename
});
var subject;
if (unused) {
me.subject = gettext('Unused Disk');
} else {
me.subject = gettext('Hard Disk') + ' (' + me.confid + ')';
}
me.items = [ ipanel ];
me.callParent();
me.load({
success: function(response, options) {
if (me.confid) {
var value = response.result.data[me.confid];
var drive = PVE.Parser.parseQemuDrive(me.confid, value);
if (!drive) {
Ext.Msg.alert(gettext('Error'), 'Unable to parse drive options');
me.close();
return;
}
ipanel.setDrive(drive);
me.isValid(); // trigger validation
}
}
});
}
});
Ext.define('PVE.qemu.CPUOptionsInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.PVE.qemu.CPUOptionsInputPanel',
onGetValues: function(values) {
var me = this;
var delete_array = [];
if (values.vcpus === '') {
delete_array.push('vcpus');
delete values.vcpus;
}
if (values.cpulimit === '' || values.cpulimit == '0') {
delete_array.push('cpulimit');
delete values.cpulimit;
}
if (values.cpuunits === '' || values.cpuunits == '1024') {
delete_array.push('cpuunits');
delete values.cpuunits;
}
if (delete_array.length) {
values['delete'] = delete_array.join(',');
}
return values;
},
initComponent : function() {
var me = this;
var items = [
{
xtype: 'numberfield',
name: 'vcpus',
minValue: 1,
maxValue: me.maxvcpus,
value: '',
fieldLabel: gettext('VCPUs'),
allowBlank: true,
emptyText: me.maxvcpus
},
{
xtype: 'numberfield',
name: 'cpulimit',
minValue: 0,
maxValue: me.maxvcpus,
value: '',
step: 1,
fieldLabel: gettext('CPU limit'),
allowBlank: true,
emptyText: gettext('unlimited')
},
{
xtype: 'numberfield',
name: 'cpuunits',
fieldLabel: gettext('CPU units'),
minValue: 8,
maxValue: 500000,
value: '1024',
allowBlank: true
}
];
me.items = items;
me.callParent();
}
});
Ext.define('PVE.qemu.CPUOptions', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
var ipanel = Ext.create('PVE.qemu.CPUOptionsInputPanel', {
maxvcpus: me.maxvcpus
});
Ext.apply(me, {
subject: gettext('CPU options'),
items: [ ipanel ]
});
me.callParent();
me.load();
}
});
Ext.define('PVE.qemu.DisplayEdit', {
extend: 'PVE.window.Edit',
vmconfig: undefined,
initComponent : function() {
var me = this;
var displayField;
var validateDisplay = function() {
/*jslint confusion: true */
var val = displayField.getValue();
if (me.vmconfig && val.match(/^serial\d+$/)) {
if (me.vmconfig[val] && me.vmconfig[val] === 'socket') {
return true;
}
return "Serial interface '" + val + "' is not correctly configured.";
}
return true;
};
displayField = Ext.createWidget('DisplaySelector', {
name: 'vga',
value: '__default__',
fieldLabel: gettext('Graphic card'),
validator: validateDisplay
});
Ext.apply(me, {
subject: gettext('Display'),
width: 350,
items: displayField
});
me.callParent();
me.load({
success: function(response, options) {
var values = response.result.data;
me.vmconfig = values;
me.setValues(values);
}
});
}
});
Ext.define('PVE.qemu.KeyboardEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.applyIf(me, {
subject: gettext('Keyboard Layout'),
items: {
xtype: 'VNCKeyboardSelector',
name: 'keyboard',
value: '__default__',
fieldLabel: gettext('Keyboard Layout')
}
});
me.callParent();
me.load();
}
});
// fixme: howto avoid jslint type confusion?
/*jslint confusion: true */
Ext.define('PVE.qemu.HardwareView', {
extend: 'PVE.grid.PendingObjectGrid',
alias: ['widget.PVE.qemu.HardwareView'],
renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
var me = this;
var rows = me.rows;
var rowdef = rows[key] || {};
metaData.tdAttr = "valign=middle";
if (rowdef.tdCls) {
metaData.tdCls = rowdef.tdCls;
if (rowdef.tdCls == 'pve-itype-icon-storage') {
var value = me.getObjectValue(key, '', true);
if (value.match(/media=cdrom/)) {
metaData.tdCls = 'pve-itype-icon-cdrom';
return rowdef.cdheader;
}
}
}
return rowdef.header || key;
},
initComponent : function() {
var me = this;
var i, confid;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var caps = Ext.state.Manager.get('GuiCap');
var rows = {
memory: {
header: gettext('Memory'),
editor: caps.vms['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined,
never_delete: true,
defaultValue: 512,
tdCls: 'pve-itype-icon-memory',
renderer: function(value, metaData, record) {
var balloon = me.getObjectValue('balloon');
if (balloon) {
return PVE.Utils.format_size(balloon*1024*1024) + "/" +
PVE.Utils.format_size(value*1024*1024);
}
return PVE.Utils.format_size(value*1024*1024);
}
},
sockets: {
header: gettext('Processors'),
never_delete: true,
editor: (caps.vms['VM.Config.CPU'] || caps.vms['VM.Config.HWType']) ?
'PVE.qemu.ProcessorEdit' : undefined,
tdCls: 'pve-itype-icon-processor',
defaultValue: 1,
multiKey: ['sockets', 'cpu', 'cores', 'numa', 'vcpus', 'cpulimit', 'cpuunits'],
renderer: function(value, metaData, record, rowIndex, colIndex, store, pending) {
var sockets = me.getObjectValue('sockets', 1, pending);
var model = me.getObjectValue('cpu', undefined, pending);
var cores = me.getObjectValue('cores', 1, pending);
var numa = me.getObjectValue('numa', undefined, pending);
var vcpus = me.getObjectValue('vcpus', undefined, pending);
var cpulimit = me.getObjectValue('cpulimit', undefined, pending);
var cpuunits = me.getObjectValue('cpuunits', undefined, pending);
var res = (sockets*cores) + ' (' + sockets + ' sockets, ' + cores + ' cores)';
if (model) {
res += ' [' + model + ']';
}
if (numa) {
res += ' [numa=' + numa +']';
}
if (vcpus) {
res += ' [vcpus=' + vcpus +']';
}
if (cpulimit) {
res += ' [cpulimit=' + cpulimit +']';
}
if (cpuunits) {
res += ' [cpuunits=' + cpuunits +']';
}
return res;
}
},
keyboard: {
header: gettext('Keyboard Layout'),
never_delete: true,
editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.KeyboardEdit' : undefined,
tdCls: 'pve-itype-icon-keyboard',
defaultValue: '',
renderer: PVE.Utils.render_kvm_language
},
vga: {
header: gettext('Display'),
editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined,
never_delete: true,
tdCls: 'pve-itype-icon-display',
defaultValue: '',
renderer: PVE.Utils.render_kvm_vga_driver
},
cores: {
visible: false
},
cpu: {
visible: false
},
numa: {
visible: false
},
balloon: {
visible: false
},
hotplug: {
visible: false
},
vcpus: {
visible: false
},
cpuunits: {
visible: false
},
cpulimit: {
visible: false
}
};
for (i = 0; i < 4; i++) {
confid = "ide" + i;
rows[confid] = {
group: 1,
tdCls: 'pve-itype-icon-storage',
editor: 'PVE.qemu.HDEdit',
never_delete: caps.vms['VM.Config.Disk'] ? false : true,
header: gettext('Hard Disk') + ' (' + confid +')',
cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
};
}
for (i = 0; i < 6; i++) {
confid = "sata" + i;
rows[confid] = {
group: 1,
tdCls: 'pve-itype-icon-storage',
editor: 'PVE.qemu.HDEdit',
never_delete: caps.vms['VM.Config.Disk'] ? false : true,
header: gettext('Hard Disk') + ' (' + confid +')',
cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
};
}
for (i = 0; i < 16; i++) {
confid = "scsi" + i;
rows[confid] = {
group: 1,
tdCls: 'pve-itype-icon-storage',
editor: 'PVE.qemu.HDEdit',
never_delete: caps.vms['VM.Config.Disk'] ? false : true,
header: gettext('Hard Disk') + ' (' + confid +')',
cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
};
}
for (i = 0; i < 16; i++) {
confid = "virtio" + i;
rows[confid] = {
group: 1,
tdCls: 'pve-itype-icon-storage',
editor: 'PVE.qemu.HDEdit',
never_delete: caps.vms['VM.Config.Disk'] ? false : true,
header: gettext('Hard Disk') + ' (' + confid +')',
cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
};
}
for (i = 0; i < 32; i++) {
confid = "net" + i;
rows[confid] = {
group: 2,
tdCls: 'pve-itype-icon-network',
editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined,
never_delete: caps.vms['VM.Config.Network'] ? false : true,
header: gettext('Network Device') + ' (' + confid +')'
};
}
for (i = 0; i < 8; i++) {
rows["unused" + i] = {
group: 3,
tdCls: 'pve-itype-icon-storage',
editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
header: gettext('Unused Disk') + ' ' + i
};
}
var sorterFn = function(rec1, rec2) {
var v1 = rec1.data.key;
var v2 = rec2.data.key;
var g1 = rows[v1].group || 0;
var g2 = rows[v2].group || 0;
return (g1 !== g2) ?
(g1 > g2 ? 1 : -1) : (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
};
var reload = function() {
me.rstore.load();
};
var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var rowdef = rows[rec.data.key];
if (!rowdef.editor) {
return;
}
var editor = rowdef.editor;
if (rowdef.tdCls == 'pve-itype-icon-storage') {
var value = me.getObjectValue(rec.data.key, '', true);
if (value.match(/media=cdrom/)) {
editor = 'PVE.qemu.CDEdit';
}
}
var win;
if (Ext.isString(editor)) {
win = Ext.create(editor, {
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
});
} else {
var config = Ext.apply({
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
}, rowdef.editor);
win = Ext.createWidget(rowdef.editor.xtype, config);
win.load();
}
win.show();
win.on('destroy', reload);
};
var run_diskthrottle = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.qemu.HDThrottle', {
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
});
win.show();
win.on('destroy', reload);
};
var run_resize = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.window.HDResize', {
disk: rec.data.key,
nodename: nodename,
vmid: vmid
});
win.show();
win.on('destroy', reload);
};
var run_cpuoptions = function() {
var sockets = me.getObjectValue('sockets', 1);
var cores = me.getObjectValue('cores', 1);
var win = Ext.create('PVE.qemu.CPUOptions', {
maxvcpus: sockets * cores,
vmid: vmid,
pveSelNode: me.pveSelNode,
url: '/api2/extjs/' + baseurl
});
win.show();
win.on('destroy', reload);
};
var run_move = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.window.HDMove', {
disk: rec.data.key,
nodename: nodename,
vmid: vmid
});
win.show();
win.on('destroy', reload);
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
selModel: sm,
disabled: true,
handler: run_editor
});
var resize_btn = new PVE.button.Button({
text: gettext('Resize disk'),
selModel: sm,
disabled: true,
handler: run_resize
});
var move_btn = new PVE.button.Button({
text: gettext('Move disk'),
selModel: sm,
disabled: true,
handler: run_move
});
var diskthrottle_btn = new PVE.button.Button({
text: gettext('Disk Throttle'),
selModel: sm,
disabled: true,
handler: run_diskthrottle
});
var cpuoptions_btn = new Ext.Button({
text: gettext('CPU options'),
handler: run_cpuoptions
});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
selModel: sm,
disabled: true,
dangerous: true,
confirmMsg: function(rec) {
var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + me.renderKey(rec.data.key, {}, rec) + "'");
if (rec.data.key.match(/^unused\d+$/)) {
msg += " " + gettext('This will permanently erase all data.');
}
return msg;
},
handler: function(b, e, rec) {
PVE.Utils.API2Request({
url: '/api2/extjs/' + baseurl,
waitMsgTarget: me,
method: 'PUT',
params: {
'delete': rec.data.key
},
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
}
});
var revert_btn = new PVE.button.Button({
text: gettext('Revert'),
selModel: sm,
disabled: true,
handler: function(b, e, rec) {
var rowdef = me.rows[rec.data.key] || {};
var keys = rowdef.multiKey || [ rec.data.key ];
var revert = keys.join(',');
PVE.Utils.API2Request({
url: '/api2/extjs/' + baseurl,
waitMsgTarget: me,
method: 'PUT',
params: {
'revert': revert
},
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert('Error',response.htmlStatus);
}
});
}
});
var set_button_status = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
remove_btn.disable();
edit_btn.disable();
resize_btn.disable();
move_btn.disable();
diskthrottle_btn.disable();
revert_btn.disable();
return;
}
var key = rec.data.key;
var value = rec.data.value;
var rowdef = rows[key];
var pending = rec.data['delete'] || me.hasPendingChanges(key);
var isDisk = !key.match(/^unused\d+/) &&
rowdef.tdCls == 'pve-itype-icon-storage' &&
(value && !value.match(/media=cdrom/));
remove_btn.setDisabled(rec.data['delete'] || (rowdef.never_delete === true));
edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor);
resize_btn.setDisabled(pending || !isDisk);
move_btn.setDisabled(pending || !isDisk);
diskthrottle_btn.setDisabled(pending || !isDisk);
revert_btn.setDisabled(!pending);
};
Ext.apply(me, {
url: '/api2/json/' + 'nodes/' + nodename + '/qemu/' + vmid + '/pending',
interval: 5000,
selModel: sm,
tbar: [
{
text: gettext('Add'),
menu: new Ext.menu.Menu({
items: [
{
text: gettext('Hard Disk'),
iconCls: 'pve-itype-icon-storage',
disabled: !caps.vms['VM.Config.Disk'],
handler: function() {
var win = Ext.create('PVE.qemu.HDEdit', {
url: '/api2/extjs/' + baseurl,
pveSelNode: me.pveSelNode
});
win.on('destroy', reload);
win.show();
}
},
{
text: gettext('CD/DVD Drive'),
iconCls: 'pve-itype-icon-cdrom',
disabled: !caps.vms['VM.Config.Disk'],
handler: function() {
var win = Ext.create('PVE.qemu.CDEdit', {
url: '/api2/extjs/' + baseurl,
pveSelNode: me.pveSelNode
});
win.on('destroy', reload);
win.show();
}
},
{
text: gettext('Network Device'),
iconCls: 'pve-itype-icon-network',
disabled: !caps.vms['VM.Config.Network'],
handler: function() {
var win = Ext.create('PVE.qemu.NetworkEdit', {
url: '/api2/extjs/' + baseurl,
pveSelNode: me.pveSelNode
});
win.on('destroy', reload);
win.show();
}
}
]
})
},
remove_btn,
edit_btn,
resize_btn,
move_btn,
diskthrottle_btn,
cpuoptions_btn,
revert_btn
],
rows: rows,
sorterFn: sorterFn,
listeners: {
itemdblclick: run_editor,
selectionchange: set_button_status
}
});
me.callParent();
me.on('activate', me.rstore.startUpdate);
me.on('hide', me.rstore.stopUpdate);
me.on('destroy', me.rstore.stopUpdate);
me.mon(me.rstore, 'refresh', function() {
set_button_status();
});
}
});
Ext.define('PVE.qemu.StartupInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
var res = PVE.Parser.printStartup(values);
if (res === undefined || res === '') {
return { 'delete': 'startup' };
}
return { startup: res };
},
setStartup: function(value) {
var me = this;
var startup = PVE.Parser.parseStartup(value);
if (startup) {
me.setValues(startup);
}
},
initComponent : function() {
var me = this;
me.items = [
{
xtype: 'textfield',
name: 'order',
defaultValue: '',
emptyText: 'any',
fieldLabel: gettext('Start/Shutdown order')
},
{
xtype: 'textfield',
name: 'up',
defaultValue: '',
emptyText: 'default',
fieldLabel: gettext('Startup delay')
},
{
xtype: 'textfield',
name: 'down',
defaultValue: '',
emptyText: 'default',
fieldLabel: gettext('Shutdown timeout')
}
];
me.callParent();
}
});
Ext.define('PVE.qemu.StartupEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
/*jslint confusion: true */
var me = this;
var ipanel = Ext.create('PVE.qemu.StartupInputPanel', {});
Ext.applyIf(me, {
subject: gettext('Start/Shutdown order'),
fieldDefaults: {
labelWidth: 120
},
items: ipanel
});
me.callParent();
me.load({
success: function(response, options) {
var i, confid;
me.vmconfig = response.result.data;
ipanel.setStartup(me.vmconfig.startup);
}
});
}
});
Ext.define('PVE.qemu.ScsiHwEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.applyIf(me, {
subject: gettext('SCSI Controller Type'),
items: {
xtype: 'pveScsiHwSelector',
name: 'scsihw',
value: '__default__',
fieldLabel: gettext('Type')
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.qemu.BiosEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.applyIf(me, {
subject: 'BIOS',
items: {
xtype: 'pveQemuBiosSelector',
name: 'bios',
value: '__default__',
fieldLabel: 'BIOS'
}
});
me.callParent();
me.load();
}
});
/*jslint confusion: true */
Ext.define('PVE.qemu.Options', {
extend: 'PVE.grid.PendingObjectGrid',
alias: ['widget.PVE.qemu.Options'],
initComponent : function() {
var me = this;
var i;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var caps = Ext.state.Manager.get('GuiCap');
var rows = {
name: {
required: true,
defaultValue: me.pveSelNode.data.name,
header: gettext('Name'),
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Name'),
items: {
xtype: 'textfield',
name: 'name',
vtype: 'DnsName',
value: '',
fieldLabel: gettext('Name'),
allowBlank: true
}
} : undefined
},
onboot: {
header: gettext('Start at boot'),
defaultValue: '',
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Start at boot'),
items: {
xtype: 'pvecheckbox',
name: 'onboot',
uncheckedValue: 0,
defaultValue: 0,
deleteDefaultValue: true,
fieldLabel: gettext('Start at boot')
}
} : undefined
},
startup: {
header: gettext('Start/Shutdown order'),
defaultValue: '',
renderer: PVE.Utils.render_kvm_startup,
editor: caps.vms['VM.Config.Options'] && caps.nodes['Sys.Modify'] ?
'PVE.qemu.StartupEdit' : undefined
},
ostype: {
header: gettext('OS Type'),
editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.OSTypeEdit' : undefined,
renderer: PVE.Utils.render_kvm_ostype,
defaultValue: 'other'
},
bootdisk: {
visible: false
},
boot: {
header: gettext('Boot order'),
defaultValue: 'cdn',
editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.BootOrderEdit' : undefined,
multiKey: ['boot', 'bootdisk'],
renderer: function(order, metaData, record, rowIndex, colIndex, store, pending) {
var i;
var text = '';
var bootdisk = me.getObjectValue('bootdisk', undefined, pending);
order = order || 'cdn';
for (i = 0; i < order.length; i++) {
var sel = order.substring(i, i + 1);
if (text) {
text += ', ';
}
if (sel === 'c') {
if (bootdisk) {
text += "Disk '" + bootdisk + "'";
} else {
text += "Disk";
}
} else if (sel === 'n') {
text += 'Network';
} else if (sel === 'a') {
text += 'Floppy';
} else if (sel === 'd') {
text += 'CD-ROM';
} else {
text += sel;
}
}
return text;
}
},
tablet: {
header: gettext('Use tablet for pointer'),
defaultValue: true,
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.HWType'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Use tablet for pointer'),
items: {
xtype: 'pvecheckbox',
name: 'tablet',
checked: true,
uncheckedValue: 0,
defaultValue: 1,
deleteDefaultValue: true,
fieldLabel: gettext('Enabled')
}
} : undefined
},
hotplug: {
header: gettext('Hotplug'),
defaultValue: 'disk,network,usb',
renderer: PVE.Utils.render_hotplug_features,
editor: caps.vms['VM.Config.HWType'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Hotplug'),
items: {
xtype: 'pveHotplugFeatureSelector',
name: 'hotplug',
value: '',
multiSelect: true,
fieldLabel: gettext('Hotplug'),
allowBlank: true
}
} : undefined
},
acpi: {
header: gettext('ACPI support'),
defaultValue: true,
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.HWType'] ? {
xtype: 'pveWindowEdit',
subject: gettext('ACPI support'),
items: {
xtype: 'pvecheckbox',
name: 'acpi',
checked: true,
uncheckedValue: 0,
defaultValue: 1,
deleteDefaultValue: true,
fieldLabel: gettext('Enabled')
}
} : undefined
},
scsihw: {
header: gettext('SCSI Controller Type'),
editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.ScsiHwEdit' : undefined,
renderer: PVE.Utils.render_scsihw,
defaultValue: ''
},
bios: {
header: 'BIOS',
editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.BiosEdit' : undefined,
renderer: PVE.Utils.render_qemu_bios,
defaultValue: ''
},
kvm: {
header: gettext('KVM hardware virtualization'),
defaultValue: true,
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.HWType'] ? {
xtype: 'pveWindowEdit',
subject: gettext('KVM hardware virtualization'),
items: {
xtype: 'pvecheckbox',
name: 'kvm',
checked: true,
uncheckedValue: 0,
defaultValue: 1,
deleteDefaultValue: true,
fieldLabel: gettext('Enabled')
}
} : undefined
},
freeze: {
header: gettext('Freeze CPU at startup'),
defaultValue: false,
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.PowerMgmt'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Freeze CPU at startup'),
items: {
xtype: 'pvecheckbox',
name: 'freeze',
uncheckedValue: 0,
defaultValue: 0,
deleteDefaultValue: true,
labelWidth: 140,
fieldLabel: gettext('Freeze CPU at startup')
}
} : undefined
},
localtime: {
header: gettext('Use local time for RTC'),
defaultValue: false,
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Use local time for RTC'),
items: {
xtype: 'pvecheckbox',
name: 'localtime',
uncheckedValue: 0,
defaultValue: 0,
deleteDefaultValue: true,
labelWidth: 140,
fieldLabel: gettext('Use local time for RTC')
}
} : undefined
},
startdate: {
header: gettext('RTC start date'),
defaultValue: 'now',
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('RTC start date'),
items: {
xtype: 'pvetextfield',
name: 'startdate',
deleteEmpty: true,
value: 'now',
fieldLabel: gettext('RTC start date'),
vtype: 'QemuStartDate',
allowBlank: true
}
} : undefined
},
smbios1: {
header: gettext('SMBIOS settings (type1)'),
defaultValue: '',
renderer: Ext.String.htmlEncode,
editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.Smbios1Edit' : undefined
},
agent: {
header: gettext('Qemu Agent'),
defaultValue: false,
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Qemu Agent'),
items: {
xtype: 'pvecheckbox',
name: 'agent',
uncheckedValue: 0,
defaultValue: 0,
deleteDefaultValue: true,
fieldLabel: gettext('Enabled')
}
} : undefined
},
protection: {
header: gettext('Protection'),
defaultValue: false,
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Protection'),
items: {
xtype: 'pvecheckbox',
name: 'protection',
uncheckedValue: 0,
defaultValue: 0,
deleteDefaultValue: true,
fieldLabel: gettext('Enabled')
}
} : undefined
}
};
var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
var reload = function() {
me.rstore.load();
};
var run_editor = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var rowdef = rows[rec.data.key];
if (!rowdef.editor) {
return;
}
var win;
if (Ext.isString(rowdef.editor)) {
win = Ext.create(rowdef.editor, {
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
});
} else {
var config = Ext.apply({
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
}, rowdef.editor);
win = Ext.createWidget(rowdef.editor.xtype, config);
win.load();
}
win.show();
win.on('destroy', reload);
};
var edit_btn = new Ext.Button({
text: gettext('Edit'),
disabled: true,
handler: run_editor
});
var revert_btn = new PVE.button.Button({
text: gettext('Revert'),
disabled: true,
handler: function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var rowdef = me.rows[rec.data.key] || {};
var keys = rowdef.multiKey || [ rec.data.key ];
var revert = keys.join(',');
PVE.Utils.API2Request({
url: '/api2/extjs/' + baseurl,
waitMsgTarget: me,
method: 'PUT',
params: {
'revert': revert
},
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert('Error',response.htmlStatus);
}
});
}
});
var set_button_status = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
edit_btn.disable();
return;
}
var key = rec.data.key;
var pending = rec.data['delete'] || me.hasPendingChanges(key);
var rowdef = rows[key];
edit_btn.setDisabled(!rowdef.editor);
revert_btn.setDisabled(!pending);
};
Ext.apply(me, {
url: "/api2/json/nodes/" + nodename + "/qemu/" + vmid + "/pending",
interval: 5000,
cwidth1: 250,
tbar: [ edit_btn, revert_btn ],
rows: rows,
listeners: {
itemdblclick: run_editor,
selectionchange: set_button_status
}
});
me.callParent();
me.on('activate', me.rstore.startUpdate);
me.on('hide', me.rstore.stopUpdate);
me.on('destroy', me.rstore.stopUpdate);
me.rstore.on('datachanged', function() {
set_button_status();
});
}
});
Ext.define('PVE.window.Snapshot', {
extend: 'Ext.window.Window',
resizable: false,
take_snapshot: function(snapname, descr, vmstate) {
var me = this;
var params = { snapname: snapname, vmstate: vmstate ? 1 : 0 };
if (descr) {
params.description = descr;
}
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/snapshot",
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', { upid: upid });
win.show();
me.close();
}
});
},
update_snapshot: function(snapname, descr) {
var me = this;
PVE.Utils.API2Request({
params: { description: descr },
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/snapshot/" +
snapname + '/config',
waitMsgTarget: me,
method: 'PUT',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
me.close();
}
});
},
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var summarystore = Ext.create('Ext.data.Store', {
model: 'KeyValue',
sorters: [
{
property : 'key',
direction: 'ASC'
}
]
});
var items = [
{
xtype: me.snapname ? 'displayfield' : 'textfield',
name: 'snapname',
value: me.snapname,
fieldLabel: gettext('Name'),
vtype: 'ConfigId',
allowBlank: false
}
];
if (me.snapname) {
items.push({
xtype: 'displayfield',
name: 'snaptime',
fieldLabel: gettext('Timestamp')
});
} else {
items.push({
xtype: 'pvecheckbox',
name: 'vmstate',
uncheckedValue: 0,
defaultValue: 0,
checked: 1,
fieldLabel: gettext('Include RAM')
});
}
items.push({
xtype: 'textareafield',
grow: true,
name: 'description',
fieldLabel: gettext('Description')
});
if (me.snapname) {
items.push({
title: gettext('Settings'),
xtype: 'grid',
height: 200,
store: summarystore,
columns: [
{header: gettext('Key'), width: 150, dataIndex: 'key'},
{header: gettext('Value'), flex: 1, dataIndex: 'value'}
]
});
}
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: items
});
var form = me.formPanel.getForm();
var submitBtn;
if (me.snapname) {
me.title = gettext('Edit') + ': ' + gettext('Snapshot');
submitBtn = Ext.create('Ext.Button', {
text: gettext('Update'),
handler: function() {
if (form.isValid()) {
var values = form.getValues();
me.update_snapshot(me.snapname, values.description);
}
}
});
} else {
me.title ="VM " + me.vmid + ': ' + gettext('Take Snapshot');
submitBtn = Ext.create('Ext.Button', {
text: gettext('Take Snapshot'),
handler: function() {
if (form.isValid()) {
var values = form.getValues();
me.take_snapshot(values.snapname, values.description, values.vmstate);
}
}
});
}
Ext.apply(me, {
modal: true,
width: 450,
border: false,
layout: 'fit',
buttons: [ submitBtn ],
items: [ me.formPanel ]
});
if (me.snapname) {
Ext.apply(me, {
width: 620,
height: 420
});
}
me.callParent();
if (!me.snapname) {
return;
}
// else load data
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + "/snapshot/" +
me.snapname + '/config',
waitMsgTarget: me,
method: 'GET',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
me.close();
},
success: function(response, options) {
var data = response.result.data;
var kvarray = [];
Ext.Object.each(data, function(key, value) {
if (key === 'description' || key === 'snaptime') {
return;
}
kvarray.push({ key: key, value: value });
});
summarystore.suspendEvents();
summarystore.add(kvarray);
summarystore.sort();
summarystore.resumeEvents();
summarystore.fireEvent('refresh', summarystore);
form.findField('snaptime').setValue(new Date(data.snaptime));
form.findField('description').setValue(data.description);
}
});
}
});
Ext.define('PVE.window.Clone', {
extend: 'Ext.window.Window',
resizable: false,
isTemplate: false,
create_clone: function(values) {
var me = this;
var params = { newid: values.newvmid };
if (values.snapname && values.snapname !== 'current') {
params.snapname = values.snapname;
}
if (values.pool) {
params.pool = values.pool;
}
if (values.name) {
params.name = values.name;
}
if (values.target) {
params.target = values.target;
}
if (values.clonemode === 'copy') {
params.full = 1;
if (values.storage) {
params.storage = values.storage;
if (values.diskformat) {
params.format = values.diskformat;
}
}
}
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/clone',
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response, options) {
me.close();
}
});
},
updateVisibility: function() {
var me = this;
var clonemode = me.kv1.getValue();
var storage = me.hdstoragesel.getValue();
var rec = me.hdstoragesel.store.getById(storage);
me.hdstoragesel.setDisabled(clonemode === 'clone');
if (!rec || clonemode === 'clone') {
me.formatsel.setDisabled(true);
return;
}
if (rec.data.type === 'lvm' ||
rec.data.type === 'lvmthin' ||
rec.data.type === 'rbd' ||
rec.data.type === 'iscsi' ||
rec.data.type === 'sheepdog' ||
rec.data.type === 'zfs' ||
rec.data.type === 'zfspool'
) {
me.formatsel.setValue('raw');
me.formatsel.setDisabled(true);
} else {
me.formatsel.setDisabled(false);
}
},
verifyFeature: function() {
var me = this;
var snapname = me.snapshotSel.getValue();
var clonemode = me.kv1.getValue();
var params = { feature: clonemode };
if (snapname !== 'current') {
params.snapname = snapname;
}
PVE.Utils.API2Request({
waitMsgTarget: me,
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/feature',
params: params,
method: 'GET',
failure: function(response, opts) {
me.submitBtn.setDisabled(false);
Ext.Msg.alert('Error', response.htmlStatus);
},
success: function(response, options) {
var res = response.result.data;
me.submitBtn.setDisabled(res.hasFeature !== 1);
me.targetSel.allowedNodes = res.nodes;
me.targetSel.validate();
}
});
},
initComponent : function() {
/*jslint confusion: true */
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
if (!me.snapname) {
me.snapname = 'current';
}
var col1 = [];
var col2 = [];
me.targetSel = Ext.create('PVE.form.NodeSelector', {
name: 'target',
fieldLabel: gettext('Target node'),
selectCurNode: true,
allowBlank: false,
onlineValidator: true
});
col1.push(me.targetSel);
var modelist = [['copy', gettext('Full Clone')]];
if (me.isTemplate) {
modelist.push(['clone', gettext('Linked Clone')]);
}
me.kv1 = Ext.create('PVE.form.KVComboBox', {
fieldLabel: gettext('Mode'),
name: 'clonemode',
allowBlank: false,
value: me.isTemplate ? 'clone' : 'copy',
comboItems: modelist
});
me.mon(me.kv1, 'change', function(t, value) {
me.updateVisibility();
me.verifyFeature();
});
col2.push(me.kv1);
me.snapshotSel = Ext.create('PVE.form.SnapshotSelector', {
name: 'snapname',
fieldLabel: gettext('Snapshot'),
nodename: me.nodename,
vmid: me.vmid,
hidden: me.isTemplate ? true : false,
disabled: false,
allowBlank: false,
value : me.snapname,
listeners: {
change: function(f, value) {
me.verifyFeature();
}
}
});
col2.push(me.snapshotSel);
col1.push(
{
xtype: 'pveVMIDSelector',
name: 'newvmid',
value: '',
loadNextFreeVMID: true,
validateExists: false
},
{
xtype: 'textfield',
name: 'name',
allowBlank: true,
fieldLabel: gettext('Name')
}
);
me.hdstoragesel = Ext.create('PVE.form.StorageSelector', {
name: 'storage',
nodename: me.nodename,
fieldLabel: gettext('Target Storage'),
storageContent: 'images',
autoSelect: me.insideWizard,
allowBlank: true,
disabled: me.isTemplate ? true : false, // because default mode is clone for templates
hidden: false,
listeners: {
change: function(f, value) {
me.updateVisibility();
}
}
});
me.targetSel.on('change', function(f, value) {
me.hdstoragesel.setTargetNode(value);
});
me.formatsel = Ext.create('PVE.form.DiskFormatSelector', {
name: 'diskformat',
fieldLabel: gettext('Format'),
value: 'raw',
disabled: true,
hidden: false,
allowBlank: false
});
col2.push({
xtype: 'pvePoolSelector',
fieldLabel: gettext('Resource Pool'),
name: 'pool',
value: '',
allowBlank: true
});
col2.push(me.hdstoragesel);
col2.push(me.formatsel);
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
layout: 'column',
defaultType: 'container',
columns: 2,
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: [
{
columnWidth: 0.5,
padding: '0 10 0 0',
layout: 'anchor',
items: col1
},
{
columnWidth: 0.5,
padding: '0 0 0 10',
layout: 'anchor',
items: col2
}
]
});
var form = me.formPanel.getForm();
var titletext = me.isTemplate ? "Template" : "VM";
me.title = "Clone " + titletext + " " + me.vmid;
me.submitBtn = Ext.create('Ext.Button', {
text: gettext('Clone'),
disabled: true,
handler: function() {
if (form.isValid()) {
var values = form.getValues();
me.create_clone(values);
}
}
});
Ext.apply(me, {
modal: true,
width: 600,
height: 250,
border: false,
layout: 'fit',
buttons: [ me.submitBtn ],
items: [ me.formPanel ]
});
me.callParent();
me.verifyFeature();
}
});
Ext.define('PVE.qemu.SnapshotTree', {
extend: 'Ext.tree.Panel',
alias: ['widget.pveQemuSnapshotTree'],
load_delay: 3000,
old_digest: 'invalid',
sorterFn: function(rec1, rec2) {
var v1 = rec1.data.snaptime;
var v2 = rec2.data.snaptime;
if (rec1.data.name === 'current') {
return 1;
}
if (rec2.data.name === 'current') {
return -1;
}
return (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
},
reload: function(repeat) {
var me = this;
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/snapshot',
method: 'GET',
failure: function(response, opts) {
PVE.Utils.setErrorMask(me, response.htmlStatus);
me.load_task.delay(me.load_delay);
},
success: function(response, opts) {
PVE.Utils.setErrorMask(me, false);
var digest = 'invalid';
var idhash = {};
var root = { name: '__root', expanded: true, children: [] };
Ext.Array.each(response.result.data, function(item) {
item.leaf = true;
item.children = [];
if (item.name === 'current') {
digest = item.digest + item.running;
if (item.running) {
item.iconCls = 'fa fa-fw fa-desktop x-fa-tree-running';
} else {
item.iconCls = 'fa fa-fw fa-desktop x-fa-tree';
}
} else {
item.iconCls = 'fa fa-fw fa-history x-fa-tree';
}
idhash[item.name] = item;
});
if (digest !== me.old_digest) {
me.old_digest = digest;
Ext.Array.each(response.result.data, function(item) {
if (item.parent && idhash[item.parent]) {
var parent_item = idhash[item.parent];
parent_item.children.push(item);
parent_item.leaf = false;
parent_item.expanded = true;
parent_item.expandable = false;
} else {
root.children.push(item);
}
});
me.setRootNode(root);
}
me.load_task.delay(me.load_delay);
}
});
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/feature',
params: { feature: 'snapshot' },
method: 'GET',
success: function(response, options) {
var res = response.result.data;
if (res.hasFeature) {
var snpBtns = Ext.ComponentQuery.query('#snapshotBtn');
snpBtns.forEach(function(item){
item.enable();
});
}
}
});
},
initComponent: function() {
var me = this;
me.nodename = me.pveSelNode.data.node;
if (!me.nodename) {
throw "no node name specified";
}
me.vmid = me.pveSelNode.data.vmid;
if (!me.vmid) {
throw "no VM ID specified";
}
me.load_task = new Ext.util.DelayedTask(me.reload, me);
var sm = Ext.create('Ext.selection.RowModel', {});
var valid_snapshot = function(record) {
return record && record.data && record.data.name &&
record.data.name !== 'current';
};
var valid_snapshot_rollback = function(record) {
return record && record.data && record.data.name &&
record.data.name !== 'current' && !record.data.snapstate;
};
var run_editor = function() {
var rec = sm.getSelection()[0];
if (valid_snapshot(rec)) {
var win = Ext.create('PVE.window.Snapshot', {
snapname: rec.data.name,
nodename: me.nodename,
vmid: me.vmid
});
win.show();
me.mon(win, 'close', me.reload, me);
}
};
var editBtn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
enableFn: valid_snapshot,
handler: run_editor
});
var rollbackBtn = new PVE.button.Button({
text: gettext('Rollback'),
disabled: true,
selModel: sm,
enableFn: valid_snapshot_rollback,
confirmMsg: function(rec) {
return PVE.Utils.format_task_description('qmrollback', me.vmid) +
" '" + rec.data.name + "'";
},
handler: function(btn, event) {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var snapname = rec.data.name;
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/snapshot/' + snapname + '/rollback',
method: 'POST',
waitMsgTarget: me,
callback: function() {
me.reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', { upid: upid });
win.show();
}
});
}
});
var removeBtn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
confirmMsg: function(rec) {
var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.name + "'");
return msg;
},
enableFn: valid_snapshot,
handler: function(btn, event) {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var snapname = rec.data.name;
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/snapshot/' + snapname,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
me.reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', { upid: upid });
win.show();
}
});
}
});
var snapshotBtn = Ext.create('Ext.Button', {
itemId: 'snapshotBtn',
text: gettext('Take Snapshot'),
disabled: true,
handler: function() {
var win = Ext.create('PVE.window.Snapshot', {
nodename: me.nodename,
vmid: me.vmid
});
win.show();
}
});
Ext.apply(me, {
layout: 'fit',
rootVisible: false,
animate: false,
sortableColumns: false,
selModel: sm,
tbar: [ snapshotBtn, rollbackBtn, removeBtn, editBtn ],
fields: [
'name', 'description', 'snapstate', 'vmstate', 'running',
{ name: 'snaptime', type: 'date', dateFormat: 'timestamp' }
],
columns: [
{
xtype: 'treecolumn',
text: gettext('Name'),
dataIndex: 'name',
width: 200,
renderer: function(value, metaData, record) {
if (value === 'current') {
return "NOW";
} else {
return value;
}
}
},
{
text: gettext('RAM'),
align: 'center',
resizable: false,
dataIndex: 'vmstate',
width: 50,
renderer: function(value, metaData, record) {
if (record.data.name !== 'current') {
return PVE.Utils.format_boolean(value);
}
}
},
{
text: gettext('Date') + "/" + gettext("Status"),
dataIndex: 'snaptime',
resizable: false,
width: 120,
renderer: function(value, metaData, record) {
if (record.data.snapstate) {
return record.data.snapstate;
}
if (value) {
return Ext.Date.format(value,'Y-m-d H:i:s');
}
}
},
{
text: gettext('Description'),
dataIndex: 'description',
flex: 1,
renderer: function(value, metaData, record) {
if (record.data.name === 'current') {
return gettext("You are here!");
} else {
return Ext.String.htmlEncode(value);
}
}
}
],
columnLines: true, // will work in 4.1?
listeners: {
activate: me.reload,
hide: me.load_task.cancel,
destroy: me.load_task.cancel,
itemdblclick: run_editor
}
});
me.callParent();
me.store.sorters.add(new Ext.util.Sorter({
sorterFn: me.sorterFn
}));
}
});
Ext.define('PVE.qemu.Config', {
extend: 'PVE.panel.Config',
alias: 'widget.PVE.qemu.Config',
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var template = me.pveSelNode.data.template;
var caps = Ext.state.Manager.get('GuiCap');
var base_url = '/nodes/' + nodename + "/qemu/" + vmid;
me.statusStore = Ext.create('PVE.data.ObjectStore', {
url: '/api2/json' + base_url + '/status/current',
interval: 1000
});
var vm_command = function(cmd, params) {
PVE.Utils.API2Request({
params: params,
url: base_url + '/status/' + cmd,
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
};
var resumeBtn = Ext.create('Ext.Button', {
text: gettext('Resume'),
disabled: !caps.vms['VM.PowerMgmt'],
hidden: true,
handler: function() {
vm_command('resume');
},
iconCls: 'fa fa-play'
});
var startBtn = Ext.create('Ext.Button', {
text: gettext('Start'),
disabled: !caps.vms['VM.PowerMgmt'],
handler: function() {
vm_command('start');
},
iconCls: 'fa fa-play'
});
var migrateBtn = Ext.create('Ext.Button', {
text: gettext('Migrate'),
disabled: !caps.vms['VM.Migrate'],
handler: function() {
var win = Ext.create('PVE.window.Migrate', {
vmtype: 'qemu',
nodename: nodename,
vmid: vmid
});
win.show();
},
iconCls: 'fa fa-send-o'
});
var resetBtn = Ext.create('PVE.button.Button', {
text: gettext('Reset'),
disabled: !caps.vms['VM.PowerMgmt'],
confirmMsg: PVE.Utils.format_task_description('qmreset', vmid),
handler: function() {
vm_command("reset");
},
iconCls: 'fa fa-bolt'
});
var shutdownBtn = Ext.create('PVE.button.Split', {
text: gettext('Shutdown'),
disabled: !caps.vms['VM.PowerMgmt'],
confirmMsg: PVE.Utils.format_task_description('qmshutdown', vmid),
handler: function() {
vm_command('shutdown');
},
menu: {
items: [{
text: gettext('Stop'),
disabled: !caps.vms['VM.PowerMgmt'],
dangerous: true,
confirmMsg: PVE.Utils.format_task_description('qmstop', vmid),
handler: function() {
vm_command("stop", { timeout: 30 });
},
iconCls: 'fa fa-stop'
}]
},
iconCls: 'fa fa-power-off'
});
var removeBtn = Ext.create('PVE.button.Button', {
text: gettext('Remove'),
disabled: !caps.vms['VM.Allocate'],
handler: function() {
Ext.create('PVE.window.SafeDestroy', {
url: base_url,
item: { type: 'VM', id: vmid }
}).show();
},
iconCls: 'fa fa-trash-o'
});
var vmname = me.pveSelNode.data.name;
var consoleBtn = Ext.create('PVE.button.ConsoleButton', {
disabled: !caps.vms['VM.Console'],
consoleType: 'kvm',
consoleName: vmname,
nodename: nodename,
vmid: vmid,
iconCls: 'fa fa-terminal'
});
var descr = vmid + " (" + (vmname ? "'" + vmname + "' " : "'VM " + vmid + "'") + ")";
Ext.apply(me, {
title: Ext.String.format(gettext("Virtual Machine {0} on node {1}"), descr, "'" + nodename + "'"),
hstateid: 'kvmtab',
tbar: [ resumeBtn, startBtn, shutdownBtn, resetBtn,
removeBtn, migrateBtn, consoleBtn],
defaults: { statusStore: me.statusStore },
items: [
{
title: gettext('Summary'),
xtype: 'pveQemuSummary',
itemId: 'summary'
},
{
title: gettext('Hardware'),
itemId: 'hardware',
xtype: 'PVE.qemu.HardwareView'
},
{
title: gettext('Options'),
itemId: 'options',
xtype: 'PVE.qemu.Options'
},
{
title: gettext('Task History'),
itemId: 'tasks',
xtype: 'pveNodeTasks',
vmidFilter: vmid
}
]
});
if (caps.vms['VM.Monitor'] && !template) {
me.items.push({
title: gettext('Monitor'),
itemId: 'monitor',
xtype: 'pveQemuMonitor'
});
}
if (caps.vms['VM.Backup']) {
me.items.push({
title: gettext('Backup'),
xtype: 'pveBackupView',
itemId: 'backup'
});
}
if (caps.vms['VM.Snapshot'] && !template) {
me.items.push({
title: gettext('Snapshots'),
xtype: 'pveQemuSnapshotTree',
itemId: 'snapshot'
});
}
if (caps.vms['VM.Console'] && !template) {
me.items.push({
title: gettext('Console'),
itemId: 'console',
xtype: 'pveNoVncConsole',
vmid: vmid,
consoleType: 'kvm',
nodename: nodename
});
}
if (caps.vms['VM.Console']) {
me.items.push(
{
xtype: 'pveFirewallPanel',
title: gettext('Firewall'),
base_url: base_url + '/firewall',
fwtype: 'vm',
phstateid: me.hstateid,
itemId: 'firewall'
}
);
}
if (caps.vms['Permissions.Modify']) {
me.items.push({
xtype: 'pveACLView',
title: gettext('Permissions'),
itemId: 'permissions',
path: '/vms/' + vmid
});
}
me.callParent();
me.mon(me.statusStore, 'load', function(s, records, success) {
var status;
var qmpstatus;
var spice = false;
if (!success) {
me.workspace.checkVmMigration(me.pveSelNode);
status = qmpstatus = 'unknown';
} else {
var rec = s.data.get('status');
status = rec ? rec.data.value : 'unknown';
rec = s.data.get('qmpstatus');
qmpstatus = rec ? rec.data.value : 'unknown';
rec = s.data.get('template');
template = rec.data.value || false;
spice = s.data.get('spice') ? true : false;
}
if (qmpstatus === 'prelaunch' || qmpstatus === 'paused') {
startBtn.setVisible(false);
resumeBtn.setVisible(true);
} else {
startBtn.setVisible(true);
resumeBtn.setVisible(false);
}
consoleBtn.setEnableSpice(spice);
startBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'running' || template);
resetBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status !== 'running' || template);
shutdownBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status !== 'running');
removeBtn.setDisabled(!caps.vms['VM.Allocate'] || status !== 'stopped');
consoleBtn.setDisabled(template);
});
me.on('afterrender', function() {
me.statusStore.startUpdate();
});
me.on('destroy', function() {
me.statusStore.stopUpdate();
});
}
});
// fixme: howto avoid jslint type confusion?
/*jslint confusion: true */
Ext.define('PVE.qemu.CreateWizard', {
extend: 'PVE.window.Wizard',
initComponent: function() {
var me = this;
var summarystore = Ext.create('Ext.data.Store', {
model: 'KeyValue',
sorters: [
{
property : 'key',
direction: 'ASC'
}
]
});
var cdpanel = Ext.create('PVE.qemu.CDInputPanel', {
title: gettext('CD/DVD'),
confid: 'ide2',
fieldDefaults: {
labelWidth: 160
},
insideWizard: true
});
var hdpanel = Ext.create('PVE.qemu.HDInputPanel', {
title: gettext('Hard Disk'),
create: true,
insideWizard: true
});
var networkpanel = Ext.create('PVE.qemu.NetworkInputPanel', {
title: gettext('Network'),
insideWizard: true
});
Ext.applyIf(me, {
subject: gettext('Virtual Machine'),
items: [
{
xtype: 'inputpanel',
title: gettext('General'),
onlineHelp: 'chapter-qm.html#_general_settings',
column1: [
{
xtype: 'pveNodeSelector',
name: 'nodename',
selectCurNode: true,
fieldLabel: gettext('Node'),
allowBlank: false,
onlineValidator: true,
listeners: {
change: function(f, value) {
networkpanel.setNodename(value);
hdpanel.setNodename(value);
cdpanel.setNodename(value);
}
}
},
{
xtype: 'pveVMIDSelector',
name: 'vmid',
value: '',
loadNextFreeVMID: true,
validateExists: false
},
{
xtype: 'textfield',
name: 'name',
vtype: 'DnsName',
value: '',
fieldLabel: gettext('Name'),
allowBlank: true
}
],
column2: [
{
xtype: 'pvePoolSelector',
fieldLabel: gettext('Resource Pool'),
name: 'pool',
value: '',
allowBlank: true
}
],
onGetValues: function(values) {
if (!values.name) {
delete values.name;
}
if (!values.pool) {
delete values.pool;
}
return values;
}
},
{
title: gettext('OS'),
xtype: 'PVE.qemu.OSTypeInputPanel'
},
cdpanel,
hdpanel,
{
xtype: 'PVE.qemu.ProcessorInputPanel',
title: gettext('CPU')
},
{
xtype: 'PVE.qemu.MemoryInputPanel',
insideWizard: true,
title: gettext('Memory')
},
networkpanel,
{
title: gettext('Confirm'),
layout: 'fit',
items: [
{
title: gettext('Settings'),
xtype: 'grid',
store: summarystore,
columns: [
{header: 'Key', width: 150, dataIndex: 'key'},
{header: 'Value', flex: 1, dataIndex: 'value'}
]
}
],
listeners: {
show: function(panel) {
var form = me.down('form').getForm();
var kv = me.getValues();
var data = [];
Ext.Object.each(kv, function(key, value) {
if (key === 'delete') { // ignore
return;
}
var html = Ext.htmlEncode(Ext.JSON.encode(value));
data.push({ key: key, value: value });
});
summarystore.suspendEvents();
summarystore.removeAll();
summarystore.add(data);
summarystore.sort();
summarystore.resumeEvents();
summarystore.fireEvent('datachanged', summarystore);
}
},
onSubmit: function() {
var kv = me.getValues();
delete kv['delete'];
var nodename = kv.nodename;
delete kv.nodename;
PVE.Utils.API2Request({
url: '/nodes/' + nodename + '/qemu',
waitMsgTarget: me,
method: 'POST',
params: kv,
success: function(response){
me.close();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
}
]
});
me.callParent();
}
});
Ext.define('PVE.lxc.StatusView', {
extend: 'PVE.grid.ObjectGrid',
disabled: true,
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var template = !!me.pveSelNode.data.template;
var render_cpu = function(value, metaData, record, rowIndex, colIndex, store) {
if (!me.getObjectValue('uptime')) {
return '-';
}
var maxcpu = me.getObjectValue('cpus', 1);
if (!(Ext.isNumeric(value) && Ext.isNumeric(maxcpu) && (maxcpu >= 1))) {
return '-';
}
var cpu = value * 100;
return cpu.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
};
var render_mem = function(value, metaData, record, rowIndex, colIndex, store) {
var maxmem = me.getObjectValue('maxmem', 0);
var per = (value / maxmem)*100;
var text = "<div>" + PVE.Utils.totalText + ": " + PVE.Utils.format_size(maxmem) + "</div>" +
"<div>" + PVE.Utils.usedText + ": " + PVE.Utils.format_size(value) + "</div>";
return text;
};
var render_swap = function(value, metaData, record, rowIndex, colIndex, store) {
var maxswap = me.getObjectValue('maxswap', 0);
var per = (value / maxswap)*100;
var text = "<div>" + PVE.Utils.totalText + ": " + PVE.Utils.format_size(maxswap) + "</div>" +
"<div>" + PVE.Utils.usedText + ": " + PVE.Utils.format_size(value) + "</div>";
return text;
};
var render_status = function(value, metaData, record, rowIndex, colIndex, store) {
var failcnt = me.getObjectValue('failcnt', 0);
if (failcnt > 0) {
return value + " (failure count " + failcnt.toString() + ")";
}
return value;
};
var rows = {};
if (template) {
rows = {
name: { header: gettext('Name'), defaultValue: 'no name specified' },
cpus: { header: gettext('CPU limit'), required: true},
maxmem: { header: gettext('Memory'), required: true, renderer: PVE.Utils.render_size },
maxswap: { header: gettext('VSwap'), required: true, renderer: PVE.Utils.render_size },
maxdisk: { header: gettext('Bootdisk size'), renderer: PVE.Utils.render_size, required: true}
};
} else {
rows = {
name: { header: gettext('Name'), defaultValue: 'no name specified' },
status: { header: gettext('Status'), defaultValue: 'unknown', renderer: render_status },
failcnt: { visible: false },
cpu: { header: gettext('CPU usage'), required: true, renderer: render_cpu },
cpus: { visible: false },
mem: { header: gettext('Memory usage'), required: true, renderer: render_mem },
maxmem: { visible: false },
swap: { header: gettext('VSwap usage'), required: true, renderer: render_swap },
maxswap: { visible: false },
maxdisk: { header: gettext('Bootdisk size'), renderer: PVE.Utils.render_size, required: true},
uptime: { header: gettext('Uptime'), required: true, renderer: PVE.Utils.render_uptime },
ha: { header: gettext('Managed by HA'), required: true, renderer: PVE.Utils.format_ha }
};
}
Ext.applyIf(me, {
cwidth1: 150,
rows: rows
});
me.callParent();
}
});
Ext.define('PVE.lxc.Summary', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveLxcSummary',
scrollable: true,
bodyPadding: 10,
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
if (!me.workspace) {
throw "no workspace specified";
}
if (!me.statusStore) {
throw "no status storage specified";
}
var template = !!me.pveSelNode.data.template;
var rstore = me.statusStore;
var statusview = Ext.create('PVE.lxc.StatusView', {
title: gettext('Status'),
pveSelNode: me.pveSelNode,
width: template ? 800 : 400,
rstore: rstore
});
var notesview = Ext.create('PVE.panel.NotesView', {
pveSelNode: me.pveSelNode,
padding: template ? '10 0 0 0' : '0 0 0 10',
flex: 1
});
if (template) {
Ext.apply(me, {
plugins: {
ptype: 'lazyitems',
items: [{
xtype: 'container',
layout: {
type: 'column'
},
defaults: {
padding: '0 10 10 0'
},
items: [{
width: 800,
layout: {
type: 'vbox',
align: 'stretch'
},
border: false,
items: [ statusview, notesview ]
}]
}]
},
listeners: {
activate: function() { notesview.load(); }
}
});
} else {
var rrdstore = Ext.create('PVE.data.RRDStore', {
rrdurl: "/api2/json/nodes/" + nodename + "/lxc/" + vmid + "/rrddata"
});
Ext.apply(me, {
tbar: [ '->' , { xtype: 'pveRRDTypeSelector' } ],
plugins: {
ptype: 'lazyitems',
items: [
{
xtype: 'container',
layout: {
type: 'column'
},
defaults: {
padding: '0 10 10 0'
},
items: [
{
width: 800,
height: 300,
layout: {
type: 'hbox',
align: 'stretch'
},
border: false,
items: [ statusview, notesview ]
},
{
xtype: 'pveRRDChart',
title: gettext('CPU usage'),
pveSelNode: me.pveSelNode,
fields: ['cpu'],
fieldTitles: [gettext('CPU usage')],
store: rrdstore
},
{
xtype: 'pveRRDChart',
title: gettext('Memory usage'),
pveSelNode: me.pveSelNode,
fields: ['maxmem', 'mem'],
fieldTitles: [gettext('Total'), gettext('RAM usage')],
store: rrdstore
},
{
xtype: 'pveRRDChart',
title: gettext('Network traffic'),
pveSelNode: me.pveSelNode,
fields: ['netin','netout'],
store: rrdstore
},
{
xtype: 'pveRRDChart',
title: gettext('Disk IO'),
pveSelNode: me.pveSelNode,
fields: ['diskread','diskwrite'],
store: rrdstore
}
]
}
]
},
listeners: {
activate: function() { notesview.load(); rrdstore.startUpdate(); },
hide: rrdstore.stopUpdate,
destroy: rrdstore.stopUpdate
}
});
}
me.callParent();
}
});
Ext.define('PVE.lxc.NetworkInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.pveLxcNetworkInputPanel',
insideWizard: false,
setNodename: function(nodename) {
var me = this;
if (!nodename || (me.nodename === nodename)) {
return;
}
me.nodename = nodename;
var bridgesel = me.query("[isFormField][name=bridge]")[0];
bridgesel.setNodename(nodename);
},
onGetValues: function(values) {
var me = this;
var id;
if (me.create) {
id = values.id;
delete values.id;
} else {
id = me.ifname;
}
if (!id) {
return {};
}
var newdata = {};
if (values.ipv6mode !== 'static') {
values.ip6 = values.ipv6mode;
}
if (values.ipv4mode !== 'static') {
values.ip = values.ipv4mode;
}
newdata[id] = PVE.Parser.printLxcNetwork(values);
return newdata;
},
initComponent : function() {
var me = this;
if (!me.dataCache) {
throw "no dataCache specified";
}
var cdata = {};
if (me.insideWizard) {
me.ifname = 'net0';
cdata.name = 'eth0';
}
if (!me.create) {
if (!me.ifname) {
throw "no interface name specified";
}
if (!me.dataCache[me.ifname]) {
throw "no such interface '" + me.ifname + "'";
}
cdata = PVE.Parser.parseLxcNetwork(me.dataCache[me.ifname]);
}
var i;
for (i = 0; i < 10; i++) {
if (me.create && !me.dataCache['net'+i.toString()]) {
me.ifname = 'net' + i.toString();
break;
}
}
var idselector = {
xtype: 'hidden',
name: 'id',
value: me.ifname
};
me.column1 = [
idselector,
{
xtype: 'textfield',
name: 'name',
fieldLabel: gettext('Name') + ' (i.e. eth0)',
allowBlank: false,
value: cdata.name,
validator: function(value) {
var result = '';
Ext.Object.each(me.dataCache, function(key, netstr) {
if (!key.match(/^net\d+/) || key === me.ifname) {
return; // continue
}
var net = PVE.Parser.parseLxcNetwork(netstr);
if (net.name === value) {
result = "interface name already in use";
return false;
}
});
if (result !== '') {
return result;
}
// validator can return bool/string
/*jslint confusion:true*/
return true;
}
},
{
xtype: 'textfield',
name: 'hwaddr',
fieldLabel: gettext('MAC address'),
vtype: 'MacAddress',
value: cdata.hwaddr,
allowBlank: true,
emptyText: 'auto'
},
{
xtype: 'PVE.form.BridgeSelector',
name: 'bridge',
nodename: me.nodename,
fieldLabel: gettext('Bridge'),
value: cdata.bridge,
allowBlank: false
},
{
xtype: 'pveVlanField',
name: 'tag',
value: cdata.tag
},
{
xtype: 'numberfield',
name: 'rate',
fieldLabel: gettext('Rate limit') + ' (MB/s)',
minValue: 0,
maxValue: 10*1024,
value: cdata.rate,
emptyText: 'unlimited',
allowBlank: true
},
{
xtype: 'pvecheckbox',
fieldLabel: gettext('Firewall'),
name: 'firewall',
checked: cdata.firewall
}
];
var dhcp4 = (cdata.ip === 'dhcp');
if (dhcp4) {
cdata.ip = '';
cdata.gw = '';
}
var auto6 = (cdata.ip6 === 'auto');
var dhcp6 = (cdata.ip6 === 'dhcp');
if (auto6 || dhcp6) {
cdata.ip6 = '';
cdata.gw6 = '';
}
me.column2 = [
{
layout: {
type: 'hbox',
align: 'middle'
},
border: false,
margin: '0 0 5 0',
items: [
{
xtype: 'label',
text: gettext('IPv4') + ':'
},
{
xtype: 'radiofield',
boxLabel: gettext('Static'),
name: 'ipv4mode',
inputValue: 'static',
checked: !dhcp4,
margin: '0 0 0 10',
listeners: {
change: function(cb, value) {
me.down('field[name=ip]').setDisabled(!value);
me.down('field[name=gw]').setDisabled(!value);
}
}
},
{
xtype: 'radiofield',
boxLabel: gettext('DHCP'),
name: 'ipv4mode',
inputValue: 'dhcp',
checked: dhcp4,
margin: '0 0 0 10'
}
]
},
{
xtype: 'textfield',
name: 'ip',
vtype: 'IPCIDRAddress',
value: cdata.ip,
disabled: dhcp4,
fieldLabel: gettext('IPv4/CIDR')
},
{
xtype: 'textfield',
name: 'gw',
value: cdata.gw,
vtype: 'IPAddress',
disabled: dhcp4,
fieldLabel: gettext('Gateway') + ' (' + gettext('IPv4') +')',
margin: '0 0 3 0' // override bottom margin to account for the menuseparator
},
{
xtype: 'menuseparator',
height: '3',
margin: '0'
},
{
layout: {
type: 'hbox',
align: 'middle'
},
border: false,
margin: '0 0 5 0',
items: [
{
xtype: 'label',
text: gettext('IPv6') + ':'
},
{
xtype: 'radiofield',
boxLabel: gettext('Static'),
name: 'ipv6mode',
inputValue: 'static',
checked: !(auto6 || dhcp6),
margin: '0 0 0 10',
listeners: {
change: function(cb, value) {
me.down('field[name=ip6]').setDisabled(!value);
me.down('field[name=gw6]').setDisabled(!value);
}
}
},
{
xtype: 'radiofield',
boxLabel: gettext('DHCP'),
name: 'ipv6mode',
inputValue: 'dhcp',
checked: dhcp6,
margin: '0 0 0 10'
},
{
xtype: 'radiofield',
boxLabel: gettext('SLAAC'),
name: 'ipv6mode',
inputValue: 'auto',
checked: auto6,
margin: '0 0 0 10'
}
]
},
{
xtype: 'textfield',
name: 'ip6',
value: cdata.ip6,
vtype: 'IP6CIDRAddress',
disabled: (dhcp6 || auto6),
fieldLabel: gettext('IPv6/CIDR')
},
{
xtype: 'textfield',
name: 'gw6',
vtype: 'IP6Address',
value: cdata.gw6,
disabled: (dhcp6 || auto6),
fieldLabel: gettext('Gateway') + ' (' + gettext('IPv6') +')'
}
];
me.callParent();
}
});
/*jslint confusion: true */
Ext.define('PVE.lxc.NetworkEdit', {
extend: 'PVE.window.Edit',
isAdd: true,
initComponent : function() {
var me = this;
if (!me.dataCache) {
throw "no dataCache specified";
}
if (!me.nodename) {
throw "no node name specified";
}
var ipanel = Ext.create('PVE.lxc.NetworkInputPanel', {
ifname: me.ifname,
nodename: me.nodename,
dataCache: me.dataCache,
create: me.create
});
Ext.apply(me, {
subject: gettext('Network Device') + ' (veth)',
digest: me.dataCache.digest,
items: [ ipanel ]
});
me.callParent();
}
});
Ext.define('PVE.lxc.NetworkView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveLxcNetworkView'],
dataCache: {}, // used to store result of last load
load: function() {
var me = this;
PVE.Utils.setErrorMask(me, true);
PVE.Utils.API2Request({
url: me.url,
failure: function(response, opts) {
PVE.Utils.setErrorMask(me, gettext('Error') + ': ' + response.htmlStatus);
},
success: function(response, opts) {
PVE.Utils.setErrorMask(me, false);
var result = Ext.decode(response.responseText);
var data = result.data || {};
me.dataCache = data;
var records = [];
Ext.Object.each(data, function(key, value) {
if (!key.match(/^net\d+/)) {
return; // continue
}
var net = PVE.Parser.parseLxcNetwork(value);
net.id = key;
records.push(net);
});
me.store.loadData(records);
me.down('button[name=addButton]').setDisabled((records.length >= 10));
}
});
},
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var caps = Ext.state.Manager.get('GuiCap');
me.url = '/nodes/' + nodename + '/lxc/' + vmid + '/config';
var store = new Ext.data.Store({
model: 'pve-lxc-network',
sorters: [
{
property : 'id',
direction: 'ASC'
}
]
});
var sm = Ext.create('Ext.selection.RowModel', {});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
enableFn: function(rec) {
return !!caps.vms['VM.Config.Network'];
},
confirmMsg: function (rec) {
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.id + "'");
},
handler: function(btn, event, rec) {
PVE.Utils.API2Request({
url: me.url,
waitMsgTarget: me,
method: 'PUT',
params: { 'delete': rec.data.id, digest: me.dataCache.digest },
callback: function() {
me.load();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
if (!caps.vms['VM.Config.Network']) {
return false;
}
var win = Ext.create('PVE.lxc.NetworkEdit', {
url: me.url,
nodename: nodename,
dataCache: me.dataCache,
ifname: rec.data.id
});
win.on('destroy', me.load, me);
win.show();
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
selModel: sm,
disabled: true,
enableFn: function(rec) {
if (!caps.vms['VM.Config.Network']) {
return false;
}
return true;
},
handler: run_editor
});
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
tbar: [
{
text: gettext('Add'),
name: 'addButton',
disabled: !caps.vms['VM.Config.Network'],
handler: function() {
var win = Ext.create('PVE.lxc.NetworkEdit', {
url: me.url,
nodename: nodename,
create: true,
dataCache: me.dataCache
});
win.on('destroy', me.load, me);
win.show();
}
},
remove_btn,
edit_btn
],
columns: [
{
header: gettext('ID'),
width: 50,
dataIndex: 'id'
},
{
header: gettext('Name'),
width: 80,
dataIndex: 'name'
},
{
header: gettext('Bridge'),
width: 80,
dataIndex: 'bridge'
},
{
header: gettext('Firewall'),
width: 80,
dataIndex: 'firewall',
renderer: PVE.Utils.format_boolean
},
{
header: gettext('VLAN Tag'),
width: 80,
dataIndex: 'tag'
},
{
header: gettext('MAC address'),
width: 110,
dataIndex: 'hwaddr'
},
{
header: gettext('IP address'),
width: 150,
dataIndex: 'ip',
renderer: function(value, metaData, rec) {
if (rec.data.ip && rec.data.ip6) {
return rec.data.ip + "<br>" + rec.data.ip6;
} else if (rec.data.ip6) {
return rec.data.ip6;
} else {
return rec.data.ip;
}
}
},
{
header: gettext('Gateway'),
width: 150,
dataIndex: 'gw',
renderer: function(value, metaData, rec) {
if (rec.data.gw && rec.data.gw6) {
return rec.data.gw + "<br>" + rec.data.gw6;
} else if (rec.data.gw6) {
return rec.data.gw6;
} else {
return rec.data.gw;
}
}
}
],
listeners: {
activate: me.load,
itemdblclick: run_editor
}
});
me.callParent();
}
}, function() {
Ext.define('pve-lxc-network', {
extend: "Ext.data.Model",
proxy: { type: 'memory' },
fields: [ 'id', 'name', 'hwaddr', 'bridge',
'ip', 'gw', 'ip6', 'gw6', 'tag', 'firewall' ]
});
});
/*jslint confusion: true */
Ext.define('PVE.lxc.RessourceView', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveLxcRessourceView'],
renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
var me = this;
var rows = me.rows;
var rowdef = rows[key] || {};
metaData.tdAttr = "valign=middle";
if (rowdef.tdCls) {
metaData.tdCls = rowdef.tdCls;
if (rowdef.tdCls == 'pve-itype-icon-storage') {
var value = me.getObjectValue(key, '', true);
}
}
return rowdef.header || key;
},
initComponent : function() {
var me = this;
var i, confid;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var caps = Ext.state.Manager.get('GuiCap');
var mpeditor = caps.vms['VM.Config.Disk'] ? 'PVE.lxc.MountPointEdit' : undefined;
var rows = {
memory: {
header: gettext('Memory'),
editor: caps.vms['VM.Config.Memory'] ? 'PVE.lxc.MemoryEdit' : undefined,
never_delete: true,
defaultValue: 512,
tdCls: 'pve-itype-icon-memory',
renderer: function(value) {
return PVE.Utils.format_size(value*1024*1024);
}
},
swap: {
header: gettext('Swap'),
editor: caps.vms['VM.Config.Memory'] ? 'PVE.lxc.MemoryEdit' : undefined,
never_delete: true,
defaultValue: 512,
tdCls: 'pve-itype-icon-swap',
renderer: function(value) {
return PVE.Utils.format_size(value*1024*1024);
}
},
cpulimit: {
header: gettext('CPU limit'),
never_delete: true,
editor: caps.vms['VM.Config.CPU'] ? 'PVE.lxc.CPUEdit' : undefined,
defaultValue: 1,
tdCls: 'pve-itype-icon-processor',
renderer: function(value) {
if (value) { return value; }
return gettext('unlimited');
}
},
cpuunits: {
header: gettext('CPU units'),
never_delete: true,
editor: caps.vms['VM.Config.CPU'] ? 'PVE.lxc.CPUEdit' : undefined,
defaultValue: 1024,
tdCls: 'pve-itype-icon-processor'
},
rootfs: {
header: gettext('Root Disk'),
defaultValue: PVE.Utils.noneText,
editor: mpeditor,
tdCls: 'pve-itype-icon-storage'
}
};
for (i = 0; i < 10; i++) {
confid = "mp" + i;
rows[confid] = {
group: 1,
tdCls: 'pve-itype-icon-storage',
editor: mpeditor,
header: gettext('Mount Point') + ' (' + confid + ')'
};
}
for (i = 0; i < 8; i++) {
confid = "unused" + i;
rows[confid] = {
group: 1,
tdCls: 'pve-itype-icon-storage',
editor: mpeditor,
header: gettext('Unused Disk') + ' ' + i
};
}
var reload = function() {
me.rstore.load();
};
var baseurl = 'nodes/' + nodename + '/lxc/' + vmid + '/config';
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var rowdef = rows[rec.data.key];
if (!rowdef.editor) {
return;
}
var editor = rowdef.editor;
var win = Ext.create(editor, {
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
});
win.show();
win.on('destroy', reload);
};
var run_resize = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.window.MPResize', {
disk: rec.data.key,
nodename: nodename,
vmid: vmid
});
win.show();
win.on('destroy', reload);
};
var run_remove = function(b, e, rec) {
PVE.Utils.API2Request({
url: '/api2/extjs/' + baseurl,
waitMsgTarget: me,
method: 'PUT',
params: {
'delete': rec.data.key
},
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
selModel: sm,
disabled: true,
enableFn: function(rec) {
if (!rec) {
return false;
}
var rowdef = rows[rec.data.key];
return !!rowdef.editor;
},
handler: run_editor
});
var resize_btn = new PVE.button.Button({
text: gettext('Resize disk'),
selModel: sm,
disabled: true,
handler: run_resize
});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
selModel: sm,
disabled: true,
dangerous: true,
confirmMsg: function(rec) {
var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + me.renderKey(rec.data.key, {}, rec) + "'");
if (rec.data.key.match(/^unused\d+$/)) {
msg += " " + gettext('This will permanently erase all data.');
}
return msg;
},
handler: run_remove
});
var set_button_status = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
edit_btn.disable();
remove_btn.disable();
resize_btn.disable();
return;
}
var key = rec.data.key;
var value = rec.data.value;
var rowdef = rows[key];
var isDisk = (rowdef.tdCls == 'pve-itype-icon-storage');
var noedit = rec.data['delete'] || !rowdef.editor;
if (!noedit && PVE.UserName !== 'root@pam' && key.match(/^mp\d+$/)) {
var mp = PVE.Parser.parseLxcMountPoint(value);
if (mp.type !== 'volume') {
noedit = true;
}
}
edit_btn.setDisabled(noedit);
remove_btn.setDisabled(!isDisk || rec.data.key === 'rootfs');
resize_btn.setDisabled(!isDisk);
};
Ext.apply(me, {
url: '/api2/json/' + baseurl,
selModel: sm,
cwidth1: 170,
tbar: [
{
text: gettext('Add'),
menu: new Ext.menu.Menu({
items: [
{
text: gettext('Mount Point'),
iconCls: 'pve-itype-icon-storage',
disabled: !caps.vms['VM.Config.Disk'],
handler: function() {
var win = Ext.create('PVE.lxc.MountPointEdit', {
url: '/api2/extjs/' + baseurl,
pveSelNode: me.pveSelNode
});
win.on('destroy', reload);
win.show();
}
}
]
})
},
edit_btn,
remove_btn,
resize_btn
],
rows: rows,
listeners: {
afterrender: reload,
itemdblclick: run_editor,
selectionchange: set_button_status
}
});
me.callParent();
}
});
/*jslint confusion: true */
Ext.define('PVE.lxc.Options', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveLxcOptions'],
initComponent : function() {
var me = this;
var i;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var caps = Ext.state.Manager.get('GuiCap');
var rows = {
onboot: {
header: gettext('Start at boot'),
defaultValue: '',
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Start at boot'),
items: {
xtype: 'pvecheckbox',
name: 'onboot',
uncheckedValue: 0,
defaultValue: 0,
fieldLabel: gettext('Start at boot')
}
} : undefined
},
startup: {
header: gettext('Start/Shutdown order'),
defaultValue: '',
renderer: PVE.Utils.render_kvm_startup,
editor: caps.vms['VM.Config.Options'] && caps.nodes['Sys.Modify'] ?
'PVE.qemu.StartupEdit' : undefined
},
ostype: {
header: gettext('OS Type'),
defaultValue: PVE.Utils.unknownText
},
arch: {
header: gettext('Architecture'),
defaultValue: PVE.Utils.unknownText
},
console: {
header: gettext('Enable /dev/console'),
defaultValue: 1,
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Enable /dev/console'),
items: {
xtype: 'pvecheckbox',
name: 'console',
uncheckedValue: 0,
defaultValue: 1,
deleteDefaultValue: true,
checked: true,
fieldLabel: gettext('Enable /dev/console')
}
} : undefined
},
tty: {
header: gettext('TTY count'),
defaultValue: 2,
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('TTY count'),
items: {
xtype: 'numberfield',
name: 'tty',
decimalPrecision: 0,
minValue: 0,
maxValue: 6,
value: 2,
fieldLabel: gettext('TTY count'),
allowEmpty: gettext('Default'),
getSubmitData: function() {
var me = this;
var val = me.getSubmitValue();
if (val !== null && val !== '' && val !== '2') {
return { tty: val };
} else {
return { 'delete' : 'tty' };
}
}
}
} : undefined
},
cmode: {
header: gettext('Console mode'),
defaultValue: 'tty',
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Console mode'),
items: {
xtype: 'pveKVComboBox',
name: 'cmode',
deleteEmpty: true,
value: '__default__',
comboItems: [
['__default__', PVE.Utils.defaultText + " (tty)"],
['tty', "/dev/tty[X]"],
['console', "/dev/console"],
['shell', "shell"]
],
fieldLabel: gettext('Console mode')
}
} : undefined
},
protection: {
header: gettext('Protection'),
defaultValue: false,
renderer: PVE.Utils.format_boolean,
editor: caps.vms['VM.Config.Options'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Protection'),
items: {
xtype: 'pvecheckbox',
name: 'protection',
uncheckedValue: 0,
defaultValue: 0,
deleteDefaultValue: true,
fieldLabel: gettext('Enabled')
}
} : undefined
}
};
var baseurl = 'nodes/' + nodename + '/lxc/' + vmid + '/config';
var reload = function() {
me.rstore.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var rowdef = rows[rec.data.key];
if (!rowdef.editor) {
return;
}
var win;
if (Ext.isString(rowdef.editor)) {
win = Ext.create(rowdef.editor, {
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
});
} else {
var config = Ext.apply({
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
}, rowdef.editor);
win = Ext.createWidget(rowdef.editor.xtype, config);
win.load();
}
win.show();
win.on('destroy', reload);
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
enableFn: function(rec) {
var rowdef = rows[rec.data.key];
return !!rowdef.editor;
},
handler: run_editor
});
Ext.apply(me, {
url: "/api2/json/nodes/" + nodename + "/lxc/" + vmid + "/config",
selModel: sm,
tbar: [ edit_btn ],
rows: rows,
listeners: {
itemdblclick: run_editor,
activate: reload
}
});
me.callParent();
}
});
Ext.define('PVE.lxc.DNSInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.pveLxcDNSInputPanel',
insideWizard: false,
onGetValues: function(values) {
var me = this;
if (!values.searchdomain) {
if (me.insideWizard) {
return {};
} else {
return { "delete": "searchdomain,nameserver" };
}
}
var list = [];
Ext.Array.each(['dns1', 'dns2', 'dns3'], function(fn) {
if (values[fn]) {
list.push(values[fn]);
}
delete values[fn];
});
if (list.length) {
values.nameserver = list.join(' ');
} else {
if (!me.insideWizard) {
values['delete'] = 'nameserver';
}
}
return values;
},
initComponent : function() {
var me = this;
var items = [
{
xtype: 'pvetextfield',
name: 'searchdomain',
skipEmptyText: true,
fieldLabel: gettext('DNS domain'),
emptyText: gettext('use host settings'),
allowBlank: true,
listeners: {
change: function(f, value) {
if (!me.rendered) {
return;
}
var field_ids = ['#dns1', '#dns2', '#dns3'];
Ext.Array.each(field_ids, function(fn) {
var field = me.down(fn);
field.setDisabled(!value);
field.clearInvalid();
});
}
}
},
{
xtype: 'pvetextfield',
fieldLabel: gettext('DNS server') + " 1",
vtype: 'IP64Address',
allowBlank: true,
disabled: true,
name: 'dns1',
itemId: 'dns1'
},
{
xtype: 'pvetextfield',
fieldLabel: gettext('DNS server') + " 2",
vtype: 'IP64Address',
skipEmptyText: true,
disabled: true,
name: 'dns2',
itemId: 'dns2'
},
{
xtype: 'pvetextfield',
fieldLabel: gettext('DNS server') + " 3",
vtype: 'IP64Address',
skipEmptyText: true,
disabled: true,
name: 'dns3',
itemId: 'dns3'
}
];
if (me.insideWizard) {
me.column1 = items;
} else {
me.items = items;
}
me.callParent();
}
});
Ext.define('PVE.lxc.DNSEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
var ipanel = Ext.create('PVE.lxc.DNSInputPanel');
Ext.apply(me, {
subject: gettext('Resources'),
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
if (values.nameserver) {
values.nameserver.replace(/[,;]/, ' ');
values.nameserver.replace(/^\s+/, '');
var nslist = values.nameserver.split(/\s+/);
values.dns1 = nslist[0];
values.dns2 = nslist[1];
values.dns3 = nslist[2];
}
ipanel.setValues(values);
}
});
}
}
});
/*jslint confusion: true */
Ext.define('PVE.lxc.DNS', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveLxcDNS'],
initComponent : function() {
var me = this;
var i;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var caps = Ext.state.Manager.get('GuiCap');
var rows = {
hostname: {
required: true,
defaultValue: me.pveSelNode.data.name,
header: gettext('Hostname'),
editor: caps.vms['VM.Config.Network'] ? {
xtype: 'pveWindowEdit',
subject: gettext('Hostname'),
items: {
xtype: 'textfield',
name: 'hostname',
vtype: 'DnsName',
value: '',
fieldLabel: gettext('Hostname'),
allowBlank: true,
emptyText: me.pveSelNode.data.name
}
} : undefined
},
searchdomain: {
header: gettext('DNS domain'),
defaultValue: '',
editor: caps.vms['VM.Config.Network'] ? 'PVE.lxc.DNSEdit' : undefined,
renderer: function(value) {
if (me.getObjectValue('nameserver') || me.getObjectValue('searchdomain')) {
return value;
}
return gettext('use host settings');
}
},
nameserver: {
header: gettext('DNS server'),
defaultValue: '',
editor: caps.vms['VM.Config.Network'] ? 'PVE.lxc.DNSEdit' : undefined,
renderer: function(value) {
if (me.getObjectValue('nameserver') || me.getObjectValue('searchdomain')) {
return value;
}
return gettext('use host settings');
}
}
};
var baseurl = 'nodes/' + nodename + '/lxc/' + vmid + '/config';
var reload = function() {
me.rstore.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var rowdef = rows[rec.data.key];
if (!rowdef.editor) {
return;
}
var win;
if (Ext.isString(rowdef.editor)) {
win = Ext.create(rowdef.editor, {
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
});
} else {
var config = Ext.apply({
pveSelNode: me.pveSelNode,
confid: rec.data.key,
url: '/api2/extjs/' + baseurl
}, rowdef.editor);
win = Ext.createWidget(rowdef.editor.xtype, config);
}
//win.load();
win.show();
win.on('destroy', reload);
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
enableFn: function(rec) {
var rowdef = rows[rec.data.key];
return !!rowdef.editor;
},
handler: run_editor
});
var set_button_status = function() {
var sm = me.getSelectionModel();
var rec = sm.getSelection()[0];
if (!rec) {
edit_btn.disable();
return;
}
var rowdef = rows[rec.data.key];
edit_btn.setDisabled(!rowdef.editor);
};
Ext.apply(me, {
url: "/api2/json/nodes/" + nodename + "/lxc/" + vmid + "/config",
selModel: sm,
cwidth1: 150,
tbar: [ edit_btn ],
rows: rows,
listeners: {
itemdblclick: run_editor,
selectionchange: set_button_status,
activate: reload
}
});
me.callParent();
}
});
Ext.define('PVE.lxc.Config', {
extend: 'PVE.panel.Config',
alias: 'widget.PVE.lxc.Config',
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var vmid = me.pveSelNode.data.vmid;
if (!vmid) {
throw "no VM ID specified";
}
var template = me.pveSelNode.data.template;
var caps = Ext.state.Manager.get('GuiCap');
var base_url = '/nodes/' + nodename + '/lxc/' + vmid;
me.statusStore = Ext.create('PVE.data.ObjectStore', {
url: '/api2/json' + base_url + '/status/current',
interval: 1000
});
var vm_command = function(cmd, params) {
PVE.Utils.API2Request({
params: params,
url: base_url + "/status/" + cmd,
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert('Error', response.htmlStatus);
}
});
};
var startBtn = Ext.create('Ext.Button', {
text: gettext('Start'),
disabled: !caps.vms['VM.PowerMgmt'],
handler: function() {
vm_command('start');
},
iconCls: 'fa fa-play'
});
var umountBtn = Ext.create('Ext.Button', {
text: gettext('Unmount'),
disabled: true,
hidden: true,
handler: function() {
vm_command('umount');
}
});
var stopBtn = Ext.create('Ext.menu.Item',{
text: gettext('Stop'),
disabled: !caps.vms['VM.PowerMgmt'],
confirmMsg: PVE.Utils.format_task_description('vzstop', vmid),
dangerous: true,
handler: function() {
vm_command("stop");
},
iconCls: 'fa fa-stop'
});
var shutdownBtn = Ext.create('PVE.button.Split', {
text: gettext('Shutdown'),
disabled: !caps.vms['VM.PowerMgmt'],
confirmMsg: PVE.Utils.format_task_description('vzshutdown', vmid),
handler: function() {
vm_command('shutdown');
},
menu: {
items:[stopBtn]
},
iconCls: 'fa fa-power-off'
});
var migrateBtn = Ext.create('Ext.Button', {
text: gettext('Migrate'),
disabled: !caps.vms['VM.Migrate'],
handler: function() {
var win = Ext.create('PVE.window.Migrate', {
vmtype: 'lxc',
nodename: nodename,
vmid: vmid
});
win.show();
},
iconCls: 'fa fa-send-o'
});
var removeBtn = Ext.create('PVE.button.Button', {
text: gettext('Remove'),
disabled: !caps.vms['VM.Allocate'],
handler: function() {
Ext.create('PVE.window.SafeDestroy', {
url: base_url,
item: { type: 'CT', id: vmid }
}).show();
},
iconCls: 'fa fa-trash-o'
});
var vmname = me.pveSelNode.data.name;
var consoleBtn = Ext.create('PVE.button.ConsoleButton', {
disabled: !caps.vms['VM.Console'],
consoleType: 'lxc',
consoleName: vmname,
nodename: nodename,
vmid: vmid,
iconCls: 'fa fa-terminal'
});
var descr = vmid + " (" + (vmname ? "'" + vmname + "' " : "'CT " + vmid + "'") + ")";
Ext.apply(me, {
title: Ext.String.format(gettext("Container {0} on node {1}"), descr, "'" + nodename + "'"),
hstateid: 'lxctab',
tbar: [ startBtn, shutdownBtn, umountBtn, removeBtn,
migrateBtn, consoleBtn ],
defaults: { statusStore: me.statusStore },
items: [
{
title: gettext('Summary'),
xtype: 'pveLxcSummary',
itemId: 'summary'
},
{
title: gettext('Resources'),
itemId: 'resources',
layout: 'fit',
plugins: {
ptype: 'lazyitems',
items: [{
xtype: 'pveLxcRessourceView',
pveSelNode: me.pveSelNode
}]
}
},
{
title: gettext('Network'),
itemId: 'network',
xtype: 'pveLxcNetworkView'
},
{
title: gettext('DNS'),
itemId: 'dns',
xtype: 'pveLxcDNS'
},
{
title: gettext('Options'),
itemId: 'options',
xtype: 'pveLxcOptions'
},
{
title: gettext('Task History'),
itemId: 'tasks',
xtype: 'pveNodeTasks',
vmidFilter: vmid
}
]
});
if (caps.vms['VM.Backup']) {
me.items.push({
title: gettext('Backup'),
xtype: 'pveBackupView',
itemId: 'backup'
});
}
if (caps.vms['VM.Console']) {
me.items.push({
title: gettext('Console'),
itemId: 'console',
xtype: 'pveNoVncConsole',
vmid: vmid,
consoleType: 'lxc',
nodename: nodename
});
}
if (caps.vms['VM.Snapshot']) {
me.items.push({
title: gettext('Snapshots'),
xtype: 'pveLxcSnapshotTree',
itemId: 'snapshot'
});
}
if (caps.vms['VM.Console']) {
me.items.push(
{
xtype: 'pveFirewallPanel',
title: gettext('Firewall'),
base_url: base_url + '/firewall',
fwtype: 'vm',
phstateid: me.hstateid,
itemId: 'firewall'
}
);
}
if (caps.vms['Permissions.Modify']) {
me.items.push({
xtype: 'pveACLView',
title: gettext('Permissions'),
itemId: 'permissions',
path: '/vms/' + vmid
});
}
me.callParent();
me.mon(me.statusStore, 'load', function(s, records, success) {
var status;
if (!success) {
me.workspace.checkVmMigration(me.pveSelNode);
status = 'unknown';
} else {
var rec = s.data.get('status');
status = rec ? rec.data.value : 'unknown';
rec = s.data.get('template');
template = rec.data.value || false;
}
startBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'running' || template);
shutdownBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status !== 'running');
stopBtn.setDisabled(!caps.vms['VM.PowerMgmt'] || status === 'stopped');
removeBtn.setDisabled(!caps.vms['VM.Allocate'] || status !== 'stopped');
consoleBtn.setDisabled(template);
if (status === 'mounted') {
umountBtn.setDisabled(false);
umountBtn.setVisible(true);
stopBtn.setDisabled(true);
} else {
umountBtn.setDisabled(true);
umountBtn.setVisible(false);
stopBtn.setDisabled(false);
}
});
me.on('afterrender', function() {
me.statusStore.startUpdate();
});
me.on('destroy', function() {
me.statusStore.stopUpdate();
});
}
});
/*jslint confusion: true */
Ext.define('PVE.lxc.CreateWizard', {
extend: 'PVE.window.Wizard',
initComponent: function() {
var me = this;
var summarystore = Ext.create('Ext.data.Store', {
model: 'KeyValue',
sorters: [
{
property : 'key',
direction: 'ASC'
}
]
});
var tmplsel = Ext.create('PVE.form.FileSelector', {
name: 'ostemplate',
storageContent: 'vztmpl',
fieldLabel: gettext('Template'),
allowBlank: false
});
var tmplstoragesel = Ext.create('PVE.form.StorageSelector', {
name: 'tmplstorage',
fieldLabel: gettext('Storage'),
storageContent: 'vztmpl',
autoSelect: true,
allowBlank: false,
listeners: {
change: function(f, value) {
tmplsel.setStorage(value);
}
}
});
var rootfspanel = Ext.create('PVE.lxc.MountPointInputPanel', {
title: gettext('Root Disk'),
insideWizard: true,
create: true,
unused: false,
confid: 'rootfs'
});
var networkpanel = Ext.create('PVE.lxc.NetworkInputPanel', {
title: gettext('Network'),
insideWizard: true,
dataCache: {},
create: true
});
Ext.applyIf(me, {
subject: gettext('LXC Container'),
items: [
{
xtype: 'inputpanel',
title: gettext('General'),
column1: [
{
xtype: 'pveNodeSelector',
name: 'nodename',
fieldLabel: gettext('Node'),
allowBlank: false,
onlineValidator: true,
listeners: {
change: function(f, value) {
tmplstoragesel.setNodename(value);
tmplsel.setStorage(undefined, value);
networkpanel.setNodename(value);
rootfspanel.setNodename(value);
}
}
},
{
xtype: 'pveVMIDSelector',
name: 'vmid',
value: '',
loadNextFreeVMID: true,
validateExists: false
},
{
xtype: 'pvetextfield',
name: 'hostname',
vtype: 'DnsName',
value: '',
fieldLabel: gettext('Hostname'),
skipEmptyText: true,
allowBlank: true
}
],
column2: [
{
xtype: 'pvePoolSelector',
fieldLabel: gettext('Resource Pool'),
name: 'pool',
value: '',
allowBlank: true
},
{
xtype: 'textfield',
inputType: 'password',
name: 'password',
value: '',
fieldLabel: gettext('Password'),
allowBlank: false,
minLength: 5,
change: function(f, value) {
if (!me.rendered) {
return;
}
me.down('field[name=confirmpw]').validate();
}
},
{
xtype: 'textfield',
inputType: 'password',
name: 'confirmpw',
value: '',
fieldLabel: gettext('Confirm password'),
allowBlank: false,
validator: function(value) {
var pw = me.down('field[name=password]').getValue();
if (pw !== value) {
return "Passwords does not match!";
}
return true;
}
}
],
onGetValues: function(values) {
delete values.confirmpw;
if (!values.pool) {
delete values.pool;
}
return values;
}
},
{
xtype: 'inputpanel',
title: gettext('Template'),
column1: [ tmplstoragesel, tmplsel]
},
rootfspanel,
{
xtype: 'pveLxcCPUInputPanel',
title: gettext('CPU'),
insideWizard: true
},
{
xtype: 'pveLxcMemoryInputPanel',
title: gettext('Memory'),
insideWizard: true
},
networkpanel,
{
xtype: 'pveLxcDNSInputPanel',
title: gettext('DNS'),
insideWizard: true
},
{
title: gettext('Confirm'),
layout: 'fit',
items: [
{
title: gettext('Settings'),
xtype: 'grid',
store: summarystore,
columns: [
{header: 'Key', width: 150, dataIndex: 'key'},
{header: 'Value', flex: 1, dataIndex: 'value'}
]
}
],
listeners: {
show: function(panel) {
var form = me.down('form').getForm();
var kv = me.getValues();
var data = [];
Ext.Object.each(kv, function(key, value) {
if (key === 'delete' || key === 'tmplstorage') { // ignore
return;
}
if (key === 'password') { // don't show pw
return;
}
var html = Ext.htmlEncode(Ext.JSON.encode(value));
data.push({ key: key, value: value });
});
summarystore.suspendEvents();
summarystore.removeAll();
summarystore.add(data);
summarystore.sort();
summarystore.resumeEvents();
summarystore.fireEvent('datachanged', summarystore);
}
},
onSubmit: function() {
var kv = me.getValues();
delete kv['delete'];
var nodename = kv.nodename;
delete kv.nodename;
delete kv.tmplstorage;
PVE.Utils.API2Request({
url: '/nodes/' + nodename + '/lxc',
waitMsgTarget: me,
method: 'POST',
params: kv,
success: function(response, opts){
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskViewer', {
upid: upid
});
win.show();
me.close();
},
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
}
]
});
me.callParent();
}
});
Ext.define('PVE.lxc.SnapshotTree', {
extend: 'Ext.tree.Panel',
alias: ['widget.pveLxcSnapshotTree'],
load_delay: 3000,
old_digest: 'invalid',
sorterFn: function(rec1, rec2) {
var v1 = rec1.data.snaptime;
var v2 = rec2.data.snaptime;
if (rec1.data.name === 'current') {
return 1;
}
if (rec2.data.name === 'current') {
return -1;
}
return (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
},
reload: function(repeat) {
var me = this;
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/snapshot',
method: 'GET',
failure: function(response, opts) {
PVE.Utils.setErrorMask(me, response.htmlStatus);
me.load_task.delay(me.load_delay);
},
success: function(response, opts) {
PVE.Utils.setErrorMask(me, false);
var digest = 'invalid';
var idhash = {};
var root = { name: '__root', expanded: true, children: [] };
Ext.Array.each(response.result.data, function(item) {
item.leaf = true;
item.children = [];
if (item.name === 'current') {
digest = item.digest + item.running;
if (item.running) {
item.iconCls = 'fa fa-fw fa-desktop x-fa-tree-running';
} else {
item.iconCls = 'fa fa-fw fa-desktop x-fa-tree';
}
} else {
item.iconCls = 'fa fa-fw fa-history x-fa-tree';
}
idhash[item.name] = item;
});
if (digest !== me.old_digest) {
me.old_digest = digest;
Ext.Array.each(response.result.data, function(item) {
if (item.parent && idhash[item.parent]) {
var parent_item = idhash[item.parent];
parent_item.children.push(item);
parent_item.leaf = false;
parent_item.expanded = true;
parent_item.expandable = false;
} else {
root.children.push(item);
}
});
me.setRootNode(root);
}
me.load_task.delay(me.load_delay);
}
});
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/feature',
params: { feature: 'snapshot' },
method: 'GET',
success: function(response, options) {
var res = response.result.data;
if (res.hasFeature) {
var snpBtns = Ext.ComponentQuery.query('#snapshotBtn');
snpBtns.forEach(function(item){
item.enable();
});
}
}
});
},
initComponent: function() {
var me = this;
me.nodename = me.pveSelNode.data.node;
if (!me.nodename) {
throw "no node name specified";
}
me.vmid = me.pveSelNode.data.vmid;
if (!me.vmid) {
throw "no VM ID specified";
}
me.load_task = new Ext.util.DelayedTask(me.reload, me);
var sm = Ext.create('Ext.selection.RowModel', {});
var valid_snapshot = function(record) {
return record && record.data && record.data.name &&
record.data.name !== 'current';
};
var valid_snapshot_rollback = function(record) {
return record && record.data && record.data.name &&
record.data.name !== 'current' && !record.data.snapstate;
};
var run_editor = function() {
var rec = sm.getSelection()[0];
if (valid_snapshot(rec)) {
var win = Ext.create('PVE.window.LxcSnapshot', {
snapname: rec.data.name,
nodename: me.nodename,
vmid: me.vmid
});
win.show();
me.mon(win, 'close', me.reload, me);
}
};
var editBtn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
enableFn: valid_snapshot,
handler: run_editor
});
var rollbackBtn = new PVE.button.Button({
text: gettext('Rollback'),
disabled: true,
selModel: sm,
enableFn: valid_snapshot_rollback,
confirmMsg: function(rec) {
return PVE.Utils.format_task_description('vzrollback', me.vmid) +
" '" + rec.data.name + "'";
},
handler: function(btn, event) {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var snapname = rec.data.name;
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/snapshot/' + snapname + '/rollback',
method: 'POST',
waitMsgTarget: me,
callback: function() {
me.reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', { upid: upid });
win.show();
}
});
}
});
var removeBtn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
confirmMsg: function(rec) {
var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.name + "'");
return msg;
},
enableFn: valid_snapshot,
handler: function(btn, event) {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var snapname = rec.data.name;
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/snapshot/' + snapname,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
me.reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', { upid: upid });
win.show();
}
});
}
});
var snapshotBtn = Ext.create('Ext.Button', {
itemId: 'snapshotBtn',
text: gettext('Take Snapshot'),
disabled: true,
handler: function() {
var win = Ext.create('PVE.window.LxcSnapshot', {
nodename: me.nodename,
vmid: me.vmid
});
win.show();
}
});
Ext.apply(me, {
layout: 'fit',
rootVisible: false,
animate: false,
sortableColumns: false,
selModel: sm,
tbar: [ snapshotBtn, rollbackBtn, removeBtn, editBtn ],
fields: [
'name', 'description', 'snapstate', 'vmstate', 'running',
{ name: 'snaptime', type: 'date', dateFormat: 'timestamp' }
],
columns: [
{
xtype: 'treecolumn',
text: gettext('Name'),
dataIndex: 'name',
width: 200,
renderer: function(value, metaData, record) {
if (value === 'current') {
return "NOW";
} else {
return value;
}
}
},
// {
// text: gettext('RAM'),
// align: 'center',
// resizable: false,
// dataIndex: 'vmstate',
// width: 50,
// renderer: function(value, metaData, record) {
// if (record.data.name !== 'current') {
// return PVE.Utils.format_boolean(value);
// }
// }
// },
{
text: gettext('Date') + "/" + gettext("Status"),
dataIndex: 'snaptime',
resizable: false,
width: 120,
renderer: function(value, metaData, record) {
if (record.data.snapstate) {
return record.data.snapstate;
}
if (value) {
return Ext.Date.format(value,'Y-m-d H:i:s');
}
}
},
{
text: gettext('Description'),
dataIndex: 'description',
flex: 1,
renderer: function(value, metaData, record) {
if (record.data.name === 'current') {
return gettext("You are here!");
} else {
return Ext.String.htmlEncode(value);
}
}
}
],
columnLines: true,
listeners: {
activate: me.reload,
hide: me.load_task.cancel,
destroy: me.load_task.cancel,
itemdblclick: run_editor
}
});
me.callParent();
me.store.sorters.add(new Ext.util.Sorter({
sorterFn: me.sorterFn
}));
}
});
Ext.define('PVE.window.LxcSnapshot', {
extend: 'Ext.window.Window',
resizable: false,
take_snapshot: function(snapname, descr, vmstate) {
var me = this;
var params = { snapname: snapname };
if (descr) {
params.description = descr;
}
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + "/snapshot",
waitMsgTarget: me,
method: 'POST',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskProgress', { upid: upid });
win.show();
me.close();
}
});
},
update_snapshot: function(snapname, descr) {
var me = this;
PVE.Utils.API2Request({
params: { description: descr },
url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + "/snapshot/" +
snapname + '/config',
waitMsgTarget: me,
method: 'PUT',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
me.close();
}
});
},
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var summarystore = Ext.create('Ext.data.Store', {
model: 'KeyValue',
sorters: [
{
property : 'key',
direction: 'ASC'
}
]
});
var items = [
{
xtype: me.snapname ? 'displayfield' : 'textfield',
name: 'snapname',
value: me.snapname,
fieldLabel: gettext('Name'),
vtype: 'ConfigId',
allowBlank: false
}
];
if (me.snapname) {
items.push({
xtype: 'displayfield',
name: 'snaptime',
fieldLabel: gettext('Timestamp')
});
}
items.push({
xtype: 'textareafield',
grow: true,
name: 'description',
fieldLabel: gettext('Description')
});
if (me.snapname) {
items.push({
title: gettext('Settings'),
xtype: 'grid',
height: 200,
store: summarystore,
columns: [
{header: gettext('Key'), width: 150, dataIndex: 'key'},
{header: gettext('Value'), flex: 1, dataIndex: 'value'}
]
});
}
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: items
});
var form = me.formPanel.getForm();
var submitBtn;
if (me.snapname) {
me.title = gettext('Edit') + ': ' + gettext('Snapshot');
submitBtn = Ext.create('Ext.Button', {
text: gettext('Update'),
handler: function() {
if (form.isValid()) {
var values = form.getValues();
me.update_snapshot(me.snapname, values.description);
}
}
});
} else {
me.title ="VM " + me.vmid + ': ' + gettext('Take Snapshot');
submitBtn = Ext.create('Ext.Button', {
text: gettext('Take Snapshot'),
handler: function() {
if (form.isValid()) {
var values = form.getValues();
me.take_snapshot(values.snapname, values.description);
}
}
});
}
Ext.apply(me, {
modal: true,
width: 450,
border: false,
layout: 'fit',
buttons: [ submitBtn ],
items: [ me.formPanel ]
});
if (me.snapname) {
Ext.apply(me, {
width: 620,
height: 420
});
}
me.callParent();
if (!me.snapname) {
return;
}
// else load data
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + "/snapshot/" +
me.snapname + '/config',
waitMsgTarget: me,
method: 'GET',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
me.close();
},
success: function(response, options) {
var data = response.result.data;
var kvarray = [];
Ext.Object.each(data, function(key, value) {
if (key === 'description' || key === 'snaptime') {
return;
}
kvarray.push({ key: key, value: value });
});
summarystore.suspendEvents();
summarystore.add(kvarray);
summarystore.sort();
summarystore.resumeEvents();
summarystore.fireEvent('refresh', summarystore);
form.findField('snaptime').setValue(new Date(data.snaptime));
form.findField('description').setValue(data.description);
}
});
}
});
/*jslint confusion: true */
var labelWidth = 120;
Ext.define('PVE.lxc.MemoryEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.apply(me, {
subject: gettext('Memory'),
items: Ext.create('PVE.lxc.MemoryInputPanel')
});
me.callParent();
me.load();
}
});
Ext.define('PVE.lxc.CPUEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.apply(me, {
subject: gettext('CPU'),
items: Ext.create('PVE.lxc.CPUInputPanel')
});
me.callParent();
me.load();
}
});
Ext.define('PVE.lxc.MountPointEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var unused = me.confid && me.confid.match(/^unused\d+$/);
me.create = me.confid ? unused : true;
var ipanel = Ext.create('PVE.lxc.MountPointInputPanel', {
confid: me.confid,
nodename: nodename,
unused: unused,
create: me.create
});
var subject;
if (unused) {
subject = gettext('Unused Disk');
} else if (me.create) {
subject = gettext('Mount Point');
} else {
subject = gettext('Mount Point') + ' (' + me.confid + ')';
}
Ext.apply(me, {
subject: subject,
items: ipanel
});
me.callParent();
me.load({
success: function(response, options) {
ipanel.setVMConfig(response.result.data);
if (me.confid) {
var value = response.result.data[me.confid];
var mp = PVE.Parser.parseLxcMountPoint(value);
if (!mp) {
Ext.Msg.alert(gettext('Error'), 'Unable to parse mount point options');
me.close();
return;
}
ipanel.setMountPoint(mp);
me.isValid(); // trigger validation
}
}
});
}
});
Ext.define('PVE.lxc.CPUInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.pveLxcCPUInputPanel',
insideWizard: false,
initComponent : function() {
var me = this;
var items = [
{
xtype: 'numberfield',
name: 'cpulimit',
minValue: 0,
value: '1',
step: 1,
fieldLabel: gettext('CPU limit'),
labelWidth: labelWidth,
allowBlank: false
},
{
xtype: 'numberfield',
name: 'cpuunits',
fieldLabel: gettext('CPU units'),
value: 1024,
minValue: 8,
maxValue: 500000,
labelWidth: labelWidth,
allowBlank: false
}
];
if (me.insideWizard) {
me.column1 = items;
} else {
me.items = items;
}
me.callParent();
}
});
Ext.define('PVE.lxc.MemoryInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.pveLxcMemoryInputPanel',
insideWizard: false,
initComponent : function() {
var me = this;
var items = [
{
xtype: 'numberfield',
name: 'memory',
minValue: 32,
maxValue: 512*1024,
value: '512',
step: 32,
fieldLabel: gettext('Memory') + ' (MB)',
labelWidth: labelWidth,
allowBlank: false
},
{
xtype: 'numberfield',
name: 'swap',
minValue: 0,
maxValue: 128*1024,
value: '512',
step: 32,
fieldLabel: gettext('Swap') + ' (MB)',
labelWidth: labelWidth,
allowBlank: false
}
];
if (me.insideWizard) {
me.column1 = items;
} else {
me.items = items;
}
me.callParent();
}
});
Ext.define('PVE.lxc.MountPointInputPanel', {
extend: 'PVE.panel.InputPanel',
alias: 'widget.pveLxcMountPointInputPanel',
insideWizard: false,
unused: false, // ADD usused disk imaged
vmconfig: {}, // used to select usused disks
onGetValues: function(values) {
var me = this;
var confid = me.confid || values.mpsel;
if (me.unused) {
me.mpdata.file = me.vmconfig[values.unusedId];
confid = values.mpsel;
} else if (me.create) {
me.mpdata.file = values.storage + ':' + values.disksize;
}
if (confid !== 'rootfs') {
me.mpdata.mp = values.mp;
}
if (values.ro) {
me.mpdata.ro = 1;
} else {
delete me.mpdata.ro;
}
if (values.quota) {
me.mpdata.quota = 1;
} else {
delete me.mpdata.quota;
}
if (values.acl === 'Default') {
delete me.mpdata.acl;
} else {
me.mpdata.acl = values.acl;
}
if (values.backup) {
me.mpdata.backup = 1;
} else {
delete me.mpdata.backup;
}
var res = {};
res[confid] = PVE.Parser.printLxcMountPoint(me.mpdata);
return res;
},
setMountPoint: function(mp) {
var me = this;
me.mpdata = mp;
if (!Ext.isDefined(me.mpdata.acl)) {
me.mpdata.acl = 'Default';
}
if (mp.type === 'bind') {
me.quota.setDisabled(true);
me.quota.setValue(false);
me.acl.setDisabled(true);
me.backup.setDisabled(true);
me.acl.setValue('Default');
me.hdstoragesel.setDisabled(true);
}
me.setValues(mp);
},
setVMConfig: function(vmconfig) {
var me = this;
me.vmconfig = vmconfig;
if (me.mpsel) {
var i;
for (i = 0; i != 8; ++i) {
var name = "mp" + i;
if (!Ext.isDefined(vmconfig[name])) {
me.mpsel.setValue(name);
break;
}
}
}
if (me.unusedDisks) {
var disklist = [];
Ext.Object.each(vmconfig, function(key, value) {
if (key.match(/^unused\d+$/)) {
disklist.push([key, value]);
}
});
me.unusedDisks.store.loadData(disklist);
me.unusedDisks.setValue(me.confid);
}
},
setNodename: function(nodename) {
var me = this;
me.hdstoragesel.setNodename(nodename);
me.hdfilesel.setStorage(undefined, nodename);
},
initComponent : function() {
var me = this;
var isroot = me.confid === 'rootfs';
me.mpdata = {};
me.column1 = [];
if (!me.confid || me.unused) {
var names = [];
var i;
for (i = 0; i != 8; ++i) {
var name = 'mp' + i;
names.push([name, name]);
}
me.mpsel = Ext.create('PVE.form.KVComboBox', {
name: 'mpsel',
fieldLabel: gettext('Mount Point'),
matchFieldWidth: false,
allowBlank: false,
comboItems: names,
validator: function(value) {
if (!me.rendered) {
return;
}
if (Ext.isDefined(me.vmconfig[value])) {
return "Mount point is already in use.";
}
return true;
},
listeners: {
change: function(field, value) {
field.validate();
}
}
});
me.column1.push(me.mpsel);
}
// we always have this around, but only visible when creating a new mp
// since this handles per-filesystem capabilities
me.hdstoragesel = Ext.create('PVE.form.StorageSelector', {
name: 'storage',
nodename: me.nodename,
fieldLabel: gettext('Storage'),
storageContent: 'rootdir',
allowBlank: false,
autoSelect: true,
hidden: me.unused || !me.create,
listeners: {
change: function(f, value) {
if (!value) { // initial store loading fires an unwanted 'change'
return;
}
if (me.mpdata.type === 'bind') {
me.quota.setDisabled(true);
me.quota.setValue(false);
me.acl.setDisabled(true);
me.backup.setDisabled(true);
me.acl.setValue('Default');
return;
}
var rec = f.store.getById(value);
if (rec.data.type === 'zfs' ||
rec.data.type === 'zfspool') {
me.quota.setDisabled(true);
me.quota.setValue(false);
} else {
me.quota.setDisabled(false);
}
if (me.unused || !me.create) {
return;
}
if (rec.data.type === 'iscsi') {
me.hdfilesel.setStorage(value);
me.hdfilesel.setDisabled(false);
me.hdfilesel.setVisible(true);
me.hdsizesel.setDisabled(true);
me.hdsizesel.setVisible(false);
} else if (rec.data.type === 'lvm' ||
rec.data.type === 'lvmthin' ||
rec.data.type === 'rbd' ||
rec.data.type === 'sheepdog' ||
rec.data.type === 'zfs' ||
rec.data.type === 'zfspool') {
me.hdfilesel.setDisabled(true);
me.hdfilesel.setVisible(false);
me.hdsizesel.setDisabled(false);
me.hdsizesel.setVisible(true);
} else {
me.hdfilesel.setDisabled(true);
me.hdfilesel.setVisible(false);
me.hdsizesel.setDisabled(false);
me.hdsizesel.setVisible(true);
}
}
}
});
me.column1.push(me.hdstoragesel);
if (me.unused) {
me.unusedDisks = Ext.create('PVE.form.KVComboBox', {
name: 'unusedId',
fieldLabel: gettext('Disk image'),
matchFieldWidth: false,
listConfig: {
width: 350
},
data: [],
allowBlank: false,
listeners: {
change: function(f, value) {
// make sure our buttons are enabled/disabled when switching
// between images on different storages:
var disk = me.vmconfig[value];
var storage = disk.split(':')[0];
me.hdstoragesel.setValue(storage);
}
}
});
me.column1.push(me.unusedDisks);
} else if (me.create) {
me.hdfilesel = Ext.create('PVE.form.FileSelector', {
name: 'file',
nodename: me.nodename,
storageContent: 'images',
fieldLabel: gettext('Disk image'),
disabled: true,
hidden: true,
allowBlank: false
});
me.hdsizesel = Ext.createWidget('numberfield', {
name: 'disksize',
minValue: 0.1,
maxValue: 128*1024,
decimalPrecision: 3,
value: '8',
step: 1,
fieldLabel: gettext('Disk size') + ' (GB)',
allowBlank: false
});
me.column1.push(me.hdfilesel);
me.column1.push(me.hdsizesel);
} else {
me.column1.push({
xtype: 'textfield',
disabled: true,
submitValue: false,
fieldLabel: gettext('Disk image'),
name: 'file'
});
}
me.acl = Ext.createWidget('pveKVComboBox', {
name: 'acl',
fieldLabel: gettext('ACLs'),
comboItems: [['Default', 'Default'], ['1', 'On'], ['0', 'Off']],
value: 'Default',
allowBlank: true
});
me.quota = Ext.createWidget('pvecheckbox', {
name: 'quota',
defaultValue: 0,
fieldLabel: gettext('Enable quota')
});
me.column2 = [
{
xtype: 'pvecheckbox',
name: 'ro',
defaultValue: 0,
fieldLabel: gettext('Read-only'),
hidden: me.insideWizard
},
me.acl,
me.quota
];
if (!isroot) {
me.backup = Ext.createWidget('pvecheckbox',{
xtype: 'pvecheckbox',
name: 'backup',
fieldLabel: gettext('Backup')
});
if (me.mpdata.type !== 'bind') {
me.column2.push(me.backup);
}
me.column2.push({
xtype: 'textfield',
name: 'mp',
value: '',
emptyText: gettext('/some/path'),
allowBlank: false,
hidden: isroot,
fieldLabel: gettext('Path')
});
}
me.callParent();
}
});
Ext.define('PVE.window.MPResize', {
extend: 'Ext.window.Window',
resizable: false,
resize_disk: function(disk, size) {
var me = this;
var params = { disk: disk, size: '+' + size + 'G' };
PVE.Utils.API2Request({
params: params,
url: '/nodes/' + me.nodename + '/lxc/' + me.vmid + '/resize',
waitMsgTarget: me,
method: 'PUT',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, opts) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskViewer', { upid: upid });
win.show();
me.close();
}
});
},
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.vmid) {
throw "no VM ID specified";
}
var items = [
{
xtype: 'displayfield',
name: 'disk',
value: me.disk,
fieldLabel: gettext('Disk'),
vtype: 'StorageId',
allowBlank: false
}
];
me.hdsizesel = Ext.createWidget('numberfield', {
name: 'size',
minValue: 0,
maxValue: 128*1024,
decimalPrecision: 3,
value: '0',
fieldLabel: gettext('Size Increment') + ' (GB)',
allowBlank: false
});
items.push(me.hdsizesel);
me.formPanel = Ext.create('Ext.form.Panel', {
bodyPadding: 10,
border: false,
fieldDefaults: {
labelWidth: 120,
anchor: '100%'
},
items: items
});
var form = me.formPanel.getForm();
var submitBtn;
me.title = gettext('Resize disk');
submitBtn = Ext.create('Ext.Button', {
text: gettext('Resize disk'),
handler: function() {
if (form.isValid()) {
var values = form.getValues();
me.resize_disk(me.disk, values.size);
}
}
});
Ext.apply(me, {
modal: true,
border: false,
layout: 'fit',
buttons: [ submitBtn ],
items: [ me.formPanel ]
});
me.callParent();
if (!me.disk) {
return;
}
}
});
Ext.define('PVE.pool.StatusView', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pvePoolStatusView'],
disabled: true,
title: gettext('Status'),
cwidth1: 150,
interval: 30000,
//height: 195,
initComponent : function() {
var me = this;
var pool = me.pveSelNode.data.pool;
if (!pool) {
throw "no pool specified";
}
var rows = {
comment: {
header: gettext('Comment'),
renderer: Ext.String.htmlEncode,
required: true
}
};
Ext.apply(me, {
url: "/api2/json/pools/" + pool,
rows: rows
});
me.callParent();
}
});
Ext.define('PVE.pool.Summary', {
extend: 'Ext.panel.Panel',
alias: 'widget.pvePoolSummary',
initComponent: function() {
var me = this;
var pool = me.pveSelNode.data.pool;
if (!pool) {
throw "no pool specified";
}
var statusview = Ext.create('PVE.pool.StatusView', {
pveSelNode: me.pveSelNode,
style: 'padding-top:0px'
});
var rstore = statusview.rstore;
Ext.apply(me, {
autoScroll: true,
bodyStyle: 'padding:10px',
defaults: {
style: 'padding-top:10px',
width: 800
},
items: [ statusview ]
});
me.on('activate', rstore.startUpdate);
me.on('hide', rstore.stopUpdate);
me.on('destroy', rstore.stopUpdate);
me.callParent();
}
});
Ext.define('PVE.pool.Config', {
extend: 'PVE.panel.Config',
alias: 'widget.pvePoolConfig',
initComponent: function() {
var me = this;
var pool = me.pveSelNode.data.pool;
if (!pool) {
throw "no pool specified";
}
Ext.apply(me, {
title: Ext.String.format(gettext("Resource Pool") + ': ' + pool),
hstateid: 'pooltab',
items: [
{
title: gettext('Summary'),
xtype: 'pvePoolSummary',
itemId: 'summary'
},
{
title: gettext('Members'),
xtype: 'pvePoolMembers',
pool: pool,
itemId: 'members'
},
{
xtype: 'pveACLView',
title: gettext('Permissions'),
itemId: 'permissions',
path: '/pool/' + pool
}
]
});
me.callParent();
}
});
Ext.define('PVE.grid.TemplateSelector', {
extend: 'Ext.grid.GridPanel',
alias: 'widget.pveTemplateSelector',
stateful: false,
viewConfig: {
trackOver: false
},
initComponent : function() {
var me = this;
if (!me.nodename) {
throw "no node name specified";
}
var baseurl = "/nodes/" + me.nodename + "/aplinfo";
var store = new Ext.data.Store({
model: 'pve-aplinfo',
groupField: 'section',
proxy: {
type: 'pve',
url: '/api2/json' + baseurl
}
});
var sm = Ext.create('Ext.selection.RowModel', {});
var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{
groupHeaderTpl: '{[ "Section: " + values.name ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
});
var reload = function() {
store.load();
};
PVE.Utils.monStoreErrors(me, store);
Ext.apply(me, {
store: store,
selModel: sm,
features: [ groupingFeature ],
columns: [
{
header: gettext('Type'),
width: 80,
dataIndex: 'type'
},
{
header: gettext('Package'),
flex: 1,
dataIndex: 'package'
},
{
header: gettext('Version'),
width: 80,
dataIndex: 'version'
},
{
header: gettext('Description'),
flex: 1.5,
renderer: Ext.String.htmlEncode,
dataIndex: 'headline'
}
],
listeners: {
afterRender: reload
}
});
me.callParent();
}
}, function() {
Ext.define('pve-aplinfo', {
extend: 'Ext.data.Model',
fields: [
'template', 'type', 'package', 'version', 'headline', 'infopage',
'description', 'os', 'section'
],
idProperty: 'template'
});
});
Ext.define('PVE.storage.TemplateDownload', {
extend: 'Ext.window.Window',
alias: 'widget.pveTemplateDownload',
modal: true,
title: gettext('Templates'),
layout: 'fit',
width: 600,
height: 400,
initComponent : function() {
/*jslint confusion: true */
var me = this;
var grid = Ext.create('PVE.grid.TemplateSelector', {
border: false,
scrollable: true,
nodename: me.nodename
});
var sm = grid.getSelectionModel();
var submitBtn = Ext.create('PVE.button.Button', {
text: gettext('Download'),
disabled: true,
selModel: sm,
handler: function(button, event, rec) {
PVE.Utils.API2Request({
url: '/nodes/' + me.nodename + '/aplinfo',
params: {
storage: me.storage,
template: rec.data.template
},
method: 'POST',
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
success: function(response, options) {
var upid = response.result.data;
var win = Ext.create('PVE.window.TaskViewer', {
upid: upid
});
win.show();
me.close();
}
});
}
});
Ext.apply(me, {
items: grid,
buttons: [ submitBtn ]
});
me.callParent();
}
});
Ext.define('PVE.storage.Upload', {
extend: 'Ext.window.Window',
alias: 'widget.pveStorageUpload',
resizable: false,
modal: true,
initComponent : function() {
/*jslint confusion: true */
var me = this;
var xhr;
if (!me.nodename) {
throw "no node name specified";
}
if (!me.storage) {
throw "no storage ID specified";
}
var baseurl = "/nodes/" + me.nodename + "/storage/" + me.storage + "/upload";
var pbar = Ext.create('Ext.ProgressBar', {
text: 'Ready',
hidden: true
});
me.formPanel = Ext.create('Ext.form.Panel', {
method: 'POST',
waitMsgTarget: true,
bodyPadding: 10,
border: false,
width: 300,
fieldDefaults: {
labelWidth: 100,
anchor: '100%'
},
items: [
{
xtype: 'pveContentTypeSelector',
cts: me.contents,
fieldLabel: gettext('Content'),
name: 'content',
value: me.contents[0] || '',
allowBlank: false
},
{
xtype: 'filefield',
name: 'filename',
buttonText: gettext('Select File...'),
allowBlank: false
},
pbar
]
});
var form = me.formPanel.getForm();
var doStandardSubmit = function() {
form.submit({
url: "/api2/htmljs" + baseurl,
waitMsg: gettext('Uploading file...'),
success: function(f, action) {
me.close();
},
failure: function(f, action) {
var msg = PVE.Utils.extractFormActionError(action);
Ext.Msg.alert(gettext('Error'), msg);
}
});
};
var updateProgress = function(per, bytes) {
var text = (per * 100).toFixed(2) + '%';
if (bytes) {
text += " (" + PVE.Utils.format_size(bytes) + ')';
}
pbar.updateProgress(per, text);
};
var abortBtn = Ext.create('Ext.Button', {
text: gettext('Abort'),
disabled: true,
handler: function() {
me.close();
}
});
var submitBtn = Ext.create('Ext.Button', {
text: gettext('Upload'),
disabled: true,
handler: function(button) {
var fd;
try {
fd = new FormData();
} catch (err) {
doStandardSubmit();
return;
}
button.setDisabled(true);
abortBtn.setDisabled(false);
var field = form.findField('content');
fd.append("content", field.getValue());
field.setDisabled(true);
field = form.findField('filename');
var file = field.fileInputEl.dom;
fd.append("filename", file.files[0]);
field.setDisabled(true);
pbar.setVisible(true);
updateProgress(0);
xhr = new XMLHttpRequest();
xhr.addEventListener("load", function(e) {
if (xhr.status == 200) {
me.close();
} else {
var msg = gettext('Error') + " " + xhr.status.toString() + ": " + Ext.htmlEncode(xhr.statusText);
var result = Ext.decode(xhr.responseText);
result.message = msg;
var htmlStatus = PVE.Utils.extractRequestError(result, true);
Ext.Msg.alert(gettext('Error'), htmlStatus, function(btn) {
me.close();
});
}
}, false);
xhr.addEventListener("error", function(e) {
var msg = "Error " + e.target.status.toString() + " occurred while receiving the document.";
Ext.Msg.alert(gettext('Error'), msg, function(btn) {
me.close();
});
});
xhr.upload.addEventListener("progress", function(evt) {
if (evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
updateProgress(percentComplete, evt.loaded);
}
}, false);
xhr.open("POST", "/api2/json" + baseurl, true);
xhr.send(fd);
}
});
form.on('validitychange', function(f, valid) {
submitBtn.setDisabled(!valid);
});
Ext.apply(me, {
title: gettext('Upload'),
items: me.formPanel,
buttons: [ abortBtn, submitBtn ],
listeners: {
close: function() {
if (xhr) {
xhr.abort();
}
}
}
});
me.callParent();
}
});
Ext.define('PVE.storage.ContentView', {
extend: 'Ext.grid.GridPanel',
alias: 'widget.pveStorageContentView',
stateful: false,
viewConfig: {
trackOver: false,
loadMask: false
},
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var storage = me.pveSelNode.data.storage;
if (!storage) {
throw "no storage ID specified";
}
var baseurl = "/nodes/" + nodename + "/storage/" + storage + "/content";
var store = Ext.create('Ext.data.Store',{
model: 'pve-storage-content',
groupField: 'content',
proxy: {
type: 'pve',
url: '/api2/json' + baseurl
},
sorters: {
property: 'volid',
order: 'DESC'
}
});
var sm = Ext.create('Ext.selection.RowModel', {});
var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{
groupHeaderTpl: '{[ PVE.Utils.format_content_types(values.name) ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
});
var reload = function() {
store.load();
me.statusStore.load();
};
PVE.Utils.monStoreErrors(me, store);
var templateButton = Ext.create('PVE.button.Button',{
itemId: 'tmpl-btn',
text: gettext('Templates'),
handler: function() {
var win = Ext.create('PVE.storage.TemplateDownload', {
nodename: nodename,
storage: storage
});
win.show();
win.on('destroy', reload);
}
});
var uploadButton = Ext.create('PVE.button.Button', {
contents : ['iso','vztmpl'],
text: gettext('Upload'),
handler: function() {
var me = this;
var win = Ext.create('PVE.storage.Upload', {
nodename: nodename,
storage: storage,
contents: me.contents
});
win.show();
win.on('destroy', reload);
}
});
me.statusStore = Ext.create('PVE.data.ObjectStore', {
url: '/api2/json/nodes/' + nodename + '/storage/' + storage + '/status'
});
Ext.apply(me, {
store: store,
selModel: sm,
features: [ groupingFeature ],
tbar: [
{
xtype: 'pveButton',
text: gettext('Restore'),
selModel: sm,
disabled: true,
enableFn: function(rec) {
return rec && rec.data.content === 'backup';
},
handler: function(b, e, rec) {
var vmtype;
if (rec.data.volid.match(/vzdump-qemu-/)) {
vmtype = 'qemu';
} else if (rec.data.volid.match(/vzdump-openvz-/) || rec.data.volid.match(/vzdump-lxc-/)) {
vmtype = 'lxc';
} else {
return;
}
var win = Ext.create('PVE.window.Restore', {
nodename: nodename,
volid: rec.data.volid,
volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
vmtype: vmtype
});
win.show();
win.on('destroy', reload);
}
},
{
xtype: 'pveButton',
text: gettext('Remove'),
selModel: sm,
disabled: true,
confirmMsg: function(rec) {
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.volid + "'");
},
enableFn: function(rec) {
return rec && rec.data.content !== 'images';
},
handler: function(b, e, rec) {
PVE.Utils.API2Request({
url: baseurl + '/' + rec.data.volid,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
},
templateButton,
uploadButton,
'->',
gettext('Search') + ':', ' ',
{
xtype: 'textfield',
width: 200,
enableKeyEvents: true,
listeners: {
buffer: 500,
keyup: function(field) {
store.clearFilter(true);
store.filter([
{
property: 'text',
value: field.getValue(),
anyMatch: true,
caseSensitive: false
}
]);
}
}
}
],
columns: [
{
header: gettext('Name'),
flex: 1,
sortable: true,
renderer: PVE.Utils.render_storage_content,
dataIndex: 'text'
},
{
header: gettext('Format'),
width: 100,
dataIndex: 'format'
},
{
header: gettext('Size'),
width: 100,
renderer: PVE.Utils.format_size,
dataIndex: 'size'
}
],
listeners: {
activate: reload
}
});
me.callParent();
// disable the buttons/restrict the upload window
// if templates or uploads are not allowed
me.mon(me.statusStore, 'load', function(s,records,succes) {
var availcontent = [];
Ext.Array.each(records, function(item){
if (item.id === 'content') {
availcontent = item.data.value.split(',');
}
});
var templ = false;
var upload = false;
var cts = [];
Ext.Array.each(availcontent, function(content) {
if (content === 'vztmpl') {
templ = true;
cts.push('vztmpl');
} else if (content === 'iso') {
upload = true;
cts.push('iso');
}
});
if (templ !== upload) {
uploadButton.contents = cts;
}
templateButton.setDisabled(!templ);
uploadButton.setDisabled(!upload && !templ);
});
}
}, function() {
Ext.define('pve-storage-content', {
extend: 'Ext.data.Model',
fields: [
'volid', 'content', 'format', 'size', 'used', 'vmid',
'channel', 'id', 'lun',
{
name: 'text',
convert: function(value, record) {
// check for volid, because if you click on a grouping header,
// it calls convert (but with an empty volid)
if (value || record.data.volid === null) {
return value;
}
return PVE.Utils.render_storage_content(value, {}, record);
}
}
],
idProperty: 'volid'
});
});
Ext.define('PVE.storage.StatusView', {
extend: 'PVE.grid.ObjectGrid',
alias: 'widget.pveStorageStatusView',
disabled: true,
title: gettext('Status'),
cwidth1: 150,
interval: 30000,
rows : {
disable: {
header: gettext('Enabled'),
required: true,
renderer: PVE.Utils.format_neg_boolean
},
active: {
header: gettext('Active'),
required: true,
renderer: PVE.Utils.format_boolean
},
content: {
header: gettext('Content'),
required: true,
renderer: PVE.Utils.format_content_types
},
type: {
header: gettext('Type'),
required: true,
renderer: PVE.Utils.format_storage_type
},
shared: {
header: gettext('Shared'),
required: true,
renderer: PVE.Utils.format_boolean
},
total: {
header: gettext('Size'),
required: true,
renderer: PVE.Utils.render_size
},
used: {
header: gettext('Used'),
required: true,
renderer: PVE.Utils.render_size
},
avail: {
header: gettext('Avail'),
required: true,
renderer: PVE.Utils.render_size
}
},
initComponent : function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var storage = me.pveSelNode.data.storage;
if (!storage) {
throw "no storage ID specified";
}
Ext.apply(me, {
url: "/api2/json/nodes/" + nodename + "/storage/" + storage + "/status"
});
me.callParent();
}
});
Ext.define('PVE.storage.Summary', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveStorageSummary',
scrollable: true,
bodyPadding: 10,
defaults: {
style: {'padding-top':'10px'},
width: 800
},
tbar: [
'->',
{
xtype: 'pveRRDTypeSelector'
}
],
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var storage = me.pveSelNode.data.storage;
if (!storage) {
throw "no storage ID specified";
}
var statusview = Ext.create('PVE.storage.StatusView', {
pveSelNode: me.pveSelNode,
style: {'padding-top':'0px'}
});
var rstore = statusview.rstore;
var rrdstore = Ext.create('PVE.data.RRDStore', {
rrdurl: "/api2/json/nodes/" + nodename + "/storage/" + storage + "/rrddata"
});
Ext.apply(me, {
items: [
statusview,
{
xtype: 'pveRRDChart',
title: gettext('Usage'),
fields: ['total','used'],
fieldTitles: ['Total Size', 'Used Size'],
store: rrdstore
}
],
listeners: {
activate: function() { rstore.startUpdate(); rrdstore.startUpdate(); },
hide: function() { rstore.stopUpdate(); rrdstore.stopUpdate(); },
destroy: function() { rstore.stopUpdate(); rrdstore.stopUpdate(); }
}
});
me.callParent();
}
});
Ext.define('PVE.storage.Browser', {
extend: 'PVE.panel.Config',
alias: 'widget.PVE.storage.Browser',
initComponent: function() {
var me = this;
var nodename = me.pveSelNode.data.node;
if (!nodename) {
throw "no node name specified";
}
var storeid = me.pveSelNode.data.storage;
if (!storeid) {
throw "no storage ID specified";
}
me.items = [
{
title: gettext('Summary'),
xtype: 'pveStorageSummary',
itemId: 'summary'
}
];
var caps = Ext.state.Manager.get('GuiCap');
Ext.apply(me, {
title: Ext.String.format(gettext("Storage {0} on node {1}"),
"'" + storeid + "'", "'" + nodename + "'"),
hstateid: 'storagetab'
});
if (caps.storage['Datastore.Allocate']) {
me.items.push({
xtype: 'pveStorageContentView',
title: gettext('Content'),
itemId: 'content'
});
}
if (caps.storage['Permissions.Modify']) {
me.items.push({
xtype: 'pveACLView',
title: gettext('Permissions'),
itemId: 'permissions',
path: '/storage/' + storeid
});
}
me.callParent();
}
});
Ext.define('PVE.storage.DirInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'dir';
} else {
delete values.storage;
}
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'path',
value: '',
fieldLabel: gettext('Directory'),
allowBlank: false
},
{
xtype: 'pveContentTypeSelector',
name: 'content',
value: 'images',
multiSelect: true,
fieldLabel: gettext('Content'),
allowBlank: false
}
];
me.column2 = [
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
},
{
xtype: 'pvecheckbox',
name: 'shared',
uncheckedValue: 0,
fieldLabel: gettext('Shared')
},
{
xtype: 'numberfield',
fieldLabel: gettext('Max Backups'),
name: 'maxfiles',
minValue: 0,
maxValue: 365,
value: me.create ? '1' : undefined,
allowBlank: false
}
];
if (me.create || me.storageId !== 'local') {
me.column2.unshift({
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
});
}
me.callParent();
}
});
Ext.define('PVE.storage.DirEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.DirInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: PVE.Utils.format_storage_type('dir'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
var ctypes = values.content || '';
values.content = ctypes.split(',');
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.storage.NFSScan', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.pveNFSScan',
queryParam: 'server',
valueField: 'path',
displayField: 'path',
matchFieldWidth: false,
listConfig: {
loadingText: gettext('Scanning...'),
width: 350
},
doRawQuery: function() {
},
onTriggerClick: function() {
var me = this;
if (!me.queryCaching || me.lastQuery !== me.nfsServer) {
me.store.removeAll();
}
me.allQuery = me.nfsServer;
me.callParent();
},
setServer: function(server) {
var me = this;
me.nfsServer = server;
},
initComponent : function() {
var me = this;
if (!me.nodename) {
me.nodename = 'localhost';
}
var store = Ext.create('Ext.data.Store', {
fields: [ 'path', 'options' ],
proxy: {
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/scan/nfs'
}
});
Ext.apply(me, {
store: store
});
me.callParent();
}
});
Ext.define('PVE.storage.NFSInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'nfs';
// hack: for now we always create nvf v3
// fixme: make this configurable
values.options = 'vers=3';
} else {
delete values.storage;
}
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'server',
value: '',
fieldLabel: gettext('Server'),
allowBlank: false,
listeners: {
change: function(f, value) {
if (me.create) {
var exportField = me.down('field[name=export]');
exportField.setServer(value);
exportField.setValue('');
}
}
}
},
{
xtype: me.create ? 'pveNFSScan' : 'displayfield',
name: 'export',
value: '',
fieldLabel: 'Export',
allowBlank: false
},
{
xtype: 'pveContentTypeSelector',
name: 'content',
value: 'images',
multiSelect: true,
fieldLabel: gettext('Content'),
allowBlank: false
}
];
me.column2 = [
{
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
},
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
},
{
xtype: 'numberfield',
fieldLabel: gettext('Max Backups'),
name: 'maxfiles',
minValue: 0,
maxValue: 365,
value: me.create ? '1' : undefined,
allowBlank: false
}
];
me.callParent();
}
});
Ext.define('PVE.storage.NFSEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.NFSInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: 'NFS',
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
var ctypes = values.content || '';
values.content = ctypes.split(',');
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.storage.GlusterFsScan', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.pveGlusterFsScan',
queryParam: 'server',
valueField: 'volname',
displayField: 'volname',
matchFieldWidth: false,
listConfig: {
loadingText: 'Scanning...',
width: 350
},
doRawQuery: function() {
},
onTriggerClick: function() {
var me = this;
if (!me.queryCaching || me.lastQuery !== me.glusterServer) {
me.store.removeAll();
}
me.allQuery = me.glusterServer;
me.callParent();
},
setServer: function(server) {
var me = this;
me.glusterServer = server;
},
initComponent : function() {
var me = this;
if (!me.nodename) {
me.nodename = 'localhost';
}
var store = Ext.create('Ext.data.Store', {
fields: [ 'volname' ],
proxy: {
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/scan/glusterfs'
}
});
Ext.apply(me, {
store: store
});
me.callParent();
}
});
Ext.define('PVE.storage.GlusterFsInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'glusterfs';
} else {
delete values.storage;
}
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'server',
value: '',
fieldLabel: gettext('Server'),
allowBlank: false,
listeners: {
change: function(f, value) {
if (me.create) {
var volumeField = me.down('field[name=volume]');
volumeField.setServer(value);
volumeField.setValue('');
}
}
}
},
{
xtype: me.create ? 'pvetextfield' : 'displayfield',
name: 'server2',
value: '',
fieldLabel: gettext('Second Server'),
allowBlank: true
},
{
xtype: me.create ? 'pveGlusterFsScan' : 'displayfield',
name: 'volume',
value: '',
fieldLabel: 'Volume name',
allowBlank: false
},
{
xtype: 'pveContentTypeSelector',
cts: ['images', 'iso', 'backup', 'vztmpl'],
name: 'content',
value: 'images',
multiSelect: true,
fieldLabel: gettext('Content'),
allowBlank: false
}
];
me.column2 = [
{
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
},
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
},
{
xtype: 'numberfield',
fieldLabel: gettext('Max Backups'),
name: 'maxfiles',
minValue: 0,
maxValue: 365,
value: me.create ? '1' : undefined,
allowBlank: false
}
];
me.callParent();
}
});
Ext.define('PVE.storage.GlusterFsEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.GlusterFsInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: PVE.Utils.format_storage_type('glusterfs'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
var ctypes = values.content || '';
values.content = ctypes.split(',');
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.storage.IScsiScan', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.pveIScsiScan',
queryParam: 'portal',
valueField: 'target',
displayField: 'target',
matchFieldWidth: false,
listConfig: {
loadingText: gettext('Scanning...'),
width: 350
},
doRawQuery: function() {
},
onTriggerClick: function() {
var me = this;
if (!me.queryCaching || me.lastQuery !== me.portal) {
me.store.removeAll();
}
me.allQuery = me.portal;
me.callParent();
},
setPortal: function(portal) {
var me = this;
me.portal = portal;
},
initComponent : function() {
var me = this;
if (!me.nodename) {
me.nodename = 'localhost';
}
var store = Ext.create('Ext.data.Store', {
fields: [ 'target', 'portal' ],
proxy: {
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/scan/iscsi'
}
});
Ext.apply(me, {
store: store
});
me.callParent();
}
});
Ext.define('PVE.storage.IScsiInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'iscsi';
} else {
delete values.storage;
}
values.content = values.luns ? 'images' : 'none';
delete values.luns;
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'portal',
value: '',
fieldLabel: 'Portal',
allowBlank: false,
listeners: {
change: function(f, value) {
if (me.create) {
var exportField = me.down('field[name=target]');
exportField.setPortal(value);
exportField.setValue('');
}
}
}
},
{
readOnly: !me.create,
xtype: me.create ? 'pveIScsiScan' : 'displayfield',
name: 'target',
value: '',
fieldLabel: 'Target',
allowBlank: false
}
];
me.column2 = [
{
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
},
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
},
{
xtype: 'checkbox',
name: 'luns',
checked: true,
fieldLabel: gettext('Use LUNs directly')
}
];
me.callParent();
}
});
Ext.define('PVE.storage.IScsiEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.IScsiInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: PVE.Utils.format_storage_type('iscsi'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
var ctypes = values.content || '';
if (values.storage === 'local') {
values.content = ctypes.split(',');
}
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
values.luns = (values.content === 'images') ? true : false;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.storage.VgSelector', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.pveVgSelector',
valueField: 'vg',
displayField: 'vg',
queryMode: 'local',
editable: false,
initComponent : function() {
var me = this;
if (!me.nodename) {
me.nodename = 'localhost';
}
var store = Ext.create('Ext.data.Store', {
autoLoad: {}, // true,
fields: [ 'vg', 'size', 'free' ],
proxy: {
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/scan/lvm'
}
});
Ext.apply(me, {
store: store,
listConfig: {
loadingText: gettext('Scanning...')
}
});
me.callParent();
}
});
Ext.define('PVE.storage.BaseStorageSelector', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.pveBaseStorageSelector',
existingGroupsText: gettext("Existing volume groups"),
queryMode: 'local',
editable: false,
value: '',
valueField: 'storage',
displayField: 'text',
initComponent : function() {
var me = this;
var store = Ext.create('Ext.data.Store', {
autoLoad: {
addRecords: true,
params: {
type: 'iscsi'
}
},
fields: [ 'storage', 'type', 'content',
{
name: 'text',
convert: function(value, record) {
if (record.data.storage) {
return record.data.storage + " (iSCSI)";
} else {
return me.existingGroupsText;
}
}
}],
proxy: {
type: 'pve',
url: '/api2/json/storage/'
}
});
store.loadData([{ storage: '' }], true);
Ext.apply(me, {
store: store
});
me.callParent();
}
});
Ext.define('PVE.storage.LVMInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'lvm';
} else {
delete values.storage;
}
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
submitValue: !!me.create,
allowBlank: false
}
];
var vgnameField = Ext.createWidget(me.create ? 'textfield' : 'displayfield', {
name: 'vgname',
hidden: !!me.create,
disabled: !!me.create,
value: '',
fieldLabel: gettext('Volume group'),
allowBlank: false
});
if (me.create) {
var vgField = Ext.create('PVE.storage.VgSelector', {
name: 'vgname',
fieldLabel: gettext('Volume group'),
allowBlank: false
});
var baseField = Ext.createWidget('pveFileSelector', {
name: 'base',
hidden: true,
disabled: true,
nodename: 'localhost',
storageContent: 'images',
fieldLabel: gettext('Base volume'),
allowBlank: false
});
me.column1.push({
xtype: 'pveBaseStorageSelector',
name: 'basesel',
fieldLabel: gettext('Base storage'),
submitValue: false,
listeners: {
change: function(f, value) {
if (value) {
vgnameField.setVisible(true);
vgnameField.setDisabled(false);
vgField.setVisible(false);
vgField.setDisabled(true);
baseField.setVisible(true);
baseField.setDisabled(false);
} else {
vgnameField.setVisible(false);
vgnameField.setDisabled(true);
vgField.setVisible(true);
vgField.setDisabled(false);
baseField.setVisible(false);
baseField.setDisabled(true);
}
baseField.setStorage(value);
}
}
});
me.column1.push(baseField);
me.column1.push(vgField);
}
me.column1.push(vgnameField);
// here value is an array,
// while before it was a string
/*jslint confusion: true*/
me.column1.push({
xtype: 'pveContentTypeSelector',
cts: ['images', 'rootdir'],
fieldLabel: gettext('Content'),
name: 'content',
value: ['images', 'rootdir'],
multiSelect: true,
allowBlank: false
});
/*jslint confusion: false*/
me.column2 = [
{
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
},
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
},
{
xtype: 'pvecheckbox',
name: 'shared',
uncheckedValue: 0,
fieldLabel: gettext('Shared')
}
];
me.callParent();
}
});
Ext.define('PVE.storage.LVMEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.LVMInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: PVE.Utils.format_storage_type('lvm'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
var ctypes = values.content || '';
values.content = ctypes.split(',');
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.storage.TPoolSelector', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.pveTPSelector',
queryParam: 'vg',
valueField: 'lv',
displayField: 'lv',
editable: false,
doRawQuery: function() {
},
onTriggerClick: function() {
var me = this;
if (!me.queryCaching || me.lastQuery !== me.vg) {
me.store.removeAll();
}
me.allQuery = me.vg;
me.callParent();
},
setVG: function(myvg) {
var me = this;
me.vg = myvg;
},
initComponent : function() {
var me = this;
if (!me.nodename) {
me.nodename = 'localhost';
}
var store = Ext.create('Ext.data.Store', {
fields: [ 'lv' ],
proxy: {
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/scan/lvmthin'
}
});
Ext.apply(me, {
store: store,
listConfig: {
loadingText: gettext('Scanning...')
}
});
me.callParent();
}
});
Ext.define('PVE.storage.BaseVGSelector', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.pveBaseVGSelector',
valueField: 'vg',
displayField: 'vg',
queryMode: 'local',
editable: false,
initComponent : function() {
var me = this;
if (!me.nodename) {
me.nodename = 'localhost';
}
var store = Ext.create('Ext.data.Store', {
autoLoad: {},
fields: [ 'vg', 'size', 'free'],
proxy: {
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/scan/lvm'
}
});
Ext.apply(me, {
store: store,
listConfig: {
loadingText: gettext('Scanning...')
}
});
me.callParent();
}
});
Ext.define('PVE.storage.LvmThinInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'lvmthin';
} else {
delete values.storage;
}
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
submitValue: !!me.create,
allowBlank: false
}
];
var vgnameField = Ext.createWidget(me.create ? 'textfield' : 'displayfield', {
name: 'vgname',
hidden: !!me.create,
disabled: !!me.create,
value: '',
fieldLabel: gettext('Volume group'),
allowBlank: false
});
var thinpoolField = Ext.createWidget(me.create ? 'textfield' : 'displayfield', {
name: 'thinpool',
hidden: !!me.create,
disabled: !!me.create,
value: '',
fieldLabel: gettext('Thin Pool'),
allowBlank: false
});
if (me.create) {
var vgField = Ext.create('PVE.storage.TPoolSelector', {
name: 'thinpool',
fieldLabel: gettext('Thin Pool'),
allowBlank: false
});
me.column1.push({
xtype: 'pveBaseVGSelector',
name: 'vgname',
fieldLabel: gettext('Volume group'),
listeners: {
change: function(f, value) {
if (me.create) {
vgField.setVG(value);
vgField.setValue('');
}
}
}
});
me.column1.push(vgField);
}
me.column1.push(vgnameField);
me.column1.push(thinpoolField);
// here value is an array,
// while before it was a string
/*jslint confusion: true*/
me.column1.push({
xtype: 'pveContentTypeSelector',
cts: ['images', 'rootdir'],
fieldLabel: gettext('Content'),
name: 'content',
value: ['images', 'rootdir'],
multiSelect: true,
allowBlank: false
});
/*jslint confusion: false*/
me.column2 = [
{
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
},
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
}
];
me.callParent();
}
});
Ext.define('PVE.storage.LvmThinEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.LvmThinInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: PVE.Utils.format_storage_type('lvmthin'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
var ctypes = values.content || '';
values.content = ctypes.split(',');
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.storage.RBDInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'rbd';
} else {
delete values.storage;
}
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'pool',
value: 'rbd',
fieldLabel: gettext('Pool'),
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'monhost',
value: '',
fieldLabel: gettext('Monitor Host'),
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'username',
value: 'admin',
fieldLabel: gettext('User name'),
allowBlank: true
}
];
// here value is an array,
// while before it was a string
/*jslint confusion: true*/
me.column2 = [
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
},
{
xtype: 'pveContentTypeSelector',
cts: ['images', 'rootdir'],
fieldLabel: gettext('Content'),
name: 'content',
value: ['images'],
multiSelect: true,
allowBlank: false
},
{
xtype: 'pvecheckbox',
name: 'krbd',
uncheckedValue: 0,
fieldLabel: gettext('KRBD')
}
];
/*jslint confusion: false*/
if (me.create || me.storageId !== 'local') {
me.column2.unshift({
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
});
}
me.callParent();
}
});
Ext.define('PVE.storage.RBDEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.RBDInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: PVE.Utils.format_storage_type('rbd'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
var ctypes = values.content || '';
values.content = ctypes.split(',');
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.storage.SheepdogInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'sheepdog';
values.content = 'images';
} else {
delete values.storage;
}
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'portal',
value: '127.0.0.1:7000',
fieldLabel: gettext('Gateway'),
allowBlank: false
}
];
me.column2 = [
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
}
];
if (me.create || me.storageId !== 'local') {
me.column2.unshift({
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
});
}
me.callParent();
}
});
Ext.define('PVE.storage.SheepdogEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.SheepdogInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: PVE.Utils.format_storage_type('sheepdog'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.storage.ZFSInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'zfs';
values.content = 'images';
} else {
delete values.storage;
}
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'portal',
value: '',
fieldLabel: gettext('Portal'),
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'pool',
value: '',
fieldLabel: gettext('Pool'),
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'blocksize',
value: '4k',
fieldLabel: gettext('Block Size'),
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'target',
value: '',
fieldLabel: gettext('Target'),
allowBlank: false
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'comstar_tg',
value: '',
fieldLabel: gettext('Target group'),
allowBlank: true
}
];
me.column2 = [
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
},
{
xtype: me.create ? 'pveiScsiProviderSelector' : 'displayfield',
name: 'iscsiprovider',
value: 'comstar',
fieldLabel: gettext('iSCSI Provider'),
allowBlank: false
},
{
xtype: 'pvecheckbox',
name: 'sparse',
checked: false,
uncheckedValue: 0,
fieldLabel: gettext('Thin provision')
},
{
xtype: 'pvecheckbox',
name: 'nowritecache',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Write cache')
},
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'comstar_hg',
value: '',
fieldLabel: gettext('Host group'),
allowBlank: true
}
];
if (me.create || me.storageId !== 'local') {
me.column2.unshift({
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
});
}
me.callParent();
}
});
Ext.define('PVE.storage.ZFSEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.ZFSInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: 'ZFS Storage',
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.storage.ZFSPoolSelector', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.pveZFSPoolSelector',
valueField: 'pool',
displayField: 'pool',
queryMode: 'local',
editable: false,
listConfig: {
loadingText: gettext('Scanning...')
},
initComponent : function() {
var me = this;
if (!me.nodename) {
me.nodename = 'localhost';
}
var store = Ext.create('Ext.data.Store', {
autoLoad: {}, // true,
fields: [ 'pool', 'size', 'free' ],
proxy: {
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/scan/zfs'
}
});
Ext.apply(me, {
store: store
});
me.callParent();
}
});
Ext.define('PVE.storage.ZFSPoolInputPanel', {
extend: 'PVE.panel.InputPanel',
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'zfspool';
} else {
delete values.storage;
}
values.disable = values.enable ? 0 : 1;
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'storage',
value: me.storageId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
allowBlank: false
}
];
if (me.create) {
me.column1.push(Ext.create('PVE.storage.ZFSPoolSelector', {
name: 'pool',
fieldLabel: gettext('ZFS Pool'),
allowBlank: false
}));
} else {
me.column1.push(Ext.createWidget('displayfield', {
name: 'pool',
value: '',
fieldLabel: gettext('ZFS Pool'),
allowBlank: false
}));
}
// value is an array,
// while before it was a string
/*jslint confusion: true*/
me.column1.push(
{xtype: 'pveContentTypeSelector',
cts: ['images', 'rootdir'],
fieldLabel: gettext('Content'),
name: 'content',
value: ['images', 'rootdir'],
multiSelect: true,
allowBlank: false
});
/*jslint confusion: false*/
me.column2 = [
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Enable')
},
{
xtype: 'pvecheckbox',
name: 'sparse',
checked: false,
uncheckedValue: 0,
fieldLabel: gettext('Thin provision')
}
];
if (me.create || me.storageId !== 'local') {
me.column2.unshift({
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
emptyText: gettext('All') + ' (' +
gettext('No restrictions') +')',
multiSelect: true,
autoSelect: false
});
}
me.callParent();
}
});
Ext.define('PVE.storage.ZFSPoolEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
me.create = !me.storageId;
if (me.create) {
me.url = '/api2/extjs/storage';
me.method = 'POST';
} else {
me.url = '/api2/extjs/storage/' + me.storageId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.storage.ZFSPoolInputPanel', {
create: me.create,
storageId: me.storageId
});
Ext.apply(me, {
subject: PVE.Utils.format_storage_type('zfspool'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
var ctypes = values.content || '';
values.content = ctypes.split(',');
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
values.enable = values.disable ? 0 : 1;
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.ha.StatusView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveHAStatusView'],
sortPriority: {
quorum: 1,
master: 2,
lrm: 3,
service: 4
},
initComponent : function() {
var me = this;
me.rstore = Ext.create('PVE.data.ObjectStore', {
interval: me.interval,
model: 'pve-ha-status',
storeid: 'pve-store-' + (++Ext.idSeed),
groupField: 'type',
proxy: {
type: 'pve',
url: '/api2/json/cluster/ha/status/current'
}
});
PVE.Utils.monStoreErrors(me, me.rstore);
var store = Ext.create('PVE.data.DiffStore', {
rstore: me.rstore,
sortAfterUpdate: true,
sorters: [{
sorterFn: function(rec1, rec2) {
var p1 = me.sortPriority[rec1.data.type];
var p2 = me.sortPriority[rec2.data.type];
return (p1 !== p2) ? ((p1 > p2) ? 1 : -1) : 0;
}
}]
});
Ext.apply(me, {
store: store,
stateful: false,
viewConfig: {
trackOver: false
},
columns: [
{
header: gettext('Type'),
width: 80,
dataIndex: 'type'
},
{
header: gettext('Status'),
width: 80,
flex: 1,
dataIndex: 'status'
}
]
});
me.callParent();
me.on('activate', me.rstore.startUpdate);
me.on('hide', me.rstore.stopUpdate);
me.on('destroy', me.rstore.stopUpdate);
}
}, function() {
Ext.define('pve-ha-status', {
extend: 'Ext.data.Model',
fields: [
'id', 'type', 'node', 'status', 'sid'
],
idProperty: 'id'
});
});
Ext.define('PVE.ha.GroupSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.pveHAGroupSelector'],
autoSelect: false,
valueField: 'group',
displayField: 'group',
listConfig: {
columns: [
{
header: gettext('Group'),
width: 100,
sortable: true,
dataIndex: 'group'
},
{
header: gettext('Nodes'),
width: 100,
sortable: false,
dataIndex: 'nodes'
},
{
header: gettext('Comment'),
flex: 1,
dataIndex: 'comment',
renderer: Ext.String.htmlEncode
}
]
},
store: {
model: 'pve-ha-groups',
sorters: {
property: 'group',
order: 'DESC'
}
},
initComponent: function() {
var me = this;
me.callParent();
me.getStore().load();
}
}, function() {
Ext.define('pve-ha-groups', {
extend: 'Ext.data.Model',
fields: [
'group', 'type', 'restricted', 'digest', 'nofailback',
'nodes', 'comment'
],
proxy: {
type: 'pve',
url: "/api2/json/cluster/ha/groups"
},
idProperty: 'group'
});
});
Ext.define('PVE.ha.VMResourceInputPanel', {
extend: 'PVE.panel.InputPanel',
vmid: undefined,
onGetValues: function(values) {
var me = this;
if (me.create) {
values.sid = values.vmid;
}
if (values.group === '') {
if (!me.create) {
values['delete'] = values['delete'] ? ',group' : 'group';
}
delete values.group;
}
delete values.vmid;
if (values.enable) {
values.state = 'enabled';
} else {
values.state = 'disabled';
}
delete values.enable;
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.vmid ? 'displayfield' : 'pveVMIDSelector',
name: 'vmid',
fieldLabel: 'VM ID',
value: me.vmid,
loadNextFreeVMID: false,
validateExists: true
}
];
me.column2 = [
{
xtype: 'pveHAGroupSelector',
name: 'group',
value: '',
fieldLabel: gettext('Group')
},
{
xtype: 'pvecheckbox',
name: 'enable',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('enable')
}
];
me.columnB = [
{
xtype: 'textfield',
name: 'comment',
fieldLabel: gettext('Comment')
}
];
me.callParent();
}
});
Ext.define('PVE.ha.VMResourceEdit', {
extend: 'PVE.window.Edit',
vmid: undefined,
initComponent : function() {
var me = this;
me.create = !me.vmid;
if (me.create) {
me.url = '/api2/extjs/cluster/ha/resources';
me.method = 'POST';
} else {
me.url = '/api2/extjs/cluster/ha/resources/' + me.vmid;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.ha.VMResourceInputPanel', {
create: me.create,
vmid: me.vmid
});
Ext.apply(me, {
subject: gettext('VM Resource'),
isAdd: true,
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
values.enable = true;
if (values.state === 'disabled') {
values.enable = false;
}
var regex = /^(\S+):(\S+)$/;
var res = regex.exec(values.sid);
if (res[1] !== 'vm' && res[1] !== 'ct') {
throw "got unexpected resource type";
}
values.vmid = res[2];
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.ha.ResourcesView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveHAResourcesView'],
initComponent : function() {
var me = this;
var caps = Ext.state.Manager.get('GuiCap');
var store = new Ext.data.Store({
model: 'pve-ha-resources',
proxy: {
type: 'pve',
url: "/api2/json/cluster/ha/resources"
},
sorters: {
property: 'sid',
order: 'DESC'
}
});
var reload = function() {
store.load();
};
var render_error = function(dataIndex, value, metaData, record) {
var errors = record.data.errors;
if (errors) {
var msg = errors[dataIndex];
if (msg) {
metaData.tdCls = 'x-form-invalid-field';
var html = '<p>' + Ext.htmlEncode(msg) + '</p>';
metaData.tdAttr = 'data-qwidth=600 data-qtitle="ERROR" data-qtip="' +
html.replace(/\"/g,'&quot;') + '"';
}
}
return value;
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
var sid = rec.data.sid;
var regex = /^(\S+):(\S+)$/;
var res = regex.exec(sid);
if (res[1] !== 'vm' && res[1] !== 'ct') {
return;
}
var vmid = res[2];
var win = Ext.create('PVE.ha.VMResourceEdit',{
vmid: vmid
});
win.on('destroy', reload);
win.show();
};
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
handler: function(btn, event, rec) {
var sid = rec.data.sid;
PVE.Utils.API2Request({
url: '/cluster/ha/resources/' + sid,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
viewConfig: {
trackOver: false
},
tbar: [
{
text: gettext('Add'),
disabled: !caps.nodes['Sys.Console'],
handler: function() {
var win = Ext.create('PVE.ha.VMResourceEdit',{});
win.on('destroy', reload);
win.show();
}
},
edit_btn, remove_btn
],
columns: [
{
header: 'ID',
width: 100,
sortable: true,
dataIndex: 'sid'
},
{
header: gettext('State'),
width: 100,
sortable: true,
renderer: function(v) {
return v || 'enabled';
},
dataIndex: 'state'
},
{
header: gettext('Group'),
width: 200,
sortable: true,
renderer: function(value, metaData, record) {
return render_error('group', value, metaData, record);
},
dataIndex: 'group'
},
{
header: gettext('Description'),
flex: 1,
renderer: Ext.String.htmlEncode,
dataIndex: 'comment'
}
],
listeners: {
activate: reload,
beforeselect: function(grid, record, index, eOpts) {
if (!caps.nodes['Sys.Console']) {
return false;
}
},
itemdblclick: run_editor
}
});
me.callParent();
}
}, function() {
Ext.define('pve-ha-resources', {
extend: 'Ext.data.Model',
fields: [
'sid', 'state', 'digest', 'errors', 'group', 'comment'
],
idProperty: 'sid'
});
});
Ext.define('PVE.ha.GroupInputPanel', {
extend: 'PVE.panel.InputPanel',
groupId: undefined,
onGetValues: function(values) {
var me = this;
if (me.create) {
values.type = 'group';
}
return values;
},
initComponent : function() {
var me = this;
me.column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'group',
value: me.groupId || '',
fieldLabel: 'ID',
vtype: 'StorageId',
allowBlank: false
},
{
xtype: 'pveNodeSelector',
name: 'nodes',
fieldLabel: gettext('Nodes'),
allowBlank: false,
multiSelect: true,
autoSelect: false
}
];
me.column2 = [
{
xtype: 'pvecheckbox',
name: 'restricted',
uncheckedValue: 0,
fieldLabel: 'restricted'
},
{
xtype: 'pvecheckbox',
name: 'nofailback',
uncheckedValue: 0,
fieldLabel: 'nofailback'
}
];
me.columnB = [
{
xtype: 'textfield',
name: 'comment',
fieldLabel: gettext('Comment')
}
];
me.callParent();
}
});
Ext.define('PVE.ha.GroupEdit', {
extend: 'PVE.window.Edit',
groupId: undefined,
initComponent : function() {
var me = this;
me.create = !me.groupId;
if (me.create) {
me.url = '/api2/extjs/cluster/ha/groups';
me.method = 'POST';
} else {
me.url = '/api2/extjs/cluster/ha/groups/' + me.groupId;
me.method = 'PUT';
}
var ipanel = Ext.create('PVE.ha.GroupInputPanel', {
create: me.create,
groupId: me.groupId
});
Ext.apply(me, {
subject: gettext('HA Group'),
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var values = response.result.data;
if (values.nodes) {
values.nodes = values.nodes.split(',');
}
ipanel.setValues(values);
}
});
}
}
});
Ext.define('PVE.ha.GroupsView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveHAGroupsView'],
initComponent : function() {
var me = this;
var caps = Ext.state.Manager.get('GuiCap');
var store = new Ext.data.Store({
model: 'pve-ha-groups',
sorters: {
property: 'group',
order: 'DESC'
}
});
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
var win = Ext.create('PVE.ha.GroupEdit',{
groupId: rec.data.group
});
win.on('destroy', reload);
win.show();
};
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
handler: function(btn, event, rec) {
var group = rec.data.group;
PVE.Utils.API2Request({
url: '/cluster/ha/groups/' + group,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
viewConfig: {
trackOver: false
},
tbar: [
{
text: gettext('Create'),
disabled: !caps.nodes['Sys.Console'],
handler: function() {
var win = Ext.create('PVE.ha.GroupEdit',{});
win.on('destroy', reload);
win.show();
}
},
edit_btn, remove_btn
],
columns: [
{
header: gettext('Group'),
width: 150,
sortable: true,
dataIndex: 'group'
},
{
header: 'restricted',
width: 100,
sortable: true,
renderer: PVE.Utils.format_boolean,
dataIndex: 'restricted'
},
{
header: 'nofailback',
width: 100,
sortable: true,
renderer: PVE.Utils.format_boolean,
dataIndex: 'nofailback'
},
{
header: gettext('Nodes'),
flex: 1,
sortable: false,
dataIndex: 'nodes'
},
{
header: gettext('Comment'),
flex: 1,
renderer: Ext.String.htmlEncode,
dataIndex: 'comment'
}
],
listeners: {
activate: reload,
beforeselect: function(grid, record, index, eOpts) {
if (!caps.nodes['Sys.Console']) {
return false;
}
},
itemdblclick: run_editor
}
});
me.callParent();
}
});
Ext.define('PVE.ha.FencingView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveFencingView'],
initComponent : function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-ha-fencing',
data: []
});
Ext.apply(me, {
store: store,
stateful: false,
viewConfig: {
trackOver: false,
deferEmptyText: false,
emptyText: 'Use watchdog based fencing.'
},
columns: [
{
header: 'Node',
width: 100,
sortable: true,
dataIndex: 'node'
},
{
header: gettext('Command'),
flex: 1,
dataIndex: 'command'
}
]
});
me.callParent();
}
}, function() {
Ext.define('pve-ha-fencing', {
extend: 'Ext.data.Model',
fields: [
'node', 'command', 'digest'
]
});
});
Ext.define('PVE.panel.HA', {
extend: 'PVE.panel.SubConfig',
alias: 'widget.pveHAPanel',
configPrefix: 'ha',
initComponent: function() {
/*jslint confusion: true */
var me = this;
var items = [
{
title: gettext('Status'),
xtype: 'pveHAStatusView',
itemId: 'status'
},
{
title: gettext('Resources'),
xtype: 'pveHAResourcesView',
itemId: 'resources'
},
{
title: gettext('Groups'),
xtype: 'pveHAGroupsView',
itemId: 'groups'
},
{
title: gettext('Fencing'),
xtype: 'pveFencingView',
itemId: 'fencing'
}
];
Ext.apply(me, {
defaults: {
border: false,
pveSelNode: me.pveSelNode
},
plugins: [{
ptype: 'lazyitems',
items: items
}]
});
me.callParent();
}
});
Ext.define('PVE.dc.NodeView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveDcNodeView'],
initComponent : function() {
var me = this;
var rstore = Ext.create('PVE.data.UpdateStore', {
interval: 3000,
storeid: 'pve-dc-nodes',
model: 'pve-dc-nodes',
proxy: {
type: 'pve',
url: "/api2/json/cluster/status"
},
filters: {
property: 'type',
value : 'node'
}
});
var store = Ext.create('PVE.data.DiffStore', { rstore: rstore });
var noClusterText = gettext("Standalone node - no cluster defined");
var status = Ext.create('Ext.Component', {
padding: 2,
html: '&nbsp;',
dock: 'bottom'
});
Ext.apply(me, {
store: store,
stateful: false,
bbar: [ status ],
columns: [
{
header: gettext('Name'),
width: 200,
sortable: true,
dataIndex: 'name'
},
{
header: 'ID',
width: 50,
sortable: true,
dataIndex: 'nodeid'
},
{
header: gettext('Online'),
width: 100,
sortable: true,
dataIndex: 'online',
renderer: PVE.Utils.format_boolean
},
{
header: gettext('Support'),
width: 100,
sortable: true,
dataIndex: 'level',
renderer: PVE.Utils.render_support_level
},
{
header: gettext('Server Address'),
flex: 1,
sortable: true,
dataIndex: 'ip'
}
],
listeners: {
show: rstore.startUpdate,
hide: rstore.stopUpdate,
destroy: rstore.stopUpdate
}
});
me.callParent();
me.mon(rstore, 'load', function(s, records, success) {
if (!success) {
return;
}
var cluster_rec = rstore.getById('cluster');
if (!cluster_rec) {
status.update(noClusterText);
return;
}
var cluster_data = cluster_rec.getData();
if (!cluster_data) {
status.update(noClusterText);
return;
}
var text = gettext("Cluster") + ": " + cluster_data.name + ", " +
gettext("Quorate") + ": " + PVE.Utils.format_boolean(cluster_data.quorate);
status.update(text);
});
}
}, function() {
Ext.define('pve-dc-nodes', {
extend: 'Ext.data.Model',
fields: [ 'id', 'type', 'name', 'nodeid', 'ip', 'level', 'local', 'online'],
idProperty: 'id'
});
});
Ext.define('PVE.dc.Summary', {
extend: 'Ext.panel.Panel',
alias: ['widget.pveDcSummary'],
initComponent: function() {
var me = this;
var nodegrid = Ext.create('PVE.dc.NodeView', {
title: gettext('Nodes'),
border: false,
region: 'center',
flex: 3
});
Ext.apply(me, {
layout: 'border',
items: [ nodegrid ],
listeners: {
activate: function() {
nodegrid.fireEvent('show', nodegrid);
},
hide: function() {
nodegrid.fireEvent('hide', nodegrid);
}
}
});
me.callParent();
}
});
Ext.define('PVE.dc.HttpProxyEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.applyIf(me, {
subject: gettext('HTTP proxy'),
items: {
xtype: 'pvetextfield',
name: 'http_proxy',
vtype: 'HttpProxy',
emptyText: gettext('Do not use any proxy'),
deleteEmpty: true,
value: '',
fieldLabel: gettext('HTTP proxy')
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.dc.KeyboardEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.applyIf(me, {
subject: gettext('Keyboard Layout'),
items: {
xtype: 'VNCKeyboardSelector',
name: 'keyboard',
value: '__default__',
fieldLabel: gettext('Keyboard Layout')
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.dc.ConsoleViewerEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
var data = [];
Ext.Array.each(['__default__','vv', 'html5'], function(value) {
data.push([value, PVE.Utils.render_console_viewer(value)]);
});
Ext.applyIf(me, {
subject: gettext('Console Viewer'),
items: {
xtype: 'pveKVComboBox',
name: 'console',
value: '__default__',
fieldLabel: gettext('Console Viewer'),
comboItems: data
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.dc.EmailFromEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
Ext.applyIf(me, {
subject: gettext('Email from address'),
items: {
xtype: 'pvetextfield',
name: 'email_from',
vtype: 'pveMail',
emptyText: 'root@$hostname',
deleteEmpty: true,
value: '',
fieldLabel: gettext('Email from address')
}
});
me.callParent();
me.load();
}
});
Ext.define('PVE.dc.OptionView', {
extend: 'PVE.grid.ObjectGrid',
alias: ['widget.pveDcOptionView'],
noProxyText: gettext('Do not use any proxy'),
initComponent : function() {
var me = this;
var reload = function() {
me.rstore.load();
};
var rows = {
keyboard: {
header: gettext('Keyboard Layout'),
editor: 'PVE.dc.KeyboardEdit',
renderer: PVE.Utils.render_kvm_language,
required: true
},
http_proxy: {
header: gettext('HTTP proxy'),
editor: 'PVE.dc.HttpProxyEdit',
required: true,
renderer: function(value) {
if (!value) {
return me.noProxyText;
}
return value;
}
},
console: {
header: gettext('Console Viewer'),
editor: 'PVE.dc.ConsoleViewerEdit',
required: true,
renderer: PVE.Utils.render_console_viewer
},
email_from: {
header: gettext('Email from address'),
editor: 'PVE.dc.EmailFromEdit',
required: true,
renderer: function(value) {
if (!value) {
return 'root@$hostname';
}
return value;
}
}
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var rowdef = rows[rec.data.key];
if (!rowdef.editor) {
return;
}
var win = Ext.create(rowdef.editor, {
url: "/api2/extjs/cluster/options",
confid: rec.data.key
});
win.show();
win.on('destroy', reload);
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
Ext.apply(me, {
url: "/api2/json/cluster/options",
interval: 1000,
selModel: sm,
tbar: [ edit_btn ],
rows: rows,
listeners: {
itemdblclick: run_editor,
activate: reload
}
});
me.callParent();
}
});
Ext.define('PVE.dc.StorageView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveStorageView'],
initComponent : function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-storage',
proxy: {
type: 'pve',
url: "/api2/json/storage"
},
sorters: {
property: 'storage',
order: 'DESC'
}
});
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var type = rec.data.type;
var editor;
if (type === 'dir') {
editor = 'PVE.storage.DirEdit';
} else if (type === 'nfs') {
editor = 'PVE.storage.NFSEdit';
} else if (type === 'glusterfs') {
editor = 'PVE.storage.GlusterFsEdit';
} else if (type === 'lvm') {
editor = 'PVE.storage.LVMEdit';
} else if (type === 'lvmthin') {
editor = 'PVE.storage.LvmThinEdit';
} else if (type === 'iscsi') {
editor = 'PVE.storage.IScsiEdit';
} else if (type === 'rbd') {
editor = 'PVE.storage.RBDEdit';
} else if (type === 'sheepdog') {
editor = 'PVE.storage.SheepdogEdit';
} else if (type === 'zfs') {
editor = 'PVE.storage.ZFSEdit';
} else if (type === 'zfspool') {
editor = 'PVE.storage.ZFSPoolEdit';
} else {
return;
}
var win = Ext.create(editor, {
storageId: rec.data.storage
});
win.show();
win.on('destroy', reload);
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
confirmMsg: function (rec) {
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.storage + "'");
},
handler: function(btn, event, rec) {
PVE.Utils.API2Request({
url: '/storage/' + rec.data.storage,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
viewConfig: {
trackOver: false
},
tbar: [
{
text: gettext('Add'),
menu: new Ext.menu.Menu({
items: [
{
text: PVE.Utils.format_storage_type('dir'),
iconCls: 'fa fa-fw fa-folder',
handler: function() {
var win = Ext.create('PVE.storage.DirEdit', {});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.format_storage_type('lvm'),
iconCls: 'fa fa-fw fa-folder',
handler: function() {
var win = Ext.create('PVE.storage.LVMEdit', {});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.format_storage_type('lvmthin'),
iconCls: 'fa fa-fw fa-folder',
handler: function() {
var win = Ext.create('PVE.storage.LvmThinEdit', {});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.format_storage_type('nfs'),
iconCls: 'fa fa-fw fa-building',
handler: function() {
var win = Ext.create('PVE.storage.NFSEdit', {});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.format_storage_type('iscsi'),
iconCls: 'fa fa-fw fa-building',
handler: function() {
var win = Ext.create('PVE.storage.IScsiEdit', {});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.format_storage_type('glusterfs'),
iconCls: 'fa fa-fw fa-building',
handler: function() {
var win = Ext.create('PVE.storage.GlusterFsEdit', {});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.format_storage_type('rbd'),
iconCls: 'fa fa-fw fa-building',
handler: function() {
var win = Ext.create('PVE.storage.RBDEdit', {});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.format_storage_type('zfs'),
iconCls: 'fa fa-fw fa-building',
handler: function() {
var win = Ext.create('PVE.storage.ZFSEdit', {});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.format_storage_type('zfspool'),
iconCls: 'fa fa-fw fa-folder',
handler: function() {
var win = Ext.create('PVE.storage.ZFSPoolEdit', {});
win.on('destroy', reload);
win.show();
}
}
/* the following type are conidered unstable
* so we do not enable that on the GUI for now
{
text: PVE.Utils.format_storage_type('sheepdog'),
iconCls: 'fa fa-fw fa-building',
handler: function() {
var win = Ext.create('PVE.storage.SheepdogEdit', {});
win.on('destroy', reload);
win.show();
}
}
*/
]
})
},
remove_btn,
edit_btn
],
columns: [
{
header: 'ID',
width: 100,
sortable: true,
dataIndex: 'storage'
},
{
header: gettext('Type'),
width: 60,
sortable: true,
dataIndex: 'type',
renderer: PVE.Utils.format_storage_type
},
{
header: gettext('Content'),
width: 150,
sortable: true,
dataIndex: 'content',
renderer: PVE.Utils.format_content_types
},
{
header: gettext('Path') + '/' + gettext('Target'),
flex: 1,
sortable: true,
dataIndex: 'path',
renderer: function(value, metaData, record) {
if (record.data.target) {
return record.data.target;
}
return value;
}
},
{
header: gettext('Shared'),
width: 80,
sortable: true,
dataIndex: 'shared',
renderer: PVE.Utils.format_boolean
},
{
header: gettext('Enabled'),
width: 80,
sortable: true,
dataIndex: 'disable',
renderer: PVE.Utils.format_neg_boolean
}
],
listeners: {
activate: reload,
itemdblclick: run_editor
}
});
me.callParent();
}
}, function() {
Ext.define('pve-storage', {
extend: 'Ext.data.Model',
fields: [
'path', 'type', 'content', 'server', 'portal', 'target', 'export', 'storage',
{ name: 'shared', type: 'boolean'},
{ name: 'disable', type: 'boolean'}
],
idProperty: 'storage'
});
});
Ext.define('PVE.dc.UserEdit', {
extend: 'PVE.window.Edit',
alias: ['widget.pveDcUserEdit'],
isAdd: true,
initComponent : function() {
var me = this;
me.create = !me.userid;
var url;
var method;
var realm;
if (me.create) {
url = '/api2/extjs/access/users';
method = 'POST';
} else {
url = '/api2/extjs/access/users/' + me.userid;
method = 'PUT';
}
var verifypw;
var pwfield;
var validate_pw = function() {
if (verifypw.getValue() !== pwfield.getValue()) {
return gettext("Passwords does not match");
}
return true;
};
verifypw = Ext.createWidget('textfield', {
inputType: 'password',
fieldLabel: gettext('Confirm password'),
name: 'verifypassword',
submitValue: false,
disabled: true,
hidden: true,
validator: validate_pw
});
pwfield = Ext.createWidget('textfield', {
inputType: 'password',
fieldLabel: gettext('Password'),
minLength: 5,
name: 'password',
disabled: true,
hidden: true,
validator: validate_pw
});
var update_passwd_field = function(realm) {
if (realm === 'pve') {
pwfield.setVisible(true);
pwfield.setDisabled(false);
verifypw.setVisible(true);
verifypw.setDisabled(false);
} else {
pwfield.setVisible(false);
pwfield.setDisabled(true);
verifypw.setVisible(false);
verifypw.setDisabled(true);
}
};
var column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'userid',
fieldLabel: gettext('User name'),
value: me.userid,
allowBlank: false,
submitValue: me.create ? true : false
},
pwfield, verifypw,
{
xtype: 'pveGroupSelector',
name: 'groups',
multiSelect: true,
allowBlank: true,
fieldLabel: gettext('Group')
},
{
xtype: 'datefield',
name: 'expire',
emptyText: 'never',
format: 'Y-m-d',
submitFormat: 'U',
fieldLabel: gettext('Expire')
},
{
xtype: 'pvecheckbox',
fieldLabel: gettext('Enabled'),
name: 'enable',
uncheckedValue: 0,
defaultValue: 1,
checked: true
}
];
var column2 = [
{
xtype: 'textfield',
name: 'firstname',
fieldLabel: gettext('First Name')
},
{
xtype: 'textfield',
name: 'lastname',
fieldLabel: gettext('Last Name')
},
{
xtype: 'textfield',
name: 'email',
fieldLabel: gettext('E-Mail'),
vtype: 'pveMail'
}
];
var columnB = [
{
xtype: 'textfield',
name: 'comment',
fieldLabel: gettext('Comment')
},
{
xtype: 'textfield',
name: 'keys',
fieldLabel: gettext('Key IDs')
}
];
if (me.create) {
column1.splice(1,0,{
xtype: 'pveRealmComboBox',
name: 'realm',
fieldLabel: gettext('Realm'),
allowBlank: false,
matchFieldWidth: false,
listConfig: { width: 300 },
listeners: {
change: function(combo, newValue){
realm = newValue;
update_passwd_field(realm);
}
},
submitValue: false
});
}
var ipanel = Ext.create('PVE.panel.InputPanel', {
column1: column1,
column2: column2,
columnB: columnB,
onGetValues: function(values) {
// hack: ExtJS datefield does not submit 0, so we need to set that
if (!values.expire) {
values.expire = 0;
}
if (realm) {
values.userid = values.userid + '@' + realm;
}
if (!values.password) {
delete values.password;
}
return values;
}
});
Ext.applyIf(me, {
subject: gettext('User'),
url: url,
method: method,
fieldDefaults: {
labelWidth: 110 // for spanish translation
},
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var data = response.result.data;
if (Ext.isDefined(data.expire)) {
if (data.expire) {
data.expire = new Date(data.expire * 1000);
} else {
// display 'never' instead of '1970-01-01'
data.expire = null;
}
}
me.setValues(data);
}
});
}
}
});
Ext.define('PVE.window.PasswordEdit', {
extend: 'PVE.window.Edit',
initComponent : function() {
var me = this;
if (!me.userid) {
throw "no userid specified";
}
var verifypw;
var pwfield;
var validate_pw = function() {
if (verifypw.getValue() !== pwfield.getValue()) {
return gettext("Passwords does not match");
}
return true;
};
verifypw = Ext.createWidget('textfield', {
inputType: 'password',
fieldLabel: gettext('Confirm password'),
name: 'verifypassword',
submitValue: false,
validator: validate_pw
});
pwfield = Ext.createWidget('textfield', {
inputType: 'password',
fieldLabel: gettext('Password'),
minLength: 5,
name: 'password',
validator: validate_pw
});
Ext.apply(me, {
subject: gettext('Password'),
url: '/api2/extjs/access/password',
items: [
pwfield, verifypw,
{
xtype: 'hiddenfield',
name: 'userid',
value: me.userid
}
]
});
me.callParent();
}
});
Ext.define('PVE.dc.UserView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveUserView'],
initComponent : function() {
var me = this;
var caps = Ext.state.Manager.get('GuiCap');
var store = new Ext.data.Store({
id: "users",
model: 'pve-users',
sorters: {
property: 'userid',
order: 'DESC'
}
});
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
enableFn: function(rec) {
if (!caps.access['User.Modify']) {
return false;
}
return rec.data.userid !== 'root@pam';
},
confirmMsg: function (rec) {
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.userid + "'");
},
handler: function(btn, event, rec) {
var userid = rec.data.userid;
PVE.Utils.API2Request({
url: '/access/users/' + userid,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec || !caps.access['User.Modify']) {
return;
}
var win = Ext.create('PVE.dc.UserEdit',{
userid: rec.data.userid
});
win.on('destroy', reload);
win.show();
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
enableFn: function(rec) {
return !!caps.access['User.Modify'];
},
selModel: sm,
handler: run_editor
});
var pwchange_btn = new PVE.button.Button({
text: gettext('Password'),
disabled: true,
selModel: sm,
handler: function(btn, event, rec) {
var win = Ext.create('PVE.window.PasswordEdit',{
userid: rec.data.userid
});
win.on('destroy', reload);
win.show();
}
});
var tbar = [
{
text: gettext('Add'),
disabled: !caps.access['User.Modify'],
handler: function() {
var win = Ext.create('PVE.dc.UserEdit',{
});
win.on('destroy', reload);
win.show();
}
},
edit_btn, remove_btn, pwchange_btn
];
var render_full_name = function(firstname, metaData, record) {
var first = firstname || '';
var last = record.data.lastname || '';
return first + " " + last;
};
var render_username = function(userid) {
return userid.match(/^(.+)(@[^@]+)$/)[1];
};
var render_realm = function(userid) {
return userid.match(/@([^@]+)$/)[1];
};
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
tbar: tbar,
viewConfig: {
trackOver: false
},
columns: [
{
header: gettext('User name'),
width: 200,
sortable: true,
renderer: render_username,
dataIndex: 'userid'
},
{
header: gettext('Realm'),
width: 100,
sortable: true,
renderer: render_realm,
dataIndex: 'userid'
},
{
header: gettext('Enabled'),
width: 80,
sortable: true,
renderer: PVE.Utils.format_boolean,
dataIndex: 'enable'
},
{
header: gettext('Expire'),
width: 80,
sortable: true,
renderer: PVE.Utils.format_expire,
dataIndex: 'expire'
},
{
header: gettext('Name'),
width: 150,
sortable: true,
renderer: render_full_name,
dataIndex: 'firstname'
},
{
header: gettext('Comment'),
sortable: false,
renderer: Ext.String.htmlEncode,
dataIndex: 'comment',
flex: 1
}
],
listeners: {
activate: reload,
itemdblclick: run_editor
}
});
me.callParent();
}
});
Ext.define('PVE.dc.PoolView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pvePoolView'],
initComponent : function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-pools',
sorters: {
property: 'poolid',
order: 'DESC'
}
});
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
confirmMsg: function (rec) {
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.poolid + "'");
},
handler: function(btn, event, rec) {
PVE.Utils.API2Request({
url: '/pools/' + rec.data.poolid,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.dc.PoolEdit',{
poolid: rec.data.poolid
});
win.on('destroy', reload);
win.show();
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
var tbar = [
{
text: gettext('Create'),
handler: function() {
var win = Ext.create('PVE.dc.PoolEdit', {});
win.on('destroy', reload);
win.show();
}
},
edit_btn, remove_btn
];
PVE.Utils.monStoreErrors(me, store);
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
tbar: tbar,
viewConfig: {
trackOver: false
},
columns: [
{
header: gettext('Name'),
width: 200,
sortable: true,
dataIndex: 'poolid'
},
{
header: gettext('Comment'),
sortable: false,
renderer: Ext.String.htmlEncode,
dataIndex: 'comment',
flex: 1
}
],
listeners: {
activate: reload,
itemdblclick: run_editor
}
});
me.callParent();
}
});
Ext.define('PVE.dc.PoolEdit', {
extend: 'PVE.window.Edit',
alias: ['widget.pveDcPoolEdit'],
initComponent : function() {
var me = this;
me.create = !me.poolid;
var url;
var method;
if (me.create) {
url = '/api2/extjs/pools';
method = 'POST';
} else {
url = '/api2/extjs/pools/' + me.poolid;
method = 'PUT';
}
Ext.applyIf(me, {
subject: gettext('Pool'),
url: url,
method: method,
items: [
{
xtype: me.create ? 'pvetextfield' : 'displayfield',
fieldLabel: gettext('Name'),
name: 'poolid',
value: me.poolid,
allowBlank: false
},
{
xtype: 'textfield',
fieldLabel: gettext('Comment'),
name: 'comment',
allowBlank: true
}
]
});
me.callParent();
if (!me.create) {
me.load();
}
}
});
Ext.define('PVE.dc.GroupView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveGroupView'],
initComponent : function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-groups',
sorters: {
property: 'groupid',
order: 'DESC'
}
});
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
confirmMsg: function (rec) {
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.groupid + "'");
},
handler: function(btn, event, rec) {
PVE.Utils.API2Request({
url: '/access/groups/' + rec.data.groupid,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.dc.GroupEdit',{
groupid: rec.data.groupid
});
win.on('destroy', reload);
win.show();
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
var tbar = [
{
text: gettext('Create'),
handler: function() {
var win = Ext.create('PVE.dc.GroupEdit', {});
win.on('destroy', reload);
win.show();
}
},
edit_btn, remove_btn
];
PVE.Utils.monStoreErrors(me, store);
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
tbar: tbar,
viewConfig: {
trackOver: false
},
columns: [
{
header: gettext('Name'),
width: 200,
sortable: true,
dataIndex: 'groupid'
},
{
header: gettext('Comment'),
sortable: false,
renderer: Ext.String.htmlEncode,
dataIndex: 'comment',
flex: 1
}
],
listeners: {
activate: reload,
itemdblclick: run_editor
}
});
me.callParent();
}
});
Ext.define('PVE.dc.GroupEdit', {
extend: 'PVE.window.Edit',
alias: ['widget.pveDcGroupEdit'],
initComponent : function() {
var me = this;
me.create = !me.groupid;
var url;
var method;
if (me.create) {
url = '/api2/extjs/access/groups';
method = 'POST';
} else {
url = '/api2/extjs/access/groups/' + me.groupid;
method = 'PUT';
}
Ext.applyIf(me, {
subject: gettext('Group'),
url: url,
method: method,
items: [
{
xtype: me.create ? 'pvetextfield' : 'displayfield',
fieldLabel: gettext('Name'),
name: 'groupid',
value: me.groupid,
allowBlank: false
},
{
xtype: 'textfield',
fieldLabel: gettext('Comment'),
name: 'comment',
allowBlank: true
}
]
});
me.callParent();
if (!me.create) {
me.load();
}
}
});
Ext.define('PVE.dc.RoleView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveRoleView'],
initComponent : function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-roles',
sorters: {
property: 'roleid',
order: 'DESC'
}
});
var render_privs = function(value, metaData) {
if (!value) {
return '-';
}
// allow word wrap
metaData.style = 'white-space:normal;';
return value.replace(/\,/g, ' ');
};
PVE.Utils.monStoreErrors(me, store);
Ext.apply(me, {
store: store,
stateful: false,
viewConfig: {
trackOver: false
},
columns: [
{
header: gettext('Name'),
width: 150,
sortable: true,
dataIndex: 'roleid'
},
{
itemid: 'privs',
header: gettext('Privileges'),
sortable: false,
renderer: render_privs,
dataIndex: 'privs',
flex: 1
}
],
listeners: {
activate: function() {
store.load();
}
}
});
me.callParent();
}
});
Ext.define('PVE.dc.ACLAdd', {
extend: 'PVE.window.Edit',
alias: ['widget.pveACLAdd'],
url: '/access/acl',
method: 'PUT',
isAdd: true,
initComponent : function() {
/*jslint confusion: true */
var me = this;
me.create = true;
var items = [
{
xtype: me.path ? 'hiddenfield' : 'textfield',
name: 'path',
value: me.path,
allowBlank: false,
fieldLabel: gettext('Path')
}
];
if (me.aclType === 'group') {
me.subject = gettext("Group Permission");
items.push({
xtype: 'pveGroupSelector',
name: 'groups',
fieldLabel: gettext('Group')
});
} else if (me.aclType === 'user') {
me.subject = gettext("User Permission");
items.push({
xtype: 'pveUserSelector',
name: 'users',
fieldLabel: gettext('User')
});
} else {
throw "unknown ACL type";
}
items.push({
xtype: 'pveRoleSelector',
name: 'roles',
value: 'NoAccess',
fieldLabel: gettext('Role')
});
if (!me.path) {
items.push({
xtype: 'pvecheckbox',
name: 'propagate',
checked: true,
uncheckedValue: 0,
fieldLabel: gettext('Propagate')
});
}
var ipanel = Ext.create('PVE.panel.InputPanel', {
items: items
});
Ext.apply(me, {
items: [ ipanel ]
});
me.callParent();
}
});
Ext.define('PVE.dc.ACLView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveACLView'],
// use fixed path
path: undefined,
initComponent : function() {
var me = this;
var store = Ext.create('Ext.data.Store',{
model: 'pve-acl',
proxy: {
type: 'pve',
url: "/api2/json/access/acl"
},
sorters: {
property: 'path',
order: 'DESC'
}
});
if (me.path) {
store.addFilter(Ext.create('Ext.util.Filter',{
filterFn: function(item) {
if (item.data.path === me.path) {
return true;
}
}
}));
}
var render_ugid = function(ugid, metaData, record) {
if (record.data.type == 'group') {
return '@' + ugid;
}
return ugid;
};
var columns = [
{
header: gettext('User') + '/' + gettext('Group'),
flex: 1,
sortable: true,
renderer: render_ugid,
dataIndex: 'ugid'
},
{
header: gettext('Role'),
flex: 1,
sortable: true,
dataIndex: 'roleid'
}
];
if (!me.path) {
columns.unshift({
header: gettext('Path'),
flex: 1,
sortable: true,
dataIndex: 'path'
});
columns.push({
header: gettext('Propagate'),
width: 80,
sortable: true,
dataIndex: 'propagate'
});
}
var sm = Ext.create('Ext.selection.RowModel', {});
var reload = function() {
store.load();
};
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
confirmMsg: gettext('Are you sure you want to remove this entry'),
handler: function(btn, event, rec) {
var params = {
'delete': 1,
path: rec.data.path,
roles: rec.data.roleid
};
if (rec.data.type === 'group') {
params.groups = rec.data.ugid;
} else if (rec.data.type === 'user') {
params.users = rec.data.ugid;
} else {
throw 'unknown data type';
}
PVE.Utils.API2Request({
url: '/access/acl',
params: params,
method: 'PUT',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
PVE.Utils.monStoreErrors(me, store);
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
tbar: [
{
text: gettext('Add'),
menu: {
xtype: 'menu',
items: [
{
text: gettext('Group Permission'),
iconCls: 'fa fa-fw fa-group',
handler: function() {
var win = Ext.create('PVE.dc.ACLAdd',{
aclType: 'group',
path: me.path
});
win.on('destroy', reload);
win.show();
}
},
{
text: gettext('User Permission'),
iconCls: 'fa fa-fw fa-user',
handler: function() {
var win = Ext.create('PVE.dc.ACLAdd',{
aclType: 'user',
path: me.path
});
win.on('destroy', reload);
win.show();
}
}
]
}
},
remove_btn
],
viewConfig: {
trackOver: false
},
columns: columns,
listeners: {
activate: reload
}
});
me.callParent();
}
}, function() {
Ext.define('pve-acl', {
extend: 'Ext.data.Model',
fields: [
'path', 'type', 'ugid', 'roleid',
{
name: 'propagate',
type: 'boolean'
}
]
});
});
Ext.define('PVE.dc.AuthView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveAuthView'],
initComponent : function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-domains',
sorters: {
property: 'realm',
order: 'DESC'
}
});
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.dc.AuthEdit',{
realm: rec.data.realm,
authType: rec.data.type
});
win.on('destroy', reload);
win.show();
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
confirmMsg: function (rec) {
return Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
"'" + rec.data.realm + "'");
},
enableFn: function(rec) {
return !(rec.data.type === 'pve' || rec.data.type === 'pam');
},
handler: function(btn, event, rec) {
var realm = rec.data.realm;
PVE.Utils.API2Request({
url: '/access/domains/' + realm,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
var tbar = [
{
text: gettext('Add'),
menu: new Ext.menu.Menu({
items: [
{
text: gettext('Active Directory Server'),
handler: function() {
var win = Ext.create('PVE.dc.AuthEdit', {
authType: 'ad'
});
win.on('destroy', reload);
win.show();
}
},
{
text: gettext('LDAP Server'),
handler: function() {
var win = Ext.create('PVE.dc.AuthEdit',{
authType: 'ldap'
});
win.on('destroy', reload);
win.show();
}
}
]
})
},
edit_btn, remove_btn
];
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
tbar: tbar,
viewConfig: {
trackOver: false
},
columns: [
{
header: gettext('Realm'),
width: 100,
sortable: true,
dataIndex: 'realm'
},
{
header: gettext('Type'),
width: 100,
sortable: true,
dataIndex: 'type'
},
{
header: gettext('TFA'),
width: 100,
sortable: true,
dataIndex: 'tfa'
},
{
header: gettext('Comment'),
sortable: false,
dataIndex: 'comment',
renderer: Ext.String.htmlEncode,
flex: 1
}
],
listeners: {
activate: reload,
itemdblclick: run_editor
}
});
me.callParent();
}
});
Ext.define('PVE.dc.AuthEdit', {
extend: 'PVE.window.Edit',
alias: ['widget.pveDcAuthEdit'],
isAdd: true,
initComponent : function() {
var me = this;
me.create = !me.realm;
var url;
var method;
var serverlist;
if (me.create) {
url = '/api2/extjs/access/domains';
method = 'POST';
} else {
url = '/api2/extjs/access/domains/' + me.realm;
method = 'PUT';
}
var column1 = [
{
xtype: me.create ? 'textfield' : 'displayfield',
name: 'realm',
fieldLabel: gettext('Realm'),
value: me.realm,
allowBlank: false
}
];
if (me.authType === 'ad') {
me.subject = gettext('Active Directory Server');
column1.push({
xtype: 'textfield',
name: 'domain',
fieldLabel: gettext('Domain'),
emptyText: 'company.net',
allowBlank: false
});
} else if (me.authType === 'ldap') {
me.subject = gettext('LDAP Server');
column1.push({
xtype: 'textfield',
name: 'base_dn',
fieldLabel: gettext('Base Domain Name'),
emptyText: 'CN=Users,DC=Company,DC=net',
allowBlank: false
});
column1.push({
xtype: 'textfield',
name: 'user_attr',
emptyText: 'uid / sAMAccountName',
fieldLabel: gettext('User Attribute Name'),
allowBlank: false
});
} else if (me.authType === 'pve') {
if (me.create) {
throw 'unknown auth type';
}
me.subject = 'Proxmox VE authentication server';
} else if (me.authType === 'pam') {
if (me.create) {
throw 'unknown auth type';
}
me.subject = 'linux PAM';
} else {
throw 'unknown auth type ';
}
column1.push({
xtype: 'pvecheckbox',
fieldLabel: gettext('Default'),
name: 'default',
uncheckedValue: 0
});
var column2 = [];
if (me.authType === 'ldap' || me.authType === 'ad') {
column2.push(
{
xtype: 'textfield',
fieldLabel: gettext('Server'),
name: 'server1',
allowBlank: false
},
{
xtype: 'pvetextfield',
fieldLabel: gettext('Fallback Server'),
deleteEmpty: !me.create,
name: 'server2'
},
{
xtype: 'numberfield',
name: 'port',
fieldLabel: gettext('Port'),
minValue: 1,
maxValue: 65535,
emptyText: gettext('Default'),
submitEmptyText: false
},
{
xtype: 'pvecheckbox',
fieldLabel: 'SSL',
name: 'secure',
uncheckedValue: 0
}
);
}
// Two Factor Auth settings
column2.push({
xtype: 'pveKVComboBox',
name: 'tfa',
deleteEmpty: !me.create,
value: '',
fieldLabel: gettext('TFA'),
comboItems: [ ['__default__', PVE.Utils.noneText], ['oath', 'OATH'], ['yubico', 'Yubico']],
listeners: {
change: function(f, value) {
if (!me.rendered) {
return;
}
me.down('field[name=oath_step]').setVisible(value === 'oath');
me.down('field[name=oath_digits]').setVisible(value === 'oath');
me.down('field[name=yubico_api_id]').setVisible(value === 'yubico');
me.down('field[name=yubico_api_key]').setVisible(value === 'yubico');
me.down('field[name=yubico_url]').setVisible(value === 'yubico');
}
}
});
column2.push({
xtype: 'numberfield',
name: 'oath_step',
value: '',
minValue: 10,
step: 1,
allowDecimals: false,
allowBlank: true,
emptyText: PVE.Utils.defaultText + ' (30)',
submitEmptyText: false,
hidden: true,
fieldLabel: 'OATH time step'
});
column2.push({
xtype: 'numberfield',
name: 'oath_digits',
value: '',
minValue: 6,
maxValue: 8,
step: 1,
allowDecimals: false,
allowBlank: true,
emptyText: PVE.Utils.defaultText + ' (6)',
submitEmptyText: false,
hidden: true,
fieldLabel: 'OATH password length'
});
column2.push({
xtype: 'textfield',
name: 'yubico_api_id',
hidden: true,
fieldLabel: 'Yubico API Id'
});
column2.push({
xtype: 'textfield',
name: 'yubico_api_key',
hidden: true,
fieldLabel: 'Yubico API Key'
});
column2.push({
xtype: 'textfield',
name: 'yubico_url',
hidden: true,
fieldLabel: 'Yubico URL'
});
var ipanel = Ext.create('PVE.panel.InputPanel', {
column1: column1,
column2: column2,
columnB: [{
xtype: 'textfield',
name: 'comment',
fieldLabel: gettext('Comment')
}],
onGetValues: function(values) {
if (!values.port) {
if (!me.create) {
PVE.Utils.assemble_field_data(values, { 'delete': 'port' });
}
delete values.port;
}
if (me.create) {
values.type = me.authType;
}
if (values.tfa === 'oath') {
values.tfa = "type=oath";
if (values.oath_step) {
values.tfa += ",step=" + values.oath_step;
}
if (values.oath_digits) {
values.tfa += ",digits=" + values.oath_digits;
}
} else if (values.tfa === 'yubico') {
values.tfa = "type=yubico";
values.tfa += ",id=" + values.yubico_api_id;
values.tfa += ",key=" + values.yubico_api_key;
if (values.yubico_url) {
values.tfa += ",url=" + values.yubico_url;
}
} else {
delete values.tfa;
}
delete values.oath_step;
delete values.oath_digits;
delete values.yubico_api_id;
delete values.yubico_api_key;
delete values.yubico_url;
return values;
}
});
Ext.applyIf(me, {
url: url,
method: method,
fieldDefaults: {
labelWidth: 120
},
items: [ ipanel ]
});
me.callParent();
if (!me.create) {
me.load({
success: function(response, options) {
var data = response.result.data || {};
// just to be sure (should not happen)
if (data.type !== me.authType) {
me.close();
throw "got wrong auth type";
}
if (data.tfa) {
var tfacfg = PVE.Parser.parseTfaConfig(data.tfa);
data.tfa = tfacfg.type;
if (tfacfg.type === 'yubico') {
data.yubico_api_key = tfacfg.key;
data.yubico_api_id = tfacfg.id;
data.yubico_url = tfacfg.url;
} else if (tfacfg.type === 'oath') {
// step is a number before
/*jslint confusion: true*/
data.oath_step = tfacfg.step;
data.oath_digits = tfacfg.digits;
/*jslint confusion: false*/
}
}
me.setValues(data);
}
});
}
}
});
Ext.define('PVE.dc.BackupEdit', {
extend: 'PVE.window.Edit',
alias: ['widget.pveDcBackupEdit'],
initComponent : function() {
/*jslint confusion: true */
var me = this;
me.create = !me.jobid;
var url;
var method;
if (me.create) {
url = '/api2/extjs/cluster/backup';
method = 'POST';
} else {
url = '/api2/extjs/cluster/backup/' + me.jobid;
method = 'PUT';
}
var vmidField = Ext.create('Ext.form.field.Hidden', {
name: 'vmid'
});
var selModeField = Ext.create('PVE.form.KVComboBox', {
xtype: 'pveKVComboBox',
comboItems: [
['include', gettext('Include selected VMs')],
['all', gettext('All')],
['exclude', gettext('Exclude selected VMs')]
],
fieldLabel: gettext('Selection mode'),
name: 'selMode',
value: ''
});
var insideUpdate = false;
var sm = Ext.create('Ext.selection.CheckboxModel', {
mode: 'SIMPLE',
listeners: {
selectionchange: function(model, selected) {
if (!insideUpdate) { // avoid endless loop
var sel = [];
Ext.Array.each(selected, function(record) {
sel.push(record.data.vmid);
});
insideUpdate = true;
vmidField.setValue(sel);
insideUpdate = false;
}
}
}
});
var storagesel = Ext.create('PVE.form.StorageSelector', {
fieldLabel: gettext('Storage'),
nodename: 'localhost',
storageContent: 'backup',
allowBlank: false,
name: 'storage'
});
var store = new Ext.data.Store({
model: 'PVEResources',
sorters: {
property: 'vmid',
order: 'ASC'
}
});
var vmgrid = Ext.createWidget('grid', {
store: store,
border: true,
height: 300,
selModel: sm,
disabled: true,
columns: [
{
header: 'ID',
dataIndex: 'vmid',
width: 60
},
{
header: gettext('Node'),
dataIndex: 'node'
},
{
header: gettext('Status'),
dataIndex: 'uptime',
renderer: function(value) {
if (value) {
return PVE.Utils.runningText;
} else {
return PVE.Utils.stoppedText;
}
}
},
{
header: gettext('Name'),
dataIndex: 'name',
flex: 1
},
{
header: gettext('Type'),
dataIndex: 'type'
}
]
});
var nodesel = Ext.create('PVE.form.NodeSelector', {
name: 'node',
fieldLabel: gettext('Node'),
allowBlank: true,
editable: true,
autoSelect: false,
emptyText: '-- ' + gettext('All') + ' --',
listeners: {
change: function(f, value) {
storagesel.setNodename(value || 'localhost');
var mode = selModeField.getValue();
store.clearFilter();
store.filterBy(function(rec) {
return (!value || rec.get('node') === value);
});
if (mode === 'all') {
sm.selectAll(true);
}
}
}
});
var column1 = [
nodesel,
storagesel,
{
xtype: 'pveDayOfWeekSelector',
name: 'dow',
fieldLabel: gettext('Day of week'),
multiSelect: true,
value: ['sat'],
allowBlank: false
},
{
xtype: 'timefield',
fieldLabel: gettext('Start Time'),
name: 'starttime',
format: 'H:i',
value: '00:00',
allowBlank: false
},
selModeField
];
var column2 = [
{
xtype: 'textfield',
fieldLabel: gettext('Send email to'),
name: 'mailto'
},
{
xtype: 'pveEmailNotificationSelector',
fieldLabel: gettext('Email notification'),
name: 'mailnotification',
deleteEmpty: me.create ? false : true,
value: me.create ? 'always' : ''
},
{
xtype: 'pveCompressionSelector',
fieldLabel: gettext('Compression'),
name: 'compress',
deleteEmpty: me.create ? false : true,
value: 'lzo'
},
{
xtype: 'pveBackupModeSelector',
fieldLabel: gettext('Mode'),
value: 'snapshot',
name: 'mode'
},
{
xtype: 'pvecheckbox',
fieldLabel: gettext('Enable'),
name: 'enabled',
uncheckedValue: 0,
defaultValue: 1,
checked: true
},
vmidField
];
var ipanel = Ext.create('PVE.panel.InputPanel', {
column1: column1,
column2: column2,
onGetValues: function(values) {
if (!values.node) {
if (!me.create) {
PVE.Utils.assemble_field_data(values, { 'delete': 'node' });
}
delete values.node;
}
var selMode = values.selMode;
delete values.selMode;
if (selMode === 'all') {
values.all = 1;
values.exclude = '';
delete values.vmid;
} else if (selMode === 'exclude') {
values.all = 1;
values.exclude = values.vmid;
delete values.vmid;
}
return values;
}
});
var update_vmid_selection = function(list, mode) {
if (insideUpdate) {
return; // should not happen - just to be sure
}
insideUpdate = true;
if (mode !== 'all') {
sm.deselectAll(true);
if (list) {
Ext.Array.each(list.split(','), function(vmid) {
var rec = store.findRecord('vmid', vmid);
if (rec) {
sm.select(rec, true);
}
});
}
}
insideUpdate = false;
};
vmidField.on('change', function(f, value) {
var mode = selModeField.getValue();
update_vmid_selection(value, mode);
});
selModeField.on('change', function(f, value, oldValue) {
if (value === 'all') {
sm.selectAll(true);
vmgrid.setDisabled(true);
} else {
vmgrid.setDisabled(false);
}
if (oldValue === 'all') {
sm.deselectAll(true);
vmidField.setValue('');
}
var list = vmidField.getValue();
update_vmid_selection(list, value);
});
var reload = function() {
store.load({
params: { type: 'vm' },
callback: function() {
var node = nodesel.getValue();
store.clearFilter();
store.filterBy(function(rec) {
return (!node || node.length === 0 || rec.get('node') === node);
});
var list = vmidField.getValue();
var mode = selModeField.getValue();
if (mode === 'all') {
sm.selectAll(true);
} else {
update_vmid_selection(list, mode);
}
}
});
};
Ext.applyIf(me, {
subject: gettext("Backup Job"),
url: url,
method: method,
items: [ ipanel, vmgrid ]
});
me.callParent();
if (me.create) {
selModeField.setValue('include');
} else {
me.load({
success: function(response, options) {
var data = response.result.data;
data.dow = data.dow.split(',');
if (data.all || data.exclude) {
if (data.exclude) {
data.vmid = data.exclude;
data.selMode = 'exclude';
} else {
data.vmid = '';
data.selMode = 'all';
}
} else {
data.selMode = 'include';
}
me.setValues(data);
}
});
}
reload();
}
});
Ext.define('PVE.dc.BackupView', {
extend: 'Ext.grid.GridPanel',
alias: ['widget.pveDcBackupView'],
allText: '-- ' + gettext('All') + ' --',
allExceptText: gettext('All except {0}'),
initComponent : function() {
var me = this;
var store = new Ext.data.Store({
model: 'pve-cluster-backup',
proxy: {
type: 'pve',
url: "/api2/json/cluster/backup"
}
});
var reload = function() {
store.load();
};
var sm = Ext.create('Ext.selection.RowModel', {});
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.dc.BackupEdit',{
jobid: rec.data.id
});
win.on('destroy', reload);
win.show();
};
var edit_btn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
var remove_btn = new PVE.button.Button({
text: gettext('Remove'),
disabled: true,
selModel: sm,
confirmMsg: gettext('Are you sure you want to remove this entry'),
handler: function(btn, event, rec) {
PVE.Utils.API2Request({
url: '/cluster/backup/' + rec.data.id,
method: 'DELETE',
waitMsgTarget: me,
callback: function() {
reload();
},
failure: function (response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
}
});
}
});
PVE.Utils.monStoreErrors(me, store);
Ext.apply(me, {
store: store,
selModel: sm,
stateful: false,
viewConfig: {
trackOver: false
},
tbar: [
{
text: gettext('Add'),
handler: function() {
var win = Ext.create('PVE.dc.BackupEdit',{});
win.on('destroy', reload);
win.show();
}
},
remove_btn,
edit_btn
],
columns: [
{
header: gettext('Enabled'),
width: 80,
dataIndex: 'enabled',
xtype: 'checkcolumn',
sortable: true,
disabled: true,
disabledCls: 'x-item-enabled',
stopSelection: false
},
{
header: gettext('Node'),
width: 100,
sortable: true,
dataIndex: 'node',
renderer: function(value) {
if (value) {
return value;
}
return me.allText;
}
},
{
header: gettext('Day of week'),
width: 200,
sortable: false,
dataIndex: 'dow',
renderer: 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(', ');
}
},
{
header: gettext('Start Time'),
width: 60,
sortable: true,
dataIndex: 'starttime'
},
{
header: gettext('Storage'),
width: 100,
sortable: true,
dataIndex: 'storage'
},
{
header: gettext('Selection'),
flex: 1,
sortable: false,
dataIndex: 'vmid',
renderer: function(value, metaData, record) {
/*jslint confusion: true */
if (record.data.all) {
if (record.data.exclude) {
return Ext.String.format(me.allExceptText, record.data.exclude);
}
return me.allText;
}
if (record.data.vmid) {
return record.data.vmid;
}
return "-";
}
}
],
listeners: {
activate: reload,
itemdblclick: run_editor
}
});
me.callParent();
}
}, function() {
Ext.define('pve-cluster-backup', {
extend: 'Ext.data.Model',
fields: [
'id', 'starttime', 'dow',
'storage', 'node', 'vmid', 'exclude',
'mailto',
{ name: 'enabled', type: 'boolean' },
{ name: 'all', type: 'boolean' },
{ name: 'snapshot', type: 'boolean' },
{ name: 'stop', type: 'boolean' },
{ name: 'suspend', type: 'boolean' },
{ name: 'compress', type: 'boolean' }
]
});
});
Ext.define('PVE.dc.Support', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveDcSupport',
pveGuidePath: '/pve-docs/index.html',
invalidHtml: '<h1>No valid subscription</h1>' + PVE.Utils.noSubKeyHtml,
communityHtml: 'Please use the public community <a target="_blank" href="http://forum.proxmox.com">forum</a> for any questions.',
activeHtml: 'Please use our <a target="_blank" href="https://my.proxmox.com">support portal</a> for any questions. You can also use the public community <a target="_blank" href="http://forum.proxmox.com">forum</a> to get additional information.',
bugzillaHtml: '<h1>Bug Tracking</h1>Our bug tracking system is available <a target="_blank" href="https://bugzilla.proxmox.com">here</a>.',
docuHtml: function() {
var me = this;
var guideUrl = window.location.origin + me.pveGuidePath;
var text = Ext.String.format('<h1>Documentation</h1>'
+ 'The official Proxmox VE Administration Guide'
+ ' is included with this installation and can be browsed at '
+ '<a target="_blank" href="{0}">{0}</a>', guideUrl);
return text;
},
updateActive: function(data) {
var me = this;
var html = '<h1>' + data.productname + '</h1>' + me.activeHtml;
html += '<br><br>' + me.docuHtml();
html += '<br><br>' + me.bugzillaHtml;
me.update(html);
},
updateCommunity: function(data) {
var me = this;
var html = '<h1>' + data.productname + '</h1>' + me.communityHtml;
html += '<br><br>' + me.docuHtml();
html += '<br><br>' + me.bugzillaHtml;
me.update(html);
},
updateInactive: function(data) {
var me = this;
me.update(me.invalidHtml);
},
initComponent: function() {
var me = this;
var reload = function() {
PVE.Utils.API2Request({
url: '/nodes/localhost/subscription',
method: 'GET',
waitMsgTarget: me,
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
me.update('Unable to load subscription status' + ": " + response.htmlStatus);
},
success: function(response, opts) {
var data = response.result.data;
if (data.status === 'Active') {
if (data.level === 'c') {
me.updateCommunity(data);
} else {
me.updateActive(data);
}
} else {
me.updateInactive(data);
}
}
});
};
Ext.apply(me, {
autoScroll: true,
bodyStyle: 'padding:10px',
listeners: {
activate: reload
}
});
me.callParent();
}
});
Ext.define('PVE.SecurityGroupEdit', {
extend: 'PVE.window.Edit',
base_url: "/cluster/firewall/groups",
allow_iface: false,
initComponent : function() {
/*jslint confusion: true */
var me = this;
me.create = (me.group_name === undefined);
var subject;
me.url = '/api2/extjs' + me.base_url;
me.method = 'POST';
var items = [
{
xtype: 'textfield',
name: 'group',
value: me.group_name || '',
fieldLabel: gettext('Name'),
allowBlank: false
},
{
xtype: 'textfield',
name: 'comment',
value: me.group_comment || '',
fieldLabel: gettext('Comment')
}
];
if (me.create) {
subject = gettext('Security Group');
} else {
subject = gettext('Security Group') + " '" + me.group_name + "'";
items.push({
xtype: 'hiddenfield',
name: 'rename',
value: me.group_name
});
}
var ipanel = Ext.create('PVE.panel.InputPanel', {
create: me.create,
items: items
});
Ext.apply(me, {
subject: subject,
items: [ ipanel ]
});
me.callParent();
}
});
Ext.define('PVE.SecurityGroupList', {
extend: 'Ext.grid.Panel',
alias: 'widget.pveSecurityGroupList',
rule_panel: undefined,
addBtn: undefined,
removeBtn: undefined,
editBtn: undefined,
base_url: "/cluster/firewall/groups",
initComponent: function() {
/*jslint confusion: true */
var me = this;
if (me.rule_panel == undefined) {
throw "no rule panel specified";
}
if (me.base_url == undefined) {
throw "no base_url specified";
}
var store = new Ext.data.Store({
fields: [ 'group', 'comment', 'digest' ],
proxy: {
type: 'pve',
url: '/api2/json' + me.base_url
},
idProperty: 'group',
sorters: {
property: 'group',
order: 'DESC'
}
});
var sm = Ext.create('Ext.selection.RowModel', {});
var reload = function() {
var oldrec = sm.getSelection()[0];
store.load(function(records, operation, success) {
if (oldrec) {
var rec = store.findRecord('group', oldrec.data.group);
if (rec) {
sm.select(rec);
}
}
});
};
var run_editor = function() {
var rec = sm.getSelection()[0];
if (!rec) {
return;
}
var win = Ext.create('PVE.SecurityGroupEdit', {
digest: rec.data.digest,
group_name: rec.data.group,
group_comment: rec.data.comment
});
win.show();
win.on('destroy', reload);
};
me.editBtn = new PVE.button.Button({
text: gettext('Edit'),
disabled: true,
selModel: sm,
handler: run_editor
});
me.addBtn = new PVE.button.Button({
text: gettext('Create'),
handler: function() {
sm.deselectAll();
var win = Ext.create('PVE.SecurityGroupEdit', {});
win.show();
win.on('destroy', reload);
}
});
me.removeBtn = new PVE.button.Button({
text: gettext('Remove'),
selModel: sm,
disabled: true,
handler: function() {
var rec = sm.getSelection()[0];
if (!rec || !me.base_url) {
return;
}
PVE.Utils.API2Request({
url: me.base_url + '/' + rec.data.group,
method: 'DELETE',
waitMsgTarget: me,
failure: function(response, options) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
callback: reload
});
}
});
Ext.apply(me, {
store: store,
tbar: [ '<b>' + gettext('Group') + ':</b>', me.addBtn, me.removeBtn, me.editBtn ],
selModel: sm,
columns: [
{ header: gettext('Group'), dataIndex: 'group', width: 100 },
{ header: gettext('Comment'), dataIndex: 'comment', renderer: Ext.String.htmlEncode, flex: 1 }
],
listeners: {
itemdblclick: run_editor,
select: function(sm, rec) {
var url = '/cluster/firewall/groups/' + rec.data.group;
me.rule_panel.setBaseUrl(url);
},
deselect: function() {
me.rule_panel.setBaseUrl(undefined);
},
show: reload
}
});
me.callParent();
store.load();
}
});
Ext.define('PVE.SecurityGroups', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveSecurityGroups',
title: 'Security Groups',
initComponent: function() {
var me = this;
var rule_panel = Ext.createWidget('pveFirewallRules', {
region: 'center',
allow_groups: false,
list_refs_url: '/cluster/firewall/refs',
tbar_prefix: '<b>' + gettext('Rules') + ':</b>',
flex: 0.75,
border: false
});
var sglist = Ext.createWidget('pveSecurityGroupList', {
region: 'west',
rule_panel: rule_panel,
flex: 0.25,
border: false,
split: true
});
Ext.apply(me, {
layout: 'border',
items: [ sglist, rule_panel ],
listeners: {
show: function() {
sglist.fireEvent('show', sglist);
}
}
});
me.callParent();
}
});
/*
* Datacenter config panel, located in the center of the ViewPort after the Datacenter view is selected
*/
Ext.define('PVE.dc.Config', {
extend: 'PVE.panel.Config',
alias: 'widget.PVE.dc.Config',
initComponent: function() {
var me = this;
var caps = Ext.state.Manager.get('GuiCap');
me.items = [];
Ext.apply(me, {
title: gettext("Datacenter"),
hstateid: 'dctab'
});
if (caps.dc['Sys.Audit']) {
me.items.push({
title: gettext('Summary'),
xtype: 'pveDcSummary',
itemId: 'summary'
});
me.items.push({
xtype: 'pveDcOptionView',
title: gettext('Options'),
itemId: 'options'
});
}
if (caps.storage['Datastore.Allocate'] || caps.dc['Sys.Audit']) {
me.items.push({
xtype: 'pveStorageView',
title: gettext('Storage'),
itemId: 'storage'
});
}
if (caps.dc['Sys.Audit']) {
me.items.push({
xtype: 'pveDcBackupView',
title: gettext('Backup'),
itemId: 'backup'
});
}
me.items.push({
xtype: 'pveUserView',
title: gettext('Users'),
itemId: 'users'
});
if (caps.dc['Sys.Audit']) {
me.items.push({
xtype: 'pveGroupView',
title: gettext('Groups'),
itemId: 'groups'
});
me.items.push({
xtype: 'pvePoolView',
title: gettext('Pools'),
itemId: 'pools'
});
me.items.push({
xtype: 'pveACLView',
title: gettext('Permissions'),
itemId: 'permissions'
});
me.items.push({
xtype: 'pveRoleView',
title: gettext('Roles'),
itemId: 'roles'
});
me.items.push({
xtype: 'pveAuthView',
title: gettext('Authentication'),
itemId: 'domains'
});
me.items.push({
xtype: 'pveHAPanel',
title: 'HA',
phstateid: me.hstateid,
itemId: 'ha'
});
me.items.push({
xtype: 'pveFirewallPanel',
title: gettext('Firewall'),
base_url: '/cluster/firewall',
fwtype: 'dc',
phstateid: me.hstateid,
itemId: 'firewall'
});
me.items.push({
xtype: 'pveDcSupport',
title: gettext('Support'),
itemId: 'support'
});
}
me.callParent();
}
});
/*
* Workspace base class
*
* popup login window when auth fails (call onLogin handler)
* update (re-login) ticket every 15 minutes
*
*/
Ext.define('PVE.Workspace', {
extend: 'Ext.container.Viewport',
title: 'Proxmox Virtual Environment',
loginData: null, // Data from last login call
onLogin: function(loginData) {},
// private
updateLoginData: function(loginData) {
var me = this;
me.loginData = loginData;
PVE.CSRFPreventionToken = loginData.CSRFPreventionToken;
PVE.UserName = loginData.username;
if (loginData.cap) {
Ext.state.Manager.set('GuiCap', loginData.cap);
}
// creates a session cookie (expire = null)
// that way the cookie gets deleted after browser window close
Ext.util.Cookies.set('PVEAuthCookie', loginData.ticket, null, '/', null, true);
me.onLogin(loginData);
},
// private
showLogin: function() {
var me = this;
PVE.Utils.authClear();
PVE.UserName = null;
me.loginData = null;
if (!me.login) {
me.login = Ext.create('PVE.window.LoginWindow', {
handler: function(data) {
me.login = null;
me.updateLoginData(data);
PVE.Utils.checked_command(function() {}); // display subscription status
}
});
}
me.onLogin(null);
me.login.show();
},
initComponent : function() {
var me = this;
Ext.tip.QuickTipManager.init();
// fixme: what about other errors
Ext.Ajax.on('requestexception', function(conn, response, options) {
if (response.status == 401) { // auth failure
me.showLogin();
}
});
me.callParent();
if (!PVE.Utils.authOK()) {
me.showLogin();
} else {
if (me.loginData) {
me.onLogin(me.loginData);
}
}
Ext.TaskManager.start({
run: function() {
var ticket = PVE.Utils.authOK();
if (!ticket || !PVE.UserName) {
return;
}
Ext.Ajax.request({
params: {
username: PVE.UserName,
password: ticket
},
url: '/api2/json/access/ticket',
method: 'POST',
success: function(response, opts) {
var obj = Ext.decode(response.responseText);
me.updateLoginData(obj.data);
}
});
},
interval: 15*60*1000
});
}
});
Ext.define('PVE.ConsoleWorkspace', {
extend: 'PVE.Workspace',
alias: ['widget.pveConsoleWorkspace'],
title: gettext('Console'),
initComponent : function() {
// novnc is a string in param
// but a boolean in content
/*jslint confusion: true*/
var me = this;
var param = Ext.Object.fromQueryString(window.location.search);
var consoleType = me.consoleType || param.console;
param.novnc = (param.novnc === '1') ? true : false;
var content;
if (consoleType === 'kvm') {
me.title = "VM " + param.vmid;
if (param.vmname) {
me.title += " ('" + param.vmname + "')";
}
content = {
xtype: 'pveKVMConsole',
novnc: param.novnc,
vmid: param.vmid,
nodename: param.node,
vmname: param.vmname,
toplevel: true
};
} else if (consoleType === 'lxc') {
me.title = "CT " + param.vmid;
if (param.vmname) {
me.title += " ('" + param.vmname + "')";
}
content = {
xtype: 'pveLxcConsole',
novnc: param.novnc,
vmid: param.vmid,
nodename: param.node,
vmname: param.vmname,
toplevel: true
};
} else if (consoleType === 'shell') {
me.title = "node '" + param.node + "'";
content = {
xtype: 'pveShell',
novnc: param.novnc,
nodename: param.node,
toplevel: true
};
} else if (consoleType === 'upgrade') {
me.title = Ext.String.format(gettext('System upgrade on node {0}'), "'" + param.node + "'");
content = {
xtype: 'pveShell',
novnc: param.novnc,
nodename: param.node,
ugradeSystem: true,
toplevel: true
};
} else {
content = {
border: false,
bodyPadding: 10,
html: gettext('Error') + ': No such console type'
};
}
Ext.apply(me, {
layout: { type: 'fit' },
border: false,
items: [ content ]
});
me.callParent();
}
});
Ext.define('PVE.StdWorkspace', {
extend: 'PVE.Workspace',
alias: ['widget.pveStdWorkspace'],
// private
setContent: function(comp) {
var me = this;
var cont = me.child('#content');
var lay = cont.getLayout();
var cur = lay.getActiveItem();
if (comp) {
PVE.Utils.setErrorMask(cont, false);
comp.border = false;
cont.add(comp);
if (cur !== null && lay.getNext()) {
lay.next();
var task = Ext.create('Ext.util.DelayedTask', function(){
cont.remove(cur);
});
task.delay(10);
}
}
// else {
// TODO: display something useful
// Note:: error mask has wrong zindex, so we do not
// use that - see bug 114
// PVE.Utils.setErrorMask(cont, 'nothing selected');
//}
},
selectById: function(nodeid) {
var me = this;
var tree = me.down('pveResourceTree');
tree.selectById(nodeid);
},
checkVmMigration: function(record) {
var me = this;
var tree = me.down('pveResourceTree');
tree.checkVmMigration(record);
},
onLogin: function(loginData) {
var me = this;
me.updateUserInfo();
if (loginData) {
PVE.data.ResourceStore.startUpdate();
PVE.Utils.API2Request({
url: '/version',
method: 'GET',
success: function(response) {
PVE.VersionInfo = response.result.data;
me.updateVersionInfo();
}
});
}
},
updateUserInfo: function() {
var me = this;
var ui = me.query('#userinfo')[0];
if (PVE.UserName) {
var msg = Ext.String.format(gettext("You are logged in as {0}"), "'" + PVE.UserName + "'");
ui.update('<div class="x-unselectable" style="white-space:nowrap;">' + msg + '</div>');
} else {
ui.update('');
}
ui.updateLayout();
},
updateVersionInfo: function() {
var me = this;
var ui = me.query('#versioninfo')[0];
if (PVE.VersionInfo) {
var version = PVE.VersionInfo.version + '-' + PVE.VersionInfo.release + '/' +
PVE.VersionInfo.repoid;
ui.update('Proxmox Virtual Environment ' + version);
} else {
ui.update('Proxmox Virtual Environment');
}
ui.updateLayout();
},
initComponent : function() {
var me = this;
Ext.History.init();
var sprovider = Ext.create('PVE.StateProvider');
Ext.state.Manager.setProvider(sprovider);
var selview = Ext.create('PVE.form.ViewSelector');
var rtree = Ext.createWidget('pveResourceTree', {
viewFilter: selview.getViewFilter(),
flex: 1,
selModel: {
selType: 'treemodel',
listeners: {
selectionchange: function(sm, selected) {
var comp;
var tlckup = {
root: 'PVE.dc.Config',
node: 'PVE.node.Config',
qemu: 'PVE.qemu.Config',
lxc: 'PVE.lxc.Config',
storage: 'PVE.storage.Browser',
pool: 'pvePoolConfig'
};
if (selected.length > 0) {
var n = selected[0];
comp = {
xtype: tlckup[n.data.type || 'root'] ||
'pvePanelConfig',
layout: { type: 'fit' },
showSearch: (n.data.id === 'root') ||
Ext.isDefined(n.data.groupbyid),
pveSelNode: n,
workspace: me,
viewFilter: selview.getViewFilter()
};
PVE.curSelectedNode = n;
}
me.setContent(comp);
}
}
}
});
selview.on('select', function(combo, records) {
if (records) {
var view = combo.getViewFilter();
rtree.setViewFilter(view);
}
});
var caps = sprovider.get('GuiCap');
var createVM = Ext.createWidget('button', {
pack: 'end',
margin: '3 5 0 0',
baseCls: 'x-btn',
iconCls: 'fa fa-desktop',
text: gettext("Create VM"),
disabled: !caps.vms['VM.Allocate'],
handler: function() {
var wiz = Ext.create('PVE.qemu.CreateWizard', {});
wiz.show();
}
});
var createCT = Ext.createWidget('button', {
pack: 'end',
margin: '3 5 0 0',
baseCls: 'x-btn',
iconCls: 'fa fa-cube',
text: gettext("Create CT"),
disabled: !caps.vms['VM.Allocate'],
handler: function() {
var wiz = Ext.create('PVE.lxc.CreateWizard', {});
wiz.show();
}
});
sprovider.on('statechange', function(sp, key, value) {
if (key === 'GuiCap' && value) {
caps = value;
createVM.setDisabled(!caps.vms['VM.Allocate']);
createCT.setDisabled(!caps.vms['VM.Allocate']);
}
});
Ext.apply(me, {
layout: { type: 'border' },
border: false,
items: [
{
region: 'north',
layout: {
type: 'hbox',
align: 'middle'
},
baseCls: 'x-plain',
defaults: {
baseCls: 'x-plain'
},
border: false,
margin: '2 0 2 5',
items: [
{
html: '<a class="x-unselectable" target=_blank href="http://www.proxmox.com">' +
'<img height=30 width=209 src="/pve2/images/proxmox_logo.png"/></a>'
},
{
minWidth: 200,
flex: 1,
id: 'versioninfo',
html: 'Proxmox Virtual Environment'
},
{
pack: 'end',
margin: '0 10 0 0',
id: 'userinfo',
stateful: false
},
createVM,
createCT,
{
pack: 'end',
margin: '0 5 0 0',
xtype: 'button',
baseCls: 'x-btn',
iconCls: 'fa fa-sign-out',
text: gettext("Logout"),
handler: function() {
PVE.data.ResourceStore.stopUpdate();
me.showLogin();
me.setContent();
var rt = me.down('pveResourceTree');
rt.clearTree();
}
}
]
},
{
region: 'center',
stateful: true,
stateId: 'pvecenter',
minWidth: 100,
minHeight: 100,
id: 'content',
xtype: 'container',
layout: { type: 'card' },
border: false,
margin: '0 5 0 0',
items: []
},
{
region: 'west',
stateful: true,
stateId: 'pvewest',
itemId: 'west',
xtype: 'container',
border: false,
layout: { type: 'vbox', align: 'stretch' },
margin: '0 0 0 5',
split: true,
width: 200,
items: [ selview, rtree ],
listeners: {
resize: function(panel, width, height) {
var viewWidth = me.getSize().width;
if (width > viewWidth - 100) {
panel.setWidth(viewWidth - 100);
}
}
}
},
{
xtype: 'pveStatusPanel',
stateful: true,
stateId: 'pvesouth',
itemId: 'south',
region: 'south',
margin:'0 5 5 5',
title: gettext('Logs'),
collapsible: true,
header: false,
height: 200,
split:true,
listeners: {
resize: function(panel, width, height) {
console.log('test');
var viewHeight = me.getSize().height;
if (height > (viewHeight - 150)) {
panel.setHeight(viewHeight - 150);
}
}
}
}
]
});
me.callParent();
me.updateUserInfo();
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment