]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/ceph/Status.js
ui: add ceph recovery graph and progressbar
[pve-manager.git] / www / manager6 / ceph / Status.js
CommitLineData
bd39c945 1Ext.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: {
79 data: [],
80 },
946730cd
DC
81 emptyText: gettext('No Warnings/Errors'),
82 columns: [
83 {
84 dataIndex: 'severity',
85 header: gettext('Severity'),
86 align: 'center',
87 width: 70,
88 renderer: function(value) {
89 var health = PVE.Utils.map_ceph_health[value];
90 var classes = PVE.Utils.get_health_icon(health);
91
92 return '<i class="fa fa-fw ' + classes + '"></i>';
93 },
94 sorter: {
95 sorterFn: function(a,b) {
96 var healthArr = ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK'];
97 return healthArr.indexOf(b.data.severity) - healthArr.indexOf(a.data.severity);
98 }
99 }
100 },
101 {
102 dataIndex: 'summary',
103 header: gettext('Summary'),
104 flex: 1
e932cd5f
DC
105 },
106 {
107 xtype: 'actioncolumn',
108 width: 40,
109 align: 'center',
110 tooltip: gettext('Detail'),
111 items: [
112 {
113 iconCls: 'x-fa fa-info-circle',
114 handler: function(grid, rowindex, colindex, item, e, record) {
115 var win = Ext.create('Ext.window.Window', {
116 title: gettext('Detail'),
117 resizable: true,
bf6e58d2 118 modal: true,
e932cd5f
DC
119 width: 650,
120 height: 400,
121 layout: {
122 type: 'fit'
123 },
124 items: [{
125 scrollable: true,
9f7cbaf3 126 padding: 10,
e932cd5f 127 xtype: 'box',
b01e7f7a
DC
128 html: [
129 '<span>' + Ext.htmlEncode(record.data.summary) + '</span>',
130 '<pre>' + Ext.htmlEncode(record.data.detail) + '</pre>'
131 ]
e932cd5f
DC
132 }]
133 });
134 win.show();
135 }
136 }
137 ]
946730cd
DC
138 }
139 ]
140 }
141 ]
142 },
143 {
144 xtype: 'pveCephStatusDetail',
145 itemId: 'statusdetail',
9f7cbaf3
DC
146 plugins: 'responsive',
147 responsiveConfig: {
148 'width < 1900': {
27b91275
DC
149 columnWidth: 1,
150 minHeight: 250
9f7cbaf3
DC
151 },
152 'width >= 1900': {
27b91275
DC
153 columnWidth: 0.5,
154 minHeight: 300
9f7cbaf3
DC
155 }
156 },
946730cd
DC
157 title: gettext('Status')
158 },
4ad4262d
DC
159 {
160 title: gettext('Services'),
161 xtype: 'pveCephServices',
162 itemId: 'services',
163 plugins: 'responsive',
164 layout: {
165 type: 'hbox',
166 align: 'stretch'
167 },
168 responsiveConfig: {
169 'width < 1900': {
170 columnWidth: 1,
171 minHeight: 200
172 },
173 'width >= 1900': {
174 columnWidth: 0.5,
175 minHeight: 200
176 }
177 }
178 },
946730cd
DC
179 {
180 xtype: 'panel',
181 title: gettext('Performance'),
9f7cbaf3
DC
182 columnWidth: 1,
183 bodyPadding: 5,
946730cd
DC
184 layout: {
185 type: 'hbox',
186 align: 'center'
187 },
188 items: [
189 {
190 flex: 1,
62281115
DC
191 xtype: 'container',
192 items: [
193 {
194 xtype: 'proxmoxGauge',
195 itemId: 'space',
196 title: gettext('Usage')
197 },
198 {
199 flex: 1,
200 border: false,
201 },
202 {
203 xtype: 'container',
204 itemId: 'recovery',
205 hidden: true,
206 padding: 25,
207 items: [
208 {
209 itemId: 'recoverychart',
210 xtype: 'pveRunningChart',
211 title: gettext('Recovery'),
212 renderer: PVE.Utils.render_bandwidth,
213 height: 100,
214 },
215 {
216 xtype: 'progressbar',
217 itemId: 'recoveryprogress',
218 },
219 ]
220 },
221 ]
946730cd
DC
222 },
223 {
224 flex: 2,
225 xtype: 'container',
226 defaults: {
9f7cbaf3 227 padding: 0,
946730cd
DC
228 height: 100
229 },
230 items: [
231 {
232 itemId: 'reads',
233 xtype: 'pveRunningChart',
234 title: gettext('Reads'),
235 renderer: PVE.Utils.render_bandwidth
236 },
237 {
238 itemId: 'writes',
239 xtype: 'pveRunningChart',
240 title: gettext('Writes'),
241 renderer: PVE.Utils.render_bandwidth
242 },
946730cd
DC
243 {
244 itemId: 'readiops',
245 xtype: 'pveRunningChart',
8f8ec25d 246 title: 'IOPS: ' + gettext('Reads'),
946730cd
DC
247 renderer: Ext.util.Format.numberRenderer('0,000')
248 },
249 {
250 itemId: 'writeiops',
251 xtype: 'pveRunningChart',
8f8ec25d 252 title: 'IOPS: ' + gettext('Writes'),
946730cd 253 renderer: Ext.util.Format.numberRenderer('0,000')
df503ff9 254 },
946730cd
DC
255 ]
256 }
257 ]
bd39c945 258 }
946730cd 259 ],
bd39c945 260
e932cd5f
DC
261 generateCheckData: function(health) {
262 var result = [];
263 var checks = health.checks || {};
264 var keys = Ext.Object.getKeys(checks).sort();
265
266 Ext.Array.forEach(keys, function(key) {
267 var details = checks[key].detail || [];
268 result.push({
269 id: key,
37f01f69
DC
270 summary: checks[key].summary.message,
271 detail: Ext.Array.reduce(
272 checks[key].detail,
273 function(first, second) {
274 return first + '\n' + second.message;
275 },
276 ''
277 ),
e932cd5f
DC
278 severity: checks[key].severity
279 });
280 });
281
282 return result;
283 },
284
946730cd
DC
285 updateAll: function(store, records, success) {
286 if (!success || records.length === 0) {
287 return;
288 }
bd39c945 289
946730cd
DC
290 var me = this;
291 var rec = records[0];
0bf3c581 292 me.status = rec.data;
bd39c945 293
946730cd 294 // add health panel
dfe6d184 295 me.down('#overallhealth').updateHealth(PVE.Utils.render_ceph_health(rec.data.health || {}));
946730cd 296 // add errors to gridstore
e932cd5f 297 me.down('#warnings').getStore().loadRawData(me.generateCheckData(rec.data.health || {}), false);
bd39c945 298
4ad4262d
DC
299 // update services
300 me.getComponent('services').updateAll(me.metadata || {}, rec.data);
301
946730cd 302 // update detailstatus panel
0bf3c581 303 me.getComponent('statusdetail').updateAll(me.metadata || {}, rec.data);
bd39c945 304
946730cd 305 // add performance data
df503ff9
TL
306 let pgmap = rec.data.pgmap;
307 let used = pgmap.bytes_used;
308 let total = pgmap.bytes_total;
bd39c945 309
946730cd
DC
310 var text = Ext.String.format(gettext('{0} of {1}'),
311 PVE.Utils.render_size(used),
312 PVE.Utils.render_size(total)
313 );
bd39c945 314
946730cd
DC
315 // update the usage widget
316 me.down('#space').updateValue(used/total, text);
bd39c945 317
df503ff9
TL
318 let readiops = pgmap.read_op_per_sec;
319 let writeiops = pgmap.write_op_per_sec;
320 let reads = pgmap.read_bytes_sec || 0;
321 let writes = pgmap.write_bytes_sec || 0;
bd39c945 322
946730cd
DC
323 // update the graphs
324 me.reads.addDataPoint(reads);
325 me.writes.addDataPoint(writes);
946730cd
DC
326 me.readiops.addDataPoint(readiops);
327 me.writeiops.addDataPoint(writeiops);
62281115
DC
328
329 let degraded = pgmap.degraded_objects || 0;
330 let misplaced = pgmap.misplaced_objects || 0;
331 let unfound = pgmap.unfound_objects || 0;
332 let unhealthy = degraded + unfound + misplaced;
333 // update recovery
334 if (pgmap.recovering_objects_per_sec !== undefined || unhealthy > 0) {
335 let total = pgmap.misplaced_total || pgmap.unfound_total || pgmap.degraded_total || 0;
336 if (total === 0) return;
337 let recovered = (total - unhealthy) || 0;
338 let speed = pgmap.recovering_bytes_per_sec || 0;
339 let speedTxt = PVE.Utils.render_bandwidth(speed);
340 let obj_per_sec = speed / (4*1024*1024); // 4MiB per Object
341 let duration = Proxmox.Utils.format_duration_human(unhealthy/obj_per_sec);
342
343 let percentage = recovered/total;
344 let txt = `${(percentage*100).toFixed(2)}%`;
345 if (speed > 0) {
346 txt += ` (${speedTxt} - ${duration} left)`;
347 }
348
349 me.down('#recovery').setVisible(true);
350 me.down('#recoveryprogress').updateValue(percentage);
351 me.down('#recoveryprogress').updateText(txt);
352 me.down('#recoverychart').addDataPoint(speed);
353 } else {
354 me.down('#recovery').setVisible(false);
355 me.down('#recoverychart').addDataPoint(0);
356 }
946730cd
DC
357 },
358
946730cd
DC
359 initComponent: function() {
360 var me = this;
bd39c945 361
946730cd 362 var nodename = me.pveSelNode.data.node;
bd39c945 363
946730cd 364 me.callParent();
d2193664 365 var baseurl = '/api2/json' + (nodename ? '/nodes/' + nodename : '/cluster') + '/ceph';
0c7c0d6b 366 me.store = Ext.create('Proxmox.data.UpdateStore', {
2365c5c1 367 storeid: 'ceph-status-' + (nodename || 'cluster'),
946730cd
DC
368 interval: 5000,
369 proxy: {
56a353b9 370 type: 'proxmox',
2365c5c1 371 url: baseurl + '/status'
bd39c945
DM
372 }
373 });
374
7f58689d
DC
375 me.metadatastore = Ext.create('Proxmox.data.UpdateStore', {
376 storeid: 'ceph-metadata-' + (nodename || 'cluster'),
377 interval: 15*1000,
378 proxy: {
379 type: 'proxmox',
44dde2cb 380 url: '/api2/json/cluster/ceph/metadata'
7f58689d
DC
381 }
382 });
383
946730cd
DC
384 // save references for the updatefunction
385 me.iops = me.down('#iops');
386 me.readiops = me.down('#readiops');
387 me.writeiops = me.down('#writeiops');
388 me.reads = me.down('#reads');
389 me.writes = me.down('#writes');
390
4616a55b
TM
391 var regex = new RegExp("not (installed|initialized)", "i");
392 PVE.Utils.handleStoreErrorOrMask(me, me.store, regex, function(me, error){
393 me.store.stopUpdate();
2365c5c1 394 PVE.Utils.showCephInstallOrMask(me, error.statusText, (nodename || 'localhost'),
4616a55b
TM
395 function(win){
396 me.mon(win, 'cephInstallWindowClosed', function(){
397 me.store.startUpdate();
398 });
399 }
400 );
401 });
402
946730cd 403 me.mon(me.store, 'load', me.updateAll, me);
7f58689d
DC
404 me.mon(me.metadatastore, 'load', function(store, records, success) {
405 if (!success || records.length < 1) {
406 return;
407 }
408 var rec = records[0];
409 me.metadata = rec.data;
410
4ad4262d
DC
411 // update services
412 me.getComponent('services').updateAll(rec.data, me.status || {});
413
7f58689d
DC
414 // update detailstatus panel
415 me.getComponent('statusdetail').updateAll(rec.data, me.status || {});
416
949a6609
DC
417 let maxversion = [];
418 let maxversiontext = "";
949a6609
DC
419 for (const [nodename, data] of Object.entries(me.metadata.node)) {
420 let version = data.version.parts;
421 if (PVE.Utils.compare_ceph_versions(version, maxversion) > 0) {
422 maxversion = version;
949a6609
DC
423 maxversiontext = data.version.str;
424 }
425 }
0beb2578 426 me.down('#versioninfo').setValue(maxversiontext);
7f58689d
DC
427 }, me);
428
946730cd 429 me.on('destroy', me.store.stopUpdate);
4ad4262d 430 me.on('destroy', me.metadatastore.stopUpdate);
946730cd 431 me.store.startUpdate();
4ad4262d 432 me.metadatastore.startUpdate();
bd39c945 433 }
946730cd 434
bd39c945 435});