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