3 // avoid errors related to Accessible Rich Internet Applications
4 // (access for people with disabilities)
5 // TODO reenable after all components are upgraded
6 Ext
.enableAria
= false;
7 Ext
.enableAriaButtons
= false;
8 Ext
.enableAriaPanels
= false;
10 // avoid errors when running without development tools
11 if (!Ext
.isDefined(Ext
.global
.console
)) {
16 console
.log("Starting PVE Manager");
18 Ext
.Ajax
.defaultHeaders
= {
19 'Accept': 'application/json'
22 Ext
.define('PVE.Utils', { utilities
: {
24 // this singleton contains miscellaneous utilities
26 toolkit
: undefined, // (extjs|touch), set inside Toolkit.js
28 bus_match
: /^(ide|sata|virtio|scsi)\d+$/,
42 'c': gettext('Community'),
43 'b': gettext('Basic'),
44 's': gettext('Standard'),
45 'p': gettext('Premium')
48 noSubKeyHtml
: 'You do not have a valid subscription for this server. Please visit <a target="_blank" href="https://www.proxmox.com/products/proxmox-ve/subscription-service-plans">www.proxmox.com</a> to get a list of available options.',
52 { desc
: '5.x - 2.6 Kernel', val
: 'l26' },
53 { desc
: '2.4 Kernel', val
: 'l24' }
55 'Microsoft Windows': [
56 { desc
: '10/2016/2019', val
: 'win10' },
57 { desc
: '8.x/2012/2012r2', val
: 'win8' },
58 { desc
: '7/2008r2', val
: 'win7' },
59 { desc
: 'Vista/2008', val
: 'w2k8' },
60 { desc
: 'XP/2003', val
: 'wxp' },
61 { desc
: '2000', val
: 'w2k' }
64 { desc
: '-', val
: 'solaris'}
67 { desc
: '-', val
: 'other'}
71 get_health_icon: function(state
, circle
) {
72 if (circle
=== undefined) {
76 if (state
=== undefined) {
80 var icon
= 'faded fa-question';
83 icon
= 'good fa-check';
86 icon
= 'warning fa-upload';
89 icon
= 'warning fa-refresh';
92 icon
= 'warning fa-exclamation';
95 icon
= 'critical fa-times';
107 parse_ceph_version: function(service
) {
108 if (service
.ceph_version_short
) {
109 return service
.ceph_version_short
;
112 if (service
.ceph_version
) {
113 var match
= service
.ceph_version
.match(/version (\d+(\.\d+)*)/);
122 compare_ceph_versions: function(a
, b
) {
130 if (Ext
.isArray(a
)) {
131 avers
= a
.slice(); // copy array
133 avers
= a
.toString().split('.');
136 if (Ext
.isArray(b
)) {
137 bvers
= b
.slice(); // copy array
139 bvers
= b
.toString().split('.');
143 let av
= avers
.shift();
144 let bv
= bvers
.shift();
146 if (av
=== undefined && bv
=== undefined) {
148 } else if (av
=== undefined) {
150 } else if (bv
=== undefined) {
153 let diff
= parseInt(av
, 10) - parseInt(bv
, 10);
154 if (diff
!= 0) return diff
;
155 // else we need to look at the next parts
161 get_ceph_icon_html: function(health
, fw
) {
162 var state
= PVE
.Utils
.map_ceph_health
[health
];
163 var cls
= PVE
.Utils
.get_health_icon(state
);
167 return "<i class='fa " + cls
+ "'></i> ";
172 'HEALTH_UPGRADE':'upgrade',
174 'HEALTH_WARN':'warning',
175 'HEALTH_ERR':'critical'
178 render_ceph_health: function(healthObj
) {
180 iconCls
: PVE
.Utils
.get_health_icon(),
184 if (!healthObj
|| !healthObj
.status
) {
188 var health
= PVE
.Utils
.map_ceph_health
[healthObj
.status
];
190 state
.iconCls
= PVE
.Utils
.get_health_icon(health
, true);
191 state
.text
= healthObj
.status
;
196 render_zfs_health: function(value
) {
197 if (typeof value
== 'undefined'){
200 var iconCls
= 'question-circle';
204 iconCls
= 'check-circle good';
208 iconCls
= 'exclamation-circle warning';
213 iconCls
= 'times-circle critical';
218 return '<i class="fa fa-' + iconCls
+ '"></i> ' + value
;
222 render_pbs_fingerprint
: fp
=> fp
.substring(0, 23),
224 render_backup_encryption: function(v
, meta
, record
) {
226 return gettext('No');
230 if (v
.match(/^[a-fA-F0-9]{2}:/)) { // fingerprint
231 tip
= `Key fingerprint ${PVE.Utils.render_pbs_fingerprint(v)}`;
233 let icon
= `<i class="fa fa-fw fa-lock good"></i>`;
234 return `<span data-qtip="${tip}">${icon} ${gettext('Encrypted')}</span>`;
237 render_backup_verification: function(v
, meta
, record
) {
238 let i
= (cls
, txt
) => `<i class="fa fa-fw fa-${cls}"></i> ${txt}`;
239 if (v
=== undefined || v
=== null) {
240 return i('question-circle-o warning', gettext('None'));
243 let txt
= gettext('Failed');
244 let iconCls
= 'times critical';
245 if (v
.state
=== 'ok') {
247 iconCls
= 'check good';
248 let now
= Date
.now() / 1000;
249 let task
= Proxmox
.Utils
.parse_task_upid(v
.upid
);
250 let verify_time
= Proxmox
.Utils
.render_timestamp(task
.starttime
);
251 tip
= `Last verify task started on ${verify_time}`;
252 if (now
- v
.starttime
> 30 * 24 * 60 * 60) {
253 tip
= `Last verify task over 30 days ago: ${verify_time}`;
254 iconCls
= 'check warning';
257 return `<span data-qtip="${tip}"> ${i(iconCls, txt)} </span>`;
260 render_backup_status: function(value
, meta
, record
) {
261 if (typeof value
== 'undefined') {
265 let iconCls
= 'check-circle good';
266 let text
= gettext('Yes');
268 if (!PVE
.Parser
.parseBoolean(value
.toString())) {
269 iconCls
= 'times-circle critical';
271 text
= gettext('No');
273 let reason
= record
.get('reason');
274 if (typeof reason
!== 'undefined') {
275 if (reason
in PVE
.Utils
.backup_reasons_table
) {
276 reason
= PVE
.Utils
.backup_reasons_table
[record
.get('reason')];
278 text
= `${text} - ${reason}`;
282 return `<i class="fa fa-${iconCls}"></i> ${text}`;
285 render_backup_days_of_week: function(val
) {
286 var dows
= ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
289 val
.split(',').forEach(function(day
){
291 var dow
= (dows
.indexOf(day
)+6)%7;
293 if (selected
.length
=== 0 || selected
[selected
.length
-1] === 0) {
296 selected
[selected
.length
-1]++;
309 selected
.forEach(function(item
) {
312 days
.push(Ext
.Date
.dayNames
[(cur
+1)] + '-' + Ext
.Date
.dayNames
[(cur
+item
)%7]);
314 } else if (item
== 2) {
315 days
.push(Ext
.Date
.dayNames
[cur
+1]);
316 days
.push(Ext
.Date
.dayNames
[(cur
+2)%7]);
318 } else if (item
== 1) {
319 days
.push(Ext
.Date
.dayNames
[(cur
+1)%7]);
322 return days
.join(', ');
325 render_backup_selection: function(value
, metaData
, record
) {
326 let allExceptText
= gettext('All except {0}');
327 let allText
= '-- ' + gettext('All') + ' --';
328 if (record
.data
.all
) {
329 if (record
.data
.exclude
) {
330 return Ext
.String
.format(allExceptText
, record
.data
.exclude
);
334 if (record
.data
.vmid
) {
335 return record
.data
.vmid
;
338 if (record
.data
.pool
) {
339 return "Pool '"+ record
.data
.pool
+ "'";
345 backup_reasons_table
: {
346 'backup=yes': gettext('Enabled'),
347 'backup=no': gettext('Disabled'),
348 'enabled': gettext('Enabled'),
349 'disabled': gettext('Disabled'),
350 'not a volume': gettext('Not a volume'),
351 'efidisk but no OMVF BIOS': gettext('EFI Disk without OMVF BIOS'),
354 get_kvm_osinfo: function(value
) {
355 var info
= { base
: 'Other' }; // default
357 Ext
.each(Object
.keys(PVE
.Utils
.kvm_ostypes
), function(k
) {
358 Ext
.each(PVE
.Utils
.kvm_ostypes
[k
], function(e
) {
359 if (e
.val
=== value
) {
360 info
= { desc
: e
.desc
, base
: k
};
368 render_kvm_ostype: function (value
) {
369 var osinfo
= PVE
.Utils
.get_kvm_osinfo(value
);
370 if (osinfo
.desc
&& osinfo
.desc
!== '-') {
371 return osinfo
.base
+ ' ' + osinfo
.desc
;
377 render_hotplug_features: function (value
) {
380 if (!value
|| (value
=== '0')) {
381 return gettext('Disabled');
385 value
= 'disk,network,usb';
388 Ext
.each(value
.split(','), function(el
) {
390 fa
.push(gettext('Disk'));
391 } else if (el
=== 'network') {
392 fa
.push(gettext('Network'));
393 } else if (el
=== 'usb') {
395 } else if (el
=== 'memory') {
396 fa
.push(gettext('Memory'));
397 } else if (el
=== 'cpu') {
398 fa
.push(gettext('CPU'));
404 return fa
.join(', ');
407 render_localtime: function(value
) {
408 if (value
=== '__default__') {
409 return Proxmox
.Utils
.defaultText
+ ' (' + gettext('Enabled for Windows') + ')';
411 return Proxmox
.Utils
.format_boolean(value
);
414 render_qga_features: function(value
) {
416 return Proxmox
.Utils
.defaultText
+ ' (' + Proxmox
.Utils
.disabledText
+ ')';
418 var props
= PVE
.Parser
.parsePropertyString(value
, 'enabled');
419 if (!PVE
.Parser
.parseBoolean(props
.enabled
)) {
420 return Proxmox
.Utils
.disabledText
;
423 delete props
.enabled
;
424 var agentstring
= Proxmox
.Utils
.enabledText
;
426 Ext
.Object
.each(props
, function(key
, value
) {
428 agentstring
+= ', ' + key
+ ': ';
430 if (key
=== 'type') {
435 agentstring
+= map
[value
] || Proxmox
.Utils
.unknownText
;
437 if (PVE
.Parser
.parseBoolean(value
)) {
438 agentstring
+= Proxmox
.Utils
.enabledText
;
440 agentstring
+= Proxmox
.Utils
.disabledText
;
448 render_qemu_machine: function(value
) {
449 return value
|| (Proxmox
.Utils
.defaultText
+ ' (i440fx)');
452 render_qemu_bios: function(value
) {
454 return Proxmox
.Utils
.defaultText
+ ' (SeaBIOS)';
455 } else if (value
=== 'seabios') {
457 } else if (value
=== 'ovmf') {
458 return "OVMF (UEFI)";
464 render_dc_ha_opts: function(value
) {
466 return Proxmox
.Utils
.defaultText
;
468 return PVE
.Parser
.printPropertyString(value
);
471 render_as_property_string: function(value
) {
472 return (!value
) ? Proxmox
.Utils
.defaultText
473 : PVE
.Parser
.printPropertyString(value
);
476 render_scsihw: function(value
) {
478 return Proxmox
.Utils
.defaultText
+ ' (LSI 53C895A)';
479 } else if (value
=== 'lsi') {
480 return 'LSI 53C895A';
481 } else if (value
=== 'lsi53c810') {
483 } else if (value
=== 'megasas') {
484 return 'MegaRAID SAS 8708EM2';
485 } else if (value
=== 'virtio-scsi-pci') {
486 return 'VirtIO SCSI';
487 } else if (value
=== 'virtio-scsi-single') {
488 return 'VirtIO SCSI single';
489 } else if (value
=== 'pvscsi') {
490 return 'VMware PVSCSI';
496 render_spice_enhancements: function(values
) {
497 let props
= PVE
.Parser
.parsePropertyString(values
);
498 if (Ext
.Object
.isEmpty(props
)) {
499 return Proxmox
.Utils
.noneText
;
503 if (PVE
.Parser
.parseBoolean(props
.foldersharing
)) {
504 output
.push('Folder Sharing: ' + gettext('Enabled'));
506 if (props
.videostreaming
=== 'all' || props
.videostreaming
=== 'filter') {
507 output
.push('Video Streaming: ' + props
.videostreaming
);
509 return output
.join(', ');
512 // fixme: auto-generate this
513 // for now, please keep in sync with PVE::Tools::kvmkeymaps
518 'de-ch': 'German (Swiss)',
519 'en-gb': 'English (UK)',
520 'en-us': 'English (USA)',
524 //fo: 'Faroe Islands',
526 'fr-be': 'French (Belgium)',
527 'fr-ca': 'French (Canada)',
528 'fr-ch': 'French (Swiss)',
538 //'nl-be': 'Dutch (Belgium)',
542 'pt-br': 'Portuguese (Brazil)',
551 std
: gettext('Standard VGA'),
552 vmware
: gettext('VMware compatible'),
554 qxl2
: 'SPICE dual monitor',
555 qxl3
: 'SPICE three monitors',
556 qxl4
: 'SPICE four monitors',
557 serial0
: gettext('Serial terminal') + ' 0',
558 serial1
: gettext('Serial terminal') + ' 1',
559 serial2
: gettext('Serial terminal') + ' 2',
560 serial3
: gettext('Serial terminal') + ' 3',
561 virtio
: 'VirtIO-GPU',
562 none
: Proxmox
.Utils
.noneText
565 render_kvm_language: function (value
) {
566 if (!value
|| value
=== '__default__') {
567 return Proxmox
.Utils
.defaultText
;
569 var text
= PVE
.Utils
.kvm_keymaps
[value
];
571 return text
+ ' (' + value
+ ')';
576 kvm_keymap_array: function() {
577 var data
= [['__default__', PVE
.Utils
.render_kvm_language('')]];
578 Ext
.Object
.each(PVE
.Utils
.kvm_keymaps
, function(key
, value
) {
579 data
.push([key
, PVE
.Utils
.render_kvm_language(value
)]);
586 '__default__': Proxmox
.Utils
.defaultText
+ ' (xterm.js)',
587 'vv': 'SPICE (remote-viewer)',
588 'html5': 'HTML5 (noVNC)',
589 'xtermjs': 'xterm.js'
592 render_console_viewer: function(value
) {
593 value
= value
|| '__default__';
594 if (PVE
.Utils
.console_map
[value
]) {
595 return PVE
.Utils
.console_map
[value
];
600 console_viewer_array: function() {
601 return Ext
.Array
.map(Object
.keys(PVE
.Utils
.console_map
), function(v
) {
602 return [v
, PVE
.Utils
.render_console_viewer(v
)];
606 render_kvm_vga_driver: function (value
) {
608 return Proxmox
.Utils
.defaultText
;
610 var vga
= PVE
.Parser
.parsePropertyString(value
, 'type');
611 var text
= PVE
.Utils
.kvm_vga_drivers
[vga
.type
];
613 text
= Proxmox
.Utils
.defaultText
;
616 return text
+ ' (' + value
+ ')';
621 kvm_vga_driver_array: function() {
622 var data
= [['__default__', PVE
.Utils
.render_kvm_vga_driver('')]];
623 Ext
.Object
.each(PVE
.Utils
.kvm_vga_drivers
, function(key
, value
) {
624 data
.push([key
, PVE
.Utils
.render_kvm_vga_driver(value
)]);
630 render_kvm_startup: function(value
) {
631 var startup
= PVE
.Parser
.parseStartup(value
);
634 if (startup
.order
=== undefined) {
637 res
+= startup
.order
;
639 if (startup
.up
!== undefined) {
640 res
+= ',up=' + startup
.up
;
642 if (startup
.down
!== undefined) {
643 res
+= ',down=' + startup
.down
;
649 extractFormActionError: function(action
) {
651 switch (action
.failureType
) {
652 case Ext
.form
.action
.Action
.CLIENT_INVALID
:
653 msg
= gettext('Form fields may not be submitted with invalid values');
655 case Ext
.form
.action
.Action
.CONNECT_FAILURE
:
656 msg
= gettext('Connection error');
657 var resp
= action
.response
;
658 if (resp
.status
&& resp
.statusText
) {
659 msg
+= " " + resp
.status
+ ": " + resp
.statusText
;
662 case Ext
.form
.action
.Action
.LOAD_FAILURE
:
663 case Ext
.form
.action
.Action
.SERVER_INVALID
:
664 msg
= Proxmox
.Utils
.extractRequestError(action
.result
, true);
671 'images': gettext('Disk image'),
672 'backup': gettext('VZDump backup file'),
673 'vztmpl': gettext('Container template'),
674 'iso': gettext('ISO image'),
675 'rootdir': gettext('Container'),
676 'snippets': gettext('Snippets')
679 volume_is_qemu_backup: function(volid
, format
) {
680 return format
=== 'pbs-vm' || volid
.match(':backup/vzdump-qemu-');
683 volume_is_lxc_backup: function(volid
, format
) {
684 return format
=== 'pbs-ct' || volid
.match(':backup/vzdump-(lxc|openvz)-');
689 name
: gettext('Active Directory Server'),
690 ipanel
: 'pveAuthADPanel',
691 syncipanel
: 'pveAuthLDAPSyncPanel',
695 name
: gettext('LDAP Server'),
696 ipanel
: 'pveAuthLDAPPanel',
697 syncipanel
: 'pveAuthLDAPSyncPanel',
702 ipanel
: 'pveAuthBasePanel',
706 name
: 'Proxmox VE authentication server',
707 ipanel
: 'pveAuthBasePanel',
714 name
: Proxmox
.Utils
.directoryText
,
715 ipanel
: 'DirInputPanel',
721 ipanel
: 'LVMInputPanel',
727 ipanel
: 'LvmThinInputPanel',
733 ipanel
: 'NFSInputPanel',
739 ipanel
: 'CIFSInputPanel',
745 ipanel
: 'GlusterFsInputPanel',
751 ipanel
: 'IScsiInputPanel',
757 ipanel
: 'CephFSInputPanel',
762 name
: 'CephFS (PVE)',
763 ipanel
: 'CephFSInputPanel',
770 ipanel
: 'RBDInputPanel',
776 ipanel
: 'RBDInputPanel',
782 name
: 'ZFS over iSCSI',
783 ipanel
: 'ZFSInputPanel',
789 ipanel
: 'ZFSPoolInputPanel',
794 name
: 'Proxmox Backup Server',
795 ipanel
: 'PBSInputPanel',
820 ipanel
: 'SimpleInputPanel',
825 ipanel
: 'VlanInputPanel',
830 ipanel
: 'QinQInputPanel',
835 ipanel
: 'VxlanInputPanel',
840 ipanel
: 'EvpnInputPanel',
845 sdncontrollerSchema
: {
852 ipanel
: 'EvpnInputPanel',
857 format_sdnvnet_type: function(value
, md
, record
) {
858 var schema
= PVE
.Utils
.sdnvnetSchema
[value
];
862 return Proxmox
.Utils
.unknownText
;
865 format_sdnzone_type: function(value
, md
, record
) {
866 var schema
= PVE
.Utils
.sdnzoneSchema
[value
];
870 return Proxmox
.Utils
.unknownText
;
873 format_sdncontroller_type: function(value
, md
, record
) {
874 var schema
= PVE
.Utils
.sdncontrollerSchema
[value
];
878 return Proxmox
.Utils
.unknownText
;
881 format_storage_type: function(value
, md
, record
) {
882 if (value
=== 'rbd') {
883 value
= (!record
|| record
.get('monhost') ? 'rbd' : 'pveceph');
884 } else if (value
=== 'cephfs') {
885 value
= (!record
|| record
.get('monhost') ? 'cephfs' : 'pvecephfs');
888 var schema
= PVE
.Utils
.storageSchema
[value
];
892 return Proxmox
.Utils
.unknownText
;
895 format_ha: function(value
) {
896 var text
= Proxmox
.Utils
.noneText
;
899 text
= value
.state
|| Proxmox
.Utils
.noneText
;
901 text
+= ', ' + Proxmox
.Utils
.groupText
+ ': ';
902 text
+= value
.group
|| Proxmox
.Utils
.noneText
;
908 format_content_types: function(value
) {
909 return value
.split(',').sort().map(function(ct
) {
910 return PVE
.Utils
.contentTypes
[ct
] || ct
;
914 render_storage_content: function(value
, metaData
, record
) {
915 var data
= record
.data
;
916 if (Ext
.isNumber(data
.channel
) &&
917 Ext
.isNumber(data
.id
) &&
918 Ext
.isNumber(data
.lun
)) {
920 Ext
.String
.leftPad(data
.channel
,2, '0') +
921 " ID " + data
.id
+ " LUN " + data
.lun
;
923 return data
.volid
.replace(/^.*?:(.*?\/)?/,'');
926 render_serverity: function (value
) {
927 return PVE
.Utils
.log_severity_hash
[value
] || value
;
930 render_cpu: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
932 if (!(record
.data
.uptime
&& Ext
.isNumeric(value
))) {
936 var maxcpu
= record
.data
.maxcpu
|| 1;
938 if (!Ext
.isNumeric(maxcpu
) && (maxcpu
>= 1)) {
942 var per
= value
* 100;
944 return per
.toFixed(1) + '% of ' + maxcpu
.toString() + (maxcpu
> 1 ? 'CPUs' : 'CPU');
947 render_size: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
949 if (!Ext
.isNumeric(value
)) {
953 return Proxmox
.Utils
.format_size(value
);
956 render_bandwidth: function(value
) {
957 if (!Ext
.isNumeric(value
)) {
961 return Proxmox
.Utils
.format_size(value
) + '/s';
964 render_timestamp_human_readable: function(value
) {
965 return Ext
.Date
.format(new Date(value
* 1000), 'l d F Y H:i:s');
968 calculate_mem_usage: function(data
) {
969 if (!Ext
.isNumeric(data
.mem
) ||
975 return (data
.mem
/ data
.maxmem
);
978 render_mem_usage_percent: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
979 if (!Ext
.isNumeric(value
) || value
=== -1) {
983 // we got no percentage but bytes
985 var maxmem
= record
.data
.maxmem
;
986 if (!record
.data
.uptime
||
988 !Ext
.isNumeric(mem
)) {
992 return ((mem
*100)/maxmem
).toFixed(1) + " %";
994 return (value
*100).toFixed(1) + " %";
997 render_mem_usage: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1000 var maxmem
= record
.data
.maxmem
;
1002 if (!record
.data
.uptime
) {
1006 if (!(Ext
.isNumeric(mem
) && maxmem
)) {
1010 return PVE
.Utils
.render_size(value
);
1013 calculate_disk_usage: function(data
) {
1015 if (!Ext
.isNumeric(data
.disk
) ||
1016 data
.type
=== 'qemu' ||
1017 (data
.type
=== 'lxc' && data
.uptime
=== 0) ||
1018 data
.maxdisk
=== 0) {
1022 return (data
.disk
/ data
.maxdisk
);
1025 render_disk_usage_percent: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1026 if (!Ext
.isNumeric(value
) || value
=== -1) {
1030 return (value
* 100).toFixed(1) + " %";
1033 render_disk_usage: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1036 var maxdisk
= record
.data
.maxdisk
;
1037 var type
= record
.data
.type
;
1039 if (!Ext
.isNumeric(disk
) ||
1042 (type
=== 'lxc' && record
.data
.uptime
=== 0)) {
1046 return PVE
.Utils
.render_size(value
);
1049 get_object_icon_class: function(type
, record
) {
1053 if (type
=== 'type') {
1055 objType
= record
.groupbyid
;
1056 } else if (record
.template
) {
1058 objType
= 'template';
1062 status
= record
.status
+ ' ha-' + record
.hastate
;
1066 status
+= ' locked lock-' + record
.lock
;
1069 var defaults
= PVE
.tree
.ResourceTree
.typeDefaults
[objType
];
1070 if (defaults
&& defaults
.iconCls
) {
1071 var retVal
= defaults
.iconCls
+ ' ' + status
;
1078 render_resource_type: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1080 var cls
= PVE
.Utils
.get_object_icon_class(value
,record
.data
);
1082 var fa
= '<i class="fa-fw x-grid-icon-custom ' + cls
+ '"></i> ';
1086 render_support_level: function(value
, metaData
, record
) {
1087 return PVE
.Utils
.support_level_hash
[value
] || '-';
1090 render_upid: function(value
, metaData
, record
) {
1091 var type
= record
.data
.type
;
1092 var id
= record
.data
.id
;
1094 return Proxmox
.Utils
.format_task_description(type
, id
);
1097 /* render functions for new status panel */
1099 render_usage: function(val
) {
1100 return (val
*100).toFixed(2) + '%';
1103 render_cpu_usage: function(val
, max
) {
1104 return Ext
.String
.format(gettext('{0}% of {1}') +
1105 ' ' + gettext('CPU(s)'), (val
*100).toFixed(2), max
);
1108 render_size_usage: function(val
, max
) {
1110 return gettext('N/A');
1112 return (val
*100/max
).toFixed(2) + '% '+ '(' +
1113 Ext
.String
.format(gettext('{0} of {1}'),
1114 PVE
.Utils
.render_size(val
), PVE
.Utils
.render_size(max
)) + ')';
1117 /* this is different for nodes */
1118 render_node_cpu_usage: function(value
, record
) {
1119 return PVE
.Utils
.render_cpu_usage(value
, record
.cpus
);
1122 /* this is different for nodes */
1123 render_node_size_usage: function(record
) {
1124 return PVE
.Utils
.render_size_usage(record
.used
, record
.total
);
1127 render_optional_url: function(value
) {
1129 if (value
&& (match
= value
.match(/^https?:\/\//)) !== null) {
1130 return '<a target="_blank" href="' + value
+ '">' + value
+ '</a>';
1135 render_san: function(value
) {
1137 if (Ext
.isArray(value
)) {
1138 value
.forEach(function(val
) {
1139 if (!Ext
.isNumber(val
)) {
1143 return names
.join('<br>');
1148 render_full_name: function(firstname
, metaData
, record
) {
1149 var first
= firstname
|| '';
1150 var last
= record
.data
.lastname
|| '';
1151 return Ext
.htmlEncode(first
+ " " + last
);
1154 render_u2f_error: function(error
) {
1156 '1': gettext('Other Error'),
1157 '2': gettext('Bad Request'),
1158 '3': gettext('Configuration Unsupported'),
1159 '4': gettext('Device Ineligible'),
1160 '5': gettext('Timeout')
1162 return "U2F Error: " + ErrorNames
[error
] || Proxmox
.Utils
.unknownText
;
1165 windowHostname: function() {
1166 return window
.location
.hostname
.replace(Proxmox
.Utils
.IP6_bracket_match
,
1167 function(m
, addr
, offset
, original
) { return addr
; });
1170 openDefaultConsoleWindow: function(consoles
, consoleType
, vmid
, nodename
, vmname
, cmd
) {
1171 var dv
= PVE
.Utils
.defaultViewer(consoles
);
1172 PVE
.Utils
.openConsoleWindow(dv
, consoleType
, vmid
, nodename
, vmname
, cmd
);
1175 openConsoleWindow: function(viewer
, consoleType
, vmid
, nodename
, vmname
, cmd
) {
1176 if (vmid
== undefined && (consoleType
=== 'kvm' || consoleType
=== 'lxc')) {
1177 throw "missing vmid";
1180 throw "no nodename specified";
1183 if (viewer
=== 'html5') {
1184 PVE
.Utils
.openVNCViewer(consoleType
, vmid
, nodename
, vmname
, cmd
);
1185 } else if (viewer
=== 'xtermjs') {
1186 Proxmox
.Utils
.openXtermJsViewer(consoleType
, vmid
, nodename
, vmname
, cmd
);
1187 } else if (viewer
=== 'vv') {
1188 let url
= '/nodes/' + nodename
+ '/spiceshell';
1190 proxy
: PVE
.Utils
.windowHostname(),
1192 if (consoleType
=== 'kvm') {
1193 url
= '/nodes/' + nodename
+ '/qemu/' + vmid
.toString() + '/spiceproxy';
1194 } else if (consoleType
=== 'lxc') {
1195 url
= '/nodes/' + nodename
+ '/lxc/' + vmid
.toString() + '/spiceproxy';
1196 } else if (consoleType
=== 'upgrade') {
1197 params
.cmd
= 'upgrade';
1198 } else if (consoleType
=== 'cmd') {
1200 } else if (consoleType
!== 'shell') {
1201 throw `unknown spice viewer type '${consoleType}'`;
1203 PVE
.Utils
.openSpiceViewer(url
, params
);
1205 throw `unknown viewer type '${viewer}'`;
1209 defaultViewer: function(consoles
) {
1211 var allowSpice
, allowXtermjs
;
1213 if (consoles
=== true) {
1215 allowXtermjs
= true;
1216 } else if (typeof consoles
=== 'object') {
1217 allowSpice
= consoles
.spice
;
1218 allowXtermjs
= !!consoles
.xtermjs
;
1220 var dv
= PVE
.VersionInfo
.console
|| 'xtermjs';
1221 if (dv
=== 'vv' && !allowSpice
) {
1222 dv
= (allowXtermjs
) ? 'xtermjs' : 'html5';
1223 } else if (dv
=== 'xtermjs' && !allowXtermjs
) {
1224 dv
= (allowSpice
) ? 'vv' : 'html5';
1230 openVNCViewer: function(vmtype
, vmid
, nodename
, vmname
, cmd
) {
1231 let scaling
= 'off';
1232 if (Proxmox
.Utils
.toolkit
!== 'touch') {
1233 var sp
= Ext
.state
.Manager
.getProvider();
1234 scaling
= sp
.get('novnc-scaling', 'off');
1236 var url
= Ext
.Object
.toQueryString({
1237 console
: vmtype
, // kvm, lxc, upgrade or shell
1245 var nw
= window
.open("?" + url
, '_blank', "innerWidth=745,innerheight=427");
1251 openSpiceViewer: function(url
, params
){
1253 var downloadWithName = function(uri
, name
) {
1254 var link
= Ext
.DomHelper
.append(document
.body
, {
1257 css
: 'display:none;visibility:hidden;height:0px;'
1260 // Note: we need to tell android the correct file name extension
1261 // but we do not set 'download' tag for other environments, because
1262 // It can have strange side effects (additional user prompt on firefox)
1263 var andriod
= navigator
.userAgent
.match(/Android/i) ? true : false;
1265 link
.download
= name
;
1268 if (link
.fireEvent
) {
1269 link
.fireEvent('onclick');
1271 let evt
= document
.createEvent("MouseEvents");
1272 evt
.initMouseEvent('click', true, true, window
, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
1273 link
.dispatchEvent(evt
);
1277 Proxmox
.Utils
.API2Request({
1281 failure: function(response
, opts
){
1282 Ext
.Msg
.alert('Error', response
.htmlStatus
);
1284 success: function(response
, opts
){
1285 var raw
= "[virt-viewer]\n";
1286 Ext
.Object
.each(response
.result
.data
, function(k
, v
) {
1287 raw
+= k
+ "=" + v
+ "\n";
1289 var url
= 'data:application/x-virt-viewer;charset=UTF-8,' +
1290 encodeURIComponent(raw
);
1292 downloadWithName(url
, "pve-spice.vv");
1297 openTreeConsole: function(tree
, record
, item
, index
, e
) {
1299 var nodename
= record
.data
.node
;
1300 var vmid
= record
.data
.vmid
;
1301 var vmname
= record
.data
.name
;
1302 if (record
.data
.type
=== 'qemu' && !record
.data
.template
) {
1303 Proxmox
.Utils
.API2Request({
1304 url
: '/nodes/' + nodename
+ '/qemu/' + vmid
+ '/status/current',
1305 failure: function(response
, opts
) {
1306 Ext
.Msg
.alert('Error', response
.htmlStatus
);
1308 success: function(response
, opts
) {
1309 let conf
= response
.result
.data
;
1311 spice
: !!conf
.spice
,
1312 xtermjs
: !!conf
.serial
,
1314 PVE
.Utils
.openDefaultConsoleWindow(consoles
, 'kvm', vmid
, nodename
, vmname
);
1317 } else if (record
.data
.type
=== 'lxc' && !record
.data
.template
) {
1318 PVE
.Utils
.openDefaultConsoleWindow(true, 'lxc', vmid
, nodename
, vmname
);
1322 // test automation helper
1323 call_menu_handler: function(menu
, text
) {
1325 var list
= menu
.query('menuitem');
1327 Ext
.Array
.each(list
, function(item
) {
1328 if (item
.text
=== text
) {
1339 createCmdMenu: function(v
, record
, item
, index
, event
) {
1341 if (!(v
instanceof Ext
.tree
.View
)) {
1345 var template
= !!record
.data
.template
;
1346 var type
= record
.data
.type
;
1349 if (type
=== 'qemu' || type
== 'lxc') {
1350 menu
= Ext
.create('PVE.menu.TemplateMenu', {
1354 } else if (type
=== 'qemu' ||
1357 menu
= Ext
.create('PVE.' + type
+ '.CmdMenu', {
1359 nodename
: record
.data
.node
1365 menu
.showAt(event
.getXY());
1369 // helper for deleting field which are set to there default values
1370 delete_if_default: function(values
, fieldname
, default_val
, create
) {
1371 if (values
[fieldname
] === '' || values
[fieldname
] === default_val
) {
1373 if (values
['delete']) {
1374 if (Ext
.isArray(values
['delete'])) {
1375 values
['delete'].push(fieldname
);
1377 values
['delete'] += ',' + fieldname
;
1380 values
['delete'] = fieldname
;
1384 delete values
[fieldname
];
1388 loadSSHKeyFromFile: function(file
, callback
) {
1389 // ssh-keygen produces 740 bytes for an average 4096 bit rsa key, with
1390 // a user@host comment, 1420 for 8192 bits; current max is 16kbit
1391 // assume: 740*8 for max. 32kbit (5920 byte file)
1392 // round upwards to nearest nice number => 8192 bytes, leaves lots of comment space
1393 if (file
.size
> 8192) {
1394 Ext
.Msg
.alert(gettext('Error'), gettext("Invalid file size: ") + file
.size
);
1400 var reader
= new FileReader();
1401 reader
.onload = function(evt
) {
1402 callback(evt
.target
.result
);
1404 reader
.readAsText(file
);
1407 loadTextFromFile: function(file
, callback
, maxBytes
) {
1408 let maxSize
= maxBytes
|| 8192;
1409 if (file
.size
> maxSize
) {
1410 Ext
.Msg
.alert(gettext('Error'), gettext("Invalid file size: ") + file
.size
);
1416 let reader
= new FileReader();
1417 reader
.onload
= evt
=> callback(evt
.target
.result
);
1418 reader
.readAsText(file
);
1421 diskControllerMaxIDs
: {
1428 // types is either undefined (all busses), an array of busses, or a single bus
1429 forEachBus: function(types
, func
) {
1430 var busses
= Object
.keys(PVE
.Utils
.diskControllerMaxIDs
);
1431 var i
, j
, count
, cont
;
1433 if (Ext
.isArray(types
)) {
1435 } else if (Ext
.isDefined(types
)) {
1439 // check if we only have valid busses
1440 for (i
= 0; i
< busses
.length
; i
++) {
1441 if (!PVE
.Utils
.diskControllerMaxIDs
[busses
[i
]]) {
1442 throw "invalid bus: '" + busses
[i
] + "'";
1446 for (i
= 0; i
< busses
.length
; i
++) {
1447 count
= PVE
.Utils
.diskControllerMaxIDs
[busses
[i
]];
1448 for (j
= 0; j
< count
; j
++) {
1449 cont
= func(busses
[i
], j
);
1450 if (!cont
&& cont
!== undefined) {
1457 mp_counts
: { mps
: 256, unused
: 256 },
1459 forEachMP: function(func
, includeUnused
) {
1461 for (i
= 0; i
< PVE
.Utils
.mp_counts
.mps
; i
++) {
1462 cont
= func('mp', i
);
1463 if (!cont
&& cont
!== undefined) {
1468 if (!includeUnused
) {
1472 for (i
= 0; i
< PVE
.Utils
.mp_counts
.unused
; i
++) {
1473 cont
= func('unused', i
);
1474 if (!cont
&& cont
!== undefined) {
1480 hardware_counts
: { net
: 32, usb
: 5, hostpci
: 16, audio
: 1, efidisk
: 1, serial
: 4, rng
: 1 },
1482 cleanEmptyObjectKeys: function (obj
) {
1484 for (propName
in obj
) {
1485 if (obj
.hasOwnProperty(propName
)) {
1486 if (obj
[propName
] === null || obj
[propName
] === undefined) {
1487 delete obj
[propName
];
1493 acmedomain_count
: 5,
1495 add_domain_to_acme: function(acme
, domain
) {
1496 if (acme
.domains
=== undefined) {
1497 acme
.domains
= [domain
];
1499 acme
.domains
.push(domain
);
1500 acme
.domains
= acme
.domains
.filter((value
, index
, self
) => {
1501 return self
.indexOf(value
) === index
;
1507 remove_domain_from_acme: function(acme
, domain
) {
1508 if (acme
.domains
!== undefined) {
1509 acme
.domains
= acme
.domains
.filter((value
, index
, self
) => {
1510 return self
.indexOf(value
) === index
&& value
!== domain
;
1516 handleStoreErrorOrMask: function(me
, store
, regex
, callback
) {
1518 me
.mon(store
, 'load', function (proxy
, response
, success
, operation
) {
1521 Proxmox
.Utils
.setErrorMask(me
, false);
1526 if (operation
.error
.statusText
) {
1527 if (operation
.error
.statusText
.match(regex
)) {
1528 callback(me
, operation
.error
);
1531 msg
= operation
.error
.statusText
+ ' (' + operation
.error
.status
+ ')';
1534 msg
= gettext('Connection error');
1536 Proxmox
.Utils
.setErrorMask(me
, msg
);
1540 showCephInstallOrMask: function(container
, msg
, nodename
, callback
){
1541 var regex
= new RegExp("not (installed|initialized)", "i");
1542 if (msg
.match(regex
)) {
1543 if (Proxmox
.UserName
=== 'root@pam') {
1544 container
.el
.mask();
1545 if (!container
.down('pveCephInstallWindow')){
1546 var isInstalled
= msg
.match(/not initialized/i) ? true : false;
1547 var win
= Ext
.create('PVE.ceph.Install', {
1550 win
.getViewModel().set('isInstalled', isInstalled
);
1556 container
.mask(Ext
.String
.format(gettext('{0} not installed.') +
1557 ' ' + gettext('Log in as root to install.'), 'Ceph'), ['pve-static-mask']);
1565 propertyStringSet: function(target
, source
, name
, value
) {
1567 if (value
=== undefined) {
1568 target
[name
] = source
;
1570 target
[name
] = value
;
1573 delete target
[name
];
1577 updateColumns: function(container
) {
1578 let mode
= Ext
.state
.Manager
.get('summarycolumns') || 'auto';
1580 if (mode
!== 'auto') {
1581 factor
= parseInt(mode
, 10);
1582 if (Number
.isNaN(factor
)) {
1586 factor
= container
.getSize().width
< 1400 ? 1 : 2;
1589 if (container
.oldFactor
=== factor
) {
1593 let items
= container
.query('>'); // direct childs
1594 factor
= Math
.min(factor
, items
.length
);
1595 container
.oldFactor
= factor
;
1597 items
.forEach((item
) => {
1598 item
.columnWidth
= 1 / factor
;
1601 // we have to update the layout twice, since the first layout change
1602 // can trigger the scrollbar which reduces the amount of space left
1603 container
.updateLayout();
1604 container
.updateLayout();
1607 forEachCorosyncLink: function(nodeinfo
, cb
) {
1608 let re
= /(?:ring|link)(\d+)_addr/;
1609 Ext
.iterate(nodeinfo
, (prop
, val
) => {
1610 let match
= re
.exec(prop
);
1612 cb(Number(match
[1]), val
);
1619 'AuthenticAMD': 'AMD',
1620 'GenuineIntel': 'Intel'
1628 "_default_": 5, // includes custom models
1631 verify_ip64_address_list: function(value
, with_suffix
) {
1632 for (let addr
of value
.split(/[ ,;]+/)) {
1638 let parts
= addr
.split('%');
1641 if (parts
.length
> 2) {
1645 if (parts
.length
> 1 && !addr
.startsWith('fe80:')) {
1650 if (!Proxmox
.Utils
.IP64_match
.test(addr
)) {
1660 constructor: function() {
1662 Ext
.apply(me
, me
.utilities
);
1664 Proxmox
.Utils
.override_task_descriptions({
1665 acmedeactivate
: ['ACME Account', gettext('Deactivate')],
1666 acmenewcert
: ['SRV', gettext('Order Certificate')],
1667 acmerefresh
: ['ACME Account', gettext('Refresh')],
1668 acmeregister
: ['ACME Account', gettext('Register')],
1669 acmerenew
: ['SRV', gettext('Renew Certificate')],
1670 acmerevoke
: ['SRV', gettext('Revoke Certificate')],
1671 acmeupdate
: ['ACME Account', gettext('Update')],
1672 'auth-realm-sync': [gettext('Realm'), gettext('Sync')],
1673 'auth-realm-sync-test': [gettext('Realm'), gettext('Sync Preview')],
1674 cephcreatemds
: ['Ceph Metadata Server', gettext('Create')],
1675 cephcreatemgr
: ['Ceph Manager', gettext('Create')],
1676 cephcreatemon
: ['Ceph Monitor', gettext('Create')],
1677 cephcreateosd
: ['Ceph OSD', gettext('Create')],
1678 cephcreatepool
: ['Ceph Pool', gettext('Create')],
1679 cephdestroymds
: ['Ceph Metadata Server', gettext('Destroy')],
1680 cephdestroymgr
: ['Ceph Manager', gettext('Destroy')],
1681 cephdestroymon
: ['Ceph Monitor', gettext('Destroy')],
1682 cephdestroyosd
: ['Ceph OSD', gettext('Destroy')],
1683 cephdestroypool
: ['Ceph Pool', gettext('Destroy')],
1684 cephfscreate
: ['CephFS', gettext('Create')],
1685 clustercreate
: ['', gettext('Create Cluster')],
1686 clusterjoin
: ['', gettext('Join Cluster')],
1687 dircreate
: [gettext('Directory Storage'), gettext('Create')],
1688 dirremove
: [gettext('Directory'), gettext('Remove')],
1689 download
: ['', gettext('Download')],
1690 hamigrate
: ['HA', gettext('Migrate')],
1691 hashutdown
: ['HA', gettext('Shutdown')],
1692 hastart
: ['HA', gettext('Start')],
1693 hastop
: ['HA', gettext('Stop')],
1694 imgcopy
: ['', gettext('Copy data')],
1695 imgdel
: ['', gettext('Erase data')],
1696 lvmcreate
: [gettext('LVM Storage'), gettext('Create')],
1697 lvmthincreate
: [gettext('LVM-Thin Storage'), gettext('Create')],
1698 migrateall
: ['', gettext('Migrate all VMs and Containers')],
1699 'move_volume': ['CT', gettext('Move Volume')],
1700 pull_file
: ['CT', gettext('Pull file')],
1701 push_file
: ['CT', gettext('Push file')],
1702 qmclone
: ['VM', gettext('Clone')],
1703 qmconfig
: ['VM', gettext('Configure')],
1704 qmcreate
: ['VM', gettext('Create')],
1705 qmdelsnapshot
: ['VM', gettext('Delete Snapshot')],
1706 qmdestroy
: ['VM', gettext('Destroy')],
1707 qmigrate
: ['VM', gettext('Migrate')],
1708 qmmove
: ['VM', gettext('Move disk')],
1709 qmpause
: ['VM', gettext('Pause')],
1710 qmreboot
: ['VM', gettext('Reboot')],
1711 qmreset
: ['VM', gettext('Reset')],
1712 qmrestore
: ['VM', gettext('Restore')],
1713 qmresume
: ['VM', gettext('Resume')],
1714 qmrollback
: ['VM', gettext('Rollback')],
1715 qmshutdown
: ['VM', gettext('Shutdown')],
1716 qmsnapshot
: ['VM', gettext('Snapshot')],
1717 qmstart
: ['VM', gettext('Start')],
1718 qmstop
: ['VM', gettext('Stop')],
1719 qmsuspend
: ['VM', gettext('Hibernate')],
1720 qmtemplate
: ['VM', gettext('Convert to template')],
1721 spiceproxy
: ['VM/CT', gettext('Console') + ' (Spice)'],
1722 spiceshell
: ['', gettext('Shell') + ' (Spice)'],
1723 startall
: ['', gettext('Start all VMs and Containers')],
1724 stopall
: ['', gettext('Stop all VMs and Containers')],
1725 unknownimgdel
: ['', gettext('Destroy image from unknown guest')],
1726 vncproxy
: ['VM/CT', gettext('Console')],
1727 vncshell
: ['', gettext('Shell')],
1728 vzclone
: ['CT', gettext('Clone')],
1729 vzcreate
: ['CT', gettext('Create')],
1730 vzdelsnapshot
: ['CT', gettext('Delete Snapshot')],
1731 vzdestroy
: ['CT', gettext('Destroy')],
1732 vzdump
: (type
, id
) => id
? `VM/CT ${id} - ${gettext('Backup')}` : gettext('Backup Job'),
1733 vzmigrate
: ['CT', gettext('Migrate')],
1734 vzmount
: ['CT', gettext('Mount')],
1735 vzreboot
: ['CT', gettext('Reboot')],
1736 vzrestore
: ['CT', gettext('Restore')],
1737 vzresume
: ['CT', gettext('Resume')],
1738 vzrollback
: ['CT', gettext('Rollback')],
1739 vzshutdown
: ['CT', gettext('Shutdown')],
1740 vzsnapshot
: ['CT', gettext('Snapshot')],
1741 vzstart
: ['CT', gettext('Start')],
1742 vzstop
: ['CT', gettext('Stop')],
1743 vzsuspend
: ['CT', gettext('Suspend')],
1744 vztemplate
: ['CT', gettext('Convert to template')],
1745 vzumount
: ['CT', gettext('Unmount')],
1746 zfscreate
: [gettext('ZFS Storage'), gettext('Create')],