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) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id C5CB2DC95 for ; Fri, 22 Sep 2023 11:13:21 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B00FC8622 for ; Fri, 22 Sep 2023 11:13:21 +0200 (CEST) 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) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Fri, 22 Sep 2023 11:13:19 +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 D51F5487EE for ; Fri, 22 Sep 2023 11:13:18 +0200 (CEST) From: Stefan Lendl To: pbs-devel@lists.proxmox.com References: <87o7huwq21.fsf@gmail.com> <85e6743a-57ca-a662-847f-c102c66bccc7@proxmox.com> Date: Fri, 22 Sep 2023 11:13:15 +0200 Message-ID: <874jjmwoyc.fsf@gmail.com> MIME-Version: 1.0 Content-Type: text/plain X-SPAM-LEVEL: Spam detection results: 0 AWL -0.400 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_ASCII_DIVIDERS 0.8 Email that uses ascii formatting dividers and possible spam tricks 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [datastore.rs, proxmox.com, record.data, rec.id] Subject: [pbs-devel] [Stefan Lendl] Re: [PATCH proxmox-backup v3] close #4723: updated gc view in the ui 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: Fri, 22 Sep 2023 09:13:21 -0000 Handling of GC tasks that never ran works great now. GUI alignment looks good. > Updated the GC overview in the datastore page. This enables > us to see: > - schedule > - State (of last run) > - Duration (of last run) > - Last Run > - Next Run > - Pending Chunks (of last run) > - Removed Chunks (of last run) > > Added `ObjectGrid` for GCView, moved to different file. Added > endpoint `gc_info` that returns all the necessary config and > last run stats. > > Signed-off-by: Gabriel Goller > --- > > update v3: > - ui: removed `required` attribute on items to get the sorting right > - made `pending_chunks` and `removed_chunks` options, so that they > are not shown when no gc run exists > > update v2: > - skip serializing if value is `None` > - return just the schedule if `upid` doesn't exist (means no gc has been > run) > - ui: removed default values on timestamps > - ui: removed flex and minHeight properties > > pbs-api-types/src/datastore.rs | 38 +++++++++++ > src/api2/admin/datastore.rs | 98 +++++++++++++++++++++++++--- > www/Makefile | 1 + > www/Utils.js | 12 ++-- > www/config/GCView.js | 116 +++++++++++++++++++++++++++++++++ > www/datastore/PruneAndGC.js | 91 +------------------------- > 6 files changed, 253 insertions(+), 103 deletions(-) > mode change 100644 => 100755 src/api2/admin/datastore.rs > create mode 100644 www/config/GCView.js > > diff --git a/pbs-api-types/src/datastore.rs b/pbs-api-types/src/datastore.rs > index 73c4890e..55caa963 100644 > --- a/pbs-api-types/src/datastore.rs > +++ b/pbs-api-types/src/datastore.rs > @@ -1250,6 +1250,44 @@ pub struct GarbageCollectionStatus { > pub still_bad: usize, > } > > +#[api( > + properties: { > + "last-run-upid": { > + optional: true, > + type: UPID, > + }, > + }, > +)] > +#[derive(Clone, Default, Serialize, Deserialize, PartialEq)] > +#[serde(rename_all = "kebab-case")] > +/// Garbage Collection general info > +pub struct GarbageCollectionInfo { > + /// upid of the last run gc job > + #[serde(skip_serializing_if = "Option::is_none")] > + pub last_run_upid: Option, > + /// Number of removed chunks > + #[serde(skip_serializing_if = "Option::is_none")] > + pub removed_chunks: Option, > + /// Number of pending chunks > + #[serde(skip_serializing_if = "Option::is_none")] > + pub pending_chunks: Option, > + /// Schedule of the gc job > + #[serde(skip_serializing_if = "Option::is_none")] > + pub schedule: Option, > + /// Time of the next gc run > + #[serde(skip_serializing_if = "Option::is_none")] > + pub next_run: Option, > + /// Endtime of the last gc run > + #[serde(skip_serializing_if = "Option::is_none")] > + pub last_run_endtime: Option, > + /// State of the last gc run > + #[serde(skip_serializing_if = "Option::is_none")] > + pub last_run_state: Option, > + /// Duration of last gc run > + #[serde(skip_serializing_if = "Option::is_none")] > + pub duration: Option, > +} > + > #[api( > properties: { > "gc-status": { > diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs > old mode 100644 > new mode 100755 > index a95031e7..1127bd95 > --- a/src/api2/admin/datastore.rs > +++ b/src/api2/admin/datastore.rs > @@ -10,6 +10,7 @@ use anyhow::{bail, format_err, Error}; > use futures::*; > use hyper::http::request::Parts; > use hyper::{header, Body, Response, StatusCode}; > +use proxmox_time::CalendarEvent; > use serde::Deserialize; > use serde_json::{json, Value}; > use tokio_stream::wrappers::ReceiverStream; > @@ -33,13 +34,14 @@ use pxar::EntryKind; > > use pbs_api_types::{ > print_ns_and_snapshot, print_store_and_ns, Authid, BackupContent, BackupNamespace, BackupType, > - Counts, CryptMode, DataStoreListItem, DataStoreStatus, GarbageCollectionStatus, GroupListItem, > - KeepOptions, Operation, PruneJobOptions, RRDMode, RRDTimeFrame, SnapshotListItem, > - SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA, > - BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, > - MAX_NAMESPACE_DEPTH, NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, > - PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ, PRIV_DATASTORE_VERIFY, > - UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA, > + Counts, CryptMode, DataStoreConfig, DataStoreListItem, DataStoreStatus, GarbageCollectionInfo, > + GarbageCollectionStatus, GroupListItem, JobScheduleStatus, KeepOptions, Operation, > + PruneJobOptions, RRDMode, RRDTimeFrame, SnapshotListItem, SnapshotVerifyState, > + BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA, BACKUP_TIME_SCHEMA, > + BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, MAX_NAMESPACE_DEPTH, > + NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, > + PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ, PRIV_DATASTORE_VERIFY, UPID, UPID_SCHEMA, > + VERIFICATION_OUTDATED_AFTER_SCHEMA, > }; > use pbs_client::pxar::{create_tar, create_zip}; > use pbs_config::CachedUserInfo; > @@ -67,7 +69,7 @@ use crate::backup::{ > ListAccessibleBackupGroups, NS_PRIVS_OK, > }; > > -use crate::server::jobstate::Job; > +use crate::server::jobstate::{compute_schedule_status, Job, JobState}; > > const GROUP_NOTES_FILE_NAME: &str = "notes"; > > @@ -1199,6 +1201,82 @@ pub fn garbage_collection_status( > Ok(status) > } > > +#[api( > + input: { > + properties: { > + store: { > + schema: DATASTORE_SCHEMA, > + }, > + }, > + }, > + returns: { > + type: GarbageCollectionInfo, > + }, > + access: { > + permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT, false), > + }, > +)] > +/// Garbage collection status. > +pub fn garbage_collection_info( > + store: String, > + _info: &ApiMethod, > + _rpcenv: &mut dyn RpcEnvironment, > +) -> Result { > + let (config, _) = pbs_config::datastore::config()?; > + let store_config: DataStoreConfig = config.lookup("datastore", &store)?; > + > + let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?; > + let status = datastore.last_gc_status(); > + > + let upid = match status.upid { > + Some(upid) => upid, > + None => { > + return Ok(GarbageCollectionInfo { > + schedule: store_config.gc_schedule, > + ..Default::default() > + }) > + } > + }; > + let last_state = JobState::load("garbage_collection", &store) > + .map_err(|err| format_err!("could not open statefile for {}: {}", upid, err)) > + .ok(); > + > + let mut computed_schedule: JobScheduleStatus = JobScheduleStatus::default(); > + if let Some(state) = last_state { > + computed_schedule = compute_schedule_status(&state, Some(&upid)).unwrap(); > + } > + > + // calculate next event > + if let Some(schedule) = &store_config.gc_schedule { > + if let (Ok(event), Some(last_run)) = ( > + schedule.parse::(), > + computed_schedule.last_run_endtime, > + ) { > + if let Ok(next_event) = event.compute_next_event(last_run) { > + computed_schedule.next_run = next_event; > + } > + } > + } If GC never ran but is scheduled, it would also be nice to show the next scheduled run. This might be usefull if people had not GC configured, then create a job to further indicate that the job is scheduled correctly. > + > + let mut duration = None; > + if let (Ok(upid), Some(endtime)) = (upid.parse::(), computed_schedule.last_run_endtime) { > + duration = Some(endtime - upid.starttime); > + } > + > + let info = GarbageCollectionInfo { > + last_run_upid: Some(upid), > + removed_chunks: Some(status.removed_chunks), > + pending_chunks: Some(status.pending_chunks), > + schedule: store_config.gc_schedule, > + next_run: computed_schedule.next_run, > + last_run_endtime: computed_schedule.last_run_endtime, > + last_run_state: computed_schedule.last_run_state, > + duration, > + }; > + > + Ok(info) > +} > + > #[api( > returns: { > description: "List the accessible datastores.", > @@ -2265,6 +2343,10 @@ const DATASTORE_INFO_SUBDIRS: SubdirMap = &[ > .get(&API_METHOD_GARBAGE_COLLECTION_STATUS) > .post(&API_METHOD_START_GARBAGE_COLLECTION), > ), > + ( > + "gc_info", > + &Router::new().get(&API_METHOD_GARBAGE_COLLECTION_INFO), > + ), > ( > "group-notes", > &Router::new() > diff --git a/www/Makefile b/www/Makefile > index 04c12b31..b67fd73f 100644 > --- a/www/Makefile > +++ b/www/Makefile > @@ -62,6 +62,7 @@ JSSRC= \ > config/SyncView.js \ > config/VerifyView.js \ > config/PruneView.js \ > + config/GCView.js \ > config/WebauthnView.js \ > config/CertificateView.js \ > config/NodeOptionView.js \ > diff --git a/www/Utils.js b/www/Utils.js > index 2eca600e..66293b17 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..1a79bb01 > --- /dev/null > +++ b/www/config/GCView.js > @@ -0,0 +1,116 @@ > +Ext.define('PBS.Datastore.GCOptions', { > + extend: 'Proxmox.grid.ObjectGrid', > + alias: 'widget.pbsGCJobView', > + mixins: ['Proxmox.Mixin.CBind'], > + > + onlineHelp: 'maintenance_pruning', > + > + cbindData: function(initial) { > + let me = this; > + > + me.datastore = encodeURIComponent(me.datastore); > + me.url = `/api2/json/admin/datastore/${me.datastore}/gc_info`; > + 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'), > + enableFn: (rec) => rec.id === 'schedule', > + 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: { > + "schedule": { > + required: true, > + defaultValue: Proxmox.Utils.NoneText, > + header: gettext('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, > + }, > + }, > + }, > + "last-run-state": { > + header: gettext('State'), > + renderer: PBS.Utils.render_task_status, > + disabled: true, > + }, > + "duration": { > + header: gettext('Duration'), > + renderer: Proxmox.Utils.render_duration, > + disabled: true, > + }, > + "last-run-endtime": { > + header: gettext('Last Run'), > + renderer: PBS.Utils.render_optional_timestamp, > + disabled: true, > + }, > + "next-run": { > + header: gettext('Next Run'), > + renderer: PBS.Utils.render_next_task_run, > + disabled: true, > + }, > + "pending-chunks": { > + header: gettext('Pending Chunks'), > + disabled: true, > + }, > + "removed-chunks": { > + header: gettext('Removed Chunks'), > + disabled: true, > + }, > + "last-run-upid": { visible: false }, > + }, > +}); > diff --git a/www/datastore/PruneAndGC.js b/www/datastore/PruneAndGC.js > index aab98dad..3b9878a3 100644 > --- a/www/datastore/PruneAndGC.js > +++ b/www/datastore/PruneAndGC.js > @@ -1,88 +1,3 @@ > -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', > @@ -99,9 +14,9 @@ Ext.define('PBS.Datastore.PruneAndGC', { > }, > items: [ > { > - xtype: 'pbsDatastoreGCOpts', > + xtype: 'pbsGCJobView', > title: gettext('Garbage Collection'), > - itemId: 'datastore-gc', > + itemId: 'datastore-gc-jobs', > nodename: 'localhost', > cbind: { > datastore: '{datastore}', > @@ -111,8 +26,6 @@ Ext.define('PBS.Datastore.PruneAndGC', { > xtype: 'pbsPruneJobView', > nodename: 'localhost', > itemId: 'datastore-prune-jobs', > - flex: 1, > - minHeight: 200, > cbind: { > datastore: '{datastore}', > }, > -- > 2.39.2 > > > > _______________________________________________ > pbs-devel mailing list > pbs-devel@lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel -------------------- Start of forwarded message -------------------- Date: Fri, 22 Sep 2023 10:51:35 +0200 Subject: Re: [pbs-devel] [PATCH proxmox-backup v3] close #4723: updated gc view in the ui To: Stefan Lendl From: Gabriel Goller Thanks for having a look at it! Could you post the message again on the mailing list (using "reply all" instead of "reply"). Thanks! On 9/22/23 10:49, Stefan Lendl wrote: > Gabriel Goller writes: > > Handling of GC tasks that never ran works great now. GUI alignment looks good. > >> Updated the GC overview in the datastore page. This enables >> us to see: >> - schedule >> - State (of last run) >> - Duration (of last run) >> - Last Run >> - Next Run >> - Pending Chunks (of last run) >> - Removed Chunks (of last run) >> >> Added `ObjectGrid` for GCView, moved to different file. Added >> endpoint `gc_info` that returns all the necessary config and >> last run stats. >> >> Signed-off-by: Gabriel Goller >> --- >> >> update v3: >> - ui: removed `required` attribute on items to get the sorting right >> - made `pending_chunks` and `removed_chunks` options, so that they >> are not shown when no gc run exists >> >> update v2: >> - skip serializing if value is `None` >> - return just the schedule if `upid` doesn't exist (means no gc has been >> run) >> - ui: removed default values on timestamps >> - ui: removed flex and minHeight properties >> >> pbs-api-types/src/datastore.rs | 38 +++++++++++ >> src/api2/admin/datastore.rs | 98 +++++++++++++++++++++++++--- >> www/Makefile | 1 + >> www/Utils.js | 12 ++-- >> www/config/GCView.js | 116 +++++++++++++++++++++++++++++++++ >> www/datastore/PruneAndGC.js | 91 +------------------------- >> 6 files changed, 253 insertions(+), 103 deletions(-) >> mode change 100644 => 100755 src/api2/admin/datastore.rs >> create mode 100644 www/config/GCView.js >> >> diff --git a/pbs-api-types/src/datastore.rs b/pbs-api-types/src/datastore.rs >> index 73c4890e..55caa963 100644 >> --- a/pbs-api-types/src/datastore.rs >> +++ b/pbs-api-types/src/datastore.rs >> @@ -1250,6 +1250,44 @@ pub struct GarbageCollectionStatus { >> pub still_bad: usize, >> } >> >> +#[api( >> + properties: { >> + "last-run-upid": { >> + optional: true, >> + type: UPID, >> + }, >> + }, >> +)] >> +#[derive(Clone, Default, Serialize, Deserialize, PartialEq)] >> +#[serde(rename_all = "kebab-case")] >> +/// Garbage Collection general info >> +pub struct GarbageCollectionInfo { >> + /// upid of the last run gc job >> + #[serde(skip_serializing_if = "Option::is_none")] >> + pub last_run_upid: Option, >> + /// Number of removed chunks >> + #[serde(skip_serializing_if = "Option::is_none")] >> + pub removed_chunks: Option, >> + /// Number of pending chunks >> + #[serde(skip_serializing_if = "Option::is_none")] >> + pub pending_chunks: Option, >> + /// Schedule of the gc job >> + #[serde(skip_serializing_if = "Option::is_none")] >> + pub schedule: Option, >> + /// Time of the next gc run >> + #[serde(skip_serializing_if = "Option::is_none")] >> + pub next_run: Option, >> + /// Endtime of the last gc run >> + #[serde(skip_serializing_if = "Option::is_none")] >> + pub last_run_endtime: Option, >> + /// State of the last gc run >> + #[serde(skip_serializing_if = "Option::is_none")] >> + pub last_run_state: Option, >> + /// Duration of last gc run >> + #[serde(skip_serializing_if = "Option::is_none")] >> + pub duration: Option, >> +} >> + >> #[api( >> properties: { >> "gc-status": { >> diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs >> old mode 100644 >> new mode 100755 >> index a95031e7..1127bd95 >> --- a/src/api2/admin/datastore.rs >> +++ b/src/api2/admin/datastore.rs >> @@ -10,6 +10,7 @@ use anyhow::{bail, format_err, Error}; >> use futures::*; >> use hyper::http::request::Parts; >> use hyper::{header, Body, Response, StatusCode}; >> +use proxmox_time::CalendarEvent; >> use serde::Deserialize; >> use serde_json::{json, Value}; >> use tokio_stream::wrappers::ReceiverStream; >> @@ -33,13 +34,14 @@ use pxar::EntryKind; >> >> use pbs_api_types::{ >> print_ns_and_snapshot, print_store_and_ns, Authid, BackupContent, BackupNamespace, BackupType, >> - Counts, CryptMode, DataStoreListItem, DataStoreStatus, GarbageCollectionStatus, GroupListItem, >> - KeepOptions, Operation, PruneJobOptions, RRDMode, RRDTimeFrame, SnapshotListItem, >> - SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA, >> - BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, >> - MAX_NAMESPACE_DEPTH, NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, >> - PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ, PRIV_DATASTORE_VERIFY, >> - UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA, >> + Counts, CryptMode, DataStoreConfig, DataStoreListItem, DataStoreStatus, GarbageCollectionInfo, >> + GarbageCollectionStatus, GroupListItem, JobScheduleStatus, KeepOptions, Operation, >> + PruneJobOptions, RRDMode, RRDTimeFrame, SnapshotListItem, SnapshotVerifyState, >> + BACKUP_ARCHIVE_NAME_SCHEMA, BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA, BACKUP_TIME_SCHEMA, >> + BACKUP_TYPE_SCHEMA, DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, MAX_NAMESPACE_DEPTH, >> + NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, >> + PRIV_DATASTORE_PRUNE, PRIV_DATASTORE_READ, PRIV_DATASTORE_VERIFY, UPID, UPID_SCHEMA, >> + VERIFICATION_OUTDATED_AFTER_SCHEMA, >> }; >> use pbs_client::pxar::{create_tar, create_zip}; >> use pbs_config::CachedUserInfo; >> @@ -67,7 +69,7 @@ use crate::backup::{ >> ListAccessibleBackupGroups, NS_PRIVS_OK, >> }; >> >> -use crate::server::jobstate::Job; >> +use crate::server::jobstate::{compute_schedule_status, Job, JobState}; >> >> const GROUP_NOTES_FILE_NAME: &str = "notes"; >> >> @@ -1199,6 +1201,82 @@ pub fn garbage_collection_status( >> Ok(status) >> } >> >> +#[api( >> + input: { >> + properties: { >> + store: { >> + schema: DATASTORE_SCHEMA, >> + }, >> + }, >> + }, >> + returns: { >> + type: GarbageCollectionInfo, >> + }, >> + access: { >> + permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT, false), >> + }, >> +)] >> +/// Garbage collection status. >> +pub fn garbage_collection_info( >> + store: String, >> + _info: &ApiMethod, >> + _rpcenv: &mut dyn RpcEnvironment, >> +) -> Result { >> + let (config, _) = pbs_config::datastore::config()?; >> + let store_config: DataStoreConfig = config.lookup("datastore", &store)?; >> + >> + let datastore = DataStore::lookup_datastore(&store, Some(Operation::Read))?; >> + let status = datastore.last_gc_status(); >> + >> + let upid = match status.upid { >> + Some(upid) => upid, >> + None => { >> + return Ok(GarbageCollectionInfo { >> + schedule: store_config.gc_schedule, >> + ..Default::default() >> + }) >> + } >> + }; >> + let last_state = JobState::load("garbage_collection", &store) >> + .map_err(|err| format_err!("could not open statefile for {}: {}", upid, err)) >> + .ok(); >> + >> + let mut computed_schedule: JobScheduleStatus = JobScheduleStatus::default(); >> + if let Some(state) = last_state { >> + computed_schedule = compute_schedule_status(&state, Some(&upid)).unwrap(); >> + } >> + >> + // calculate next event >> + if let Some(schedule) = &store_config.gc_schedule { >> + if let (Ok(event), Some(last_run)) = ( >> + schedule.parse::(), >> + computed_schedule.last_run_endtime, >> + ) { >> + if let Ok(next_event) = event.compute_next_event(last_run) { >> + computed_schedule.next_run = next_event; >> + } >> + } >> + } > > If GC never ran but is scheduled, it would also be nice to show the next > scheduled run. > This might be usefull if people had not GC configured, then create a job > to further indicate that the job is scheduled correctly. > > >> + >> + let mut duration = None; >> + if let (Ok(upid), Some(endtime)) = (upid.parse::(), computed_schedule.last_run_endtime) { >> + duration = Some(endtime - upid.starttime); >> + } >> + >> + let info = GarbageCollectionInfo { >> + last_run_upid: Some(upid), >> + removed_chunks: Some(status.removed_chunks), >> + pending_chunks: Some(status.pending_chunks), >> + schedule: store_config.gc_schedule, >> + next_run: computed_schedule.next_run, >> + last_run_endtime: computed_schedule.last_run_endtime, >> + last_run_state: computed_schedule.last_run_state, >> + duration, >> + }; >> + >> + Ok(info) >> +} >> + >> #[api( >> returns: { >> description: "List the accessible datastores.", >> @@ -2265,6 +2343,10 @@ const DATASTORE_INFO_SUBDIRS: SubdirMap = &[ >> .get(&API_METHOD_GARBAGE_COLLECTION_STATUS) >> .post(&API_METHOD_START_GARBAGE_COLLECTION), >> ), >> + ( >> + "gc_info", >> + &Router::new().get(&API_METHOD_GARBAGE_COLLECTION_INFO), >> + ), >> ( >> "group-notes", >> &Router::new() >> diff --git a/www/Makefile b/www/Makefile >> index 04c12b31..b67fd73f 100644 >> --- a/www/Makefile >> +++ b/www/Makefile >> @@ -62,6 +62,7 @@ JSSRC= \ >> config/SyncView.js \ >> config/VerifyView.js \ >> config/PruneView.js \ >> + config/GCView.js \ >> config/WebauthnView.js \ >> config/CertificateView.js \ >> config/NodeOptionView.js \ >> diff --git a/www/Utils.js b/www/Utils.js >> index 2eca600e..66293b17 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..1a79bb01 >> --- /dev/null >> +++ b/www/config/GCView.js >> @@ -0,0 +1,116 @@ >> +Ext.define('PBS.Datastore.GCOptions', { >> + extend: 'Proxmox.grid.ObjectGrid', >> + alias: 'widget.pbsGCJobView', >> + mixins: ['Proxmox.Mixin.CBind'], >> + >> + onlineHelp: 'maintenance_pruning', >> + >> + cbindData: function(initial) { >> + let me = this; >> + >> + me.datastore = encodeURIComponent(me.datastore); >> + me.url = `/api2/json/admin/datastore/${me.datastore}/gc_info`; >> + 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'), >> + enableFn: (rec) => rec.id === 'schedule', >> + 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: { >> + "schedule": { >> + required: true, >> + defaultValue: Proxmox.Utils.NoneText, >> + header: gettext('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, >> + }, >> + }, >> + }, >> + "last-run-state": { >> + header: gettext('State'), >> + renderer: PBS.Utils.render_task_status, >> + disabled: true, >> + }, >> + "duration": { >> + header: gettext('Duration'), >> + renderer: Proxmox.Utils.render_duration, >> + disabled: true, >> + }, >> + "last-run-endtime": { >> + header: gettext('Last Run'), >> + renderer: PBS.Utils.render_optional_timestamp, >> + disabled: true, >> + }, >> + "next-run": { >> + header: gettext('Next Run'), >> + renderer: PBS.Utils.render_next_task_run, >> + disabled: true, >> + }, >> + "pending-chunks": { >> + header: gettext('Pending Chunks'), >> + disabled: true, >> + }, >> + "removed-chunks": { >> + header: gettext('Removed Chunks'), >> + disabled: true, >> + }, >> + "last-run-upid": { visible: false }, >> + }, >> +}); >> diff --git a/www/datastore/PruneAndGC.js b/www/datastore/PruneAndGC.js >> index aab98dad..3b9878a3 100644 >> --- a/www/datastore/PruneAndGC.js >> +++ b/www/datastore/PruneAndGC.js >> @@ -1,88 +1,3 @@ >> -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', >> @@ -99,9 +14,9 @@ Ext.define('PBS.Datastore.PruneAndGC', { >> }, >> items: [ >> { >> - xtype: 'pbsDatastoreGCOpts', >> + xtype: 'pbsGCJobView', >> title: gettext('Garbage Collection'), >> - itemId: 'datastore-gc', >> + itemId: 'datastore-gc-jobs', >> nodename: 'localhost', >> cbind: { >> datastore: '{datastore}', >> @@ -111,8 +26,6 @@ Ext.define('PBS.Datastore.PruneAndGC', { >> xtype: 'pbsPruneJobView', >> nodename: 'localhost', >> itemId: 'datastore-prune-jobs', >> - flex: 1, >> - minHeight: 200, >> cbind: { >> datastore: '{datastore}', >> }, >> -- >> 2.39.2 >> >> >> >> _______________________________________________ >> pbs-devel mailing list >> pbs-devel@lists.proxmox.com >> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel -------------------- End of forwarded message --------------------