From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <f.ebner@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 6173E62621
 for <pve-devel@lists.proxmox.com>; Wed, 16 Sep 2020 14:51:34 +0200 (CEST)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
 by firstgate.proxmox.com (Proxmox) with ESMTP id A69742440C
 for <pve-devel@lists.proxmox.com>; Wed, 16 Sep 2020 14:51:03 +0200 (CEST)
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))
 (No client certificate requested)
 by firstgate.proxmox.com (Proxmox) with ESMTPS id 806EE24102
 for <pve-devel@lists.proxmox.com>; Wed, 16 Sep 2020 14:50:52 +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 46F35453CE
 for <pve-devel@lists.proxmox.com>; Wed, 16 Sep 2020 14:50:52 +0200 (CEST)
From: Fabian Ebner <f.ebner@proxmox.com>
To: pve-devel@lists.proxmox.com
Date: Wed, 16 Sep 2020 14:50:41 +0200
Message-Id: <20200916125041.4151-21-f.ebner@proxmox.com>
X-Mailer: git-send-email 2.20.1
In-Reply-To: <20200916125041.4151-1-f.ebner@proxmox.com>
References: <20200916125041.4151-1-f.ebner@proxmox.com>
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-SPAM-LEVEL: Spam detection results:  0
 AWL -0.053 Adjusted score from AWL reputation of From: address
 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. [x.storage, me.storage]
Subject: [pve-devel] [PATCH v2 manager 20/20] backup view: add prune window
X-BeenThere: pve-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Proxmox VE 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: Wed, 16 Sep 2020 12:51:34 -0000

adapted from PBS. Main differences are:
1. loading of the prune-backups options from the storage
configuration, when the window is opened
2. API has GET/DELETE distinction instead of 'dry-run'
3. API expects a single property string for the prune options

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
---

Changes from v1:
    * adapt to the new gropuing
    * add button to group header

 www/manager6/Makefile              |   1 +
 www/manager6/storage/BackupView.js |  34 ++++-
 www/manager6/window/Prune.js       | 230 +++++++++++++++++++++++++++++
 3 files changed, 264 insertions(+), 1 deletion(-)
 create mode 100644 www/manager6/window/Prune.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 4b9dcf58..b34bb41d 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -249,6 +249,7 @@ JSSRC= 							\
 	window/LoginWindow.js				\
 	window/Migrate.js				\
 	window/NotesEdit.js				\
+	window/Prune.js					\
 	window/Restore.js				\
 	window/SafeDestroy.js				\
 	window/Settings.js				\
diff --git a/www/manager6/storage/BackupView.js b/www/manager6/storage/BackupView.js
index ad377de6..9b243974 100644
--- a/www/manager6/storage/BackupView.js
+++ b/www/manager6/storage/BackupView.js
@@ -6,7 +6,19 @@ Ext.define('PVE.storage.BackupView', {
     features: [
 	{
 	    ftype: 'grouping',
-	    groupHeaderTpl: '{name} ({rows.length} Backup{[values.rows.length > 1 ? "s" : ""]})',
+	    groupHeaderTpl: new Ext.XTemplate(
+		'<tpl for=".">',
+		'{name} ({rows.length} Backup{[values.rows.length > 1 ? "s" : ""]})',
+		'<tpl if="name != \'other\'">',
+		' | ' + gettext('Prune') + ': ',
+		'<button id="prune-btn" ' +
+		    'class="x-btn x-unselectable x-btn-custom-groupheader" ' +
+		    'data-qtip="Prune">',
+		'<i id="prune-btn-icon" class="fa fa-scissors"></i>',
+		'</button>',
+		'</tpl>',
+		'</tpl>',
+	    ),
 	},
     ],
 
@@ -89,6 +101,26 @@ Ext.define('PVE.storage.BackupView', {
 	    },
 	});
 
+	Ext.apply(me, {
+	    listeners: {
+		groupclick: function (view, node, group, e, eOpts) {
+		    if (e.getTarget().id.startsWith('prune-btn')) {
+			view.features[0].expand(group); // keep group to be pruned expanded
+
+			let [ type, vmid ] = group.split('/');
+			var win = Ext.create('PVE.window.Prune', {
+			    nodename: nodename,
+			    storage: storage,
+			    backup_id: vmid,
+			    backup_type: type,
+			});
+			win.show();
+			win.on('destroy', reload);
+		    }
+		},
+	    },
+	});
+
 	me.callParent();
     },
 });
