]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/ceph/StatusDetail.js
update shipped appliance info index
[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 '#FFCC00',
98 '#FF6C59',
99 ],
100 store: { },
101 series: [
102 {
103 type: 'pie',
104 donut: 60,
105 angleField: 'count',
106 tooltip: {
107 trackMouse: true,
108 renderer: function(tooltip, record, ctx) {
109 var html = record.get('text');
110 html += '<br>';
111 record.get('states').forEach(function(state) {
112 html += '<br>' +
113 state.state_name + ': ' + state.count.toString();
114 });
115 tooltip.setHtml(html);
116 },
117 },
118 subStyle: {
119 strokeStyle: false,
120 },
121 },
122 ],
123 },
124 {
125 flex: 1.6,
126 itemId: 'pgs',
127 padding: '0 10',
128 maxHeight: 250,
129 scrollable: true,
130 data: {
131 states: [],
132 },
133 tpl: [
134 '<h3>PGs</h3>',
135 '<tpl for="states">',
136 '<div class="left-aligned"><i class ="fa fa-circle {cls}"></i> {state_name}:</div>',
137 '<div class="right-aligned">{count}</div><br />',
138 '<div style="clear:both"></div>',
139 '</tpl>',
140 ],
141 }],
142
143 // similar to mgr dashboard
144 pgstates: {
145 // clean
146 clean: 1,
147 active: 1,
148
149 // working
150 activating: 2,
151 backfill_wait: 2,
152 backfilling: 2,
153 creating: 2,
154 deep: 2,
155 degraded: 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 // error
169 backfill_toofull: 3,
170 backfill_unfound: 3,
171 down: 3,
172 incomplete: 3,
173 inconsistent: 3,
174 recovery_toofull: 3,
175 recovery_unfound: 3,
176 snaptrim_error: 3,
177 stale: 3,
178 undersized: 3,
179 },
180
181 statecategories: [
182 {
183 text: gettext('Unknown'),
184 count: 0,
185 states: [],
186 cls: 'faded',
187 },
188 {
189 text: gettext('Clean'),
190 cls: 'good',
191 },
192 {
193 text: gettext('Working'),
194 cls: 'warning',
195 },
196 {
197 text: gettext('Error'),
198 cls: 'critical',
199 },
200 ],
201
202 updateAll: function(metadata, status) {
203 let me = this;
204 me.suspendLayout = true;
205
206 let maxversion = "0";
207 Object.values(metadata.node || {}).forEach(function(node) {
208 if (PVE.Utils.compare_ceph_versions(node?.version?.parts, maxversion) > 0) {
209 maxversion = node.version.parts;
210 }
211 });
212
213 let oldOSD = [], ghostOSD = [];
214 metadata.osd?.forEach(osd => {
215 let version = PVE.Utils.parse_ceph_version(osd);
216 if (version !== undefined) {
217 if (PVE.Utils.compare_ceph_versions(version, maxversion) !== 0) {
218 oldOSD.push({
219 id: osd.id,
220 version: version,
221 });
222 }
223 } else {
224 if (Object.keys(osd).length > 1) {
225 console.warn('got OSD entry with no valid version but other keys', osd);
226 }
227 ghostOSD.push({
228 id: osd.id,
229 });
230 }
231 });
232
233 // update PGs sorted
234 let pgmap = status.pgmap || {};
235 let pgs_by_state = pgmap.pgs_by_state || [];
236 pgs_by_state.sort(function(a, b) {
237 return a.state_name < b.state_name?-1:a.state_name === b.state_name?0:1;
238 });
239
240 me.statecategories.forEach(function(cat) {
241 cat.count = 0;
242 cat.states = [];
243 });
244
245 pgs_by_state.forEach(function(state) {
246 let states = state.state_name.split(/[^a-z]+/);
247 let result = 0;
248 for (let i = 0; i < states.length; i++) {
249 if (me.pgstates[states[i]] > result) {
250 result = me.pgstates[states[i]];
251 }
252 }
253 // for the list
254 state.cls = me.statecategories[result].cls;
255
256 me.statecategories[result].count += state.count;
257 me.statecategories[result].states.push(state);
258 });
259
260 me.getComponent('pgchart').getStore().setData(me.statecategories);
261 me.getComponent('pgs').update({ states: pgs_by_state });
262
263 let health = status.health || {};
264 // we collect monitor/osd information from the checks
265 const downinregex = /(\d+) osds down/;
266 let downin_osds = 0;
267 Ext.Object.each(health.checks, function(key, value, obj) {
268 var found = null;
269 if (key === 'OSD_DOWN') {
270 found = value.summary.message.match(downinregex);
271 if (found !== null) {
272 downin_osds = parseInt(found[1], 10);
273 }
274 }
275 });
276
277 let osdmap = status.osdmap || {};
278 if (typeof osdmap.osdmap !== "undefined") {
279 osdmap = osdmap.osdmap;
280 }
281 // update OSDs counts
282 let total_osds = osdmap.num_osds || 0;
283 let in_osds = osdmap.num_in_osds || 0;
284 let up_osds = osdmap.num_up_osds || 0;
285 let down_osds = total_osds - up_osds;
286
287 let downout_osds = down_osds - downin_osds;
288 let upin_osds = in_osds - downin_osds;
289 let upout_osds = up_osds - upin_osds;
290
291 let osds = {
292 total: total_osds,
293 upin: upin_osds,
294 upout: upout_osds,
295 downin: downin_osds,
296 downout: downout_osds,
297 oldOSD: oldOSD,
298 ghostOSD,
299 };
300 let osdcomponent = me.getComponent('osds');
301 osdcomponent.update(Ext.apply(osdcomponent.data, osds));
302
303 me.suspendLayout = false;
304 me.updateLayout();
305 },
306 });
307