]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/tree/ResourceTree.js
show ha error state in tree and resource grid
[pve-manager.git] / www / manager6 / tree / ResourceTree.js
1 /*
2 * Left Treepanel, containing all the ressources we manage in this datacenter: server nodes, server storages, VMs and Containers
3 */
4 Ext.define('PVE.tree.ResourceTree', {
5 extend: 'Ext.tree.TreePanel',
6 alias: ['widget.pveResourceTree'],
7
8 statics: {
9 typeDefaults: {
10 node: {
11 iconCls: 'fa fa-building x-fa-tree',
12 text: gettext('Nodes')
13 },
14 pool: {
15 iconCls: 'fa fa-tags fa-dark x-fa-tree',
16 text: gettext('Resource Pool')
17 },
18 storage: {
19 iconCls: 'fa fa-database fa-dark x-fa-tree',
20 text: gettext('Storage')
21 },
22 qemu: {
23 iconCls: 'fa fa-desktop x-fa-tree',
24 text: gettext('Virtual Machine')
25 },
26 lxc: {
27 //iconCls: 'x-tree-node-lxc',
28 iconCls: 'fa fa-cube x-fa-tree',
29 text: gettext('LXC Container')
30 },
31 template: {
32 iconCls: 'fa fa-file-o fa-dark x-fa-tree-template'
33 },
34 datacenter: {
35 iconCls: 'fa fa-server x-fa-tree-datacenter'
36 }
37 }
38 },
39
40 useArrows: true,
41
42 // private
43 nodeSortFn: function(node1, node2) {
44 var n1 = node1.data;
45 var n2 = node2.data;
46
47 if ((n1.groupbyid && n2.groupbyid) ||
48 !(n1.groupbyid || n2.groupbyid)) {
49
50 var tcmp;
51
52 var v1 = n1.type;
53 var v2 = n2.type;
54
55 if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
56 return tcmp;
57 }
58
59 // numeric compare for VM IDs
60 // sort templates after regular VMs
61 if (v1 === 'qemu' || v1 === 'lxc') {
62 if (n1.template && !n2.template) {
63 return 1;
64 } else if (n2.template && !n1.template) {
65 return -1;
66 }
67 v1 = n1.vmid;
68 v2 = n2.vmid;
69 if ((tcmp = v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)) != 0) {
70 return tcmp;
71 }
72 }
73
74 return n1.text > n2.text ? 1 : (n1.text < n2.text ? -1 : 0);
75 } else if (n1.groupbyid) {
76 return -1;
77 } else if (n2.groupbyid) {
78 return 1;
79 }
80 },
81
82 // private: fast binary search
83 findInsertIndex: function(node, child, start, end) {
84 var me = this;
85
86 var diff = end - start;
87
88 var mid = start + (diff>>1);
89
90 if (diff <= 0) {
91 return start;
92 }
93
94 var res = me.nodeSortFn(child, node.childNodes[mid]);
95 if (res <= 0) {
96 return me.findInsertIndex(node, child, start, mid);
97 } else {
98 return me.findInsertIndex(node, child, mid + 1, end);
99 }
100 },
101
102 setIconCls: function(info) {
103 var me = this;
104
105 var defaults = PVE.tree.ResourceTree.typeDefaults[info.type];
106 if (info.id === 'root') {
107 defaults = PVE.tree.ResourceTree.typeDefaults.datacenter;
108 } else if (info.type === 'type') {
109 defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
110 }
111 if (defaults && defaults.iconCls) {
112 var iconClsAdd = '';
113
114 if (info.running && info.type === 'node') {
115 iconClsAdd = '-online';
116 } else if (info.running) {
117 iconClsAdd = '-running';
118 if (info.status === 'paused') {
119 iconClsAdd = '-paused';
120 }
121 } else if (info.type === 'lxc' || info.type === 'qemu') {
122 iconClsAdd = '-stopped';
123 } else if (info.type === 'node') {
124 iconClsAdd = '-offline';
125 }
126
127 // overwrite any other class
128 if (info.hastate === 'error') {
129 iconClsAdd = '-offline';
130 }
131
132 info.iconCls = defaults.iconCls + iconClsAdd;
133
134 if (info.template) {
135 iconClsAdd = '-template';
136 info.iconCls = PVE.tree.ResourceTree.typeDefaults.template.iconCls + '-' + info.type;
137 }
138
139 }
140 },
141
142 // private
143 addChildSorted: function(node, info) {
144 var me = this;
145
146 me.setIconCls(info);
147
148 var defaults;
149 if (info.groupbyid) {
150 info.text = info.groupbyid;
151 if (info.type === 'type') {
152 defaults = PVE.tree.ResourceTree.typeDefaults[info.groupbyid];
153 if (defaults && defaults.text) {
154 info.text = defaults.text;
155 }
156 }
157 }
158 var child = Ext.create('PVETree', info);
159
160 var cs = node.childNodes;
161 var pos;
162 if (cs) {
163 pos = cs[me.findInsertIndex(node, child, 0, cs.length)];
164 }
165
166 node.insertBefore(child, pos);
167
168 return child;
169 },
170
171 // private
172 groupChild: function(node, info, groups, level) {
173 var me = this;
174
175 var groupby = groups[level];
176 var v = info[groupby];
177
178 if (v) {
179 var group = node.findChild('groupbyid', v);
180 if (!group) {
181 var groupinfo;
182 if (info.type === groupby) {
183 groupinfo = info;
184 } else {
185 groupinfo = {
186 type: groupby,
187 id : groupby + "/" + v
188 };
189 if (groupby !== 'type') {
190 groupinfo[groupby] = v;
191 }
192 }
193 groupinfo.leaf = false;
194 groupinfo.groupbyid = v;
195 group = me.addChildSorted(node, groupinfo);
196 // fixme: remove when EXTJS has fixed those bugs?!
197 group.expand(); group.collapse();
198 }
199 if (info.type === groupby) {
200 return group;
201 }
202 if (group) {
203 return me.groupChild(group, info, groups, level + 1);
204 }
205 }
206
207 return me.addChildSorted(node, info);
208 },
209
210 initComponent : function() {
211 var me = this;
212
213 var rstore = PVE.data.ResourceStore;
214 var sp = Ext.state.Manager.getProvider();
215
216 if (!me.viewFilter) {
217 me.viewFilter = {};
218 }
219
220 var pdata = {
221 dataIndex: {},
222 updateCount: 0
223 };
224
225 var store = Ext.create('Ext.data.TreeStore', {
226 model: 'PVETree',
227 root: {
228 expanded: true,
229 id: 'root',
230 text: gettext('Datacenter')
231 }
232 });
233
234 var stateid = 'rid';
235
236 var updateTree = function() {
237 var tmp;
238
239 // fixme: suspend events ?
240
241 var rootnode = me.store.getRootNode();
242 me.setIconCls(rootnode.data);
243 // remember selected node (and all parents)
244 var sm = me.getSelectionModel();
245
246 var lastsel = sm.getSelection()[0];
247 var parents = [];
248 var p = lastsel;
249 while (p && !!(p = p.parentNode)) {
250 parents.push(p);
251 }
252
253 var index = pdata.dataIndex;
254
255 var groups = me.viewFilter.groups || [];
256 var filterfn = me.viewFilter.filterfn;
257
258 // remove vanished or moved items
259 // update in place changed items
260 var key;
261 for (key in index) {
262 if (index.hasOwnProperty(key)) {
263 var olditem = index[key];
264
265 // getById() use find(), which is slow (ExtJS4 DP5)
266 //var item = rstore.getById(olditem.data.id);
267 var item = rstore.data.get(olditem.data.id);
268
269 var changed = false;
270 var moved = false;
271 if (item) {
272 // test if any grouping attributes changed
273 // this will also catch migrated nodes
274 // in server view
275 var i, len;
276 for (i = 0, len = groups.length; i < len; i++) {
277 var attr = groups[i];
278 if (item.data[attr] != olditem.data[attr]) {
279 //console.log("changed " + attr);
280 moved = true;
281 break;
282 }
283 }
284
285 // explicitely check for node, since
286 // in some views, node is not a grouping
287 // attribute
288 if (!moved && item.data.node !== olditem.data.node) {
289 moved = true;
290 }
291
292 // tree item has been updated
293 if ((item.data.text !== olditem.data.text) ||
294 (item.data.running !== olditem.data.running) ||
295 (item.data.template !== olditem.data.template) ||
296 (item.data.status !== olditem.data.status) ||
297 (item.data.hastate!== olditem.data.hastate)) {
298 //console.log("changed node/text/running " + olditem.data.id);
299 changed = true;
300 }
301
302 // fixme: also test filterfn()?
303 }
304
305 if (changed) {
306 olditem.beginEdit();
307 //console.log("REM UPDATE UID: " + key + " ITEM " + item.data.running);
308 var info = olditem.data;
309 Ext.apply(info, item.data);
310 me.setIconCls(info);
311 olditem.commit();
312 }
313 if ((!item || moved) && olditem.isLeaf()) {
314 //console.log("REM UID: " + key + " ITEM " + olditem.data.id);
315 delete index[key];
316 var parentNode = olditem.parentNode;
317 parentNode.removeChild(olditem, true);
318 }
319 }
320 }
321
322 // add new items
323 rstore.each(function(item) {
324 var olditem = index[item.data.id];
325 if (olditem) {
326 return;
327 }
328
329 if (filterfn && !filterfn(item)) {
330 return;
331 }
332
333 //console.log("ADD UID: " + item.data.id);
334
335 var info = Ext.apply({ leaf: true }, item.data);
336
337 var child = me.groupChild(rootnode, info, groups, 0);
338 if (child) {
339 index[item.data.id] = child;
340 }
341 });
342
343 // select parent node is selection vanished
344 if (lastsel && !rootnode.findChild('id', lastsel.data.id, true)) {
345 lastsel = rootnode;
346 while (!!(p = parents.shift())) {
347 if (!!(tmp = rootnode.findChild('id', p.data.id, true))) {
348 lastsel = tmp;
349 break;
350 }
351 }
352 me.selectById(lastsel.data.id);
353 }
354
355 // on first tree load set the selection from the stateful provider
356 if (!pdata.updateCount) {
357 rootnode.collapse();
358 rootnode.expand();
359 me.applyState(sp.get(stateid));
360 }
361
362 pdata.updateCount++;
363 };
364
365 var statechange = function(sp, key, value) {
366 if (key === stateid) {
367 me.applyState(value);
368 }
369 };
370
371 sp.on('statechange', statechange);
372
373 Ext.apply(me, {
374 allowSelection: true,
375 store: store,
376 viewConfig: {
377 // note: animate cause problems with applyState
378 animate: false
379 },
380 //useArrows: true,
381 //rootVisible: false,
382 //title: 'Resource Tree',
383 listeners: {
384 itemcontextmenu: PVE.Utils.createCmdMenu,
385 destroy: function() {
386 rstore.un("load", updateTree);
387 },
388 beforecellmousedown: function (tree, td, cellIndex, record, tr, rowIndex, ev) {
389 // disable selection when right clicking
390 me.allowSelection = (ev.button !== 2);
391 },
392 beforeselect: function (tree, record, index, eopts) {
393 var allow = me.allowSelection;
394 me.allowSelection = true;
395 return allow;
396 },
397 itemdblclick: PVE.Utils.openTreeConsole
398 },
399 setViewFilter: function(view) {
400 me.viewFilter = view;
401 me.clearTree();
402 updateTree();
403 },
404 clearTree: function() {
405 pdata.updateCount = 0;
406 var rootnode = me.store.getRootNode();
407 rootnode.collapse();
408 rootnode.removeAll();
409 pdata.dataIndex = {};
410 me.getSelectionModel().deselectAll();
411 },
412 selectExpand: function(node) {
413 var sm = me.getSelectionModel();
414 if (!sm.isSelected(node)) {
415 sm.select(node);
416 var cn = node;
417 while (!!(cn = cn.parentNode)) {
418 if (!cn.isExpanded()) {
419 cn.expand();
420 }
421 }
422 me.getView().focusRow(node);
423 }
424 },
425 selectById: function(nodeid) {
426 var rootnode = me.store.getRootNode();
427 var sm = me.getSelectionModel();
428 var node;
429 if (nodeid === 'root') {
430 node = rootnode;
431 } else {
432 node = rootnode.findChild('id', nodeid, true);
433 }
434 if (node) {
435 me.selectExpand(node);
436 }
437 },
438 checkVmMigration: function(record) {
439 if (!(record.data.type === 'qemu' || record.data.type === 'lxc')) {
440 throw "not a vm type";
441 }
442
443 var rootnode = me.store.getRootNode();
444 var node = rootnode.findChild('id', record.data.id, true);
445
446 if (node && node.data.type === record.data.type &&
447 node.data.node !== record.data.node) {
448 // defer select (else we get strange errors)
449 Ext.defer(function() { me.selectExpand(node); }, 100, me);
450 }
451 },
452 applyState : function(state) {
453 var sm = me.getSelectionModel();
454 if (state && state.value) {
455 me.selectById(state.value);
456 } else {
457 sm.deselectAll();
458 }
459 }
460 });
461
462 me.callParent();
463
464 var sm = me.getSelectionModel();
465 sm.on('select', function(sm, n) {
466 sp.set(stateid, { value: n.data.id});
467 });
468
469 rstore.on("load", updateTree);
470 rstore.startUpdate();
471 //rstore.stopUpdate();
472 }
473
474 });