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