pve-manager/www/manager6/ceph/StatusDetail.js
Thomas Lamprecht a8d1bc80b5 ui: ceph status: rename working state into busy
Working could be confused with "being ok", which isn't what we want to
convey here, as the lack of this status doesn't mean something "isn't
working".

So use busy, not 100% perfect but a bit closer to what we want to
convey while not taking up a whole paragraph or the like.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2023-11-13 16:24:04 +01:00

348 lines
7.5 KiB
JavaScript

Ext.define('PVE.ceph.StatusDetail', {
extend: 'Ext.panel.Panel',
alias: 'widget.pveCephStatusDetail',
layout: {
type: 'hbox',
align: 'stretch',
},
bodyPadding: '0 5',
defaults: {
xtype: 'box',
style: {
'text-align': 'center',
},
},
items: [{
flex: 1,
itemId: 'osds',
maxHeight: 250,
scrollable: true,
padding: '0 10 5 10',
data: {
total: 0,
upin: 0,
upout: 0,
downin: 0,
downout: 0,
oldOSD: [],
ghostOSD: [],
},
tpl: [
'<h3>OSDs</h3>',
'<table class="osds">',
'<tr><td></td>',
'<td><i class="fa fa-fw good fa-circle"></i>',
gettext('In'),
'</td>',
'<td><i class="fa fa-fw warning fa-circle-o"></i>',
gettext('Out'),
'</td>',
'</tr>',
'<tr>',
'<td><i class="fa fa-fw good fa-arrow-circle-up"></i>',
gettext('Up'),
'</td>',
'<td>{upin}</td>',
'<td>{upout}</td>',
'</tr>',
'<tr>',
'<td><i class="fa fa-fw critical fa-arrow-circle-down"></i>',
gettext('Down'),
'</td>',
'<td>{downin}</td>',
'<td>{downout}</td>',
'</tr>',
'</table>',
'<br /><div>',
gettext('Total'),
': {total}',
'</div><br />',
'<tpl if="oldOSD.length &gt; 0">',
'<i class="fa fa-refresh warning"></i> ' + gettext('Outdated OSDs') + "<br>",
'<div class="osds">',
'<tpl for="oldOSD">',
'<div class="left-aligned">osd.{id}:</div>',
'<div class="right-aligned">{version}</div><br />',
'<div style="clear:both"></div>',
'</tpl>',
'</div>',
'</tpl>',
'</div>',
'<tpl if="ghostOSD.length &gt; 0">',
'<br />',
`<i class="fa fa-question-circle warning"></i> ${gettext('Ghost OSDs')}<br>`,
`<div data-qtip="${gettext('OSDs with no metadata, possibly left over from removal')}" class="osds">`,
'<tpl for="ghostOSD">',
'<div class="left-aligned">osd.{id}</div>',
'<div style="clear:both"></div>',
'</tpl>',
'</div>',
'</tpl>',
],
},
{
flex: 1,
border: false,
itemId: 'pgchart',
xtype: 'polar',
height: 184,
innerPadding: 5,
insetPadding: 5,
colors: [
'#CFCFCF',
'#21BF4B',
'#3892d4',
'#FFCC00',
'#FF6C59',
],
store: { },
series: [
{
type: 'pie',
donut: 60,
angleField: 'count',
tooltip: {
trackMouse: true,
renderer: function(tooltip, record, ctx) {
var html = record.get('text');
html += '<br>';
record.get('states').forEach(function(state) {
html += '<br>' +
state.state_name + ': ' + state.count.toString();
});
tooltip.setHtml(html);
},
},
subStyle: {
strokeStyle: false,
},
},
],
},
{
flex: 1.6,
itemId: 'pgs',
padding: '0 10',
maxHeight: 250,
scrollable: true,
data: {
states: [],
},
tpl: [
'<h3>PGs</h3>',
'<tpl for="states">',
'<div class="left-aligned"><i class ="fa fa-circle {cls}"></i> {state_name}:</div>',
'<div class="right-aligned">{count}</div><br />',
'<div style="clear:both"></div>',
'</tpl>',
],
}],
// similar to mgr dashboard
pgstates: {
// clean
clean: 1,
active: 1,
// busy
activating: 2,
backfill_wait: 2,
backfilling: 2,
creating: 2,
deep: 2,
forced_backfill: 2,
forced_recovery: 2,
peered: 2,
peering: 2,
recovering: 2,
recovery_wait: 2,
remapped: 2,
repair: 2,
scrubbing: 2,
snaptrim: 2,
snaptrim_wait: 2,
// warning
degraded: 3,
undersized: 3,
// critical
backfill_toofull: 4,
backfill_unfound: 4,
down: 4,
incomplete: 4,
inconsistent: 4,
recovery_toofull: 4,
recovery_unfound: 4,
snaptrim_error: 4,
stale: 4,
},
statecategories: [
{
text: gettext('Unknown'),
count: 0,
states: [],
cls: 'faded',
},
{
text: gettext('Clean'),
cls: 'good',
},
{
text: gettext('Busy'),
cls: 'pve-ceph-status-busy',
},
{
text: gettext('Warning'),
cls: 'warning',
},
{
text: gettext('Critical'),
cls: 'critical',
},
],
checkThemeColors: function() {
let me = this;
let rootStyle = getComputedStyle(document.documentElement);
// get color
let background = rootStyle.getPropertyValue("--pwt-panel-background").trim() || "#ffffff";
// set the colors
me.chart.setBackground(background);
me.chart.redraw();
},
updateAll: function(metadata, status) {
let me = this;
me.suspendLayout = true;
let maxversion = "0";
Object.values(metadata.node || {}).forEach(function(node) {
if (PVE.Utils.compare_ceph_versions(node?.version?.parts, maxversion) > 0) {
maxversion = node.version.parts;
}
});
let oldOSD = [], ghostOSD = [];
metadata.osd?.forEach(osd => {
let version = PVE.Utils.parse_ceph_version(osd);
if (version !== undefined) {
if (PVE.Utils.compare_ceph_versions(version, maxversion) !== 0) {
oldOSD.push({
id: osd.id,
version: version,
});
}
} else {
if (Object.keys(osd).length > 1) {
console.warn('got OSD entry with no valid version but other keys', osd);
}
ghostOSD.push({
id: osd.id,
});
}
});
// update PGs sorted
let pgmap = status.pgmap || {};
let pgs_by_state = pgmap.pgs_by_state || [];
pgs_by_state.sort(function(a, b) {
return a.state_name < b.state_name?-1:a.state_name === b.state_name?0:1;
});
me.statecategories.forEach(function(cat) {
cat.count = 0;
cat.states = [];
});
pgs_by_state.forEach(function(state) {
let states = state.state_name.split(/[^a-z]+/);
let result = 0;
for (let i = 0; i < states.length; i++) {
if (me.pgstates[states[i]] > result) {
result = me.pgstates[states[i]];
}
}
// for the list
state.cls = me.statecategories[result].cls;
me.statecategories[result].count += state.count;
me.statecategories[result].states.push(state);
});
me.chart.getStore().setData(me.statecategories);
me.getComponent('pgs').update({ states: pgs_by_state });
let health = status.health || {};
// we collect monitor/osd information from the checks
const downinregex = /(\d+) osds down/;
let downin_osds = 0;
Ext.Object.each(health.checks, function(key, value, obj) {
var found = null;
if (key === 'OSD_DOWN') {
found = value.summary.message.match(downinregex);
if (found !== null) {
downin_osds = parseInt(found[1], 10);
}
}
});
let osdmap = status.osdmap || {};
if (typeof osdmap.osdmap !== "undefined") {
osdmap = osdmap.osdmap;
}
// update OSDs counts
let total_osds = osdmap.num_osds || 0;
let in_osds = osdmap.num_in_osds || 0;
let up_osds = osdmap.num_up_osds || 0;
let down_osds = total_osds - up_osds;
let downout_osds = down_osds - downin_osds;
let upin_osds = in_osds - downin_osds;
let upout_osds = up_osds - upin_osds;
let osds = {
total: total_osds,
upin: upin_osds,
upout: upout_osds,
downin: downin_osds,
downout: downout_osds,
oldOSD: oldOSD,
ghostOSD,
};
let osdcomponent = me.getComponent('osds');
osdcomponent.update(Ext.apply(osdcomponent.data, osds));
me.suspendLayout = false;
me.updateLayout();
},
initComponent: function() {
var me = this;
me.callParent();
me.chart = me.getComponent('pgchart');
me.checkThemeColors();
// switch colors on media query changes
me.mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
me.themeListener = (e) => { me.checkThemeColors(); };
me.mediaQueryList.addEventListener("change", me.themeListener);
},
doDestroy: function() {
let me = this;
me.mediaQueryList.removeEventListener("change", me.themeListener);
me.callParent();
},
});