mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-07-25 17:46:52 +00:00

inline the transformation for the health store and also avoid setting raw data from the outside and drop some bogus comments along the way, first one should mostly use "why?" not "what happens?" comments and second, commenting straight forward things always makes one pause and recheck everything far to often, as a comment indicates there something non-obvious happening. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
414 lines
9.9 KiB
JavaScript
414 lines
9.9 KiB
JavaScript
Ext.define('PVE.node.CephStatus', {
|
|
extend: 'Ext.panel.Panel',
|
|
alias: 'widget.pveNodeCephStatus',
|
|
|
|
onlineHelp: 'chapter_pveceph',
|
|
|
|
scrollable: true,
|
|
bodyPadding: 5,
|
|
layout: {
|
|
type: 'column',
|
|
},
|
|
|
|
defaults: {
|
|
padding: 5,
|
|
},
|
|
|
|
items: [
|
|
{
|
|
xtype: 'panel',
|
|
title: gettext('Health'),
|
|
bodyPadding: 10,
|
|
plugins: 'responsive',
|
|
responsiveConfig: {
|
|
'width < 1600': {
|
|
minHeight: 230,
|
|
columnWidth: 1,
|
|
},
|
|
'width >= 1600': {
|
|
minHeight: 500,
|
|
columnWidth: 0.5,
|
|
},
|
|
},
|
|
layout: {
|
|
type: 'hbox',
|
|
align: 'stretch',
|
|
},
|
|
items: [
|
|
{
|
|
xtype: 'container',
|
|
layout: {
|
|
type: 'vbox',
|
|
align: 'stretch',
|
|
},
|
|
flex: 1,
|
|
items: [
|
|
{
|
|
|
|
xtype: 'pveHealthWidget',
|
|
itemId: 'overallhealth',
|
|
flex: 1,
|
|
title: gettext('Status'),
|
|
},
|
|
{
|
|
xtype: 'displayfield',
|
|
itemId: 'versioninfo',
|
|
fieldLabel: gettext('Ceph Version'),
|
|
value: "",
|
|
autoEl: {
|
|
tag: 'div',
|
|
'data-qtip': gettext('The newest version installed in the Cluster.'),
|
|
},
|
|
padding: '10 0 0 0',
|
|
style: {
|
|
'text-align': 'center',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
xtype: 'grid',
|
|
itemId: 'warnings',
|
|
flex: 2,
|
|
stateful: true,
|
|
stateId: 'ceph-status-warnings',
|
|
// we load the store manually, to show an emptyText specify an empty intermediate store
|
|
store: {
|
|
trackRemoved: false,
|
|
data: [],
|
|
},
|
|
updateHealth: function(health) {
|
|
let checks = health.checks || {};
|
|
|
|
let checkRecords = Object.keys(checks).sort().map(key => {
|
|
let check = checks[key];
|
|
return {
|
|
id: key,
|
|
summary: check.summary.message,
|
|
detail: check.detail.reduce((acc, v) => `${acc}\n${v.message}`, ''),
|
|
severity: check.severity,
|
|
};
|
|
});
|
|
|
|
this.getStore().loadRawData(checkRecords, false);
|
|
},
|
|
emptyText: gettext('No Warnings/Errors'),
|
|
columns: [
|
|
{
|
|
dataIndex: 'severity',
|
|
header: gettext('Severity'),
|
|
align: 'center',
|
|
width: 70,
|
|
renderer: function(value) {
|
|
let health = PVE.Utils.map_ceph_health[value];
|
|
let icon = PVE.Utils.get_health_icon(health);
|
|
return `<i class="fa fa-fw ${icon}"></i>`;
|
|
},
|
|
sorter: {
|
|
sorterFn: function(a, b) {
|
|
let health = ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK'];
|
|
return health.indexOf(b.data.severity) - health.indexOf(a.data.severity);
|
|
},
|
|
},
|
|
},
|
|
{
|
|
dataIndex: 'summary',
|
|
header: gettext('Summary'),
|
|
flex: 1,
|
|
},
|
|
{
|
|
xtype: 'actioncolumn',
|
|
width: 40,
|
|
align: 'center',
|
|
tooltip: gettext('Detail'),
|
|
items: [
|
|
{
|
|
iconCls: 'x-fa fa-info-circle',
|
|
handler: function(grid, rowindex, colindex, item, e, record) {
|
|
var win = Ext.create('Ext.window.Window', {
|
|
title: gettext('Detail'),
|
|
resizable: true,
|
|
modal: true,
|
|
width: 650,
|
|
height: 400,
|
|
layout: {
|
|
type: 'fit',
|
|
},
|
|
items: [{
|
|
scrollable: true,
|
|
padding: 10,
|
|
xtype: 'box',
|
|
html: [
|
|
'<span>' + Ext.htmlEncode(record.data.summary) + '</span>',
|
|
'<pre>' + Ext.htmlEncode(record.data.detail) + '</pre>',
|
|
],
|
|
}],
|
|
});
|
|
win.show();
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
xtype: 'pveCephStatusDetail',
|
|
itemId: 'statusdetail',
|
|
plugins: 'responsive',
|
|
responsiveConfig: {
|
|
'width < 1600': {
|
|
columnWidth: 1,
|
|
minHeight: 250,
|
|
},
|
|
'width >= 1600': {
|
|
columnWidth: 0.5,
|
|
minHeight: 300,
|
|
},
|
|
},
|
|
title: gettext('Status'),
|
|
},
|
|
{
|
|
xtype: 'pveCephServices',
|
|
title: gettext('Services'),
|
|
itemId: 'services',
|
|
plugins: 'responsive',
|
|
layout: {
|
|
type: 'hbox',
|
|
align: 'stretch',
|
|
},
|
|
responsiveConfig: {
|
|
'width < 1600': {
|
|
columnWidth: 1,
|
|
minHeight: 200,
|
|
},
|
|
'width >= 1600': {
|
|
columnWidth: 0.5,
|
|
minHeight: 200,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
xtype: 'panel',
|
|
title: gettext('Performance'),
|
|
columnWidth: 1,
|
|
bodyPadding: 5,
|
|
layout: {
|
|
type: 'hbox',
|
|
align: 'center',
|
|
},
|
|
items: [
|
|
{
|
|
xtype: 'container',
|
|
flex: 1,
|
|
items: [
|
|
{
|
|
xtype: 'proxmoxGauge',
|
|
itemId: 'space',
|
|
title: gettext('Usage'),
|
|
},
|
|
{
|
|
flex: 1,
|
|
border: false,
|
|
},
|
|
{
|
|
xtype: 'container',
|
|
itemId: 'recovery',
|
|
hidden: true,
|
|
padding: 25,
|
|
items: [
|
|
{
|
|
xtype: 'pveRunningChart',
|
|
itemId: 'recoverychart',
|
|
title: gettext('Recovery') +'/ '+ gettext('Rebalance'),
|
|
renderer: PVE.Utils.render_bandwidth,
|
|
height: 100,
|
|
},
|
|
{
|
|
xtype: 'progressbar',
|
|
itemId: 'recoveryprogress',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
xtype: 'container',
|
|
flex: 2,
|
|
defaults: {
|
|
padding: 0,
|
|
height: 100,
|
|
},
|
|
items: [
|
|
{
|
|
xtype: 'pveRunningChart',
|
|
itemId: 'reads',
|
|
title: gettext('Reads'),
|
|
renderer: PVE.Utils.render_bandwidth,
|
|
},
|
|
{
|
|
xtype: 'pveRunningChart',
|
|
itemId: 'writes',
|
|
title: gettext('Writes'),
|
|
renderer: PVE.Utils.render_bandwidth,
|
|
},
|
|
{
|
|
xtype: 'pveRunningChart',
|
|
itemId: 'readiops',
|
|
title: 'IOPS: ' + gettext('Reads'),
|
|
renderer: Ext.util.Format.numberRenderer('0,000'),
|
|
},
|
|
{
|
|
xtype: 'pveRunningChart',
|
|
itemId: 'writeiops',
|
|
title: 'IOPS: ' + gettext('Writes'),
|
|
renderer: Ext.util.Format.numberRenderer('0,000'),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
|
|
updateAll: function(store, records, success) {
|
|
if (!success || records.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var me = this;
|
|
var rec = records[0];
|
|
me.status = rec.data;
|
|
|
|
// add health panel
|
|
me.down('#overallhealth').updateHealth(PVE.Utils.render_ceph_health(rec.data.health || {}));
|
|
me.down('#warnings').updateHealth(rec.data.health || {}); // add errors to gridstore
|
|
|
|
me.getComponent('services').updateAll(me.metadata || {}, rec.data);
|
|
|
|
me.getComponent('statusdetail').updateAll(me.metadata || {}, rec.data);
|
|
|
|
// add performance data
|
|
let pgmap = rec.data.pgmap;
|
|
let used = pgmap.bytes_used;
|
|
let total = pgmap.bytes_total;
|
|
|
|
var text = Ext.String.format(gettext('{0} of {1}'),
|
|
Proxmox.Utils.render_size(used),
|
|
Proxmox.Utils.render_size(total),
|
|
);
|
|
|
|
// update the usage widget
|
|
me.down('#space').updateValue(used/total, text);
|
|
|
|
let readiops = pgmap.read_op_per_sec;
|
|
let writeiops = pgmap.write_op_per_sec;
|
|
let reads = pgmap.read_bytes_sec || 0;
|
|
let writes = pgmap.write_bytes_sec || 0;
|
|
|
|
// update the graphs
|
|
me.reads.addDataPoint(reads);
|
|
me.writes.addDataPoint(writes);
|
|
me.readiops.addDataPoint(readiops);
|
|
me.writeiops.addDataPoint(writeiops);
|
|
|
|
let degraded = pgmap.degraded_objects || 0;
|
|
let misplaced = pgmap.misplaced_objects || 0;
|
|
let unfound = pgmap.unfound_objects || 0;
|
|
let unhealthy = degraded + unfound + misplaced;
|
|
// update recovery
|
|
if (pgmap.recovering_objects_per_sec !== undefined || unhealthy > 0) {
|
|
let toRecoverObjects = pgmap.misplaced_total || pgmap.unfound_total || pgmap.degraded_total || 0;
|
|
if (toRecoverObjects === 0) {
|
|
return; // FIXME: unexpected return and leaves things possible visible when it shouldn't?
|
|
}
|
|
let recovered = toRecoverObjects - unhealthy || 0;
|
|
let speed = pgmap.recovering_bytes_per_sec || 0;
|
|
|
|
let recoveryRatio = recovered / toRecoverObjects;
|
|
let txt = `${(recoveryRatio * 100).toFixed(2)}%`;
|
|
if (speed > 0) {
|
|
let obj_per_sec = speed / (4 * 1024 * 1024); // 4 MiB per Object
|
|
let duration = Proxmox.Utils.format_duration_human(unhealthy/obj_per_sec);
|
|
let speedTxt = PVE.Utils.render_bandwidth(speed);
|
|
txt += ` (${speedTxt} - ${duration} left)`;
|
|
}
|
|
|
|
me.down('#recovery').setVisible(true);
|
|
me.down('#recoveryprogress').updateValue(recoveryRatio);
|
|
me.down('#recoveryprogress').updateText(txt);
|
|
me.down('#recoverychart').addDataPoint(speed);
|
|
} else {
|
|
me.down('#recovery').setVisible(false);
|
|
me.down('#recoverychart').addDataPoint(0);
|
|
}
|
|
},
|
|
|
|
initComponent: function() {
|
|
var me = this;
|
|
|
|
var nodename = me.pveSelNode.data.node;
|
|
|
|
me.callParent();
|
|
var baseurl = '/api2/json' + (nodename ? '/nodes/' + nodename : '/cluster') + '/ceph';
|
|
me.store = Ext.create('Proxmox.data.UpdateStore', {
|
|
storeid: 'ceph-status-' + (nodename || 'cluster'),
|
|
interval: 5000,
|
|
proxy: {
|
|
type: 'proxmox',
|
|
url: baseurl + '/status',
|
|
},
|
|
});
|
|
|
|
me.metadatastore = Ext.create('Proxmox.data.UpdateStore', {
|
|
storeid: 'ceph-metadata-' + (nodename || 'cluster'),
|
|
interval: 15*1000,
|
|
proxy: {
|
|
type: 'proxmox',
|
|
url: '/api2/json/cluster/ceph/metadata',
|
|
},
|
|
});
|
|
|
|
// save references for the updatefunction
|
|
me.iops = me.down('#iops');
|
|
me.readiops = me.down('#readiops');
|
|
me.writeiops = me.down('#writeiops');
|
|
me.reads = me.down('#reads');
|
|
me.writes = me.down('#writes');
|
|
|
|
// manages the "install ceph?" overlay
|
|
PVE.Utils.monitor_ceph_installed(me, me.store, nodename);
|
|
|
|
me.mon(me.store, 'load', me.updateAll, me);
|
|
me.mon(me.metadatastore, 'load', function(store, records, success) {
|
|
if (!success || records.length < 1) {
|
|
return;
|
|
}
|
|
me.metadata = records[0].data;
|
|
|
|
// update services
|
|
me.getComponent('services').updateAll(me.metadata, me.status || {});
|
|
|
|
// update detailstatus panel
|
|
me.getComponent('statusdetail').updateAll(me.metadata, me.status || {});
|
|
|
|
let maxversion = [];
|
|
let maxversiontext = "";
|
|
for (const [_nodename, data] of Object.entries(me.metadata.node)) {
|
|
let version = data.version.parts;
|
|
if (PVE.Utils.compare_ceph_versions(version, maxversion) > 0) {
|
|
maxversion = version;
|
|
maxversiontext = data.version.str;
|
|
}
|
|
}
|
|
me.down('#versioninfo').setValue(maxversiontext);
|
|
}, me);
|
|
|
|
me.on('destroy', me.store.stopUpdate);
|
|
me.on('destroy', me.metadatastore.stopUpdate);
|
|
me.store.startUpdate();
|
|
me.metadatastore.startUpdate();
|
|
},
|
|
|
|
});
|