[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