1 Ext
.define('pve-ceph-warnings', {
2 extend
: 'Ext.data.Model',
3 fields
: ['id', 'summary', 'detail', 'severity'],
8 Ext
.define('PVE.node.CephStatus', {
9 extend
: 'Ext.panel.Panel',
10 alias
: 'widget.pveNodeCephStatus',
12 onlineHelp
: 'chapter_pveceph',
27 title
: gettext('Health'),
29 plugins
: 'responsive',
55 xtype
: 'pveHealthWidget',
56 itemId
: 'overallhealth',
58 title
: gettext('Status'),
61 xtype
: 'displayfield',
62 itemId
: 'versioninfo',
63 fieldLabel
: gettext('Ceph Version'),
67 'data-qtip': gettext('The newest version installed in the Cluster.'),
71 'text-align': 'center',
82 stateId
: 'ceph-status-warnings',
84 enableTextSelection
: true,
86 // we load the store manually, to show an emptyText specify an empty intermediate store
92 storeid
: 'pve-ceph-warnings',
94 model
: 'pve-ceph-warnings',
97 updateHealth: function(health
) {
98 let checks
= health
.checks
|| {};
100 let checkRecords
= Object
.keys(checks
).sort().map(key
=> {
101 let check
= checks
[key
];
104 summary
: check
.summary
.message
,
105 detail
: check
.detail
.reduce((acc
, v
) => `${acc}\n${v.message}`, '').trimStart(),
106 severity
: check
.severity
,
108 if (data
.detail
.length
=== 0) {
109 data
.detail
= "no additional data";
114 let rstore
= this.getStore().rstore
;
115 rstore
.loadData(checkRecords
, false);
116 rstore
.fireEvent('load', rstore
, checkRecords
, true);
118 emptyText
: gettext('No Warnings/Errors'),
121 dataIndex
: 'severity',
122 tooltip
: gettext('Severity'),
125 renderer: function(value
) {
126 let health
= PVE
.Utils
.map_ceph_health
[value
];
127 let icon
= PVE
.Utils
.get_health_icon(health
);
128 return `<i class="fa fa-fw ${icon}"></i>`;
131 sorterFn: function(a
, b
) {
132 let health
= ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK'];
133 return health
.indexOf(b
.data
.severity
) - health
.indexOf(a
.data
.severity
);
138 dataIndex
: 'summary',
139 header
: gettext('Summary'),
143 xtype
: 'actioncolumn',
146 tooltip
: gettext('Actions'),
149 iconCls
: 'x-fa fa-files-o',
150 tooltip
: gettext('Copy summary'),
151 handler: function(grid
, rowindex
, colindex
, item
, e
, record
) {
153 .writeText(record
.data
.summary
)
154 .catch(err
=> Ext
.Msg
.alert(gettext('Error'), err
));
158 iconCls
: 'x-fa fa-clipboard',
159 tooltip
: gettext('Copy details'),
160 handler: function(grid
, rowindex
, colindex
, item
, e
, record
) {
162 .writeText(record
.data
.detail
)
163 .catch(err
=> Ext
.Msg
.alert(gettext('Error'), err
));
170 itemdblclick: function(view
, record
, row
, rowIdx
, e
) {
171 // inspired by Ext.grid.plugin.RowExpander, but for double click
172 let rowNode
= view
.getNode(rowIdx
);
173 let normalRow
= Ext
.fly(rowNode
);
175 let collapsedCls
= view
.rowBodyFeature
.rowCollapsedCls
;
177 if (normalRow
.hasCls(collapsedCls
)) {
178 view
.rowBodyFeature
.rowExpander
.toggleRow(rowIdx
, record
);
184 ptype
: 'rowexpander',
185 expandOnDblClick
: false,
186 rowBodyTpl
: '<pre class="pve-ceph-warning-detail">{detail}</pre>',
193 xtype
: 'pveCephStatusDetail',
194 itemId
: 'statusdetail',
195 plugins
: 'responsive',
206 title
: gettext('Status'),
209 xtype
: 'pveCephServices',
210 title
: gettext('Services'),
212 plugins
: 'responsive',
230 title
: gettext('Performance'),
243 xtype
: 'proxmoxGauge',
245 title
: gettext('Usage'),
258 xtype
: 'pveRunningChart',
259 itemId
: 'recoverychart',
260 title
: gettext('Recovery') +'/ '+ gettext('Rebalance'),
261 renderer
: PVE
.Utils
.render_bandwidth
,
265 xtype
: 'progressbar',
266 itemId
: 'recoveryprogress',
281 xtype
: 'pveRunningChart',
283 title
: gettext('Reads'),
284 renderer
: PVE
.Utils
.render_bandwidth
,
287 xtype
: 'pveRunningChart',
289 title
: gettext('Writes'),
290 renderer
: PVE
.Utils
.render_bandwidth
,
293 xtype
: 'pveRunningChart',
295 title
: 'IOPS: ' + gettext('Reads'),
296 renderer
: Ext
.util
.Format
.numberRenderer('0,000'),
299 xtype
: 'pveRunningChart',
301 title
: 'IOPS: ' + gettext('Writes'),
302 renderer
: Ext
.util
.Format
.numberRenderer('0,000'),
310 updateAll: function(store
, records
, success
) {
311 if (!success
|| records
.length
=== 0) {
316 var rec
= records
[0];
317 me
.status
= rec
.data
;
320 me
.down('#overallhealth').updateHealth(PVE
.Utils
.render_ceph_health(rec
.data
.health
|| {}));
321 me
.down('#warnings').updateHealth(rec
.data
.health
|| {}); // add errors to gridstore
323 me
.getComponent('services').updateAll(me
.metadata
|| {}, rec
.data
);
325 me
.getComponent('statusdetail').updateAll(me
.metadata
|| {}, rec
.data
);
327 // add performance data
328 let pgmap
= rec
.data
.pgmap
;
329 let used
= pgmap
.bytes_used
;
330 let total
= pgmap
.bytes_total
;
332 var text
= Ext
.String
.format(gettext('{0} of {1}'),
333 Proxmox
.Utils
.render_size(used
),
334 Proxmox
.Utils
.render_size(total
),
337 // update the usage widget
338 me
.down('#space').updateValue(used
/total
, text
);
340 let readiops
= pgmap
.read_op_per_sec
;
341 let writeiops
= pgmap
.write_op_per_sec
;
342 let reads
= pgmap
.read_bytes_sec
|| 0;
343 let writes
= pgmap
.write_bytes_sec
|| 0;
346 me
.reads
.addDataPoint(reads
);
347 me
.writes
.addDataPoint(writes
);
348 me
.readiops
.addDataPoint(readiops
);
349 me
.writeiops
.addDataPoint(writeiops
);
351 let degraded
= pgmap
.degraded_objects
|| 0;
352 let misplaced
= pgmap
.misplaced_objects
|| 0;
353 let unfound
= pgmap
.unfound_objects
|| 0;
354 let unhealthy
= degraded
+ unfound
+ misplaced
;
356 if (pgmap
.recovering_objects_per_sec
!== undefined || unhealthy
> 0) {
357 let toRecoverObjects
= pgmap
.misplaced_total
|| pgmap
.unfound_total
|| pgmap
.degraded_total
|| 0;
358 if (toRecoverObjects
=== 0) {
359 return; // FIXME: unexpected return and leaves things possible visible when it shouldn't?
361 let recovered
= toRecoverObjects
- unhealthy
|| 0;
362 let speed
= pgmap
.recovering_bytes_per_sec
|| 0;
364 let recoveryRatio
= recovered
/ toRecoverObjects
;
365 let txt
= `${(recoveryRatio * 100).toFixed(2)}%`;
367 let obj_per_sec
= speed
/ (4 * 1024 * 1024); // 4 MiB per Object
368 let duration
= Proxmox
.Utils
.format_duration_human(unhealthy
/obj_per_sec
);
369 let speedTxt
= PVE
.Utils
.render_bandwidth(speed
);
370 txt
+= ` (${speedTxt} - ${duration} left)`;
373 me
.down('#recovery').setVisible(true);
374 me
.down('#recoveryprogress').updateValue(recoveryRatio
);
375 me
.down('#recoveryprogress').updateText(txt
);
376 me
.down('#recoverychart').addDataPoint(speed
);
378 me
.down('#recovery').setVisible(false);
379 me
.down('#recoverychart').addDataPoint(0);
383 initComponent: function() {
386 var nodename
= me
.pveSelNode
.data
.node
;
389 var baseurl
= '/api2/json' + (nodename
? '/nodes/' + nodename
: '/cluster') + '/ceph';
390 me
.store
= Ext
.create('Proxmox.data.UpdateStore', {
391 storeid
: 'ceph-status-' + (nodename
|| 'cluster'),
395 url
: baseurl
+ '/status',
399 me
.metadatastore
= Ext
.create('Proxmox.data.UpdateStore', {
400 storeid
: 'ceph-metadata-' + (nodename
|| 'cluster'),
404 url
: '/api2/json/cluster/ceph/metadata',
408 // save references for the updatefunction
409 me
.iops
= me
.down('#iops');
410 me
.readiops
= me
.down('#readiops');
411 me
.writeiops
= me
.down('#writeiops');
412 me
.reads
= me
.down('#reads');
413 me
.writes
= me
.down('#writes');
415 // manages the "install ceph?" overlay
416 PVE
.Utils
.monitor_ceph_installed(me
, me
.store
, nodename
);
418 me
.mon(me
.store
, 'load', me
.updateAll
, me
);
419 me
.mon(me
.metadatastore
, 'load', function(store
, records
, success
) {
420 if (!success
|| records
.length
< 1) {
423 me
.metadata
= records
[0].data
;
426 me
.getComponent('services').updateAll(me
.metadata
, me
.status
|| {});
428 // update detailstatus panel
429 me
.getComponent('statusdetail').updateAll(me
.metadata
, me
.status
|| {});
432 let maxversiontext
= "";
433 for (const [_nodename
, data
] of Object
.entries(me
.metadata
.node
)) {
434 let version
= data
.version
.parts
;
435 if (PVE
.Utils
.compare_ceph_versions(version
, maxversion
) > 0) {
436 maxversion
= version
;
437 maxversiontext
= data
.version
.str
;
440 me
.down('#versioninfo').setValue(maxversiontext
);
443 me
.on('destroy', me
.store
.stopUpdate
);
444 me
.on('destroy', me
.metadatastore
.stopUpdate
);
445 me
.store
.startUpdate();
446 me
.metadatastore
.startUpdate();