* [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