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 CFB16605FB for ; Wed, 2 Sep 2020 13:04:48 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 11EBD101F1 for ; Wed, 2 Sep 2020 13:03:56 +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 59D671012F for ; Wed, 2 Sep 2020 13:03:49 +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 22F08449C5 for ; Wed, 2 Sep 2020 13:03:49 +0200 (CEST) From: Fabian Ebner To: pve-devel@lists.proxmox.com Date: Wed, 2 Sep 2020 13:03:37 +0200 Message-Id: <20200902110337.25004-15-f.ebner@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20200902110337.25004-1-f.ebner@proxmox.com> References: <20200902110337.25004-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.042 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. [me.storage, x.storage] Subject: [pve-devel] [PATCH manager 14/14] 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, 02 Sep 2020 11:04:48 -0000 adapted from PBS. Main differences are: 1. keep-last defaults to 1 to avoid error on load 2. keep options are loaded from the storage configuration if there are any 3. API has GET/DELETE distinction instead of 'dry-run' 4. API expects a single property string for the prune options Signed-off-by: Fabian Ebner --- www/manager6/Makefile | 1 + www/manager6/storage/BackupView.js | 25 ++++ www/manager6/window/Prune.js | 230 +++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+) 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 8c1e2ed6..6bf26167 100644 --- a/www/manager6/storage/BackupView.js +++ b/www/manager6/storage/BackupView.js @@ -64,6 +64,31 @@ Ext.define('PVE.storage.BackupView', { win.show(); } }, + { + xtype: 'proxmoxButton', + text: gettext('Prune'), + selModel: sm, + disabled: true, + handler: function(b, e, rec) { + var backup_type; + if (PVE.Utils.volume_is_qemu_backup(rec.data.volid, rec.data.format)) { + backup_type = 'qemu'; + } else if (PVE.Utils.volume_is_lxc_backup(rec.data.volid, rec.data.format)) { + backup_type = 'lxc'; + } else { + return; + } + + var win = Ext.create('PVE.window.Prune', { + nodename: nodename, + storage: storage, + backup_id: rec.data.vmid, + backup_type: backup_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..4c90f87a --- /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.prune_store.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.prune_store = 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.prune_store, + 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 backup_type_str; + if (me.backup_type === 'qemu') { + backup_type_str = 'VM'; + } else if (me.backup_type === 'lxc') { + backup_type_str = 'CT'; + } else { + throw "unknown backup type"; + } + let backup_group_str = backup_type_str + '/' + me.backup_id; + let title = Ext.String.format( + gettext("Prune Backups for '{0}' on Storage '{1}'"), + backup_group_str, + 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