]>
Commit | Line | Data |
---|---|---|
95d0de89 DC |
1 | Ext.define('PVE.ceph.Services', { |
2 | extend: 'Ext.panel.Panel', | |
3 | alias: 'widget.pveCephServices', | |
4 | ||
5 | layout: { | |
6 | type: 'hbox', | |
f6710aac | 7 | align: 'stretch', |
95d0de89 DC |
8 | }, |
9 | ||
10 | bodyPadding: '0 5 20', | |
11 | defaults: { | |
12 | xtype: 'box', | |
13 | style: { | |
8058410f | 14 | 'text-align': 'center', |
f6710aac | 15 | }, |
95d0de89 DC |
16 | }, |
17 | ||
18 | items: [ | |
19 | { | |
20 | flex: 1, | |
21 | xtype: 'pveCephServiceList', | |
22 | itemId: 'mons', | |
f6710aac | 23 | title: gettext('Monitors'), |
95d0de89 DC |
24 | }, |
25 | { | |
26 | flex: 1, | |
27 | xtype: 'pveCephServiceList', | |
28 | itemId: 'mgrs', | |
f6710aac | 29 | title: gettext('Managers'), |
95d0de89 DC |
30 | }, |
31 | { | |
32 | flex: 1, | |
33 | xtype: 'pveCephServiceList', | |
34 | itemId: 'mdss', | |
f6710aac TL |
35 | title: gettext('Meta Data Servers'), |
36 | }, | |
95d0de89 DC |
37 | ], |
38 | ||
39 | updateAll: function(metadata, status) { | |
40 | var me = this; | |
41 | ||
f87b8d2f | 42 | const healthstates = { |
95d0de89 DC |
43 | 'HEALTH_UNKNOWN': 0, |
44 | 'HEALTH_ERR': 1, | |
45 | 'HEALTH_WARN': 2, | |
23c83a3a DC |
46 | 'HEALTH_UPGRADE': 3, |
47 | 'HEALTH_OLD': 4, | |
f6710aac | 48 | 'HEALTH_OK': 5, |
95d0de89 | 49 | }; |
f87b8d2f TL |
50 | // order guarantee since es2020, but browsers did so before. Note, integers would break it. |
51 | const healthmap = Object.keys(healthstates); | |
52 | let maxversion = "00.0.00"; | |
d6fb92d7 DC |
53 | Object.values(metadata.node || {}).forEach(function(node) { |
54 | if (PVE.Utils.compare_ceph_versions(node?.version?.parts, maxversion) > 0) { | |
55 | maxversion = node?.version?.parts; | |
95d0de89 DC |
56 | } |
57 | }); | |
53e3ea84 | 58 | var quorummap = status && status.quorum_names ? status.quorum_names : []; |
f87b8d2f | 59 | let monmessages = {}, mgrmessages = {}, mdsmessages = {}; |
95d0de89 DC |
60 | if (status) { |
61 | if (status.health) { | |
f87b8d2f | 62 | Ext.Object.each(status.health.checks, function(key, value, _obj) { |
95d0de89 DC |
63 | if (!Ext.String.startsWith(key, "MON_")) { |
64 | return; | |
65 | } | |
f87b8d2f TL |
66 | for (let i = 0; i < value.detail.length; i++) { |
67 | let match = value.detail[i].message.match(/mon.([a-zA-Z0-9\-.]+)/); | |
9e546001 DC |
68 | if (!match) { |
69 | continue; | |
70 | } | |
f87b8d2f | 71 | let monid = match[1]; |
9e546001 DC |
72 | if (!monmessages[monid]) { |
73 | monmessages[monid] = { | |
74 | worstSeverity: healthstates.HEALTH_OK, | |
f6710aac | 75 | messages: [], |
9e546001 DC |
76 | }; |
77 | } | |
95d0de89 | 78 | |
f87b8d2f TL |
79 | let severityIcon = PVE.Utils.get_ceph_icon_html(value.severity, true); |
80 | let details = value.detail.reduce((acc, v) => `${acc}\n${v.message}`, ''); | |
81 | monmessages[monid].messages.push(severityIcon + details); | |
95d0de89 | 82 | |
9e546001 DC |
83 | if (healthstates[value.severity] < monmessages[monid].worstSeverity) { |
84 | monmessages[monid].worstSeverity = healthstates[value.severity]; | |
85 | } | |
95d0de89 DC |
86 | } |
87 | }); | |
88 | } | |
89 | ||
90 | if (status.mgrmap) { | |
91 | mgrmessages[status.mgrmap.active_name] = "active"; | |
92 | status.mgrmap.standbys.forEach(function(mgr) { | |
93 | mgrmessages[mgr.name] = "standby"; | |
94 | }); | |
95 | } | |
96 | ||
97 | if (status.fsmap) { | |
98 | status.fsmap.by_rank.forEach(function(mds) { | |
99 | mdsmessages[mds.name] = 'rank: ' + mds.rank + "; " + mds.status; | |
100 | }); | |
101 | } | |
102 | } | |
103 | ||
f87b8d2f | 104 | let checks = { |
95d0de89 DC |
105 | mon: function(mon) { |
106 | if (quorummap.indexOf(mon.name) !== -1) { | |
107 | mon.health = healthstates.HEALTH_OK; | |
108 | } else { | |
109 | mon.health = healthstates.HEALTH_ERR; | |
110 | } | |
111 | if (monmessages[mon.name]) { | |
112 | if (monmessages[mon.name].worstSeverity < mon.health) { | |
113 | mon.health = monmessages[mon.name].worstSeverity; | |
114 | } | |
115 | Array.prototype.push.apply(mon.messages, monmessages[mon.name].messages); | |
116 | } | |
117 | return mon; | |
118 | }, | |
119 | mgr: function(mgr) { | |
120 | if (mgrmessages[mgr.name] === 'active') { | |
121 | mgr.title = '<b>' + mgr.title + '</b>'; | |
122 | mgr.statuses.push(gettext('Status') + ': <b>active</b>'); | |
123 | } else if (mgrmessages[mgr.name] === 'standby') { | |
124 | mgr.statuses.push(gettext('Status') + ': standby'); | |
125 | } else if (mgr.health > healthstates.HEALTH_WARN) { | |
126 | mgr.health = healthstates.HEALTH_WARN; | |
127 | } | |
128 | ||
129 | return mgr; | |
130 | }, | |
131 | mds: function(mds) { | |
132 | if (mdsmessages[mds.name]) { | |
133 | mds.title = '<b>' + mds.title + '</b>'; | |
134 | mds.statuses.push(gettext('Status') + ': <b>' + mdsmessages[mds.name]+"</b>"); | |
135 | } else if (mds.addr !== Proxmox.Utils.unknownText) { | |
136 | mds.statuses.push(gettext('Status') + ': standby'); | |
137 | } | |
138 | ||
139 | return mds; | |
f6710aac | 140 | }, |
95d0de89 DC |
141 | }; |
142 | ||
df598da5 | 143 | for (let type of ['mon', 'mgr', 'mds']) { |
95d0de89 DC |
144 | var ids = Object.keys(metadata[type] || {}); |
145 | me[type] = {}; | |
146 | ||
df598da5 | 147 | for (let id of ids) { |
f87b8d2f TL |
148 | const [name, host] = id.split('@'); |
149 | let result = { | |
95d0de89 DC |
150 | id: id, |
151 | health: healthstates.HEALTH_OK, | |
152 | statuses: [], | |
153 | messages: [], | |
154 | name: name, | |
155 | title: metadata[type][id].name || name, | |
156 | host: host, | |
157 | version: PVE.Utils.parse_ceph_version(metadata[type][id]), | |
158 | service: metadata[type][id].service, | |
f6710aac | 159 | addr: metadata[type][id].addr || metadata[type][id].addrs || Proxmox.Utils.unknownText, |
95d0de89 DC |
160 | }; |
161 | ||
162 | result.statuses = [ | |
f87b8d2f | 163 | gettext('Host') + ": " + host, |
f6710aac | 164 | gettext('Address') + ": " + result.addr, |
95d0de89 DC |
165 | ]; |
166 | ||
167 | if (checks[type]) { | |
168 | result = checks[type](result); | |
169 | } | |
170 | ||
171 | if (result.service && !result.version) { | |
172 | result.messages.push( | |
173 | PVE.Utils.get_ceph_icon_html('HEALTH_UNKNOWN', true) + | |
f6710aac | 174 | gettext('Stopped'), |
95d0de89 DC |
175 | ); |
176 | result.health = healthstates.HEALTH_UNKNOWN; | |
177 | } | |
178 | ||
179 | if (!result.version && result.addr === Proxmox.Utils.unknownText) { | |
180 | result.health = healthstates.HEALTH_UNKNOWN; | |
181 | } | |
182 | ||
183 | if (result.version) { | |
184 | result.statuses.push(gettext('Version') + ": " + result.version); | |
185 | ||
d6fb92d7 | 186 | if (PVE.Utils.compare_ceph_versions(result.version, maxversion) !== 0) { |
c6801352 | 187 | let host_version = metadata.node[host]?.version?.parts || metadata.version?.[host] || ""; |
30a6ce73 | 188 | if (PVE.Utils.compare_ceph_versions(host_version, maxversion) === 0) { |
0575611e DC |
189 | if (result.health > healthstates.HEALTH_OLD) { |
190 | result.health = healthstates.HEALTH_OLD; | |
191 | } | |
192 | result.messages.push( | |
193 | PVE.Utils.get_ceph_icon_html('HEALTH_OLD', true) + | |
f6710aac | 194 | gettext('A newer version was installed but old version still running, please restart'), |
0575611e DC |
195 | ); |
196 | } else { | |
197 | if (result.health > healthstates.HEALTH_UPGRADE) { | |
198 | result.health = healthstates.HEALTH_UPGRADE; | |
199 | } | |
200 | result.messages.push( | |
201 | PVE.Utils.get_ceph_icon_html('HEALTH_UPGRADE', true) + | |
f6710aac | 202 | gettext('Other cluster members use a newer version of this service, please upgrade and restart'), |
0575611e | 203 | ); |
95d0de89 | 204 | } |
95d0de89 DC |
205 | } |
206 | } | |
207 | ||
208 | result.statuses.push(''); // empty line | |
209 | result.text = result.statuses.concat(result.messages).join('<br>'); | |
210 | ||
211 | result.health = healthmap[result.health]; | |
212 | ||
213 | me[type][id] = result; | |
214 | } | |
215 | } | |
216 | ||
217 | me.getComponent('mons').updateAll(Object.values(me.mon)); | |
218 | me.getComponent('mgrs').updateAll(Object.values(me.mgr)); | |
219 | me.getComponent('mdss').updateAll(Object.values(me.mds)); | |
f6710aac | 220 | }, |
95d0de89 DC |
221 | }); |
222 | ||
223 | Ext.define('PVE.ceph.ServiceList', { | |
224 | extend: 'Ext.container.Container', | |
225 | xtype: 'pveCephServiceList', | |
226 | ||
227 | style: { | |
8058410f | 228 | 'text-align': 'center', |
95d0de89 DC |
229 | }, |
230 | defaults: { | |
231 | xtype: 'box', | |
232 | style: { | |
8058410f | 233 | 'text-align': 'center', |
f6710aac | 234 | }, |
95d0de89 DC |
235 | }, |
236 | ||
237 | items: [ | |
238 | { | |
239 | itemId: 'title', | |
240 | data: { | |
f6710aac | 241 | title: '', |
95d0de89 | 242 | }, |
f6710aac TL |
243 | tpl: '<h3>{title}</h3>', |
244 | }, | |
95d0de89 DC |
245 | ], |
246 | ||
247 | updateAll: function(list) { | |
248 | var me = this; | |
249 | me.suspendLayout = true; | |
250 | ||
df598da5 | 251 | list.sort((a, b) => a.id > b.id ? 1 : a.id < b.id ? -1 : 0); |
f87b8d2f TL |
252 | if (!me.ids) { |
253 | me.ids = []; | |
95d0de89 | 254 | } |
f87b8d2f TL |
255 | let pendingRemoval = {}; |
256 | me.ids.forEach(id => { pendingRemoval[id] = true; }); // mark all as to-remove first here | |
257 | ||
258 | for (let i = 0; i < list.length; i++) { | |
259 | let service = me.getComponent(list[i].id); | |
95d0de89 | 260 | if (!service) { |
f87b8d2f TL |
261 | // services and list are sorted, so just insert at i + 1 (first el. is the title) |
262 | service = me.insert(i + 1, { | |
95d0de89 | 263 | xtype: 'pveCephServiceWidget', |
f6710aac | 264 | itemId: list[i].id, |
95d0de89 | 265 | }); |
95d0de89 DC |
266 | me.ids.push(list[i].id); |
267 | } else { | |
f87b8d2f | 268 | delete pendingRemoval[list[i].id]; // drop exisiting from for-removal |
95d0de89 DC |
269 | } |
270 | service.updateService(list[i].title, list[i].text, list[i].health); | |
271 | } | |
f87b8d2f | 272 | Object.keys(pendingRemoval).forEach(id => me.remove(id)); // GC |
95d0de89 | 273 | |
95d0de89 DC |
274 | me.suspendLayout = false; |
275 | me.updateLayout(); | |
276 | }, | |
277 | ||
278 | initComponent: function() { | |
279 | var me = this; | |
280 | me.callParent(); | |
281 | me.getComponent('title').update({ | |
f6710aac | 282 | title: me.title, |
95d0de89 | 283 | }); |
f6710aac | 284 | }, |
95d0de89 DC |
285 | }); |
286 | ||
95d0de89 DC |
287 | Ext.define('PVE.ceph.ServiceWidget', { |
288 | extend: 'Ext.Component', | |
289 | alias: 'widget.pveCephServiceWidget', | |
290 | ||
291 | userCls: 'monitor inline-block', | |
292 | data: { | |
293 | title: '0', | |
294 | health: 'HEALTH_ERR', | |
295 | text: '', | |
f6710aac | 296 | iconCls: PVE.Utils.get_health_icon(), |
95d0de89 DC |
297 | }, |
298 | ||
299 | tpl: [ | |
300 | '{title}: ', | |
f6710aac | 301 | '<i class="fa fa-fw {iconCls}"></i>', |
95d0de89 DC |
302 | ], |
303 | ||
304 | updateService: function(title, text, health) { | |
305 | var me = this; | |
306 | ||
307 | me.update(Ext.apply(me.data, { | |
308 | health: health, | |
309 | text: text, | |
310 | title: title, | |
f6710aac | 311 | iconCls: PVE.Utils.get_health_icon(PVE.Utils.map_ceph_health[health]), |
95d0de89 DC |
312 | })); |
313 | ||
314 | if (me.tooltip) { | |
315 | me.tooltip.setHtml(text); | |
316 | } | |
317 | }, | |
318 | ||
319 | listeners: { | |
320 | destroy: function() { | |
f87b8d2f | 321 | let me = this; |
95d0de89 DC |
322 | if (me.tooltip) { |
323 | me.tooltip.destroy(); | |
324 | delete me.tooltip; | |
325 | } | |
326 | }, | |
327 | mouseenter: { | |
328 | element: 'el', | |
329 | fn: function(events, element) { | |
f87b8d2f TL |
330 | let view = this.component; |
331 | if (!view) { | |
95d0de89 DC |
332 | return; |
333 | } | |
f87b8d2f TL |
334 | if (!view.tooltip || view.data.text !== view.tooltip.html) { |
335 | view.tooltip = Ext.create('Ext.tip.ToolTip', { | |
336 | target: view.el, | |
95d0de89 DC |
337 | trackMouse: true, |
338 | dismissDelay: 0, | |
339 | renderTo: Ext.getBody(), | |
f87b8d2f | 340 | html: view.data.text, |
95d0de89 DC |
341 | }); |
342 | } | |
f87b8d2f | 343 | view.tooltip.show(); |
f6710aac | 344 | }, |
95d0de89 DC |
345 | }, |
346 | mouseleave: { | |
347 | element: 'el', | |
348 | fn: function(events, element) { | |
f87b8d2f TL |
349 | let view = this.component; |
350 | if (view.tooltip) { | |
351 | view.tooltip.destroy(); | |
352 | delete view.tooltip; | |
95d0de89 | 353 | } |
f6710aac TL |
354 | }, |
355 | }, | |
356 | }, | |
95d0de89 | 357 | }); |