1 Ext
.define('PVE.node.CephStatus', {
2 extend
: 'Ext.panel.Panel',
3 alias
: 'widget.pveNodeCephStatus',
5 onlineHelp
: 'chapter_pveceph',
22 title
: gettext('Health'),
24 plugins
: 'responsive',
50 itemId
: 'overallhealth',
51 xtype
: 'pveHealthWidget',
52 title
: gettext('Status'),
55 itemId
: 'versioninfo',
56 xtype
: 'displayfield',
57 fieldLabel
: gettext('Ceph Version'),
61 'data-qtip': gettext('The newest version installed in the Cluster.'),
65 'text-align': 'center',
74 stateId
: 'ceph-status-warnings',
76 // since we load the store manually to show the emptytext,
77 // we have to specify an empty one here
82 emptyText
: gettext('No Warnings/Errors'),
85 dataIndex
: 'severity',
86 header
: gettext('Severity'),
89 renderer: function(value
) {
90 var health
= PVE
.Utils
.map_ceph_health
[value
];
91 var classes
= PVE
.Utils
.get_health_icon(health
);
93 return '<i class="fa fa-fw ' + classes
+ '"></i>';
96 sorterFn: function(a
, b
) {
97 var healthArr
= ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK'];
98 return healthArr
.indexOf(b
.data
.severity
) - healthArr
.indexOf(a
.data
.severity
);
103 dataIndex
: 'summary',
104 header
: gettext('Summary'),
108 xtype
: 'actioncolumn',
111 tooltip
: gettext('Detail'),
114 iconCls
: 'x-fa fa-info-circle',
115 handler: function(grid
, rowindex
, colindex
, item
, e
, record
) {
116 var win
= Ext
.create('Ext.window.Window', {
117 title
: gettext('Detail'),
130 '<span>' + Ext
.htmlEncode(record
.data
.summary
) + '</span>',
131 '<pre>' + Ext
.htmlEncode(record
.data
.detail
) + '</pre>',
145 xtype
: 'pveCephStatusDetail',
146 itemId
: 'statusdetail',
147 plugins
: 'responsive',
158 title
: gettext('Status'),
161 title
: gettext('Services'),
162 xtype
: 'pveCephServices',
164 plugins
: 'responsive',
182 title
: gettext('Performance'),
195 xtype
: 'proxmoxGauge',
197 title
: gettext('Usage'),
210 itemId
: 'recoverychart',
211 xtype
: 'pveRunningChart',
212 title
: gettext('Recovery') +'/ '+ gettext('Rebalance'),
213 renderer
: PVE
.Utils
.render_bandwidth
,
217 xtype
: 'progressbar',
218 itemId
: 'recoveryprogress',
234 xtype
: 'pveRunningChart',
235 title
: gettext('Reads'),
236 renderer
: PVE
.Utils
.render_bandwidth
,
240 xtype
: 'pveRunningChart',
241 title
: gettext('Writes'),
242 renderer
: PVE
.Utils
.render_bandwidth
,
246 xtype
: 'pveRunningChart',
247 title
: 'IOPS: ' + gettext('Reads'),
248 renderer
: Ext
.util
.Format
.numberRenderer('0,000'),
252 xtype
: 'pveRunningChart',
253 title
: 'IOPS: ' + gettext('Writes'),
254 renderer
: Ext
.util
.Format
.numberRenderer('0,000'),
262 generateCheckData: function(health
) {
264 var checks
= health
.checks
|| {};
265 var keys
= Ext
.Object
.getKeys(checks
).sort();
267 Ext
.Array
.forEach(keys
, function(key
) {
268 var details
= checks
[key
].detail
|| [];
271 summary
: checks
[key
].summary
.message
,
272 detail
: Ext
.Array
.reduce(
274 function(first
, second
) {
275 return first
+ '\n' + second
.message
;
279 severity
: checks
[key
].severity
,
286 updateAll: function(store
, records
, success
) {
287 if (!success
|| records
.length
=== 0) {
292 var rec
= records
[0];
293 me
.status
= rec
.data
;
296 me
.down('#overallhealth').updateHealth(PVE
.Utils
.render_ceph_health(rec
.data
.health
|| {}));
297 // add errors to gridstore
298 me
.down('#warnings').getStore().loadRawData(me
.generateCheckData(rec
.data
.health
|| {}), false);
301 me
.getComponent('services').updateAll(me
.metadata
|| {}, rec
.data
);
303 // update detailstatus panel
304 me
.getComponent('statusdetail').updateAll(me
.metadata
|| {}, rec
.data
);
306 // add performance data
307 let pgmap
= rec
.data
.pgmap
;
308 let used
= pgmap
.bytes_used
;
309 let total
= pgmap
.bytes_total
;
311 var text
= Ext
.String
.format(gettext('{0} of {1}'),
312 Proxmox
.Utils
.render_size(used
),
313 Proxmox
.Utils
.render_size(total
),
316 // update the usage widget
317 me
.down('#space').updateValue(used
/total
, text
);
319 let readiops
= pgmap
.read_op_per_sec
;
320 let writeiops
= pgmap
.write_op_per_sec
;
321 let reads
= pgmap
.read_bytes_sec
|| 0;
322 let writes
= pgmap
.write_bytes_sec
|| 0;
325 me
.reads
.addDataPoint(reads
);
326 me
.writes
.addDataPoint(writes
);
327 me
.readiops
.addDataPoint(readiops
);
328 me
.writeiops
.addDataPoint(writeiops
);
330 let degraded
= pgmap
.degraded_objects
|| 0;
331 let misplaced
= pgmap
.misplaced_objects
|| 0;
332 let unfound
= pgmap
.unfound_objects
|| 0;
333 let unhealthy
= degraded
+ unfound
+ misplaced
;
335 if (pgmap
.recovering_objects_per_sec
!== undefined || unhealthy
> 0) {
336 let total
= pgmap
.misplaced_total
|| pgmap
.unfound_total
|| pgmap
.degraded_total
|| 0;
337 if (total
=== 0) return;
338 let recovered
= total
- unhealthy
|| 0;
339 let speed
= pgmap
.recovering_bytes_per_sec
|| 0;
340 let speedTxt
= PVE
.Utils
.render_bandwidth(speed
);
341 let obj_per_sec
= speed
/ (4*1024*1024); // 4MiB per Object
342 let duration
= Proxmox
.Utils
.format_duration_human(unhealthy
/obj_per_sec
);
344 let percentage
= recovered
/total
;
345 let txt
= `${(percentage*100).toFixed(2)}%`;
347 txt
+= ` (${speedTxt} - ${duration} left)`;
350 me
.down('#recovery').setVisible(true);
351 me
.down('#recoveryprogress').updateValue(percentage
);
352 me
.down('#recoveryprogress').updateText(txt
);
353 me
.down('#recoverychart').addDataPoint(speed
);
355 me
.down('#recovery').setVisible(false);
356 me
.down('#recoverychart').addDataPoint(0);
360 initComponent: function() {
363 var nodename
= me
.pveSelNode
.data
.node
;
366 var baseurl
= '/api2/json' + (nodename
? '/nodes/' + nodename
: '/cluster') + '/ceph';
367 me
.store
= Ext
.create('Proxmox.data.UpdateStore', {
368 storeid
: 'ceph-status-' + (nodename
|| 'cluster'),
372 url
: baseurl
+ '/status',
376 me
.metadatastore
= Ext
.create('Proxmox.data.UpdateStore', {
377 storeid
: 'ceph-metadata-' + (nodename
|| 'cluster'),
381 url
: '/api2/json/cluster/ceph/metadata',
385 // save references for the updatefunction
386 me
.iops
= me
.down('#iops');
387 me
.readiops
= me
.down('#readiops');
388 me
.writeiops
= me
.down('#writeiops');
389 me
.reads
= me
.down('#reads');
390 me
.writes
= me
.down('#writes');
392 // manages the "install ceph?" overlay
393 PVE
.Utils
.monitor_ceph_installed(me
, me
.store
, nodename
);
395 me
.mon(me
.store
, 'load', me
.updateAll
, me
);
396 me
.mon(me
.metadatastore
, 'load', function(store
, records
, success
) {
397 if (!success
|| records
.length
< 1) {
400 var rec
= records
[0];
401 me
.metadata
= rec
.data
;
404 me
.getComponent('services').updateAll(rec
.data
, me
.status
|| {});
406 // update detailstatus panel
407 me
.getComponent('statusdetail').updateAll(rec
.data
, me
.status
|| {});
410 let maxversiontext
= "";
411 for (const [nodename
, data
] of Object
.entries(me
.metadata
.node
)) {
412 let version
= data
.version
.parts
;
413 if (PVE
.Utils
.compare_ceph_versions(version
, maxversion
) > 0) {
414 maxversion
= version
;
415 maxversiontext
= data
.version
.str
;
418 me
.down('#versioninfo').setValue(maxversiontext
);
421 me
.on('destroy', me
.store
.stopUpdate
);
422 me
.on('destroy', me
.metadatastore
.stopUpdate
);
423 me
.store
.startUpdate();
424 me
.metadatastore
.startUpdate();