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