]> git.proxmox.com Git - pve-manager.git/commitdiff
gui: dc/backup: add new backup job detail view
authorAaron Lauterer <a.lauterer@proxmox.com>
Tue, 7 Jul 2020 09:49:00 +0000 (11:49 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Thu, 9 Jul 2020 12:12:59 +0000 (14:12 +0200)
The new detail view for backup jobs shows the settings similar to the
edit dialog but read only. Additionally it does show a list of all
included guests with their volumes and whether these volumes will be
included in the backup.

Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
www/manager6/Utils.js
www/manager6/dc/Backup.js

index 8295331abc391b7573e2e10eadca1044baa489d1..bf9ceda9ba4b4b4f68a92292809ea6a8ed90c459 100644 (file)
@@ -219,6 +219,31 @@ Ext.define('PVE.Utils', { utilities: {
 
     },
 
+    render_backup_status: function(value, meta, record) {
+       if (typeof value == 'undefined') {
+           return "";
+       }
+
+       let iconCls = 'check-circle good';
+       let text = gettext('Yes');
+
+       if (!PVE.Parser.parseBoolean(value.toString())) {
+           iconCls = 'times-circle critical';
+
+           text = gettext('No');
+
+           let reason = record.get('reason');
+           if (typeof reason !== 'undefined') {
+               if (reason in PVE.Utils.backup_reasons_table) {
+                   reason = PVE.Utils.backup_reasons_table[record.get('reason')];
+               }
+               text = `${text} - ${reason}`;
+           }
+       }
+
+       return `<i class="fa fa-${iconCls}"></i> ${text}`;
+    },
+
     render_backup_days_of_week: function(val) {
        var dows = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
        var selected = [];
@@ -279,6 +304,15 @@ Ext.define('PVE.Utils', { utilities: {
        return "-";
     },
 
+    backup_reasons_table: {
+       'backup=yes': gettext('Enabled'),
+       'backup=no': gettext('Disabled'),
+       'enabled': gettext('Enabled'),
+       'disabled': gettext('Disabled'),
+       'not a volume': gettext('Not a volume'),
+       'efidisk but no OMVF BIOS': gettext('EFI Disk without OMVF BIOS'),
+    },
+
     get_kvm_osinfo: function(value) {
        var info = { base: 'Other' }; // default
        if (value) {
index 1ef092c53a3e7a425aec553d422486004c2634f7..1e0705104aa32efb40a2c52ac7e934c3920c22dc 100644 (file)
@@ -375,6 +375,327 @@ Ext.define('PVE.dc.BackupEdit', {
 });
 
 
+Ext.define('PVE.dc.BackupDiskTree', {
+    extend: 'Ext.tree.Panel',
+    alias: 'widget.pveBackupDiskTree',
+
+    folderSort: true,
+    rootVisible: false,
+
+    store: {
+       sorters: 'id',
+       data: {},
+    },
+
+    tools: [
+       {
+           type: 'expand',
+           tooltip: gettext('Expand All'),
+           scope: this,
+           callback: function(panel) {
+               panel.expandAll();
+           },
+       },
+       {
+           type: 'collapse',
+           tooltip: gettext('Collapse All'),
+           scope: this,
+           callback: function(panel) {
+               panel.collapseAll();
+           }
+       },
+    ],
+
+    columns: [
+       {
+           xtype: 'treecolumn',
+           text: gettext('Guest Image'),
+           renderer: function(value, meta, record) {
+               if (record.data.type) {
+                   // guest level
+                   let ret = value;
+                   if (record.data.name) {
+                       ret += " (" + record.data.name + ")";
+                   }
+                   return ret;
+               } else {
+                   // volume level
+                   // extJS needs unique IDs but we only want to show the
+                   // volumes key from "vmid:key"
+                   return value.split(':')[1] + " - " + record.data.name;
+               }
+           },
+           dataIndex: 'id',
+           flex: 6,
+       },
+       {
+           text: gettext('Type'),
+           dataIndex: 'type',
+           flex: 1,
+       },
+       {
+           text: gettext('Backup Job'),
+           renderer: PVE.Utils.render_backup_status,
+           dataIndex: 'included',
+           flex: 3,
+       },
+    ],
+
+    reload: function() {
+       var me = this;
+       var sm = me.getSelectionModel();
+
+       Proxmox.Utils.API2Request({
+           url: "/cluster/backup/" + me.jobid + "/included_volumes",
+           waitMsgTarget: me,
+           method: 'GET',
+           failure: function(response, opts) {
+               Proxmox.Utils.setErrorMask(me, response.htmlStatus);
+           },
+           success: function(response, opts) {
+               sm.deselectAll();
+               me.setRootNode(response.result.data);
+               me.expandAll();
+           },
+       });
+    },
+
+    initComponent: function() {
+       var me = this;
+
+       if (!me.jobid) {
+           throw "no job id specified";
+       }
+
+       var sm = Ext.create('Ext.selection.TreeModel', {});
+
+       Ext.apply(me, {
+           selModel: sm,
+           fields: ['id', 'type',
+               {
+                   type: 'string',
+                   name: 'iconCls',
+                   calculate: function(data) {
+                       var txt = 'fa x-fa-tree fa-';
+                       if (data.leaf && !data.type) {
+                           return txt + 'hdd-o';
+                       } else if (data.type === 'qemu') {
+                           return txt + 'desktop';
+                       } else if (data.type === 'lxc') {
+                           return txt + 'cube';
+                       } else {
+                           return txt + 'question-circle';
+                       }
+                   }
+               }
+           ],
+           tbar: [
+               '->',
+               gettext('Search') + ':', ' ',
+               {
+                   xtype: 'textfield',
+                   width: 200,
+                   enableKeyEvents: true,
+                   listeners: {
+                       buffer: 500,
+                       keyup: function(field) {
+                           let searchValue = field.getValue();
+                           searchValue = searchValue.toLowerCase();
+
+                           me.store.clearFilter(true);
+                           me.store.filterBy(function(record) {
+                               let match = false;
+
+                               let data = '';
+                               if (record.data.depth == 0) {
+                                   return true;
+                               } else if (record.data.depth == 1) {
+                                   data = record.data;
+                               } else if (record.data.depth == 2) {
+                                   data = record.parentNode.data;
+                               }
+
+                               Ext.each(['name', 'id', 'type'], function(property) {
+                                   if (data[property] == null) {
+                                       return;
+                                   }
+
+                                   let v = data[property].toString();
+                                   if (v !== undefined) {
+                                       v = v.toLowerCase();
+                                       if (v.includes(searchValue)) {
+                                           match = true;
+                                           return;
+                                       }
+                                   }
+                               });
+                               return match;
+                           });
+                       }
+                   }
+               }
+           ],
+       });
+
+       me.callParent();
+
+       me.reload();
+    }
+});
+
+Ext.define('PVE.dc.BackupInfo', {
+    extend: 'Proxmox.panel.InputPanel',
+    alias: 'widget.pveBackupInfo',
+
+    padding: 10,
+
+    column1: [
+       {
+           name: 'node',
+           fieldLabel: gettext('Node'),
+           xtype: 'displayfield',
+           renderer: function (value) {
+               if (!value) {
+                   return '-- ' + gettext('All') + ' --';
+               } else {
+                   return value;
+               }
+           },
+       },
+       {
+           name: 'storage',
+           fieldLabel: gettext('Storage'),
+           xtype: 'displayfield',
+       },
+       {
+           name: 'dow',
+           fieldLabel: gettext('Day of week'),
+           xtype: 'displayfield',
+           renderer: PVE.Utils.render_backup_days_of_week,
+       },
+       {
+           name: 'starttime',
+           fieldLabel: gettext('Start Time'),
+           xtype: 'displayfield',
+       },
+       {
+           name: 'selMode',
+           fieldLabel: gettext('Selection mode'),
+           xtype: 'displayfield',
+       },
+       {
+           name: 'pool',
+           fieldLabel: gettext('Pool to backup'),
+           xtype: 'displayfield',
+       }
+    ],
+    column2: [
+       {
+           name: 'mailto',
+           fieldLabel: gettext('Send email to'),
+           xtype: 'displayfield',
+       },
+       {
+           name: 'mailnotification',
+           fieldLabel: gettext('Email notification'),
+           xtype: 'displayfield',
+           renderer: function (value) {
+               let msg;
+               switch (value) {
+                   case 'always':
+                       msg = gettext('Always');
+                       break;
+                   case 'failure':
+                       msg = gettext('On failure only');
+                       break;
+               }
+               return msg;
+           },
+       },
+       {
+           name: 'compress',
+           fieldLabel: gettext('Compression'),
+           xtype: 'displayfield',
+       },
+       {
+           name: 'mode',
+           fieldLabel: gettext('Mode'),
+           xtype: 'displayfield',
+           renderer: function (value) {
+               let msg;
+               switch (value) {
+                   case 'snapshot':
+                       msg = gettext('Snapshot');
+                       break;
+                   case 'suspend':
+                       msg = gettext('Suspend');
+                       break;
+                   case 'stop':
+                       msg = gettext('Stop');
+                       break;
+               }
+               return msg;
+           },
+       },
+       {
+           name: 'enabled',
+           fieldLabel: gettext('Enabled'),
+           xtype: 'displayfield',
+           renderer: function (value) {
+               if (PVE.Parser.parseBoolean(value.toString())) {
+                   return gettext('Yes');
+               } else {
+                   return gettext('No');
+               }
+           },
+       },
+    ],
+
+    setValues: function(values) {
+       var me = this;
+
+        Ext.iterate(values, function(fieldId, val) {
+           let field = me.query('[isFormField][name=' + fieldId + ']')[0];
+           if (field) {
+               field.setValue(val);
+            }
+       });
+
+       // selection Mode depends on the presence/absence of several keys
+       let selModeField = me.query('[isFormField][name=selMode]')[0];
+       let selMode = 'none';
+       if (values.vmid) {
+           selMode = gettext('Include selected VMs');
+       }
+       if (values.all) {
+           selMode = gettext('All');
+       }
+       if (values.exclude) {
+            selMode = gettext('Exclude selected VMs');
+       }
+       if (values.pool) {
+           selMode = gettext('Pool based');
+       }
+       selModeField.setValue(selMode);
+
+       if (!values.pool) {
+           let poolField = me.query('[isFormField][name=pool]')[0];
+           poolField.setVisible(0);
+       }
+    },
+
+    initComponent: function() {
+       var me = this;
+
+       if (!me.record) {
+           throw "no data provided";
+       }
+       me.callParent();
+
+       me.setValues(me.record);
+    }
+});
+
 Ext.define('PVE.dc.BackupView', {
     extend: 'Ext.grid.GridPanel',
 
@@ -414,6 +735,45 @@ Ext.define('PVE.dc.BackupView', {
            win.show();
        };
 
+       var run_detail = function() {
+           let record = sm.getSelection()[0]
+           if (!record) {
+               return;
+           }
+           var me = this;
+           var infoview = Ext.create('PVE.dc.BackupInfo', {
+               flex: 0,
+               layout: 'fit',
+               record: record.data,
+           });
+           var disktree = Ext.create('PVE.dc.BackupDiskTree', {
+               title: gettext('Included disks'),
+               flex: 1,
+               jobid: record.data.id,
+           });
+
+           var win = Ext.create('Ext.window.Window', {
+               modal: true,
+               width: 600,
+               height: 500,
+               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: [infoview, disktree],
+               }]
+           }).show();
+       };
+
        var run_backup_now = function(job) {
            job = Ext.clone(job);
 
@@ -514,6 +874,14 @@ Ext.define('PVE.dc.BackupView', {
            }
        });
 
+       var detail_btn = new Proxmox.button.Button({
+           text: gettext('Detail'),
+           disabled: true,
+           tooltip: gettext('Show job details and which guests and volumes are affected by the backup job'),
+           selModel: sm,
+           handler: run_detail,
+       });
+
        Proxmox.Utils.monStoreErrors(me, store);
 
        Ext.apply(me, {
@@ -536,8 +904,10 @@ Ext.define('PVE.dc.BackupView', {
                '-',
                remove_btn,
                edit_btn,
+               detail_btn,
+               '-',
+               run_btn,
                '-',
-               run_btn
            ],
            columns: [
                {