From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: 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 ; 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 ; 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 ; 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 ; Wed, 16 Sep 2020 14:50:52 +0200 (CEST) From: Fabian Ebner 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 List-Unsubscribe: , List-Archive: List-Post: List-Help: List-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 --- 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( + '', + '{name} ({rows.length} Backup{[values.rows.length > 1 ? "s" : ""]})', + '', + ' | ' + gettext('Prune') + ': ', + '', + '', + '', + ), }, ], @@ -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 '
'+ text +'
'; + } 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