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 5B5C893462 for ; Mon, 5 Feb 2024 13:00:48 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4BEC916258 for ; Mon, 5 Feb 2024 13:00:17 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (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 for ; Mon, 5 Feb 2024 13:00:15 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 41818442D7 for ; Mon, 5 Feb 2024 13:00:12 +0100 (CET) From: Stefan Lendl To: pbs-devel@lists.proxmox.com Date: Mon, 5 Feb 2024 12:58:25 +0100 Message-ID: <20240205115830.523721-4-s.lendl@proxmox.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240205115830.523721-2-s.lendl@proxmox.com> References: <20240205115830.523721-2-s.lendl@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.038 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pbs-devel] [PATCH proxmox-backup 2/8] gc: global prune and gc job view X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 05 Feb 2024 12:00:48 -0000 In the global datastore view, extend the prune view to display gc job status as a table. Use the same widget in the local view and dispaly gc job status as a single row. The local PruneAndGC view is parameterized (cbind) with the datastore. At initialization the only row is selected. This allows the rest of the grid to act on selected rows and it requires far less special casing if the datastore is set on the view or not. Having a single row always selected and therefore highlighted, is visually not appealing. Therefore, highlighting of selected rows is disabled in the local view. Moved GCView to different file and enhanced it with last, next run, status and duration. Added button to show task log. Changed `render_task_status()` to also take in account upids stored in other 'columns'. Originally-by: Gabriel Goller Signed-off-by: Stefan Lendl --- www/Makefile | 2 + www/Utils.js | 12 +- www/config/GCView.js | 207 +++++++++++++++++++++++++++++++++ www/datastore/DataStoreList.js | 4 +- www/datastore/Panel.js | 1 - www/datastore/PruneAndGC.js | 101 ++-------------- www/window/GCJobEdit.js | 28 +++++ 7 files changed, 255 insertions(+), 100 deletions(-) create mode 100644 www/config/GCView.js create mode 100644 www/window/GCJobEdit.js diff --git a/www/Makefile b/www/Makefile index c2755ac8..03263588 100644 --- a/www/Makefile +++ b/www/Makefile @@ -63,6 +63,7 @@ JSSRC= \ config/SyncView.js \ config/VerifyView.js \ config/PruneView.js \ + config/GCView.js \ config/WebauthnView.js \ config/CertificateView.js \ config/NodeOptionView.js \ @@ -79,6 +80,7 @@ JSSRC= \ window/NotifyOptions.js \ window/SyncJobEdit.js \ window/PruneJobEdit.js \ + window/GCJobEdit.js \ window/UserEdit.js \ window/UserPassword.js \ window/Settings.js \ diff --git a/www/Utils.js b/www/Utils.js index 5357949b..16c91358 100644 --- a/www/Utils.js +++ b/www/Utils.js @@ -199,14 +199,14 @@ Ext.define('PBS.Utils', { return fingerprint.substring(0, 23); }, - render_task_status: function(value, metadata, record) { - if (!record.data['last-run-upid']) { - return '-'; + render_task_status: function(value, metadata, record, rowIndex, colIndex, store) { + if (!record.data['last-run-upid'] && !store.getById('last-run-upid')?.data.value) { + return '-'; } - if (!record.data['last-run-endtime']) { - metadata.tdCls = 'x-grid-row-loading'; - return ''; + if (!record.data['last-run-endtime'] && !store.getById('last-run-endtime')?.data.value) { + metadata.tdCls = 'x-grid-row-loading'; + return ''; } let parsed = Proxmox.Utils.parse_task_status(value); diff --git a/www/config/GCView.js b/www/config/GCView.js new file mode 100644 index 00000000..09e2932b --- /dev/null +++ b/www/config/GCView.js @@ -0,0 +1,207 @@ +Ext.define('pbs-gc-jobs-status', { + extend: 'Ext.data.Model', + fields: [ + 'store', 'last-run-upid', 'removed-chunks', 'pending-chunks', 'schedule', + 'next-run', 'last-run-endtime', 'last-run-state', + { + name: 'duration', + calculate: function(data) { + let endtime = data['last-run-endtime']; + if (!endtime) return undefined; + let task = Proxmox.Utils.parse_task_upid(data['last-run-upid']); + return endtime - task.starttime; + }, + }, + ], + idProperty: 'store', + proxy: { + type: 'proxmox', + url: '/api2/json/admin/gc', + }, +}); + +Ext.define('PBS.config.GCJobView', { + extend: 'Ext.grid.GridPanel', + alias: 'widget.pbsGCJobView', + + stateful: true, + stateId: 'grid-gc-jobs-v1', + allowDeselect: false, + + title: gettext('Garbage Collect Jobs'), + + controller: { + xclass: 'Ext.app.ViewController', + + init: function(view) { + let params = {}; + let store = view.getStore(); + let proxy = store.rstore.getProxy() + if (view.datastore) { + params.store = view.datastore; + + // after the store is loaded, select the row to enable the Edit,.. buttons + store.rstore.proxy.on({ + 'afterload': { + fn: () => view.getSelectionModel().select(0), + single: true + } + }); + + // do not highlight the selected row + view.items.items[0].selectedItemCls = ''; + view.items.items[0].overItemCls = ''; + } + proxy.setExtraParams(params); + Proxmox.Utils.monStoreErrors(view, store.rstore); + }, + + getDatastoreName: function() { + return this.getView().getSelection()[0]?.data.store; + }, + + getData: function() { + let view = this.getView(); + let datastore = this.getDatastoreName(); + return view.getStore().getById(datastore).data; + }, + + editGCJob: function() { + let data = this.getData(); + Ext.create('PBS.window.GCJobEdit', { + datastore: data.store, + id: data.store, + schedule: data.schedule, + listeners: { + destroy: () => this.reload(), + }, + }).show(); + }, + + garbageCollect: function() { + let datastore = this.getDatastoreName(); + Proxmox.Utils.API2Request({ + url: `/admin/datastore/${datastore}/gc`, + method: 'POST', + failure: function(response) { + Ext.Msg.alert(gettext('Error'), response.htmlStatus); + }, + success: function(response, options) { + Ext.create('Proxmox.window.TaskViewer', { + upid: response.result.data, + }).show(); + }, + }); + }, + + showTaskLog: function() { + let me = this; + + let upid = this.getData()['last-run-upid']; + if (!upid) return; + + Ext.create('Proxmox.window.TaskViewer', { upid }).show(); + }, + + startStore: function() { this.getView().getStore().rstore.startUpdate(); }, + stopStore: function() { this.getView().getStore().rstore.stopUpdate(); }, + reload: function() { this.getView().getStore().rstore.load(); }, + + }, + + listeners: { + activate: 'startStore', + destroy: 'stopStore', + deactivate: 'stopStore', + itemdblclick: 'editGCJob', + }, + + store: { + type: 'diff', + autoDestroy: true, + autoDestroyRstore: true, + sorters: 'store', + rstore: { + type: 'update', + storeid: 'pbs-gc-jobs-status', + model: 'pbs-gc-jobs-status', + interval: 5000, + }, + }, + + tbar: [ + { + xtype: 'proxmoxButton', + text: gettext('Edit'), + handler: 'editGCJob', + enableFn: (rec) => !!rec, + disabled: true, + }, + '-', + { + xtype: 'proxmoxButton', + text: gettext('Show Log'), + handler: 'showTaskLog', + enableFn: (rec) => !!rec.data["last-run-upid"], + disabled: true, + }, + { + xtype: 'proxmoxButton', + text: gettext('Run now'), + handler: 'garbageCollect', + enableFn: (rec) => !!rec, + disabled: true, + }, + ], + + columns: [ + { + header: gettext('Datastore'), + dataIndex: 'store', + renderer: Ext.String.htmlEncode, + width: 120, + sortable: true, + hideable: false, + }, + { + header: gettext('Schedule'), + dataIndex: 'schedule', + maxWidth: 220, + minWidth: 80, + flex: 1, + sortable: false, + hideable: false, + renderer: (value) => value ? value : Proxmox.Utils.NoneText, + }, + { + header: gettext('Last GC'), + dataIndex: 'last-run-endtime', + renderer: PBS.Utils.render_optional_timestamp, + minWidth: 150, + sortable: true, + }, + { + text: gettext('Duration'), + dataIndex: 'duration', + renderer: Proxmox.Utils.render_duration, + sortable: false, + width: 80, + }, + { + header: gettext('Last Status'), + dataIndex: 'last-run-state', + renderer: PBS.Utils.render_task_status, + sortable: true, + flex: 3, + maxWidth: 100, + minWidth: 80, + }, + { + header: gettext('Next Run'), + dataIndex: 'next-run', + renderer: PBS.Utils.render_next_task_run, + width: 150, + sortable: true, + }, + ], +}); diff --git a/www/datastore/DataStoreList.js b/www/datastore/DataStoreList.js index b496bcbc..a4b77dbf 100644 --- a/www/datastore/DataStoreList.js +++ b/www/datastore/DataStoreList.js @@ -239,8 +239,8 @@ Ext.define('PBS.datastore.DataStores', { }, { iconCls: 'fa fa-trash-o', - itemId: 'prunejobs', - xtype: 'pbsPruneJobView', + itemId: 'prunegc', + xtype: 'pbsDatastorePruneAndGC', }, { iconCls: 'fa fa-check-circle', diff --git a/www/datastore/Panel.js b/www/datastore/Panel.js index fd1b4611..0fc97d14 100644 --- a/www/datastore/Panel.js +++ b/www/datastore/Panel.js @@ -58,7 +58,6 @@ Ext.define('PBS.DataStorePanel', { }, }, { - title: gettext('Prune & GC'), xtype: 'pbsDatastorePruneAndGC', itemId: 'prunegc', iconCls: 'fa fa-trash-o', diff --git a/www/datastore/PruneAndGC.js b/www/datastore/PruneAndGC.js index aab98dad..33ca0108 100644 --- a/www/datastore/PruneAndGC.js +++ b/www/datastore/PruneAndGC.js @@ -1,91 +1,8 @@ -Ext.define('PBS.Datastore.GCOptions', { - extend: 'Proxmox.grid.ObjectGrid', - alias: 'widget.pbsDatastoreGCOpts', - mixins: ['Proxmox.Mixin.CBind'], - - onlineHelp: 'maintenance_pruning', - - cbindData: function(initial) { - let me = this; - - me.datastore = encodeURIComponent(me.datastore); - me.url = `/api2/json/config/datastore/${me.datastore}`; - me.editorConfig = { - url: `/api2/extjs/config/datastore/${me.datastore}`, - }; - return {}; - }, - - controller: { - xclass: 'Ext.app.ViewController', - - edit: function() { this.getView().run_editor(); }, - - garbageCollect: function() { - let me = this; - let view = me.getView(); - Proxmox.Utils.API2Request({ - url: `/admin/datastore/${view.datastore}/gc`, - method: 'POST', - failure: function(response) { - Ext.Msg.alert(gettext('Error'), response.htmlStatus); - }, - success: function(response, options) { - Ext.create('Proxmox.window.TaskViewer', { - upid: response.result.data, - }).show(); - }, - }); - }, - }, - - tbar: [ - { - xtype: 'proxmoxButton', - text: gettext('Edit'), - disabled: true, - handler: 'edit', - }, - '-', - { - xtype: 'proxmoxButton', - text: gettext('Start Garbage Collection'), - selModel: null, - handler: 'garbageCollect', - }, - ], - - listeners: { - activate: function() { this.rstore.startUpdate(); }, - destroy: function() { this.rstore.stopUpdate(); }, - deactivate: function() { this.rstore.stopUpdate(); }, - itemdblclick: 'edit', - }, - - rows: { - "gc-schedule": { - required: true, - defaultValue: Proxmox.Utils.NoneText, - header: gettext('Garbage Collection Schedule'), - editor: { - xtype: 'proxmoxWindowEdit', - title: gettext('GC Schedule'), - onlineHelp: 'maintenance_gc', - items: { - xtype: 'pbsCalendarEvent', - name: 'gc-schedule', - fieldLabel: gettext("GC Schedule"), - emptyText: Proxmox.Utils.noneText, - deleteEmpty: true, - }, - }, - }, - }, -}); - Ext.define('PBS.Datastore.PruneAndGC', { extend: 'Ext.panel.Panel', alias: 'widget.pbsDatastorePruneAndGC', + title: gettext('Prune & GC Jobs'), + mixins: ['Proxmox.Mixin.CBind'], layout: { @@ -99,9 +16,8 @@ Ext.define('PBS.Datastore.PruneAndGC', { }, items: [ { - xtype: 'pbsDatastoreGCOpts', - title: gettext('Garbage Collection'), - itemId: 'datastore-gc', + xtype: 'pbsGCJobView', + itemId: 'gcjobs', nodename: 'localhost', cbind: { datastore: '{datastore}', @@ -110,9 +26,7 @@ Ext.define('PBS.Datastore.PruneAndGC', { { xtype: 'pbsPruneJobView', nodename: 'localhost', - itemId: 'datastore-prune-jobs', - flex: 1, - minHeight: 200, + itemId: 'prunejobs', cbind: { datastore: '{datastore}', }, @@ -130,4 +44,9 @@ Ext.define('PBS.Datastore.PruneAndGC', { component.relayEvents(me, ['activate', 'deactivate', 'destroy']); } }, + + cbindData: function(initalConfig) { + let me = this; + me.datastore = initalConfig.datastore ? initalConfig.datastore : undefined; + }, }); diff --git a/www/window/GCJobEdit.js b/www/window/GCJobEdit.js new file mode 100644 index 00000000..de74bf5c --- /dev/null +++ b/www/window/GCJobEdit.js @@ -0,0 +1,28 @@ +Ext.define('PBS.window.GCJobEdit', { + extend: 'Proxmox.window.Edit', + alias: 'widget.pbsGCJobEdit', + mixins: ['Proxmox.Mixin.CBind'], + + userid: undefined, + onlineHelp: 'maintenance_gc', + isAdd: false, + + subject: gettext('Garbage Collect Schedule'), + + cbindData: function(initial) { + let me = this; + + me.datastore = encodeURIComponent(me.datastore); + me.url = `/api2/extjs/config/datastore/${me.datastore}`; + me.method = 'PUT'; + me.autoLoad = true; + return {}; + }, + + items: { + xtype: 'pbsCalendarEvent', + name: 'gc-schedule', + fieldLabel: gettext("GC Schedule"), + emptyText: gettext(Proxmox.Utils.NoneText + " (disabled)"), + }, +}); -- 2.43.0