public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux
@ 2020-10-27 15:20 Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 1/8] api/{verify, syncjobs}: add optional datastore parameter Dominik Csapak
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Dominik Csapak @ 2020-10-27 15:20 UTC (permalink / raw)
  To: pbs-devel

this series aims to improve the datastore ux by
* moving all datastore relevant settings/options/views into the
  datastore tabpanel
* adding a 'Summary' panel with some important information
* improving tab selection between datastores/browser reloads/etc.

this needs my previous series: "improve and extend admin/datastore/status api"
to work

some things are still to be improved, namely:
* multiline comments for datastores
* automatic ids for sync/verify jobs
* improved prune/gc panel (e.g. with status of last run)
* comments for backup snapshots

Dominik Csapak (8):
  api/{verify,syncjobs}: add optional datastore parameter
  ui: DataStoreContent: add 'Verify All' button
  ui: add DataStorePruneAndGC panel and add it to datastore panel
  ui: add DataStoreSummary and move Statistics into it
  ui: move sync/verify jobs to the datastores
  ui: NavigationTree: add 'Add Datastore' button below datastore list
  ui: MainView/NavigationTree: improve tree selection handling
  ui: DataStorePanel: save active tab statefully

 src/api2/admin/sync.rs        |  19 ++-
 src/api2/admin/verify.rs      |  19 ++-
 www/DataStoreContent.js       |  23 +++
 www/DataStoreNotes.js         | 104 ++++++++++++
 www/DataStorePanel.js         |  52 +++++-
 www/DataStorePruneAndGC.js    | 164 +++++++++++++++++++
 www/DataStoreStatistic.js     | 104 ------------
 www/DataStoreSummary.js       | 296 ++++++++++++++++++++++++++++++++++
 www/MainView.js               |  70 ++++----
 www/Makefile                  |   5 +-
 www/NavigationTree.js         | 101 ++++++++----
 www/config/DataStoreConfig.js | 227 --------------------------
 www/config/SyncView.js        |  14 +-
 www/config/VerifyView.js      |  20 ++-
 www/window/DataStoreEdit.js   | 147 +++++++++--------
 www/window/SyncJobEdit.js     |   6 +-
 www/window/VerifyJobEdit.js   |   6 +-
 17 files changed, 898 insertions(+), 479 deletions(-)
 create mode 100644 www/DataStoreNotes.js
 create mode 100644 www/DataStorePruneAndGC.js
 delete mode 100644 www/DataStoreStatistic.js
 create mode 100644 www/DataStoreSummary.js
 delete mode 100644 www/config/DataStoreConfig.js

-- 
2.20.1





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 1/8] api/{verify, syncjobs}: add optional datastore parameter
  2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
@ 2020-10-27 15:20 ` Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 2/8] ui: DataStoreContent: add 'Verify All' button Dominik Csapak
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2020-10-27 15:20 UTC (permalink / raw)
  To: pbs-devel

to limit the lists to the given datastores

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/api2/admin/sync.rs   | 19 +++++++++++++++++--
 src/api2/admin/verify.rs | 19 +++++++++++++++++--
 2 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/src/api2/admin/sync.rs b/src/api2/admin/sync.rs
index bea52a0a..e1072f72 100644
--- a/src/api2/admin/sync.rs
+++ b/src/api2/admin/sync.rs
@@ -15,7 +15,12 @@ use crate::tools::systemd::time::{
 
 #[api(
     input: {
-        properties: {},
+        properties: {
+            store: {
+                schema: DATASTORE_SCHEMA,
+                optional: true,
+            },
+        },
     },
     returns: {
         description: "List configured jobs and their status.",
@@ -25,13 +30,23 @@ use crate::tools::systemd::time::{
 )]
 /// List all sync jobs
 pub fn list_sync_jobs(
+    store: Option<String>,
     _param: Value,
     mut rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<Vec<SyncJobStatus>, Error> {
 
     let (config, digest) = sync::config()?;
 
-    let mut list: Vec<SyncJobStatus> = config.convert_to_typed_array("sync")?;
+    let mut list: Vec<SyncJobStatus> = config
+        .convert_to_typed_array("sync")?
+        .into_iter()
+        .filter(|job: &SyncJobStatus| {
+            if let Some(store) = &store {
+                &job.store == store
+            } else {
+                true
+            }
+        }).collect();
 
     for job in &mut list {
         let last_state = JobState::load("syncjob", &job.id)
diff --git a/src/api2/admin/verify.rs b/src/api2/admin/verify.rs
index f61373a0..c5d84b43 100644
--- a/src/api2/admin/verify.rs
+++ b/src/api2/admin/verify.rs
@@ -15,7 +15,12 @@ use crate::server::UPID;
 
 #[api(
     input: {
-        properties: {},
+        properties: {
+            store: {
+                schema: DATASTORE_SCHEMA,
+                optional: true,
+            },
+        },
     },
     returns: {
         description: "List configured jobs and their status.",
@@ -25,13 +30,23 @@ use crate::server::UPID;
 )]
 /// List all verification jobs
 pub fn list_verification_jobs(
+    store: Option<String>,
     _param: Value,
     mut rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<Vec<VerificationJobStatus>, Error> {
 
     let (config, digest) = verify::config()?;
 
-    let mut list: Vec<VerificationJobStatus> = config.convert_to_typed_array("verification")?;
+    let mut list: Vec<VerificationJobStatus> = config
+        .convert_to_typed_array("verification")?
+        .into_iter()
+        .filter(|job: &VerificationJobStatus| {
+            if let Some(store) = &store {
+                &job.store == store
+            } else {
+                true
+            }
+        }).collect();
 
     for job in &mut list {
         let last_state = JobState::load("verificationjob", &job.id)
-- 
2.20.1





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 2/8] ui: DataStoreContent: add 'Verify All' button
  2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 1/8] api/{verify, syncjobs}: add optional datastore parameter Dominik Csapak
@ 2020-10-27 15:20 ` Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 3/8] ui: add DataStorePruneAndGC panel and add it to datastore panel Dominik Csapak
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2020-10-27 15:20 UTC (permalink / raw)
  To: pbs-devel

to verify the complete datastore

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/DataStoreContent.js | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/www/DataStoreContent.js b/www/DataStoreContent.js
index 28dfd743..870d6328 100644
--- a/www/DataStoreContent.js
+++ b/www/DataStoreContent.js
@@ -285,6 +285,23 @@ Ext.define('PBS.DataStoreContent', {
 	    win.show();
 	},
 
+	verifyAll: function() {
+	    var view = this.getView();
+
+	    Proxmox.Utils.API2Request({
+		url: `/admin/datastore/${view.datastore}/verify`,
+		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();
+		},
+	    });
+	},
+
 	onVerify: function(view, rI, cI, item, e, rec) {
 	    let me = this;
 	    view = me.getView();
@@ -679,6 +696,12 @@ Ext.define('PBS.DataStoreContent', {
 	    iconCls: 'fa fa-refresh',
 	    handler: 'reload',
 	},
+	'-',
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Verify All'),
+	    handler: 'verifyAll',
+	},
 	'->',
 	{
 	    xtype: 'tbtext',
-- 
2.20.1





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 3/8] ui: add DataStorePruneAndGC panel and add it to datastore panel
  2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 1/8] api/{verify, syncjobs}: add optional datastore parameter Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 2/8] ui: DataStoreContent: add 'Verify All' button Dominik Csapak
