all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Aaron Lauterer <a.lauterer@proxmox.com>
To: pve-devel@pve.proxmox.com
Subject: [pve-devel] [PATCH v4 manager 3/5] gui: dc/backup: add new backup job detail view
Date: Tue,  7 Jul 2020 11:49:00 +0200	[thread overview]
Message-ID: <20200707094902.24712-4-a.lauterer@proxmox.com> (raw)
In-Reply-To: <20200707094902.24712-1-a.lauterer@proxmox.com>

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>
---
v3 -> v4:
* added search box
* removed "not all permissions" notification as we don't show such a
  notifcation naywhere else. makes dealing with the API data esier
* fixed if clauses to show the correct selection mode. `exclude vmids`
  was overruled by `all` in previous version.
* added more returned reasons to the backup_reasons_table. The qemu side
  got new return values in the latest patches [0]


v2->v3:
* made the comment why we need to split the "vmid:key" ID disks
 more descriptive
* changed double negative for permissions `not_all_permissions` to
  `permissions_for_all`

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

   [0] https://pve.proxmox.com/pipermail/pve-devel/2020-June/044052.html

 www/manager6/Utils.js     |  34 ++++
 www/manager6/dc/Backup.js | 372 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 405 insertions(+), 1 deletion(-)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 158b370b..91e6238b 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -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) {
diff --git a/www/manager6/dc/Backup.js b/www/manager6/dc/Backup.js
index 1ef092c5..1e070510 100644
--- a/www/manager6/dc/Backup.js
+++ b/www/manager6/dc/Backup.js
@@ -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: [
 		{
-- 
2.20.1





  parent reply	other threads:[~2020-07-07  9:56 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-07-07  9:48 [pve-devel] [PATCH v4 manager 0/5] add backup detail and not backed up view Aaron Lauterer
2020-07-07  9:48 ` [pve-devel] [PATCH v4 manager 1/5] api: backup: add endpoint to list included guests and volumes Aaron Lauterer
2020-07-08  7:11   ` Thomas Lamprecht
2020-07-08  7:21     ` Aaron Lauterer
2020-07-07  9:48 ` [pve-devel] [PATCH v4 manager 2/5] gui: dc/backup: move renderers to Utils.js Aaron Lauterer
2020-07-07  9:49 ` Aaron Lauterer [this message]
2020-07-07  9:49 ` [pve-devel] [PATCH v4 manager 4/5] fix #2609 api: backupinfo: add non job specific endpoint Aaron Lauterer
2020-07-07  9:49 ` [pve-devel] [PATCH v4 manager 5/5] fix #2609 gui: backup: add window for not backed guests Aaron Lauterer
2020-07-09 18:06 ` [pve-devel] applied-series: Re: [PATCH v4 manager 0/5] add backup detail and not backed up view Thomas Lamprecht

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200707094902.24712-4-a.lauterer@proxmox.com \
    --to=a.lauterer@proxmox.com \
    --cc=pve-devel@pve.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal