From 68931742cb30222fd57ad50273d57dc86eb7b94c Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Tue, 27 Oct 2020 16:20:07 +0100 Subject: [PATCH] ui: add DataStoreSummary and move Statistics into it 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 --- 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: `${gettext('Backup Count')}`, + 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: `${gettext('Stats from last Garbage Collection')}`, + 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.39.2