all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Dominik Csapak <d.csapak@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox-backup 4/8] ui: add DataStoreSummary and move Statistics into it
Date: Tue, 27 Oct 2020 16:20:07 +0100	[thread overview]
Message-ID: <20201027152011.7373-5-d.csapak@proxmox.com> (raw)
In-Reply-To: <20201027152011.7373-1-d.csapak@proxmox.com>

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





  parent reply	other threads:[~2020-10-27 15:21 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Dominik Csapak [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201027152011.7373-5-d.csapak@proxmox.com \
    --to=d.csapak@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal