]> git.proxmox.com Git - pve-manager.git/commitdiff
ui: storage backup view: add prune window
authorFabian Ebner <f.ebner@proxmox.com>
Tue, 24 Nov 2020 13:00:53 +0000 (14:00 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Tue, 24 Nov 2020 13:13:20 +0000 (14:13 +0100)
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.ebner@proxmox.com>
www/manager6/Makefile
www/manager6/storage/BackupView.js
www/manager6/window/Prune.js [new file with mode: 0644]

index 9e6e56efc62419179527196a0106ec4b71dd9425..85f90ecd3f9b1311bd5980786a2c410e09edde1c 100644 (file)
@@ -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                              \
index 8c1e2ed66ee5ac60a32a881b33101609cb5f9503..632a1d362a741972cfb72dc5ec1af4af51610d97 100644 (file)
@@ -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 (file)
index 0000000..f503773
--- /dev/null
@@ -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();
+    },
+});