[pve-devel] [PATCH v3 manager 3/5] gui: dc/backup: add new backup job detail view
Aaron Lauterer
a.lauterer at proxmox.com
Thu May 7 13:57:27 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>
---
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
www/manager6/Utils.js | 32 ++++
www/manager6/dc/Backup.js | 351 +++++++++++++++++++++++++++++++++++++-
2 files changed, 382 insertions(+), 1 deletion(-)
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index f947cc89..50a0d84c 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 719b8996..d4ecf4a8 100644
--- a/www/manager6/dc/Backup.js
+++ b/www/manager6/dc/Backup.js
@@ -377,6 +377,308 @@ 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: {
+ permissionsForAll: 1,
+ },
+ formulas: {
+ hidePermissionsHint: (get) => get('permissionsForAll'),
+ }
+ },
+
+ dockedItems: [{
+ xtype: 'toolbar',
+ dock: 'bottom',
+ userCls: 'pmx-hint',
+ items: [
+ {
+ xtype: 'displayfield',
+ value: gettext("You don't have permission to see all guests."),
+ },
+ ],
+ bind: {
+ hidden: '{hidePermissionsHint}',
+ },
+ }],
+
+ 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) {
+ let viewModel = me.getViewModel();
+ viewModel.set('permissionsForAll', response.result.data['permissions_for_all']);
+
+ 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 +718,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 +855,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 +885,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