mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-07-24 20:20:13 +00:00

It seems we did not prepare the GUI enough for the API changes planned when stopping to broadcast the old single string version. We have to use the node specific versions, not the global 'versions' object. Also use the `PVE.Utils.compare_ceph_versions` everywhere, since some versions are strings and others are the parts of the version (e.g. ["16", "2, "6"]) Signed-off-by: Dominik Csapak <d.csapak@proxmox.com> Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
357 lines
8.7 KiB
JavaScript
357 lines
8.7 KiB
JavaScript
Ext.define('PVE.ceph.Services', {
|
|
extend: 'Ext.panel.Panel',
|
|
alias: 'widget.pveCephServices',
|
|
|
|
layout: {
|
|
type: 'hbox',
|
|
align: 'stretch',
|
|
},
|
|
|
|
bodyPadding: '0 5 20',
|
|
defaults: {
|
|
xtype: 'box',
|
|
style: {
|
|
'text-align': 'center',
|
|
},
|
|
},
|
|
|
|
items: [
|
|
{
|
|
flex: 1,
|
|
xtype: 'pveCephServiceList',
|
|
itemId: 'mons',
|
|
title: gettext('Monitors'),
|
|
},
|
|
{
|
|
flex: 1,
|
|
xtype: 'pveCephServiceList',
|
|
itemId: 'mgrs',
|
|
title: gettext('Managers'),
|
|
},
|
|
{
|
|
flex: 1,
|
|
xtype: 'pveCephServiceList',
|
|
itemId: 'mdss',
|
|
title: gettext('Meta Data Servers'),
|
|
},
|
|
],
|
|
|
|
updateAll: function(metadata, status) {
|
|
var me = this;
|
|
|
|
const healthstates = {
|
|
'HEALTH_UNKNOWN': 0,
|
|
'HEALTH_ERR': 1,
|
|
'HEALTH_WARN': 2,
|
|
'HEALTH_UPGRADE': 3,
|
|
'HEALTH_OLD': 4,
|
|
'HEALTH_OK': 5,
|
|
};
|
|
// order guarantee since es2020, but browsers did so before. Note, integers would break it.
|
|
const healthmap = Object.keys(healthstates);
|
|
let maxversion = "00.0.00";
|
|
Object.values(metadata.node || {}).forEach(function(node) {
|
|
if (PVE.Utils.compare_ceph_versions(node?.version?.parts, maxversion) > 0) {
|
|
maxversion = node?.version?.parts;
|
|
}
|
|
});
|
|
var quorummap = status && status.quorum_names ? status.quorum_names : [];
|
|
let monmessages = {}, mgrmessages = {}, mdsmessages = {};
|
|
if (status) {
|
|
if (status.health) {
|
|
Ext.Object.each(status.health.checks, function(key, value, _obj) {
|
|
if (!Ext.String.startsWith(key, "MON_")) {
|
|
return;
|
|
}
|
|
for (let i = 0; i < value.detail.length; i++) {
|
|
let match = value.detail[i].message.match(/mon.([a-zA-Z0-9\-.]+)/);
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
let monid = match[1];
|
|
if (!monmessages[monid]) {
|
|
monmessages[monid] = {
|
|
worstSeverity: healthstates.HEALTH_OK,
|
|
messages: [],
|
|
};
|
|
}
|
|
|
|
let severityIcon = PVE.Utils.get_ceph_icon_html(value.severity, true);
|
|
let details = value.detail.reduce((acc, v) => `${acc}\n${v.message}`, '');
|
|
monmessages[monid].messages.push(severityIcon + details);
|
|
|
|
if (healthstates[value.severity] < monmessages[monid].worstSeverity) {
|
|
monmessages[monid].worstSeverity = healthstates[value.severity];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
if (status.mgrmap) {
|
|
mgrmessages[status.mgrmap.active_name] = "active";
|
|
status.mgrmap.standbys.forEach(function(mgr) {
|
|
mgrmessages[mgr.name] = "standby";
|
|
});
|
|
}
|
|
|
|
if (status.fsmap) {
|
|
status.fsmap.by_rank.forEach(function(mds) {
|
|
mdsmessages[mds.name] = 'rank: ' + mds.rank + "; " + mds.status;
|
|
});
|
|
}
|
|
}
|
|
|
|
let checks = {
|
|
mon: function(mon) {
|
|
if (quorummap.indexOf(mon.name) !== -1) {
|
|
mon.health = healthstates.HEALTH_OK;
|
|
} else {
|
|
mon.health = healthstates.HEALTH_ERR;
|
|
}
|
|
if (monmessages[mon.name]) {
|
|
if (monmessages[mon.name].worstSeverity < mon.health) {
|
|
mon.health = monmessages[mon.name].worstSeverity;
|
|
}
|
|
Array.prototype.push.apply(mon.messages, monmessages[mon.name].messages);
|
|
}
|
|
return mon;
|
|
},
|
|
mgr: function(mgr) {
|
|
if (mgrmessages[mgr.name] === 'active') {
|
|
mgr.title = '<b>' + mgr.title + '</b>';
|
|
mgr.statuses.push(gettext('Status') + ': <b>active</b>');
|
|
} else if (mgrmessages[mgr.name] === 'standby') {
|
|
mgr.statuses.push(gettext('Status') + ': standby');
|
|
} else if (mgr.health > healthstates.HEALTH_WARN) {
|
|
mgr.health = healthstates.HEALTH_WARN;
|
|
}
|
|
|
|
return mgr;
|
|
},
|
|
mds: function(mds) {
|
|
if (mdsmessages[mds.name]) {
|
|
mds.title = '<b>' + mds.title + '</b>';
|
|
mds.statuses.push(gettext('Status') + ': <b>' + mdsmessages[mds.name]+"</b>");
|
|
} else if (mds.addr !== Proxmox.Utils.unknownText) {
|
|
mds.statuses.push(gettext('Status') + ': standby');
|
|
}
|
|
|
|
return mds;
|
|
},
|
|
};
|
|
|
|
for (let type of ['mon', 'mgr', 'mds']) {
|
|
var ids = Object.keys(metadata[type] || {});
|
|
me[type] = {};
|
|
|
|
for (let id of ids) {
|
|
const [name, host] = id.split('@');
|
|
let result = {
|
|
id: id,
|
|
health: healthstates.HEALTH_OK,
|
|
statuses: [],
|
|
messages: [],
|
|
name: name,
|
|
title: metadata[type][id].name || name,
|
|
host: host,
|
|
version: PVE.Utils.parse_ceph_version(metadata[type][id]),
|
|
service: metadata[type][id].service,
|
|
addr: metadata[type][id].addr || metadata[type][id].addrs || Proxmox.Utils.unknownText,
|
|
};
|
|
|
|
result.statuses = [
|
|
gettext('Host') + ": " + host,
|
|
gettext('Address') + ": " + result.addr,
|
|
];
|
|
|
|
if (checks[type]) {
|
|
result = checks[type](result);
|
|
}
|
|
|
|
if (result.service && !result.version) {
|
|
result.messages.push(
|
|
PVE.Utils.get_ceph_icon_html('HEALTH_UNKNOWN', true) +
|
|
gettext('Stopped'),
|
|
);
|
|
result.health = healthstates.HEALTH_UNKNOWN;
|
|
}
|
|
|
|
if (!result.version && result.addr === Proxmox.Utils.unknownText) {
|
|
result.health = healthstates.HEALTH_UNKNOWN;
|
|
}
|
|
|
|
if (result.version) {
|
|
result.statuses.push(gettext('Version') + ": " + result.version);
|
|
|
|
if (PVE.Utils.compare_ceph_versions(result.version, maxversion) !== 0) {
|
|
if (metadata.version[host] === maxversion) {
|
|
if (result.health > healthstates.HEALTH_OLD) {
|
|
result.health = healthstates.HEALTH_OLD;
|
|
}
|
|
result.messages.push(
|
|
PVE.Utils.get_ceph_icon_html('HEALTH_OLD', true) +
|
|
gettext('A newer version was installed but old version still running, please restart'),
|
|
);
|
|
} else {
|
|
if (result.health > healthstates.HEALTH_UPGRADE) {
|
|
result.health = healthstates.HEALTH_UPGRADE;
|
|
}
|
|
result.messages.push(
|
|
PVE.Utils.get_ceph_icon_html('HEALTH_UPGRADE', true) +
|
|
gettext('Other cluster members use a newer version of this service, please upgrade and restart'),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
result.statuses.push(''); // empty line
|
|
result.text = result.statuses.concat(result.messages).join('<br>');
|
|
|
|
result.health = healthmap[result.health];
|
|
|
|
me[type][id] = result;
|
|
}
|
|
}
|
|
|
|
me.getComponent('mons').updateAll(Object.values(me.mon));
|
|
me.getComponent('mgrs').updateAll(Object.values(me.mgr));
|
|
me.getComponent('mdss').updateAll(Object.values(me.mds));
|
|
},
|
|
});
|
|
|
|
Ext.define('PVE.ceph.ServiceList', {
|
|
extend: 'Ext.container.Container',
|
|
xtype: 'pveCephServiceList',
|
|
|
|
style: {
|
|
'text-align': 'center',
|
|
},
|
|
defaults: {
|
|
xtype: 'box',
|
|
style: {
|
|
'text-align': 'center',
|
|
},
|
|
},
|
|
|
|
items: [
|
|
{
|
|
itemId: 'title',
|
|
data: {
|
|
title: '',
|
|
},
|
|
tpl: '<h3>{title}</h3>',
|
|
},
|
|
],
|
|
|
|
updateAll: function(list) {
|
|
var me = this;
|
|
me.suspendLayout = true;
|
|
|
|
list.sort((a, b) => a.id > b.id ? 1 : a.id < b.id ? -1 : 0);
|
|
if (!me.ids) {
|
|
me.ids = [];
|
|
}
|
|
let pendingRemoval = {};
|
|
me.ids.forEach(id => { pendingRemoval[id] = true; }); // mark all as to-remove first here
|
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
let service = me.getComponent(list[i].id);
|
|
if (!service) {
|
|
// services and list are sorted, so just insert at i + 1 (first el. is the title)
|
|
service = me.insert(i + 1, {
|
|
xtype: 'pveCephServiceWidget',
|
|
itemId: list[i].id,
|
|
});
|
|
me.ids.push(list[i].id);
|
|
} else {
|
|
delete pendingRemoval[list[i].id]; // drop exisiting from for-removal
|
|
}
|
|
service.updateService(list[i].title, list[i].text, list[i].health);
|
|
}
|
|
Object.keys(pendingRemoval).forEach(id => me.remove(id)); // GC
|
|
|
|
me.suspendLayout = false;
|
|
me.updateLayout();
|
|
},
|
|
|
|
initComponent: function() {
|
|
var me = this;
|
|
me.callParent();
|
|
me.getComponent('title').update({
|
|
title: me.title,
|
|
});
|
|
},
|
|
});
|
|
|
|
Ext.define('PVE.ceph.ServiceWidget', {
|
|
extend: 'Ext.Component',
|
|
alias: 'widget.pveCephServiceWidget',
|
|
|
|
userCls: 'monitor inline-block',
|
|
data: {
|
|
title: '0',
|
|
health: 'HEALTH_ERR',
|
|
text: '',
|
|
iconCls: PVE.Utils.get_health_icon(),
|
|
},
|
|
|
|
tpl: [
|
|
'{title}: ',
|
|
'<i class="fa fa-fw {iconCls}"></i>',
|
|
],
|
|
|
|
updateService: function(title, text, health) {
|
|
var me = this;
|
|
|
|
me.update(Ext.apply(me.data, {
|
|
health: health,
|
|
text: text,
|
|
title: title,
|
|
iconCls: PVE.Utils.get_health_icon(PVE.Utils.map_ceph_health[health]),
|
|
}));
|
|
|
|
if (me.tooltip) {
|
|
me.tooltip.setHtml(text);
|
|
}
|
|
},
|
|
|
|
listeners: {
|
|
destroy: function() {
|
|
let me = this;
|
|
if (me.tooltip) {
|
|
me.tooltip.destroy();
|
|
delete me.tooltip;
|
|
}
|
|
},
|
|
mouseenter: {
|
|
element: 'el',
|
|
fn: function(events, element) {
|
|
let view = this.component;
|
|
if (!view) {
|
|
return;
|
|
}
|
|
if (!view.tooltip || view.data.text !== view.tooltip.html) {
|
|
view.tooltip = Ext.create('Ext.tip.ToolTip', {
|
|
target: view.el,
|
|
trackMouse: true,
|
|
dismissDelay: 0,
|
|
renderTo: Ext.getBody(),
|
|
html: view.data.text,
|
|
});
|
|
}
|
|
view.tooltip.show();
|
|
},
|
|
},
|
|
mouseleave: {
|
|
element: 'el',
|
|
fn: function(events, element) {
|
|
let view = this.component;
|
|
if (view.tooltip) {
|
|
view.tooltip.destroy();
|
|
delete view.tooltip;
|
|
}
|
|
},
|
|
},
|
|
},
|
|
});
|