]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/ceph/Services.js
update shipped appliance info index
[pve-manager.git] / www / manager6 / ceph / Services.js
1 Ext.define('PVE.ceph.Services', {
2 extend: 'Ext.panel.Panel',
3 alias: 'widget.pveCephServices',
4
5 layout: {
6 type: 'hbox',
7 align: 'stretch',
8 },
9
10 bodyPadding: '0 5 20',
11 defaults: {
12 xtype: 'box',
13 style: {
14 'text-align': 'center',
15 },
16 },
17
18 items: [
19 {
20 flex: 1,
21 xtype: 'pveCephServiceList',
22 itemId: 'mons',
23 title: gettext('Monitors'),
24 },
25 {
26 flex: 1,
27 xtype: 'pveCephServiceList',
28 itemId: 'mgrs',
29 title: gettext('Managers'),
30 },
31 {
32 flex: 1,
33 xtype: 'pveCephServiceList',
34 itemId: 'mdss',
35 title: gettext('Meta Data Servers'),
36 },
37 ],
38
39 updateAll: function(metadata, status) {
40 var me = this;
41
42 const healthstates = {
43 'HEALTH_UNKNOWN': 0,
44 'HEALTH_ERR': 1,
45 'HEALTH_WARN': 2,
46 'HEALTH_UPGRADE': 3,
47 'HEALTH_OLD': 4,
48 'HEALTH_OK': 5,
49 };
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";
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;
56 }
57 });
58 var quorummap = status && status.quorum_names ? status.quorum_names : [];
59 let monmessages = {}, mgrmessages = {}, mdsmessages = {};
60 if (status) {
61 if (status.health) {
62 Ext.Object.each(status.health.checks, function(key, value, _obj) {
63 if (!Ext.String.startsWith(key, "MON_")) {
64 return;
65 }
66 for (let i = 0; i < value.detail.length; i++) {
67 let match = value.detail[i].message.match(/mon.([a-zA-Z0-9\-.]+)/);
68 if (!match) {
69 continue;
70 }
71 let monid = match[1];
72 if (!monmessages[monid]) {
73 monmessages[monid] = {
74 worstSeverity: healthstates.HEALTH_OK,
75 messages: [],
76 };
77 }
78
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);
82
83 if (healthstates[value.severity] < monmessages[monid].worstSeverity) {
84 monmessages[monid].worstSeverity = healthstates[value.severity];
85 }
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
104 let checks = {
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;
140 },
141 };
142
143 for (let type of ['mon', 'mgr', 'mds']) {
144 var ids = Object.keys(metadata[type] || {});
145 me[type] = {};
146
147 for (let id of ids) {
148 const [name, host] = id.split('@');
149 let result = {
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,
159 addr: metadata[type][id].addr || metadata[type][id].addrs || Proxmox.Utils.unknownText,
160 };
161
162 result.statuses = [
163 gettext('Host') + ": " + host,
164 gettext('Address') + ": " + result.addr,
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) +
174 gettext('Stopped'),
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
186 if (PVE.Utils.compare_ceph_versions(result.version, maxversion) !== 0) {
187 let host_version = metadata.node[host]?.version?.parts || metadata.version?.[host] || "";
188 if (PVE.Utils.compare_ceph_versions(host_version, maxversion) === 0) {
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) +
194 gettext('A newer version was installed but old version still running, please restart'),
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) +
202 gettext('Other cluster members use a newer version of this service, please upgrade and restart'),
203 );
204 }
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));
220 },
221 });
222
223 Ext.define('PVE.ceph.ServiceList', {
224 extend: 'Ext.container.Container',
225 xtype: 'pveCephServiceList',
226
227 style: {
228 'text-align': 'center',
229 },
230 defaults: {
231 xtype: 'box',
232 style: {
233 'text-align': 'center',
234 },
235 },
236
237 items: [
238 {
239 itemId: 'title',
240 data: {
241 title: '',
242 },
243 tpl: '<h3>{title}</h3>',
244 },
245 ],
246
247 updateAll: function(list) {
248 var me = this;
249 me.suspendLayout = true;
250
251 list.sort((a, b) => a.id > b.id ? 1 : a.id < b.id ? -1 : 0);
252 if (!me.ids) {
253 me.ids = [];
254 }
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);
260 if (!service) {
261 // services and list are sorted, so just insert at i + 1 (first el. is the title)
262 service = me.insert(i + 1, {
263 xtype: 'pveCephServiceWidget',
264 itemId: list[i].id,
265 });
266 me.ids.push(list[i].id);
267 } else {
268 delete pendingRemoval[list[i].id]; // drop exisiting from for-removal
269 }
270 service.updateService(list[i].title, list[i].text, list[i].health);
271 }
272 Object.keys(pendingRemoval).forEach(id => me.remove(id)); // GC
273
274 me.suspendLayout = false;
275 me.updateLayout();
276 },
277
278 initComponent: function() {
279 var me = this;
280 me.callParent();
281 me.getComponent('title').update({
282 title: me.title,
283 });
284 },
285 });
286
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: '',
296 iconCls: PVE.Utils.get_health_icon(),
297 },
298
299 tpl: [
300 '{title}: ',
301 '<i class="fa fa-fw {iconCls}"></i>',
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,
311 iconCls: PVE.Utils.get_health_icon(PVE.Utils.map_ceph_health[health]),
312 }));
313
314 if (me.tooltip) {
315 me.tooltip.setHtml(text);
316 }
317 },
318
319 listeners: {
320 destroy: function() {
321 let me = this;
322 if (me.tooltip) {
323 me.tooltip.destroy();
324 delete me.tooltip;
325 }
326 },
327 mouseenter: {
328 element: 'el',
329 fn: function(events, element) {
330 let view = this.component;
331 if (!view) {
332 return;
333 }
334 if (!view.tooltip || view.data.text !== view.tooltip.html) {
335 view.tooltip = Ext.create('Ext.tip.ToolTip', {
336 target: view.el,
337 trackMouse: true,
338 dismissDelay: 0,
339 renderTo: Ext.getBody(),
340 html: view.data.text,
341 });
342 }
343 view.tooltip.show();
344 },
345 },
346 mouseleave: {
347 element: 'el',
348 fn: function(events, element) {
349 let view = this.component;
350 if (view.tooltip) {
351 view.tooltip.destroy();
352 delete view.tooltip;
353 }
354 },
355 },
356 },
357 });