proxmox-backup/www/datastore/Summary.js
Thomas Lamprecht f784201c63 ui: datastore summary: start store updates after mount task is done
Without this, we immediately start the store updates even before the
browser created the (async) mount API request. So it's very likely
that the first store load will still get an error due to the backing
device of the datastore not being mounted yet. That in turn will
trigger our error detection behavior in the load even listener and
disable periodic store updates again.

Move the start of the update into the taskDone handler. We do not need
to check if the task succeeded, as either it did, and we will do
periodic updates, or it did not and we do at least one update to load
the current status and then stop again auto-loading the store anyway.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2024-11-25 21:34:22 +01:00

453 lines
11 KiB
JavaScript

Ext.define('pve-rrd-datastore', {
extend: 'Ext.data.Model',
fields: [
'used',
'total',
{
name: 'unpriv-total', // Can't reuse 'total' here as that creates a stack overflow
calculate: function(data) {
let used = data.used;
let avail = data.available;
if (avail && used) {
return avail + used;
}
return data.total;
},
},
'available',
'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,
mountpoint: "",
},
},
controller: {
xclass: 'Ext.app.ViewController',
onLoad: function(store, data, success) {
let me = this;
if (!success) {
Proxmox.Utils.API2Request({
url: `/config/datastore/${me.view.datastore}`,
success: function(response) {
let maintenanceString = response.result.data['maintenance-mode'];
let removable = !!response.result.data['backing-device'];
if (!maintenanceString && !removable) {
me.view.el.mask(gettext('Datastore is not available'));
return;
}
let [_type, msg] = PBS.Utils.parseMaintenanceMode(maintenanceString);
let isUnplugged = !maintenanceString && removable;
let maskMessage = isUnplugged
? gettext('Datastore is not mounted')
: `${gettext('Datastore is in maintenance mode')}${msg ? ': ' + msg : ''}`;
let maskIcon = isUnplugged ? 'fa pbs-unplugged-mask' : 'fa pbs-maintenance-mask';
me.view.el.mask(maskMessage, maskIcon);
},
});
return;
}
me.view.el.unmask();
let vm = me.getViewModel();
let counts = store.getById('counts').data.value;
let used = store.getById('used').data.value;
let total = store.getById('avail').data.value + used;
let usage = Proxmox.Utils.render_size_usage(used, total, true);
vm.set('usagetext', usage);
vm.set('usage', used/total);
let countstext = function(count) {
count = count || {};
return `${count.groups || 0} ${gettext('Groups')}, ${count.snapshots || 0} ${gettext('Snapshots')}`;
};
let gcstatus = store.getById('gc-status')?.data.value;
if (gcstatus) {
let dedup = PBS.Utils.calculate_dedup_factor(gcstatus);
vm.set('deduplication', dedup.toFixed(2));
vm.set('stillbad', gcstatus['still-bad']);
}
vm.set('ctcount', countstext(counts.ct));
vm.set('vmcount', countstext(counts.vm));
vm.set('hostcount', countstext(counts.host));
},
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/?verbose=true`,
});
me.store.on('load', me.onLoad, me);
},
},
listeners: {
activate: 'startStore',
beforedestroy: 'stopStore',
deactivate: 'stopStore',
},
defaults: {
xtype: 'pmxInfoWidget',
},
bodyPadding: 20,
items: [
{
iconCls: 'fa fa-fw 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-fw fa-cube',
title: gettext('CT'),
printBar: false,
bind: {
data: {
text: '{ctcount}',
},
},
},
{
iconCls: 'fa fa-fw fa-building',
title: gettext('Host'),
printBar: false,
bind: {
data: {
text: '{hostcount}',
},
},
},
{
iconCls: 'fa fa-fw 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-fw fa-compress',
title: gettext('Deduplication Factor'),
printBar: false,
bind: {
data: {
text: '{deduplication}',
},
},
},
{
iconCls: 'fa critical fa-fw 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: 'button',
text: gettext('Show Connection Information'),
handler: function() {
let me = this;
let datastore = me.up('panel').datastore;
Ext.create('PBS.window.DatastoreRepoInfo', {
datastore,
autoShow: true,
});
},
},
{ xtype: 'tbseparator', reference: 'mountButtonSeparator', hidden: true },
{
xtype: 'button',
text: gettext('Unmount'),
hidden: true,
itemId: 'unmountButton',
reference: 'unmountButton',
handler: function() {
let me = this;
let datastore = me.up('panel').datastore;
Proxmox.Utils.API2Request({
url: `/admin/datastore/${datastore}/unmount`,
method: 'POST',
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
success: function(response, options) {
Ext.create('Proxmox.window.TaskViewer', {
upid: response.result.data,
}).show();
},
});
},
},
{
xtype: 'button',
text: gettext('Mount'),
hidden: true,
itemId: 'mountButton',
reference: 'mountButton',
handler: function() {
let me = this;
let datastore = me.up('panel').datastore;
Proxmox.Utils.API2Request({
url: `/admin/datastore/${datastore}/mount`,
method: 'POST',
failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
success: function(response, options) {
Ext.create('Proxmox.window.TaskViewer', {
upid: response.result.data,
taskDone: () => me.up('panel').statusStore.startUpdate(),
}).show();
},
});
},
},
'->',
{
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: ['unpriv-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',
itemId: 'ioDelayChart',
hidden: true,
title: gettext('IO Delay (ms)'),
fields: ['io_delay'],
fieldTitles: [gettext('IO Delay')],
},
],
listeners: {
activate: function() { this.rrdstore.startUpdate(); },
afterrender: function() { this.statusStore.startUpdate(); },
deactivate: function() { this.rrdstore.stopUpdate(); },
destroy: function() {
this.rrdstore.stopUpdate();
this.statusStore.stopUpdate();
},
resize: function(panel) {
Proxmox.Utils.updateColumns(panel);
},
},
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.statusStore = Ext.create('Proxmox.data.ObjectStore', {
url: `/api2/json/admin/datastore/${me.datastore}/status`,
interval: 1000,
});
me.mon(me.statusStore, 'load', (s, records, success) => {
let mountBtn = me.lookupReferenceHolder().lookupReference('mountButton');
let unmountBtn = me.lookupReferenceHolder().lookupReference('unmountButton');
if (!success) {
me.statusStore.stopUpdate();
me.down('pbsDataStoreInfo').fireEvent('deactivate');
Proxmox.Utils.API2Request({
url: `/config/datastore/${me.datastore}`,
success: response => {
let mode = response.result.data['maintenance-mode'];
let [type, _message] = PBS.Utils.parseMaintenanceMode(mode);
if (!response.result.data['backing-device']) {
return;
}
if (!type || type === 'read-only') {
unmountBtn.setDisabled(true);
mountBtn.setDisabled(false);
} else if (type === 'unmount') {
unmountBtn.setDisabled(true);
mountBtn.setDisabled(true);
} else {
unmountBtn.setDisabled(false);
mountBtn.setDisabled(false);
}
},
});
} else {
me.down('pbsDataStoreInfo').fireEvent('activate');
unmountBtn.setDisabled(false);
mountBtn.setDisabled(true);
}
});
let sp = Ext.state.Manager.getProvider();
me.mon(sp, 'statechange', function(provider, key, value) {
if (key !== 'summarycolumns') {
return;
}
if (!me.rendered) {
return;
}
Proxmox.Utils.updateColumns(me);
});
me.callParent();
Proxmox.Utils.API2Request({
url: `/config/datastore/${me.datastore}`,
waitMsgTarget: me.down('pbsDataStoreInfo'),
success: function(response) {
let data = response.result.data;
const removable = !!data['backing-device'];
me.lookupReferenceHolder().lookupReference('mountButtonSeparator').setHidden(!removable);
me.lookupReferenceHolder().lookupReference('mountButton').setHidden(!removable);
me.lookupReferenceHolder().lookupReference('unmountButton').setHidden(!removable);
let path = Ext.htmlEncode(data.path);
me.down('pbsDataStoreInfo').setTitle(`${me.datastore} (${path})`);
me.down('pbsDataStoreNotes').setNotes(data.comment);
},
failure: function(response) {
// fallback if e.g. we have no permissions to the config
let rec = Ext.getStore('pbs-datastore-list')
.findRecord('store', me.datastore, 0, false, true, true);
if (rec) {
me.down('pbsDataStoreNotes').setNotes(rec.data.comment || "");
}
},
});
me.mon(me.rrdstore, 'load', function(store, records, success) {
let hasIoTicks = records?.some((rec) => rec?.data?.io_ticks !== undefined);
me.down('#ioDelayChart').setVisible(!success || hasIoTicks);
}, undefined, { single: true });
me.query('proxmoxRRDChart').forEach((chart) => {
chart.setStore(me.rrdstore);
});
me.down('pbsDataStoreInfo').relayEvents(me, ['activate', 'deactivate']);
},
});