]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/dc/Backup.js
ui: dc/Backup: never show id input field, autogenerate id
[pve-manager.git] / www / manager6 / dc / Backup.js
index 34052746334e5c6ed097c39b610cfee1b266fb6f..3f250a76329809c941d9b76104c6b2aa0736763e 100644 (file)
@@ -4,42 +4,39 @@ Ext.define('PVE.dc.BackupEdit', {
 
     defaultFocus: undefined,
 
-    initComponent : function() {
-         var me = this;
+    initComponent: function() {
+       let me = this;
 
-        me.isCreate = !me.jobid;
-
-       var url;
-       var method;
+       me.isCreate = !me.jobid;
 
+       let url, method;
        if (me.isCreate) {
-            url = '/api2/extjs/cluster/backup';
-            method = 'POST';
-        } else {
-            url = '/api2/extjs/cluster/backup/' + me.jobid;
-            method = 'PUT';
-        }
-
-       var vmidField = Ext.create('Ext.form.field.Hidden', {
-           name: 'vmid'
+           url = '/api2/extjs/cluster/backup';
+           method = 'POST';
+       } else {
+           url = '/api2/extjs/cluster/backup/' + me.jobid;
+           method = 'PUT';
+       }
+
+       let vmidField = Ext.create('Ext.form.field.Hidden', {
+           name: 'vmid',
        });
 
-       /*jslint confusion: true*/
        // 'value' can be assigned a string or an array
-       var selModeField =  Ext.create('Proxmox.form.KVComboBox', {
+       let selModeField = Ext.create('Proxmox.form.KVComboBox', {
            xtype: 'proxmoxKVComboBox',
            comboItems: [
                ['include', gettext('Include selected VMs')],
                ['all', gettext('All')],
                ['exclude', gettext('Exclude selected VMs')],
-               ['pool', gettext('Pool')]
+               ['pool', gettext('Pool based')],
            ],
            fieldLabel: gettext('Selection mode'),
            name: 'selMode',
-           value: ''
+           value: '',
        });
 
-       var sm = Ext.create('Ext.selection.CheckboxModel', {
+       let sm = Ext.create('Ext.selection.CheckboxModel', {
            mode: 'SIMPLE',
            listeners: {
                selectionchange: function(model, selected) {
@@ -53,27 +50,41 @@ Ext.define('PVE.dc.BackupEdit', {
                    vmidField.suspendEvent('change');
                    vmidField.setValue(sel);
                    vmidField.resumeEvent('change');
-               }
-           }
+               },
+           },
        });
 
-       var storagesel = Ext.create('PVE.form.StorageSelector', {
+       let storagesel = Ext.create('PVE.form.StorageSelector', {
            fieldLabel: gettext('Storage'),
-           nodename: 'localhost',
+           clusterView: true,
            storageContent: 'backup',
            allowBlank: false,
-           name: 'storage'
+           name: 'storage',
+           listeners: {
+               change: function(f, v) {
+                   let store = f.getStore();
+                   let rec = store.findRecord('storage', v, 0, false, true, true);
+                   let compressionSelector = me.down('pveCompressionSelector');
+
+                   if (rec && rec.data && rec.data.type === 'pbs') {
+                       compressionSelector.setValue('zstd');
+                       compressionSelector.setDisabled(true);
+                   } else if (!compressionSelector.getEditable()) {
+                       compressionSelector.setDisabled(false);
+                   }
+               },
+           },
        });
 
-       var store = new Ext.data.Store({
+       let store = new Ext.data.Store({
            model: 'PVEResources',
            sorters: {
                property: 'vmid',
-               order: 'ASC'
-           }
+               order: 'ASC',
+           },
        });
 
-       var vmgrid = Ext.createWidget('grid', {
+       let vmgrid = Ext.createWidget('grid', {
            store: store,
            border: true,
            height: 300,
@@ -83,11 +94,11 @@ Ext.define('PVE.dc.BackupEdit', {
                {
                    header: 'ID',
                    dataIndex: 'vmid',
-                   width: 60
+                   width: 60,
                },
                {
                    header: gettext('Node'),
-                   dataIndex: 'node'
+                   dataIndex: 'node',
                },
                {
                    header: gettext('Status'),
@@ -98,21 +109,21 @@ Ext.define('PVE.dc.BackupEdit', {
                        } else {
                            return Proxmox.Utils.stoppedText;
                        }
-                   }
+                   },
                },
                {
                    header: gettext('Name'),
                    dataIndex: 'name',
-                   flex: 1
+                   flex: 1,
                },
                {
                    header: gettext('Type'),
-                   dataIndex: 'type'
-               }
-           ]
+                   dataIndex: 'type',
+               },
+           ],
        });
 
-       var selectPoolMembers = function(poolid) {
+       let selectPoolMembers = function(poolid) {
            if (!poolid) {
                return;
            }
@@ -121,25 +132,25 @@ Ext.define('PVE.dc.BackupEdit', {
                {
                    id: 'poolFilter',
                    property: 'pool',
-                   value: poolid
-               }
+                   value: poolid,
+               },
            ]);
            sm.selectAll(true);
        };
 
-       var selPool = Ext.create('PVE.form.PoolSelector', {
-           fieldLabel: gettext('Pool'),
+       let selPool = Ext.create('PVE.form.PoolSelector', {
+           fieldLabel: gettext('Pool to backup'),
            hidden: true,
            allowBlank: true,
            name: 'pool',
            listeners: {
-               change: function( selpool, newValue, oldValue) {
+               change: function(selpool, newValue, oldValue) {
                    selectPoolMembers(newValue);
-               }
-           }
+               },
+           },
        });
 
-       var nodesel = Ext.create('PVE.form.NodeSelector', {
+       let nodesel = Ext.create('PVE.form.NodeSelector', {
            name: 'node',
            fieldLabel: gettext('Node'),
            allowBlank: true,
@@ -148,72 +159,60 @@ Ext.define('PVE.dc.BackupEdit', {
            emptyText: '-- ' + gettext('All') + ' --',
            listeners: {
                change: function(f, value) {
-                   storagesel.setNodename(value || 'localhost');
-                   var mode = selModeField.getValue();
+                   storagesel.setNodename(value);
+                   let mode = selModeField.getValue();
                    store.clearFilter();
                    store.filterBy(function(rec) {
-                       return (!value || rec.get('node') === value);
+                       return !value || rec.get('node') === value;
                    });
                    if (mode === 'all') {
                        sm.selectAll(true);
                    }
-
                    if (mode === 'pool') {
                        selectPoolMembers(selPool.value);
                    }
-               }
-           }
+               },
+           },
        });
 
-       var column1 = [
+       let column1 = [
            nodesel,
            storagesel,
            {
-               xtype: 'pveDayOfWeekSelector',
-               name: 'dow',
-               fieldLabel: gettext('Day of week'),
-               multiSelect: true,
-               value: ['sat'],
-               allowBlank: false
-           },
-           {
-               xtype: 'timefield',
-               fieldLabel: gettext('Start Time'),
-               name: 'starttime',
-               format: 'H:i',
-               formatText: 'HH:MM',
-               value: '00:00',
-               allowBlank: false
+               xtype: 'pveCalendarEvent',
+               fieldLabel: gettext('Schedule'),
+               allowBlank: false,
+               name: 'schedule',
            },
            selModeField,
-           selPool
+           selPool,
        ];
 
-       var column2 = [
+       let column2 = [
            {
                xtype: 'textfield',
                fieldLabel: gettext('Send email to'),
-               name: 'mailto'
+               name: 'mailto',
            },
            {
                xtype: 'pveEmailNotificationSelector',
-               fieldLabel: gettext('Email notification'),
+               fieldLabel: gettext('Email'),
                name: 'mailnotification',
-               deleteEmpty: me.isCreate ? false : true,
-               value: me.isCreate ? 'always' : ''
+               deleteEmpty: !me.isCreate,
+               value: me.isCreate ? 'always' : '',
            },
            {
                xtype: 'pveCompressionSelector',
                fieldLabel: gettext('Compression'),
                name: 'compress',
-               deleteEmpty: me.isCreate ? false : true,
-               value: 'lzo'
+               deleteEmpty: !me.isCreate,
+               value: 'zstd',
            },
            {
                xtype: 'pveBackupModeSelector',
                fieldLabel: gettext('Mode'),
                value: 'snapshot',
-               name: 'mode'
+               name: 'mode',
            },
            {
                xtype: 'proxmoxcheckbox',
@@ -221,16 +220,23 @@ Ext.define('PVE.dc.BackupEdit', {
                name: 'enabled',
                uncheckedValue: 0,
                defaultValue: 1,
-               checked: true
+               checked: true,
            },
-           vmidField
+           vmidField,
        ];
-       /*jslint confusion: false*/
 
-       var ipanel = Ext.create('Proxmox.panel.InputPanel', {
+       let ipanel = Ext.create('Proxmox.panel.InputPanel', {
            onlineHelp: 'chapter_vzdump',
            column1: column1,
-           column2:  column2,
+           column2: column2,
+           columnB: [
+               {
+                   xtype: 'proxmoxtextfield',
+                   name: 'comment',
+                   fieldLabel: gettext('Comment'),
+                   deleteEmpty: !me.isCreate,
+               },
+           ],
            onGetValues: function(values) {
                if (!values.node) {
                    if (!me.isCreate) {
@@ -239,7 +245,11 @@ Ext.define('PVE.dc.BackupEdit', {
                    delete values.node;
                }
 
-               var selMode = values.selMode;
+               if (!values.id && me.isCreate) {
+                   values.id = 'backup-' + Ext.data.identifier.Uuid.Global.generate().slice(0, 13);
+               }
+
+               let selMode = values.selMode;
                delete values.selMode;
 
                if (selMode === 'all') {
@@ -258,15 +268,15 @@ Ext.define('PVE.dc.BackupEdit', {
                    delete values.pool;
                }
                return values;
-           }
+           },
        });
 
-       var update_vmid_selection = function(list, mode) {
+       let update_vmid_selection = function(list, mode) {
            if (mode !== 'all' && mode !== 'pool') {
                sm.deselectAll(true);
                if (list) {
                    Ext.Array.each(list.split(','), function(vmid) {
-                       var rec = store.findRecord('vmid', vmid);
+                       var rec = store.findRecord('vmid', vmid, 0, false, true, true);
                        if (rec) {
                            sm.select(rec, true);
                        }
@@ -276,7 +286,7 @@ Ext.define('PVE.dc.BackupEdit', {
        };
 
        vmidField.on('change', function(f, value) {
-           var mode = selModeField.getValue();
+           let mode = selModeField.getValue();
            update_vmid_selection(value, mode);
        });
 
@@ -303,54 +313,85 @@ Ext.define('PVE.dc.BackupEdit', {
                selPool.setVisible(true);
                selPool.allowBlank = false;
                selectPoolMembers(selPool.value);
-
            } else {
                selPool.setVisible(false);
                selPool.allowBlank = true;
            }
-           var list = vmidField.getValue();
+           let list = vmidField.getValue();
            update_vmid_selection(list, value);
        });
 
-       var reload = function() {
+       let reload = function() {
            store.load({
-               params: { type: 'vm' },
+               params: {
+                   type: 'vm',
+               },
                callback: function() {
-                   var node = nodesel.getValue();
+                   let node = nodesel.getValue();
                    store.clearFilter();
-                   store.filterBy(function(rec) {
-                       return (!node || node.length === 0 || rec.get('node') === node);
-                   });
-                   var list = vmidField.getValue();
-                   var mode = selModeField.getValue();
+                   store.filterBy(rec => !node || node.length === 0 || rec.get('node') === node);
+                   let list = vmidField.getValue();
+                   let mode = selModeField.getValue();
                    if (mode === 'all') {
                        sm.selectAll(true);
-                   } else if (mode === 'pool'){
+                   } else if (mode === 'pool') {
                        selectPoolMembers(selPool.value);
                    } else {
                        update_vmid_selection(list, mode);
                    }
-               }
+               },
            });
        };
 
-        Ext.applyIf(me, {
-            subject: gettext("Backup Job"),
-            url: url,
-            method: method,
-           items: [ ipanel, vmgrid ]
-        });
+       Ext.applyIf(me, {
+           subject: gettext("Backup Job"),
+           url: url,
+           method: method,
+           bodyPadding: 0,
+           items: [
+               {
+                   xtype: 'tabpanel',
+                   region: 'center',
+                   layout: 'fit',
+                   bodyPadding: 10,
+                   items: [
+                       {
+                           xtype: 'container',
+                           title: gettext('General'),
+                           region: 'center',
+                           layout: {
+                               type: 'vbox',
+                               align: 'stretch',
+                           },
+                           items: [
+                               ipanel,
+                               vmgrid,
+                           ],
+                       },
+                       {
+                           xtype: 'pveBackupJobPrunePanel',
+                           title: gettext('Retention'),
+                           isCreate: me.isCreate,
+                           keepAllDefaultForCreate: false,
+                           showPBSHint: false,
+                           fallbackHintHtml: gettext('Without any keep option, the storage\'s configuration or node\'s vzdump.conf is used as fallback'),
+                       },
+                   ],
+               },
+           ],
+
+       });
 
-        me.callParent();
+       me.callParent();
 
-        if (me.isCreate) {
+       if (me.isCreate) {
            selModeField.setValue('include');
        } else {
             me.load({
                success: function(response, options) {
-                   var data = response.result.data;
+                   let data = response.result.data;
 
-                   data.dow = data.dow.split(',');
+                   data.dow = (data.dow || '').split(',');
 
                    if (data.all || data.exclude) {
                        if (data.exclude) {
@@ -367,16 +408,27 @@ Ext.define('PVE.dc.BackupEdit', {
                        data.selMode = 'include';
                    }
 
+                   if (data['prune-backups']) {
+                       Object.assign(data, data['prune-backups']);
+                       delete data['prune-backups'];
+                   } else if (data.maxfiles !== undefined) {
+                       if (data.maxfiles > 0) {
+                           data['keep-last'] = data.maxfiles;
+                       } else {
+                           data['keep-all'] = 1;
+                       }
+                       delete data.maxfiles;
+                   }
+
                    me.setValues(data);
-               }
-            });
-        }
+               },
+           });
+       }
 
        reload();
-    }
+    },
 });
 
-
 Ext.define('PVE.dc.BackupView', {
     extend: 'Ext.grid.GridPanel',
 
@@ -385,43 +437,217 @@ Ext.define('PVE.dc.BackupView', {
     onlineHelp: 'chapter_vzdump',
 
     allText: '-- ' + gettext('All') + ' --',
-    allExceptText: gettext('All except {0}'),
 
-    initComponent : function() {
-       var me = this;
+    initComponent: function() {
+       let me = this;
 
-       var store = new Ext.data.Store({
+       let store = new Ext.data.Store({
            model: 'pve-cluster-backup',
            proxy: {
-                type: 'proxmox',
-               url: "/api2/json/cluster/backup"
-           }
+               type: 'proxmox',
+               url: "/api2/json/cluster/backup",
+           },
+       });
+
+       let not_backed_store = new Ext.data.Store({
+           sorters: 'vmid',
+           proxy: {
+               type: 'proxmox',
+               url: 'api2/json/cluster/backup-info/not-backed-up',
+           },
        });
 
-       var reload = function() {
+       let noBackupJobWarning, noBackupJobInfoButton;
+       let reload = function() {
            store.load();
+           not_backed_store.load({
+               callback: function(records, operation, success) {
+                   noBackupJobWarning.setVisible(records.length > 0);
+                   noBackupJobInfoButton.setVisible(records.length > 0);
+               },
+           });
        };
 
-       var sm = Ext.create('Ext.selection.RowModel', {});
+       let sm = Ext.create('Ext.selection.RowModel', {});
 
-       var run_editor = function() {
-           var rec = sm.getSelection()[0];
+       let run_editor = function() {
+           let rec = sm.getSelection()[0];
            if (!rec) {
                return;
            }
 
-            var win = Ext.create('PVE.dc.BackupEdit',{
-                jobid: rec.data.id
-            });
-            win.on('destroy', reload);
-            win.show();
+           let win = Ext.create('PVE.dc.BackupEdit', {
+               jobid: rec.data.id,
+           });
+           win.on('destroy', reload);
+           win.show();
+       };
+
+       let run_detail = function() {
+           let record = sm.getSelection()[0];
+           if (!record) {
+               return;
+           }
+           Ext.create('Ext.window.Window', {
+               modal: true,
+               width: 800,
+               height: 600,
+               stateful: true,
+               stateId: 'backup-detail-view',
+               resizable: true,
+               layout: 'fit',
+               title: gettext('Backup Details'),
+               items: [
+                   {
+                       xtype: 'panel',
+                       region: 'center',
+                       layout: {
+                           type: 'vbox',
+                           align: 'stretch',
+                       },
+                       items: [
+                           {
+                               xtype: 'pveBackupInfo',
+                               flex: 0,
+                               layout: 'fit',
+                               record: record.data,
+                           },
+                           {
+                               xtype: 'pveBackupDiskTree',
+                               title: gettext('Included disks'),
+                               flex: 1,
+                               jobid: record.data.id,
+                           },
+                       ],
+                   },
+               ],
+           }).show();
+       };
+
+       let run_backup_now = function(job) {
+           job = Ext.clone(job);
+
+           let jobNode = job.node;
+           // Remove properties related to scheduling
+           delete job.enabled;
+           delete job.starttime;
+           delete job.dow;
+           delete job.id;
+           delete job.schedule;
+           delete job.type;
+           delete job.node;
+           job.all = job.all === true ? 1 : 0;
+
+           if (job['prune-backups']) {
+               job['prune-backups'] = PVE.Parser.printPropertyString(job['prune-backups']);
+           }
+
+           let allNodes = PVE.data.ResourceStore.getNodes();
+           let nodes = allNodes.filter(node => node.status === 'online').map(node => node.node);
+           let errors = [];
+
+           if (jobNode !== undefined) {
+               if (!nodes.includes(jobNode)) {
+                   Ext.Msg.alert('Error', "Node '"+ jobNode +"' from backup job isn't online!");
+                   return;
+               }
+               nodes = [jobNode];
+           } else {
+               let unkownNodes = allNodes.filter(node => node.status !== 'online');
+               if (unkownNodes.length > 0) {errors.push(unkownNodes.map(node => node.node + ": " + gettext("Node is offline")));}
+           }
+           let jobTotalCount = nodes.length, jobsStarted = 0;
+
+           Ext.Msg.show({
+               title: gettext('Please wait...'),
+               closable: false,
+               progress: true,
+               progressText: '0/' + jobTotalCount,
+           });
+
+           let postRequest = function() {
+               jobsStarted++;
+               Ext.Msg.updateProgress(jobsStarted / jobTotalCount, jobsStarted + '/' + jobTotalCount);
+
+               if (jobsStarted === jobTotalCount) {
+                   Ext.Msg.hide();
+                   if (errors.length > 0) {
+                       Ext.Msg.alert('Error', 'Some errors have been encountered:<br />' + errors.join('<br />'));
+                   }
+               }
+           };
+
+           nodes.forEach(node => Proxmox.Utils.API2Request({
+               url: '/nodes/' + node + '/vzdump',
+               method: 'POST',
+               params: job,
+               failure: function(response, opts) {
+                   errors.push(node + ': ' + response.htmlStatus);
+                   postRequest();
+               },
+               success: postRequest,
+           }));
+       };
+
+       let run_show_not_backed = function() {
+           Ext.create('Ext.window.Window', {
+               modal: true,
+               width: 600,
+               height: 500,
+               resizable: true,
+               layout: 'fit',
+               title: gettext('Guests without backup job'),
+               items: [
+                   {
+                       xtype: 'panel',
+                       region: 'center',
+                       layout: {
+                           type: 'vbox',
+                           align: 'stretch',
+                       },
+                       items: [
+                           {
+                               xtype: 'pveBackedGuests',
+                               flex: 1,
+                               layout: 'fit',
+                               store: not_backed_store,
+                           },
+                       ],
+                   },
+               ],
+           }).show();
        };
 
        var edit_btn = new Proxmox.button.Button({
            text: gettext('Edit'),
            disabled: true,
            selModel: sm,
-           handler: run_editor
+           handler: run_editor,
+       });
+
+       var run_btn = new Proxmox.button.Button({
+           text: gettext('Run now'),
+           disabled: true,
+           selModel: sm,
+           handler: function() {
+               var rec = sm.getSelection()[0];
+               if (!rec) {
+                   return;
+               }
+
+               Ext.Msg.show({
+                   title: gettext('Confirm'),
+                   icon: Ext.Msg.QUESTION,
+                   msg: gettext('Start the selected backup job now?'),
+                   buttons: Ext.Msg.YESNO,
+                   callback: function(btn) {
+                       if (btn !== 'yes') {
+                           return;
+                       }
+                       run_backup_now(rec.data);
+                   },
+               });
+           },
        });
 
        var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
@@ -429,7 +655,26 @@ Ext.define('PVE.dc.BackupView', {
            baseurl: '/cluster/backup',
            callback: function() {
                reload();
-           }
+           },
+       });
+
+       var detail_btn = new Proxmox.button.Button({
+           text: gettext('Job Detail'),
+           disabled: true,
+           tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'),
+           selModel: sm,
+           handler: run_detail,
+       });
+
+       noBackupJobWarning = Ext.create('Ext.toolbar.TextItem', {
+           html: '<i class="fa fa-fw fa-exclamation-circle"></i>' + gettext('Some guests are not covered by any backup job.'),
+           hidden: true,
+       });
+
+       noBackupJobInfoButton = new Proxmox.button.Button({
+           text: gettext('Show'),
+           hidden: true,
+           handler: run_show_not_backed,
        });
 
        Proxmox.Utils.monStoreErrors(me, store);
@@ -440,19 +685,26 @@ Ext.define('PVE.dc.BackupView', {
            stateful: true,
            stateId: 'grid-dc-backup',
            viewConfig: {
-               trackOver: false
+               trackOver: false,
            },
            tbar: [
                {
                    text: gettext('Add'),
                    handler: function() {
-                       var win = Ext.create('PVE.dc.BackupEdit',{});
+                       var win = Ext.create('PVE.dc.BackupEdit', {});
                        win.on('destroy', reload);
                        win.show();
-                   }
+                   },
                },
+               '-',
                remove_btn,
-               edit_btn
+               edit_btn,
+               detail_btn,
+               '-',
+               run_btn,
+               '->',
+               noBackupJobWarning,
+               noBackupJobInfoButton,
            ],
            columns: [
                {
@@ -463,7 +715,12 @@ Ext.define('PVE.dc.BackupView', {
                    sortable: true,
                    disabled: true,
                    disabledCls: 'x-item-enabled',
-                   stopSelection: false
+                   stopSelection: false,
+               },
+               {
+                   header: gettext('ID'),
+                   dataIndex: 'id',
+                   hidden: true,
                },
                {
                    header: gettext('Node'),
@@ -475,112 +732,58 @@ Ext.define('PVE.dc.BackupView', {
                            return value;
                        }
                        return me.allText;
-                   }
+                   },
                },
                {
-                   header: gettext('Day of week'),
-                   width: 200,
-                   sortable: false,
-                   dataIndex: 'dow',
-                   renderer: function(val) {
-                       var dows = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
-                       var selected = [];
-                       var cur = -1;
-                       val.split(',').forEach(function(day){
-                           cur++;
-                           var dow = (dows.indexOf(day)+6)%7;
-                           if (cur === dow) {
-                               if (selected.length === 0 || selected[selected.length-1] === 0) {
-                                   selected.push(1);
-                               } else {
-                                   selected[selected.length-1]++;
-                               }
-                           } else {
-                               while (cur < dow) {
-                                   cur++;
-                                   selected.push(0);
-                               }
-                               selected.push(1);
-                           }
-                       });
-
-                       cur = -1;
-                       var days = [];
-                       selected.forEach(function(item) {
-                           cur++;
-                           if (item > 2) {
-                               days.push(Ext.Date.dayNames[(cur+1)] + '-' + Ext.Date.dayNames[(cur+item)%7]);
-                               cur += item-1;
-                           } else if (item == 2) {
-                               days.push(Ext.Date.dayNames[cur+1]);
-                               days.push(Ext.Date.dayNames[(cur+2)%7]);
-                               cur++;
-                           } else if (item == 1) {
-                               days.push(Ext.Date.dayNames[(cur+1)%7]);
-                           }
-                       });
-                       return days.join(', ');
-                   }
-               },
-               {
-                   header: gettext('Start Time'),
-                   width: 60,
-                   sortable: true,
-                   dataIndex: 'starttime'
+                   header: gettext('Schedule'),
+                   width: 150,
+                   dataIndex: 'schedule',
                },
                {
                    header: gettext('Storage'),
                    width: 100,
                    sortable: true,
-                   dataIndex: 'storage'
+                   dataIndex: 'storage',
                },
                {
-                   header: gettext('Selection'),
+                   header: gettext('Comment'),
+                   dataIndex: 'comment',
+                   renderer: Ext.htmlEncode,
                    flex: 1,
+               },
+               {
+                   header: gettext('Selection'),
+                   flex: 2,
                    sortable: false,
                    dataIndex: 'vmid',
-                   renderer: function(value, metaData, record) {
-                       /*jslint confusion: true */
-                       if (record.data.all) {
-                           if (record.data.exclude) {
-                               return Ext.String.format(me.allExceptText, record.data.exclude);
-                           }
-                           return me.allText;
-                       }
-                       if (record.data.vmid) {
-                           return record.data.vmid;
-                       }
-
-                       if (record.data.pool) {
-                           return record.data.pool;
-                       }
-
-                       return "-";
-                   }
-               }
+                   renderer: PVE.Utils.render_backup_selection,
+               },
            ],
            listeners: {
                activate: reload,
-               itemdblclick: run_editor
-           }
+               itemdblclick: run_editor,
+           },
        });
 
        me.callParent();
-    }
+    },
 }, function() {
-
     Ext.define('pve-cluster-backup', {
        extend: 'Ext.data.Model',
        fields: [
-           'id', 'starttime', 'dow',
-           'storage', 'node', 'vmid', 'exclude',
-           'mailto', 'pool',
+           'id',
+           'compress',
+           'dow',
+           'exclude',
+           'mailto',
+           'mode',
+           'node',
+           'pool',
+           'starttime',
+           'storage',
+           'vmid',
            { name: 'enabled', type: 'boolean' },
            { name: 'all', type: 'boolean' },
-           { name: 'snapshot', type: 'boolean' },
-           { name: 'stop', type: 'boolean' },
-           { name: 'suspend', type: 'boolean' },
-           { name: 'compress', type: 'boolean' }
-       ]
+       ],
     });
 });