[pbs-devel] [PATCH proxmox-backup 09/10] ui: add Panels necessary for Datastores Overview

Dominik Csapak d.csapak at proxmox.com
Mon Nov 9 16:01:29 CET 2020


a panel for a single datastore that gets updated from an external caller
shows the usage, estimated full date, history and task summary grid

a panel that dynamically generates the panel above for each datastore

and a tabpanel that includes the panel above, as well as a global
syncview, verifiyview and aclview

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 www/Makefile                          |   2 +
 www/datastore/DataStoreList.js        | 229 ++++++++++++++++++++++++++
 www/datastore/DataStoreListSummary.js | 138 ++++++++++++++++
 3 files changed, 369 insertions(+)
 create mode 100644 www/datastore/DataStoreList.js
 create mode 100644 www/datastore/DataStoreListSummary.js

diff --git a/www/Makefile b/www/Makefile
index f9d8f8e4..192341ec 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -56,6 +56,8 @@ JSSRC=							\
 	datastore/Content.js				\
 	datastore/OptionView.js				\
 	datastore/Panel.js				\
+	datastore/DataStoreListSummary.js		\
+	datastore/DataStoreList.js			\
 	ServerStatus.js					\
 	ServerAdministration.js				\
 	Dashboard.js					\
diff --git a/www/datastore/DataStoreList.js b/www/datastore/DataStoreList.js
new file mode 100644
index 00000000..71dd5fdb
--- /dev/null
+++ b/www/datastore/DataStoreList.js
@@ -0,0 +1,229 @@
+// Overview over all datastores
+Ext.define('PBS.datastore.DataStoreList', {
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pbsDataStoreList',
+
+    title: gettext('Summary'),
+
+    scrollable: true,
+
+    bodyPadding: 5,
+    defaults: {
+	xtype: 'pbsDataStoreListSummary',
+	padding: 5,
+    },
+
+    datastores: {},
+    tasks: {},
+
+    updateTasks: function(taskStore, records, success) {
+	let me = this;
+	if (!success) {
+	    return;
+	}
+
+	for (const store of Object.keys(me.tasks)) {
+	    me.tasks[store] = {};
+	}
+
+	records.forEach(record => {
+	    let task = record.data;
+	    if (!task.worker_id) {
+		return;
+	    }
+
+	    let type = task.worker_type;
+	    if (type === 'syncjob') {
+		type = 'sync';
+	    }
+
+	    if (type.startsWith('verif')) {
+		type = 'verify';
+	    }
+
+	    let datastore = PBS.Utils.parse_datastore_worker_id(type, task.worker_id);
+	    if (!datastore) {
+		return;
+	    }
+
+	    if (!me.tasks[datastore]) {
+		me.tasks[datastore] = {};
+	    }
+
+	    if (!me.tasks[datastore][type]) {
+		me.tasks[datastore][type] = {};
+	    }
+
+	    if (me.tasks[datastore][type] && task.status) {
+		let parsed = Proxmox.Utils.parse_task_status(task.status);
+		if (!me.tasks[datastore][type][parsed]) {
+		    me.tasks[datastore][type][parsed] = 0;
+		}
+		me.tasks[datastore][type][parsed]++;
+	    }
+	});
+
+	for (const [store, panel] of Object.entries(me.datastores)) {
+	    panel.setTasks(me.tasks[store], me.since);
+	}
+    },
+
+    updateStores: function(usageStore, records, success) {
+	let me = this;
+	if (!success) {
+	    return;
+	}
+
+	let found = {};
+
+	records.forEach((rec) => {
+	    found[rec.data.store] = true;
+	    me.addSorted(rec.data);
+	});
+
+	for (const [store, panel] of Object.entries(me.datastores)) {
+	    if (!found[store]) {
+		me.remove(panel);
+	    }
+	}
+    },
+
+    addSorted: function(data) {
+	let me = this;
+	let i = 0;
+	let datastores = Object
+	    .keys(me.datastores)
+	    .sort((a, b) => a.localeCompare(b));
+
+	for (const datastore of datastores) {
+	    let result = datastore.localeCompare(data.store);
+	    if (result === 0) {
+		me.datastores[datastore].setStatus(data);
+		return;
+	    } else if (result > 0) {
+		break;
+	    }
+	    i++;
+	}
+
+	me.datastores[data.store] = me.insert(i, {
+	    datastore: data.store,
+	});
+	me.datastores[data.store].setStatus(data);
+	me.datastores[data.store].setTasks(me.tasks[data.store], me.since);
+    },
+
+    initComponent: function() {
+	let me = this;
+	me.items = [];
+	me.datastores = {};
+	// todo make configurable?
+	me.since = (Date.now()/1000 - 30 * 24*3600).toFixed(0);
+
+	me.usageStore = Ext.create('Proxmox.data.UpdateStore', {
+	    storeid: 'datastore-overview-usage',
+	    interval: 5000,
+	    proxy: {
+		type: 'proxmox',
+		url: '/api2/json/status/datastore-usage',
+	    },
+	    listeners: {
+		load: {
+		    fn: me.updateStores,
+		    scope: me,
+		},
+	    },
+	});
+
+	me.taskStore = Ext.create('Proxmox.data.UpdateStore', {
+	    storeid: 'datastore-overview-tasks',
+	    interval: 15000,
+	    model: 'proxmox-tasks',
+	    proxy: {
+		type: 'proxmox',
+		url: '/api2/json/nodes/localhost/tasks',
+		extraParams: {
+		    limit: 0,
+		    since: me.since,
+		},
+	    },
+	    listeners: {
+		load: {
+		    fn: me.updateTasks,
+		    scope: me,
+		},
+	    },
+	});
+
+	me.callParent();
+	Proxmox.Utils.monStoreErrors(me, me.usageStore);
+	Proxmox.Utils.monStoreErrors(me, me.taskStore);
+	me.on('activate', function() {
+	    me.usageStore.startUpdate();
+	    me.taskStore.startUpdate();
+	});
+	me.on('destroy', function() {
+	    me.usageStore.stopUpdate();
+	    me.taskStore.stopUpdate();
+	});
+	me.on('deactivate', function() {
+	    me.usageStore.stopUpdate();
+	    me.taskStore.stopUpdate();
+	});
+    },
+});
+
+Ext.define('PBS.datastore.DataStores', {
+    extend: 'Ext.tab.Panel',
+    alias: 'widget.pbsDataStores',
+
+    title: gettext('Datastores'),
+
+    stateId: 'pbs-datastores-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,
+    },
+
+    items: [
+	{
+	    xtype: 'pbsDataStoreList',
+	    iconCls: 'fa fa-book',
+	},
+
+	{
+	    iconCls: 'fa fa-refresh',
+	    itemId: 'syncjobs',
+	    xtype: 'pbsSyncJobView',
+	},
+	{
+	    iconCls: 'fa fa-check-circle',
+	    itemId: 'verifyjobs',
+	    xtype: 'pbsVerifyJobView',
+	},
+	{
+	    itemId: 'acl',
+	    xtype: 'pbsACLView',
+	    iconCls: 'fa fa-unlock',
+	    aclPath: '/datastore',
+	},
+    ],
+});
diff --git a/www/datastore/DataStoreListSummary.js b/www/datastore/DataStoreListSummary.js
new file mode 100644
index 00000000..a9018a7c
--- /dev/null
+++ b/www/datastore/DataStoreListSummary.js
@@ -0,0 +1,138 @@
+// Summary Panel for a single datastore in overview
+Ext.define('PBS.datastore.DataStoreListSummary', {
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pbsDataStoreListSummary',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    cbind: {
+	title: '{datastore}',
+    },
+    bodyPadding: 10,
+
+    viewModel: {
+	data: {
+	    usage: "N/A",
+	    full: "N/A",
+	    history: [],
+	},
+
+	stores: {
+	    historystore: {
+		data: [],
+	    },
+	},
+    },
+    setTasks: function(taskdata, since) {
+	let me = this;
+	me.down('pbsTaskSummary').updateTasks(taskdata, since);
+    },
+
+    setStatus: function(statusData) {
+	let me = this;
+	let vm = me.getViewModel();
+	vm.set('usagetext', PBS.Utils.render_size_usage(statusData.used, statusData.total));
+	vm.set('usage', statusData.used/statusData.total);
+	let estimate = PBS.Utils.render_estimate(statusData['estimated-full-date']);
+	vm.set('full', estimate);
+	let last = 0;
+	let data = statusData.history.map((val) => {
+	    if (val === null) {
+		val = last;
+	    } else {
+		last = val;
+	    }
+	    return val;
+	});
+	let historyStore = vm.getStore('historystore');
+	historyStore.setData([
+	    {
+		history: data,
+	    },
+	]);
+    },
+
+    items: [
+	{
+	    xtype: 'container',
+	    layout: {
+		type: 'hbox',
+		align: 'stretch',
+	    },
+
+	    defaults: {
+		flex: 1,
+		padding: 5,
+	    },
+
+	    items: [
+		{
+		    xtype: 'pmxInfoWidget',
+		    iconCls: 'fa fa-fw fa-hdd-o',
+		    title: gettext('Usage'),
+		    bind: {
+			data: {
+			    usage: '{usage}',
+			    text: '{usagetext}',
+			},
+		    },
+		},
+		{
+		    xtype: 'pmxInfoWidget',
+		    title: gettext('Estimated Full'),
+		    printBar: false,
+		    bind: {
+			data: {
+			    usage: '0',
+			    text: '{full}',
+			},
+		    },
+		},
+	    ],
+	},
+	{
+	    // we cannot autosize a sparklineline widget,
+	    // abuse a grid with a single column/row to do it for us
+	    xtype: 'grid',
+	    hideHeaders: true,
+	    minHeight: 50,
+	    border: false,
+	    bodyBorder: false,
+	    rowLines: false,
+	    disableSelection: true,
+	    viewConfig: {
+		trackOver: false,
+	    },
+	    bind: {
+		store: '{historystore}',
+	    },
+	    columns: [{
+		xtype: 'widgetcolumn',
+		flex: 1,
+		dataIndex: 'history',
+		widget: {
+		    xtype: 'sparklineline',
+		    bind: '{record.history}',
+		    spotRadius: 0,
+		    fillColor: '#ddd',
+		    lineColor: '#555',
+		    lineWidth: 0,
+		    chartRangeMin: 0,
+		    chartRangeMax: 1,
+		    tipTpl: '{y:number("0.00")*100}%',
+		    height: 40,
+		},
+	    }],
+	},
+	{
+	    xtype: 'pbsTaskSummary',
+	    border: false,
+	    header: false,
+	    subPanelModal: true,
+	    bodyPadding: 0,
+	    minHeight: 0,
+	    cbind: {
+		datastore: '{datastore}',
+	    },
+	},
+    ],
+});
-- 
2.20.1






More information about the pbs-devel mailing list