Ext.define('PVE.ceph.StatusDetail', { extend: 'Ext.panel.Panel', alias: 'widget.pveCephStatusDetail', layout: { type: 'hbox', align: 'stretch' }, bodyPadding: '0 5 20', defaults: { xtype: 'box', style: { 'text-align':'center' } }, items: [{ flex: 1, itemId: 'monitors', xtype: 'container', items: [ { xtype: 'box', width: '100%', html: '

' + gettext('Monitors') + '

' } ] },{ flex: 1, itemId: 'osds', data: { total: 0, upin: 0, upout: 0, downin: 0, downout: 0 }, tpl: [ '

' + gettext('OSDs') + '

', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '
', gettext('In'), '', gettext('Out'), '
', gettext('Up'), '{upin}{upout}
', gettext('Down'), '{downin}{downout}
', '
', gettext('Total'), ': {total}', '
' ] }, { flex: 1.6, itemId: 'pgs', padding: '0 10', data: { monitors: [] }, tpl: [ '

' + gettext('PGs') + '

', '', '
{state_name}:
', '
{count}

', '
', '
' ] }], updateAll: function(record) { var me = this; me.suspendLayout = true; if (!record.data.pgmap || !record.data.osdmap || !record.data.osdmap.osdmap || !record.data.health || !record.data.health.timechecks || !record.data.monmap || !record.data.monmap.mons || !record.data.health.health || !record.data.health.health.health_services || !record.data.health.health.health_services[0]) { // only continue if we have all the data return; } // update pgs sorted var pgs_by_state = record.data.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.getComponent('pgs').update({monitors: pgs_by_state}); // update osds counts // caution: this code is not the nicest, // but since the status call only gives us // the total, up and in value, // we parse the health summary and look for the // x/y in osds are down message // to get the rest of the numbers // // the alternative would be to make a second api call, // as soon as not all osds are up, but those are costly var total_osds = record.data.osdmap.osdmap.num_osds || 0; var in_osds = record.data.osdmap.osdmap.num_in_osds || 0; var up_osds = record.data.osdmap.osdmap.num_up_osds || 0; var out_osds = total_osds - in_osds; var down_osds = total_osds - up_osds; var downin_osds = 0; var downinregex = /(\d+)\/(\d+) in osds are down/; Ext.Array.some(record.data.health.summary, function(item) { var found = item.summary.match(downinregex); if (found !== null) { // sanity check, test if the message is // consistent with the direct value // for in osds if (found[2] == in_osds) { downin_osds = parseInt(found[1],10); return true; } } return false; }); var downout_osds = down_osds - downin_osds; var upin_osds = in_osds - downin_osds; var upout_osds = up_osds - upin_osds; var osds = { total: total_osds, upin: upin_osds, upout: upout_osds, downin: downin_osds, downout: downout_osds }; me.getComponent('osds').update(osds); // update the monitors var mons = record.data.monmap.mons.sort(function(a,b) { return (a.name < b.name)?-1:(a.name > b.name)?1:0; }); var monTimes = record.data.health.timechecks.mons || []; var monHealth = record.data.health.health.health_services[0].mons || []; var timechecks = {}; var healthchecks = {}; var monContainer = me.getComponent('monitors'); var i; for (i = 0; i < mons.length && i < monTimes.length; i++) { timechecks[monTimes[i].name] = monTimes[i].health; } if (mons.length === 1) { timechecks[mons[0].name] = "HEALTH_OK"; } for (i = 0; i < mons.length && i < monHealth.length; i++) { healthchecks[monHealth[i].name] = monHealth[i].health; } 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(timechecks[mons[i].name], mons[i], record.data.quorum_names, healthchecks[mons[i].name]); } 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', iconCls: PVE.Utils.get_health_icon(), addr: '' }, tpl: [ '{name}: ', '' ], // expects 3 variables which are // timestate: the status from timechecks.mons // data: the monmap.mons data // quorum_names: the quorum_names array updateMonitor: function(timestate, data, quorum_names, health) { var me = this; var state = 'HEALTH_ERR'; var healthstates = { 'HEALTH_OK': 3, 'HEALTH_WARN': 2, 'HEALTH_ERR': 1 }; // if the monitor is part of the quorum // and has a timestate, get the timestate, // otherwise the state is ERR if (timestate && health && quorum_names && quorum_names.indexOf(data.name) !== -1) { state = (healthstates[health] < healthstates[timestate])? health : timestate; } me.update(Ext.apply(me.data, { health: state, 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 + '
' + gettext('Address') + ': ' + me.data.addr + '
' + gettext('Health') + ': ' + me.data.health }); } me.tooltip.show(); } }, mouseleave: { element: 'el', fn: function(events, element) { var me = this.component; if (me.tooltip) { me.tooltip.hide(); } } } } });