@ 2020-10-27 15:20 ` Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 4/8] ui: add DataStoreSummary and move Statistics into it Dominik Csapak
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2020-10-27 15:20 UTC (permalink / raw)
  To: pbs-devel

a simple objectgrid to display datastore gc/prune options
needs the prune inputpanel to be refactored in its own class

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/DataStorePanel.js       |   9 ++
 www/DataStorePruneAndGC.js  | 164 ++++++++++++++++++++++++++++++++++++
 www/Makefile                |   1 +
 www/window/DataStoreEdit.js | 147 ++++++++++++++++++--------------
 4 files changed, 256 insertions(+), 65 deletions(-)
 create mode 100644 www/DataStorePruneAndGC.js

diff --git a/www/DataStorePanel.js b/www/DataStorePanel.js
index 2739614d..88ef02a8 100644
--- a/www/DataStorePanel.js
+++ b/www/DataStorePanel.js
@@ -18,6 +18,15 @@ Ext.define('PBS.DataStorePanel', {
     items: [
 	{
 	    xtype: 'pbsDataStoreContent',
+	    itemId: 'content',
+	    cbind: {
+		datastore: '{datastore}',
+	    },
+	},
+	{
+	    title: gettext('Prune & Garbage collection'),
+	    xtype: 'pbsDataStorePruneAndGC',
+	    itemId: 'prunegc',
 	    cbind: {
 		datastore: '{datastore}',
 	    },
diff --git a/www/DataStorePruneAndGC.js b/www/DataStorePruneAndGC.js
new file mode 100644
index 00000000..6ce33248
--- /dev/null
+++ b/www/DataStorePruneAndGC.js
@@ -0,0 +1,164 @@
+Ext.define('PBS.DataStorePruneAndGC', {
+    extend: 'Proxmox.grid.ObjectGrid',
+    alias: 'widget.pbsDataStorePruneAndGC',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    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 GC'),
+	    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('GC Schedule'),
+	    editor: {
+		xtype: 'proxmoxWindowEdit',
+		title: gettext('GC Schedule'),
+		items: {
+		    xtype: 'pbsCalendarEvent',
+		    name: 'gc-schedule',
+		    fieldLabel: gettext("GC Schedule"),
+		    emptyText: Proxmox.Utils.noneText,
+		    deleteEmpty: true,
+		},
+	    },
+	},
+	"prune-schedule": {
+	    required: true,
+	    defaultValue: Proxmox.Utils.NoneText,
+	    header: gettext('Prune Schedule'),
+	    editor: {
+		xtype: 'proxmoxWindowEdit',
+		title: gettext('Prune Schedule'),
+		items: {
+		    xtype: 'pbsCalendarEvent',
+		    name: 'prune-schedule',
+		    fieldLabel: gettext("Prune Schedule"),
+		    emptyText: Proxmox.Utils.noneText,
+		    deleteEmpty: true,
+		},
+	    },
+	},
+	"keep-last": {
+	    required: true,
+	    header: gettext('Keep Last'),
+	    editor: {
+		xtype: 'proxmoxWindowEdit',
+		title: gettext('Prune Options'),
+		items: {
+		    xtype: 'pbsPruneInputPanel',
+		    isCreate: false,
+		},
+	    },
+	},
+	"keep-hourly": {
+	    required: true,
+	    header: gettext('Keep Hourly'),
+	    editor: {
+		xtype: 'proxmoxWindowEdit',
+		title: gettext('Prune Options'),
+		items: {
+		    xtype: 'pbsPruneInputPanel',
+		},
+	    },
+	},
+	"keep-daily": {
+	    required: true,
+	    header: gettext('Keep Daily'),
+	    editor: {
+		xtype: 'proxmoxWindowEdit',
+		title: gettext('Prune Options'),
+		items: {
+		    xtype: 'pbsPruneInputPanel',
+		},
+	    },
+	},
+	"keep-weekly": {
+	    required: true,
+	    header: gettext('Keep Weekly'),
+	    editor: {
+		xtype: 'proxmoxWindowEdit',
+		title: gettext('Prune Options'),
+		items: {
+		    xtype: 'pbsPruneInputPanel',
+		},
+	    },
+	},
+	"keep-monthly": {
+	    required: true,
+	    header: gettext('Keep Monthly'),
+	    editor: {
+		xtype: 'proxmoxWindowEdit',
+		title: gettext('Prune Options'),
+		items: {
+		    xtype: 'pbsPruneInputPanel',
+		},
+	    },
+	},
+	"keep-yearly": {
+	    required: true,
+	    header: gettext('Keep Yearly'),
+	    editor: {
+		xtype: 'proxmoxWindowEdit',
+		title: gettext('Prune Options'),
+		items: {
+		    xtype: 'pbsPruneInputPanel',
+		},
+	    },
+	},
+    },
+});
diff --git a/www/Makefile b/www/Makefile
index e04af930..afc240c5 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -40,6 +40,7 @@ JSSRC=							\
 	VersionInfo.js					\
 	SystemConfiguration.js				\
 	Subscription.js					\
+	DataStorePruneAndGC.js				\
 	DataStorePrune.js				\
 	DataStoreStatistic.js				\
 	DataStoreContent.js				\
diff --git a/www/window/DataStoreEdit.js b/www/window/DataStoreEdit.js
index ab2f3175..2499b54a 100644
--- a/www/window/DataStoreEdit.js
+++ b/www/window/DataStoreEdit.js
@@ -1,3 +1,81 @@
+Ext.define('PBS.panel.PruneInputPanel', {
+    extend: 'Proxmox.panel.InputPanel',
+    xtype: 'pbsPruneInputPanel',
+
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    cbindData: function() {
+	let me = this;
+	me.isCreate = !!me.isCreate;
+	return {};
+    },
+
+    column1: [
+	{
+	    xtype: 'proxmoxintegerfield',
+	    fieldLabel: gettext('Keep Last'),
+	    name: 'keep-last',
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	    minValue: 1,
+	    allowBlank: true,
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    fieldLabel: gettext('Keep Daily'),
+	    name: 'keep-daily',
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	    minValue: 1,
+	    allowBlank: true,
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    fieldLabel: gettext('Keep Monthly'),
+	    name: 'keep-monthly',
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	    minValue: 1,
+	    allowBlank: true,
+	},
+    ],
+    column2: [
+	{
+	    xtype: 'proxmoxintegerfield',
+	    fieldLabel: gettext('Keep Hourly'),
+	    name: 'keep-hourly',
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	    minValue: 1,
+	    allowBlank: true,
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    fieldLabel: gettext('Keep Weekly'),
+	    name: 'keep-weekly',
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	    minValue: 1,
+	    allowBlank: true,
+	},
+	{
+	    xtype: 'proxmoxintegerfield',
+	    fieldLabel: gettext('Keep Yearly'),
+	    name: 'keep-yearly',
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	    minValue: 1,
+	    allowBlank: true,
+	},
+    ],
+
+});
 Ext.define('PBS.DataStoreEdit', {
     extend: 'Proxmox.window.Edit',
     alias: 'widget.pbsDataStoreEdit',
@@ -88,72 +166,11 @@ Ext.define('PBS.DataStoreEdit', {
 	    },
 	    {
 		title: gettext('Prune Options'),
-		xtype: 'inputpanel',
+		xtype: 'pbsPruneInputPanel',
+		cbind: {
+		    isCreate: '{isCreate}',
+		},
 		onlineHelp: 'backup_pruning',
-		column1: [
-		    {
-			xtype: 'proxmoxintegerfield',
-			fieldLabel: gettext('Keep Last'),
-			name: 'keep-last',
-			cbind: {
-			    deleteEmpty: '{!isCreate}',
-			},
-			minValue: 1,
-			allowBlank: true,
-		    },
-		    {
-			xtype: 'proxmoxintegerfield',
-			fieldLabel: gettext('Keep Daily'),
-			name: 'keep-daily',
-			cbind: {
-			    deleteEmpty: '{!isCreate}',
-			},
-			minValue: 1,
-			allowBlank: true,
-		    },
-		    {
-			xtype: 'proxmoxintegerfield',
-			fieldLabel: gettext('Keep Monthly'),
-			name: 'keep-monthly',
-			cbind: {
-			    deleteEmpty: '{!isCreate}',
-			},
-			minValue: 1,
-			allowBlank: true,
-		    },
-		],
-		column2: [
-		    {
-			xtype: 'proxmoxintegerfield',
-			fieldLabel: gettext('Keep Hourly'),
-			name: 'keep-hourly',
-			cbind: {
-			    deleteEmpty: '{!isCreate}',
-			},
-			minValue: 1,
-			allowBlank: true,
-		    },
-		    {
-			xtype: 'proxmoxintegerfield',
-			fieldLabel: gettext('Keep Weekly'),
-			name: 'keep-weekly',
-			cbind: {
-			    deleteEmpty: '{!isCreate}',
-			},
-			minValue: 1,
-			allowBlank: true,
-		    },
-		    {
-			xtype: 'proxmoxintegerfield',
-			fieldLabel: gettext('Keep Yearly'),
-			name: 'keep-yearly',
-			cbind: {
-			    deleteEmpty: '{!isCreate}',
-			},
-			minValue: 1,
-			allowBlank: true,
-		    },
-		],
 	    },
 	],
     },
-- 
2.20.1





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 4/8] ui: add DataStoreSummary and move Statistics into it
  2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
                   ` (2 preceding siblings ...)
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 3/8] ui: add DataStorePruneAndGC panel and add it to datastore panel Dominik Csapak
@ 2020-10-27 15:20 ` Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 5/8] ui: move sync/verify jobs to the datastores Dominik Csapak
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2020-10-27 15:20 UTC (permalink / raw)
  To: pbs-devel

this adds a 'Summary' panel to the datastores, similar to what we have
for PVE's nodes/guests/storages

contains an info panel with useful information, a comment field, and
the charts from the statistics panel (which can be deleted since it is
not necessary any more)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/DataStoreNotes.js     | 104 ++++++++++++++
 www/DataStorePanel.js     |  14 +-
 www/DataStoreStatistic.js | 104 --------------
 www/DataStoreSummary.js   | 296 ++++++++++++++++++++++++++++++++++++++
 www/Makefile              |   3 +-
 5 files changed, 410 insertions(+), 111 deletions(-)
 create mode 100644 www/DataStoreNotes.js
 delete mode 100644 www/DataStoreStatistic.js
 create mode 100644 www/DataStoreSummary.js

diff --git a/www/DataStoreNotes.js b/www/DataStoreNotes.js
new file mode 100644
index 00000000..21462805
--- /dev/null
+++ b/www/DataStoreNotes.js
@@ -0,0 +1,104 @@
+Ext.define('PBS.DataStoreNotes', {
+    extend: 'Ext.panel.Panel',
+    xtype: 'pbsDataStoreNotes',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    title: gettext("Comment"),
+    bodyStyle: 'white-space:pre',
+    bodyPadding: 10,
+    scrollable: true,
+    animCollapse: false,
+
+    cbindData: function(initalConfig) {
+	let me = this;
+	me.url = `/api2/extjs/config/datastore/${me.datastore}`;
+	return { };
+    },
+
+    run_editor: function() {
+	let me = this;
+	let win = Ext.create('Proxmox.window.Edit', {
+	    title: gettext('Comment'),
+	    width: 600,
+	    resizable: true,
+	    layout: 'fit',
+	    defaultButton: undefined,
+	    items: {
+		xtype: 'textfield',
+		name: 'comment',
+		value: '',
+		hideLabel: true,
+	    },
+	    url: me.url,
+	    listeners: {
+		destroy: function() {
+		    me.load();
+		},
+	    },
+	}).show();
+	win.load();
+    },
+
+    setNotes: function(value) {
+	let me = this;
+	var data = value || '';
+	me.update(Ext.htmlEncode(data));
+
+	if (me.collapsible && me.collapseMode === 'auto') {
+	    me.setCollapsed(data === '');
+	}
+    },
+
+    load: function() {
+	var me = this;
+
+	Proxmox.Utils.API2Request({
+	    url: me.url,
+	    waitMsgTarget: me,
+	    failure: function(response, opts) {
+		me.update(gettext('Error') + " " + response.htmlStatus);
+		me.setCollapsed(false);
+	    },
+	    success: function(response, opts) {
+		me.setNotes(response.result.data.comment);
+	    },
+	});
+    },
+
+    listeners: {
+	render: function(c) {
+	    var me = this;
+	    me.getEl().on('dblclick', me.run_editor, me);
+	},
+	afterlayout: function() {
+	    let me = this;
+	    if (me.collapsible && !me.getCollapsed() && me.collapseMode === 'always') {
+		me.setCollapsed(true);
+		me.collapseMode = ''; // only once, on initial load!
+	    }
+	},
+    },
+
+    tools: [{
+	type: 'gear',
+	handler: function() {
+	    this.up('panel').run_editor();
+	},
+    }],
+
+    collapsible: true,
+    collapseDirection: 'right',
+
+    initComponent: function() {
+	var me = this;
+
+	me.callParent();
+
+	let sp = Ext.state.Manager.getProvider();
+	me.collapseMode = sp.get('notes-collapse', 'never');
+
+	if (me.collapseMode === 'auto') {
+	    me.setCollapsed(true);
+	}
+    },
+});
diff --git a/www/DataStorePanel.js b/www/DataStorePanel.js
index 88ef02a8..a00ccd47 100644
--- a/www/DataStorePanel.js
+++ b/www/DataStorePanel.js
@@ -17,22 +17,24 @@ Ext.define('PBS.DataStorePanel', {
 
     items: [
 	{
-	    xtype: 'pbsDataStoreContent',
-	    itemId: 'content',
+	    xtype: 'pbsDataStoreSummary',
+	    title: gettext('Summary'),
+	    itemId: 'summary',
 	    cbind: {
 		datastore: '{datastore}',
 	    },
 	},
 	{
-	    title: gettext('Prune & Garbage collection'),
-	    xtype: 'pbsDataStorePruneAndGC',
-	    itemId: 'prunegc',
+	    xtype: 'pbsDataStoreContent',
+	    itemId: 'content',
 	    cbind: {
 		datastore: '{datastore}',
 	    },
 	},
 	{
-	    xtype: 'pbsDataStoreStatistic',
+	    title: gettext('Prune & Garbage collection'),
+	    xtype: 'pbsDataStorePruneAndGC',
+	    itemId: 'prunegc',
 	    cbind: {
 		datastore: '{datastore}',
 	    },
diff --git a/www/DataStoreStatistic.js b/www/DataStoreStatistic.js
deleted file mode 100644
index c22640e4..00000000
--- a/www/DataStoreStatistic.js
+++ /dev/null
@@ -1,104 +0,0 @@
-Ext.define('pve-rrd-datastore', {
-    extend: 'Ext.data.Model',
-    fields: [
-	'used',
-	'total',
-	'read_ios',
-	'read_bytes',
-	'write_ios',
-	'write_bytes',
-        'io_ticks',
-	{
-	    name: 'io_delay', calculate: function(data) {
-		let ios = 0;
-		if (data.read_ios !== undefined) { ios += data.read_ios; }
-		if (data.write_ios !== undefined) { ios += data.write_ios; }
-		if (data.io_ticks === undefined) {
-		    return undefined;
-		} else if (ios === 0) {
-		    return 0;
-		}
-		return (data.io_ticks*1000.0)/ios;
-	    },
-	},
-	{ type: 'date', dateFormat: 'timestamp', name: 'time' },
-    ],
-});
-
-Ext.define('PBS.DataStoreStatistic', {
-    extend: 'Ext.panel.Panel',
-    alias: 'widget.pbsDataStoreStatistic',
-
-    title: gettext('Statistics'),
-
-    scrollable: true,
-
-    initComponent: function() {
-        var me = this;
-
-	if (!me.datastore) {
-	    throw "no datastore specified";
-	}
-
-	me.tbar = ['->', { xtype: 'proxmoxRRDTypeSelector' }];
-
-	var rrdstore = Ext.create('Proxmox.data.RRDStore', {
-	    rrdurl: "/api2/json/admin/datastore/" + me.datastore + "/rrd",
-	    model: 'pve-rrd-datastore',
-	});
-
-	me.items = {
-	    xtype: 'container',
-	    itemId: 'itemcontainer',
-	    layout: 'column',
-	    minWidth: 700,
-	    defaults: {
-		minHeight: 320,
-		padding: 5,
-		columnWidth: 1,
-	    },
-	    items: [
-		{
-		    xtype: 'proxmoxRRDChart',
-		    title: gettext('Storage usage (bytes)'),
-		    fields: ['total', 'used'],
-		    fieldTitles: [gettext('Total'), gettext('Storage usage')],
-		    store: rrdstore,
-		},
-		{
-		    xtype: 'proxmoxRRDChart',
-		    title: gettext('Transfer Rate (bytes/second)'),
-		    fields: ['read_bytes', 'write_bytes'],
-		    fieldTitles: [gettext('Read'), gettext('Write')],
-		    store: rrdstore,
-		},
-		{
-		    xtype: 'proxmoxRRDChart',
-		    title: gettext('Input/Output Operations per Second (IOPS)'),
-		    fields: ['read_ios', 'write_ios'],
-		    fieldTitles: [gettext('Read'), gettext('Write')],
-		    store: rrdstore,
-		},
-		{
-		    xtype: 'proxmoxRRDChart',
-		    title: gettext('IO Delay (ms)'),
-		    fields: ['io_delay'],
-		    fieldTitles: [gettext('IO Delay')],
-		    store: rrdstore,
-		},
-	    ],
-	};
-
-	me.listeners = {
-	    activate: function() {
-		rrdstore.startUpdate();
-	    },
-	    destroy: function() {
-		rrdstore.stopUpdate();
-	    },
-	};
-
-	me.callParent();
-    },
-
-});
diff --git a/www/DataStoreSummary.js b/www/DataStoreSummary.js
new file mode 100644
index 00000000..539075a1
--- /dev/null
+++ b/www/DataStoreSummary.js
@@ -0,0 +1,296 @@
+Ext.define('pve-rrd-datastore', {
+    extend: 'Ext.data.Model',
+    fields: [
+        'used',
+        'total',
+        'read_ios',
+        'read_bytes',
+        'write_ios',
+        'write_bytes',
+        'io_ticks',
+        {
+            name: 'io_delay', calculate: function(data) {
+                let ios = 0;
+                if (data.read_ios !== undefined) { ios += data.read_ios; }
+                if (data.write_ios !== undefined) { ios += data.write_ios; }
+                if (data.io_ticks === undefined) {
+                    return undefined;
+                } else if (ios === 0) {
+                    return 0;
+                }
+                return (data.io_ticks*1000.0)/ios;
+            },
+        },
+        { type: 'date', dateFormat: 'timestamp', name: 'time' },
+    ],
+});
+
+Ext.define('PBS.DataStoreInfo', {
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pbsDataStoreInfo',
+
+    viewModel: {
+	data: {
+	    countstext: '',
+	    usage: {},
+	    stillbad: 0,
+	    removedbytes: 0,
+	    mountpoint: "",
+	},
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	onLoad: function(store, data, success) {
+	    if (!success) return;
+	    let me = this;
+	    let vm = me.getViewModel();
+
+	    let counts = store.getById('counts').data.value;
+	    let storage = store.getById('storage').data.value;
+
+	    let used = Proxmox.Utils.format_size(storage.used);
+	    let total = Proxmox.Utils.format_size(storage.total);
+	    let percent = 100*storage.used/storage.total;
+	    if (storage.total === 0) {
+		percent = 0;
+	    }
+	    let used_percent = `${percent.toFixed(2)}%`;
+
+	    let usage = used_percent + ' (' +
+		Ext.String.format(gettext('{0} of {1}'),
+				  used, total) + ')';
+	    vm.set('usagetext', usage);
+	    vm.set('usage', storage.used/storage.total);
+
+	    let gcstatus = store.getById('gc-status').data.value;
+
+	    let dedup = (gcstatus['index-data-bytes'] || 0)/
+			(gcstatus['disk-bytes'] || Infinity);
+
+	    let countstext = function(count) {
+		return `${count[0]} ${gettext('Groups')}, ${count[1]} ${gettext('Snapshots')}`;
+	    };
+
+	    vm.set('ctcount', countstext(counts.ct || [0, 0]));
+	    vm.set('vmcount', countstext(counts.vm || [0, 0]));
+	    vm.set('hostcount', countstext(counts.host || [0, 0]));
+	    vm.set('deduplication', dedup.toFixed(2));
+	    vm.set('stillbad', gcstatus['still-bad']);
+	    vm.set('removedbytes', Proxmox.Utils.format_size(gcstatus['removed-bytes']));
+	},
+
+	startStore: function() { this.store.startUpdate(); },
+	stopStore: function() { this.store.stopUpdate(); },
+
+	init: function(view) {
+	    let me = this;
+	    let datastore = encodeURIComponent(view.datastore);
+	    me.store = Ext.create('Proxmox.data.ObjectStore', {
+		interval: 5*1000,
+		url: `/api2/json/admin/datastore/${datastore}/status`,
+	    });
+	    me.store.on('load', me.onLoad, me);
+	},
+    },
+
+    listeners: {
+	activate: 'startStore',
+	destroy: 'stopStore',
+	deactivate: 'stopStore',
+    },
+
+    defaults: {
+	xtype: 'pmxInfoWidget',
+    },
+
+    bodyPadding: 20,
+
+    items: [
+	{
+	    iconCls: 'fa fa-hdd-o',
+	    title: gettext('Usage'),
+	    bind: {
+		data: {
+		    usage: '{usage}',
+		    text: '{usagetext}',
+		},
+	    },
+	},
+	{
+	    xtype: 'box',
+	    html: `<b>${gettext('Backup Count')}</b>`,
+	    padding: '10 0 5 0',
+	},
+	{
+	    iconCls: 'fa fa-cube',
+	    title: gettext('CT'),
+	    printBar: false,
+	    bind: {
+		data: {
+		    text: '{ctcount}',
+		},
+	    },
+	},
+	{
+	    iconCls: 'fa fa-building',
+	    title: gettext('Host'),
+	    printBar: false,
+	    bind: {
+		data: {
+		    text: '{hostcount}',
+		},
+	    },
+	},
+	{
+	    iconCls: 'fa fa-desktop',
+	    title: gettext('VM'),
+	    printBar: false,
+	    bind: {
+		data: {
+		    text: '{vmcount}',
+		},
+	    },
+	},
+	{
+	    xtype: 'box',
+	    html: `<b>${gettext('Stats from last Garbage Collection')}</b>`,
+	    padding: '10 0 5 0',
+	},
+	{
+	    iconCls: 'fa fa-compress',
+	    title: gettext('Deduplication'),
+	    printBar: false,
+	    bind: {
+		data: {
+		    text: '{deduplication}',
+		},
+	    },
+	},
+	{
+	    iconCls: 'fa fa-trash-o',
+	    title: gettext('Removed Bytes'),
+	    printBar: false,
+	    bind: {
+		data: {
+		    text: '{removedbytes}',
+		},
+	    },
+	},
+	{
+	    iconCls: 'fa critical fa-exclamation-triangle',
+	    title: gettext('Bad Chunks'),
+	    printBar: false,
+	    bind: {
+		data: {
+		    text: '{stillbad}',
+		},
+		visible: '{stillbad}',
+	    },
+	},
+    ],
+});
+
+Ext.define('PBS.DataStoreSummary', {
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pbsDataStoreSummary',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    layout: 'column',
+    scrollable: true,
+
+    bodyPadding: 5,
+    defaults: {
+	columnWidth: 1,
+	padding: 5,
+    },
+
+    tbar: ['->', { xtype: 'proxmoxRRDTypeSelector' }],
+
+    items: [
+	{
+	    xtype: 'container',
+	    height: 300,
+	    layout: {
+		type: 'hbox',
+		align: 'stretch',
+	    },
+	    items: [
+		{
+		    xtype: 'pbsDataStoreInfo',
+		    flex: 1,
+		    padding: '0 10 0 0',
+		    cbind: {
+			title: '{datastore}',
+			datastore: '{datastore}',
+		    },
+		},
+		{
+		    xtype: 'pbsDataStoreNotes',
+		    flex: 1,
+		    cbind: {
+			datastore: '{datastore}',
+		    },
+		},
+	    ],
+	},
+	{
+	    xtype: 'proxmoxRRDChart',
+	    title: gettext('Storage usage (bytes)'),
+	    fields: ['total', 'used'],
+	    fieldTitles: [gettext('Total'), gettext('Storage usage')],
+	},
+	{
+	    xtype: 'proxmoxRRDChart',
+	    title: gettext('Transfer Rate (bytes/second)'),
+	    fields: ['read_bytes', 'write_bytes'],
+	    fieldTitles: [gettext('Read'), gettext('Write')],
+	},
+	{
+	    xtype: 'proxmoxRRDChart',
+	    title: gettext('Input/Output Operations per Second (IOPS)'),
+	    fields: ['read_ios', 'write_ios'],
+	    fieldTitles: [gettext('Read'), gettext('Write')],
+	},
+	{
+	    xtype: 'proxmoxRRDChart',
+	    title: gettext('IO Delay (ms)'),
+	    fields: ['io_delay'],
+	    fieldTitles: [gettext('IO Delay')],
+	},
+    ],
+
+    listeners: {
+	activate: function() { this.rrdstore.startUpdate(); },
+	deactivate: function() { this.rrdstore.stopUpdate(); },
+	destroy: function() { this.rrdstore.stopUpdate(); },
+    },
+
+    initComponent: function() {
+	let me = this;
+
+	me.rrdstore = Ext.create('Proxmox.data.RRDStore', {
+	    rrdurl: "/api2/json/admin/datastore/" + me.datastore + "/rrd",
+	    model: 'pve-rrd-datastore',
+	});
+
+	me.callParent();
+
+	Proxmox.Utils.API2Request({
+	    url: `/config/datastore/${me.datastore}`,
+	    waitMsgTarget: me.down('pbsDataStoreInfo'),
+	    success: function(response) {
+		let path = Ext.htmlEncode(response.result.data.path);
+		me.down('pbsDataStoreInfo').setTitle(`${me.datastore} (${path})`);
+		me.down('pbsDataStoreNotes').setNotes(response.result.data.comment);
+	    },
+	});
+
+	me.query('proxmoxRRDChart').forEach((chart) => {
+	    chart.setStore(me.rrdstore);
+	});
+
+	me.down('pbsDataStoreInfo').relayEvents(me, ['activate', 'deactivate']);
+    },
+});
diff --git a/www/Makefile b/www/Makefile
index afc240c5..97b9b848 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -40,9 +40,10 @@ JSSRC=							\
 	VersionInfo.js					\
 	SystemConfiguration.js				\
 	Subscription.js					\
+	DataStoreSummary.js				\
+	DataStoreNotes.js				\
 	DataStorePruneAndGC.js				\
 	DataStorePrune.js				\
-	DataStoreStatistic.js				\
 	DataStoreContent.js				\
 	DataStorePanel.js				\
 	ServerStatus.js					\
-- 
2.20.1





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 5/8] ui: move sync/verify jobs to the datastores
  2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
                   ` (3 preceding siblings ...)
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 4/8] ui: add DataStoreSummary and move Statistics into it Dominik Csapak
@ 2020-10-27 15:20 ` Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 6/8] ui: NavigationTree: add 'Add Datastore' button below datastore list Dominik Csapak
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2020-10-27 15:20 UTC (permalink / raw)
  To: pbs-devel

add the datastore as parameter for the store, remove
the datastore selector for the edit windows and give the datastore
to it instead

also remove the autostart from the rstore, since we only want to start
it when we change to the relevant tab

and add icons for all other datastore tabs

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/DataStorePanel.js       | 20 ++++++++++++++++++++
 www/NavigationTree.js       | 12 ------------
 www/config/SyncView.js      | 14 ++++++++++++--
 www/config/VerifyView.js    | 20 ++++++++++++--------
 www/window/SyncJobEdit.js   |  6 ++++--
 www/window/VerifyJobEdit.js |  6 ++++--
 6 files changed, 52 insertions(+), 26 deletions(-)

diff --git a/www/DataStorePanel.js b/www/DataStorePanel.js
index a00ccd47..059fd3b2 100644
--- a/www/DataStorePanel.js
+++ b/www/DataStorePanel.js
@@ -20,6 +20,7 @@ Ext.define('PBS.DataStorePanel', {
 	    xtype: 'pbsDataStoreSummary',
 	    title: gettext('Summary'),
 	    itemId: 'summary',
+	    iconCls: 'fa fa-book',
 	    cbind: {
 		datastore: '{datastore}',
 	    },
@@ -27,6 +28,7 @@ Ext.define('PBS.DataStorePanel', {
 	{
 	    xtype: 'pbsDataStoreContent',
 	    itemId: 'content',
+	    iconCls: 'fa fa-th',
 	    cbind: {
 		datastore: '{datastore}',
 	    },
@@ -35,6 +37,23 @@ Ext.define('PBS.DataStorePanel', {
 	    title: gettext('Prune & Garbage collection'),
 	    xtype: 'pbsDataStorePruneAndGC',
 	    itemId: 'prunegc',
+	    iconCls: 'fa fa-trash-o',
+	    cbind: {
+		datastore: '{datastore}',
+	    },
+	},
+	{
+	    iconCls: 'fa fa-refresh',
+	    itemId: 'syncjobs',
+	    xtype: 'pbsSyncJobView',
+	    cbind: {
+		datastore: '{datastore}',
+	    },
+	},
+	{
+	    iconCls: 'fa fa-check-circle',
+	    itemId: 'verifyjobs',
+	    xtype: 'pbsVerifyJobView',
 	    cbind: {
 		datastore: '{datastore}',
 	    },
@@ -42,6 +61,7 @@ Ext.define('PBS.DataStorePanel', {
 	{
 	    itemId: 'acl',
 	    xtype: 'pbsACLView',
+	    iconCls: 'fa fa-unlock',
 	    aclExact: true,
 	    cbind: {
 		aclPath: '{aclPath}',
diff --git a/www/NavigationTree.js b/www/NavigationTree.js
index a2835cd2..c37e2613 100644
--- a/www/NavigationTree.js
+++ b/www/NavigationTree.js
@@ -36,18 +36,6 @@ Ext.define('PBS.store.NavigationStore', {
 			path: 'pbsRemoteView',
 			leaf: true,
 		    },
-		    {
-			text: gettext('Sync Jobs'),
-			iconCls: 'fa fa-refresh',
-			path: 'pbsSyncJobView',
-			leaf: true,
-		    },
-		    {
-			text: gettext('Verify Jobs'),
-			iconCls: 'fa fa-check-circle',
-			path: 'pbsVerifyJobView',
-			leaf: true,
-		    },
 		    {
 			text: gettext('Subscription'),
 			iconCls: 'fa fa-support',
diff --git a/www/config/SyncView.js b/www/config/SyncView.js
index 679624a8..513ddd9b 100644
--- a/www/config/SyncView.js
+++ b/www/config/SyncView.js
@@ -12,6 +12,7 @@ Ext.define('pbs-sync-jobs-status', {
 		return endtime - task.starttime;
 	    },
 	},
+	'comment',
     ],
     idProperty: 'id',
     proxy: {
@@ -34,7 +35,9 @@ Ext.define('PBS.config.SyncJobView', {
 
 	addSyncJob: function() {
 	    let me = this;
+	    let view = me.getView();
             Ext.create('PBS.window.SyncJobEdit', {
+		datastore: view.datastore,
 		listeners: {
 		    destroy: function() {
 			me.reload();
@@ -50,6 +53,7 @@ Ext.define('PBS.config.SyncJobView', {
 	    if (selection.length < 1) return;
 
             Ext.create('PBS.window.SyncJobEdit', {
+		datastore: view.datastore,
                 id: selection[0].data.id,
 		listeners: {
 		    destroy: function() {
@@ -147,15 +151,22 @@ Ext.define('PBS.config.SyncJobView', {
 	    return Proxmox.Utils.render_timestamp(value);
 	},
 
+	startStore: function() { this.getView().getStore().rstore.startUpdate(); },
+	stopStore: function() { this.getView().getStore().rstore.stopUpdate(); },
+
 	reload: function() { this.getView().getStore().rstore.load(); },
 
 	init: function(view) {
+	    view.getStore().rstore.getProxy().setExtraParams({
+		store: view.datastore,
+	    });
 	    Proxmox.Utils.monStoreErrors(view, view.getStore().rstore);
 	},
     },
 
     listeners: {
-	activate: 'reload',
+	activate: 'startStore',
+	deactivate: 'stopStore',
 	itemdblclick: 'editSyncJob',
     },
 
@@ -168,7 +179,6 @@ Ext.define('PBS.config.SyncJobView', {
 	    type: 'update',
 	    storeid: 'pbs-sync-jobs-status',
 	    model: 'pbs-sync-jobs-status',
-	    autoStart: true,
 	    interval: 5000,
 	},
     },
diff --git a/www/config/VerifyView.js b/www/config/VerifyView.js
index db541cf4..7e391226 100644
--- a/www/config/VerifyView.js
+++ b/www/config/VerifyView.js
@@ -12,6 +12,7 @@ Ext.define('pbs-verify-jobs-status', {
 		return endtime - task.starttime;
 	    },
 	},
+	'comment',
     ],
     idProperty: 'id',
     proxy: {
@@ -34,7 +35,9 @@ Ext.define('PBS.config.VerifyJobView', {
 
 	addVerifyJob: function() {
 	    let me = this;
+	    let view = me.getView();
 	    Ext.create('PBS.window.VerifyJobEdit', {
+		datastore: view.datastore,
 		listeners: {
 		    destroy: function() {
 			me.reload();
@@ -50,6 +53,7 @@ Ext.define('PBS.config.VerifyJobView', {
 	    if (selection.length < 1) return;
 
 	    Ext.create('PBS.window.VerifyJobEdit', {
+		datastore: view.datastore,
 		id: selection[0].data.id,
 		listeners: {
 		    destroy: function() {
@@ -147,15 +151,22 @@ Ext.define('PBS.config.VerifyJobView', {
 	    return Proxmox.Utils.render_timestamp(value);
 	},
 
+	startStore: function() { this.getView().getStore().rstore.startUpdate(); },
+	stopStore: function() { this.getView().getStore().rstore.stopUpdate(); },
+
 	reload: function() { this.getView().getStore().rstore.load(); },
 
 	init: function(view) {
+	    view.getStore().rstore.getProxy().setExtraParams({
+		store: view.datastore,
+	    });
 	    Proxmox.Utils.monStoreErrors(view, view.getStore().rstore);
 	},
     },
 
     listeners: {
-	activate: 'reload',
+	activate: 'startStore',
+	deactivate: 'stopStore',
 	itemdblclick: 'editVerifyJob',
     },
 
@@ -168,7 +179,6 @@ Ext.define('PBS.config.VerifyJobView', {
 	    type: 'update',
 	    storeid: 'pbs-verify-jobs-status',
 	    model: 'pbs-verify-jobs-status',
-	    autoStart: true,
 	    interval: 5000,
 	},
     },
@@ -219,12 +229,6 @@ Ext.define('PBS.config.VerifyJobView', {
 	    renderer: Ext.String.htmlEncode,
 	    dataIndex: 'id',
 	},
-	{
-	    header: gettext('Datastore'),
-	    width: 100,
-	    sortable: true,
-	    dataIndex: 'store',
-	},
 	{
 	    header: gettext('Days valid'),
 	    width: 125,
diff --git a/www/window/SyncJobEdit.js b/www/window/SyncJobEdit.js
index 08209e64..2002c2fa 100644
--- a/www/window/SyncJobEdit.js
+++ b/www/window/SyncJobEdit.js
@@ -53,10 +53,12 @@ Ext.define('PBS.window.SyncJobEdit', {
 		name: 'remote-store',
 	    },
 	    {
-		fieldLabel: gettext('Local Datastore'),
-		xtype: 'pbsDataStoreSelector',
+		xtype: 'hiddenfield',
 		allowBlank: false,
 		name: 'store',
+		cbind: {
+		    value: '{datastore}',
+		},
 	    },
 	],
 
diff --git a/www/window/VerifyJobEdit.js b/www/window/VerifyJobEdit.js
index 9d29eba7..ddcf355b 100644
--- a/www/window/VerifyJobEdit.js
+++ b/www/window/VerifyJobEdit.js
@@ -41,10 +41,12 @@ Ext.define('PBS.window.VerifyJobEdit', {
 		},
 	    },
 	    {
-		fieldLabel: gettext('Datastore'),
-		xtype: 'pbsDataStoreSelector',
+		xtype: 'hiddenfield',
 		allowBlank: false,
 		name: 'store',
+		cbind: {
+		    value: '{datastore}',
+		},
 	    },
 	    {
 		xtype: 'proxmoxintegerfield',
-- 
2.20.1





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 6/8] ui: NavigationTree: add 'Add Datastore' button below datastore list
  2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
                   ` (4 preceding siblings ...)
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 5/8] ui: move sync/verify jobs to the datastores Dominik Csapak
@ 2020-10-27 15:20 ` Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 7/8] ui: MainView/NavigationTree: improve tree selection handling Dominik Csapak
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2020-10-27 15:20 UTC (permalink / raw)
  To: pbs-devel

and make 'Datastore' unclickable

since we have all options and information on the relevant datastore panels,
we do not need a datastore config anymore (besides the creation,
which we add here)

this also fixes the sorted insertion and removal of new/old datastores

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/Makefile                  |   1 -
 www/NavigationTree.js         |  67 ++++++++--
 www/config/DataStoreConfig.js | 227 ----------------------------------
 3 files changed, 54 insertions(+), 241 deletions(-)
 delete mode 100644 www/config/DataStoreConfig.js

diff --git a/www/Makefile b/www/Makefile
index 97b9b848..75d389d9 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -17,7 +17,6 @@ JSSRC=							\
 	config/ACLView.js				\
 	config/SyncView.js				\
 	config/VerifyView.js				\
-	config/DataStoreConfig.js			\
 	window/UserEdit.js				\
 	window/UserPassword.js				\
 	window/VerifyJobEdit.js				\
diff --git a/www/NavigationTree.js b/www/NavigationTree.js
index c37e2613..54e0adeb 100644
--- a/www/NavigationTree.js
+++ b/www/NavigationTree.js
@@ -1,3 +1,13 @@
+Ext.define('pbs-datastore-list', {
+    extend: 'Ext.data.Model',
+    fields: ['name', 'comment'],
+    proxy: {
+        type: 'proxmox',
+        url: "/api2/json/admin/datastore",
+    },
+    idProperty: 'store',
+});
+
 Ext.define('PBS.store.NavigationStore', {
     extend: 'Ext.data.TreeStore',
 
@@ -76,9 +86,18 @@ Ext.define('PBS.store.NavigationStore', {
 	    {
 		text: gettext('Datastore'),
 		iconCls: 'fa fa-archive',
-		path: 'pbsDataStoreConfig',
+		id: 'datastores',
 		expanded: true,
+		expandable: false,
 		leaf: false,
+		children: [
+		    {
+			text: gettext('Add Datastore'),
+			iconCls: 'fa fa-plus-circle',
+			leaf: true,
+			id: 'addbutton',
+		    },
+		],
 	    },
 	],
     },
@@ -110,21 +129,23 @@ Ext.define('PBS.view.main.NavigationTree', {
 
 	    let root = view.getStore().getRoot();
 
-	    // FIXME: newly added always get appended to the end..
-	    records.sort((a, b) => {
-		if (a.id > b.id) return 1;
-		if (a.id < b.id) return -1;
-		return 0;
-	    });
+	    records.sort((a, b) => a.id.localeCompare(b.id));
 
-	    var list = root.findChild('path', 'pbsDataStoreConfig', false);
+	    var list = root.findChild('id', 'datastores', false);
 	    var length = records.length;
 	    var lookup_hash = {};
-	    for (var i = 0; i < length; i++) {
+	    let j = 0;
+	    for (let i = 0; i < length; i++) {
 		let name = records[i].id;
 		lookup_hash[name] = true;
-		if (!list.findChild('text', name, false)) {
-		    list.appendChild({
+
+		while (name.localeCompare(list.getChildAt(j).data.text) > 0 &&
+		       (j + 1) < list.childNodes.length) {
+		    j++;
+		}
+
+		if (list.getChildAt(j).data.text.localeCompare(name) !== 0) {
+		    list.insertChild(j, {
 			text: name,
 			path: `DataStore-${name}`,
 			iconCls: 'fa fa-database',
@@ -136,12 +157,32 @@ Ext.define('PBS.view.main.NavigationTree', {
 	    var erase_list = [];
 	    list.eachChild(function(node) {
 		let name = node.data.text;
-		if (!lookup_hash[name]) {
+		if (!lookup_hash[name] && node.data.id !== 'addbutton') {
 		    erase_list.push(node);
 		}
 	    });
 
-	    Ext.Array.forEach(erase_list, function(node) { node.erase(); });
+	    Ext.Array.forEach(erase_list, function(node) { list.removeChild(node, true); });
+	},
+    },
+
+    listeners: {
+	itemclick: function(tl, info) {
+	    if (info.node.data.id === 'datastores') {
+		return false;
+	    }
+	    if (info.node.data.id === 'addbutton') {
+		let me = this;
+		Ext.create('PBS.DataStoreEdit', {
+		    listeners: {
+			destroy: function() {
+			    me.rstore.reload();
+			},
+		    },
+		}).show();
+		return false;
+	    }
+	    return true;
 	},
     },
 
diff --git a/www/config/DataStoreConfig.js b/www/config/DataStoreConfig.js
deleted file mode 100644
index 440feea5..00000000
--- a/www/config/DataStoreConfig.js
+++ /dev/null
@@ -1,227 +0,0 @@
-Ext.define('pbs-datastore-list', {
-    extend: 'Ext.data.Model',
-    fields: ['name', 'comment'],
-    proxy: {
-        type: 'proxmox',
-	url: "/api2/json/admin/datastore",
-    },
-    idProperty: 'store',
-});
-
-Ext.define('pbs-data-store-config', {
-    extend: 'Ext.data.Model',
-    fields: [
-	'name', 'path', 'comment', 'gc-schedule', 'prune-schedule',
-	'keep-last', 'keep-hourly', 'keep-daily',
-	'keep-weekly', 'keep-monthly', 'keep-yearly',
-    ],
-    proxy: {
-        type: 'proxmox',
-	url: "/api2/json/config/datastore",
-    },
-    idProperty: 'name',
-});
-
-Ext.define('PBS.DataStoreConfig', {
-    extend: 'Ext.grid.GridPanel',
-    alias: 'widget.pbsDataStoreConfig',
-
-    title: gettext('Datastore Configuration'),
-
-    controller: {
-	xclass: 'Ext.app.ViewController',
-
-	createDataStore: function() {
-	    let me = this;
-	    Ext.create('PBS.DataStoreEdit', {
-		listeners: {
-		    destroy: function() {
-			me.reload();
-		    },
-		},
-	    }).show();
-	},
-
-	editDataStore: function() {
-	    let me = this;
-	    let view = me.getView();
-	    let selection = view.getSelection();
-	    if (selection.length < 1) return;
-
-	    let name = encodeURIComponent(selection[0].data.name);
-	    Ext.create('PBS.DataStoreEdit', {
-		name: name,
-		listeners: {
-		    destroy: function() {
-			me.reload();
-		    },
-		},
-	    }).show();
-	},
-
-	onVerify: function() {
-	    var view = this.getView();
-
-	    let rec = view.selModel.getSelection()[0];
-	    if (!(rec && rec.data)) return;
-	    let data = rec.data;
-
-	    Proxmox.Utils.API2Request({
-		url: `/admin/datastore/${data.name}/verify`,
-		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();
-		},
-	    });
-	},
-
-	garbageCollect: function() {
-	    let me = this;
-	    let view = me.getView();
-	    let selection = view.getSelection();
-	    if (selection.length < 1) return;
-
-	    let name = encodeURIComponent(selection[0].data.name);
-	    Proxmox.Utils.API2Request({
-		url: `/admin/datastore/${name}/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();
-		},
-	    });
-	},
-
-	reload: function() { this.getView().getStore().rstore.load(); },
-
-	init: function(view) {
-	    Proxmox.Utils.monStoreErrors(view, view.getStore().rstore);
-	},
-    },
-
-    store: {
-	type: 'diff',
-	autoDestroy: true,
-	autoDestroyRstore: true,
-	sorters: 'name',
-	rstore: {
-	    type: 'update',
-	    storeid: 'pbs-data-store-config',
-	    model: 'pbs-data-store-config',
-	    autoStart: true,
-	    interval: 10000,
-	},
-    },
-
-    tbar: [
-	{
-	    xtype: 'proxmoxButton',
-	    selModel: false,
-	    text: gettext('Create'),
-	    handler: 'createDataStore',
-	},
-	{
-	    xtype: 'proxmoxButton',
-	    text: gettext('Edit'),
-	    disabled: true,
-	    handler: 'editDataStore',
-	},
-	// remove_btn
-	'-',
-	{
-	    xtype: 'proxmoxButton',
-	    text: gettext('Verify'),
-	    disabled: true,
-	    handler: 'onVerify',
-	},
-	{
-	    xtype: 'proxmoxButton',
-	    text: gettext('Start GC'),
-	    disabled: true,
-	    handler: 'garbageCollect',
-	},
-    ],
-
-    columns: [
-	{
-	    header: gettext('Name'),
-	    sortable: true,
-	    dataIndex: 'name',
-	    flex: 1,
-	},
-	{
-	    header: gettext('Path'),
-	    sortable: true,
-	    dataIndex: 'path',
-	    flex: 1,
-	},
-	{
-	    header: gettext('GC Schedule'),
-	    sortable: false,
-	    width: 120,
-	    dataIndex: 'gc-schedule',
-	},
-	{
-	    header: gettext('Prune Schedule'),
-	    sortable: false,
-	    width: 120,
-	    dataIndex: 'prune-schedule',
-	},
-	{
-	    header: gettext('Keep'),
-	    columns: [
-		{
-		    text: gettext('Last'),
-		    dataIndex: 'keep-last',
-		    width: 70,
-		},
-		{
-		    text: gettext('Hourly'),
-		    dataIndex: 'keep-hourly',
-		    width: 70,
-		},
-		{
-		    text: gettext('Daily'),
-		    dataIndex: 'keep-daily',
-		    width: 70,
-		},
-		{
-		    text: gettext('Weekly'),
-		    dataIndex: 'keep-weekly',
-		    width: 70,
-		},
-		{
-		    text: gettext('Monthly'),
-		    dataIndex: 'keep-monthly',
-		    width: 70,
-		},
-		{
-		    text: gettext('Yearly'),
-		    dataIndex: 'keep-yearly',
-		    width: 70,
-		},
-	    ],
-	},
-	{
-	    header: gettext('Comment'),
-	    sortable: false,
-	    dataIndex: 'comment',
-	    renderer: Ext.String.htmlEncode,
-	    flex: 2,
-	},
-    ],
-
-    listeners: {
-	activate: 'reload',
-	itemdblclick: 'editDataStore',
-    },
-});
-- 
2.20.1





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 7/8] ui: MainView/NavigationTree: improve tree selection handling
  2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
                   ` (5 preceding siblings ...)
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 6/8] ui: NavigationTree: add 'Add Datastore' button below datastore list Dominik Csapak
@ 2020-10-27 15:20 ` Dominik Csapak
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 8/8] ui: DataStorePanel: save active tab statefully Dominik Csapak
  2020-10-27 16:55 ` [pbs-devel] applied-series: [PATCH proxmox-backup 0/8] improve datstore ux Thomas Lamprecht
  8 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2020-10-27 15:20 UTC (permalink / raw)
  To: pbs-devel

this fixes some bugs related to selection handling in the treelist:
* datastores were not selected after a reload
* reloading when in a tabpanel on any tab but the first, would
  not select a treenode
* changing between datastores on any tab but the first would
  not select the same tab on the new datastore

fixed those by mostly rewriting the changePath handling for
datastores and tabpanels in general

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/MainView.js       | 70 ++++++++++++++++++++++---------------------
 www/NavigationTree.js | 22 ++++++++++++--
 2 files changed, 55 insertions(+), 37 deletions(-)

diff --git a/www/MainView.js b/www/MainView.js
index cfd19058..7998e535 100644
--- a/www/MainView.js
+++ b/www/MainView.js
@@ -67,46 +67,48 @@ Ext.define('PBS.MainView', {
 	    var contentpanel = me.lookupReference('contentpanel');
 	    var lastpanel = contentpanel.getLayout().getActiveItem();
 
-	    var obj;
-	    if (PBS.Utils.isDataStorePath(path)) {
-		let datastore = PBS.Utils.getDataStoreFromPath(path);
-		obj = contentpanel.add({
-		    xtype: 'pbsDataStorePanel',
-		    nodename: 'localhost',
-		    datastore,
-		});
-	    } else {
-		obj = contentpanel.add({
-		    xtype: path,
-		    nodename: 'localhost',
-		    border: false,
-		});
-	    }
+	    let tabChangeListener = function(tp, newc, oldc) {
+		let newpath = path;
 
-	    var treelist = me.lookupReference('navtree');
-
-	    treelist.suspendEvents();
-	    if (subpath === undefined) {
-		treelist.select(path);
-	    } else {
-		treelist.select(path + ':' + subpath);
-	    }
-	    treelist.resumeEvents();
+		// only add the subpath part for the
+		// non-default tabs
+		if (tp.items.findIndex('id', newc.id) !== 0) {
+		    newpath += `:${newc.getItemId()}`;
+		}
 
-	    if (Ext.isFunction(obj.setActiveTab)) {
-		obj.setActiveTab(subpath || 0);
-		obj.addListener('tabchange', function(tabpanel, newc, oldc) {
-		    var newpath = path;
+		me.redirectTo(newpath);
+	    };
 
-		    // only add the subpath part for the
-		    // non-default tabs
-		    if (tabpanel.items.findIndex('id', newc.id) !== 0) {
-			newpath += ":" + newc.getItemId();
+	    let xtype = path;
+	    var obj;
+	    let datastore;
+	    if (PBS.Utils.isDataStorePath(path)) {
+		datastore = PBS.Utils.getDataStoreFromPath(path);
+		if (lastpanel && lastpanel.xtype === 'pbsDataStorePanel' && !subpath) {
+		    let activeTab = lastpanel.getActiveTab();
+		    let newpath = path;
+		    if (lastpanel.items.indexOf(activeTab) !== 0) {
+			subpath = activeTab.getItemId();
+			newpath += `:${subpath}`;
 		    }
-
 		    me.redirectTo(newpath);
-		});
+		}
+		xtype = 'pbsDataStorePanel';
 	    }
+	    obj = contentpanel.add({
+		xtype,
+		datastore,
+		nodename: 'localhost',
+		border: false,
+		activeTab: subpath || 0,
+		listeners: {
+		    tabchange: tabChangeListener,
+		},
+	    });
+
+	    var treelist = me.lookupReference('navtree');
+
+	    treelist.select(path, true);
 
 	    contentpanel.setActiveItem(obj);
 
diff --git a/www/NavigationTree.js b/www/NavigationTree.js
index 54e0adeb..6524a5c3 100644
--- a/www/NavigationTree.js
+++ b/www/NavigationTree.js
@@ -163,6 +163,12 @@ Ext.define('PBS.view.main.NavigationTree', {
 	    });
 
 	    Ext.Array.forEach(erase_list, function(node) { list.removeChild(node, true); });
+
+	    if (view.pathToSelect !== undefined) {
+		let path = view.pathToSelect;
+		delete view.pathToSelect;
+		view.select(path, true);
+	    }
 	},
     },
 
@@ -186,10 +192,20 @@ Ext.define('PBS.view.main.NavigationTree', {
 	},
     },
 
-    select: function(path) {
+    select: function(path, silent) {
 	var me = this;
-	var item = me.getStore().findRecord('path', path, 0, false, true, true);
-	me.setSelection(item);
+	if (me.rstore.isLoaded()) {
+	    if (silent) {
+		me.suspendEvents(false);
+	    }
+	    var item = me.getStore().findRecord('path', path, 0, false, true, true);
+	    me.setSelection(item);
+	    if (silent) {
+		me.resumeEvents(true);
+	    }
+	} else {
+	    me.pathToSelect = path;
+	}
     },
 
     animation: false,
-- 
2.20.1





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pbs-devel] [PATCH proxmox-backup 8/8] ui: DataStorePanel: save active tab statefully
  2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
                   ` (6 preceding siblings ...)
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 7/8] ui: MainView/NavigationTree: improve tree selection handling Dominik Csapak
@ 2020-10-27 15:20 ` Dominik Csapak
  2020-10-27 16:55 ` [pbs-devel] applied-series: [PATCH proxmox-backup 0/8] improve datstore ux Thomas Lamprecht
  8 siblings, 0 replies; 10+ messages in thread
