layout: {
type: 'hbox',
- align: 'stretch'
+ align: 'stretch',
},
- bodyPadding: '0 5 20',
+ bodyPadding: '0 5',
defaults: {
xtype: 'box',
style: {
- 'text-align':'center'
- }
+ 'text-align': 'center',
+ },
},
items: [{
- flex: 1,
- itemId: 'monitors',
- xtype: 'container',
- items: [
- {
- xtype: 'box',
- width: '100%',
- html: '<h3>' + gettext('Monitors') + '</h3>'
- }
- ]
- },{
flex: 1,
itemId: 'osds',
+ maxHeight: 250,
+ scrollable: true,
+ padding: '0 10 5 10',
data: {
total: 0,
upin: 0,
upout: 0,
downin: 0,
- downout: 0
+ downout: 0,
+ oldosds: [],
},
tpl: [
- '<h3>' + gettext('OSDs') + '</h3>',
+ '<h3>' + 'OSDs' + '</h3>',
'<table class="osds">',
'<tr><td></td>',
'<td><i class="fa fa-fw good fa-circle"></i>',
'<br /><div>',
gettext('Total'),
': {total}',
- '</div>'
- ]
+ '</div><br />',
+ '<tpl if="oldosds.length > 0">',
+ '<i class="fa fa-refresh warning"></i> ' + gettext('Outdated OSDs') + "<br>",
+ '<div class="osds">',
+ '<tpl for="oldosds">',
+ '<div class="left-aligned">osd.{id}:</div>',
+ '<div class="right-aligned">{version}</div><br />',
+ '<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',
+ '#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: []
+ states: [],
},
tpl: [
- '<h3>' + gettext('PGs') + '</h3>',
+ '<h3>' + 'PGs' + '</h3>',
'<tpl for="states">',
- '<div class="left-aligned">{state_name}:</div>',
+ '<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>'
- ]
+ '</tpl>',
+ ],
}],
- updateAll: function(health, monmap, pgmap, osdmap, quorum_names) {
+ // similar to mgr dashboard
+ pgstates: {
+ // clean
+ clean: 1,
+ active: 1,
+
+ // working
+ activating: 2,
+ backfill_wait: 2,
+ backfilling: 2,
+ creating: 2,
+ deep: 2,
+ degraded: 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,
+
+ // error
+ backfill_toofull: 3,
+ backfill_unfound: 3,
+ down: 3,
+ incomplete: 3,
+ inconsistent: 3,
+ recovery_toofull: 3,
+ recovery_unfound: 3,
+ snaptrim_error: 3,
+ stale: 3,
+ undersized: 3,
+ },
+
+ statecategories: [
+ {
+ text: gettext('Unknown'),
+ count: 0,
+ states: [],
+ cls: 'faded',
+ },
+ {
+ text: gettext('Clean'),
+ cls: 'good',
+ },
+ {
+ text: gettext('Working'),
+ cls: 'warning',
+ },
+ {
+ text: gettext('Error'),
+ cls: 'critical',
+ },
+ ],
+
+ updateAll: function(metadata, status) {
var me = this;
me.suspendLayout = true;
- // update pgs sorted
+ var maxversion = "0";
+ Object.values(metadata.version || {}).forEach(function(version) {
+ if (PVE.Utils.compare_ceph_versions(version, maxversion) > 0) {
+ maxversion = version;
+ }
+ });
+
+ var oldosds = [];
+
+ if (metadata.osd) {
+ metadata.osd.forEach(function(osd) {
+ var version = PVE.Utils.parse_ceph_version(osd);
+ if (version != maxversion) {
+ oldosds.push({
+ id: osd.id,
+ version: version,
+ });
+ }
+ });
+ }
+
+ // update PGs sorted
+ var pgmap = status.pgmap || {};
var pgs_by_state = pgmap.pgs_by_state || [];
- pgs_by_state.sort(function(a,b){
+ pgs_by_state.sort(function(a, b) {
return (a.state_name < b.state_name)?-1:(a.state_name === b.state_name)?0:1;
});
- me.getComponent('pgs').update({states: pgs_by_state});
+
+ me.statecategories.forEach(function(cat) {
+ cat.count = 0;
+ cat.states = [];
+ });
+
+ pgs_by_state.forEach(function(state) {
+ var i;
+ var states = state.state_name.split(/[^a-z]+/);
+ var result = 0;
+ for (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.getComponent('pgchart').getStore().setData(me.statecategories);
+ me.getComponent('pgs').update({ states: pgs_by_state });
var downinregex = /(\d+) osds down/;
- var monnameregex = /^mon.(\S+) /;
var downin_osds = 0;
- var monmsgs = {};
+ var health = status.health || {};
// we collect monitor/osd information from the checks
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);
- }
- }
- else if (Ext.String.startsWith(key, 'MON_')) {
- if (!value.detail) {
- return;
- }
- found = value.detail[0].message.match(monnameregex);
- if (found !== null) {
- if (!monmsgs[found[1]]) {
- monmsgs[found[1]] = [];
- }
- monmsgs[found[1]].push({
- text: Ext.Array.reduce(value.detail, function(first, second) {
- return first + '\n' + second.message;
- }, ''),
- severity: value.severity
- });
+ downin_osds = parseInt(found[1], 10);
}
}
});
+ var osdmap = status.osdmap || {};
+ if (typeof osdmap.osdmap != "undefined") {
+ osdmap = osdmap.osdmap;
+ }
// update osds counts
-
- var total_osds = osdmap.osdmap.num_osds || 0;
- var in_osds = osdmap.osdmap.num_in_osds || 0;
- var up_osds = osdmap.osdmap.num_up_osds || 0;
+ var total_osds = osdmap.num_osds || 0;
+ var in_osds = osdmap.num_in_osds || 0;
+ var up_osds = osdmap.num_up_osds || 0;
var out_osds = total_osds - in_osds;
var down_osds = total_osds - up_osds;
upin: upin_osds,
upout: upout_osds,
downin: downin_osds,
- downout: downout_osds
+ downout: downout_osds,
+ oldosds: oldosds,
};
- me.getComponent('osds').update(osds);
-
- // update the monitors
- var mons = monmap.mons.sort(function(a,b) {
- return (a.name < b.name)?-1:(a.name > b.name)?1:0;
- });
+ var osdcomponent = me.getComponent('osds');
+ osdcomponent.update(Ext.apply(osdcomponent.data, osds));
- var monContainer = me.getComponent('monitors');
-
- var i;
- for (i = 0; i < mons.length; i++) {
- var monitor = monContainer.getComponent('mon.' + mons[i].name);
- if (!monitor) {
- // since mons are already sorted, and
- // we always have a sorted list
- // we can add it at the mons+1 position (because of the title)
- monitor = monContainer.insert(i+1, {
- xtype: 'pveCephMonitorWidget',
- itemId: 'mon.' + mons[i].name
- });
- }
- monitor.updateMonitor(mons[i], monmsgs, quorum_names);
- }
me.suspendLayout = false;
me.updateLayout();
- }
-});
-
-Ext.define('PVE.ceph.MonitorWidget', {
- extend: 'Ext.Component',
- alias: 'widget.pveCephMonitorWidget',
-
- userCls: 'monitor inline-block',
- data: {
- name: '0',
- health: 'HEALTH_ERR',
- text: '',
- iconCls: PVE.Utils.get_health_icon(),
- addr: ''
- },
-
- tpl: [
- '{name}: ',
- '<i class="fa fa-fw {iconCls}"></i>'
- ],
-
- // expects 3 variables which are
- // timestate: the status from timechecks.mons
- // data: the monmap.mons data
- // quorum_names: the quorum_names array
- updateMonitor: function(data, monmsgs, quorum_names) {
- var me = this;
- var state = 'HEALTH_ERR';
- var text = '';
- var healthstates = {
- 'HEALTH_OK': 3,
- 'HEALTH_WARN': 2,
- 'HEALTH_ERR': 1
- };
-
- if (quorum_names &&
- quorum_names.indexOf(data.name) !== -1) {
- state = 'HEALTH_OK';
- }
-
- if (monmsgs[data.name]) {
- Ext.Array.forEach(monmsgs[data.name], function(msg) {
- if (healthstates[msg.severity] < healthstates[state]) {
- state = msg.severity;
- }
-
- text += msg.text + "\n";
- });
- }
-
- me.update(Ext.apply(me.data, {
- health: state,
- text: text,
- addr: data.addr,
- name: data.name,
- iconCls: PVE.Utils.get_health_icon(PVE.Utils.map_ceph_health[state])
- }));
},
-
- listeners: {
- mouseenter: {
- element: 'el',
- fn: function(events, element) {
- var me = this.component;
- if (!me) {
- return;
- }
- if (!me.tooltip) {
- me.tooltip = Ext.create('Ext.tip.ToolTip', {
- target: me.el,
- trackMouse: true,
- renderTo: Ext.getBody(),
- html: gettext('Monitor') + ': ' + me.data.name + '<br />' +
- gettext('Address') + ': ' + me.data.addr + '<br />' +
- gettext('Health') + ': ' + me.data.health + '<br />' +
- me.data.text
- });
- }
- me.tooltip.show();
- }
- },
- mouseleave: {
- element: 'el',
- fn: function(events, element) {
- var me = this.component;
- if (me.tooltip) {
- me.tooltip.destroy();
- delete me.tooltip;
- }
- }
- }
- }
});
+