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