Ext.define('PVE.node.CephStatus', { extend: 'Ext.panel.Panel', alias: 'widget.pveNodeCephStatus', onlineHelp: 'chapter_pveceph', scrollable: true, bodyPadding: 5, layout: { type: 'column', }, defaults: { padding: 5, }, items: [ { xtype: 'panel', title: gettext('Health'), bodyPadding: 10, plugins: 'responsive', responsiveConfig: { 'width < 1600': { minHeight: 230, columnWidth: 1, }, 'width >= 1600': { minHeight: 500, columnWidth: 0.5, }, }, layout: { type: 'hbox', align: 'stretch', }, items: [ { xtype: 'container', layout: { type: 'vbox', align: 'stretch', }, flex: 1, items: [ { flex: 1, itemId: 'overallhealth', xtype: 'pveHealthWidget', title: gettext('Status'), }, { itemId: 'versioninfo', xtype: 'displayfield', fieldLabel: gettext('Ceph Version'), value: "", autoEl: { tag: 'div', 'data-qtip': gettext('The newest version installed in the Cluster.'), }, padding: '10 0 0 0', style: { 'text-align': 'center', }, }, ], }, { flex: 2, itemId: 'warnings', stateful: true, stateId: 'ceph-status-warnings', xtype: 'grid', // we load the store manually, to show an emptyText specify an empty intermediate store store: { trackRemoved: false, data: [], }, emptyText: gettext('No Warnings/Errors'), columns: [ { dataIndex: 'severity', header: gettext('Severity'), align: 'center', width: 70, renderer: function(value) { let health = PVE.Utils.map_ceph_health[value]; let icon = PVE.Utils.get_health_icon(health); return ``; }, sorter: { sorterFn: function(a, b) { let health = ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK']; return health.indexOf(b.data.severity) - health.indexOf(a.data.severity); }, }, }, { dataIndex: 'summary', header: gettext('Summary'), flex: 1, }, { xtype: 'actioncolumn', width: 40, align: 'center', tooltip: gettext('Detail'), items: [ { iconCls: 'x-fa fa-info-circle', handler: function(grid, rowindex, colindex, item, e, record) { var win = Ext.create('Ext.window.Window', { title: gettext('Detail'), resizable: true, modal: true, width: 650, height: 400, layout: { type: 'fit', }, items: [{ scrollable: true, padding: 10, xtype: 'box', html: [ '' + Ext.htmlEncode(record.data.summary) + '', '
' + Ext.htmlEncode(record.data.detail) + '
', ], }], }); win.show(); }, }, ], }, ], }, ], }, { xtype: 'pveCephStatusDetail', itemId: 'statusdetail', plugins: 'responsive', responsiveConfig: { 'width < 1600': { columnWidth: 1, minHeight: 250, }, 'width >= 1600': { columnWidth: 0.5, minHeight: 300, }, }, title: gettext('Status'), }, { title: gettext('Services'), xtype: 'pveCephServices', itemId: 'services', plugins: 'responsive', layout: { type: 'hbox', align: 'stretch', }, responsiveConfig: { 'width < 1600': { columnWidth: 1, minHeight: 200, }, 'width >= 1600': { columnWidth: 0.5, minHeight: 200, }, }, }, { xtype: 'panel', title: gettext('Performance'), columnWidth: 1, bodyPadding: 5, layout: { type: 'hbox', align: 'center', }, items: [ { flex: 1, xtype: 'container', items: [ { xtype: 'proxmoxGauge', itemId: 'space', title: gettext('Usage'), }, { flex: 1, border: false, }, { xtype: 'container', itemId: 'recovery', hidden: true, padding: 25, items: [ { itemId: 'recoverychart', xtype: 'pveRunningChart', title: gettext('Recovery') +'/ '+ gettext('Rebalance'), renderer: PVE.Utils.render_bandwidth, height: 100, }, { xtype: 'progressbar', itemId: 'recoveryprogress', }, ], }, ], }, { flex: 2, xtype: 'container', defaults: { padding: 0, height: 100, }, items: [ { itemId: 'reads', xtype: 'pveRunningChart', title: gettext('Reads'), renderer: PVE.Utils.render_bandwidth, }, { itemId: 'writes', xtype: 'pveRunningChart', title: gettext('Writes'), renderer: PVE.Utils.render_bandwidth, }, { itemId: 'readiops', xtype: 'pveRunningChart', title: 'IOPS: ' + gettext('Reads'), renderer: Ext.util.Format.numberRenderer('0,000'), }, { itemId: 'writeiops', xtype: 'pveRunningChart', title: 'IOPS: ' + gettext('Writes'), renderer: Ext.util.Format.numberRenderer('0,000'), }, ], }, ], }, ], generateCheckData: function(health) { var result = []; let checks = health.checks || {}; Object.keys(checks).sort().forEach(key => { let check = checks[key]; result.push({ id: key, summary: check.summary.message, detail: check.detail.reduce((acc, v) => `${acc}\n${v.message}`, ''), severity: check.severity, }); }); return result; }, updateAll: function(store, records, success) { if (!success || records.length === 0) { return; } var me = this; var rec = records[0]; me.status = rec.data; // add health panel me.down('#overallhealth').updateHealth(PVE.Utils.render_ceph_health(rec.data.health || {})); // add errors to gridstore me.down('#warnings').getStore().loadRawData(me.generateCheckData(rec.data.health || {}), false); // update services me.getComponent('services').updateAll(me.metadata || {}, rec.data); // update detailstatus panel me.getComponent('statusdetail').updateAll(me.metadata || {}, rec.data); // add performance data let pgmap = rec.data.pgmap; let used = pgmap.bytes_used; let total = pgmap.bytes_total; var text = Ext.String.format(gettext('{0} of {1}'), Proxmox.Utils.render_size(used), Proxmox.Utils.render_size(total), ); // update the usage widget me.down('#space').updateValue(used/total, text); let readiops = pgmap.read_op_per_sec; let writeiops = pgmap.write_op_per_sec; let reads = pgmap.read_bytes_sec || 0; let writes = pgmap.write_bytes_sec || 0; // update the graphs me.reads.addDataPoint(reads); me.writes.addDataPoint(writes); me.readiops.addDataPoint(readiops); me.writeiops.addDataPoint(writeiops); let degraded = pgmap.degraded_objects || 0; let misplaced = pgmap.misplaced_objects || 0; let unfound = pgmap.unfound_objects || 0; let unhealthy = degraded + unfound + misplaced; // update recovery if (pgmap.recovering_objects_per_sec !== undefined || unhealthy > 0) { let toRecover = pgmap.misplaced_total || pgmap.unfound_total || pgmap.degraded_total || 0; if (toRecover === 0) { return; // FIXME: unexpected return and leaves things possible visible when it shouldn't? } let recovered = toRecover - unhealthy || 0; let speed = pgmap.recovering_bytes_per_sec || 0; let recoveryRatio = recovered / total; let txt = `${(recoveryRatio * 100).toFixed(2)}%`; if (speed > 0) { let obj_per_sec = speed / (4 * 1024 * 1024); // 4 MiB per Object let duration = Proxmox.Utils.format_duration_human(unhealthy/obj_per_sec); let speedTxt = PVE.Utils.render_bandwidth(speed); txt += ` (${speedTxt} - ${duration} left)`; } me.down('#recovery').setVisible(true); me.down('#recoveryprogress').updateValue(recoveryRatio); me.down('#recoveryprogress').updateText(txt); me.down('#recoverychart').addDataPoint(speed); } else { me.down('#recovery').setVisible(false); me.down('#recoverychart').addDataPoint(0); } }, initComponent: function() { var me = this; var nodename = me.pveSelNode.data.node; me.callParent(); var baseurl = '/api2/json' + (nodename ? '/nodes/' + nodename : '/cluster') + '/ceph'; me.store = Ext.create('Proxmox.data.UpdateStore', { storeid: 'ceph-status-' + (nodename || 'cluster'), interval: 5000, proxy: { type: 'proxmox', url: baseurl + '/status', }, }); me.metadatastore = Ext.create('Proxmox.data.UpdateStore', { storeid: 'ceph-metadata-' + (nodename || 'cluster'), interval: 15*1000, proxy: { type: 'proxmox', url: '/api2/json/cluster/ceph/metadata', }, }); // save references for the updatefunction me.iops = me.down('#iops'); me.readiops = me.down('#readiops'); me.writeiops = me.down('#writeiops'); me.reads = me.down('#reads'); me.writes = me.down('#writes'); // manages the "install ceph?" overlay PVE.Utils.monitor_ceph_installed(me, me.store, nodename); me.mon(me.store, 'load', me.updateAll, me); me.mon(me.metadatastore, 'load', function(store, records, success) { if (!success || records.length < 1) { return; } me.metadata = records[0].data; // update services me.getComponent('services').updateAll(me.metadata, me.status || {}); // update detailstatus panel me.getComponent('statusdetail').updateAll(me.metadata, me.status || {}); let maxversion = []; let maxversiontext = ""; for (const [_nodename, data] of Object.entries(me.metadata.node)) { let version = data.version.parts; if (PVE.Utils.compare_ceph_versions(version, maxversion) > 0) { maxversion = version; maxversiontext = data.version.str; } } me.down('#versioninfo').setValue(maxversiontext); }, me); me.on('destroy', me.store.stopUpdate); me.on('destroy', me.metadatastore.stopUpdate); me.store.startUpdate(); me.metadatastore.startUpdate(); }, });