]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/Utils.js
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', {
25 // this singleton contains miscellaneous utilities
27 toolkit
: undefined, // (extjs|touch), set inside Toolkit.js
29 bus_match
: /^(ide|sata|virtio|scsi)\d+$/,
43 'c': gettext('Community'),
44 'b': gettext('Basic'),
45 's': gettext('Standard'),
46 'p': gettext('Premium'),
49 noSubKeyHtml
: 'You do not have a valid subscription for this server. Please visit '
50 +'<a target="_blank" href="https://www.proxmox.com/products/proxmox-ve/subscription-service-plans">'
51 +'www.proxmox.com</a> to get a list of available options.',
55 { desc
: '5.x - 2.6 Kernel', val
: 'l26' },
56 { desc
: '2.4 Kernel', val
: 'l24' },
58 'Microsoft Windows': [
59 { desc
: '10/2016/2019', val
: 'win10' },
60 { desc
: '8.x/2012/2012r2', val
: 'win8' },
61 { desc
: '7/2008r2', val
: 'win7' },
62 { desc
: 'Vista/2008', val
: 'w2k8' },
63 { desc
: 'XP/2003', val
: 'wxp' },
64 { desc
: '2000', val
: 'w2k' },
67 { desc
: '-', val
: 'solaris' },
70 { desc
: '-', val
: 'other' },
74 is_windows: function(ostype
) {
75 for (let entry
of PVE
.Utils
.kvm_ostypes
['Microsoft Windows']) {
76 if (entry
.val
=== ostype
) {
83 get_health_icon: function(state
, circle
) {
84 if (circle
=== undefined) {
88 if (state
=== undefined) {
92 var icon
= 'faded fa-question';
95 icon
= 'good fa-check';
98 icon
= 'warning fa-upload';
101 icon
= 'warning fa-refresh';
104 icon
= 'warning fa-exclamation';
107 icon
= 'critical fa-times';
119 parse_ceph_version: function(service
) {
120 if (service
.ceph_version_short
) {
121 return service
.ceph_version_short
;
124 if (service
.ceph_version
) {
125 var match
= service
.ceph_version
.match(/version (\d+(\.\d+)*)/);
134 compare_ceph_versions: function(a
, b
) {
142 if (Ext
.isArray(a
)) {
143 avers
= a
.slice(); // copy array
145 avers
= a
.toString().split('.');
148 if (Ext
.isArray(b
)) {
149 bvers
= b
.slice(); // copy array
151 bvers
= b
.toString().split('.');
155 let av
= avers
.shift();
156 let bv
= bvers
.shift();
158 if (av
=== undefined && bv
=== undefined) {
160 } else if (av
=== undefined) {
162 } else if (bv
=== undefined) {
165 let diff
= parseInt(av
, 10) - parseInt(bv
, 10);
166 if (diff
!= 0) return diff
;
167 // else we need to look at the next parts
172 get_ceph_icon_html: function(health
, fw
) {
173 var state
= PVE
.Utils
.map_ceph_health
[health
];
174 var cls
= PVE
.Utils
.get_health_icon(state
);
178 return "<i class='fa " + cls
+ "'></i> ";
183 'HEALTH_UPGRADE': 'upgrade',
185 'HEALTH_WARN': 'warning',
186 'HEALTH_ERR': 'critical',
189 render_sdn_pending: function(rec
,value
,key
, index
) {
190 if (rec
.data
.state
=== undefined || rec
.data
.state
=== null) {
194 if (rec
.data
.state
=== 'deleted') {
195 if (value
=== undefined) {
198 return '<div style="text-decoration: line-through;">'+ value
+'</div>';
202 if (rec
.data
.pending
[key
] !== undefined && rec
.data
.pending
[key
] !== null) {
203 if (rec
.data
.pending
[key
] === 'deleted') {
206 return rec
.data
.pending
[key
];
215 render_sdn_pending_state: function(rec
,value
) {
217 if (value
=== undefined || value
=== null) {
221 let icon
= `<i class="fa fa-fw fa-refresh warning"></i>`;
223 if (value
=== 'deleted') {
224 return '<span>' + icon
+ value
+ '</span>';
227 let tip
= 'Pending apply: <br>';
229 for (const [key
, keyvalue
] of Object
.entries(rec
.data
.pending
)) {
230 if (((rec
.data
[key
] !== undefined && rec
.data
.pending
[key
] !== rec
.data
[key
]) || rec
.data
[key
] === undefined)) {
231 tip
= tip
+ `${key}: ${keyvalue} <br>`;
234 return '<span data-qtip="' + tip
+ '">'+ icon
+ value
+ '</span>';
237 render_ceph_health: function(healthObj
) {
239 iconCls
: PVE
.Utils
.get_health_icon(),
243 if (!healthObj
|| !healthObj
.status
) {
247 var health
= PVE
.Utils
.map_ceph_health
[healthObj
.status
];
249 state
.iconCls
= PVE
.Utils
.get_health_icon(health
, true);
250 state
.text
= healthObj
.status
;
255 render_zfs_health: function(value
) {
256 if (typeof value
== 'undefined') {
259 var iconCls
= 'question-circle';
263 iconCls
= 'check-circle good';
267 iconCls
= 'exclamation-circle warning';
272 iconCls
= 'times-circle critical';
277 return '<i class="fa fa-' + iconCls
+ '"></i> ' + value
;
280 render_pbs_fingerprint
: fp
=> fp
.substring(0, 23),
282 render_backup_encryption: function(v
, meta
, record
) {
284 return gettext('No');
288 if (v
.match(/^[a-fA-F0-9]{2}:/)) { // fingerprint
289 tip
= `Key fingerprint ${PVE.Utils.render_pbs_fingerprint(v)}`;
291 let icon
= `<i class="fa fa-fw fa-lock good"></i>`;
292 return `<span data-qtip="${tip}">${icon} ${gettext('Encrypted')}</span>`;
295 render_backup_verification: function(v
, meta
, record
) {
296 let i
= (cls
, txt
) => `<i class="fa fa-fw fa-${cls}"></i> ${txt}`;
297 if (v
=== undefined || v
=== null) {
298 return i('question-circle-o warning', gettext('None'));
301 let txt
= gettext('Failed');
302 let iconCls
= 'times critical';
303 if (v
.state
=== 'ok') {
305 iconCls
= 'check good';
306 let now
= Date
.now() / 1000;
307 let task
= Proxmox
.Utils
.parse_task_upid(v
.upid
);
308 let verify_time
= Proxmox
.Utils
.render_timestamp(task
.starttime
);
309 tip
= `Last verify task started on ${verify_time}`;
310 if (now
- v
.starttime
> 30 * 24 * 60 * 60) {
311 tip
= `Last verify task over 30 days ago: ${verify_time}`;
312 iconCls
= 'check warning';
315 return `<span data-qtip="${tip}"> ${i(iconCls, txt)} </span>`;
318 render_backup_status: function(value
, meta
, record
) {
319 if (typeof value
== 'undefined') {
323 let iconCls
= 'check-circle good';
324 let text
= gettext('Yes');
326 if (!PVE
.Parser
.parseBoolean(value
.toString())) {
327 iconCls
= 'times-circle critical';
329 text
= gettext('No');
331 let reason
= record
.get('reason');
332 if (typeof reason
!== 'undefined') {
333 if (reason
in PVE
.Utils
.backup_reasons_table
) {
334 reason
= PVE
.Utils
.backup_reasons_table
[record
.get('reason')];
336 text
= `${text} - ${reason}`;
340 return `<i class="fa fa-${iconCls}"></i> ${text}`;
343 render_backup_days_of_week: function(val
) {
344 var dows
= ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
347 val
.split(',').forEach(function(day
) {
349 var dow
= (dows
.indexOf(day
)+6)%7;
351 if (selected
.length
=== 0 || selected
[selected
.length
-1] === 0) {
354 selected
[selected
.length
-1]++;
367 selected
.forEach(function(item
) {
370 days
.push(Ext
.Date
.dayNames
[cur
+1] + '-' + Ext
.Date
.dayNames
[(cur
+item
)%7]);
372 } else if (item
== 2) {
373 days
.push(Ext
.Date
.dayNames
[cur
+1]);
374 days
.push(Ext
.Date
.dayNames
[(cur
+2)%7]);
376 } else if (item
== 1) {
377 days
.push(Ext
.Date
.dayNames
[(cur
+1)%7]);
380 return days
.join(', ');
383 render_backup_selection: function(value
, metaData
, record
) {
384 let allExceptText
= gettext('All except {0}');
385 let allText
= '-- ' + gettext('All') + ' --';
386 if (record
.data
.all
) {
387 if (record
.data
.exclude
) {
388 return Ext
.String
.format(allExceptText
, record
.data
.exclude
);
392 if (record
.data
.vmid
) {
393 return record
.data
.vmid
;
396 if (record
.data
.pool
) {
397 return "Pool '"+ record
.data
.pool
+ "'";
403 backup_reasons_table
: {
404 'backup=yes': gettext('Enabled'),
405 'backup=no': gettext('Disabled'),
406 'enabled': gettext('Enabled'),
407 'disabled': gettext('Disabled'),
408 'not a volume': gettext('Not a volume'),
409 'efidisk but no OMVF BIOS': gettext('EFI Disk without OMVF BIOS'),
412 get_kvm_osinfo: function(value
) {
413 var info
= { base
: 'Other' }; // default
415 Ext
.each(Object
.keys(PVE
.Utils
.kvm_ostypes
), function(k
) {
416 Ext
.each(PVE
.Utils
.kvm_ostypes
[k
], function(e
) {
417 if (e
.val
=== value
) {
418 info
= { desc
: e
.desc
, base
: k
};
426 render_kvm_ostype: function(value
) {
427 var osinfo
= PVE
.Utils
.get_kvm_osinfo(value
);
428 if (osinfo
.desc
&& osinfo
.desc
!== '-') {
429 return osinfo
.base
+ ' ' + osinfo
.desc
;
435 render_hotplug_features: function(value
) {
438 if (!value
|| value
=== '0') {
439 return gettext('Disabled');
443 value
= 'disk,network,usb';
446 Ext
.each(value
.split(','), function(el
) {
448 fa
.push(gettext('Disk'));
449 } else if (el
=== 'network') {
450 fa
.push(gettext('Network'));
451 } else if (el
=== 'usb') {
453 } else if (el
=== 'memory') {
454 fa
.push(gettext('Memory'));
455 } else if (el
=== 'cpu') {
456 fa
.push(gettext('CPU'));
462 return fa
.join(', ');
465 render_localtime: function(value
) {
466 if (value
=== '__default__') {
467 return Proxmox
.Utils
.defaultText
+ ' (' + gettext('Enabled for Windows') + ')';
469 return Proxmox
.Utils
.format_boolean(value
);
472 render_qga_features: function(value
) {
474 return Proxmox
.Utils
.defaultText
+ ' (' + Proxmox
.Utils
.disabledText
+ ')';
476 var props
= PVE
.Parser
.parsePropertyString(value
, 'enabled');
477 if (!PVE
.Parser
.parseBoolean(props
.enabled
)) {
478 return Proxmox
.Utils
.disabledText
;
481 delete props
.enabled
;
482 var agentstring
= Proxmox
.Utils
.enabledText
;
484 Ext
.Object
.each(props
, function(key
, value
) {
486 agentstring
+= ', ' + key
+ ': ';
488 if (key
=== 'type') {
493 agentstring
+= map
[value
] || Proxmox
.Utils
.unknownText
;
495 if (PVE
.Parser
.parseBoolean(value
)) {
496 agentstring
+= Proxmox
.Utils
.enabledText
;
498 agentstring
+= Proxmox
.Utils
.disabledText
;
506 render_qemu_machine: function(value
) {
507 return value
|| Proxmox
.Utils
.defaultText
+ ' (i440fx)';
510 render_qemu_bios: function(value
) {
512 return Proxmox
.Utils
.defaultText
+ ' (SeaBIOS)';
513 } else if (value
=== 'seabios') {
515 } else if (value
=== 'ovmf') {
516 return "OVMF (UEFI)";
522 render_dc_ha_opts: function(value
) {
524 return Proxmox
.Utils
.defaultText
;
526 return PVE
.Parser
.printPropertyString(value
);
529 render_as_property_string: function(value
) {
530 return !value
? Proxmox
.Utils
.defaultText
531 : PVE
.Parser
.printPropertyString(value
);
534 render_scsihw: function(value
) {
536 return Proxmox
.Utils
.defaultText
+ ' (LSI 53C895A)';
537 } else if (value
=== 'lsi') {
538 return 'LSI 53C895A';
539 } else if (value
=== 'lsi53c810') {
541 } else if (value
=== 'megasas') {
542 return 'MegaRAID SAS 8708EM2';
543 } else if (value
=== 'virtio-scsi-pci') {
544 return 'VirtIO SCSI';
545 } else if (value
=== 'virtio-scsi-single') {
546 return 'VirtIO SCSI single';
547 } else if (value
=== 'pvscsi') {
548 return 'VMware PVSCSI';
554 render_spice_enhancements: function(values
) {
555 let props
= PVE
.Parser
.parsePropertyString(values
);
556 if (Ext
.Object
.isEmpty(props
)) {
557 return Proxmox
.Utils
.noneText
;
561 if (PVE
.Parser
.parseBoolean(props
.foldersharing
)) {
562 output
.push('Folder Sharing: ' + gettext('Enabled'));
564 if (props
.videostreaming
=== 'all' || props
.videostreaming
=== 'filter') {
565 output
.push('Video Streaming: ' + props
.videostreaming
);
567 return output
.join(', ');
570 // fixme: auto-generate this
571 // for now, please keep in sync with PVE::Tools::kvmkeymaps
576 'de-ch': 'German (Swiss)',
577 'en-gb': 'English (UK)',
578 'en-us': 'English (USA)',
582 //fo: 'Faroe Islands',
584 'fr-be': 'French (Belgium)',
585 'fr-ca': 'French (Canada)',
586 'fr-ch': 'French (Swiss)',
596 //'nl-be': 'Dutch (Belgium)',
600 'pt-br': 'Portuguese (Brazil)',
609 std
: gettext('Standard VGA'),
610 vmware
: gettext('VMware compatible'),
612 qxl2
: 'SPICE dual monitor',
613 qxl3
: 'SPICE three monitors',
614 qxl4
: 'SPICE four monitors',
615 serial0
: gettext('Serial terminal') + ' 0',
616 serial1
: gettext('Serial terminal') + ' 1',
617 serial2
: gettext('Serial terminal') + ' 2',
618 serial3
: gettext('Serial terminal') + ' 3',
619 virtio
: 'VirtIO-GPU',
620 none
: Proxmox
.Utils
.noneText
,
623 render_kvm_language: function(value
) {
624 if (!value
|| value
=== '__default__') {
625 return Proxmox
.Utils
.defaultText
;
627 var text
= PVE
.Utils
.kvm_keymaps
[value
];
629 return text
+ ' (' + value
+ ')';
634 kvm_keymap_array: function() {
635 var data
= [['__default__', PVE
.Utils
.render_kvm_language('')]];
636 Ext
.Object
.each(PVE
.Utils
.kvm_keymaps
, function(key
, value
) {
637 data
.push([key
, PVE
.Utils
.render_kvm_language(value
)]);
644 '__default__': Proxmox
.Utils
.defaultText
+ ' (xterm.js)',
645 'vv': 'SPICE (remote-viewer)',
646 'html5': 'HTML5 (noVNC)',
647 'xtermjs': 'xterm.js',
650 render_console_viewer: function(value
) {
651 value
= value
|| '__default__';
652 if (PVE
.Utils
.console_map
[value
]) {
653 return PVE
.Utils
.console_map
[value
];
658 console_viewer_array: function() {
659 return Ext
.Array
.map(Object
.keys(PVE
.Utils
.console_map
), function(v
) {
660 return [v
, PVE
.Utils
.render_console_viewer(v
)];
664 render_kvm_vga_driver: function(value
) {
666 return Proxmox
.Utils
.defaultText
;
668 var vga
= PVE
.Parser
.parsePropertyString(value
, 'type');
669 var text
= PVE
.Utils
.kvm_vga_drivers
[vga
.type
];
671 text
= Proxmox
.Utils
.defaultText
;
674 return text
+ ' (' + value
+ ')';
679 kvm_vga_driver_array: function() {
680 var data
= [['__default__', PVE
.Utils
.render_kvm_vga_driver('')]];
681 Ext
.Object
.each(PVE
.Utils
.kvm_vga_drivers
, function(key
, value
) {
682 data
.push([key
, PVE
.Utils
.render_kvm_vga_driver(value
)]);
688 render_kvm_startup: function(value
) {
689 var startup
= PVE
.Parser
.parseStartup(value
);
692 if (startup
.order
=== undefined) {
695 res
+= startup
.order
;
697 if (startup
.up
!== undefined) {
698 res
+= ',up=' + startup
.up
;
700 if (startup
.down
!== undefined) {
701 res
+= ',down=' + startup
.down
;
707 extractFormActionError: function(action
) {
709 switch (action
.failureType
) {
710 case Ext
.form
.action
.Action
.CLIENT_INVALID
:
711 msg
= gettext('Form fields may not be submitted with invalid values');
713 case Ext
.form
.action
.Action
.CONNECT_FAILURE
:
714 msg
= gettext('Connection error');
715 var resp
= action
.response
;
716 if (resp
.status
&& resp
.statusText
) {
717 msg
+= " " + resp
.status
+ ": " + resp
.statusText
;
720 case Ext
.form
.action
.Action
.LOAD_FAILURE
:
721 case Ext
.form
.action
.Action
.SERVER_INVALID
:
722 msg
= Proxmox
.Utils
.extractRequestError(action
.result
, true);
729 'images': gettext('Disk image'),
730 'backup': gettext('VZDump backup file'),
731 'vztmpl': gettext('Container template'),
732 'iso': gettext('ISO image'),
733 'rootdir': gettext('Container'),
734 'snippets': gettext('Snippets'),
737 volume_is_qemu_backup: function(volid
, format
) {
738 return format
=== 'pbs-vm' || volid
.match(':backup/vzdump-qemu-');
741 volume_is_lxc_backup: function(volid
, format
) {
742 return format
=== 'pbs-ct' || volid
.match(':backup/vzdump-(lxc|openvz)-');
747 name
: gettext('Active Directory Server'),
748 ipanel
: 'pveAuthADPanel',
749 syncipanel
: 'pveAuthLDAPSyncPanel',
753 name
: gettext('LDAP Server'),
754 ipanel
: 'pveAuthLDAPPanel',
755 syncipanel
: 'pveAuthLDAPSyncPanel',
760 ipanel
: 'pveAuthBasePanel',
764 name
: 'Proxmox VE authentication server',
765 ipanel
: 'pveAuthBasePanel',
772 name
: Proxmox
.Utils
.directoryText
,
773 ipanel
: 'DirInputPanel',
779 ipanel
: 'LVMInputPanel',
785 ipanel
: 'LvmThinInputPanel',
791 ipanel
: 'NFSInputPanel',
797 ipanel
: 'CIFSInputPanel',
803 ipanel
: 'GlusterFsInputPanel',
809 ipanel
: 'IScsiInputPanel',
815 ipanel
: 'CephFSInputPanel',
820 name
: 'CephFS (PVE)',
821 ipanel
: 'CephFSInputPanel',
828 ipanel
: 'RBDInputPanel',
834 ipanel
: 'RBDInputPanel',
840 name
: 'ZFS over iSCSI',
841 ipanel
: 'ZFSInputPanel',
847 ipanel
: 'ZFSPoolInputPanel',
852 name
: 'Proxmox Backup Server',
853 ipanel
: 'PBSInputPanel',
878 ipanel
: 'SimpleInputPanel',
883 ipanel
: 'VlanInputPanel',
888 ipanel
: 'QinQInputPanel',
893 ipanel
: 'VxlanInputPanel',
898 ipanel
: 'EvpnInputPanel',
903 sdncontrollerSchema
: {
910 ipanel
: 'EvpnInputPanel',
911 faIcon
: 'crosshairs',
915 ipanel
: 'BgpInputPanel',
927 ipanel
: 'PVEIpamInputPanel',
933 ipanel
: 'NetboxInputPanel',
938 ipanel
: 'PhpIpamInputPanel',
950 ipanel
: 'PowerdnsInputPanel',
955 format_sdnvnet_type: function(value
, md
, record
) {
956 var schema
= PVE
.Utils
.sdnvnetSchema
[value
];
960 return Proxmox
.Utils
.unknownText
;
963 format_sdnzone_type: function(value
, md
, record
) {
964 var schema
= PVE
.Utils
.sdnzoneSchema
[value
];
968 return Proxmox
.Utils
.unknownText
;
971 format_sdncontroller_type: function(value
, md
, record
) {
972 var schema
= PVE
.Utils
.sdncontrollerSchema
[value
];
976 return Proxmox
.Utils
.unknownText
;
979 format_sdnipam_type: function(value
, md
, record
) {
980 var schema
= PVE
.Utils
.sdnipamSchema
[value
];
984 return Proxmox
.Utils
.unknownText
;
987 format_sdndns_type: function(value
, md
, record
) {
988 var schema
= PVE
.Utils
.sdndnsSchema
[value
];
992 return Proxmox
.Utils
.unknownText
;
995 format_storage_type: function(value
, md
, record
) {
996 if (value
=== 'rbd') {
997 value
= !record
|| record
.get('monhost') ? 'rbd' : 'pveceph';
998 } else if (value
=== 'cephfs') {
999 value
= !record
|| record
.get('monhost') ? 'cephfs' : 'pvecephfs';
1002 var schema
= PVE
.Utils
.storageSchema
[value
];
1006 return Proxmox
.Utils
.unknownText
;
1009 format_ha: function(value
) {
1010 var text
= Proxmox
.Utils
.noneText
;
1012 if (value
.managed
) {
1013 text
= value
.state
|| Proxmox
.Utils
.noneText
;
1015 text
+= ', ' + Proxmox
.Utils
.groupText
+ ': ';
1016 text
+= value
.group
|| Proxmox
.Utils
.noneText
;
1022 format_content_types: function(value
) {
1023 return value
.split(',').sort().map(function(ct
) {
1024 return PVE
.Utils
.contentTypes
[ct
] || ct
;
1028 render_storage_content: function(value
, metaData
, record
) {
1029 var data
= record
.data
;
1030 if (Ext
.isNumber(data
.channel
) &&
1031 Ext
.isNumber(data
.id
) &&
1032 Ext
.isNumber(data
.lun
)) {
1034 Ext
.String
.leftPad(data
.channel
, 2, '0') +
1035 " ID " + data
.id
+ " LUN " + data
.lun
;
1037 return data
.volid
.replace(/^.*?:(.*?\/)?/, '');
1040 render_serverity: function(value
) {
1041 return PVE
.Utils
.log_severity_hash
[value
] || value
;
1044 calculate_hostcpu: function(data
) {
1046 if (!(data
.uptime
&& Ext
.isNumeric(data
.cpu
))) {
1050 if (data
.type
!== 'qemu' && data
.type
!== 'lxc') {
1054 var index
= PVE
.data
.ResourceStore
.findExact('id', 'node/' + data
.node
);
1055 var node
= PVE
.data
.ResourceStore
.getAt(index
);
1056 if (!Ext
.isDefined(node
) || node
=== null) {
1059 var maxcpu
= node
.data
.maxcpu
|| 1;
1061 if (!Ext
.isNumeric(maxcpu
) && (maxcpu
>= 1)) {
1065 return ((data
.cpu
/maxcpu
) * data
.maxcpu
);
1068 render_hostcpu: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1070 if (!(record
.data
.uptime
&& Ext
.isNumeric(record
.data
.cpu
))) {
1074 if (record
.data
.type
!== 'qemu' && record
.data
.type
!== 'lxc') {
1078 var index
= PVE
.data
.ResourceStore
.findExact('id', 'node/' + record
.data
.node
);
1079 var node
= PVE
.data
.ResourceStore
.getAt(index
);
1080 if (!Ext
.isDefined(node
) || node
=== null) {
1083 var maxcpu
= node
.data
.maxcpu
|| 1;
1085 if (!Ext
.isNumeric(maxcpu
) && (maxcpu
>= 1)) {
1089 var per
= (record
.data
.cpu
/maxcpu
) * record
.data
.maxcpu
* 100;
1091 return per
.toFixed(1) + '% of ' + maxcpu
.toString() + (maxcpu
> 1 ? 'CPUs' : 'CPU');
1094 render_bandwidth: function(value
) {
1095 if (!Ext
.isNumeric(value
)) {
1099 return Proxmox
.Utils
.format_size(value
) + '/s';
1102 render_timestamp_human_readable: function(value
) {
1103 return Ext
.Date
.format(new Date(value
* 1000), 'l d F Y H:i:s');
1106 calculate_mem_usage: function(data
) {
1107 if (!Ext
.isNumeric(data
.mem
) ||
1108 data
.maxmem
=== 0 ||
1113 return data
.mem
/ data
.maxmem
;
1116 calculate_hostmem_usage: function(data
) {
1118 if (data
.type
!== 'qemu' && data
.type
!== 'lxc') {
1122 var index
= PVE
.data
.ResourceStore
.findExact('id', 'node/' + data
.node
);
1123 var node
= PVE
.data
.ResourceStore
.getAt(index
);
1125 if (!Ext
.isDefined(node
) || node
=== null) {
1128 var maxmem
= node
.data
.maxmem
|| 0;
1130 if (!Ext
.isNumeric(data
.mem
) ||
1136 return (data
.mem
/ maxmem
);
1139 render_mem_usage_percent: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1140 if (!Ext
.isNumeric(value
) || value
=== -1) {
1144 // we got no percentage but bytes
1146 var maxmem
= record
.data
.maxmem
;
1147 if (!record
.data
.uptime
||
1149 !Ext
.isNumeric(mem
)) {
1153 return (mem
*100/maxmem
).toFixed(1) + " %";
1155 return (value
*100).toFixed(1) + " %";
1158 render_hostmem_usage_percent: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1160 if (!Ext
.isNumeric(record
.data
.mem
) || value
=== -1) {
1164 if (record
.data
.type
!== 'qemu' && record
.data
.type
!== 'lxc') {
1168 var index
= PVE
.data
.ResourceStore
.findExact('id', 'node/' + record
.data
.node
);
1169 var node
= PVE
.data
.ResourceStore
.getAt(index
);
1170 var maxmem
= node
.data
.maxmem
|| 0;
1172 if (record
.data
.mem
> 1 ) {
1173 // we got no percentage but bytes
1174 var mem
= record
.data
.mem
;
1175 if (!record
.data
.uptime
||
1177 !Ext
.isNumeric(mem
)) {
1181 return ((mem
*100)/maxmem
).toFixed(1) + " %";
1183 return (value
*100).toFixed(1) + " %";
1186 render_mem_usage: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1188 var maxmem
= record
.data
.maxmem
;
1190 if (!record
.data
.uptime
) {
1194 if (!(Ext
.isNumeric(mem
) && maxmem
)) {
1198 return Proxmox
.Utils
.render_size(value
);
1201 calculate_disk_usage: function(data
) {
1202 if (!Ext
.isNumeric(data
.disk
) ||
1203 data
.type
=== 'qemu' ||
1204 data
.type
=== 'lxc' && data
.uptime
=== 0 ||
1205 data
.maxdisk
=== 0) {
1209 return data
.disk
/ data
.maxdisk
;
1212 render_disk_usage_percent: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1213 if (!Ext
.isNumeric(value
) || value
=== -1) {
1217 return (value
* 100).toFixed(1) + " %";
1220 render_disk_usage: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1222 var maxdisk
= record
.data
.maxdisk
;
1223 var type
= record
.data
.type
;
1225 if (!Ext
.isNumeric(disk
) ||
1228 type
=== 'lxc' && record
.data
.uptime
=== 0) {
1232 return Proxmox
.Utils
.render_size(value
);
1235 get_object_icon_class: function(type
, record
) {
1239 if (type
=== 'type') {
1241 objType
= record
.groupbyid
;
1242 } else if (record
.template
) {
1244 objType
= 'template';
1248 status
= record
.status
+ ' ha-' + record
.hastate
;
1252 status
+= ' locked lock-' + record
.lock
;
1255 var defaults
= PVE
.tree
.ResourceTree
.typeDefaults
[objType
];
1256 if (defaults
&& defaults
.iconCls
) {
1257 var retVal
= defaults
.iconCls
+ ' ' + status
;
1264 render_resource_type: function(value
, metaData
, record
, rowIndex
, colIndex
, store
) {
1265 var cls
= PVE
.Utils
.get_object_icon_class(value
, record
.data
);
1267 var fa
= '<i class="fa-fw x-grid-icon-custom ' + cls
+ '"></i> ';
1271 render_support_level: function(value
, metaData
, record
) {
1272 return PVE
.Utils
.support_level_hash
[value
] || '-';
1275 render_upid: function(value
, metaData
, record
) {
1276 var type
= record
.data
.type
;
1277 var id
= record
.data
.id
;
1279 return Proxmox
.Utils
.format_task_description(type
, id
);
1282 render_optional_url: function(value
) {
1284 if (value
&& (match
= value
.match(/^https?:\/\//)) !== null) {
1285 return '<a target="_blank" href="' + value
+ '">' + value
+ '</a>';
1290 render_san: function(value
) {
1292 if (Ext
.isArray(value
)) {
1293 value
.forEach(function(val
) {
1294 if (!Ext
.isNumber(val
)) {
1298 return names
.join('<br>');
1303 render_full_name: function(firstname
, metaData
, record
) {
1304 var first
= firstname
|| '';
1305 var last
= record
.data
.lastname
|| '';
1306 return Ext
.htmlEncode(first
+ " " + last
);
1309 render_u2f_error: function(error
) {
1311 '1': gettext('Other Error'),
1312 '2': gettext('Bad Request'),
1313 '3': gettext('Configuration Unsupported'),
1314 '4': gettext('Device Ineligible'),
1315 '5': gettext('Timeout'),
1317 return "U2F Error: " + ErrorNames
[error
] || Proxmox
.Utils
.unknownText
;
1320 windowHostname: function() {
1321 return window
.location
.hostname
.replace(Proxmox
.Utils
.IP6_bracket_match
,
1322 function(m
, addr
, offset
, original
) { return addr
; });
1325 openDefaultConsoleWindow: function(consoles
, consoleType
, vmid
, nodename
, vmname
, cmd
) {
1326 var dv
= PVE
.Utils
.defaultViewer(consoles
, consoleType
);
1327 PVE
.Utils
.openConsoleWindow(dv
, consoleType
, vmid
, nodename
, vmname
, cmd
);
1330 openConsoleWindow: function(viewer
, consoleType
, vmid
, nodename
, vmname
, cmd
) {
1331 if (vmid
== undefined && (consoleType
=== 'kvm' || consoleType
=== 'lxc')) {
1332 throw "missing vmid";
1335 throw "no nodename specified";
1338 if (viewer
=== 'html5') {
1339 PVE
.Utils
.openVNCViewer(consoleType
, vmid
, nodename
, vmname
, cmd
);
1340 } else if (viewer
=== 'xtermjs') {
1341 Proxmox
.Utils
.openXtermJsViewer(consoleType
, vmid
, nodename
, vmname
, cmd
);
1342 } else if (viewer
=== 'vv') {
1343 let url
= '/nodes/' + nodename
+ '/spiceshell';
1345 proxy
: PVE
.Utils
.windowHostname(),
1347 if (consoleType
=== 'kvm') {
1348 url
= '/nodes/' + nodename
+ '/qemu/' + vmid
.toString() + '/spiceproxy';
1349 } else if (consoleType
=== 'lxc') {
1350 url
= '/nodes/' + nodename
+ '/lxc/' + vmid
.toString() + '/spiceproxy';
1351 } else if (consoleType
=== 'upgrade') {
1352 params
.cmd
= 'upgrade';
1353 } else if (consoleType
=== 'cmd') {
1355 } else if (consoleType
!== 'shell') {
1356 throw `unknown spice viewer type '${consoleType}'`;
1358 PVE
.Utils
.openSpiceViewer(url
, params
);
1360 throw `unknown viewer type '${viewer}'`;
1364 defaultViewer: function(consoles
, type
) {
1365 var allowSpice
, allowXtermjs
;
1367 if (consoles
=== true) {
1369 allowXtermjs
= true;
1370 } else if (typeof consoles
=== 'object') {
1371 allowSpice
= consoles
.spice
;
1372 allowXtermjs
= !!consoles
.xtermjs
;
1374 let dv
= PVE
.VersionInfo
.console
|| (type
=== 'kvm' ? 'vv' : 'xtermjs');
1375 if (dv
=== 'vv' && !allowSpice
) {
1376 dv
= allowXtermjs
? 'xtermjs' : 'html5';
1377 } else if (dv
=== 'xtermjs' && !allowXtermjs
) {
1378 dv
= allowSpice
? 'vv' : 'html5';
1384 openVNCViewer: function(vmtype
, vmid
, nodename
, vmname
, cmd
) {
1385 let scaling
= 'off';
1386 if (Proxmox
.Utils
.toolkit
!== 'touch') {
1387 var sp
= Ext
.state
.Manager
.getProvider();
1388 scaling
= sp
.get('novnc-scaling', 'off');
1390 var url
= Ext
.Object
.toQueryString({
1391 console
: vmtype
, // kvm, lxc, upgrade or shell
1399 var nw
= window
.open("?" + url
, '_blank', "innerWidth=745,innerheight=427");
1405 openSpiceViewer: function(url
, params
) {
1406 var downloadWithName = function(uri
, name
) {
1407 var link
= Ext
.DomHelper
.append(document
.body
, {
1410 css
: 'display:none;visibility:hidden;height:0px;',
1413 // Note: we need to tell Android and Chrome the correct file name extension
1414 // but we do not set 'download' tag for other environments, because
1415 // It can have strange side effects (additional user prompt on firefox)
1416 if (navigator
.userAgent
.match(/Android|Chrome/i)) {
1417 link
.download
= name
;
1420 if (link
.fireEvent
) {
1421 link
.fireEvent('onclick');
1423 let evt
= document
.createEvent("MouseEvents");
1424 evt
.initMouseEvent('click', true, true, window
, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
1425 link
.dispatchEvent(evt
);
1429 Proxmox
.Utils
.API2Request({
1433 failure: function(response
, opts
) {
1434 Ext
.Msg
.alert('Error', response
.htmlStatus
);
1436 success: function(response
, opts
) {
1437 var raw
= "[virt-viewer]\n";
1438 Ext
.Object
.each(response
.result
.data
, function(k
, v
) {
1439 raw
+= k
+ "=" + v
+ "\n";
1441 var url
= 'data:application/x-virt-viewer;charset=UTF-8,' +
1442 encodeURIComponent(raw
);
1444 downloadWithName(url
, "pve-spice.vv");
1449 openTreeConsole: function(tree
, record
, item
, index
, e
) {
1451 var nodename
= record
.data
.node
;
1452 var vmid
= record
.data
.vmid
;
1453 var vmname
= record
.data
.name
;
1454 if (record
.data
.type
=== 'qemu' && !record
.data
.template
) {
1455 Proxmox
.Utils
.API2Request({
1456 url
: '/nodes/' + nodename
+ '/qemu/' + vmid
+ '/status/current',
1457 failure: function(response
, opts
) {
1458 Ext
.Msg
.alert('Error', response
.htmlStatus
);
1460 success: function(response
, opts
) {
1461 let conf
= response
.result
.data
;
1463 spice
: !!conf
.spice
,
1464 xtermjs
: !!conf
.serial
,
1466 PVE
.Utils
.openDefaultConsoleWindow(consoles
, 'kvm', vmid
, nodename
, vmname
);
1469 } else if (record
.data
.type
=== 'lxc' && !record
.data
.template
) {
1470 PVE
.Utils
.openDefaultConsoleWindow(true, 'lxc', vmid
, nodename
, vmname
);
1474 // test automation helper
1475 call_menu_handler: function(menu
, text
) {
1476 var list
= menu
.query('menuitem');
1478 Ext
.Array
.each(list
, function(item
) {
1479 if (item
.text
=== text
) {
1490 createCmdMenu: function(v
, record
, item
, index
, event
) {
1492 if (!(v
instanceof Ext
.tree
.View
)) {
1496 var template
= !!record
.data
.template
;
1497 var type
= record
.data
.type
;
1500 if (type
=== 'qemu' || type
== 'lxc') {
1501 menu
= Ext
.create('PVE.menu.TemplateMenu', {
1505 } else if (type
=== 'qemu' ||
1508 menu
= Ext
.create('PVE.' + type
+ '.CmdMenu', {
1510 nodename
: record
.data
.node
,
1516 menu
.showAt(event
.getXY());
1520 // helper for deleting field which are set to there default values
1521 delete_if_default: function(values
, fieldname
, default_val
, create
) {
1522 if (values
[fieldname
] === '' || values
[fieldname
] === default_val
) {
1524 if (values
.delete) {
1525 if (Ext
.isArray(values
.delete)) {
1526 values
.delete.push(fieldname
);
1528 values
.delete += ',' + fieldname
;
1531 values
.delete = fieldname
;
1535 delete values
[fieldname
];
1539 loadSSHKeyFromFile: function(file
, callback
) {
1540 // ssh-keygen produces 740 bytes for an average 4096 bit rsa key, with
1541 // a user@host comment, 1420 for 8192 bits; current max is 16kbit
1542 // assume: 740*8 for max. 32kbit (5920 byte file)
1543 // round upwards to nearest nice number => 8192 bytes, leaves lots of comment space
1544 if (file
.size
> 8192) {
1545 Ext
.Msg
.alert(gettext('Error'), gettext("Invalid file size: ") + file
.size
);
1551 var reader
= new FileReader();
1552 reader
.onload = function(evt
) {
1553 callback(evt
.target
.result
);
1555 reader
.readAsText(file
);
1558 loadTextFromFile: function(file
, callback
, maxBytes
) {
1559 let maxSize
= maxBytes
|| 8192;
1560 if (file
.size
> maxSize
) {
1561 Ext
.Msg
.alert(gettext('Error'), gettext("Invalid file size: ") + file
.size
);
1567 let reader
= new FileReader();
1568 reader
.onload
= evt
=> callback(evt
.target
.result
);
1569 reader
.readAsText(file
);
1572 diskControllerMaxIDs
: {
1579 // types is either undefined (all busses), an array of busses, or a single bus
1580 forEachBus: function(types
, func
) {
1581 var busses
= Object
.keys(PVE
.Utils
.diskControllerMaxIDs
);
1582 var i
, j
, count
, cont
;
1584 if (Ext
.isArray(types
)) {
1586 } else if (Ext
.isDefined(types
)) {
1590 // check if we only have valid busses
1591 for (i
= 0; i
< busses
.length
; i
++) {
1592 if (!PVE
.Utils
.diskControllerMaxIDs
[busses
[i
]]) {
1593 throw "invalid bus: '" + busses
[i
] + "'";
1597 for (i
= 0; i
< busses
.length
; i
++) {
1598 count
= PVE
.Utils
.diskControllerMaxIDs
[busses
[i
]];
1599 for (j
= 0; j
< count
; j
++) {
1600 cont
= func(busses
[i
], j
);
1601 if (!cont
&& cont
!== undefined) {
1608 mp_counts
: { mps
: 256, unused
: 256 },
1610 forEachMP: function(func
, includeUnused
) {
1612 for (i
= 0; i
< PVE
.Utils
.mp_counts
.mps
; i
++) {
1613 cont
= func('mp', i
);
1614 if (!cont
&& cont
!== undefined) {
1619 if (!includeUnused
) {
1623 for (i
= 0; i
< PVE
.Utils
.mp_counts
.unused
; i
++) {
1624 cont
= func('unused', i
);
1625 if (!cont
&& cont
!== undefined) {
1631 hardware_counts
: { net
: 32, usb
: 5, hostpci
: 16, audio
: 1, efidisk
: 1, serial
: 4, rng
: 1 },
1633 cleanEmptyObjectKeys: function(obj
) {
1635 for (propName
in obj
) {
1636 if (obj
.hasOwnProperty(propName
)) {
1637 if (obj
[propName
] === null || obj
[propName
] === undefined) {
1638 delete obj
[propName
];
1644 acmedomain_count
: 5,
1646 add_domain_to_acme: function(acme
, domain
) {
1647 if (acme
.domains
=== undefined) {
1648 acme
.domains
= [domain
];
1650 acme
.domains
.push(domain
);
1651 acme
.domains
= acme
.domains
.filter((value
, index
, self
) => self
.indexOf(value
) === index
);
1656 remove_domain_from_acme: function(acme
, domain
) {
1657 if (acme
.domains
!== undefined) {
1658 acme
.domains
= acme
.domains
.filter((value
, index
, self
) => self
.indexOf(value
) === index
&& value
!== domain
);
1663 handleStoreErrorOrMask: function(me
, store
, regex
, callback
) {
1664 me
.mon(store
, 'load', function(proxy
, response
, success
, operation
) {
1666 Proxmox
.Utils
.setErrorMask(me
, false);
1671 if (operation
.error
.statusText
) {
1672 if (operation
.error
.statusText
.match(regex
)) {
1673 callback(me
, operation
.error
);
1676 msg
= operation
.error
.statusText
+ ' (' + operation
.error
.status
+ ')';
1679 msg
= gettext('Connection error');
1681 Proxmox
.Utils
.setErrorMask(me
, msg
);
1685 showCephInstallOrMask: function(container
, msg
, nodename
, callback
) {
1686 if (msg
.match(/not (installed|initialized)/i)) {
1687 if (Proxmox
.UserName
=== 'root@pam') {
1688 container
.el
.mask();
1689 if (!container
.down('pveCephInstallWindow')) {
1690 var isInstalled
= !!msg
.match(/not initialized/i);
1691 var win
= Ext
.create('PVE.ceph.Install', {
1694 win
.getViewModel().set('isInstalled', isInstalled
);
1700 container
.mask(Ext
.String
.format(gettext('{0} not installed.') +
1701 ' ' + gettext('Log in as root to install.'), 'Ceph'), ['pve-static-mask']);
1709 monitor_ceph_installed: function(view
, rstore
, nodename
, maskOwnerCt
) {
1710 PVE
.Utils
.handleStoreErrorOrMask(
1713 /not (installed|initialized)/i,
1715 nodename
= nodename
|| 'localhost';
1716 let maskTarget
= maskOwnerCt
? view
.ownerCt
: view
;
1717 rstore
.stopUpdate();
1718 PVE
.Utils
.showCephInstallOrMask(maskTarget
, error
.statusText
, nodename
, win
=> {
1719 view
.mon(win
, 'cephInstallWindowClosed', () => rstore
.startUpdate());
1726 propertyStringSet: function(target
, source
, name
, value
) {
1728 if (value
=== undefined) {
1729 target
[name
] = source
;
1731 target
[name
] = value
;
1734 delete target
[name
];
1738 forEachCorosyncLink: function(nodeinfo
, cb
) {
1739 let re
= /(?:ring|link)(\d+)_addr/;
1740 Ext
.iterate(nodeinfo
, (prop
, val
) => {
1741 let match
= re
.exec(prop
);
1743 cb(Number(match
[1]), val
);
1750 'AuthenticAMD': 'AMD',
1751 'GenuineIntel': 'Intel',
1759 "_default_": 5, // includes custom models
1762 verify_ip64_address_list: function(value
, with_suffix
) {
1763 for (let addr
of value
.split(/[ ,;]+/)) {
1769 let parts
= addr
.split('%');
1772 if (parts
.length
> 2) {
1776 if (parts
.length
> 1 && !addr
.startsWith('fe80:')) {
1781 if (!Proxmox
.Utils
.IP64_match
.test(addr
)) {
1791 constructor: function() {
1793 Ext
.apply(me
, me
.utilities
);
1795 Proxmox
.Utils
.override_task_descriptions({
1796 acmedeactivate
: ['ACME Account', gettext('Deactivate')],
1797 acmenewcert
: ['SRV', gettext('Order Certificate')],
1798 acmerefresh
: ['ACME Account', gettext('Refresh')],
1799 acmeregister
: ['ACME Account', gettext('Register')],
1800 acmerenew
: ['SRV', gettext('Renew Certificate')],
1801 acmerevoke
: ['SRV', gettext('Revoke Certificate')],
1802 acmeupdate
: ['ACME Account', gettext('Update')],
1803 'auth-realm-sync': [gettext('Realm'), gettext('Sync')],
1804 'auth-realm-sync-test': [gettext('Realm'), gettext('Sync Preview')],
1805 cephcreatemds
: ['Ceph Metadata Server', gettext('Create')],
1806 cephcreatemgr
: ['Ceph Manager', gettext('Create')],
1807 cephcreatemon
: ['Ceph Monitor', gettext('Create')],
1808 cephcreateosd
: ['Ceph OSD', gettext('Create')],
1809 cephcreatepool
: ['Ceph Pool', gettext('Create')],
1810 cephdestroymds
: ['Ceph Metadata Server', gettext('Destroy')],
1811 cephdestroymgr
: ['Ceph Manager', gettext('Destroy')],
1812 cephdestroymon
: ['Ceph Monitor', gettext('Destroy')],
1813 cephdestroyosd
: ['Ceph OSD', gettext('Destroy')],
1814 cephdestroypool
: ['Ceph Pool', gettext('Destroy')],
1815 cephfscreate
: ['CephFS', gettext('Create')],
1816 cephsetpool
: ['Ceph Pool', gettext('Edit')],
1817 cephsetflags
: ['', gettext('Change global Ceph flags')],
1818 clustercreate
: ['', gettext('Create Cluster')],
1819 clusterjoin
: ['', gettext('Join Cluster')],
1820 dircreate
: [gettext('Directory Storage'), gettext('Create')],
1821 dirremove
: [gettext('Directory'), gettext('Remove')],
1822 download
: ['', gettext('Download')],
1823 hamigrate
: ['HA', gettext('Migrate')],
1824 hashutdown
: ['HA', gettext('Shutdown')],
1825 hastart
: ['HA', gettext('Start')],
1826 hastop
: ['HA', gettext('Stop')],
1827 imgcopy
: ['', gettext('Copy data')],
1828 imgdel
: ['', gettext('Erase data')],
1829 lvmcreate
: [gettext('LVM Storage'), gettext('Create')],
1830 lvmthincreate
: [gettext('LVM-Thin Storage'), gettext('Create')],
1831 migrateall
: ['', gettext('Migrate all VMs and Containers')],
1832 'move_volume': ['CT', gettext('Move Volume')],
1833 'pbs-download': ['VM/CT', gettext('File Restore Download')],
1834 pull_file
: ['CT', gettext('Pull file')],
1835 push_file
: ['CT', gettext('Push file')],
1836 qmclone
: ['VM', gettext('Clone')],
1837 qmconfig
: ['VM', gettext('Configure')],
1838 qmcreate
: ['VM', gettext('Create')],
1839 qmdelsnapshot
: ['VM', gettext('Delete Snapshot')],
1840 qmdestroy
: ['VM', gettext('Destroy')],
1841 qmigrate
: ['VM', gettext('Migrate')],
1842 qmmove
: ['VM', gettext('Move disk')],
1843 qmpause
: ['VM', gettext('Pause')],
1844 qmreboot
: ['VM', gettext('Reboot')],
1845 qmreset
: ['VM', gettext('Reset')],
1846 qmrestore
: ['VM', gettext('Restore')],
1847 qmresume
: ['VM', gettext('Resume')],
1848 qmrollback
: ['VM', gettext('Rollback')],
1849 qmshutdown
: ['VM', gettext('Shutdown')],
1850 qmsnapshot
: ['VM', gettext('Snapshot')],
1851 qmstart
: ['VM', gettext('Start')],
1852 qmstop
: ['VM', gettext('Stop')],
1853 qmsuspend
: ['VM', gettext('Hibernate')],
1854 qmtemplate
: ['VM', gettext('Convert to template')],
1855 spiceproxy
: ['VM/CT', gettext('Console') + ' (Spice)'],
1856 spiceshell
: ['', gettext('Shell') + ' (Spice)'],
1857 startall
: ['', gettext('Start all VMs and Containers')],
1858 stopall
: ['', gettext('Stop all VMs and Containers')],
1859 unknownimgdel
: ['', gettext('Destroy image from unknown guest')],
1860 vncproxy
: ['VM/CT', gettext('Console')],
1861 vncshell
: ['', gettext('Shell')],
1862 vzclone
: ['CT', gettext('Clone')],
1863 vzcreate
: ['CT', gettext('Create')],
1864 vzdelsnapshot
: ['CT', gettext('Delete Snapshot')],
1865 vzdestroy
: ['CT', gettext('Destroy')],
1866 vzdump
: (type
, id
) => id
? `VM/CT ${id} - ${gettext('Backup')}` : gettext('Backup Job'),
1867 vzmigrate
: ['CT', gettext('Migrate')],
1868 vzmount
: ['CT', gettext('Mount')],
1869 vzreboot
: ['CT', gettext('Reboot')],
1870 vzrestore
: ['CT', gettext('Restore')],
1871 vzresume
: ['CT', gettext('Resume')],
1872 vzrollback
: ['CT', gettext('Rollback')],
1873 vzshutdown
: ['CT', gettext('Shutdown')],
1874 vzsnapshot
: ['CT', gettext('Snapshot')],
1875 vzstart
: ['CT', gettext('Start')],
1876 vzstop
: ['CT', gettext('Stop')],
1877 vzsuspend
: ['CT', gettext('Suspend')],
1878 vztemplate
: ['CT', gettext('Convert to template')],
1879 vzumount
: ['CT', gettext('Unmount')],
1880 zfscreate
: [gettext('ZFS Storage'), gettext('Create')],