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 data
.noDetails
= data
.detail
.length
=== 0;
109 if (data
.detail
.length
=== 0) {
110 data
.detail
= "no additional data";
115 let rstore
= this.getStore().rstore
;
116 rstore
.loadData(checkRecords
, false);
117 rstore
.fireEvent('load', rstore
, checkRecords
, true);
119 emptyText
: gettext('No Warnings/Errors'),
122 dataIndex
: 'severity',
123 tooltip
: gettext('Severity'),
126 renderer: function(value
) {
127 let health
= PVE
.Utils
.map_ceph_health
[value
];
128 let icon
= PVE
.Utils
.get_health_icon(health
);
129 return `<i class="fa fa-fw ${icon}"></i>`;
132 sorterFn: function(a
, b
) {
133 let health
= ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK'];
134 return health
.indexOf(b
.data
.severity
) - health
.indexOf(a
.data
.severity
);
139 dataIndex
: 'summary',
140 header
: gettext('Summary'),
144 xtype
: 'actioncolumn',
147 tooltip
: gettext('Actions'),
150 iconCls
: 'x-fa fa-files-o',
151 tooltip
: gettext('Copy Summary'),
152 handler: function(grid
, rowindex
, colindex
, item
, e
, record
) {
154 .writeText(record
.data
.summary
)
155 .catch(err
=> Ext
.Msg
.alert(gettext('Error'), err
));
159 iconCls
: 'x-fa fa-clipboard',
160 tooltip
: gettext('Copy All'),
161 isActionDisabled
: (v
, r
, c
, i
, { data
}) => !!data
.noDetails
,
162 handler: function(grid
, rowindex
, colindex
, item
, e
, { data
}) {
164 .writeText(`${data.severity}: ${data.summary}\n${data.detail}`)
165 .catch(err
=> Ext
.Msg
.alert(gettext('Error'), err
));
172 itemdblclick: function(view
, record
, row
, rowIdx
, e
) {
173 // inspired by Ext.grid.plugin.RowExpander, but for double click
174 let rowNode
= view
.getNode(rowIdx
);
175 let normalRow
= Ext
.fly(rowNode
);
177 let collapsedCls
= view
.rowBodyFeature
.rowCollapsedCls
;
179 if (normalRow
.hasCls(collapsedCls
)) {
180 view
.rowBodyFeature
.rowExpander
.toggleRow(rowIdx
, record
);
186 ptype
: 'rowexpander',
187 expandOnDblClick
: false,
188 rowBodyTpl
: '<pre class="pve-ceph-warning-detail">{detail}</pre>',
195 xtype
: 'pveCephStatusDetail',
196 itemId
: 'statusdetail',
197 plugins
: 'responsive',
208 title
: gettext('Status'),
211 xtype
: 'pveCephServices',
212 title
: gettext('Services'),
214 plugins
: 'responsive',
232 title
: gettext('Performance'),
245 xtype
: 'proxmoxGauge',
247 title
: gettext('Usage'),
260 xtype
: 'pveRunningChart',
261 itemId
: 'recoverychart',
262 title
: gettext('Recovery') +'/ '+ gettext('Rebalance'),
263 renderer
: PVE
.Utils
.render_bandwidth
,
267 xtype
: 'progressbar',
268 itemId
: 'recoveryprogress',
283 xtype
: 'pveRunningChart',
285 title
: gettext('Reads'),
286 renderer
: PVE
.Utils
.render_bandwidth
,
289 xtype
: 'pveRunningChart',
291 title
: gettext('Writes'),
292 renderer
: PVE
.Utils
.render_bandwidth
,
295 xtype
: 'pveRunningChart',
297 title
: 'IOPS: ' + gettext('Reads'),
298 renderer
: Ext
.util
.Format
.numberRenderer('0,000'),
301 xtype
: 'pveRunningChart',
303 title
: 'IOPS: ' + gettext('Writes'),
304 renderer
: Ext
.util
.Format
.numberRenderer('0,000'),
312 updateAll: function(store
, records
, success
) {
313 if (!success
|| records
.length
=== 0) {
318 var rec
= records
[0];
319 me
.status
= rec
.data
;
322 me
.down('#overallhealth').updateHealth(PVE
.Utils
.render_ceph_health(rec
.data
.health
|| {}));
323 me
.down('#warnings').updateHealth(rec
.data
.health
|| {}); // add errors to gridstore
325 me
.getComponent('services').updateAll(me
.metadata
|| {}, rec
.data
);
327 me
.getComponent('statusdetail').updateAll(me
.metadata
|| {}, rec
.data
);
329 // add performance data
330 let pgmap
= rec
.data
.pgmap
;
331 let used
= pgmap
.bytes_used
;
332 let total
= pgmap
.bytes_total
;
334 var text
= Ext
.String
.format(gettext('{0} of {1}'),
335 Proxmox
.Utils
.render_size(used
),
336 Proxmox
.Utils
.render_size(total
),
339 // update the usage widget
340 me
.down('#space').updateValue(used
/total
, text
);
342 let readiops
= pgmap
.read_op_per_sec
;
343 let writeiops
= pgmap
.write_op_per_sec
;
344 let reads
= pgmap
.read_bytes_sec
|| 0;
345 let writes
= pgmap
.write_bytes_sec
|| 0;
348 me
.reads
.addDataPoint(reads
);
349 me
.writes
.addDataPoint(writes
);
350 me
.readiops
.addDataPoint(readiops
);
351 me
.writeiops
.addDataPoint(writeiops
);
353 let degraded
= pgmap
.degraded_objects
|| 0;
354 let misplaced
= pgmap
.misplaced_objects
|| 0;
355 let unfound
= pgmap
.unfound_objects
|| 0;
356 let unhealthy
= degraded
+ unfound
+ misplaced
;
358 if (pgmap
.recovering_objects_per_sec
!== undefined || unhealthy
> 0) {
359 let toRecoverObjects
= pgmap
.misplaced_total
|| pgmap
.unfound_total
|| pgmap
.degraded_total
|| 0;
360 if (toRecoverObjects
=== 0) {
361 return; // FIXME: unexpected return and leaves things possible visible when it shouldn't?
363 let recovered
= toRecoverObjects
- unhealthy
|| 0;
364 let speed
= pgmap
.recovering_bytes_per_sec
|| 0;
366 let recoveryRatio
= recovered
/ toRecoverObjects
;
367 let txt
= `${(recoveryRatio * 100).toFixed(2)}%`;
369 let obj_per_sec
= speed
/ (4 * 1024 * 1024); // 4 MiB per Object
370 let duration
= Proxmox
.Utils
.format_duration_human(unhealthy
/obj_per_sec
);
371 let speedTxt
= PVE
.Utils
.render_bandwidth(speed
);
372 txt
+= ` (${speedTxt} - ${duration} left)`;
375 me
.down('#recovery').setVisible(true);
376 me
.down('#recoveryprogress').updateValue(recoveryRatio
);
377 me
.down('#recoveryprogress').updateText(txt
);
378 me
.down('#recoverychart').addDataPoint(speed
);
380 me
.down('#recovery').setVisible(false);
381 me
.down('#recoverychart').addDataPoint(0);
385 initComponent: function() {
388 var nodename
= me
.pveSelNode
.data
.node
;
391 var baseurl
= '/api2/json' + (nodename
? '/nodes/' + nodename
: '/cluster') + '/ceph';
392 me
.store
= Ext
.create('Proxmox.data.UpdateStore', {
393 storeid
: 'ceph-status-' + (nodename
|| 'cluster'),
397 url
: baseurl
+ '/status',
401 me
.metadatastore
= Ext
.create('Proxmox.data.UpdateStore', {
402 storeid
: 'ceph-metadata-' + (nodename
|| 'cluster'),
406 url
: '/api2/json/cluster/ceph/metadata',
410 // save references for the updatefunction
411 me
.iops
= me
.down('#iops');
412 me
.readiops
= me
.down('#readiops');
413 me
.writeiops
= me
.down('#writeiops');
414 me
.reads
= me
.down('#reads');
415 me
.writes
= me
.down('#writes');
417 // manages the "install ceph?" overlay
418 PVE
.Utils
.monitor_ceph_installed(me
, me
.store
, nodename
);
420 me
.mon(me
.store
, 'load', me
.updateAll
, me
);
421 me
.mon(me
.metadatastore
, 'load', function(store
, records
, success
) {
422 if (!success
|| records
.length
< 1) {
425 me
.metadata
= records
[0].data
;
428 me
.getComponent('services').updateAll(me
.metadata
, me
.status
|| {});
430 // update detailstatus panel
431 me
.getComponent('statusdetail').updateAll(me
.metadata
, me
.status
|| {});
434 let maxversiontext
= "";
435 for (const [_nodename
, data
] of Object
.entries(me
.metadata
.node
)) {
436 let version
= data
.version
.parts
;
437 if (PVE
.Utils
.compare_ceph_versions(version
, maxversion
) > 0) {
438 maxversion
= version
;
439 maxversiontext
= data
.version
.str
;
442 me
.down('#versioninfo').setValue(maxversiontext
);
445 me
.on('destroy', me
.store
.stopUpdate
);
446 me
.on('destroy', me
.metadatastore
.stopUpdate
);
447 me
.store
.startUpdate();
448 me
.metadatastore
.startUpdate();