adapted from PBS. Main differences are:
    * API has GET/DELETE distinction instead of 'dry-run'
    * API expects a single property string for the prune options

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

Needs a dependency bump for proxmox-widget-toolkit.

Changes from v4:
    * Switch to widget toolkit's prune keep fields.
    * Don't load values from the storage initially. While it could be done,
      doing a manual prune doesn't need to have to do anything at all with the
      configuration on the storage. Problem is also that a clear trigger
      would reset to that value instead of clearing, which obviously makes
      sense when editing the storage configuration, but not really for
      doing a one-shot prune operation.
    * Use "renamed" as a reason instead of "strange name" for renamed backups

 www/manager6/Makefile              |   1 +
 www/manager6/storage/BackupView.js |  51 ++++++
 www/manager6/window/Prune.js       | 257 +++++++++++++++++++++++++++++
 3 files changed, 309 insertions(+)
 create mode 100644 www/manager6/window/Prune.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 9e6e56ef..85f90ecd 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -97,6 +97,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..632a1d36 100644
--- a/www/manager6/storage/BackupView.js
+++ b/www/manager6/storage/BackupView.js
@@ -24,6 +24,56 @@ Ext.define('PVE.storage.BackupView', {
            me.store.load();
        };
 
+       let pruneButton = Ext.create('Proxmox.button.Button', {
+           text: gettext('Prune group'),
+           disabled: true,
+           selModel: sm,
+           setBackupGroup: function(backup) {
+               if (backup) {
+                   let name = backup.text;
+                   let vmid = backup.vmid;
+                   let format = backup.format;
+
+                   let vmtype;
+                   if (name.startsWith('vzdump-lxc-') || format === "pbs-ct") {
+                       vmtype = 'lxc';
+                   } else if (name.startsWith('vzdump-qemu-') || format === 
"pbs-vm") {
+                       vmtype = 'qemu';
+                   }
+
+                   if (vmid && vmtype) {
+                       this.setText(gettext('Prune group') + ` 
${vmtype}/${vmid}`);
+                       this.vmid = vmid;
+                       this.vmtype = vmtype;
+                       this.setDisabled(false);
+                       return;
+                   }
+               }
+               this.setText(gettext('Prune group'));
+               this.vmid = null;
+               this.vmtype = null;
+               this.setDisabled(true);
+           },
+           handler: function(b, e, rec) {
+               let win = Ext.create('PVE.window.Prune', {
+                   nodename: nodename,
+                   storage: storage,
+                   backup_id: this.vmid,
+                   backup_type: this.vmtype,
+               });
+               win.show();
+               win.on('destroy', reload);
+           },
+       });
+
+       me.on('selectionchange', function(model, srecords, eOpts) {
+           if (srecords.length === 1) {
+               pruneButton.setBackupGroup(srecords[0].data);
+           } else {
+               pruneButton.setBackupGroup(null);
+           }
+       });
+
        me.tbar = [
            {
                xtype: 'proxmoxButton',
@@ -64,6 +114,7 @@ Ext.define('PVE.storage.BackupView', {
                    win.show();
                }
            },
+           pruneButton,
        ];
 
        me.callParent();
diff --git a/www/manager6/window/Prune.js b/www/manager6/window/Prune.js
new file mode 100644
index 00000000..f503773d
--- /dev/null
+++ b/www/manager6/window/Prune.js
@@ -0,0 +1,257 @@
+Ext.define('pve-prune-list', {
+    extend: 'Ext.data.Model',
+    fields: [
+       'type',
+       'vmid',
+       {
+           name: 'ctime',
+           type: 'date',
+           dateFormat: 'timestamp',
+       },
+    ],
+});
+
+Ext.define('PVE.PruneInputPanel', {
+    extend: 'Proxmox.panel.InputPanel',
+    alias: 'widget.pvePruneInputPanel',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onGetValues: function(values) {
+       let me = this;
+
+       // the API expects a single prune-backups property string
+       let pruneBackups = PVE.Parser.printPropertyString(values);
+       values = {
+           'prune-backups': pruneBackups,
+           'type': me.backup_type,
+           '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() {
+           let view = this.getView();
+
+           // helper to allow showing why a backup is kept
+           let addKeepReasons = function(backups, params) {
+               const rules = [
+                   'keep-last',
+                   'keep-hourly',
+                   'keep-daily',
+                   'keep-weekly',
+                   'keep-monthly',
+                   'keep-yearly',
+                   'keep-all', // when all keep options are not set
+               ];
+               let counter = {};
+
+               backups.sort(function(a, b) {
+                   return a.ctime < b.ctime;
+               });
+
+               let ruleIndex = -1;
+               let nextRule = function() {
+                   let rule;
+                   do {
+                       ruleIndex++;
+                       rule = rules[ruleIndex];
+                   } while (!params[rule] && rule !== 'keep-all');
+                   counter[rule] = 0;
+                   return rule;
+               };
+
+               let rule = nextRule();
+               for (let backup of backups) {
+                   if (backup.mark === 'keep') {
+                       counter[rule]++;
+                       if (rule !== 'keep-all') {
+                           backup.keepReason = rule + ': ' + counter[rule];
+                           if (counter[rule] >= params[rule]) {
+                               rule = nextRule();
+                           }
+                       } else {
+                           backup.keepReason = rule;
+                       }
+                   }
+               }
+           };
+
+           let params = view.getValues();
+           let keepParams = 
PVE.Parser.parsePropertyString(params["prune-backups"]);
+
+           Proxmox.Utils.API2Request({
+               url: view.url,
+               method: "GET",
+               params: params,
+               callback: function() {
+                   // for easy breakpoint setting
+               },
+               failure: function(response, opts) {
+                   Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+               },
+               success: function(response, options) {
+                   var data = response.result.data;
+                   addKeepReasons(data, keepParams);
+                   view.pruneStore.setData(data);
+               },
+           });
+       },
+
+       control: {
+           field: { change: 'reload' },
+       },
+    },
+
+    column1: [
+       {
+           xtype: 'pmxPruneKeepField',
+           name: 'keep-last',
+           fieldLabel: gettext('keep-last'),
+       },
+       {
+           xtype: 'pmxPruneKeepField',
+           name: 'keep-hourly',
+           fieldLabel: gettext('keep-hourly'),
+       },
+       {
+           xtype: 'pmxPruneKeepField',
+           name: 'keep-daily',
+           fieldLabel: gettext('keep-daily'),
+       },
+       {
+           xtype: 'pmxPruneKeepField',
+           name: 'keep-weekly',
+           fieldLabel: gettext('keep-weekly'),
+       },
+       {
+           xtype: 'pmxPruneKeepField',
+           name: 'keep-monthly',
+           fieldLabel: gettext('keep-monthly'),
+       },
+       {
+           xtype: 'pmxPruneKeepField',
+           name: 'keep-yearly',
+           fieldLabel: gettext('keep-yearly'),
+       },
+    ],
+
+    initComponent: function() {
+        var me = this;
+
+       me.pruneStore = Ext.create('Ext.data.Store', {
+           model: 'pve-prune-list',
+           sorters: { property: 'ctime', direction: 'DESC' },
+       });
+
+       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 (reason)',
+                       dataIndex: 'mark',
+                       renderer: function(value, metaData, record) {
+                           if (record.data.mark === 'keep') {
+                               return 'true (' + record.data.keepReason + ')';
+                           } else if (record.data.mark === 'protected') {
+                               return 'true (renamed)';
+                           } else {
+                               return 'false';
+                           }
+                       },
+                       flex: 1,
+                   },
+               ],
+           },
+       ];
+
+       me.callParent();
+    },
+});
+
+Ext.define('PVE.window.Prune', {
+    extend: 'Proxmox.window.Edit',
+
+    method: 'DELETE',
+    submitText: gettext("Prune"),
+
+    fieldDefaults: { labelWidth: 130 },
+
+    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_type !== 'qemu' && me.backup_type !== 'lxc') {
+           throw "unknown backup type: " + me.backup_type;
+       }
+       if (!me.backup_id) {
+           throw "no backup_id specified";
+       }
+
+       let title = Ext.String.format(
+           gettext("Prune Backups for '{0}' on Storage '{1}'"),
+           me.backup_type + '/' + me.backup_id,
+           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



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to