]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/ceph/StatusDetail.js
bump version to 8.2.7
[pve-manager.git] / www / manager6 / ceph / StatusDetail.js
1 Ext.define('PVE.ceph.StatusDetail', {
2 extend: 'Ext.panel.Panel',
3 alias: 'widget.pveCephStatusDetail',
4
5 layout: {
6 type: 'hbox',
7 align: 'stretch',
8 },
9
10 bodyPadding: '0 5',
11 defaults: {
12 xtype: 'box',
13 style: {
14 'text-align': 'center',
15 },
16 },
17
18 items: [{
19 flex: 1,
20 itemId: 'osds',
21 maxHeight: 250,
22 scrollable: true,
23 padding: '0 10 5 10',
24 data: {
25 total: 0,
26 upin: 0,
27 upout: 0,
28 downin: 0,
29 downout: 0,
30 oldOSD: [],
31 ghostOSD: [],
32 },
33 tpl: [
34 '<h3>OSDs</h3>',
35 '<table class="osds">',
36 '<tr><td></td>',
37 '<td><i class="fa fa-fw good fa-circle"></i>',
38 gettext('In'),
39 '</td>',
40 '<td><i class="fa fa-fw warning fa-circle-o"></i>',
41 gettext('Out'),
42 '</td>',
43 '</tr>',
44 '<tr>',
45 '<td><i class="fa fa-fw good fa-arrow-circle-up"></i>',
46 gettext('Up'),
47 '</td>',
48 '<td>{upin}</td>',
49 '<td>{upout}</td>',
50 '</tr>',
51 '<tr>',
52 '<td><i class="fa fa-fw critical fa-arrow-circle-down"></i>',
53 gettext('Down'),
54 '</td>',
55 '<td>{downin}</td>',
56 '<td>{downout}</td>',
57 '</tr>',
58 '</table>',
59 '<br /><div>',
60 gettext('Total'),
61 ': {total}',
62 '</div><br />',
63 '<tpl if="oldOSD.length &gt; 0">',
64 '<i class="fa fa-refresh warning"></i> ' + gettext('Outdated OSDs') + "<br>",
65 '<div class="osds">',
66 '<tpl for="oldOSD">',
67 '<div class="left-aligned">osd.{id}:</div>',
68 '<div class="right-aligned">{version}</div><br />',
69 '<div style="clear:both"></div>',
70 '</tpl>',
71 '</div>',
72 '</tpl>',
73 '</div>',
74 '<tpl if="ghostOSD.length &gt; 0">',
75 '<br />',
76 `<i class="fa fa-question-circle warning"></i> ${gettext('Ghost OSDs')}<br>`,
77 `<div data-qtip="${gettext('OSDs with no metadata, possibly left over from removal')}" class="osds">`,
78 '<tpl for="ghostOSD">',
79 '<div class="left-aligned">osd.{id}</div>',
80 '<div style="clear:both"></div>',
81 '</tpl>',
82 '</div>',
83 '</tpl>',
84 ],
85 },
86 {
87 flex: 1,
88 border: false,
89 itemId: 'pgchart',
90 xtype: 'polar',
91 height: 184,
92 innerPadding: 5,
93 insetPadding: 5,
94 colors: [
95 '#CFCFCF',
96 '#21BF4B',
97 '#3892d4',
98 '#FFCC00',
99 '#FF6C59',
100 ],
101 store: { },
102 series: [
103 {
104 type: 'pie',
105 donut: 60,
106 angleField: 'count',
107 tooltip: {
108 trackMouse: true,
109 renderer: function(tooltip, record, ctx) {
110 var html = record.get('text');
111 html += '<br>';
112 record.get('states').forEach(function(state) {
113 html += '<br>' +
114 state.state_name + ': ' + state.count.toString();
115 });
116 tooltip.setHtml(html);
117 },
118 },
119 subStyle: {
120 strokeStyle: false,
121 },
122 },
123 ],
124 },
125 {
126 flex: 1.6,
127 itemId: 'pgs',
128 padding: '0 10',
129 maxHeight: 250,
130 scrollable: true,
131 data: {
132 states: [],
133 },
134 tpl: [
135 '<h3>PGs</h3>',
136 '<tpl for="states">',
137 '<div class="left-aligned"><i class ="fa fa-circle {cls}"></i> {state_name}:</div>',
138 '<div class="right-aligned">{count}</div><br />',
139 '<div style="clear:both"></div>',
140 '</tpl>',
141 ],
142 }],
143
144 // similar to mgr dashboard
145 pgstates: {
146 // clean
147 clean: 1,
148 active: 1,
149
150 // busy
151 activating: 2,
152 backfill_wait: 2,
153 backfilling: 2,
154 creating: 2,
155 deep: 2,
156 forced_backfill: 2,
157 forced_recovery: 2,
158 peered: 2,
159 peering: 2,
160 recovering: 2,
161 recovery_wait: 2,
162 remapped: 2,
163 repair: 2,
164 scrubbing: 2,
165 snaptrim: 2,
166 snaptrim_wait: 2,
167
168 // warning
169 degraded: 3,
170 undersized: 3,
171
172 // critical
173 backfill_toofull: 4,
174 backfill_unfound: 4,
175 down: 4,
176 incomplete: 4,
177 inconsistent: 4,
178 recovery_toofull: 4,
179 recovery_unfound: 4,
180 snaptrim_error: 4,
181 stale: 4,
182 },
183
184 statecategories: [
185 {
186 text: gettext('Unknown'),
187 count: 0,
188 states: [],
189 cls: 'faded',
190 },
191 {
192 text: gettext('Clean'),
193 cls: 'good',
194 },
195 {
196 text: gettext('Busy'),
197 cls: 'pve-ceph-status-busy',
198 },
199 {
200 text: gettext('Warning'),
201 cls: 'warning',
202 },
203 {
204 text: gettext('Critical'),
205 cls: 'critical',
206 },
207 ],
208
209 checkThemeColors: function() {
210 let me = this;
211 let rootStyle = getComputedStyle(document.documentElement);
212
213 // get color
214 let background = rootStyle.getPropertyValue("--pwt-panel-background").trim() || "#ffffff";
215
216 // set the colors
217 me.chart.setBackground(background);
218 me.chart.redraw();
219 },
220
221 updateAll: function(metadata, status) {
222 let me = this;
223 me.suspendLayout = true;
224
225 let maxversion = "0";
226 Object.values(metadata.node || {}).forEach(function(node) {
227 if (PVE.Utils.compare_ceph_versions(node?.version?.parts, maxversion) > 0) {
228 maxversion = node.version.parts;
229 }
230 });
231
232 let oldOSD = [], ghostOSD = [];
233 metadata.osd?.forEach(osd => {
234 let version = PVE.Utils.parse_ceph_version(osd);
235 if (version !== undefined) {
236 if (PVE.Utils.compare_ceph_versions(version, maxversion) !== 0) {
237 oldOSD.push({
238 id: osd.id,
239 version: version,
240 });
241 }
242 } else {
243 if (Object.keys(osd).length > 1) {
244 console.warn('got OSD entry with no valid version but other keys', osd);
245 }
246 ghostOSD.push({
247 id: osd.id,
248 });
249 }
250 });
251
252 // update PGs sorted
253 let pgmap = status.pgmap || {};
254 let pgs_by_state = pgmap.pgs_by_state || [];
255 pgs_by_state.sort(function(a, b) {
256 return a.state_name < b.state_name?-1:a.state_name === b.state_name?0:1;
257 });
258
259 me.statecategories.forEach(function(cat) {
260 cat.count = 0;
261 cat.states = [];
262 });
263
264 pgs_by_state.forEach(function(state) {
265 let states = state.state_name.split(/[^a-z]+/);
266 let result = 0;
267 for (let i = 0; i < states.length; i++) {
268 if (me.pgstates[states[i]] > result) {
269 result = me.pgstates[states[i]];
270 }
271 }
272 // for the list
273 state.cls = me.statecategories[result].cls;
274
275 me.statecategories[result].count += state.count;
276 me.statecategories[result].states.push(state);
277 });
278
279 me.chart.getStore().setData(me.statecategories);
280 me.getComponent('pgs').update({ states: pgs_by_state });
281
282 let health = status.health || {};
283 // we collect monitor/osd information from the checks
284 const downinregex = /(\d+) osds down/;
285 let downin_osds = 0;
286 Ext.Object.each(health.checks, function(key, value, obj) {
287 var found = null;
288 if (key === 'OSD_DOWN') {
289 found = value.summary.message.match(downinregex);
290 if (found !== null) {
291 downin_osds = parseInt(found[1], 10);
292 }
293 }
294 });
295
296 let osdmap = status.osdmap || {};
297 if (typeof osdmap.osdmap !== "undefined") {
298 osdmap = osdmap.osdmap;
299 }
300 // update OSDs counts
301 let total_osds = osdmap.num_osds || 0;
302 let in_osds = osdmap.num_in_osds || 0;
303 let up_osds = osdmap.num_up_osds || 0;
304 let down_osds = total_osds - up_osds;
305
306 let downout_osds = down_osds - downin_osds;
307 let upin_osds = in_osds - downin_osds;
308 let upout_osds = up_osds - upin_osds;
309
310 let osds = {
311 total: total_osds,
312 upin: upin_osds,
313 upout: upout_osds,
314 downin: downin_osds,
315 downout: downout_osds,
316 oldOSD: oldOSD,
317 ghostOSD,
318 };
319 let osdcomponent = me.getComponent('osds');
320 osdcomponent.update(Ext.apply(osdcomponent.data, osds));
321
322 me.suspendLayout = false;
323 me.updateLayout();
324 },
325
326 initComponent: function() {
327 var me = this;
328 me.callParent();
329
330 me.chart = me.getComponent('pgchart');
331 me.checkThemeColors();
332
333 // switch colors on media query changes
334 me.mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
335 me.themeListener = (e) => { me.checkThemeColors(); };
336 me.mediaQueryList.addEventListener("change", me.themeListener);
337 },
338
339 doDestroy: function() {
340 let me = this;
341
342 me.mediaQueryList.removeEventListener("change", me.themeListener);
343
344 me.callParent();
345 },
346 });
347