1 Ext
.define('PVE.dc.BackupEdit', {
2 extend
: 'Proxmox.window.Edit',
3 alias
: ['widget.pveDcBackupEdit'],
5 defaultFocus
: undefined,
7 initComponent : function() {
10 me
.isCreate
= !me
.jobid
;
16 url
= '/api2/extjs/cluster/backup';
19 url
= '/api2/extjs/cluster/backup/' + me
.jobid
;
23 var vmidField
= Ext
.create('Ext.form.field.Hidden', {
27 // 'value' can be assigned a string or an array
28 var selModeField
= Ext
.create('Proxmox.form.KVComboBox', {
29 xtype
: 'proxmoxKVComboBox',
31 ['include', gettext('Include selected VMs')],
32 ['all', gettext('All')],
33 ['exclude', gettext('Exclude selected VMs')],
34 ['pool', gettext('Pool based')]
36 fieldLabel
: gettext('Selection mode'),
41 var sm
= Ext
.create('Ext.selection.CheckboxModel', {
44 selectionchange: function(model
, selected
) {
46 Ext
.Array
.each(selected
, function(record
) {
47 sel
.push(record
.data
.vmid
);
50 // to avoid endless recursion suspend the vmidField change
51 // event temporary as it calls us again
52 vmidField
.suspendEvent('change');
53 vmidField
.setValue(sel
);
54 vmidField
.resumeEvent('change');
59 var storagesel
= Ext
.create('PVE.form.StorageSelector', {
60 fieldLabel
: gettext('Storage'),
61 nodename
: 'localhost',
62 storageContent
: 'backup',
67 var store
= new Ext
.data
.Store({
68 model
: 'PVEResources',
75 var vmgrid
= Ext
.createWidget('grid', {
88 header
: gettext('Node'),
92 header
: gettext('Status'),
94 renderer: function(value
) {
96 return Proxmox
.Utils
.runningText
;
98 return Proxmox
.Utils
.stoppedText
;
103 header
: gettext('Name'),
108 header
: gettext('Type'),
114 var selectPoolMembers = function(poolid
) {
118 sm
.deselectAll(true);
129 var selPool
= Ext
.create('PVE.form.PoolSelector', {
130 fieldLabel
: gettext('Pool to backup'),
135 change: function( selpool
, newValue
, oldValue
) {
136 selectPoolMembers(newValue
);
141 var nodesel
= Ext
.create('PVE.form.NodeSelector', {
143 fieldLabel
: gettext('Node'),
147 emptyText
: '-- ' + gettext('All') + ' --',
149 change: function(f
, value
) {
150 storagesel
.setNodename(value
|| 'localhost');
151 var mode
= selModeField
.getValue();
153 store
.filterBy(function(rec
) {
154 return (!value
|| rec
.get('node') === value
);
156 if (mode
=== 'all') {
160 if (mode
=== 'pool') {
161 selectPoolMembers(selPool
.value
);
171 xtype
: 'pveDayOfWeekSelector',
173 fieldLabel
: gettext('Day of week'),
180 fieldLabel
: gettext('Start Time'),
194 fieldLabel
: gettext('Send email to'),
198 xtype
: 'pveEmailNotificationSelector',
199 fieldLabel
: gettext('Email notification'),
200 name
: 'mailnotification',
201 deleteEmpty
: me
.isCreate
? false : true,
202 value
: me
.isCreate
? 'always' : ''
205 xtype
: 'pveCompressionSelector',
206 fieldLabel
: gettext('Compression'),
208 deleteEmpty
: me
.isCreate
? false : true,
212 xtype
: 'pveBackupModeSelector',
213 fieldLabel
: gettext('Mode'),
218 xtype
: 'proxmoxcheckbox',
219 fieldLabel
: gettext('Enable'),
228 var ipanel
= Ext
.create('Proxmox.panel.InputPanel', {
229 onlineHelp
: 'chapter_vzdump',
232 onGetValues: function(values
) {
235 Proxmox
.Utils
.assemble_field_data(values
, { 'delete': 'node' });
240 var selMode
= values
.selMode
;
241 delete values
.selMode
;
243 if (selMode
=== 'all') {
247 } else if (selMode
=== 'exclude') {
249 values
.exclude
= values
.vmid
;
251 } else if (selMode
=== 'pool') {
255 if (selMode
!== 'pool') {
262 var update_vmid_selection = function(list
, mode
) {
263 if (mode
!== 'all' && mode
!== 'pool') {
264 sm
.deselectAll(true);
266 Ext
.Array
.each(list
.split(','), function(vmid
) {
267 var rec
= store
.findRecord('vmid', vmid
);
269 sm
.select(rec
, true);
276 vmidField
.on('change', function(f
, value
) {
277 var mode
= selModeField
.getValue();
278 update_vmid_selection(value
, mode
);
281 selModeField
.on('change', function(f
, value
, oldValue
) {
282 if (oldValue
=== 'pool') {
283 store
.removeFilter('poolFilter');
286 if (oldValue
=== 'all') {
287 sm
.deselectAll(true);
288 vmidField
.setValue('');
291 if (value
=== 'all') {
293 vmgrid
.setDisabled(true);
295 vmgrid
.setDisabled(false);
298 if (value
=== 'pool') {
299 vmgrid
.setDisabled(true);
300 vmidField
.setValue('');
301 selPool
.setVisible(true);
302 selPool
.allowBlank
= false;
303 selectPoolMembers(selPool
.value
);
306 selPool
.setVisible(false);
307 selPool
.allowBlank
= true;
309 var list
= vmidField
.getValue();
310 update_vmid_selection(list
, value
);
313 var reload = function() {
315 params
: { type
: 'vm' },
316 callback: function() {
317 var node
= nodesel
.getValue();
319 store
.filterBy(function(rec
) {
320 return (!node
|| node
.length
=== 0 || rec
.get('node') === node
);
322 var list
= vmidField
.getValue();
323 var mode
= selModeField
.getValue();
324 if (mode
=== 'all') {
326 } else if (mode
=== 'pool'){
327 selectPoolMembers(selPool
.value
);
329 update_vmid_selection(list
, mode
);
336 subject
: gettext("Backup Job"),
339 items
: [ ipanel
, vmgrid
]
345 selModeField
.setValue('include');
348 success: function(response
, options
) {
349 var data
= response
.result
.data
;
351 data
.dow
= data
.dow
.split(',');
353 if (data
.all
|| data
.exclude
) {
355 data
.vmid
= data
.exclude
;
356 data
.selMode
= 'exclude';
359 data
.selMode
= 'all';
361 } else if (data
.pool
) {
362 data
.selMode
= 'pool';
363 data
.selPool
= data
.pool
;
365 data
.selMode
= 'include';
378 Ext
.define('PVE.dc.BackupDiskTree', {
379 extend
: 'Ext.tree.Panel',
380 alias
: 'widget.pveBackupDiskTree',
393 tooltip
: gettext('Expand All'),
395 callback: function(panel
) {
401 tooltip
: gettext('Collapse All'),
403 callback: function(panel
) {
412 text
: gettext('Guest Image'),
413 renderer: function(value
, meta
, record
) {
414 if (record
.data
.type
) {
417 if (record
.data
.name
) {
418 ret
+= " (" + record
.data
.name
+ ")";
423 // extJS needs unique IDs but we only want to show the
424 // volumes key from "vmid:key"
425 return value
.split(':')[1] + " - " + record
.data
.name
;
432 text
: gettext('Type'),
437 text
: gettext('Backup Job'),
438 renderer
: PVE
.Utils
.render_backup_status
,
439 dataIndex
: 'included',
446 var sm
= me
.getSelectionModel();
448 Proxmox
.Utils
.API2Request({
449 url
: "/cluster/backup/" + me
.jobid
+ "/included_volumes",
452 failure: function(response
, opts
) {
453 Proxmox
.Utils
.setErrorMask(me
, response
.htmlStatus
);
455 success: function(response
, opts
) {
457 me
.setRootNode(response
.result
.data
);
463 initComponent: function() {
467 throw "no job id specified";
470 var sm
= Ext
.create('Ext.selection.TreeModel', {});
474 fields
: ['id', 'type',
478 calculate: function(data
) {
479 var txt
= 'fa x-fa-tree fa-';
480 if (data
.leaf
&& !data
.type
) {
481 return txt
+ 'hdd-o';
482 } else if (data
.type
=== 'qemu') {
483 return txt
+ 'desktop';
484 } else if (data
.type
=== 'lxc') {
487 return txt
+ 'question-circle';
495 fieldLabel
: gettext('Search'),
497 emptyText
: 'Name, VMID, Type',
500 enableKeyEvents
: true,
503 keyup: function(field
) {
504 let searchValue
= field
.getValue();
505 searchValue
= searchValue
.toLowerCase();
507 me
.store
.clearFilter(true);
508 me
.store
.filterBy(function(record
) {
512 if (record
.data
.depth
== 0) {
514 } else if (record
.data
.depth
== 1) {
516 } else if (record
.data
.depth
== 2) {
517 data
= record
.parentNode
.data
;
520 Ext
.each(['name', 'id', 'type'], function(property
) {
521 if (data
[property
] === null) {
525 let v
= data
[property
].toString();
526 if (v
!== undefined) {
528 if (v
.includes(searchValue
)) {
548 Ext
.define('PVE.dc.BackupInfo', {
549 extend
: 'Proxmox.panel.InputPanel',
550 alias
: 'widget.pveBackupInfo',
557 fieldLabel
: gettext('Node'),
558 xtype
: 'displayfield',
559 renderer: function (value
) {
561 return '-- ' + gettext('All') + ' --';
569 fieldLabel
: gettext('Storage'),
570 xtype
: 'displayfield',
574 fieldLabel
: gettext('Day of week'),
575 xtype
: 'displayfield',
576 renderer
: PVE
.Utils
.render_backup_days_of_week
,
580 fieldLabel
: gettext('Start Time'),
581 xtype
: 'displayfield',
585 fieldLabel
: gettext('Selection mode'),
586 xtype
: 'displayfield',
590 fieldLabel
: gettext('Pool to backup'),
591 xtype
: 'displayfield',
597 fieldLabel
: gettext('Send email to'),
598 xtype
: 'displayfield',
601 name
: 'mailnotification',
602 fieldLabel
: gettext('Email notification'),
603 xtype
: 'displayfield',
604 renderer: function (value
) {
608 msg
= gettext('Always');
611 msg
= gettext('On failure only');
619 fieldLabel
: gettext('Compression'),
620 xtype
: 'displayfield',
624 fieldLabel
: gettext('Mode'),
625 xtype
: 'displayfield',
626 renderer: function (value
) {
630 msg
= gettext('Snapshot');
633 msg
= gettext('Suspend');
636 msg
= gettext('Stop');
644 fieldLabel
: gettext('Enabled'),
645 xtype
: 'displayfield',
646 renderer: function (value
) {
647 if (PVE
.Parser
.parseBoolean(value
.toString())) {
648 return gettext('Yes');
650 return gettext('No');
656 setValues: function(values
) {
659 Ext
.iterate(values
, function(fieldId
, val
) {
660 let field
= me
.query('[isFormField][name=' + fieldId
+ ']')[0];
666 // selection Mode depends on the presence/absence of several keys
667 let selModeField
= me
.query('[isFormField][name=selMode]')[0];
668 let selMode
= 'none';
670 selMode
= gettext('Include selected VMs');
673 selMode
= gettext('All');
675 if (values
.exclude
) {
676 selMode
= gettext('Exclude selected VMs');
679 selMode
= gettext('Pool based');
681 selModeField
.setValue(selMode
);
684 let poolField
= me
.query('[isFormField][name=pool]')[0];
685 poolField
.setVisible(0);
689 initComponent: function() {
693 throw "no data provided";
697 me
.setValues(me
.record
);
702 Ext
.define('PVE.dc.BackedGuests', {
703 extend
: 'Ext.grid.GridPanel',
704 alias
: 'widget.pveBackedGuests',
710 header
: gettext('Type'),
712 renderer
: PVE
.Utils
.render_resource_type
,
717 header
: gettext('VMID'),
723 header
: gettext('Name'),
730 initComponent: function() {
733 me
.store
.clearFilter(true);
737 stateId
: 'grid-dc-backed-guests',
740 gettext('Search') + ':', ' ',
744 enableKeyEvents
: true,
747 keyup: function(field
) {
748 let searchValue
= field
.getValue();
749 searchValue
= searchValue
.toLowerCase();
751 me
.store
.clearFilter(true);
752 me
.store
.filterBy(function(record
) {
755 Ext
.each(['name', 'vmid', 'type'], function(property
) {
756 if (record
.data
[property
] == null) {
760 let v
= record
.data
[property
].toString();
761 if (v
!== undefined) {
763 if (v
.includes(searchValue
)) {
784 Ext
.define('PVE.dc.BackupView', {
785 extend
: 'Ext.grid.GridPanel',
787 alias
: ['widget.pveDcBackupView'],
789 onlineHelp
: 'chapter_vzdump',
791 allText
: '-- ' + gettext('All') + ' --',
793 initComponent : function() {
796 var store
= new Ext
.data
.Store({
797 model
: 'pve-cluster-backup',
800 url
: "/api2/json/cluster/backup"
804 var not_backed_store
= new Ext
.data
.Store({
808 url
: 'api2/json/cluster/backupinfo/not_backed_up',
812 var reload = function() {
814 not_backed_store
.load({
815 callback: function(records
, operation
, success
) {
816 if (records
.length
) {
817 not_backed_warning
.setVisible(true);
818 not_backed_btn
.setVisible(true);
820 not_backed_warning
.setVisible(false);
821 not_backed_btn
.setVisible(false);
827 var sm
= Ext
.create('Ext.selection.RowModel', {});
829 var run_editor = function() {
830 var rec
= sm
.getSelection()[0];
835 var win
= Ext
.create('PVE.dc.BackupEdit', {
838 win
.on('destroy', reload
);
842 var run_detail = function() {
843 let record
= sm
.getSelection()[0]
848 var infoview
= Ext
.create('PVE.dc.BackupInfo', {
853 var disktree
= Ext
.create('PVE.dc.BackupDiskTree', {
854 title
: gettext('Included disks'),
856 jobid
: record
.data
.id
,
859 var win
= Ext
.create('Ext.window.Window', {
864 stateId
: 'backup-detail-view',
867 title
: gettext('Backup Details'),
876 items
: [infoview
, disktree
],
881 var run_backup_now = function(job
) {
882 job
= Ext
.clone(job
);
884 let jobNode
= job
.node
;
885 // Remove properties related to scheduling
887 delete job
.starttime
;
891 job
.all
= job
.all
=== true ? 1 : 0;
893 let allNodes
= PVE
.data
.ResourceStore
.getNodes();
894 let nodes
= allNodes
.filter(node
=> node
.status
=== 'online').map(node
=> node
.node
);
897 if (jobNode
!== undefined) {
898 if (!nodes
.includes(jobNode
)) {
899 Ext
.Msg
.alert('Error', "Node '"+ jobNode
+"' from backup job isn't online!");
904 let unkownNodes
= allNodes
.filter(node
=> node
.status
!== 'online');
905 if (unkownNodes
.length
> 0)
906 errors
.push(unkownNodes
.map(node
=> node
.node
+ ": " + gettext("Node is offline")));
908 let jobTotalCount
= nodes
.length
, jobsStarted
= 0;
911 title
: gettext('Please wait...'),
914 progressText
: '0/' + jobTotalCount
,
917 let postRequest = function () {
919 Ext
.Msg
.updateProgress(jobsStarted
/ jobTotalCount
, jobsStarted
+ '/' + jobTotalCount
);
921 if (jobsStarted
== jobTotalCount
) {
923 if (errors
.length
> 0) {
924 Ext
.Msg
.alert('Error', 'Some errors have been encountered:<br />' + errors
.join('<br />'));
929 nodes
.forEach(node
=> Proxmox
.Utils
.API2Request({
930 url
: '/nodes/' + node
+ '/vzdump',
933 failure: function (response
, opts
) {
934 errors
.push(node
+ ': ' + response
.htmlStatus
);
941 var run_show_not_backed = function() {
943 var backedinfo
= Ext
.create('PVE.dc.BackedGuests', {
946 store
: not_backed_store
,
949 var win
= Ext
.create('Ext.window.Window', {
955 title
: gettext('Guests without backup job'),
969 var edit_btn
= new Proxmox
.button
.Button({
970 text
: gettext('Edit'),
976 var run_btn
= new Proxmox
.button
.Button({
977 text
: gettext('Run now'),
980 handler: function() {
981 var rec
= sm
.getSelection()[0];
987 title
: gettext('Confirm'),
988 icon
: Ext
.Msg
.QUESTION
,
989 msg
: gettext('Start the selected backup job now?'),
990 buttons
: Ext
.Msg
.YESNO
,
991 callback: function(btn
) {
995 run_backup_now(rec
.data
);
1001 var remove_btn
= Ext
.create('Proxmox.button.StdRemoveButton', {
1003 baseurl
: '/cluster/backup',
1004 callback: function() {
1009 var detail_btn
= new Proxmox
.button
.Button({
1010 text
: gettext('Detail'),
1012 tooltip
: gettext('Show job details and which guests and volumes are affected by the backup job'),
1014 handler
: run_detail
,
1017 var not_backed_warning
= Ext
.create('Ext.toolbar.TextItem', {
1018 html
: '<i class="fa fa-fw fa-exclamation-circle"></i>' + gettext('Some guests are not covered by any backup job.'),
1022 var not_backed_btn
= new Proxmox
.button
.Button({
1023 text
: gettext('Show'),
1025 handler
: run_show_not_backed
,
1028 Proxmox
.Utils
.monStoreErrors(me
, store
);
1034 stateId
: 'grid-dc-backup',
1040 text
: gettext('Add'),
1041 handler: function() {
1042 var win
= Ext
.create('PVE.dc.BackupEdit',{});
1043 win
.on('destroy', reload
);
1059 header
: gettext('Enabled'),
1061 dataIndex
: 'enabled',
1062 xtype
: 'checkcolumn',
1065 disabledCls
: 'x-item-enabled',
1066 stopSelection
: false
1069 header
: gettext('Node'),
1073 renderer: function(value
) {
1081 header
: gettext('Day of week'),
1085 renderer
: PVE
.Utils
.render_backup_days_of_week
1088 header
: gettext('Start Time'),
1091 dataIndex
: 'starttime'
1094 header
: gettext('Storage'),
1097 dataIndex
: 'storage'
1100 header
: gettext('Selection'),
1104 renderer
: PVE
.Utils
.render_backup_selection
1109 itemdblclick
: run_editor
1117 Ext
.define('pve-cluster-backup', {
1118 extend
: 'Ext.data.Model',
1120 'id', 'starttime', 'dow',
1121 'storage', 'node', 'vmid', 'exclude',
1122 'mailto', 'pool', 'compress', 'mode',
1123 { name
: 'enabled', type
: 'boolean' },
1124 { name
: 'all', type
: 'boolean' }