]>
Commit | Line | Data |
---|---|---|
bd39c945 | 1 | Ext.define('PVE.node.CephStatus', { |
946730cd DC |
2 | extend: 'Ext.panel.Panel', |
3 | alias: 'widget.pveNodeCephStatus', | |
4 | ||
ba93a9c6 | 5 | onlineHelp: 'chapter_pveceph', |
bd39c945 | 6 | |
946730cd DC |
7 | scrollable: true, |
8 | ||
9f7cbaf3 DC |
9 | bodyPadding: 5, |
10 | ||
11 | layout: { | |
12 | type: 'column' | |
13 | }, | |
946730cd DC |
14 | |
15 | defaults: { | |
9f7cbaf3 | 16 | padding: 5 |
946730cd DC |
17 | }, |
18 | ||
19 | items: [ | |
20 | { | |
21 | xtype: 'panel', | |
22 | title: gettext('Health'), | |
9f7cbaf3 DC |
23 | bodyPadding: 10, |
24 | plugins: 'responsive', | |
25 | responsiveConfig: { | |
26 | 'width < 1900': { | |
27b91275 | 27 | minHeight: 230, |
9f7cbaf3 DC |
28 | columnWidth: 1 |
29 | }, | |
30 | 'width >= 1900': { | |
27b91275 | 31 | minHeight: 500, |
9f7cbaf3 DC |
32 | columnWidth: 0.5 |
33 | } | |
34 | }, | |
946730cd DC |
35 | layout: { |
36 | type: 'hbox', | |
9f7cbaf3 | 37 | align: 'stretch' |
946730cd DC |
38 | }, |
39 | items: [ | |
40 | { | |
949a6609 DC |
41 | xtype: 'container', |
42 | layout: { | |
43 | type: 'vbox', | |
44 | align: 'stretch', | |
45 | }, | |
946730cd | 46 | flex: 1, |
949a6609 DC |
47 | items: [ |
48 | { | |
49 | flex: 1, | |
50 | itemId: 'overallhealth', | |
51 | xtype: 'pveHealthWidget', | |
52 | title: gettext('Status') | |
53 | }, | |
54 | { | |
55 | itemId: 'versioninfo', | |
0beb2578 TL |
56 | xtype: 'displayfield', |
57 | fieldLabel: gettext('Ceph Version'), | |
58 | value: "", | |
59 | autoEl: { | |
60 | tag: 'div', | |
61 | 'data-qtip': gettext('The newest version installed in the Cluster.'), | |
949a6609 DC |
62 | }, |
63 | padding: '10 0 0 0', | |
64 | style: { | |
65 | 'text-align': 'center', | |
66 | }, | |
949a6609 | 67 | } |
0beb2578 | 68 | ], |
946730cd DC |
69 | }, |
70 | { | |
71 | flex: 2, | |
72 | itemId: 'warnings', | |
73 | stateful: true, | |
74 | stateId: 'ceph-status-warnings', | |
946730cd | 75 | xtype: 'grid', |
23f14fd9 TL |
76 | // since we load the store manually to show the emptytext, |
77 | // we have to specify an empty one here | |
78 | store: { | |
ca0267fd | 79 | trackRemoved: false, |
23f14fd9 TL |
80 | data: [], |
81 | }, | |
946730cd DC |
82 | emptyText: gettext('No Warnings/Errors'), |
83 | columns: [ | |
84 | { | |
85 | dataIndex: 'severity', | |
86 | header: gettext('Severity'), | |
87 | align: 'center', | |
88 | width: 70, | |
89 | renderer: function(value) { | |
90 | var health = PVE.Utils.map_ceph_health[value]; | |
91 | var classes = PVE.Utils.get_health_icon(health); | |
92 | ||
93 | return '<i class="fa fa-fw ' + classes + '"></i>'; | |
94 | }, | |
95 | sorter: { | |
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); | |
99 | } | |
100 | } | |
101 | }, | |
102 | { | |
103 | dataIndex: 'summary', | |
104 | header: gettext('Summary'), | |
105 | flex: 1 | |
e932cd5f DC |
106 | }, |
107 | { | |
108 | xtype: 'actioncolumn', | |
109 | width: 40, | |
110 | align: 'center', | |
111 | tooltip: gettext('Detail'), | |
112 | items: [ | |
113 | { | |
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'), | |
118 | resizable: true, | |
bf6e58d2 | 119 | modal: true, |
e932cd5f DC |
120 | width: 650, |
121 | height: 400, | |
122 | layout: { | |
123 | type: 'fit' | |
124 | }, | |
125 | items: [{ | |
126 | scrollable: true, | |
9f7cbaf3 | 127 | padding: 10, |
e932cd5f | 128 | xtype: 'box', |
b01e7f7a DC |
129 | html: [ |
130 | '<span>' + Ext.htmlEncode(record.data.summary) + '</span>', | |
131 | '<pre>' + Ext.htmlEncode(record.data.detail) + '</pre>' | |
132 | ] | |
e932cd5f DC |
133 | }] |
134 | }); | |
135 | win.show(); | |
136 | } | |
137 | } | |
138 | ] | |
946730cd DC |
139 | } |
140 | ] | |
141 | } | |
142 | ] | |
143 | }, | |
144 | { | |
145 | xtype: 'pveCephStatusDetail', | |
146 | itemId: 'statusdetail', | |
9f7cbaf3 DC |
147 | plugins: 'responsive', |
148 | responsiveConfig: { | |
149 | 'width < 1900': { | |
27b91275 DC |
150 | columnWidth: 1, |
151 | minHeight: 250 | |
9f7cbaf3 DC |
152 | }, |
153 | 'width >= 1900': { | |
27b91275 DC |
154 | columnWidth: 0.5, |
155 | minHeight: 300 | |
9f7cbaf3 DC |
156 | } |
157 | }, | |
946730cd DC |
158 | title: gettext('Status') |
159 | }, | |
4ad4262d DC |
160 | { |
161 | title: gettext('Services'), | |
162 | xtype: 'pveCephServices', | |
163 | itemId: 'services', | |
164 | plugins: 'responsive', | |
165 | layout: { | |
166 | type: 'hbox', | |
167 | align: 'stretch' | |
168 | }, | |
169 | responsiveConfig: { | |
170 | 'width < 1900': { | |
171 | columnWidth: 1, | |
172 | minHeight: 200 | |
173 | }, | |
174 | 'width >= 1900': { | |
175 | columnWidth: 0.5, | |
176 | minHeight: 200 | |
177 | } | |
178 | } | |
179 | }, | |
946730cd DC |
180 | { |
181 | xtype: 'panel', | |
182 | title: gettext('Performance'), | |
9f7cbaf3 DC |
183 | columnWidth: 1, |
184 | bodyPadding: 5, | |
946730cd DC |
185 | layout: { |
186 | type: 'hbox', | |
187 | align: 'center' | |
188 | }, | |
189 | items: [ | |
190 | { | |
191 | flex: 1, | |
62281115 DC |
192 | xtype: 'container', |
193 | items: [ | |
194 | { | |
195 | xtype: 'proxmoxGauge', | |
196 | itemId: 'space', | |
197 | title: gettext('Usage') | |
198 | }, | |
199 | { | |
200 | flex: 1, | |
201 | border: false, | |
202 | }, | |
203 | { | |
204 | xtype: 'container', | |
205 | itemId: 'recovery', | |
206 | hidden: true, | |
207 | padding: 25, | |
208 | items: [ | |
209 | { | |
210 | itemId: 'recoverychart', | |
211 | xtype: 'pveRunningChart', | |
e767e1ec | 212 | title: gettext('Recovery') +'/ '+ gettext('Rebalance'), |
62281115 DC |
213 | renderer: PVE.Utils.render_bandwidth, |
214 | height: 100, | |
215 | }, | |
216 | { | |
217 | xtype: 'progressbar', | |
218 | itemId: 'recoveryprogress', | |
219 | }, | |
220 | ] | |
221 | }, | |
222 | ] | |
946730cd DC |
223 | }, |
224 | { | |
225 | flex: 2, | |
226 | xtype: 'container', | |
227 | defaults: { | |
9f7cbaf3 | 228 | padding: 0, |
946730cd DC |
229 | height: 100 |
230 | }, | |
231 | items: [ | |
232 | { | |
233 | itemId: 'reads', | |
234 | xtype: 'pveRunningChart', | |
235 | title: gettext('Reads'), | |
236 | renderer: PVE.Utils.render_bandwidth | |
237 | }, | |
238 | { | |
239 | itemId: 'writes', | |
240 | xtype: 'pveRunningChart', | |
241 | title: gettext('Writes'), | |
242 | renderer: PVE.Utils.render_bandwidth | |
243 | }, | |
946730cd DC |
244 | { |
245 | itemId: 'readiops', | |
246 | xtype: 'pveRunningChart', | |
8f8ec25d | 247 | title: 'IOPS: ' + gettext('Reads'), |
946730cd DC |
248 | renderer: Ext.util.Format.numberRenderer('0,000') |
249 | }, | |
250 | { | |
251 | itemId: 'writeiops', | |
252 | xtype: 'pveRunningChart', | |
8f8ec25d | 253 | title: 'IOPS: ' + gettext('Writes'), |
946730cd | 254 | renderer: Ext.util.Format.numberRenderer('0,000') |
df503ff9 | 255 | }, |
946730cd DC |
256 | ] |
257 | } | |
258 | ] | |
bd39c945 | 259 | } |
946730cd | 260 | ], |
bd39c945 | 261 | |
e932cd5f DC |
262 | generateCheckData: function(health) { |
263 | var result = []; | |
264 | var checks = health.checks || {}; | |
265 | var keys = Ext.Object.getKeys(checks).sort(); | |
266 | ||
267 | Ext.Array.forEach(keys, function(key) { | |
268 | var details = checks[key].detail || []; | |
269 | result.push({ | |
270 | id: key, | |
37f01f69 DC |
271 | summary: checks[key].summary.message, |
272 | detail: Ext.Array.reduce( | |
273 | checks[key].detail, | |
274 | function(first, second) { | |
275 | return first + '\n' + second.message; | |
276 | }, | |
277 | '' | |
278 | ), | |
e932cd5f DC |
279 | severity: checks[key].severity |
280 | }); | |
281 | }); | |
282 | ||
283 | return result; | |
284 | }, | |
285 | ||
946730cd DC |
286 | updateAll: function(store, records, success) { |
287 | if (!success || records.length === 0) { | |
288 | return; | |
289 | } | |
bd39c945 | 290 | |
946730cd DC |
291 | var me = this; |
292 | var rec = records[0]; | |
0bf3c581 | 293 | me.status = rec.data; |
bd39c945 | 294 | |
946730cd | 295 | // add health panel |
dfe6d184 | 296 | me.down('#overallhealth').updateHealth(PVE.Utils.render_ceph_health(rec.data.health || {})); |
946730cd | 297 | // add errors to gridstore |
e932cd5f | 298 | me.down('#warnings').getStore().loadRawData(me.generateCheckData(rec.data.health || {}), false); |
bd39c945 | 299 | |
4ad4262d DC |
300 | // update services |
301 | me.getComponent('services').updateAll(me.metadata || {}, rec.data); | |
302 | ||
946730cd | 303 | // update detailstatus panel |
0bf3c581 | 304 | me.getComponent('statusdetail').updateAll(me.metadata || {}, rec.data); |
bd39c945 | 305 | |
946730cd | 306 | // add performance data |
df503ff9 TL |
307 | let pgmap = rec.data.pgmap; |
308 | let used = pgmap.bytes_used; | |
309 | let total = pgmap.bytes_total; | |
bd39c945 | 310 | |
946730cd DC |
311 | var text = Ext.String.format(gettext('{0} of {1}'), |
312 | PVE.Utils.render_size(used), | |
313 | PVE.Utils.render_size(total) | |
314 | ); | |
bd39c945 | 315 | |
946730cd DC |
316 | // update the usage widget |
317 | me.down('#space').updateValue(used/total, text); | |
bd39c945 | 318 | |
df503ff9 TL |
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; | |
bd39c945 | 323 | |
946730cd DC |
324 | // update the graphs |
325 | me.reads.addDataPoint(reads); | |
326 | me.writes.addDataPoint(writes); | |
946730cd DC |
327 | me.readiops.addDataPoint(readiops); |
328 | me.writeiops.addDataPoint(writeiops); | |
62281115 DC |
329 | |
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; | |
334 | // update recovery | |
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); | |
343 | ||
344 | let percentage = recovered/total; | |
345 | let txt = `${(percentage*100).toFixed(2)}%`; | |
346 | if (speed > 0) { | |
347 | txt += ` (${speedTxt} - ${duration} left)`; | |
348 | } | |
349 | ||
350 | me.down('#recovery').setVisible(true); | |
351 | me.down('#recoveryprogress').updateValue(percentage); | |
352 | me.down('#recoveryprogress').updateText(txt); | |
353 | me.down('#recoverychart').addDataPoint(speed); | |
354 | } else { | |
355 | me.down('#recovery').setVisible(false); | |
356 | me.down('#recoverychart').addDataPoint(0); | |
357 | } | |
946730cd DC |
358 | }, |
359 | ||
946730cd DC |
360 | initComponent: function() { |
361 | var me = this; | |
bd39c945 | 362 | |
946730cd | 363 | var nodename = me.pveSelNode.data.node; |
bd39c945 | 364 | |
946730cd | 365 | me.callParent(); |
d2193664 | 366 | var baseurl = '/api2/json' + (nodename ? '/nodes/' + nodename : '/cluster') + '/ceph'; |
0c7c0d6b | 367 | me.store = Ext.create('Proxmox.data.UpdateStore', { |
2365c5c1 | 368 | storeid: 'ceph-status-' + (nodename || 'cluster'), |
946730cd DC |
369 | interval: 5000, |
370 | proxy: { | |
56a353b9 | 371 | type: 'proxmox', |
2365c5c1 | 372 | url: baseurl + '/status' |
bd39c945 DM |
373 | } |
374 | }); | |
375 | ||
7f58689d DC |
376 | me.metadatastore = Ext.create('Proxmox.data.UpdateStore', { |
377 | storeid: 'ceph-metadata-' + (nodename || 'cluster'), | |
378 | interval: 15*1000, | |
379 | proxy: { | |
380 | type: 'proxmox', | |
44dde2cb | 381 | url: '/api2/json/cluster/ceph/metadata' |
7f58689d DC |
382 | } |
383 | }); | |
384 | ||
946730cd DC |
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'); | |
391 | ||
4616a55b TM |
392 | var regex = new RegExp("not (installed|initialized)", "i"); |
393 | PVE.Utils.handleStoreErrorOrMask(me, me.store, regex, function(me, error){ | |
394 | me.store.stopUpdate(); | |
2365c5c1 | 395 | PVE.Utils.showCephInstallOrMask(me, error.statusText, (nodename || 'localhost'), |
4616a55b TM |
396 | function(win){ |
397 | me.mon(win, 'cephInstallWindowClosed', function(){ | |
398 | me.store.startUpdate(); | |
399 | }); | |
400 | } | |
401 | ); | |
402 | }); | |
403 | ||
946730cd | 404 | me.mon(me.store, 'load', me.updateAll, me); |
7f58689d DC |
405 | me.mon(me.metadatastore, 'load', function(store, records, success) { |
406 | if (!success || records.length < 1) { | |
407 | return; | |
408 | } | |
409 | var rec = records[0]; | |
410 | me.metadata = rec.data; | |
411 | ||
4ad4262d DC |
412 | // update services |
413 | me.getComponent('services').updateAll(rec.data, me.status || {}); | |
414 | ||
7f58689d DC |
415 | // update detailstatus panel |
416 | me.getComponent('statusdetail').updateAll(rec.data, me.status || {}); | |
417 | ||
949a6609 DC |
418 | let maxversion = []; |
419 | let maxversiontext = ""; | |
949a6609 DC |
420 | for (const [nodename, data] of Object.entries(me.metadata.node)) { |
421 | let version = data.version.parts; | |
422 | if (PVE.Utils.compare_ceph_versions(version, maxversion) > 0) { | |
423 | maxversion = version; | |
949a6609 DC |
424 | maxversiontext = data.version.str; |
425 | } | |
426 | } | |
0beb2578 | 427 | me.down('#versioninfo').setValue(maxversiontext); |
7f58689d DC |
428 | }, me); |
429 | ||
946730cd | 430 | me.on('destroy', me.store.stopUpdate); |
4ad4262d | 431 | me.on('destroy', me.metadatastore.stopUpdate); |
946730cd | 432 | me.store.startUpdate(); |
4ad4262d | 433 | me.metadatastore.startUpdate(); |
bd39c945 | 434 | } |
946730cd | 435 | |
bd39c945 | 436 | }); |