3 console
.log("Starting Proxmox VE Manager");
5 Ext
.Ajax
.defaultHeaders
= {
6 'Accept': 'application/json',
9 Ext
.define('PVE.Utils', {
12 // this singleton contains miscellaneous utilities
14 toolkit
: undefined, // (extjs|touch), set inside Toolkit.js
16 bus_match
: /^(ide|sata|virtio|scsi)(\d+)$/,
30 'c': gettext('Community'),
31 'b': gettext('Basic'),
32 's': gettext('Standard'),
33 'p': gettext('Premium'),
36 noSubKeyHtml
: 'You do not have a valid subscription for this server. Please visit '
37 +'<a target="_blank" href="https://www.proxmox.com/products/proxmox-ve/subscription-service-plans">'
38 +'www.proxmox.com</a> to get a list of available options.',
42 { desc
: '5.x - 2.6 Kernel', val
: 'l26' },
43 { desc
: '2.4 Kernel', val
: 'l24' },
45 'Microsoft Windows': [
46 { desc
: '11/2022', val
: 'win11' },
47 { desc
: '10/2016/2019', val
: 'win10' },
48 { desc
: '8.x/2012/2012r2', val
: 'win8' },
49 { desc
: '7/2008r2', val
: 'win7' },
50 { desc
: 'Vista/2008', val
: 'w2k8' },
51 { desc
: 'XP/2003', val
: 'wxp' },
52 { desc
: '2000', val
: 'w2k' },
55 { desc
: '-', val
: 'solaris' },
58 { desc
: '-', val
: 'other' },
62 is_windows: function(ostype
) {
63 for (let entry
of PVE
.Utils
.kvm_ostypes
['Microsoft Windows']) {
64 if (entry
.val
=== ostype
) {
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
160 get_ceph_icon_html: function(health
, fw
) {
161 var state
= PVE
.Utils
.map_ceph_health
[health
];
162 var cls
= PVE
.Utils
.get_health_icon(state
);
166 return "<i class='fa " + cls
+ "'></i> ";
171 'HEALTH_UPGRADE': 'upgrade',
173 'HEALTH_WARN': 'warning',
174 'HEALTH_ERR': 'critical',
177 render_sdn_pending: function(rec
, value
, key
, index
) {
178 if (rec
.data
.state
=== undefined || rec
.data
.state
=== null) {
182 if (rec
.data
.state
=== 'deleted') {
183 if (value
=== undefined) {
186 return '<div style="text-decoration: line-through;">'+ value
+'</div>';
188 } else if (rec
.data
.pending
[key
] !== undefined && rec
.data
.pending
[key
] !== null) {
189 if (rec
.data
.pending
[key
] === 'deleted') {
192 return rec
.data
.pending
[key
];
198 render_sdn_pending_state: function(rec
, value
) {
199 if (value
=== undefined || value
=== null) {
203 let icon
= `<i class="fa fa-fw fa-refresh warning"></i>`;
205 if (value
=== 'deleted') {
206 return '<span>' + icon
+ value
+ '</span>';
209 let tip
= gettext('Pending Changes') + ': <br>';
211 for (const [key
, keyvalue
] of Object
.entries(rec
.data
.pending
)) {
212 if ((rec
.data
[key
] !== undefined && rec
.data
.pending
[key
] !== rec
.data
[key
]) ||
213 rec
.data
[key
] === undefined
215 tip
+= `${key}: ${keyvalue} <br>`;
218 return '<span data-qtip="' + tip
+ '">'+ icon
+ value
+ '</span>';
221 render_ceph_health: function(healthObj
) {
223 iconCls
: PVE
.Utils
.get_health_icon(),
227 if (!healthObj
|| !healthObj
.status
) {
231 var health
= PVE
.Utils
.map_ceph_health
[healthObj
.status
];
233 state
.iconCls
= PVE
.Utils
.get_health_icon(health
, true);
234 state
.text
= healthObj
.status
;
239 render_zfs_health: function(value
) {
240 if (typeof value
=== 'undefined') {
243 var iconCls
= 'question-circle';
247 iconCls
= 'check-circle good';
251 iconCls
= 'exclamation-circle warning';
256 iconCls
= 'times-circle critical';
261 return '<i class="fa fa-' + iconCls
+ '"></i> ' + value
;
264 render_pbs_fingerprint
: fp
=> fp
.substring(0, 23),
266 render_backup_encryption: function(v
, meta
, record
) {
268 return gettext('No');
272 if (v
.match(/^[a-fA-F0-9]{2}:/)) { // fingerprint
273 tip
= `Key fingerprint ${PVE.Utils.render_pbs_fingerprint(v)}`;
275 let icon
= `<i class="fa fa-fw fa-lock good"></i>`;
276 return `<span data-qtip="${tip}">${icon} ${gettext('Encrypted')}</span>`;
279 render_backup_verification: function(v
, meta
, record
) {
280 let i
= (cls
, txt
) => `<i class="fa fa-fw fa-${cls}"></i> ${txt}`;
281 if (v
=== undefined || v
=== null) {
282 return i('question-circle-o warning', gettext('None'));
285 let txt
= gettext('Failed');
286 let iconCls
= 'times critical';
287 if (v
.state
=== 'ok') {
289 iconCls
= 'check good';
290 let now
= Date
.now() / 1000;
291 let task
= Proxmox
.Utils
.parse_task_upid(v
.upid
);
292 let verify_time
= Proxmox
.Utils
.render_timestamp(task
.starttime
);
293 tip
= `Last verify task started on ${verify_time}`;
294 if (now
- v
.starttime
> 30 * 24 * 60 * 60) {
295 tip
= `Last verify task over 30 days ago: ${verify_time}`;
296 iconCls
= 'check warning';
299 return `<span data-qtip="${tip}"> ${i(iconCls, txt)} </span>`;
302 render_backup_status: function(value
, meta
, record
) {
303 if (typeof value
=== 'undefined') {
307 let iconCls
= 'check-circle good';
308 let text
= gettext('Yes');
310 if (!PVE
.Parser
.parseBoolean(value
.toString())) {
311 iconCls
= 'times-circle critical';
313 text
= gettext('No');
315 let reason
= record
.get('reason');
316 if (typeof reason
!== 'undefined') {
317 if (reason
in PVE
.Utils
.backup_reasons_table
) {
318 reason
= PVE
.Utils
.backup_reasons_table
[record
.get('reason')];
320 text
= `${text} - ${reason}`;
324 return `<i class="fa fa-${iconCls}"></i> ${text}`;
327 render_backup_days_of_week: function(val
) {
328 var dows
= ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
331 val
.split(',').forEach(function(day
) {
333 var dow
= (dows
.indexOf(day
)+6)%7;
335 if (selected
.length
=== 0 || selected
[selected
.length
-1] === 0) {
338 selected
[selected
.length
-1]++;
351 selected
.forEach(function(item
) {
354 days
.push(Ext
.Date
.dayNames
[cur
+1] + '-' + Ext
.Date
.dayNames
[(cur
+item
)%7]);
356 } else if (item
=== 2) {
357 days
.push(Ext
.Date
.dayNames
[cur
+1]);
358 days
.push(Ext
.Date
.dayNames
[(cur
+2)%7]);
360 } else if (item
=== 1) {
361 days
.push(Ext
.Date
.dayNames
[(cur
+1)%7]);
364 return days
.join(', ');
367 render_backup_selection: function(value
, metaData
, record
) {
368 let allExceptText
= gettext('All except {0}');
369 let allText
= '-- ' + gettext('All') + ' --';
370 if (record
.data
.all
) {
371 if (record
.data
.exclude
) {
372 return Ext
.String
.format(allExceptText
, record
.data
.exclude
);
376 if (record
.data
.vmid
) {
377 return record
.data
.vmid
;
380 if (record
.data
.pool
) {
381 return "Pool '"+ record
.data
.pool
+ "'";
387 backup_reasons_table
: {
388 'backup=yes': gettext('Enabled'),
389 'backup=no': gettext('Disabled'),
390 'enabled': gettext('Enabled'),
391 'disabled': gettext('Disabled'),
392 'not a volume': gettext('Not a volume'),
393 'efidisk but no OMVF BIOS': gettext('EFI Disk without OMVF BIOS'),
396 get_kvm_osinfo: function(value
) {
397 var info
= { base
: 'Other' }; // default
399 Ext
.each(Object
.keys(PVE
.Utils
.kvm_ostypes
), function(k
) {
400 Ext
.each(PVE
.Utils
.kvm_ostypes
[k
], function(e
) {
401 if (e
.val
=== value
) {
402 info
= { desc
: e
.desc
, base
: k
};
410 render_kvm_ostype: function(value
) {
411 var osinfo
= PVE
.Utils
.get_kvm_osinfo(value
);
412 if (osinfo
.desc
&& osinfo
.desc
!== '-') {
413 return osinfo
.base
+ ' ' + osinfo
.desc
;
419 render_hotplug_features: function(value
) {
422 if (!value
|| value
=== '0') {
423 return gettext('Disabled');
427 value
= 'disk,network,usb';
430 Ext
.each(value
.split(','), function(el
) {
432 fa
.push(gettext('Disk'));
433 } else if (el
=== 'network') {
434 fa
.push(gettext('Network'));
435 } else if (el
=== 'usb') {
437 } else if (el
=== 'memory') {
438 fa
.push(gettext('Memory'));
439 } else if (el
=== 'cpu') {
440 fa
.push(gettext('CPU'));
446 return fa
.join(', ');
449 render_localtime: function(value
) {
450 if (value
=== '__default__') {
451 return Proxmox
.Utils
.defaultText
+ ' (' + gettext('Enabled for Windows') + ')';
453 return Proxmox
.Utils
.format_boolean(value
);
456 render_qga_features: function(config
) {
458 return Proxmox
.Utils
.defaultText
+ ' (' + Proxmox
.Utils
.disabledText
+ ')';
460 let qga
= PVE
.Parser
.parsePropertyString(config
, 'enabled');
461 if (!PVE
.Parser
.parseBoolean(qga
.enabled
)) {
462 return Proxmox
.Utils
.disabledText
;
466 let agentstring
= Proxmox
.Utils
.enabledText
;
468 for (const [key
, value
] of Object
.entries(qga
)) {
469 let displayText
= Proxmox
.Utils
.disabledText
;
470 if (key
=== 'type') {
475 displayText
= map
[value
] || Proxmox
.Utils
.unknownText
;
476 } else if (PVE
.Parser
.parseBoolean(value
)) {
477 displayText
= Proxmox
.Utils
.enabledText
;
479 agentstring
+= `, ${key}: ${displayText}`;
485 render_qemu_machine: function(value
) {
486 return value
|| Proxmox
.Utils
.defaultText
+ ' (i440fx)';
489 render_qemu_bios: function(value
) {
491 return Proxmox
.Utils
.defaultText
+ ' (SeaBIOS)';
492 } else if (value
=== 'seabios') {
494 } else if (value
=== 'ovmf') {
495 return "OVMF (UEFI)";
501 render_dc_ha_opts: function(value
) {
503 return Proxmox
.Utils
.defaultText
;
505 return PVE
.Parser
.printPropertyString(value
);
508 render_as_property_string
: v
=> !v
? Proxmox
.Utils
.defaultText
: PVE
.Parser
.printPropertyString(v
),
510 render_scsihw: function(value
) {
511 if (!value
|| value
=== '__default__') {
512 return Proxmox
.Utils
.defaultText
+ ' (LSI 53C895A)';
513 } else if (value
=== 'lsi') {
514 return 'LSI 53C895A';
515 } else if (value
=== 'lsi53c810') {
517 } else if (value
=== 'megasas') {
518 return 'MegaRAID SAS 8708EM2';
519 } else if (value
=== 'virtio-scsi-pci') {
520 return 'VirtIO SCSI';
521 } else if (value
=== 'virtio-scsi-single') {
522 return 'VirtIO SCSI single';
523 } else if (value
=== 'pvscsi') {
524 return 'VMware PVSCSI';
530 render_spice_enhancements: function(values
) {
531 let props
= PVE
.Parser
.parsePropertyString(values
);
532 if (Ext
.Object
.isEmpty(props
)) {
533 return Proxmox
.Utils
.noneText
;
537 if (PVE
.Parser
.parseBoolean(props
.foldersharing
)) {
538 output
.push('Folder Sharing: ' + gettext('Enabled'));
540 if (props
.videostreaming
=== 'all' || props
.videostreaming
=== 'filter') {
541 output
.push('Video Streaming: ' + props
.videostreaming
);
543 return output
.join(', ');
546 // fixme: auto-generate this
547 // for now, please keep in sync with PVE::Tools::kvmkeymaps
549 '__default__': Proxmox
.Utils
.defaultText
,
553 'de-ch': 'German (Swiss)',
554 'en-gb': 'English (UK)',
555 'en-us': 'English (USA)',
559 //fo: 'Faroe Islands',
561 'fr-be': 'French (Belgium)',
562 'fr-ca': 'French (Canada)',
563 'fr-ch': 'French (Swiss)',
573 //'nl-be': 'Dutch (Belgium)',
577 'pt-br': 'Portuguese (Brazil)',
586 '__default__': Proxmox
.Utils
.defaultText
,
587 std
: gettext('Standard VGA'),
588 vmware
: gettext('VMware compatible'),
590 qxl2
: 'SPICE dual monitor',
591 qxl3
: 'SPICE three monitors',
592 qxl4
: 'SPICE four monitors',
593 serial0
: gettext('Serial terminal') + ' 0',
594 serial1
: gettext('Serial terminal') + ' 1',
595 serial2
: gettext('Serial terminal') + ' 2',
596 serial3
: gettext('Serial terminal') + ' 3',
597 virtio
: 'VirtIO-GPU',
598 'virtio-gl': 'VirGL GPU',
599 none
: Proxmox
.Utils
.noneText
,
602 render_kvm_language: function(value
) {
603 if (!value
|| value
=== '__default__') {
604 return Proxmox
.Utils
.defaultText
;
606 let text
= PVE
.Utils
.kvm_keymaps
[value
];
607 return text
? `${text} (${value})` : value
;
611 '__default__': Proxmox
.Utils
.defaultText
+ ' (xterm.js)',
612 'vv': 'SPICE (remote-viewer)',
613 'html5': 'HTML5 (noVNC)',
614 'xtermjs': 'xterm.js',
617 render_console_viewer: function(value
) {
618 value
= value
|| '__default__';
619 return PVE
.Utils
.console_map
[value
] || value
;
622 render_kvm_vga_driver: function(value
) {
624 return Proxmox
.Utils
.defaultText
;
626 let vga
= PVE
.Parser
.parsePropertyString(value
, 'type');
627 let text
= PVE
.Utils
.kvm_vga_drivers
[vga
.type
];
629 text
= Proxmox
.Utils
.defaultText
;
631 return text
? `${text} (${value})` : value
;
634 render_kvm_startup: function(value
) {
635 var startup
= PVE
.Parser
.parseStartup(value
);
638 if (startup
.order
=== undefined) {
641 res
+= startup
.order
;
643 if (startup
.up
!== undefined) {
644 res
+= ',up=' + startup
.up
;
646 if (startup
.down
!== undefined) {
647 res
+= ',down=' + startup
.down
;
653 extractFormActionError: function(action
) {
655 switch (action
.failureType
) {
656 case Ext
.form
.action
.Action
.CLIENT_INVALID
:
657 msg
= gettext('Form fields may not be submitted with invalid values');
659 case Ext
.form
.action
.Action
.CONNECT_FAILURE
:
660 msg
= gettext('Connection error');
661 var resp
= action
.response
;
662 if (resp
.status
&& resp
.statusText
) {
663 msg
+= " " + resp
.status
+ ": " + resp
.statusText
;
666 case Ext
.form
.action
.Action
.LOAD_FAILURE
:
667 case Ext
.form
.action
.Action
.SERVER_INVALID
:
668 msg
= Proxmox
.Utils
.extractRequestError(action
.result
, true);
675 'images': gettext('Disk image'),
676 'backup': gettext('VZDump backup file'),
677 'vztmpl': gettext('Container template'),
678 'iso': gettext('ISO image'),
679 'rootdir': gettext('Container'),
680 'snippets': gettext('Snippets'),
683 volume_is_qemu_backup: function(volid
, format
) {
684 return format
=== 'pbs-vm' || volid
.match(':backup/vzdump-qemu-');
687 volume_is_lxc_backup: function(volid
, format
) {
688 return format
=== 'pbs-ct' || volid
.match(':backup/vzdump-(lxc|openvz)-');
693 name
: gettext('Active Directory Server'),
694 ipanel
: 'pveAuthADPanel',
695 syncipanel
: 'pveAuthLDAPSyncPanel',
701 name
: gettext('LDAP Server'),
702 ipanel
: 'pveAuthLDAPPanel',
703 syncipanel
: 'pveAuthLDAPSyncPanel',
709 name
: gettext('OpenID Connect Server'),
710 ipanel
: 'pveAuthOpenIDPanel',
714 iconCls
: 'pmx-itype-icon-openid-logo',
718 ipanel
: 'pveAuthBasePanel',
724 name
: 'Proxmox VE authentication server',
725 ipanel
: 'pveAuthBasePanel',
734 name
: Proxmox
.Utils
.directoryText
,
735 ipanel
: 'DirInputPanel',
741 ipanel
: 'LVMInputPanel',
747 ipanel
: 'LvmThinInputPanel',
753 ipanel
: 'BTRFSInputPanel',
759 ipanel
: 'NFSInputPanel',
765 ipanel
: 'CIFSInputPanel',
771 ipanel
: 'GlusterFsInputPanel',
777 ipanel
: 'IScsiInputPanel',
783 ipanel
: 'CephFSInputPanel',
788 name
: 'CephFS (PVE)',
789 ipanel
: 'CephFSInputPanel',
796 ipanel
: 'RBDInputPanel',
802 ipanel
: 'RBDInputPanel',
808 name
: 'ZFS over iSCSI',
809 ipanel
: 'ZFSInputPanel',
815 ipanel
: 'ZFSPoolInputPanel',
820 name
: 'Proxmox Backup Server',
821 ipanel
: 'PBSInputPanel',
846 ipanel
: 'SimpleInputPanel',
851 ipanel
: 'VlanInputPanel',
856 ipanel
: 'QinQInputPanel',
861 ipanel
: 'VxlanInputPanel',
866 ipanel
: 'EvpnInputPanel',
871 sdncontrollerSchema
: {
878 ipanel
: 'EvpnInputPanel',
879 faIcon
: 'crosshairs',
883 ipanel
: 'BgpInputPanel',
884 faIcon
: 'crosshairs',
895 ipanel
: 'PVEIpamInputPanel',
901 ipanel
: 'NetboxInputPanel',
906 ipanel
: 'PhpIpamInputPanel',
918 ipanel
: 'PowerdnsInputPanel',
923 format_sdnvnet_type: function(value
, md
, record
) {
924 var schema
= PVE
.Utils
.sdnvnetSchema
[value
];
928 return Proxmox
.Utils
.unknownText
;
931 format_sdnzone_type: function(value
, md
, record
) {
932 var schema
= PVE
.Utils
.sdnzoneSchema
[value
];
936 return Proxmox
.Utils
.unknownText
;
939 format_sdncontroller_type: function(value
, md
, record
) {
940 var schema
= PVE
.Utils
.sdncontrollerSchema
[value
];
944 return Proxmox
.Utils
.unknownText
;
947 format_sdnipam_type: function(value
, md
, record
) {
948 var schema
= PVE
.Utils
.sdnipamSchema
[value
];
952 return Proxmox
.Utils
.unknownText
;
955 format_sdndns_type: function(value
, md
, record
) {
956 var schema
= PVE
.Utils
.sdndnsSchema
[value
];
960 return Proxmox
.Utils
.unknownText
;
963 format_storage_type: function(value
, md
, record
) {
964 if (value
=== 'rbd') {
965 value
= !record
|| record
.get('monhost') ? 'rbd' : 'pveceph';
966 } else if (value
=== 'cephfs') {
967 value
= !record
|| record
.get('monhost') ? 'cephfs' : 'pvecephfs';
970 let schema
= PVE
.Utils
.storageSchema
[value
];
971 return schema
?.name
?? value
;
974 format_ha: function(value
) {
975 var text
= Proxmox
.Utils
.noneText
;
978 text
= value
.state
|| Proxmox
.Utils
.noneText
;
980 text
+= ', ' + Proxmox
.Utils
.groupText
+ ': ';
981 text
+= value
.group
|| Proxmox
.Utils
.noneText
;
987 format_content_types: function(value
) {
988 return value
.split(',').sort().map(function(ct
) {
989 return PVE
.Utils
.contentTypes
[ct
] || ct
;
993 render_storage_content: function(value
, metaData
, record
) {
994 var data
= record
.data
;
995 if (Ext
.isNumber(data
.channel
) &&
996 Ext
.isNumber(data
.id
) &&
997 Ext
.isNumber(data
.lun
)) {
999 Ext
.String
.leftPad(data
.channel
, 2, '0') +
1000 " ID " + data
.id
+ " LUN " + data
.lun
;
1002 return data
.volid
.replace(/^.*?:(.*?\/)?/, '');
1005 render_serverity: function(value
) {
1006 return PVE
.Utils
.log_severity_hash
[value
] || value
;
1009 calculate_hostcpu: function(data
) {
1010 if (!(data
.uptime
&& Ext
.isNumeric(data
.cpu
))) {
1014 if (data
.type
!== 'qemu' && data
.type
!== 'lxc') {
1018 var index
= PVE
.data
.ResourceStore
.findExact('id', 'node/' + data
.node
);
1019 var node
= PVE
.data
.ResourceStore
.getAt(index
);
1020 if (!Ext
.isDefined(node
) || node
=== null) {
1023 var maxcpu
= node
.data
.maxcpu
|| 1;
1025 if (!Ext
.isNumeric(maxcpu
) && (maxcpu
>= 1)) {
1029 return (data
.cpu
/maxcpu
) * data
.maxcpu
;
1032 render_hostcpu: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1033 if (!(record
.data
.uptime
&& Ext
.isNumeric(record
.data
.cpu
))) {
1037 if (record
.data
.type
!== 'qemu' && record
.data
.type
!== 'lxc') {
1041 var index
= PVE
.data
.ResourceStore
.findExact('id', 'node/' + record
.data
.node
);
1042 var node
= PVE
.data
.ResourceStore
.getAt(index
);
1043 if (!Ext
.isDefined(node
) || node
=== null) {
1046 var maxcpu
= node
.data
.maxcpu
|| 1;
1048 if (!Ext
.isNumeric(maxcpu
) && (maxcpu
>= 1)) {
1052 var per
= (record
.data
.cpu
/maxcpu
) * record
.data
.maxcpu
* 100;
1054 return per
.toFixed(1) + '% of ' + maxcpu
.toString() + (maxcpu
> 1 ? 'CPUs' : 'CPU');
1057 render_bandwidth: function(value
) {
1058 if (!Ext
.isNumeric(value
)) {
1062 return Proxmox
.Utils
.format_size(value
) + '/s';
1065 render_timestamp_human_readable: function(value
) {
1066 return Ext
.Date
.format(new Date(value
* 1000), 'l d F Y H:i:s');
1069 // render a timestamp or pending
1070 render_next_event: function(value
) {
1074 let now
= new Date(), next
= new Date(value
* 1000);
1076 return gettext('pending');
1078 return Proxmox
.Utils
.render_timestamp(value
);
1081 calculate_mem_usage: function(data
) {
1082 if (!Ext
.isNumeric(data
.mem
) ||
1083 data
.maxmem
=== 0 ||
1088 return data
.mem
/ data
.maxmem
;
1091 calculate_hostmem_usage: function(data
) {
1092 if (data
.type
!== 'qemu' && data
.type
!== 'lxc') {
1096 var index
= PVE
.data
.ResourceStore
.findExact('id', 'node/' + data
.node
);
1097 var node
= PVE
.data
.ResourceStore
.getAt(index
);
1099 if (!Ext
.isDefined(node
) || node
=== null) {
1102 var maxmem
= node
.data
.maxmem
|| 0;
1104 if (!Ext
.isNumeric(data
.mem
) ||
1110 return data
.mem
/ maxmem
;
1113 render_mem_usage_percent: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1114 if (!Ext
.isNumeric(value
) || value
=== -1) {
1118 // we got no percentage but bytes
1120 var maxmem
= record
.data
.maxmem
;
1121 if (!record
.data
.uptime
||
1123 !Ext
.isNumeric(mem
)) {
1127 return (mem
*100/maxmem
).toFixed(1) + " %";
1129 return (value
*100).toFixed(1) + " %";
1132 render_hostmem_usage_percent: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1133 if (!Ext
.isNumeric(record
.data
.mem
) || value
=== -1) {
1137 if (record
.data
.type
!== 'qemu' && record
.data
.type
!== 'lxc') {
1141 var index
= PVE
.data
.ResourceStore
.findExact('id', 'node/' + record
.data
.node
);
1142 var node
= PVE
.data
.ResourceStore
.getAt(index
);
1143 var maxmem
= node
.data
.maxmem
|| 0;
1145 if (record
.data
.mem
> 1) {
1146 // we got no percentage but bytes
1147 var mem
= record
.data
.mem
;
1148 if (!record
.data
.uptime
||
1150 !Ext
.isNumeric(mem
)) {
1154 return ((mem
*100)/maxmem
).toFixed(1) + " %";
1156 return (value
*100).toFixed(1) + " %";
1159 render_mem_usage: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1161 var maxmem
= record
.data
.maxmem
;
1163 if (!record
.data
.uptime
) {
1167 if (!(Ext
.isNumeric(mem
) && maxmem
)) {
1171 return Proxmox
.Utils
.render_size(value
);
1174 calculate_disk_usage: function(data
) {
1175 if (!Ext
.isNumeric(data
.disk
) ||
1176 ((data
.type
=== 'qemu' || data
.type
=== 'lxc') && data
.uptime
=== 0) ||
1182 return data
.disk
/ data
.maxdisk
;
1185 render_disk_usage_percent: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1186 if (!Ext
.isNumeric(value
) || value
=== -1) {
1190 return (value
* 100).toFixed(1) + " %";
1193 render_disk_usage: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1195 var maxdisk
= record
.data
.maxdisk
;
1196 var type
= record
.data
.type
;
1198 if (!Ext
.isNumeric(disk
) ||
1200 ((type
=== 'qemu' || type
=== 'lxc') && record
.data
.uptime
=== 0)
1205 return Proxmox
.Utils
.render_size(value
);
1208 get_object_icon_class: function(type
, record
) {
1212 if (type
=== 'type') {
1214 objType
= record
.groupbyid
;
1215 } else if (record
.template
) {
1217 objType
= 'template';
1221 status
= record
.status
+ ' ha-' + record
.hastate
;
1225 status
+= ' locked lock-' + record
.lock
;
1228 var defaults
= PVE
.tree
.ResourceTree
.typeDefaults
[objType
];
1229 if (defaults
&& defaults
.iconCls
) {
1230 var retVal
= defaults
.iconCls
+ ' ' + status
;
1237 render_resource_type: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1238 var cls
= PVE
.Utils
.get_object_icon_class(value
, record
.data
);
1240 var fa
= '<i class="fa-fw x-grid-icon-custom ' + cls
+ '"></i> ';
1244 render_support_level: function(value
, metaData
, record
) {
1245 return PVE
.Utils
.support_level_hash
[value
] || '-';
1248 render_upid: function(value
, metaData
, record
) {
1249 var type
= record
.data
.type
;
1250 var id
= record
.data
.id
;
1252 return Proxmox
.Utils
.format_task_description(type
, id
);
1255 render_optional_url: function(value
) {
1256 if (value
&& value
.match(/^https?:\/\//)) {
1257 return '<a target="_blank" href="' + value
+ '">' + value
+ '</a>';
1262 render_san: function(value
) {
1264 if (Ext
.isArray(value
)) {
1265 value
.forEach(function(val
) {
1266 if (!Ext
.isNumber(val
)) {
1270 return names
.join('<br>');
1275 render_full_name: function(firstname
, metaData
, record
) {
1276 var first
= firstname
|| '';
1277 var last
= record
.data
.lastname
|| '';
1278 return Ext
.htmlEncode(first
+ " " + last
);
1281 windowHostname: function() {
1282 return window
.location
.hostname
.replace(Proxmox
.Utils
.IP6_bracket_match
,
1283 function(m
, addr
, offset
, original
) { return addr
; });
1286 openDefaultConsoleWindow: function(consoles
, consoleType
, vmid
, nodename
, vmname
, cmd
) {
1287 var dv
= PVE
.Utils
.defaultViewer(consoles
, consoleType
);
1288 PVE
.Utils
.openConsoleWindow(dv
, consoleType
, vmid
, nodename
, vmname
, cmd
);
1291 openConsoleWindow: function(viewer
, consoleType
, vmid
, nodename
, vmname
, cmd
) {
1292 if (vmid
=== undefined && (consoleType
=== 'kvm' || consoleType
=== 'lxc')) {
1293 throw "missing vmid";
1296 throw "no nodename specified";
1299 if (viewer
=== 'html5') {
1300 PVE
.Utils
.openVNCViewer(consoleType
, vmid
, nodename
, vmname
, cmd
);
1301 } else if (viewer
=== 'xtermjs') {
1302 Proxmox
.Utils
.openXtermJsViewer(consoleType
, vmid
, nodename
, vmname
, cmd
);
1303 } else if (viewer
=== 'vv') {
1304 let url
= '/nodes/' + nodename
+ '/spiceshell';
1306 proxy
: PVE
.Utils
.windowHostname(),
1308 if (consoleType
=== 'kvm') {
1309 url
= '/nodes/' + nodename
+ '/qemu/' + vmid
.toString() + '/spiceproxy';
1310 } else if (consoleType
=== 'lxc') {
1311 url
= '/nodes/' + nodename
+ '/lxc/' + vmid
.toString() + '/spiceproxy';
1312 } else if (consoleType
=== 'upgrade') {
1313 params
.cmd
= 'upgrade';
1314 } else if (consoleType
=== 'cmd') {
1316 } else if (consoleType
!== 'shell') {
1317 throw `unknown spice viewer type '${consoleType}'`;
1319 PVE
.Utils
.openSpiceViewer(url
, params
);
1321 throw `unknown viewer type '${viewer}'`;
1325 defaultViewer: function(consoles
, type
) {
1326 var allowSpice
, allowXtermjs
;
1328 if (consoles
=== true) {
1330 allowXtermjs
= true;
1331 } else if (typeof consoles
=== 'object') {
1332 allowSpice
= consoles
.spice
;
1333 allowXtermjs
= !!consoles
.xtermjs
;
1335 let dv
= PVE
.UIOptions
.console
|| (type
=== 'kvm' ? 'vv' : 'xtermjs');
1336 if (dv
=== 'vv' && !allowSpice
) {
1337 dv
= allowXtermjs
? 'xtermjs' : 'html5';
1338 } else if (dv
=== 'xtermjs' && !allowXtermjs
) {
1339 dv
= allowSpice
? 'vv' : 'html5';
1345 openVNCViewer: function(vmtype
, vmid
, nodename
, vmname
, cmd
) {
1346 let scaling
= 'off';
1347 if (Proxmox
.Utils
.toolkit
!== 'touch') {
1348 var sp
= Ext
.state
.Manager
.getProvider();
1349 scaling
= sp
.get('novnc-scaling', 'off');
1351 var url
= Ext
.Object
.toQueryString({
1352 console
: vmtype
, // kvm, lxc, upgrade or shell
1360 var nw
= window
.open("?" + url
, '_blank', "innerWidth=745,innerheight=427");
1366 openSpiceViewer: function(url
, params
) {
1367 var downloadWithName = function(uri
, name
) {
1368 var link
= Ext
.DomHelper
.append(document
.body
, {
1371 css
: 'display:none;visibility:hidden;height:0px;',
1374 // Note: we need to tell Android and Chrome the correct file name extension
1375 // but we do not set 'download' tag for other environments, because
1376 // It can have strange side effects (additional user prompt on firefox)
1377 if (navigator
.userAgent
.match(/Android|Chrome/i)) {
1378 link
.download
= name
;
1381 if (link
.fireEvent
) {
1382 link
.fireEvent('onclick');
1384 let evt
= document
.createEvent("MouseEvents");
1385 evt
.initMouseEvent('click', true, true, window
, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
1386 link
.dispatchEvent(evt
);
1390 Proxmox
.Utils
.API2Request({
1394 failure: function(response
, opts
) {
1395 Ext
.Msg
.alert('Error', response
.htmlStatus
);
1397 success: function(response
, opts
) {
1398 let cfg
= response
.result
.data
;
1399 let raw
= Object
.entries(cfg
).reduce((acc
, [k
, v
]) => acc
+ `${k}=${v}\n`, "[virt-viewer]\n");
1400 let spiceDownload
= 'data:application/x-virt-viewer;charset=UTF-8,' + encodeURIComponent(raw
);
1401 downloadWithName(spiceDownload
, "pve-spice.vv");
1406 openTreeConsole: function(tree
, record
, item
, index
, e
) {
1408 let nodename
= record
.data
.node
;
1409 let vmid
= record
.data
.vmid
;
1410 let vmname
= record
.data
.name
;
1411 if (record
.data
.type
=== 'qemu' && !record
.data
.template
) {
1412 Proxmox
.Utils
.API2Request({
1413 url
: `/nodes/${nodename}/qemu/${vmid}/status/current`,
1414 failure
: response
=> Ext
.Msg
.alert('Error', response
.htmlStatus
),
1415 success: function(response
, opts
) {
1416 let conf
= response
.result
.data
;
1418 spice
: !!conf
.spice
,
1419 xtermjs
: !!conf
.serial
,
1421 PVE
.Utils
.openDefaultConsoleWindow(consoles
, 'kvm', vmid
, nodename
, vmname
);
1424 } else if (record
.data
.type
=== 'lxc' && !record
.data
.template
) {
1425 PVE
.Utils
.openDefaultConsoleWindow(true, 'lxc', vmid
, nodename
, vmname
);
1429 // test automation helper
1430 call_menu_handler: function(menu
, text
) {
1431 let item
= menu
.query('menuitem').find(el
=> el
.text
=== text
);
1432 if (item
&& item
.handler
) {
1437 createCmdMenu: function(v
, record
, item
, index
, event
) {
1439 if (!(v
instanceof Ext
.tree
.View
)) {
1443 let type
= record
.data
.type
;
1445 if (record
.data
.template
) {
1446 if (type
=== 'qemu' || type
=== 'lxc') {
1447 menu
= Ext
.create('PVE.menu.TemplateMenu', {
1451 } else if (type
=== 'qemu' || type
=== 'lxc' || type
=== 'node') {
1452 menu
= Ext
.create('PVE.' + type
+ '.CmdMenu', {
1454 nodename
: record
.data
.node
,
1460 menu
.showAt(event
.getXY());
1464 // helper for deleting field which are set to there default values
1465 delete_if_default: function(values
, fieldname
, default_val
, create
) {
1466 if (values
[fieldname
] === '' || values
[fieldname
] === default_val
) {
1468 if (values
.delete) {
1469 if (Ext
.isArray(values
.delete)) {
1470 values
.delete.push(fieldname
);
1472 values
.delete += ',' + fieldname
;
1475 values
.delete = fieldname
;
1479 delete values
[fieldname
];
1483 loadSSHKeyFromFile: function(file
, callback
) {
1484 // ssh-keygen produces ~ 740 bytes for a 4096 bit RSA key, current max is 16 kbit, so assume:
1485 // 740 * 8 for max. 32kbit (5920 bytes), round upwards to 8192 bytes, leaves lots of comment space
1486 PVE
.Utils
.loadFile(file
, callback
, 8192);
1489 loadFile: function(file
, callback
, maxSize
) {
1490 maxSize
= maxSize
|| 32 * 1024;
1491 if (file
.size
> maxSize
) {
1492 Ext
.Msg
.alert(gettext('Error'), `${gettext("Invalid file size")}: ${file.size} > ${maxSize}`);
1495 let reader
= new FileReader();
1496 reader
.onload
= evt
=> callback(evt
.target
.result
);
1497 reader
.readAsText(file
);
1500 loadTextFromFile: function(file
, callback
, maxBytes
) {
1501 let maxSize
= maxBytes
|| 8192;
1502 if (file
.size
> maxSize
) {
1503 Ext
.Msg
.alert(gettext('Error'), gettext("Invalid file size: ") + file
.size
);
1506 let reader
= new FileReader();
1507 reader
.onload
= evt
=> callback(evt
.target
.result
);
1508 reader
.readAsText(file
);
1511 diskControllerMaxIDs
: {
1519 // types is either undefined (all busses), an array of busses, or a single bus
1520 forEachBus: function(types
, func
) {
1521 let busses
= Object
.keys(PVE
.Utils
.diskControllerMaxIDs
);
1523 if (Ext
.isArray(types
)) {
1525 } else if (Ext
.isDefined(types
)) {
1529 // check if we only have valid busses
1530 for (let i
= 0; i
< busses
.length
; i
++) {
1531 if (!PVE
.Utils
.diskControllerMaxIDs
[busses
[i
]]) {
1532 throw "invalid bus: '" + busses
[i
] + "'";
1536 for (let i
= 0; i
< busses
.length
; i
++) {
1537 let count
= PVE
.Utils
.diskControllerMaxIDs
[busses
[i
]];
1538 for (let j
= 0; j
< count
; j
++) {
1539 let cont
= func(busses
[i
], j
);
1540 if (!cont
&& cont
!== undefined) {
1552 forEachMP: function(func
, includeUnused
) {
1553 for (let i
= 0; i
< PVE
.Utils
.mp_counts
.mp
; i
++) {
1554 let cont
= func('mp', i
);
1555 if (!cont
&& cont
!== undefined) {
1560 if (!includeUnused
) {
1564 for (let i
= 0; i
< PVE
.Utils
.mp_counts
.unused
; i
++) {
1565 let cont
= func('unused', i
);
1566 if (!cont
&& cont
!== undefined) {
1584 // we can have usb6 and up only for specific machine/ostypes
1585 get_max_usb_count: function(ostype
, machine
) {
1587 return PVE
.Utils
.hardware_counts
.usb_old
;
1590 let match
= /-(\d+).(\d+)/.exec(machine
?? '');
1591 if (!match
|| PVE
.Utils
.qemu_min_version([match
[1], match
[2]], [7, 1])) {
1592 if (ostype
=== 'l26') {
1593 return PVE
.Utils
.hardware_counts
.usb
;
1595 let os_match
= /^win(\d+)$/.exec(ostype
);
1596 if (os_match
&& os_match
[1] > 7) {
1597 return PVE
.Utils
.hardware_counts
.usb
;
1601 return PVE
.Utils
.hardware_counts
.usb_old
;
1604 // parameters are expected to be arrays, e.g. [7,1], [4,0,1]
1605 // returns true if toCheck is equal or greater than minVersion
1606 qemu_min_version: function(toCheck
, minVersion
) {
1608 for (i
= 0; i
< toCheck
.length
&& i
< minVersion
.length
; i
++) {
1609 if (toCheck
[i
] < minVersion
[i
]) {
1614 if (minVersion
.length
> toCheck
.length
) {
1615 for (; i
< minVersion
.length
; i
++) {
1616 if (minVersion
[i
] !== 0) {
1625 cleanEmptyObjectKeys: function(obj
) {
1626 for (const propName
of Object
.keys(obj
)) {
1627 if (obj
[propName
] === null || obj
[propName
] === undefined) {
1628 delete obj
[propName
];
1633 acmedomain_count
: 5,
1635 add_domain_to_acme: function(acme
, domain
) {
1636 if (acme
.domains
=== undefined) {
1637 acme
.domains
= [domain
];
1639 acme
.domains
.push(domain
);
1640 acme
.domains
= acme
.domains
.filter((value
, index
, self
) => self
.indexOf(value
) === index
);
1645 remove_domain_from_acme: function(acme
, domain
) {
1646 if (acme
.domains
!== undefined) {
1649 .filter((value
, index
, self
) => self
.indexOf(value
) === index
&& value
!== domain
);
1654 handleStoreErrorOrMask: function(view
, store
, regex
, callback
) {
1655 view
.mon(store
, 'load', function(proxy
, response
, success
, operation
) {
1657 Proxmox
.Utils
.setErrorMask(view
, false);
1661 if (operation
.error
.statusText
) {
1662 if (operation
.error
.statusText
.match(regex
)) {
1663 callback(view
, operation
.error
);
1666 msg
= operation
.error
.statusText
+ ' (' + operation
.error
.status
+ ')';
1669 msg
= gettext('Connection error');
1671 Proxmox
.Utils
.setErrorMask(view
, msg
);
1675 showCephInstallOrMask: function(container
, msg
, nodename
, callback
) {
1676 if (msg
.match(/not (installed|initialized)/i)) {
1677 if (Proxmox
.UserName
=== 'root@pam') {
1678 container
.el
.mask();
1679 if (!container
.down('pveCephInstallWindow')) {
1680 var isInstalled
= !!msg
.match(/not initialized/i);
1681 var win
= Ext
.create('PVE.ceph.Install', {
1684 win
.getViewModel().set('isInstalled', isInstalled
);
1690 container
.mask(Ext
.String
.format(gettext('{0} not installed.') +
1691 ' ' + gettext('Log in as root to install.'), 'Ceph'), ['pve-static-mask']);
1699 monitor_ceph_installed: function(view
, rstore
, nodename
, maskOwnerCt
) {
1700 PVE
.Utils
.handleStoreErrorOrMask(
1703 /not (installed|initialized)/i,
1705 nodename
= nodename
|| 'localhost';
1706 let maskTarget
= maskOwnerCt
? view
.ownerCt
: view
;
1707 rstore
.stopUpdate();
1708 PVE
.Utils
.showCephInstallOrMask(maskTarget
, error
.statusText
, nodename
, win
=> {
1709 view
.mon(win
, 'cephInstallWindowClosed', () => rstore
.startUpdate());
1716 propertyStringSet: function(target
, source
, name
, value
) {
1718 if (value
=== undefined) {
1719 target
[name
] = source
;
1721 target
[name
] = value
;
1724 delete target
[name
];
1728 forEachCorosyncLink: function(nodeinfo
, cb
) {
1729 let re
= /(?:ring|link)(\d+)_addr/;
1730 Ext
.iterate(nodeinfo
, (prop
, val
) => {
1731 let match
= re
.exec(prop
);
1733 cb(Number(match
[1]), val
);
1740 'AuthenticAMD': 'AMD',
1741 'GenuineIntel': 'Intel',
1749 "_default_": 5, // includes custom models
1752 verify_ip64_address_list: function(value
, with_suffix
) {
1753 for (let addr
of value
.split(/[ ,;]+/)) {
1759 let parts
= addr
.split('%');
1762 if (parts
.length
> 2) {
1766 if (parts
.length
> 1 && !addr
.startsWith('fe80:')) {
1771 if (!Proxmox
.Utils
.IP64_match
.test(addr
)) {
1779 sortByPreviousUsage: function(vmconfig
, controllerList
) {
1780 if (!controllerList
) {
1781 controllerList
= ['ide', 'virtio', 'scsi', 'sata'];
1783 let usedControllers
= {};
1784 for (const type
of Object
.keys(PVE
.Utils
.diskControllerMaxIDs
)) {
1785 usedControllers
[type
] = 0;
1788 for (const property
of Object
.keys(vmconfig
)) {
1789 if (property
.match(PVE
.Utils
.bus_match
) && !vmconfig
[property
].match(/media=cdrom/)) {
1790 const foundController
= property
.match(PVE
.Utils
.bus_match
)[1];
1791 usedControllers
[foundController
]++;
1795 let sortPriority
= PVE
.qemu
.OSDefaults
.getDefaults(vmconfig
.ostype
).busPriority
;
1797 let sortedList
= Ext
.clone(controllerList
);
1798 sortedList
.sort(function(a
, b
) {
1799 if (usedControllers
[b
] === usedControllers
[a
]) {
1800 return sortPriority
[b
] - sortPriority
[a
];
1802 return usedControllers
[b
] - usedControllers
[a
];
1808 nextFreeDisk: function(controllers
, config
) {
1809 for (const controller
of controllers
) {
1810 for (let i
= 0; i
< PVE
.Utils
.diskControllerMaxIDs
[controller
]; i
++) {
1811 let confid
= controller
+ i
.toString();
1812 if (!Ext
.isDefined(config
[confid
])) {
1825 nextFreeMP: function(type
, config
) {
1826 for (let i
= 0; i
< PVE
.Utils
.mp_counts
[type
]; i
++) {
1827 let confid
= `${type}${i}`;
1828 if (!Ext
.isDefined(config
[confid
])) {
1840 escapeNotesTemplate: function(value
) {
1845 return value
.replace(/(\\|[\n])/g, match
=> replace
[match
]);
1848 unEscapeNotesTemplate: function(value
) {
1853 return value
.replace(/(\\\\|\\n)/g, match
=> replace
[match
]);
1856 notesTemplateVars
: ['cluster', 'guestname', 'node', 'vmid'],
1858 updateUIOptions: function() {
1859 Proxmox
.Utils
.API2Request({
1860 url
: '/cluster/options',
1862 success: function(response
) {
1866 for (const option
of ['allowed-tags', 'console', 'tag-style']) {
1867 PVE
.UIOptions
[option
] = response
?.result
?.data
?.[option
];
1870 PVE
.Utils
.updateTagList(PVE
.UIOptions
['allowed-tags']);
1871 PVE
.Utils
.updateTagSettings(PVE
.UIOptions
?.['tag-style']);
1878 updateTagList: function(tags
) {
1879 PVE
.Utils
.tagList
= [...new Set([...tags
])].sort();
1882 parseTagOverrides: function(overrides
) {
1884 (overrides
|| "").split(';').forEach(color
=> {
1888 let [tag
, color_hex
, font_hex
] = color
.split(':');
1889 let r
= parseInt(color_hex
.slice(0, 2), 16);
1890 let g
= parseInt(color_hex
.slice(2, 4), 16);
1891 let b
= parseInt(color_hex
.slice(4, 6), 16);
1892 colors
[tag
] = [r
, g
, b
];
1894 colors
[tag
].push(parseInt(font_hex
.slice(0, 2), 16));
1895 colors
[tag
].push(parseInt(font_hex
.slice(2, 4), 16));
1896 colors
[tag
].push(parseInt(font_hex
.slice(4, 6), 16));
1904 updateTagOverrides: function(colors
) {
1905 let sp
= Ext
.state
.Manager
.getProvider();
1906 let color_state
= sp
.get('colors', '');
1907 let browser_colors
= PVE
.Utils
.parseTagOverrides(color_state
);
1908 PVE
.Utils
.tagOverrides
= Ext
.apply({}, browser_colors
, colors
);
1911 updateTagSettings: function(style
) {
1912 let overrides
= style
?.['color-map'];
1913 PVE
.Utils
.updateTagOverrides(PVE
.Utils
.parseTagOverrides(overrides
?? ""));
1915 let shape
= style
?.shape
?? 'circle';
1916 if (shape
=== '__default__') {
1920 Ext
.ComponentQuery
.query('pveResourceTree')[0].setUserCls(`proxmox-tags-${shape}`);
1921 PVE
.data
.ResourceStore
.fireEvent('load');
1922 Ext
.GlobalEvents
.fireEvent('loadedUiOptions');
1926 '__default__': `${Proxmox.Utils.defaultText} (${gettext('Cirlce')})`,
1927 'full': gettext('Full'),
1928 'circle': gettext('Circle'),
1929 'dense': gettext('Dense'),
1930 'none': Proxmox
.Utils
.NoneText
,
1934 '__default__': `${Proxmox.Utils.defaultText} (${gettext('Alphabetical')})`,
1935 'config': gettext('Configuration'),
1936 'alphabetical': gettext('Alphabetical'),
1939 renderTags: function(tagstext
, overrides
) {
1942 let tags
= (tagstext
.split(/[,; ]/) || []).filter(t
=> !!t
);
1943 if (PVE
.Utils
.shouldSortTags()) {
1944 tags
= tags
.sort((a
,b
) => {
1945 let alc
= a
.toLowerCase();
1946 let blc
= b
.toLowerCase();
1947 return alc
< blc
? -1 : blc
< alc
? 1 : a
.localeCompare(b
);
1951 tags
.forEach((tag
) => {
1952 text
+= Proxmox
.Utils
.getTagElement(tag
, overrides
);
1958 shouldSortTags: function() {
1959 return !(PVE
.UIOptions
?.['tag-style']?.ordering
=== 'config');
1962 tagCharRegex
: /^[a-z0-9+_.-]+$/i,
1966 constructor: function() {
1968 Ext
.apply(me
, me
.utilities
);
1970 Proxmox
.Utils
.override_task_descriptions({
1971 acmedeactivate
: ['ACME Account', gettext('Deactivate')],
1972 acmenewcert
: ['SRV', gettext('Order Certificate')],
1973 acmerefresh
: ['ACME Account', gettext('Refresh')],
1974 acmeregister
: ['ACME Account', gettext('Register')],
1975 acmerenew
: ['SRV', gettext('Renew Certificate')],
1976 acmerevoke
: ['SRV', gettext('Revoke Certificate')],
1977 acmeupdate
: ['ACME Account', gettext('Update')],
1978 'auth-realm-sync': [gettext('Realm'), gettext('Sync')],
1979 'auth-realm-sync-test': [gettext('Realm'), gettext('Sync Preview')],
1980 cephcreatemds
: ['Ceph Metadata Server', gettext('Create')],
1981 cephcreatemgr
: ['Ceph Manager', gettext('Create')],
1982 cephcreatemon
: ['Ceph Monitor', gettext('Create')],
1983 cephcreateosd
: ['Ceph OSD', gettext('Create')],
1984 cephcreatepool
: ['Ceph Pool', gettext('Create')],
1985 cephdestroymds
: ['Ceph Metadata Server', gettext('Destroy')],
1986 cephdestroymgr
: ['Ceph Manager', gettext('Destroy')],
1987 cephdestroymon
: ['Ceph Monitor', gettext('Destroy')],
1988 cephdestroyosd
: ['Ceph OSD', gettext('Destroy')],
1989 cephdestroypool
: ['Ceph Pool', gettext('Destroy')],
1990 cephdestroyfs
: ['CephFS', gettext('Destroy')],
1991 cephfscreate
: ['CephFS', gettext('Create')],
1992 cephsetpool
: ['Ceph Pool', gettext('Edit')],
1993 cephsetflags
: ['', gettext('Change global Ceph flags')],
1994 clustercreate
: ['', gettext('Create Cluster')],
1995 clusterjoin
: ['', gettext('Join Cluster')],
1996 dircreate
: [gettext('Directory Storage'), gettext('Create')],
1997 dirremove
: [gettext('Directory'), gettext('Remove')],
1998 download
: [gettext('File'), gettext('Download')],
1999 hamigrate
: ['HA', gettext('Migrate')],
2000 hashutdown
: ['HA', gettext('Shutdown')],
2001 hastart
: ['HA', gettext('Start')],
2002 hastop
: ['HA', gettext('Stop')],
2003 imgcopy
: ['', gettext('Copy data')],
2004 imgdel
: ['', gettext('Erase data')],
2005 lvmcreate
: [gettext('LVM Storage'), gettext('Create')],
2006 lvmremove
: ['Volume Group', gettext('Remove')],
2007 lvmthincreate
: [gettext('LVM-Thin Storage'), gettext('Create')],
2008 lvmthinremove
: ['Thinpool', gettext('Remove')],
2009 migrateall
: ['', gettext('Migrate all VMs and Containers')],
2010 'move_volume': ['CT', gettext('Move Volume')],
2011 'pbs-download': ['VM/CT', gettext('File Restore Download')],
2012 pull_file
: ['CT', gettext('Pull file')],
2013 push_file
: ['CT', gettext('Push file')],
2014 qmclone
: ['VM', gettext('Clone')],
2015 qmconfig
: ['VM', gettext('Configure')],
2016 qmcreate
: ['VM', gettext('Create')],
2017 qmdelsnapshot
: ['VM', gettext('Delete Snapshot')],
2018 qmdestroy
: ['VM', gettext('Destroy')],
2019 qmigrate
: ['VM', gettext('Migrate')],
2020 qmmove
: ['VM', gettext('Move disk')],
2021 qmpause
: ['VM', gettext('Pause')],
2022 qmreboot
: ['VM', gettext('Reboot')],
2023 qmreset
: ['VM', gettext('Reset')],
2024 qmrestore
: ['VM', gettext('Restore')],
2025 qmresume
: ['VM', gettext('Resume')],
2026 qmrollback
: ['VM', gettext('Rollback')],
2027 qmshutdown
: ['VM', gettext('Shutdown')],
2028 qmsnapshot
: ['VM', gettext('Snapshot')],
2029 qmstart
: ['VM', gettext('Start')],
2030 qmstop
: ['VM', gettext('Stop')],
2031 qmsuspend
: ['VM', gettext('Hibernate')],
2032 qmtemplate
: ['VM', gettext('Convert to template')],
2033 spiceproxy
: ['VM/CT', gettext('Console') + ' (Spice)'],
2034 spiceshell
: ['', gettext('Shell') + ' (Spice)'],
2035 startall
: ['', gettext('Start all VMs and Containers')],
2036 stopall
: ['', gettext('Stop all VMs and Containers')],
2037 unknownimgdel
: ['', gettext('Destroy image from unknown guest')],
2038 wipedisk
: ['Device', gettext('Wipe Disk')],
2039 vncproxy
: ['VM/CT', gettext('Console')],
2040 vncshell
: ['', gettext('Shell')],
2041 vzclone
: ['CT', gettext('Clone')],
2042 vzcreate
: ['CT', gettext('Create')],
2043 vzdelsnapshot
: ['CT', gettext('Delete Snapshot')],
2044 vzdestroy
: ['CT', gettext('Destroy')],
2045 vzdump
: (type
, id
) => id
? `VM/CT ${id} - ${gettext('Backup')}` : gettext('Backup Job'),
2046 vzmigrate
: ['CT', gettext('Migrate')],
2047 vzmount
: ['CT', gettext('Mount')],
2048 vzreboot
: ['CT', gettext('Reboot')],
2049 vzrestore
: ['CT', gettext('Restore')],
2050 vzresume
: ['CT', gettext('Resume')],
2051 vzrollback
: ['CT', gettext('Rollback')],
2052 vzshutdown
: ['CT', gettext('Shutdown')],
2053 vzsnapshot
: ['CT', gettext('Snapshot')],
2054 vzstart
: ['CT', gettext('Start')],
2055 vzstop
: ['CT', gettext('Stop')],
2056 vzsuspend
: ['CT', gettext('Suspend')],
2057 vztemplate
: ['CT', gettext('Convert to template')],
2058 vzumount
: ['CT', gettext('Unmount')],
2059 zfscreate
: [gettext('ZFS Storage'), gettext('Create')],
2060 zfsremove
: ['ZFS Pool', gettext('Remove')],