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