mirror of
https://git.proxmox.com/git/proxmox-backup
synced 2025-08-04 18:31:31 +00:00

Disabling basically was already done only on an transition edge from "success" -> "failure" (= !success), as we stopped the periodic store load in that case, thus we never trigger to "failures" after each other without any user input. But on success we always unconditionally fired an activate, which cause the status store to start its store updates, which in turn immediately triggered as store load. So the verbose status call of the info panel was now coupled to the 1s update period of the encompassing summary panel, not the slower 5s period it actually wanted to trigger an update. So save the last state and check if it actually differs before causing such action. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
461 lines
11 KiB
JavaScript
461 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,
|
|
taskDone: () => me.up('panel').statusStore.load(),
|
|
}).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,
|
|
});
|
|
|
|
let lastRequestWasFailue = false;
|
|
me.mon(me.statusStore, 'load', (s, records, success) => {
|
|
let mountBtn = me.lookupReferenceHolder().lookupReference('mountButton');
|
|
let unmountBtn = me.lookupReferenceHolder().lookupReference('unmountButton');
|
|
if (!success) {
|
|
lastRequestWasFailue = true;
|
|
|
|
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 {
|
|
// only trigger on edges, else we couple our interval to the info one
|
|
if (lastRequestWasFailue) {
|
|
me.down('pbsDataStoreInfo').fireEvent('activate');
|
|
}
|
|
unmountBtn.setDisabled(false);
|
|
mountBtn.setDisabled(true);
|
|
lastRequestWasFailue = false;
|
|
}
|
|
});
|
|
|
|
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']);
|
|
},
|
|
});
|