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: {
- monitors: []
+ states: [],
},
tpl: [
- '<h3>' + gettext('PGs') + '</h3>',
- '<tpl for="monitors">',
- '<div class="left-aligned">{state_name}:</div>',
+ '<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>'
- ]
+ '</tpl>',
+ ],
}],
- updateAll: function(record) {
+ // 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;
- 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) {
- // only continue if we have all the data
- return;
+ 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 pgs_by_state = record.data.pgmap.pgs_by_state || [];
- pgs_by_state.sort(function(a,b){
+ // update PGs sorted
+ var pgmap = status.pgmap || {};
+ var 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.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
+ me.statecategories.forEach(function(cat) {
+ cat.count = 0;
+ cat.states = [];
+ });
- 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;
+ 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 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;
+ 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);
}
}
-
- return false;
});
+ var osdmap = status.osdmap || {};
+ if (typeof osdmap.osdmap != "undefined") {
+ osdmap = osdmap.osdmap;
+ }
+ // update osds counts
+ 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;
+
var downout_osds = down_osds - downin_osds;
var upin_osds = in_osds - downin_osds;
var upout_osds = up_osds - upin_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 = record.data.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 monTimes = record.data.health.timechecks.mons;
- var timechecks = {};
- var monContainer = me.getComponent('monitors');
- var i;
- for (i = 0; i < mons.length && i < monTimes.length; i++) {
- timechecks[monTimes[i].name] = monTimes[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);
- }
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}: ',
- '<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(timestate, data, quorum_names) {
- var me = this;
- var state = 'HEALTH_ERR';
-
- // if the monitor is part of the quorum
- // and has a timestate, get the timestate,
- // otherwise the state is ERR
- if (timestate && quorum_names &&
- quorum_names.indexOf(data.name) !== -1) {
- state = 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 + '<br />' +
- gettext('Address') + ': ' + me.data.addr + '<br />' +
- 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();
- }
- }
- }
- }
});
+