]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/ceph/Status.js
ui: ceph: factor out installation mask store monitoring
[pve-manager.git] / www / manager6 / ceph / Status.js
1 Ext.define('PVE.node.CephStatus', {
2 extend: 'Ext.panel.Panel',
3 alias: 'widget.pveNodeCephStatus',
4
5 onlineHelp: 'chapter_pveceph',
6
7 scrollable: true,
8
9 bodyPadding: 5,
10
11 layout: {
12 type: 'column',
13 },
14
15 defaults: {
16 padding: 5,
17 },
18
19 items: [
20 {
21 xtype: 'panel',
22 title: gettext('Health'),
23 bodyPadding: 10,
24 plugins: 'responsive',
25 responsiveConfig: {
26 'width < 1900': {
27 minHeight: 230,
28 columnWidth: 1,
29 },
30 'width >= 1900': {
31 minHeight: 500,
32 columnWidth: 0.5,
33 },
34 },
35 layout: {
36 type: 'hbox',
37 align: 'stretch',
38 },
39 items: [
40 {
41 xtype: 'container',
42 layout: {
43 type: 'vbox',
44 align: 'stretch',
45 },
46 flex: 1,
47 items: [
48 {
49 flex: 1,
50 itemId: 'overallhealth',
51 xtype: 'pveHealthWidget',
52 title: gettext('Status'),
53 },
54 {
55 itemId: 'versioninfo',
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.'),
62 },
63 padding: '10 0 0 0',
64 style: {
65 'text-align': 'center',
66 },
67 },
68 ],
69 },
70 {
71 flex: 2,
72 itemId: 'warnings',
73 stateful: true,
74 stateId: 'ceph-status-warnings',
75 xtype: 'grid',
76 // since we load the store manually to show the emptytext,
77 // we have to specify an empty one here
78 store: {
79 trackRemoved: false,
80 data: [],
81 },
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,
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,
119 modal: true,
120 width: 650,
121 height: 400,
122 layout: {
123 type: 'fit',
124 },
125 items: [{
126 scrollable: true,
127 padding: 10,
128 xtype: 'box',
129 html: [
130 '<span>' + Ext.htmlEncode(record.data.summary) + '</span>',
131 '<pre>' + Ext.htmlEncode(record.data.detail) + '</pre>',
132 ],
133 }],
134 });
135 win.show();
136 },
137 },
138 ],
139 },
140 ],
141 },
142 ],
143 },
144 {
145 xtype: 'pveCephStatusDetail',
146 itemId: 'statusdetail',
147 plugins: 'responsive',
148 responsiveConfig: {
149 'width < 1900': {
150 columnWidth: 1,
151 minHeight: 250,
152 },
153 'width >= 1900': {
154 columnWidth: 0.5,
155 minHeight: 300,
156 },
157 },
158 title: gettext('Status'),
159 },
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 },
180 {
181 xtype: 'panel',
182 title: gettext('Performance'),
183 columnWidth: 1,
184 bodyPadding: 5,
185 layout: {
186 type: 'hbox',
187 align: 'center',
188 },
189 items: [
190 {
191 flex: 1,
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',
212 title: gettext('Recovery') +'/ '+ gettext('Rebalance'),
213 renderer: PVE.Utils.render_bandwidth,
214 height: 100,
215 },
216 {
217 xtype: 'progressbar',
218 itemId: 'recoveryprogress',
219 },
220 ],
221 },
222 ],
223 },
224 {
225 flex: 2,
226 xtype: 'container',
227 defaults: {
228 padding: 0,
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 },
244 {
245 itemId: 'readiops',
246 xtype: 'pveRunningChart',
247 title: 'IOPS: ' + gettext('Reads'),
248 renderer: Ext.util.Format.numberRenderer('0,000'),
249 },
250 {
251 itemId: 'writeiops',
252 xtype: 'pveRunningChart',
253 title: 'IOPS: ' + gettext('Writes'),
254 renderer: Ext.util.Format.numberRenderer('0,000'),
255 },
256 ],
257 },
258 ],
259 },
260 ],
261
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,
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 ),
279 severity: checks[key].severity,
280 });
281 });
282
283 return result;
284 },
285
286 updateAll: function(store, records, success) {
287 if (!success || records.length === 0) {
288 return;
289 }
290
291 var me = this;
292 var rec = records[0];
293 me.status = rec.data;
294
295 // add health panel
296 me.down('#overallhealth').updateHealth(PVE.Utils.render_ceph_health(rec.data.health || {}));
297 // add errors to gridstore
298 me.down('#warnings').getStore().loadRawData(me.generateCheckData(rec.data.health || {}), false);
299
300 // update services
301 me.getComponent('services').updateAll(me.metadata || {}, rec.data);
302
303 // update detailstatus panel
304 me.getComponent('statusdetail').updateAll(me.metadata || {}, rec.data);
305
306 // add performance data
307 let pgmap = rec.data.pgmap;
308 let used = pgmap.bytes_used;
309 let total = pgmap.bytes_total;
310
311 var text = Ext.String.format(gettext('{0} of {1}'),
312 Proxmox.Utils.render_size(used),
313 Proxmox.Utils.render_size(total),
314 );
315
316 // update the usage widget
317 me.down('#space').updateValue(used/total, text);
318
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;
323
324 // update the graphs
325 me.reads.addDataPoint(reads);
326 me.writes.addDataPoint(writes);
327 me.readiops.addDataPoint(readiops);
328 me.writeiops.addDataPoint(writeiops);
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 }
358 },
359
360 initComponent: function() {
361 var me = this;
362
363 var nodename = me.pveSelNode.data.node;
364
365 me.callParent();
366 var baseurl = '/api2/json' + (nodename ? '/nodes/' + nodename : '/cluster') + '/ceph';
367 me.store = Ext.create('Proxmox.data.UpdateStore', {
368 storeid: 'ceph-status-' + (nodename || 'cluster'),
369 interval: 5000,
370 proxy: {
371 type: 'proxmox',
372 url: baseurl + '/status',
373 },
374 });
375
376 me.metadatastore = Ext.create('Proxmox.data.UpdateStore', {
377 storeid: 'ceph-metadata-' + (nodename || 'cluster'),
378 interval: 15*1000,
379 proxy: {
380 type: 'proxmox',
381 url: '/api2/json/cluster/ceph/metadata',
382 },
383 });
384
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
392 // manages the "install ceph?" overlay
393 PVE.Utils.monitor_ceph_installed(me, me.store, nodename);
394
395 me.mon(me.store, 'load', me.updateAll, me);
396 me.mon(me.metadatastore, 'load', function(store, records, success) {
397 if (!success || records.length < 1) {
398 return;
399 }
400 var rec = records[0];
401 me.metadata = rec.data;
402
403 // update services
404 me.getComponent('services').updateAll(rec.data, me.status || {});
405
406 // update detailstatus panel
407 me.getComponent('statusdetail').updateAll(rec.data, me.status || {});
408
409 let maxversion = [];
410 let maxversiontext = "";
411 for (const [nodename, data] of Object.entries(me.metadata.node)) {
412 let version = data.version.parts;
413 if (PVE.Utils.compare_ceph_versions(version, maxversion) > 0) {
414 maxversion = version;
415 maxversiontext = data.version.str;
416 }
417 }
418 me.down('#versioninfo').setValue(maxversiontext);
419 }, me);
420
421 me.on('destroy', me.store.stopUpdate);
422 me.on('destroy', me.metadatastore.stopUpdate);
423 me.store.startUpdate();
424 me.metadatastore.startUpdate();
425 },
426
427 });