[pve-devel] [RFC v2 manager 3/5] gui: dc/backup: add new backup job detail view

Aaron Lauterer a.lauterer at proxmox.com
Mon Apr 6 16:24:24 CEST 2020


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 at proxmox.com>
---

v1->v2:
* made render_backup_status more generic
* reworked the reasons why a volume might not be included. turns out we
   cannot easily determine if a volume is in-/excluded by default or
   because the user actively wanted it


 www/manager6/Utils.js     |  32 ++++
 www/manager6/dc/Backup.js | 350 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 381 insertions(+), 1 deletion(-)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 96b6e550..28816cd4 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -220,6 +220,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 = [];
@@ -281,6 +306,13 @@ Ext.define('PVE.Utils', { utilities: {
 	return "-";
     },
 
+    backup_reasons_table: {
+	'enabled': gettext('Enabled'),
+	'disabled': gettext('Disabled'),
+	'not a volume': gettext('Not a volume'),
+	'efidisk with non omvf bios': gettext('EFI Disk without OMVF BIOS'),
+    },
+
     get_kvm_osinfo: function(value) {
 	var info = { base: 'Other' }; // default
 	if (value) {
diff --git a/www/manager6/dc/Backup.js b/www/manager6/dc/Backup.js
index f7792002..f2a28ce7 100644
--- a/www/manager6/dc/Backup.js
+++ b/www/manager6/dc/Backup.js
@@ -377,6 +377,307 @@ 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: {},
+    },
+
+    viewModel: {
+	data: {
+	    missingPermissions: 0,
+	},
+	formulas: {
+	    showPermissionsHint: (get) => get('missingPermissions'),
+	}
+    },
+
+    dockedItems: [{
+	xtype: 'toolbar',
+	dock: 'bottom',
+	userCls: 'pmx-hint',
+	items: [
+	    {
+		xtype: 'displayfield',
+		value: gettext("You don't have permission to see all guests."),
+	    },
+	],
+	bind: {
+	    hidden: '{!showPermissionsHint}',
+	},
+    }],
+
+    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
+		    // split unique ID "vmid:key" to get only the disks 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) {
+		let viewModel = me.getViewModel();
+		viewModel.set('missingPermissions', response.result.data['not_all_permissions']);
+
+		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';
+			}
+		    }
+		}
+	    ],
+	});
+
+	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.exclude) {
+	     selMode = gettext('Exclude selected VMs');
+	}
+	if (values.vmid) {
+	    selMode = gettext('Include selected VMs');
+	}
+	if (values.all) {
+	    selMode = gettext('All');
+	}
+	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',
 
@@ -416,6 +717,43 @@ Ext.define('PVE.dc.BackupView', {
 	    win.show();
 	};
 
+	var run_detail = function() {
+	    let rec = sm.getSelection()[0]
+	    if (!rec) {
+		return;
+	    }
+	    var me = this;
+	    var infoview = Ext.create('PVE.dc.BackupInfo', {
+		flex: 0,
+		layout: 'fit',
+		record: rec.data,
+	    });
+	    var disktree = Ext.create('PVE.dc.BackupDiskTree', {
+		title: gettext('Included disks'),
+		flex: 1,
+		jobid: rec.data.id,
+	    });
+
+	    var win = Ext.create('Ext.window.Window', {
+		modal: true,
+		width: 600,
+		height: 500,
+		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);
 
@@ -516,6 +854,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, {
@@ -538,8 +884,10 @@ Ext.define('PVE.dc.BackupView', {
 		'-',
 		remove_btn,
 		edit_btn,
+		detail_btn,
+		'-',
+		run_btn,
 		'-',
-		run_btn
 	    ],
 	    columns: [
 		{
-- 
2.20.1





More information about the pve-devel mailing list