diff --git a/www/manager6/window/Prune.js b/www/manager6/window/Prune.js
new file mode 100644
index 00000000..44ac8594
--- /dev/null
+++ b/www/manager6/window/Prune.js
@@ -0,0 +1,230 @@
+Ext.define('pve-prune-list', {
+    extend: 'Ext.data.Model',
+    fields: [
+	'backup-type',
+	'backup-id',
+	{
+	    name: 'ctime',
+	    type: 'date',
+	    dateFormat: 'timestamp',
+	},
+    ],
+});
+
+Ext.define('PVE.PruneInputPanel', {
+    extend: 'Proxmox.panel.InputPanel',
+    alias: 'widget.pvePruneInputPanel',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onGetValues: function(values) {
+	var me = this;
+
+	// the API expects a single prune-backups property string
+	let prune_opts = PVE.Parser.printPropertyString(values);
+	values = {};
+	values["prune-backups"] = prune_opts;
+	values.type = me.backup_type;
+	values.vmid = me.backup_id;
+
+	return values;
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	init: function(view) {
+	    if (!view.url) {
+		throw "no url specified";
+	    }
+	    if (!view.backup_type) {
+		throw "no backup_type specified";
+	    }
+	    if (!view.backup_id) {
+		throw "no backup_id specified";
+	    }
+
+	    this.reload(); // initial load
+	},
+
+	reload: function() {
+	    var view = this.getView();
+
+	    let params = view.getValues();
+
+	    Proxmox.Utils.API2Request({
+		url: view.url,
+		method: "GET",
+		params: params,
+		callback: function() {
+		    return; // for easy breakpoint setting
+		},
+		failure: function(response, opts) {
+		    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+		},
+		success: function(response, options) {
+		    var data = response.result.data;
+		    view.pruneStore.setData(data);
+		},
+	    });
+	},
+
+	control: {
+	    field: { change: 'reload' },
+	},
+    },
+
+    column1: [
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'keep-last',
+	    allowBlank: true,
+	    fieldLabel: gettext('keep-last'),
+	    value: 1,
+	    minValue: 1,
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'keep-hourly',
+	    allowBlank: true,
+	    fieldLabel: gettext('keep-hourly'),
+	    minValue: 1,
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'keep-daily',
+	    allowBlank: true,
+	    fieldLabel: gettext('keep-daily'),
+	    minValue: 1,
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'keep-weekly',
+	    allowBlank: true,
+	    fieldLabel: gettext('keep-weekly'),
+	    minValue: 1,
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'keep-monthly',
+	    allowBlank: true,
+	    fieldLabel: gettext('keep-monthly'),
+	    minValue: 1,
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    name: 'keep-yearly',
+	    allowBlank: true,
+	    fieldLabel: gettext('keep-yearly'),
+	    minValue: 1,
+	},
+    ],
+
+    initComponent: function() {
+        var me = this;
+
+	me.pruneStore = Ext.create('Ext.data.Store', {
+	    model: 'pve-prune-list',
+	    sorters: { property: 'ctime', direction: 'DESC' },
+	});
+
+	Proxmox.Utils.API2Request({
+	    url: "/storage",
+	    method: 'GET',
+	    success: function(response, opts) {
+		let scfg = response.result.data.find(x => x.storage === me.storage);
+		if (!scfg || !scfg["prune-backups"]) {
+		    return;
+		}
+		let prune_opts = PVE.Parser.parsePropertyString(scfg["prune-backups"]);
+		me.setValues(prune_opts);
+	    },
+	});
+
+	me.column2 = [
+	    {
+		xtype: 'grid',
+		height: 200,
+		store: me.pruneStore,
+		columns: [
+		    {
+			header: gettext('Backup Time'),
+			sortable: true,
+			dataIndex: 'ctime',
+			renderer: function(value, metaData, record) {
+			    let text = Ext.Date.format(value, 'Y-m-d H:i:s');
+			    if (record.data.mark === 'remove') {
+				return '<div style="text-decoration: line-through;">'+ text +'</div>';
+			    } else {
+				return text;
+			    }
+			},
+			flex: 1,
+		    },
+		    {
+			text: "keep",
+			dataIndex: 'mark',
+		    },
+		],
+	    },
+	];
+
+	me.callParent();
+    },
+});
+
+Ext.define('PVE.window.Prune', {
+    extend: 'Proxmox.window.Edit',
+
+    method: 'DELETE',
+    submitText: gettext("Prune"),
+
+    isCreate: true,
+
+    initComponent: function() {
+        var me = this;
+
+	if (!me.nodename) {
+	    throw "no nodename specified";
+	}
+	if (!me.storage) {
+	    throw "no storage specified";
+	}
+	if (!me.backup_type) {
+	    throw "no backup_type specified";
+	}
+	if (!me.backup_id) {
+	    throw "no backup_id specified";
+	}
+
+	let backupGroupStr = me.backup_type + '/' + me.backup_id;
+
+	if (me.backup_type === 'CT') {
+	    me.backup_type = 'lxc';
+	} else if (me.backup_type === 'VM') {
+	    me.backup_type = 'qemu';
+	} else {
+	    throw "unknown backup type";
+	}
+	let title = Ext.String.format(
+	    gettext("Prune Backups for '{0}' on Storage '{1}'"),
+	    backupGroupStr,
+	    me.storage,
+	);
+
+	Ext.apply(me, {
+	    url: '/api2/extjs/nodes/' + me.nodename + '/storage/' + me.storage + "/prunebackups",
+	    title: title,
+	    items: [
+		{
+		    xtype: 'pvePruneInputPanel',
+		    url: '/api2/extjs/nodes/' + me.nodename + '/storage/' + me.storage + "/prunebackups",
+		    backup_type: me.backup_type,
+		    backup_id: me.backup_id,
+		    storage: me.storage,
+		},
+	    ],
+	});
+
+	me.callParent();
+    },
+});
-- 
2.20.1