]>
Commit | Line | Data |
---|---|---|
459b6c31 AL |
1 | Ext.define('pve-ceph-warnings', { |
2 | extend: 'Ext.data.Model', | |
3 | fields: ['id', 'summary', 'detail', 'severity'], | |
4 | idProperty: 'id', | |
5 | }); | |
6 | ||
7 | ||
bd39c945 | 8 | Ext.define('PVE.node.CephStatus', { |
946730cd DC |
9 | extend: 'Ext.panel.Panel', |
10 | alias: 'widget.pveNodeCephStatus', | |
11 | ||
ba93a9c6 | 12 | onlineHelp: 'chapter_pveceph', |
bd39c945 | 13 | |
946730cd | 14 | scrollable: true, |
9f7cbaf3 | 15 | bodyPadding: 5, |
9f7cbaf3 | 16 | layout: { |
f6710aac | 17 | type: 'column', |
9f7cbaf3 | 18 | }, |
946730cd DC |
19 | |
20 | defaults: { | |
f6710aac | 21 | padding: 5, |
946730cd DC |
22 | }, |
23 | ||
24 | items: [ | |
25 | { | |
26 | xtype: 'panel', | |
27 | title: gettext('Health'), | |
9f7cbaf3 DC |
28 | bodyPadding: 10, |
29 | plugins: 'responsive', | |
30 | responsiveConfig: { | |
35279245 | 31 | 'width < 1600': { |
27b91275 | 32 | minHeight: 230, |
f6710aac | 33 | columnWidth: 1, |
9f7cbaf3 | 34 | }, |
35279245 | 35 | 'width >= 1600': { |
27b91275 | 36 | minHeight: 500, |
f6710aac TL |
37 | columnWidth: 0.5, |
38 | }, | |
9f7cbaf3 | 39 | }, |
946730cd DC |
40 | layout: { |
41 | type: 'hbox', | |
f6710aac | 42 | align: 'stretch', |
946730cd DC |
43 | }, |
44 | items: [ | |
45 | { | |
949a6609 DC |
46 | xtype: 'container', |
47 | layout: { | |
48 | type: 'vbox', | |
49 | align: 'stretch', | |
50 | }, | |
946730cd | 51 | flex: 1, |
949a6609 DC |
52 | items: [ |
53 | { | |
616d54de | 54 | |
949a6609 | 55 | xtype: 'pveHealthWidget', |
616d54de TL |
56 | itemId: 'overallhealth', |
57 | flex: 1, | |
f6710aac | 58 | title: gettext('Status'), |
949a6609 DC |
59 | }, |
60 | { | |
0beb2578 | 61 | xtype: 'displayfield', |
616d54de | 62 | itemId: 'versioninfo', |
0beb2578 TL |
63 | fieldLabel: gettext('Ceph Version'), |
64 | value: "", | |
65 | autoEl: { | |
66 | tag: 'div', | |
67 | 'data-qtip': gettext('The newest version installed in the Cluster.'), | |
949a6609 DC |
68 | }, |
69 | padding: '10 0 0 0', | |
70 | style: { | |
71 | 'text-align': 'center', | |
72 | }, | |
f6710aac | 73 | }, |
0beb2578 | 74 | ], |
946730cd DC |
75 | }, |
76 | { | |
616d54de | 77 | xtype: 'grid', |
946730cd | 78 | itemId: 'warnings', |
616d54de | 79 | flex: 2, |
459b6c31 | 80 | maxHeight: 430, |
946730cd DC |
81 | stateful: true, |
82 | stateId: 'ceph-status-warnings', | |
459b6c31 AL |
83 | viewConfig: { |
84 | enableTextSelection: true, | |
6e7cef49 DC |
85 | listeners: { |
86 | collapsebody: function(rowNode, record) { | |
87 | record.set('expanded', false); | |
88 | record.commit(); | |
89 | }, | |
90 | expandbody: function(rowNode, record) { | |
91 | record.set('expanded', true); | |
92 | record.commit(); | |
93 | }, | |
94 | }, | |
459b6c31 | 95 | }, |
10b00f3a | 96 | // we load the store manually, to show an emptyText specify an empty intermediate store |
23f14fd9 | 97 | store: { |
459b6c31 | 98 | type: 'diff', |
ca0267fd | 99 | trackRemoved: false, |
23f14fd9 | 100 | data: [], |
459b6c31 AL |
101 | rstore: { |
102 | storeid: 'pve-ceph-warnings', | |
103 | type: 'update', | |
104 | model: 'pve-ceph-warnings', | |
105 | }, | |
23f14fd9 | 106 | }, |
b8febbcc TL |
107 | updateHealth: function(health) { |
108 | let checks = health.checks || {}; | |
109 | ||
110 | let checkRecords = Object.keys(checks).sort().map(key => { | |
111 | let check = checks[key]; | |
459b6c31 | 112 | let data = { |
b8febbcc TL |
113 | id: key, |
114 | summary: check.summary.message, | |
459b6c31 | 115 | detail: check.detail.reduce((acc, v) => `${acc}\n${v.message}`, '').trimStart(), |
b8febbcc TL |
116 | severity: check.severity, |
117 | }; | |
43fdec75 | 118 | data.noDetails = data.detail.length === 0; |
e828af06 | 119 | data.detailsCls = data.detail.length === 0 ? 'pmx-opacity-75' : ''; |
459b6c31 AL |
120 | if (data.detail.length === 0) { |
121 | data.detail = "no additional data"; | |
122 | } | |
123 | return data; | |
b8febbcc TL |
124 | }); |
125 | ||
459b6c31 AL |
126 | let rstore = this.getStore().rstore; |
127 | rstore.loadData(checkRecords, false); | |
128 | rstore.fireEvent('load', rstore, checkRecords, true); | |
b8febbcc | 129 | }, |
946730cd DC |
130 | emptyText: gettext('No Warnings/Errors'), |
131 | columns: [ | |
132 | { | |
133 | dataIndex: 'severity', | |
459b6c31 | 134 | tooltip: gettext('Severity'), |
946730cd | 135 | align: 'center', |
459b6c31 | 136 | width: 38, |
946730cd | 137 | renderer: function(value) { |
10b00f3a TL |
138 | let health = PVE.Utils.map_ceph_health[value]; |
139 | let icon = PVE.Utils.get_health_icon(health); | |
5f2e6c2e | 140 | return `<i class="fa fa-fw ${icon}"></i>`; |
946730cd DC |
141 | }, |
142 | sorter: { | |
f6710aac | 143 | sorterFn: function(a, b) { |
10b00f3a TL |
144 | let health = ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK']; |
145 | return health.indexOf(b.data.severity) - health.indexOf(a.data.severity); | |
f6710aac TL |
146 | }, |
147 | }, | |
946730cd DC |
148 | }, |
149 | { | |
150 | dataIndex: 'summary', | |
151 | header: gettext('Summary'), | |
6e7cef49 DC |
152 | renderer: function(value, metaData, record, rI, cI, store, view) { |
153 | if (record.get('expanded')) { | |
154 | metaData.tdCls = 'pmx-column-wrapped'; | |
155 | } | |
156 | return value; | |
157 | }, | |
f6710aac | 158 | flex: 1, |
e932cd5f DC |
159 | }, |
160 | { | |
161 | xtype: 'actioncolumn', | |
459b6c31 | 162 | width: 50, |
e932cd5f | 163 | align: 'center', |
459b6c31 | 164 | tooltip: gettext('Actions'), |
e932cd5f | 165 | items: [ |
459b6c31 AL |
166 | { |
167 | iconCls: 'x-fa fa-clipboard', | |
7e7e2dc4 | 168 | tooltip: gettext('Copy to Clipboard'), |
f2a0f0ec | 169 | handler: function(grid, rowindex, colindex, item, e, { data }) { |
7e7e2dc4 | 170 | let detail = data.noDetails ? '': `\n${data.detail}`; |
feb19220 | 171 | navigator.clipboard |
7e7e2dc4 | 172 | .writeText(`${data.severity}: ${data.summary}${detail}`) |
feb19220 | 173 | .catch(err => Ext.Msg.alert(gettext('Error'), err)); |
f6710aac TL |
174 | }, |
175 | }, | |
176 | ], | |
177 | }, | |
178 | ], | |
459b6c31 AL |
179 | listeners: { |
180 | itemdblclick: function(view, record, row, rowIdx, e) { | |
feb19220 TL |
181 | // inspired by Ext.grid.plugin.RowExpander, but for double click |
182 | let rowNode = view.getNode(rowIdx); | |
183 | let normalRow = Ext.fly(rowNode); | |
459b6c31 AL |
184 | |
185 | let collapsedCls = view.rowBodyFeature.rowCollapsedCls; | |
186 | ||
187 | if (normalRow.hasCls(collapsedCls)) { | |
188 | view.rowBodyFeature.rowExpander.toggleRow(rowIdx, record); | |
189 | } | |
190 | }, | |
191 | }, | |
192 | plugins: [ | |
193 | { | |
194 | ptype: 'rowexpander', | |
195 | expandOnDblClick: false, | |
e4a2efc6 | 196 | scrollIntoViewOnExpand: false, |
aa730149 | 197 | rowBodyTpl: '<pre class="pve-ceph-warning-detail {detailsCls}">{detail}</pre>', |
459b6c31 AL |
198 | }, |
199 | ], | |
f6710aac TL |
200 | }, |
201 | ], | |
946730cd DC |
202 | }, |
203 | { | |
204 | xtype: 'pveCephStatusDetail', | |
205 | itemId: 'statusdetail', | |
9f7cbaf3 DC |
206 | plugins: 'responsive', |
207 | responsiveConfig: { | |
35279245 | 208 | 'width < 1600': { |
27b91275 | 209 | columnWidth: 1, |
f6710aac | 210 | minHeight: 250, |
9f7cbaf3 | 211 | }, |
35279245 | 212 | 'width >= 1600': { |
27b91275 | 213 | columnWidth: 0.5, |
f6710aac TL |
214 | minHeight: 300, |
215 | }, | |
9f7cbaf3 | 216 | }, |
f6710aac | 217 | title: gettext('Status'), |
946730cd | 218 | }, |
4ad4262d | 219 | { |
4ad4262d | 220 | xtype: 'pveCephServices', |
616d54de | 221 | title: gettext('Services'), |
4ad4262d DC |
222 | itemId: 'services', |
223 | plugins: 'responsive', | |
224 | layout: { | |
225 | type: 'hbox', | |
f6710aac | 226 | align: 'stretch', |
4ad4262d DC |
227 | }, |
228 | responsiveConfig: { | |
35279245 | 229 | 'width < 1600': { |
4ad4262d | 230 | columnWidth: 1, |
f6710aac | 231 | minHeight: 200, |
4ad4262d | 232 | }, |
35279245 | 233 | 'width >= 1600': { |
4ad4262d | 234 | columnWidth: 0.5, |
f6710aac TL |
235 | minHeight: 200, |
236 | }, | |
237 | }, | |
4ad4262d | 238 | }, |
946730cd DC |
239 | { |
240 | xtype: 'panel', | |
241 | title: gettext('Performance'), | |
9f7cbaf3 DC |
242 | columnWidth: 1, |
243 | bodyPadding: 5, | |
946730cd DC |
244 | layout: { |
245 | type: 'hbox', | |
f6710aac | 246 | align: 'center', |
946730cd DC |
247 | }, |
248 | items: [ | |
249 | { | |
62281115 | 250 | xtype: 'container', |
616d54de | 251 | flex: 1, |
62281115 DC |
252 | items: [ |
253 | { | |
254 | xtype: 'proxmoxGauge', | |
255 | itemId: 'space', | |
f6710aac | 256 | title: gettext('Usage'), |
62281115 DC |
257 | }, |
258 | { | |
259 | flex: 1, | |
260 | border: false, | |
261 | }, | |
262 | { | |
263 | xtype: 'container', | |
264 | itemId: 'recovery', | |
265 | hidden: true, | |
266 | padding: 25, | |
267 | items: [ | |
268 | { | |
62281115 | 269 | xtype: 'pveRunningChart', |
616d54de | 270 | itemId: 'recoverychart', |
e767e1ec | 271 | title: gettext('Recovery') +'/ '+ gettext('Rebalance'), |
62281115 DC |
272 | renderer: PVE.Utils.render_bandwidth, |
273 | height: 100, | |
274 | }, | |
275 | { | |
276 | xtype: 'progressbar', | |
277 | itemId: 'recoveryprogress', | |
278 | }, | |
f6710aac | 279 | ], |
62281115 | 280 | }, |
f6710aac | 281 | ], |
946730cd DC |
282 | }, |
283 | { | |
946730cd | 284 | xtype: 'container', |
616d54de | 285 | flex: 2, |
946730cd | 286 | defaults: { |
9f7cbaf3 | 287 | padding: 0, |
f6710aac | 288 | height: 100, |
946730cd DC |
289 | }, |
290 | items: [ | |
291 | { | |
946730cd | 292 | xtype: 'pveRunningChart', |
616d54de | 293 | itemId: 'reads', |
946730cd | 294 | title: gettext('Reads'), |
f6710aac | 295 | renderer: PVE.Utils.render_bandwidth, |
946730cd DC |
296 | }, |
297 | { | |
946730cd | 298 | xtype: 'pveRunningChart', |
616d54de | 299 | itemId: 'writes', |
946730cd | 300 | title: gettext('Writes'), |
f6710aac | 301 | renderer: PVE.Utils.render_bandwidth, |
946730cd | 302 | }, |
946730cd | 303 | { |
946730cd | 304 | xtype: 'pveRunningChart', |
616d54de | 305 | itemId: 'readiops', |
8f8ec25d | 306 | title: 'IOPS: ' + gettext('Reads'), |
f6710aac | 307 | renderer: Ext.util.Format.numberRenderer('0,000'), |
946730cd DC |
308 | }, |
309 | { | |
946730cd | 310 | xtype: 'pveRunningChart', |
616d54de | 311 | itemId: 'writeiops', |
8f8ec25d | 312 | title: 'IOPS: ' + gettext('Writes'), |
f6710aac | 313 | renderer: Ext.util.Format.numberRenderer('0,000'), |
df503ff9 | 314 | }, |
f6710aac TL |
315 | ], |
316 | }, | |
317 | ], | |
318 | }, | |
946730cd | 319 | ], |
bd39c945 | 320 | |
946730cd DC |
321 | updateAll: function(store, records, success) { |
322 | if (!success || records.length === 0) { | |
323 | return; | |
324 | } | |
bd39c945 | 325 | |
946730cd DC |
326 | var me = this; |
327 | var rec = records[0]; | |
0bf3c581 | 328 | me.status = rec.data; |
bd39c945 | 329 | |
946730cd | 330 | // add health panel |
dfe6d184 | 331 | me.down('#overallhealth').updateHealth(PVE.Utils.render_ceph_health(rec.data.health || {})); |
b8febbcc | 332 | me.down('#warnings').updateHealth(rec.data.health || {}); // add errors to gridstore |
bd39c945 | 333 | |
4ad4262d DC |
334 | me.getComponent('services').updateAll(me.metadata || {}, rec.data); |
335 | ||
0bf3c581 | 336 | me.getComponent('statusdetail').updateAll(me.metadata || {}, rec.data); |
bd39c945 | 337 | |
946730cd | 338 | // add performance data |
df503ff9 TL |
339 | let pgmap = rec.data.pgmap; |
340 | let used = pgmap.bytes_used; | |
341 | let total = pgmap.bytes_total; | |
bd39c945 | 342 | |
946730cd | 343 | var text = Ext.String.format(gettext('{0} of {1}'), |
1bd7bcdb DC |
344 | Proxmox.Utils.render_size(used), |
345 | Proxmox.Utils.render_size(total), | |
946730cd | 346 | ); |
bd39c945 | 347 | |
946730cd | 348 | // update the usage widget |
da266fdc CH |
349 | const usage = total > 0 ? used / total : 0; |
350 | me.down('#space').updateValue(usage, text); | |
bd39c945 | 351 | |
df503ff9 TL |
352 | let readiops = pgmap.read_op_per_sec; |
353 | let writeiops = pgmap.write_op_per_sec; | |
354 | let reads = pgmap.read_bytes_sec || 0; | |
355 | let writes = pgmap.write_bytes_sec || 0; | |
bd39c945 | 356 | |
946730cd DC |
357 | // update the graphs |
358 | me.reads.addDataPoint(reads); | |
359 | me.writes.addDataPoint(writes); | |
946730cd DC |
360 | me.readiops.addDataPoint(readiops); |
361 | me.writeiops.addDataPoint(writeiops); | |
62281115 DC |
362 | |
363 | let degraded = pgmap.degraded_objects || 0; | |
364 | let misplaced = pgmap.misplaced_objects || 0; | |
365 | let unfound = pgmap.unfound_objects || 0; | |
366 | let unhealthy = degraded + unfound + misplaced; | |
367 | // update recovery | |
368 | if (pgmap.recovering_objects_per_sec !== undefined || unhealthy > 0) { | |
cb2013db DC |
369 | let toRecoverObjects = pgmap.misplaced_total || pgmap.unfound_total || pgmap.degraded_total || 0; |
370 | if (toRecoverObjects === 0) { | |
10b00f3a TL |
371 | return; // FIXME: unexpected return and leaves things possible visible when it shouldn't? |
372 | } | |
cb2013db | 373 | let recovered = toRecoverObjects - unhealthy || 0; |
62281115 | 374 | let speed = pgmap.recovering_bytes_per_sec || 0; |
62281115 | 375 | |
cb2013db | 376 | let recoveryRatio = recovered / toRecoverObjects; |
10b00f3a | 377 | let txt = `${(recoveryRatio * 100).toFixed(2)}%`; |
62281115 | 378 | if (speed > 0) { |
10b00f3a TL |
379 | let obj_per_sec = speed / (4 * 1024 * 1024); // 4 MiB per Object |
380 | let duration = Proxmox.Utils.format_duration_human(unhealthy/obj_per_sec); | |
381 | let speedTxt = PVE.Utils.render_bandwidth(speed); | |
62281115 DC |
382 | txt += ` (${speedTxt} - ${duration} left)`; |
383 | } | |
384 | ||
385 | me.down('#recovery').setVisible(true); | |
10b00f3a | 386 | me.down('#recoveryprogress').updateValue(recoveryRatio); |
62281115 DC |
387 | me.down('#recoveryprogress').updateText(txt); |
388 | me.down('#recoverychart').addDataPoint(speed); | |
389 | } else { | |
390 | me.down('#recovery').setVisible(false); | |
391 | me.down('#recoverychart').addDataPoint(0); | |
392 | } | |
946730cd DC |
393 | }, |
394 | ||
946730cd DC |
395 | initComponent: function() { |
396 | var me = this; | |
bd39c945 | 397 | |
946730cd | 398 | var nodename = me.pveSelNode.data.node; |
bd39c945 | 399 | |
946730cd | 400 | me.callParent(); |
d2193664 | 401 | var baseurl = '/api2/json' + (nodename ? '/nodes/' + nodename : '/cluster') + '/ceph'; |
0c7c0d6b | 402 | me.store = Ext.create('Proxmox.data.UpdateStore', { |
2365c5c1 | 403 | storeid: 'ceph-status-' + (nodename || 'cluster'), |
946730cd DC |
404 | interval: 5000, |
405 | proxy: { | |
56a353b9 | 406 | type: 'proxmox', |
f6710aac TL |
407 | url: baseurl + '/status', |
408 | }, | |
bd39c945 DM |
409 | }); |
410 | ||
7f58689d DC |
411 | me.metadatastore = Ext.create('Proxmox.data.UpdateStore', { |
412 | storeid: 'ceph-metadata-' + (nodename || 'cluster'), | |
413 | interval: 15*1000, | |
414 | proxy: { | |
415 | type: 'proxmox', | |
f6710aac TL |
416 | url: '/api2/json/cluster/ceph/metadata', |
417 | }, | |
7f58689d DC |
418 | }); |
419 | ||
946730cd DC |
420 | // save references for the updatefunction |
421 | me.iops = me.down('#iops'); | |
422 | me.readiops = me.down('#readiops'); | |
423 | me.writeiops = me.down('#writeiops'); | |
424 | me.reads = me.down('#reads'); | |
425 | me.writes = me.down('#writes'); | |
426 | ||
13786fb0 TL |
427 | // manages the "install ceph?" overlay |
428 | PVE.Utils.monitor_ceph_installed(me, me.store, nodename); | |
4616a55b | 429 | |
946730cd | 430 | me.mon(me.store, 'load', me.updateAll, me); |
7f58689d DC |
431 | me.mon(me.metadatastore, 'load', function(store, records, success) { |
432 | if (!success || records.length < 1) { | |
433 | return; | |
434 | } | |
10b00f3a | 435 | me.metadata = records[0].data; |
7f58689d | 436 | |
4ad4262d | 437 | // update services |
10b00f3a | 438 | me.getComponent('services').updateAll(me.metadata, me.status || {}); |
4ad4262d | 439 | |
7f58689d | 440 | // update detailstatus panel |
10b00f3a | 441 | me.getComponent('statusdetail').updateAll(me.metadata, me.status || {}); |
7f58689d | 442 | |
949a6609 DC |
443 | let maxversion = []; |
444 | let maxversiontext = ""; | |
10b00f3a | 445 | for (const [_nodename, data] of Object.entries(me.metadata.node)) { |
949a6609 DC |
446 | let version = data.version.parts; |
447 | if (PVE.Utils.compare_ceph_versions(version, maxversion) > 0) { | |
448 | maxversion = version; | |
949a6609 DC |
449 | maxversiontext = data.version.str; |
450 | } | |
451 | } | |
0beb2578 | 452 | me.down('#versioninfo').setValue(maxversiontext); |
7f58689d DC |
453 | }, me); |
454 | ||
946730cd | 455 | me.on('destroy', me.store.stopUpdate); |
4ad4262d | 456 | me.on('destroy', me.metadatastore.stopUpdate); |
946730cd | 457 | me.store.startUpdate(); |
4ad4262d | 458 | me.metadatastore.startUpdate(); |
f6710aac | 459 | }, |
946730cd | 460 | |
bd39c945 | 461 | }); |