From: Dominik Csapak @ 2020-10-27 15:20 UTC (permalink / raw)
  To: pbs-devel

so that the last selected tab for datastores will get selected
the next time any datastore is selected, even across browser
reloads

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/DataStorePanel.js | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/www/DataStorePanel.js b/www/DataStorePanel.js
index 059fd3b2..0da94361 100644
--- a/www/DataStorePanel.js
+++ b/www/DataStorePanel.js
@@ -10,6 +10,25 @@ Ext.define('PBS.DataStorePanel', {
 	};
     },
 
+    stateId: 'pbs-datastore-panel',
+    stateful: true,
+
+    stateEvents: ['tabchange'],
+
+    applyState: function(state) {
+	let me = this;
+	if (state.tab !== undefined) {
+	    me.setActiveTab(state.tab);
+	}
+    },
+
+    getState: function() {
+	let me = this;
+	return {
+	    tab: me.getActiveTab().getItemId(),
+	};
+    },
+
     border: false,
     defaults: {
 	border: false,
-- 
2.20.1





^ permalink raw reply	[flat|nested] 10+ messages in thread

* [pbs-devel] applied-series: [PATCH proxmox-backup 0/8] improve datstore ux
  2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
                   ` (7 preceding siblings ...)
  2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 8/8] ui: DataStorePanel: save active tab statefully Dominik Csapak
@ 2020-10-27 16:55 ` Thomas Lamprecht
  8 siblings, 0 replies; 10+ messages in thread
From: Thomas Lamprecht @ 2020-10-27 16:55 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Dominik Csapak

On 27.10.20 16:20, Dominik Csapak wrote:
> this series aims to improve the datastore ux by
> * moving all datastore relevant settings/options/views into the
>   datastore tabpanel
> * adding a 'Summary' panel with some important information
> * improving tab selection between datastores/browser reloads/etc.
> 
> this needs my previous series: "improve and extend admin/datastore/status api"
> to work
> 
> some things are still to be improved, namely:
> * multiline comments for datastores
> * automatic ids for sync/verify jobs
> * improved prune/gc panel (e.g. with status of last run)
> * comments for backup snapshots
> 
> Dominik Csapak (8):
>   api/{verify,syncjobs}: add optional datastore parameter
>   ui: DataStoreContent: add 'Verify All' button
>   ui: add DataStorePruneAndGC panel and add it to datastore panel
>   ui: add DataStoreSummary and move Statistics into it
>   ui: move sync/verify jobs to the datastores
>   ui: NavigationTree: add 'Add Datastore' button below datastore list
>   ui: MainView/NavigationTree: improve tree selection handling
>   ui: DataStorePanel: save active tab statefully
> 
>  src/api2/admin/sync.rs        |  19 ++-
>  src/api2/admin/verify.rs      |  19 ++-
>  www/DataStoreContent.js       |  23 +++
>  www/DataStoreNotes.js         | 104 ++++++++++++
>  www/DataStorePanel.js         |  52 +++++-
>  www/DataStorePruneAndGC.js    | 164 +++++++++++++++++++
>  www/DataStoreStatistic.js     | 104 ------------
>  www/DataStoreSummary.js       | 296 ++++++++++++++++++++++++++++++++++
>  www/MainView.js               |  70 ++++----
>  www/Makefile                  |   5 +-
>  www/NavigationTree.js         | 101 ++++++++----
>  www/config/DataStoreConfig.js | 227 --------------------------
>  www/config/SyncView.js        |  14 +-
>  www/config/VerifyView.js      |  20 ++-
>  www/window/DataStoreEdit.js   | 147 +++++++++--------
>  www/window/SyncJobEdit.js     |   6 +-
>  www/window/VerifyJobEdit.js   |   6 +-
>  17 files changed, 898 insertions(+), 479 deletions(-)
>  create mode 100644 www/DataStoreNotes.js
>  create mode 100644 www/DataStorePruneAndGC.js
>  delete mode 100644 www/DataStoreStatistic.js
>  create mode 100644 www/DataStoreSummary.js
>  delete mode 100644 www/config/DataStoreConfig.js
> 



applied series, much thanks! FYI, pushed the following small patches on top:

ui: datastore summary: clarify that it's a deduplication factor
ui: datastore: used fixed-width icons for summary
ui: datastore: change GC/Prune title and buttons a bit
ui: datastore: add confirmation message to verify all


Some possible additions and thoughts:

* warning on summary if no GC, prune or verify job is configured (especially verify should
  be an warning-triangle, the other could be "notice-exclamation-mark" level)

* The "Removed Bytes" in the summary is a bit strange to me, I'd rather drop it

* make the backup count thing aligned like a table, with the Groups and Snapshots as column
  header, and maybe a "Total" row at the end with all summed up

* not sure where we'd put the only recently added "verify_new" datastore option, it could be
  possible to rename "Verify Jobs" to "Verification" and add a option panel over the jobs one,
  would only host that setting for now, though.

* onlineHelp buttons for all panels and their edit dialogs please (@Dylan, @Oguz, @all)





^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2020-10-27 16:56 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-27 15:20 [pbs-devel] [PATCH proxmox-backup 0/8] improve datstore ux Dominik Csapak
2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 1/8] api/{verify, syncjobs}: add optional datastore parameter Dominik Csapak
2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 2/8] ui: DataStoreContent: add 'Verify All' button Dominik Csapak
2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 3/8] ui: add DataStorePruneAndGC panel and add it to datastore panel Dominik Csapak
2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 4/8] ui: add DataStoreSummary and move Statistics into it Dominik Csapak
2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 5/8] ui: move sync/verify jobs to the datastores Dominik Csapak
2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 6/8] ui: NavigationTree: add 'Add Datastore' button below datastore list Dominik Csapak
2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 7/8] ui: MainView/NavigationTree: improve tree selection handling Dominik Csapak
2020-10-27 15:20 ` [pbs-devel] [PATCH proxmox-backup 8/8] ui: DataStorePanel: save active tab statefully Dominik Csapak
2020-10-27 16:55 ` [pbs-devel] applied-series: [PATCH proxmox-backup 0/8] improve datstore ux Thomas Lamprecht

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal