From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <a.lauterer@proxmox.com>
Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits))
 (No client certificate requested)
 by lists.proxmox.com (Postfix) with ESMTPS id 1982F5B4F7
 for <pve-devel@pve.proxmox.com>; Tue,  7 Jul 2020 11:56:23 +0200 (CEST)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
 by firstgate.proxmox.com (Proxmox) with ESMTP id E26CE263AD
 for <pve-devel@pve.proxmox.com>; Tue,  7 Jul 2020 11:56:22 +0200 (CEST)
Received-SPF: pass (proxmox.com: 212.186.127.180 is authorized to use
 'a.lauterer@proxmox.com' in 'mfrom' identity (mechanism 'mx' matched))
 receiver=firstgate.proxmox.com; identity=mailfrom;
 envelope-from="a.lauterer@proxmox.com"; helo=proxmox-new.maurer-it.com;
 client-ip=212.186.127.180
Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com
 [212.186.127.180])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256)
 (No client certificate requested)
 by firstgate.proxmox.com (Proxmox) with ESMTPS id B992726388
 for <pve-devel@pve.proxmox.com>; Tue,  7 Jul 2020 11:56:19 +0200 (CEST)
Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1])
 by proxmox-new.maurer-it.com (Proxmox) with ESMTP id D99CA44497
 for <pve-devel@pve.proxmox.com>; Tue,  7 Jul 2020 11:49:03 +0200 (CEST)
From: Aaron Lauterer <a.lauterer@proxmox.com>
To: pve-devel@pve.proxmox.com
Date: Tue,  7 Jul 2020 11:49:02 +0200
Message-Id: <20200707094902.24712-6-a.lauterer@proxmox.com>
X-Mailer: git-send-email 2.20.1
In-Reply-To: <20200707094902.24712-1-a.lauterer@proxmox.com>
References: <20200707094902.24712-1-a.lauterer@proxmox.com>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-SPAM-LEVEL: Spam detection results:  0
 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment
 RCVD_IN_DNSWL_MED        -2.3 Sender listed at https://www.dnswl.org/,
 medium trust
 SPF_HELO_NONE           0.001 SPF: HELO does not publish an SPF Record
 SPF_PASS               -0.001 SPF: sender matches SPF record
 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See
 http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more
 information. [record.data]
X-Mailman-Approved-At: Tue, 07 Jul 2020 12:27:43 +0200
Subject: [pve-devel] [PATCH v4 manager 5/5] fix #2609 gui: backup: add
 window for not backed guests
X-BeenThere: pve-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: PVE development discussion <pve-devel.lists.proxmox.com>
List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=unsubscribe>
List-Archive: <http://lists.proxmox.com/pipermail/pve-devel/>
List-Post: <mailto:pve-devel@lists.proxmox.com>
List-Help: <mailto:pve-devel-request@lists.proxmox.com?subject=help>
List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=subscribe>
X-List-Received-Date: Tue, 07 Jul 2020 09:56:23 -0000

In case that there are guests which are not covered by any backup job, a
notification is shown and a window with a grid can be opened to view
these guests.

Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
---
v2 -> v4: (v3 was skipped to align version number to the rest of the
	   series)
* changed from having a dedicated `Summary` button to showing a
  notification if there are guests not covered by any job
* API data is now fetched in the main backup grid and pushed to the
  window showing the not covered guests
* code for the search box has been simplified

v1->v2:
* renamed ostore to cold_store
* changed double negative for permissions `not_all_permissions` to
  `permissions_for_all`


 www/manager6/dc/Backup.js | 145 +++++++++++++++++++++++++++++++++++++-
 1 file changed, 144 insertions(+), 1 deletion(-)

diff --git a/www/manager6/dc/Backup.js b/www/manager6/dc/Backup.js
index 1e070510..1c0b5c57 100644
--- a/www/manager6/dc/Backup.js
+++ b/www/manager6/dc/Backup.js
@@ -696,6 +696,89 @@ Ext.define('PVE.dc.BackupInfo', {
     }
 });
 
+
+Ext.define('PVE.dc.BackedGuests', {
+    extend: 'Ext.grid.GridPanel',
+    alias: 'widget.pveBackedGuests',
+
+    textfilter: '',
+
+    columns: [
+	{
+	    header: gettext('Type'),
+	    dataIndex: "type",
+	    renderer: PVE.Utils.render_resource_type,
+	    flex: 1,
+	    sortable: true,
+	},
+	{
+	    header: gettext('VMID'),
+	    dataIndex: 'vmid',
+	    flex: 1,
+	    sortable: true,
+	},
+	{
+	    header: gettext('Name'),
+	    dataIndex: 'name',
+	    flex: 2,
+	    sortable: true,
+	},
+    ],
+
+    initComponent: function() {
+	let me = this;
+
+	me.store.clearFilter(true);
+
+	Ext.apply(me, {
+	    stateful: true,
+	    stateId: 'grid-dc-backed-guests',
+	    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;
+
+				Ext.each(['name', 'vmid', 'type'], function(property) {
+				    if (record.data[property] == null) {
+					return;
+				    }
+
+				    let v = record.data[property].toString();
+				    if (v !== undefined) {
+					v = v.toLowerCase();
+					if (v.includes(searchValue)) {
+					    match = true;
+					    return;
+					}
+				    }
+				});
+				return match;
+			    });
+			}
+		    }
+		}
+	    ],
+	    viewConfig: {
+		stripeRows: true,
+		trackOver: false,
+            },
+	});
+	me.callParent();
+    },
+});
+
 Ext.define('PVE.dc.BackupView', {
     extend: 'Ext.grid.GridPanel',
 
@@ -716,8 +799,27 @@ Ext.define('PVE.dc.BackupView', {
 	    }
 	});
 
+	var not_backed_store = new Ext.data.Store({
+	    sorters: 'vmid',
+	    proxy:{
+		type: 'proxmox',
+		url: 'api2/json/cluster/backupinfo/not_backed_up',
+	    },
+	});
+
 	var reload = function() {
 	    store.load();
+	    not_backed_store.load({
+		callback: function(records, operation, success) {
+		    if (records.length) {
+			not_backed_warning.setVisible(true);
+			not_backed_btn.setVisible(true);
+		    } else {
+			not_backed_warning.setVisible(false);
+			not_backed_btn.setVisible(false);
+		    }
+		},
+	    });
 	};
 
 	var sm = Ext.create('Ext.selection.RowModel', {});
@@ -834,6 +936,34 @@ Ext.define('PVE.dc.BackupView', {
 	    }));
 	};
 
+	var run_show_not_backed = function() {
+	    var me = this;
+	    var backedinfo = Ext.create('PVE.dc.BackedGuests', {
+		flex: 1,
+		layout: 'fit',
+		store: not_backed_store,
+	    });
+
+	    var win = 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: [backedinfo],
+		}]
+	    }).show();
+	};
+
 	var edit_btn = new Proxmox.button.Button({
 	    text: gettext('Edit'),
 	    disabled: true,
@@ -882,6 +1012,17 @@ Ext.define('PVE.dc.BackupView', {
 	    handler: run_detail,
 	});
 
+	var not_backed_warning = 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,
+	});
+
+	var not_backed_btn = new Proxmox.button.Button({
+	    text: gettext('Show'),
+	    hidden: true,
+	    handler: run_show_not_backed,
+	});
+
 	Proxmox.Utils.monStoreErrors(me, store);
 
 	Ext.apply(me, {
@@ -907,7 +1048,9 @@ Ext.define('PVE.dc.BackupView', {
 		detail_btn,
 		'-',
 		run_btn,
-		'-',
+		'->',
+		not_backed_warning,
+		not_backed_btn,
 	    ],
 	    columns: [
 		{
-- 
2.20.1